Compare commits
	
		
			73 Commits
		
	
	
		
			nrc-test-m
			...
			pierremtb/
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 34272b872d | |||
| 668e2afb99 | |||
| 548c664db0 | |||
| d3a3f4410c | |||
| 22eb343171 | |||
| f2cfa4d5cf | |||
| 3f1f40eeba | |||
| ff2d161606 | |||
| 210c78029d | |||
| e27840219b | |||
| c943a3f192 | |||
| 6aa588f09f | |||
| 59a6333aad | |||
| 403f1507ae | |||
| eac7b83504 | |||
| 667500d1b9 | |||
| b15aac9f48 | |||
| 54153aa646 | |||
| 943cf21d34 | |||
| 5a6728c45a | |||
| ff2103d493 | |||
| 2dfa8f2176 | |||
| 29ed330326 | |||
| ca2cc825a6 | |||
| 83fe1b7ce0 | |||
| 157b76cc78 | |||
| cf957d880e | |||
| dfc3d19677 | |||
| dd370a9365 | |||
| 2274d6459c | |||
| 32ce857119 | |||
| 88b51da417 | |||
| 30d365aeb3 | |||
| 7af62399ac | |||
| 441d957228 | |||
| 9e57034873 | |||
| eb96d6539c | |||
| 513c76ecc8 | |||
| 51d9449280 | |||
| 6366bc4766 | |||
| 7a21918223 | |||
| 8072f1db63 | |||
| 18e1855fa9 | |||
| 7be53c7d4a | |||
| 2bf20988ef | |||
| 1495cc6d18 | |||
| f876e6ca3c | |||
| 60a0c811ab | |||
| cab0c1e6a1 | |||
| 417d720b22 | |||
| 77293952c0 | |||
| ea3d604b73 | |||
| 023a659491 | |||
| dd3a2b14f9 | |||
| 424b409cc1 | |||
| 82a58e69c2 | |||
| 776b420031 | |||
| 1087d4223b | |||
| 089d6df889 | |||
| efb067af58 | |||
| 2aa27eab01 | |||
| 9c47ac5b57 | |||
| 5ae1aecd74 | |||
| 68ae7e98f9 | |||
| 56771d561a | |||
| f09411817c | |||
| bed7ae3b8b | |||
| c43510732c | |||
| 51f0b669a4 | |||
| 3cbedcd3e7 | |||
| 5d2fa43150 | |||
| ec49b0752e | |||
| 3b171fb881 | 
							
								
								
									
										32
									
								
								.github/workflows/build-apps.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -362,6 +362,17 @@ jobs: | |||||||
|       - name: List artifacts |       - name: List artifacts | ||||||
|         run: "ls -R out" |         run: "ls -R out" | ||||||
|  |  | ||||||
|  |       - name: Set more complete nightly release notes | ||||||
|  |         if: ${{ env.IS_NIGHTLY == 'true' }} | ||||||
|  |         run: | | ||||||
|  |           # Note: preferred going this way instead of a full clone in the checkout step, | ||||||
|  |           # see https://github.com/actions/checkout/issues/1471 | ||||||
|  |           git fetch --prune --unshallow --tags | ||||||
|  |           export TAG="nightly-${VERSION}" | ||||||
|  |           export PREVIOUS_TAG=$(git describe --tags --match="nightly-v[0-9]*" --abbrev=0) | ||||||
|  |           export NOTES=$(./scripts/get-nightly-changelog.sh) | ||||||
|  |           yarn files:set-notes | ||||||
|  |  | ||||||
|       - name: Authenticate to Google Cloud |       - name: Authenticate to Google Cloud | ||||||
|         if: ${{ env.IS_NIGHTLY == 'true' }} |         if: ${{ env.IS_NIGHTLY == 'true' }} | ||||||
|         uses: 'google-github-actions/auth@v2.1.7' |         uses: 'google-github-actions/auth@v2.1.7' | ||||||
| @ -383,12 +394,17 @@ jobs: | |||||||
|           parent: false |           parent: false | ||||||
|           destination: 'dl.kittycad.io/releases/modeling-app/nightly' |           destination: 'dl.kittycad.io/releases/modeling-app/nightly' | ||||||
|  |  | ||||||
|       - name: Create draft release |       - name: Invalidate bucket cache on latest*.yml and last_download.json files | ||||||
|         uses: softprops/action-gh-release@v2 |         if: ${{ env.IS_NIGHTLY == 'true' }} | ||||||
|         if: ${{ env.IS_RELEASE == 'true' }} |         run: yarn files:invalidate-bucket:nightly | ||||||
|  |  | ||||||
|  |       - name: Tag nightly commit | ||||||
|  |         if: ${{ env.IS_NIGHTLY == 'true' }} | ||||||
|  |         uses: actions/github-script@v7 | ||||||
|         with: |         with: | ||||||
|           name: ${{ env.VERSION }} |           script: | | ||||||
|           tag_name: ${{ env.VERSION }} |             const { VERSION } = process.env           | ||||||
|           draft: true |             const { owner, repo } = context.repo | ||||||
|           generate_release_notes: true |             const { sha } = context | ||||||
|           files: 'out/Zoo*' |             const ref = `refs/tags/nightly-${VERSION}` | ||||||
|  |             github.rest.git.createRef({ owner, repo, sha, ref }) | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								.github/workflows/e2e-tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -68,7 +68,7 @@ jobs: | |||||||
|     - name: Download Wasm Cache |     - name: Download Wasm Cache | ||||||
|       id: download-wasm |       id: download-wasm | ||||||
|       if: needs.check-rust-changes.outputs.rust-changed == 'false' |       if: needs.check-rust-changes.outputs.rust-changed == 'false' | ||||||
|       uses: dawidd6/action-download-artifact@v6 |       uses: dawidd6/action-download-artifact@v7 | ||||||
|       continue-on-error: true |       continue-on-error: true | ||||||
|       with: |       with: | ||||||
|         github_token: ${{secrets.GITHUB_TOKEN}} |         github_token: ${{secrets.GITHUB_TOKEN}} | ||||||
| @ -255,7 +255,7 @@ jobs: | |||||||
|     - name: Download Wasm Cache |     - name: Download Wasm Cache | ||||||
|       id: download-wasm |       id: download-wasm | ||||||
|       if: needs.check-rust-changes.outputs.rust-changed == 'false' |       if: needs.check-rust-changes.outputs.rust-changed == 'false' | ||||||
|       uses: dawidd6/action-download-artifact@v6 |       uses: dawidd6/action-download-artifact@v7 | ||||||
|       continue-on-error: true |       continue-on-error: true | ||||||
|       with: |       with: | ||||||
|         github_token: ${{secrets.GITHUB_TOKEN}} |         github_token: ${{secrets.GITHUB_TOKEN}} | ||||||
|  | |||||||
							
								
								
									
										12
									
								
								.github/workflows/publish-apps-release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -126,11 +126,13 @@ jobs: | |||||||
|           destination: 'dl.kittycad.io/releases/modeling-app' |           destination: 'dl.kittycad.io/releases/modeling-app' | ||||||
|  |  | ||||||
|       - name: Invalidate bucket cache on latest*.yml and last_download.json files |       - name: Invalidate bucket cache on latest*.yml and last_download.json files | ||||||
|         run: | |         run: yarn files:invalidate-bucket | ||||||
|           gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/last_download.json" --async |  | ||||||
|           gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest-linux-arm64.yml" --async |       - name: Upload release files to Github | ||||||
|           gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest-mac.yml" --async |         if: ${{ github.event_name == 'release' }} | ||||||
|           gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest.yml" --async |         uses: softprops/action-gh-release@v2 | ||||||
|  |         with: | ||||||
|  |           files: 'out/Zoo*' | ||||||
|  |  | ||||||
|  |  | ||||||
|   announce_release: |   announce_release: | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -61,6 +61,7 @@ Mac_App_Distribution.provisionprofile | |||||||
| *.tsbuildinfo | *.tsbuildinfo | ||||||
| src/wasm-lib/pkg | src/wasm-lib/pkg | ||||||
|  |  | ||||||
|  | .eslintcache | ||||||
| venv | venv | ||||||
| .vite/ | .vite/ | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										43
									
								
								INSTALL.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,43 @@ | |||||||
|  | # Setting Up Zoo Modeling App | ||||||
|  |  | ||||||
|  | Compared to other CAD software, getting Zoo Modeling App up and running is quick and straightforward across platforms. It's about 100MB to download and is quick to install. | ||||||
|  |  | ||||||
|  | ## Windows | ||||||
|  |  | ||||||
|  | 1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for Windows and for your processor type. | ||||||
|  |  | ||||||
|  | 2. Once downloaded, run the installer `Zoo Modeling App-{version}-{arch}-win.exe` which should take a few seconds. | ||||||
|  |  | ||||||
|  | 3. The installation happens at `C:\Program Files\Zoo Modeling App`. A shortcut in the start menu is also created so you can run the app easily by clicking on it. | ||||||
|  |  | ||||||
|  | ## macOS | ||||||
|  |  | ||||||
|  | 1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for macOS and for your processor type. | ||||||
|  |  | ||||||
|  | 2. Once downloaded, open the disk image `Zoo Modeling App-{version}-{arch}-mac.dmg` and drag the applications to your `Applications` directory. | ||||||
|  |  | ||||||
|  | 3. You can then open your `Applications` directory and double-click on `Zoo Modeling App` to open. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## Linux  | ||||||
|  |  | ||||||
|  | 1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for Linux and for your processor type. | ||||||
|  |  | ||||||
|  | 2. Install the dependencies needed to run the [AppImage format](https://appimage.org/). | ||||||
|  |     -  On Ubuntu, install the FUSE library with these commands in a terminal. | ||||||
|  |        ```bash | ||||||
|  |        sudo apt update | ||||||
|  |        sudo apt install libfuse2 | ||||||
|  |        ``` | ||||||
|  |     - Optionally, follow [these steps](https://github.com/probonopd/go-appimage/blob/master/src/appimaged/README.md#initial-setup) to install `appimaged`. It is a daemon that makes interacting with AppImage files more seamless.  | ||||||
|  |     - Once installed, copy the downloaded `Zoo Modeling App-{version}-{arch}-linux.AppImage` to the directory of your choice, for instance `~/Applications`. | ||||||
|  |  | ||||||
|  |    - `appimaged` should automatically find it and make it executable. If not, run: | ||||||
|  |      ```bash | ||||||
|  |      chmod a+x ~/Applications/Zoo\ Modeling\ App-{version}-{arch}-linux.AppImage | ||||||
|  |      ``` | ||||||
|  |  | ||||||
|  | 3. You can double-click on the AppImage to run it, or in a terminal with this command: | ||||||
|  |    ```bash | ||||||
|  |     ~/Applications/Zoo\ Modeling\ App-{version}-{arch}-linux.AppImage | ||||||
|  |    ``` | ||||||
							
								
								
									
										16
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @ -99,7 +99,7 @@ yarn tron:start | |||||||
|  |  | ||||||
| This will start the application and hot-reload on changes. | This will start the application and hot-reload on changes. | ||||||
|  |  | ||||||
| Devtools can be opened with the usual Cmd/Ctrl-Shift-I. | Devtools can be opened with the usual Cmd-Opt-I (Mac) or Ctrl-Shift-I (Linux and Windows). | ||||||
|  |  | ||||||
| To build, run `yarn tron:package`. | To build, run `yarn tron:package`. | ||||||
|  |  | ||||||
| @ -136,7 +136,7 @@ https://github.com/KittyCAD/modeling-app/issues/new | |||||||
|  |  | ||||||
| #### 2. Push a new tag | #### 2. Push a new tag | ||||||
|  |  | ||||||
| Create a new tag and push it to the repo (eg. `v0.28.0` for `$VERSION`) | Create a new tag and push it to the repo. The `semantic-release.sh` script will automatically bump the minor part, which we use the most. For instance going from `v0.27.0` to `v0.28.0`. | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
| VERSION=$(./scripts/semantic-release.sh) | VERSION=$(./scripts/semantic-release.sh) | ||||||
| @ -146,16 +146,14 @@ git push origin --tags | |||||||
|  |  | ||||||
| This will trigger the `build-apps` workflow, set the version, build & sign the apps, and generate release files as well as updater-test artifacts. | This will trigger the `build-apps` workflow, set the version, build & sign the apps, and generate release files as well as updater-test artifacts. | ||||||
|  |  | ||||||
| Once the workflow succeeds, a draft release will be created at https://github.com/KittyCAD/modeling-app/releases. | The workflow should be listed right away [in this list](https://github.com/KittyCAD/modeling-app/actions/workflows/build-apps.yml?query=event%3Apush)). | ||||||
|  |  | ||||||
| #### 3. Manually test artifacts from the Cut Release PR | #### 3. Manually test artifacts | ||||||
|  |  | ||||||
| ##### Release builds | ##### Release builds | ||||||
|  |  | ||||||
| The release builds can be found under the `out-{arch}-{platform}` zip files, at the very bottom of the `build-apps` summary page for the workflow (triggered by the tag in 2.). | The release builds can be found under the `out-{arch}-{platform}` zip files, at the very bottom of the `build-apps` summary page for the workflow (triggered by the tag in 2.). | ||||||
|  |  | ||||||
| Alternatively, the draft release will also include these builds. |  | ||||||
|  |  | ||||||
| Manually test against this [list](https://github.com/KittyCAD/modeling-app/issues/3588) across Windows, MacOS, Linux and posting results as comments in the issue. | Manually test against this [list](https://github.com/KittyCAD/modeling-app/issues/3588) across Windows, MacOS, Linux and posting results as comments in the issue. | ||||||
|  |  | ||||||
| ##### Updater-test builds | ##### Updater-test builds | ||||||
| @ -178,9 +176,11 @@ If the prompt doesn't show up, start the app in command line to grab the electro | |||||||
|  |  | ||||||
| #### 4. Publish the release | #### 4. Publish the release | ||||||
|  |  | ||||||
| Head over to https://github.com/KittyCAD/modeling-app/releases, paste in the changelog discussed in the issue, and publish the draft release created by the `build-apps` workflow from step 2. | Head over to https://github.com/KittyCAD/modeling-app/releases/new, pick the newly created tag and type it in the _Release title_ field as well. | ||||||
|  |  | ||||||
| A new Action kicks in at https://github.com/KittyCAD/modeling-app/actions, which can be found under `release` event filter. On success, the files will be uploaded to the public bucket and the announcement on Discord will be sent.  | Hit _Generate release notes_ as a starting point to discuss the changelog in the issue. Once done, make sure _Set as the latest release_ is checked, and hit _Publish release_.  | ||||||
|  |  | ||||||
|  | A new `publish-apps-release` will kick in and you should be able to find it [here](https://github.com/KittyCAD/modeling-app/actions?query=event%3Arelease). On success, the files will be uploaded to the public bucket as well as to the GitHub release, and the announcement on Discord will be sent.  | ||||||
|  |  | ||||||
| #### 5. Close the issue | #### 5. Close the issue | ||||||
|  |  | ||||||
|  | |||||||
| Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 119 KiB | 
| Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 259 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icon.ico
									
									
									
									
									
								
							
							
						
						| Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 114 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icon.png
									
									
									
									
									
								
							
							
						
						| Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 113 KiB | 
| @ -45,7 +45,7 @@ circles = map([1..3], drawCircle) | |||||||
| ```js | ```js | ||||||
| r = 10 // radius | r = 10 // radius | ||||||
| // Call `map`, using an anonymous function instead of a named one. | // Call `map`, using an anonymous function instead of a named one. | ||||||
| circles = map([1..3], (id) { | circles = map([1..3], fn(id) { | ||||||
|   return startSketchOn("XY") |   return startSketchOn("XY") | ||||||
|     |> circle({ center = [id * 2 * r, 0], radius = r }, %) |     |> circle({ center = [id * 2 * r, 0], radius = r }, %) | ||||||
| }) | }) | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ to other modules. | |||||||
|  |  | ||||||
| ``` | ``` | ||||||
| // util.kcl | // util.kcl | ||||||
| export fn increment = (x) => { | export fn increment(x) { | ||||||
|   return x + 1 |   return x + 1 | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| @ -37,11 +37,11 @@ Multiple functions can be exported in a file. | |||||||
|  |  | ||||||
| ``` | ``` | ||||||
| // util.kcl | // util.kcl | ||||||
| export fn increment = (x) => { | export fn increment(x) { | ||||||
|   return x + 1 |   return x + 1 | ||||||
| } | } | ||||||
|  |  | ||||||
| export fn decrement = (x) => { | export fn decrement(x) { | ||||||
|   return x - 1 |   return x - 1 | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
|  | |||||||
| @ -61,7 +61,7 @@ assertEqual(sum([1, 2, 3]), 6, 0.00001, "1 + 2 + 3 summed is 6") | |||||||
| // an anonymous `add` function as its parameter, instead of declaring a | // an anonymous `add` function as its parameter, instead of declaring a | ||||||
| // named function outside. | // named function outside. | ||||||
| arr = [1, 2, 3] | arr = [1, 2, 3] | ||||||
| sum = reduce(arr, 0, (i, result_so_far) { | sum = reduce(arr, 0, fn(i, result_so_far) { | ||||||
|   return i + result_so_far |   return i + result_so_far | ||||||
| }) | }) | ||||||
|  |  | ||||||
| @ -84,7 +84,7 @@ fn decagon(radius) { | |||||||
|   // Use a `reduce` to draw the remaining decagon sides. |   // Use a `reduce` to draw the remaining decagon sides. | ||||||
|   // For each number in the array 1..10, run the given function, |   // For each number in the array 1..10, run the given function, | ||||||
|   // which takes a partially-sketched decagon and adds one more edge to it. |   // which takes a partially-sketched decagon and adds one more edge to it. | ||||||
|   fullDecagon = reduce([1..10], startOfDecagonSketch, (i, partialDecagon) { |   fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) { | ||||||
|     // Draw one edge of the decagon. |     // Draw one edge of the decagon. | ||||||
|     x = cos(stepAngle * i) * radius |     x = cos(stepAngle * i) * radius | ||||||
|     y = sin(stepAngle * i) * radius |     y = sin(stepAngle * i) * radius | ||||||
|  | |||||||
							
								
								
									
										20910
									
								
								docs/kcl/std.json
									
									
									
									
									
								
							
							
						
						| @ -54,7 +54,7 @@ We also have support for defining your own functions. Functions can take in any | |||||||
| type of argument. Below is an example of the syntax: | type of argument. Below is an example of the syntax: | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
| fn myFn = (x) => { | fn myFn(x) { | ||||||
|   return x |   return x | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| @ -118,7 +118,7 @@ use the tag `rectangleSegmentA001` in any function or expression in the file. | |||||||
| However if the code was written like this: | However if the code was written like this: | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
| fn rect = (origin) => { | fn rect(origin) { | ||||||
|   return startSketchOn('XZ') |   return startSketchOn('XZ') | ||||||
|     |> startProfileAt(origin, %) |     |> startProfileAt(origin, %) | ||||||
|     |> angledLine([0, 191.26], %, $rectangleSegmentA001) |     |> angledLine([0, 191.26], %, $rectangleSegmentA001) | ||||||
| @ -146,7 +146,7 @@ Tags are accessible through the sketch group they are declared in. | |||||||
| For example the following code works. | For example the following code works. | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
| fn rect = (origin) => { | fn rect(origin) { | ||||||
|   return startSketchOn('XZ') |   return startSketchOn('XZ') | ||||||
|     |> startProfileAt(origin, %) |     |> startProfileAt(origin, %) | ||||||
|     |> angledLine([0, 191.26], %, $rectangleSegmentA001) |     |> angledLine([0, 191.26], %, $rectangleSegmentA001) | ||||||
|  | |||||||
| @ -1,161 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "BinaryOperator" |  | ||||||
| excerpt: "" |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **This schema accepts exactly one of the following:** |  | ||||||
|  |  | ||||||
| Add two numbers. |  | ||||||
|  |  | ||||||
| **enum:** `+` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
| Subtract two numbers. |  | ||||||
|  |  | ||||||
| **enum:** `-` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
| Multiply two numbers. |  | ||||||
|  |  | ||||||
| **enum:** `*` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
| Divide two numbers. |  | ||||||
|  |  | ||||||
| **enum:** `/` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
| Modulo two numbers. |  | ||||||
|  |  | ||||||
| **enum:** `%` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
| Raise a number to a power. |  | ||||||
|  |  | ||||||
| **enum:** `^` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
| Are two numbers equal? |  | ||||||
|  |  | ||||||
| **enum:** `==` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
| Are two numbers not equal? |  | ||||||
|  |  | ||||||
| **enum:** `!=` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
| Is left greater than right |  | ||||||
|  |  | ||||||
| **enum:** `>` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
| Is left greater than or equal to right |  | ||||||
|  |  | ||||||
| **enum:** `>=` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
| Is left less than right |  | ||||||
|  |  | ||||||
| **enum:** `<` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
| Is left less than or equal to right |  | ||||||
|  |  | ||||||
| **enum:** `<=` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,160 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "BinaryPart" |  | ||||||
| excerpt: "" |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **This schema accepts exactly one of the following:** |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `Literal`|  | No | |  | ||||||
| | `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)|  | No | |  | ||||||
| | `raw` |`string`|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)|  | No | |  | ||||||
| | `name` |`string`|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `BinaryExpression`|  | No | |  | ||||||
| | `operator` |[`BinaryOperator`](/docs/kcl/types/BinaryOperator)|  | No | |  | ||||||
| | `left` |[`BinaryPart`](/docs/kcl/types/BinaryPart)|  | No | |  | ||||||
| | `right` |[`BinaryPart`](/docs/kcl/types/BinaryPart)|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `CallExpression`|  | No | |  | ||||||
| | `callee` |[`Identifier`](/docs/kcl/types/Identifier)|  | No | |  | ||||||
| | `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `UnaryExpression`|  | No | |  | ||||||
| | `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)|  | No | |  | ||||||
| | `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `MemberExpression`|  | No | |  | ||||||
| | `object` |[`MemberObject`](/docs/kcl/types/MemberObject)|  | No | |  | ||||||
| | `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)|  | No | |  | ||||||
| | `computed` |`boolean`|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `IfExpression`|  | No | |  | ||||||
| | `cond` |[`Expr`](/docs/kcl/types/Expr)|  | No | |  | ||||||
| | `then_val` |[`Program`](/docs/kcl/types/Program)|  | No | |  | ||||||
| | `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`|  | No | |  | ||||||
| | `final_else` |[`Program`](/docs/kcl/types/Program)|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,97 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "BodyItem" |  | ||||||
| excerpt: "" |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **This schema accepts exactly one of the following:** |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `ImportStatement`|  | No | |  | ||||||
| | `items` |`[` [`ImportItem`](/docs/kcl/types/ImportItem) `]`|  | No | |  | ||||||
| | `path` |`string`|  | No | |  | ||||||
| | `raw_path` |`string`|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `ExpressionStatement`|  | No | |  | ||||||
| | `expression` |[`Expr`](/docs/kcl/types/Expr)|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `VariableDeclaration`|  | No | |  | ||||||
| | `declarations` |`[` [`VariableDeclarator`](/docs/kcl/types/VariableDeclarator) `]`|  | No | |  | ||||||
| | `visibility` |[`ItemVisibility`](/docs/kcl/types/ItemVisibility)|  | No | |  | ||||||
| | `kind` |[`VariableKind`](/docs/kcl/types/VariableKind)|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `ReturnStatement`|  | No | |  | ||||||
| | `argument` |[`Expr`](/docs/kcl/types/Expr)|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,41 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "CommentStyle" |  | ||||||
| excerpt: "" |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **This schema accepts exactly one of the following:** |  | ||||||
|  |  | ||||||
| Like // foo |  | ||||||
|  |  | ||||||
| **enum:** `line` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
| Like /* foo */ |  | ||||||
|  |  | ||||||
| **enum:** `block` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,24 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "ElseIf" |  | ||||||
| excerpt: "" |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `cond` |[`Expr`](/docs/kcl/types/Expr)|  | No | |  | ||||||
| | `then_val` |[`Program`](/docs/kcl/types/Program)|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,16 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "EnvironmentRef" |  | ||||||
| excerpt: "An index pointing to an environment." |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| An index pointing to an environment. |  | ||||||
|  |  | ||||||
| **Type:** `integer` (`uint`) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,317 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "Expr" |  | ||||||
| excerpt: "An expression can be evaluated to yield a single KCL value." |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| An expression can be evaluated to yield a single KCL value. |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **This schema accepts exactly one of the following:** |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `Literal`|  | No | |  | ||||||
| | `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| An expression can be evaluated to yield a single KCL value. | No | |  | ||||||
| | `raw` |`string`|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)|  | No | |  | ||||||
| | `name` |`string`|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: [`TagDeclarator`](/docs/kcl/types#tag-declaration)|  | No | |  | ||||||
| | `value` |`string`|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `BinaryExpression`|  | No | |  | ||||||
| | `operator` |[`BinaryOperator`](/docs/kcl/types/BinaryOperator)| An expression can be evaluated to yield a single KCL value. | No | |  | ||||||
| | `left` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No | |  | ||||||
| | `right` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: [`FunctionExpression`](/docs/kcl/types/FunctionExpression)|  | No | |  | ||||||
| | `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`|  | No | |  | ||||||
| | `body` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `CallExpression`|  | No | |  | ||||||
| | `callee` |[`Identifier`](/docs/kcl/types/Identifier)| An expression can be evaluated to yield a single KCL value. | No | |  | ||||||
| | `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `PipeExpression`|  | No | |  | ||||||
| | `body` |`[` [`Expr`](/docs/kcl/types/Expr) `]`|  | No | |  | ||||||
| | `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `PipeSubstitution`|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `ArrayExpression`|  | No | |  | ||||||
| | `elements` |`[` [`Expr`](/docs/kcl/types/Expr) `]`|  | No | |  | ||||||
| | `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `ArrayRangeExpression`|  | No | |  | ||||||
| | `startElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No | |  | ||||||
| | `endElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No | |  | ||||||
| | `endInclusive` |`boolean`| Is the `end_element` included in the range? | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `ObjectExpression`|  | No | |  | ||||||
| | `properties` |`[` [`ObjectProperty`](/docs/kcl/types/ObjectProperty) `]`|  | No | |  | ||||||
| | `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `MemberExpression`|  | No | |  | ||||||
| | `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| An expression can be evaluated to yield a single KCL value. | No | |  | ||||||
| | `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| An expression can be evaluated to yield a single KCL value. | No | |  | ||||||
| | `computed` |`boolean`|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `UnaryExpression`|  | No | |  | ||||||
| | `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)| An expression can be evaluated to yield a single KCL value. | No | |  | ||||||
| | `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `IfExpression`|  | No | |  | ||||||
| | `cond` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No | |  | ||||||
| | `then_val` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No | |  | ||||||
| | `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`|  | No | |  | ||||||
| | `final_else` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
| KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application). |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `None`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,24 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "FunctionExpression" |  | ||||||
| excerpt: "" |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`|  | No | |  | ||||||
| | `body` |[`Program`](/docs/kcl/types/Program)|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,23 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "Identifier" |  | ||||||
| excerpt: "" |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `name` |`string`|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,24 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "ImportItem" |  | ||||||
| excerpt: "" |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `name` |[`Identifier`](/docs/kcl/types/Identifier)| Name of the item to import. | No | |  | ||||||
| | `alias` |[`Identifier`](/docs/kcl/types/Identifier)| Rename the item using an identifier after "as". | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,16 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "ItemVisibility" |  | ||||||
| excerpt: "" |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **enum:** `default`, `export` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -317,7 +317,6 @@ Data for an imported geometry. | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `Function`|  | No | | | `type` |enum: `Function`|  | No | | ||||||
| | `expression` |[`FunctionExpression`](/docs/kcl/types/FunctionExpression)| Any KCL value. | No | |  | ||||||
| | `memory` |[`ProgramMemory`](/docs/kcl/types/ProgramMemory)| Any KCL value. | No | | | `memory` |[`ProgramMemory`](/docs/kcl/types/ProgramMemory)| Any KCL value. | No | | ||||||
| | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`|  | No | | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`|  | No | | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,56 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "LiteralIdentifier" |  | ||||||
| excerpt: "" |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **This schema accepts exactly one of the following:** |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)|  | No | |  | ||||||
| | `name` |`string`|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `Literal`|  | No | |  | ||||||
| | `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)|  | No | |  | ||||||
| | `raw` |`string`|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,47 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "LiteralValue" |  | ||||||
| excerpt: "" |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **This schema accepts any of the following:** |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **Type:** `number` (`double`) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `string` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `boolean` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,57 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "MemberObject" |  | ||||||
| excerpt: "" |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **This schema accepts exactly one of the following:** |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `MemberExpression`|  | No | |  | ||||||
| | `object` |[`MemberObject`](/docs/kcl/types/MemberObject)|  | No | |  | ||||||
| | `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)|  | No | |  | ||||||
| | `computed` |`boolean`|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)|  | No | |  | ||||||
| | `name` |`string`|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,22 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "NonCodeMeta" |  | ||||||
| excerpt: "" |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `nonCodeNodes` |`object`|  | No | |  | ||||||
| | `startNodes` |`[` [`NonCodeNode`](/docs/kcl/types/NonCodeNode) `]`|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,23 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "NonCodeNode" |  | ||||||
| excerpt: "" |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `value` |[`NonCodeValue`](/docs/kcl/types/NonCodeValue)|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,86 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "NonCodeValue" |  | ||||||
| excerpt: "" |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **This schema accepts exactly one of the following:** |  | ||||||
|  |  | ||||||
| An inline comment. Here are examples: `1 + 1 // This is an inline comment`. `1 + 1 /* Here's another */`. |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `inlineComment`|  | No | |  | ||||||
| | `value` |`string`|  | No | |  | ||||||
| | `style` |[`CommentStyle`](/docs/kcl/types/CommentStyle)|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
| A block comment. An example of this is the following: ```python,no_run /* This is a block comment */ 1 + 1 ``` Now this is important. The block comment is attached to the next line. This is always the case. Also the block comment doesn't have a new line above it. If it did it would be a `NewLineBlockComment`. |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `blockComment`|  | No | |  | ||||||
| | `value` |`string`|  | No | |  | ||||||
| | `style` |[`CommentStyle`](/docs/kcl/types/CommentStyle)|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
| A block comment that has a new line above it. The user explicitly added a new line above the block comment. |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `newLineBlockComment`|  | No | |  | ||||||
| | `value` |`string`|  | No | |  | ||||||
| | `style` |[`CommentStyle`](/docs/kcl/types/CommentStyle)|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `newLine`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,24 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "ObjectProperty" |  | ||||||
| excerpt: "" |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `key` |[`Identifier`](/docs/kcl/types/Identifier)|  | No | |  | ||||||
| | `value` |[`Expr`](/docs/kcl/types/Expr)|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,23 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "Parameter" |  | ||||||
| excerpt: "Parameter of a KCL function." |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| Parameter of a KCL function. |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `identifier` |[`Identifier`](/docs/kcl/types/Identifier)| The parameter's label or name. | No | |  | ||||||
| | `optional` |`boolean`| Is the parameter optional? | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,26 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "Program" |  | ||||||
| excerpt: "A KCL program top level, or function body." |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| A KCL program top level, or function body. |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `body` |`[` [`BodyItem`](/docs/kcl/types/BodyItem) `]`|  | No | |  | ||||||
| | `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| A KCL program top level, or function body. | No | |  | ||||||
| | `shebang` |[`Shebang`](/docs/kcl/types/Shebang)|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,23 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "Shebang" |  | ||||||
| excerpt: "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```" |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ``` |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `content` |`string`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,15 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "Uint" |  | ||||||
| excerpt: "" |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **Type:** `integer` (`uint32`) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,41 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "UnaryOperator" |  | ||||||
| excerpt: "" |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **This schema accepts exactly one of the following:** |  | ||||||
|  |  | ||||||
| Negate a number. |  | ||||||
|  |  | ||||||
| **enum:** `-` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
| Negate a boolean. |  | ||||||
|  |  | ||||||
| **enum:** `!` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,24 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "VariableDeclarator" |  | ||||||
| excerpt: "" |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `id` |[`Identifier`](/docs/kcl/types/Identifier)| The identifier of the variable. | No | |  | ||||||
| | `init` |[`Expr`](/docs/kcl/types/Expr)| The value of the variable. | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
| | `start` |`integer`|  | No | |  | ||||||
| | `end` |`integer`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,41 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "VariableKind" |  | ||||||
| excerpt: "" |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **This schema accepts exactly one of the following:** |  | ||||||
|  |  | ||||||
| Declare a named constant. |  | ||||||
|  |  | ||||||
| **enum:** `const` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
| Declare a function. |  | ||||||
|  |  | ||||||
| **enum:** `fn` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -458,8 +458,8 @@ test.describe('Editor tests', () => { | |||||||
|  |  | ||||||
|     /* add the following code to the editor ($ error is not a valid line) |     /* add the following code to the editor ($ error is not a valid line) | ||||||
|       $ error |       $ error | ||||||
|       const topAng = 30 |       topAng = 30 | ||||||
|       const bottomAng = 25 |       bottomAng = 25 | ||||||
|      */ |      */ | ||||||
|     await u.codeLocator.click() |     await u.codeLocator.click() | ||||||
|     await page.keyboard.type('$ error') |     await page.keyboard.type('$ error') | ||||||
| @ -474,12 +474,14 @@ test.describe('Editor tests', () => { | |||||||
|     await page.keyboard.type('bottomAng = 25') |     await page.keyboard.type('bottomAng = 25') | ||||||
|     await page.keyboard.press('Enter') |     await page.keyboard.press('Enter') | ||||||
|  |  | ||||||
|     // error in guter |     // error in gutter | ||||||
|     await expect(page.locator('.cm-lint-marker-error')).toBeVisible() |     await expect(page.locator('.cm-lint-marker-error')).toBeVisible() | ||||||
|  |  | ||||||
|     // error text on hover |     // error text on hover | ||||||
|     await page.hover('.cm-lint-marker-error') |     await page.hover('.cm-lint-marker-error') | ||||||
|     await expect(page.getByText('Unexpected token: $').first()).toBeVisible() |     await expect( | ||||||
|  |       page.getByText('Tag names must not be empty').first() | ||||||
|  |     ).toBeVisible() | ||||||
|  |  | ||||||
|     // select the line that's causing the error and delete it |     // select the line that's causing the error and delete it | ||||||
|     await page.getByText('$ error').click() |     await page.getByText('$ error').click() | ||||||
| @ -518,7 +520,10 @@ test.describe('Editor tests', () => { | |||||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() |     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('error with 2 source ranges gets 2 diagnostics', async ({ page }) => { |   // TODO currently multiple source ranges are not supported | ||||||
|  |   test.skip('error with 2 source ranges gets 2 diagnostics', async ({ | ||||||
|  |     page, | ||||||
|  |   }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
|       localStorage.setItem( |       localStorage.setItem( | ||||||
|  | |||||||
| @ -45,7 +45,6 @@ test.describe('integrations tests', () => { | |||||||
|             { |             { | ||||||
|               title: 'test-sample', |               title: 'test-sample', | ||||||
|               fileCount: 1, |               fileCount: 1, | ||||||
|               folderCount: 1, |  | ||||||
|             }, |             }, | ||||||
|           ], |           ], | ||||||
|           sortBy: 'last-modified-desc', |           sortBy: 'last-modified-desc', | ||||||
| @ -233,7 +232,6 @@ test.describe('when using the file tree to', () => { | |||||||
|             { |             { | ||||||
|               title: projectName, |               title: projectName, | ||||||
|               fileCount: 2, |               fileCount: 2, | ||||||
|               folderCount: 2, // TODO: This is a pre-existing bug, there are no folders within the project |  | ||||||
|             }, |             }, | ||||||
|           ], |           ], | ||||||
|           sortBy: 'last-modified-desc', |           sortBy: 'last-modified-desc', | ||||||
|  | |||||||
| @ -4,7 +4,6 @@ import { expect } from '@playwright/test' | |||||||
| interface ProjectCardState { | interface ProjectCardState { | ||||||
|   title: string |   title: string | ||||||
|   fileCount: number |   fileCount: number | ||||||
|   folderCount: number |  | ||||||
| } | } | ||||||
|  |  | ||||||
| interface HomePageState { | interface HomePageState { | ||||||
| @ -61,15 +60,13 @@ export class HomePageFixture { | |||||||
|     const projectCards = await this.projectCard.all() |     const projectCards = await this.projectCard.all() | ||||||
|     const projectCardStates: Array<ProjectCardState> = [] |     const projectCardStates: Array<ProjectCardState> = [] | ||||||
|     for (const projectCard of projectCards) { |     for (const projectCard of projectCards) { | ||||||
|       const [title, fileCount, folderCount] = await Promise.all([ |       const [title, fileCount] = await Promise.all([ | ||||||
|         (await projectCard.locator(this.projectCardTitle).textContent()) || '', |         (await projectCard.locator(this.projectCardTitle).textContent()) || '', | ||||||
|         Number(await projectCard.locator(this.projectCardFile).textContent()), |         Number(await projectCard.locator(this.projectCardFile).textContent()), | ||||||
|         Number(await projectCard.locator(this.projectCardFolder).textContent()), |  | ||||||
|       ]) |       ]) | ||||||
|       projectCardStates.push({ |       projectCardStates.push({ | ||||||
|         title: title, |         title: title, | ||||||
|         fileCount, |         fileCount, | ||||||
|         folderCount, |  | ||||||
|       }) |       }) | ||||||
|     } |     } | ||||||
|     return projectCardStates |     return projectCardStates | ||||||
|  | |||||||
| @ -6,6 +6,8 @@ export class ToolbarFixture { | |||||||
|   public page: Page |   public page: Page | ||||||
|  |  | ||||||
|   extrudeButton!: Locator |   extrudeButton!: Locator | ||||||
|  |   loftButton!: Locator | ||||||
|  |   shellButton!: Locator | ||||||
|   offsetPlaneButton!: Locator |   offsetPlaneButton!: Locator | ||||||
|   startSketchBtn!: Locator |   startSketchBtn!: Locator | ||||||
|   lineBtn!: Locator |   lineBtn!: Locator | ||||||
| @ -26,6 +28,8 @@ export class ToolbarFixture { | |||||||
|   reConstruct = (page: Page) => { |   reConstruct = (page: Page) => { | ||||||
|     this.page = page |     this.page = page | ||||||
|     this.extrudeButton = page.getByTestId('extrude') |     this.extrudeButton = page.getByTestId('extrude') | ||||||
|  |     this.loftButton = page.getByTestId('loft') | ||||||
|  |     this.shellButton = page.getByTestId('shell') | ||||||
|     this.offsetPlaneButton = page.getByTestId('plane-offset') |     this.offsetPlaneButton = page.getByTestId('plane-offset') | ||||||
|     this.startSketchBtn = page.getByTestId('sketch') |     this.startSketchBtn = page.getByTestId('sketch') | ||||||
|     this.lineBtn = page.getByTestId('line') |     this.lineBtn = page.getByTestId('line') | ||||||
|  | |||||||
| @ -677,3 +677,259 @@ test(`Offset plane point-and-click`, async ({ | |||||||
|     await scene.expectPixelColor([74, 74, 74], testPoint, 15) |     await scene.expectPixelColor([74, 74, 74], testPoint, 15) | ||||||
|   }) |   }) | ||||||
| }) | }) | ||||||
|  |  | ||||||
|  | const loftPointAndClickCases = [ | ||||||
|  |   { shouldPreselect: true }, | ||||||
|  |   { shouldPreselect: false }, | ||||||
|  | ] | ||||||
|  | loftPointAndClickCases.forEach(({ shouldPreselect }) => { | ||||||
|  |   test(`Loft point-and-click (preselected sketches: ${shouldPreselect})`, async ({ | ||||||
|  |     app, | ||||||
|  |     page, | ||||||
|  |     scene, | ||||||
|  |     editor, | ||||||
|  |     toolbar, | ||||||
|  |     cmdBar, | ||||||
|  |   }) => { | ||||||
|  |     const initialCode = `sketch001 = startSketchOn('XZ') | ||||||
|  |     |> circle({ center = [0, 0], radius = 30 }, %) | ||||||
|  |     plane001 = offsetPlane('XZ', 50) | ||||||
|  |     sketch002 = startSketchOn(plane001) | ||||||
|  |     |> circle({ center = [0, 0], radius = 20 }, %) | ||||||
|  | ` | ||||||
|  |     await app.initialise(initialCode) | ||||||
|  |  | ||||||
|  |     // One dumb hardcoded screen pixel value | ||||||
|  |     const testPoint = { x: 575, y: 200 } | ||||||
|  |     const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y) | ||||||
|  |     const [clickOnSketch2] = scene.makeMouseHelpers( | ||||||
|  |       testPoint.x, | ||||||
|  |       testPoint.y + 80 | ||||||
|  |     ) | ||||||
|  |     const loftDeclaration = 'loft001 = loft([sketch001, sketch002])' | ||||||
|  |  | ||||||
|  |     await test.step(`Look for the white of the sketch001 shape`, async () => { | ||||||
|  |       await scene.expectPixelColor([254, 254, 254], testPoint, 15) | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     async function selectSketches() { | ||||||
|  |       await clickOnSketch1() | ||||||
|  |       await page.keyboard.down('Shift') | ||||||
|  |       await clickOnSketch2() | ||||||
|  |       await app.page.waitForTimeout(500) | ||||||
|  |       await page.keyboard.up('Shift') | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (!shouldPreselect) { | ||||||
|  |       await test.step(`Go through the command bar flow without preselected sketches`, async () => { | ||||||
|  |         await toolbar.loftButton.click() | ||||||
|  |         await cmdBar.expectState({ | ||||||
|  |           stage: 'arguments', | ||||||
|  |           currentArgKey: 'selection', | ||||||
|  |           currentArgValue: '', | ||||||
|  |           headerArguments: { Selection: '' }, | ||||||
|  |           highlightedHeaderArg: 'selection', | ||||||
|  |           commandName: 'Loft', | ||||||
|  |         }) | ||||||
|  |         await selectSketches() | ||||||
|  |         await cmdBar.progressCmdBar() | ||||||
|  |         await cmdBar.expectState({ | ||||||
|  |           stage: 'review', | ||||||
|  |           headerArguments: { Selection: '2 faces' }, | ||||||
|  |           commandName: 'Loft', | ||||||
|  |         }) | ||||||
|  |         await cmdBar.progressCmdBar() | ||||||
|  |       }) | ||||||
|  |     } else { | ||||||
|  |       await test.step(`Preselect the two sketches`, async () => { | ||||||
|  |         await selectSketches() | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       await test.step(`Go through the command bar flow with preselected sketches`, async () => { | ||||||
|  |         await toolbar.loftButton.click() | ||||||
|  |         await cmdBar.progressCmdBar() | ||||||
|  |         await cmdBar.expectState({ | ||||||
|  |           stage: 'review', | ||||||
|  |           headerArguments: { Selection: '2 faces' }, | ||||||
|  |           commandName: 'Loft', | ||||||
|  |         }) | ||||||
|  |         await cmdBar.progressCmdBar() | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     await test.step(`Confirm code is added to the editor, scene has changed`, async () => { | ||||||
|  |       await editor.expectEditor.toContain(loftDeclaration) | ||||||
|  |       await editor.expectState({ | ||||||
|  |         diagnostics: [], | ||||||
|  |         activeLines: [loftDeclaration], | ||||||
|  |         highlightedCode: '', | ||||||
|  |       }) | ||||||
|  |       await scene.expectPixelColor([89, 89, 89], testPoint, 15) | ||||||
|  |     }) | ||||||
|  |   }) | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | const shellPointAndClickCapCases = [ | ||||||
|  |   { shouldPreselect: true }, | ||||||
|  |   { shouldPreselect: false }, | ||||||
|  | ] | ||||||
|  | shellPointAndClickCapCases.forEach(({ shouldPreselect }) => { | ||||||
|  |   test(`Shell point-and-click cap (preselected sketches: ${shouldPreselect})`, async ({ | ||||||
|  |     app, | ||||||
|  |     scene, | ||||||
|  |     editor, | ||||||
|  |     toolbar, | ||||||
|  |     cmdBar, | ||||||
|  |   }) => { | ||||||
|  |     const initialCode = `sketch001 = startSketchOn('XZ') | ||||||
|  |     |> circle({ center = [0, 0], radius = 30 }, %) | ||||||
|  |     extrude001 = extrude(30, sketch001) | ||||||
|  |     ` | ||||||
|  |     await app.initialise(initialCode) | ||||||
|  |  | ||||||
|  |     // One dumb hardcoded screen pixel value | ||||||
|  |     const testPoint = { x: 575, y: 200 } | ||||||
|  |     const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y) | ||||||
|  |     const shellDeclaration = | ||||||
|  |       "shell001 = shell({ faces = ['end'], thickness = 5 }, extrude001)" | ||||||
|  |  | ||||||
|  |     await test.step(`Look for the grey of the shape`, async () => { | ||||||
|  |       await scene.expectPixelColor([127, 127, 127], testPoint, 15) | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     if (!shouldPreselect) { | ||||||
|  |       await test.step(`Go through the command bar flow without preselected faces`, async () => { | ||||||
|  |         await toolbar.shellButton.click() | ||||||
|  |         await cmdBar.expectState({ | ||||||
|  |           stage: 'arguments', | ||||||
|  |           currentArgKey: 'selection', | ||||||
|  |           currentArgValue: '', | ||||||
|  |           headerArguments: { | ||||||
|  |             Selection: '', | ||||||
|  |             Thickness: '', | ||||||
|  |           }, | ||||||
|  |           highlightedHeaderArg: 'selection', | ||||||
|  |           commandName: 'Shell', | ||||||
|  |         }) | ||||||
|  |         await clickOnCap() | ||||||
|  |         await app.page.waitForTimeout(500) | ||||||
|  |         await cmdBar.progressCmdBar() | ||||||
|  |         await cmdBar.progressCmdBar() | ||||||
|  |         await cmdBar.expectState({ | ||||||
|  |           stage: 'review', | ||||||
|  |           headerArguments: { | ||||||
|  |             Selection: '1 cap', | ||||||
|  |             Thickness: '5', | ||||||
|  |           }, | ||||||
|  |           commandName: 'Shell', | ||||||
|  |         }) | ||||||
|  |         await cmdBar.progressCmdBar() | ||||||
|  |       }) | ||||||
|  |     } else { | ||||||
|  |       await test.step(`Preselect the cap`, async () => { | ||||||
|  |         await clickOnCap() | ||||||
|  |         await app.page.waitForTimeout(500) | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => { | ||||||
|  |         await toolbar.shellButton.click() | ||||||
|  |         await cmdBar.progressCmdBar() | ||||||
|  |         await cmdBar.progressCmdBar() | ||||||
|  |         await cmdBar.expectState({ | ||||||
|  |           stage: 'review', | ||||||
|  |           headerArguments: { | ||||||
|  |             Selection: '1 cap', | ||||||
|  |             Thickness: '5', | ||||||
|  |           }, | ||||||
|  |           commandName: 'Shell', | ||||||
|  |         }) | ||||||
|  |         await cmdBar.progressCmdBar() | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     await test.step(`Confirm code is added to the editor, scene has changed`, async () => { | ||||||
|  |       await editor.expectEditor.toContain(shellDeclaration) | ||||||
|  |       await editor.expectState({ | ||||||
|  |         diagnostics: [], | ||||||
|  |         activeLines: [shellDeclaration], | ||||||
|  |         highlightedCode: '', | ||||||
|  |       }) | ||||||
|  |       await scene.expectPixelColor([146, 146, 146], testPoint, 15) | ||||||
|  |     }) | ||||||
|  |   }) | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | test('Shell point-and-click wall', async ({ | ||||||
|  |   app, | ||||||
|  |   page, | ||||||
|  |   scene, | ||||||
|  |   editor, | ||||||
|  |   toolbar, | ||||||
|  |   cmdBar, | ||||||
|  | }) => { | ||||||
|  |   const initialCode = `sketch001 = startSketchOn('XY') | ||||||
|  |   |> startProfileAt([-20, 20], %) | ||||||
|  |   |> xLine(40, %) | ||||||
|  |   |> yLine(-60, %) | ||||||
|  |   |> xLine(-40, %) | ||||||
|  |   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||||
|  |   |> close(%) | ||||||
|  | extrude001 = extrude(40, sketch001) | ||||||
|  |   ` | ||||||
|  |   await app.initialise(initialCode) | ||||||
|  |  | ||||||
|  |   // One dumb hardcoded screen pixel value | ||||||
|  |   const testPoint = { x: 580, y: 180 } | ||||||
|  |   const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y) | ||||||
|  |   const [clickOnWall] = scene.makeMouseHelpers(testPoint.x, testPoint.y + 70) | ||||||
|  |   const mutatedCode = 'xLine(-40, %, $seg01)' | ||||||
|  |   const shellDeclaration = | ||||||
|  |     "shell001 = shell({  faces = ['end', seg01],  thickness = 5}, extrude001)" | ||||||
|  |   const formattedOutLastLine = '}, extrude001)' | ||||||
|  |  | ||||||
|  |   await test.step(`Look for the grey of the shape`, async () => { | ||||||
|  |     await scene.expectPixelColor([99, 99, 99], testPoint, 15) | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   await test.step(`Go through the command bar flow, selecting a wall and keeping default thickness`, async () => { | ||||||
|  |     await toolbar.shellButton.click() | ||||||
|  |     await cmdBar.expectState({ | ||||||
|  |       stage: 'arguments', | ||||||
|  |       currentArgKey: 'selection', | ||||||
|  |       currentArgValue: '', | ||||||
|  |       headerArguments: { | ||||||
|  |         Selection: '', | ||||||
|  |         Thickness: '', | ||||||
|  |       }, | ||||||
|  |       highlightedHeaderArg: 'selection', | ||||||
|  |       commandName: 'Shell', | ||||||
|  |     }) | ||||||
|  |     await clickOnCap() | ||||||
|  |     await page.keyboard.down('Shift') | ||||||
|  |     await clickOnWall() | ||||||
|  |     await app.page.waitForTimeout(500) | ||||||
|  |     await page.keyboard.up('Shift') | ||||||
|  |     await cmdBar.progressCmdBar() | ||||||
|  |     await cmdBar.progressCmdBar() | ||||||
|  |     await cmdBar.expectState({ | ||||||
|  |       stage: 'review', | ||||||
|  |       headerArguments: { | ||||||
|  |         Selection: '1 cap, 1 face', | ||||||
|  |         Thickness: '5', | ||||||
|  |       }, | ||||||
|  |       commandName: 'Shell', | ||||||
|  |     }) | ||||||
|  |     await cmdBar.progressCmdBar() | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   await test.step(`Confirm code is added to the editor, scene has changed`, async () => { | ||||||
|  |     await editor.expectEditor.toContain(mutatedCode) | ||||||
|  |     await editor.expectEditor.toContain(shellDeclaration) | ||||||
|  |     await editor.expectState({ | ||||||
|  |       diagnostics: [], | ||||||
|  |       activeLines: [formattedOutLastLine], | ||||||
|  |       highlightedCode: '', | ||||||
|  |     }) | ||||||
|  |     await scene.expectPixelColor([49, 49, 49], testPoint, 15) | ||||||
|  |   }) | ||||||
|  | }) | ||||||
|  | |||||||
| @ -136,6 +136,335 @@ test( | |||||||
|   } |   } | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | test( | ||||||
|  |   'open a file in a project works and renders, open another file in different project with errors, it should clear the scene', | ||||||
|  |   { tag: '@electron' }, | ||||||
|  |   async ({ browserName }, testInfo) => { | ||||||
|  |     const { electronApp, page } = await setupElectron({ | ||||||
|  |       testInfo, | ||||||
|  |       folderSetupFn: async (dir) => { | ||||||
|  |         const bracketDir = join(dir, 'bracket') | ||||||
|  |         await fsp.mkdir(bracketDir, { recursive: true }) | ||||||
|  |         await fsp.copyFile( | ||||||
|  |           executorInputPath('focusrite_scarlett_mounting_braket.kcl'), | ||||||
|  |           join(bracketDir, 'main.kcl') | ||||||
|  |         ) | ||||||
|  |         const errorDir = join(dir, 'broken-code') | ||||||
|  |         await fsp.mkdir(errorDir, { recursive: true }) | ||||||
|  |         await fsp.copyFile( | ||||||
|  |           executorInputPath('broken-code-test.kcl'), | ||||||
|  |           join(errorDir, 'main.kcl') | ||||||
|  |         ) | ||||||
|  |       }, | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |     const u = await getUtils(page) | ||||||
|  |  | ||||||
|  |     page.on('console', console.log) | ||||||
|  |  | ||||||
|  |     const pointOnModel = { x: 630, y: 280 } | ||||||
|  |  | ||||||
|  |     await test.step('Opening the bracket project should load the stream', async () => { | ||||||
|  |       // expect to see the text bracket | ||||||
|  |       await expect(page.getByText('bracket')).toBeVisible() | ||||||
|  |  | ||||||
|  |       await page.getByText('bracket').click() | ||||||
|  |  | ||||||
|  |       await expect(page.getByTestId('loading')).toBeAttached() | ||||||
|  |       await expect(page.getByTestId('loading')).not.toBeAttached({ | ||||||
|  |         timeout: 20_000, | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       await expect( | ||||||
|  |         page.getByRole('button', { name: 'Start Sketch' }) | ||||||
|  |       ).toBeEnabled({ | ||||||
|  |         timeout: 20_000, | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       // gray at this pixel means the stream has loaded in the most | ||||||
|  |       // user way we can verify it (pixel color) | ||||||
|  |       await expect | ||||||
|  |         .poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), { | ||||||
|  |           timeout: 10_000, | ||||||
|  |         }) | ||||||
|  |         .toBeLessThan(15) | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     await test.step('Clicking the logo takes us back to the projects page / home', async () => { | ||||||
|  |       await page.getByTestId('app-logo').click() | ||||||
|  |  | ||||||
|  |       await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible() | ||||||
|  |       await expect(page.getByText('broken-code')).toBeVisible() | ||||||
|  |       await expect(page.getByText('bracket')).toBeVisible() | ||||||
|  |       await expect(page.getByText('New Project')).toBeVisible() | ||||||
|  |     }) | ||||||
|  |     await test.step('opening broken code project should clear the scene and show the error', async () => { | ||||||
|  |       // Go back home. | ||||||
|  |       await expect(page.getByText('broken-code')).toBeVisible() | ||||||
|  |  | ||||||
|  |       await page.getByText('broken-code').click() | ||||||
|  |  | ||||||
|  |       // error in guter | ||||||
|  |       await expect(page.locator('.cm-lint-marker-error')).toBeVisible() | ||||||
|  |  | ||||||
|  |       // error text on hover | ||||||
|  |       await page.hover('.cm-lint-marker-error') | ||||||
|  |       const crypticErrorText = `Expected a tag declarator` | ||||||
|  |       await expect(page.getByText(crypticErrorText).first()).toBeVisible() | ||||||
|  |  | ||||||
|  |       // black pixel means the scene has been cleared. | ||||||
|  |       await expect | ||||||
|  |         .poll(() => u.getGreatestPixDiff(pointOnModel, [30, 30, 30]), { | ||||||
|  |           timeout: 10_000, | ||||||
|  |         }) | ||||||
|  |         .toBeLessThan(15) | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     await electronApp.close() | ||||||
|  |   } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | test( | ||||||
|  |   'open a file in a project works and renders, open another file in different project that is empty, it should clear the scene', | ||||||
|  |   { tag: '@electron' }, | ||||||
|  |   async ({ browserName }, testInfo) => { | ||||||
|  |     const { electronApp, page } = await setupElectron({ | ||||||
|  |       testInfo, | ||||||
|  |       folderSetupFn: async (dir) => { | ||||||
|  |         const bracketDir = join(dir, 'bracket') | ||||||
|  |         await fsp.mkdir(bracketDir, { recursive: true }) | ||||||
|  |         await fsp.copyFile( | ||||||
|  |           executorInputPath('focusrite_scarlett_mounting_braket.kcl'), | ||||||
|  |           join(bracketDir, 'main.kcl') | ||||||
|  |         ) | ||||||
|  |         const emptyDir = join(dir, 'empty') | ||||||
|  |         await fsp.mkdir(emptyDir, { recursive: true }) | ||||||
|  |         await fsp.writeFile(join(emptyDir, 'main.kcl'), '') | ||||||
|  |       }, | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |     const u = await getUtils(page) | ||||||
|  |  | ||||||
|  |     page.on('console', console.log) | ||||||
|  |  | ||||||
|  |     const pointOnModel = { x: 630, y: 280 } | ||||||
|  |  | ||||||
|  |     await test.step('Opening the bracket project should load the stream', async () => { | ||||||
|  |       // expect to see the text bracket | ||||||
|  |       await expect(page.getByText('bracket')).toBeVisible() | ||||||
|  |  | ||||||
|  |       await page.getByText('bracket').click() | ||||||
|  |  | ||||||
|  |       await expect(page.getByTestId('loading')).toBeAttached() | ||||||
|  |       await expect(page.getByTestId('loading')).not.toBeAttached({ | ||||||
|  |         timeout: 20_000, | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       await expect( | ||||||
|  |         page.getByRole('button', { name: 'Start Sketch' }) | ||||||
|  |       ).toBeEnabled({ | ||||||
|  |         timeout: 20_000, | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       // gray at this pixel means the stream has loaded in the most | ||||||
|  |       // user way we can verify it (pixel color) | ||||||
|  |       await expect | ||||||
|  |         .poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), { | ||||||
|  |           timeout: 10_000, | ||||||
|  |         }) | ||||||
|  |         .toBeLessThan(15) | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     await test.step('Clicking the logo takes us back to the projects page / home', async () => { | ||||||
|  |       await page.getByTestId('app-logo').click() | ||||||
|  |  | ||||||
|  |       await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible() | ||||||
|  |       await expect(page.getByText('empty')).toBeVisible() | ||||||
|  |       await expect(page.getByText('bracket')).toBeVisible() | ||||||
|  |       await expect(page.getByText('New Project')).toBeVisible() | ||||||
|  |     }) | ||||||
|  |     await test.step('opening empty code project should clear the scene', async () => { | ||||||
|  |       // Go back home. | ||||||
|  |       await expect(page.getByText('empty')).toBeVisible() | ||||||
|  |  | ||||||
|  |       await page.getByText('empty').click() | ||||||
|  |  | ||||||
|  |       // Ensure the code is empty. | ||||||
|  |       await expect(u.codeLocator).toContainText('') | ||||||
|  |       expect(u.codeLocator.innerHTML.length).toBeLessThan(2) | ||||||
|  |  | ||||||
|  |       // planes colors means the scene has been cleared. | ||||||
|  |       await expect | ||||||
|  |         .poll(() => u.getGreatestPixDiff(pointOnModel, [92, 53, 53]), { | ||||||
|  |           timeout: 10_000, | ||||||
|  |         }) | ||||||
|  |         .toBeLessThan(15) | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     await electronApp.close() | ||||||
|  |   } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | test( | ||||||
|  |   'open a file in a project works and renders, open empty file, it should clear the scene', | ||||||
|  |   { tag: '@electron' }, | ||||||
|  |   async ({ browserName }, testInfo) => { | ||||||
|  |     const { electronApp, page } = await setupElectron({ | ||||||
|  |       testInfo, | ||||||
|  |       folderSetupFn: async (dir) => { | ||||||
|  |         const bracketDir = join(dir, 'bracket') | ||||||
|  |         await fsp.mkdir(bracketDir, { recursive: true }) | ||||||
|  |         await fsp.copyFile( | ||||||
|  |           executorInputPath('focusrite_scarlett_mounting_braket.kcl'), | ||||||
|  |           join(bracketDir, 'main.kcl') | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         await fsp.writeFile(join(bracketDir, 'empty.kcl'), '') | ||||||
|  |       }, | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |     const u = await getUtils(page) | ||||||
|  |  | ||||||
|  |     page.on('console', console.log) | ||||||
|  |  | ||||||
|  |     const pointOnModel = { x: 630, y: 280 } | ||||||
|  |  | ||||||
|  |     await test.step('Opening the bracket project should load the stream', async () => { | ||||||
|  |       // expect to see the text bracket | ||||||
|  |       await expect(page.getByText('bracket')).toBeVisible() | ||||||
|  |  | ||||||
|  |       await page.getByText('bracket').click() | ||||||
|  |  | ||||||
|  |       await expect(page.getByTestId('loading')).toBeAttached() | ||||||
|  |       await expect(page.getByTestId('loading')).not.toBeAttached({ | ||||||
|  |         timeout: 20_000, | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       await expect( | ||||||
|  |         page.getByRole('button', { name: 'Start Sketch' }) | ||||||
|  |       ).toBeEnabled({ | ||||||
|  |         timeout: 20_000, | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       // gray at this pixel means the stream has loaded in the most | ||||||
|  |       // user way we can verify it (pixel color) | ||||||
|  |       await expect | ||||||
|  |         .poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), { | ||||||
|  |           timeout: 10_000, | ||||||
|  |         }) | ||||||
|  |         .toBeLessThan(15) | ||||||
|  |     }) | ||||||
|  |     await test.step('creating a empty file should clear the scene', async () => { | ||||||
|  |       // open the file pane. | ||||||
|  |       await page.getByTestId('files-pane-button').click() | ||||||
|  |  | ||||||
|  |       // OPen the other file. | ||||||
|  |       const file = page.getByRole('button', { name: 'empty.kcl' }) | ||||||
|  |       await expect(file).toBeVisible() | ||||||
|  |  | ||||||
|  |       await file.click() | ||||||
|  |  | ||||||
|  |       // planes colors means the scene has been cleared. | ||||||
|  |       await expect | ||||||
|  |         .poll(() => u.getGreatestPixDiff(pointOnModel, [92, 53, 53]), { | ||||||
|  |           timeout: 10_000, | ||||||
|  |         }) | ||||||
|  |         .toBeLessThan(15) | ||||||
|  |  | ||||||
|  |       // Ensure the code is empty. | ||||||
|  |       await expect(u.codeLocator).toContainText('') | ||||||
|  |       expect(u.codeLocator.innerHTML.length).toBeLessThan(2) | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     await electronApp.close() | ||||||
|  |   } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | test( | ||||||
|  |   'open a file in a project works and renders, open another file in the same project with errors, it should clear the scene', | ||||||
|  |   { tag: '@electron' }, | ||||||
|  |   async ({ browserName }, testInfo) => { | ||||||
|  |     const { electronApp, page } = await setupElectron({ | ||||||
|  |       testInfo, | ||||||
|  |       folderSetupFn: async (dir) => { | ||||||
|  |         const bracketDir = join(dir, 'bracket') | ||||||
|  |         await fsp.mkdir(bracketDir, { recursive: true }) | ||||||
|  |         await fsp.copyFile( | ||||||
|  |           executorInputPath('focusrite_scarlett_mounting_braket.kcl'), | ||||||
|  |           join(bracketDir, 'main.kcl') | ||||||
|  |         ) | ||||||
|  |         await fsp.copyFile( | ||||||
|  |           executorInputPath('broken-code-test.kcl'), | ||||||
|  |           join(bracketDir, 'broken-code-test.kcl') | ||||||
|  |         ) | ||||||
|  |       }, | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |     const u = await getUtils(page) | ||||||
|  |  | ||||||
|  |     page.on('console', console.log) | ||||||
|  |  | ||||||
|  |     const pointOnModel = { x: 630, y: 280 } | ||||||
|  |  | ||||||
|  |     await test.step('Opening the bracket project should load the stream', async () => { | ||||||
|  |       // expect to see the text bracket | ||||||
|  |       await expect(page.getByText('bracket')).toBeVisible() | ||||||
|  |  | ||||||
|  |       await page.getByText('bracket').click() | ||||||
|  |  | ||||||
|  |       await expect(page.getByTestId('loading')).toBeAttached() | ||||||
|  |       await expect(page.getByTestId('loading')).not.toBeAttached({ | ||||||
|  |         timeout: 20_000, | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       await expect( | ||||||
|  |         page.getByRole('button', { name: 'Start Sketch' }) | ||||||
|  |       ).toBeEnabled({ | ||||||
|  |         timeout: 20_000, | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       // gray at this pixel means the stream has loaded in the most | ||||||
|  |       // user way we can verify it (pixel color) | ||||||
|  |       await expect | ||||||
|  |         .poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), { | ||||||
|  |           timeout: 10_000, | ||||||
|  |         }) | ||||||
|  |         .toBeLessThan(15) | ||||||
|  |     }) | ||||||
|  |     await test.step('opening broken code file should clear the scene and show the error', async () => { | ||||||
|  |       // open the file pane. | ||||||
|  |       await page.getByTestId('files-pane-button').click() | ||||||
|  |  | ||||||
|  |       // OPen the other file. | ||||||
|  |       const file = page.getByRole('button', { name: 'broken-code-test.kcl' }) | ||||||
|  |       await expect(file).toBeVisible() | ||||||
|  |  | ||||||
|  |       await file.click() | ||||||
|  |  | ||||||
|  |       // error in guter | ||||||
|  |       await expect(page.locator('.cm-lint-marker-error')).toBeVisible() | ||||||
|  |  | ||||||
|  |       // error text on hover | ||||||
|  |       await page.hover('.cm-lint-marker-error') | ||||||
|  |       const crypticErrorText = `Expected a tag declarator` | ||||||
|  |       await expect(page.getByText(crypticErrorText).first()).toBeVisible() | ||||||
|  |  | ||||||
|  |       // black pixel means the scene has been cleared. | ||||||
|  |       await expect | ||||||
|  |         .poll(() => u.getGreatestPixDiff(pointOnModel, [30, 30, 30]), { | ||||||
|  |           timeout: 10_000, | ||||||
|  |         }) | ||||||
|  |         .toBeLessThan(15) | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     await electronApp.close() | ||||||
|  |   } | ||||||
|  | ) | ||||||
|  |  | ||||||
| test( | test( | ||||||
|   'when code with error first loads you get errors in console', |   'when code with error first loads you get errors in console', | ||||||
|   { tag: '@electron' }, |   { tag: '@electron' }, | ||||||
|  | |||||||
| @ -550,7 +550,7 @@ sketch001 = startSketchAt([-0, -0]) | |||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|  |  | ||||||
|     // Constants and locators |     // Constants and locators | ||||||
|     const planeColor: [number, number, number] = [170, 220, 170] |     const planeColor: [number, number, number] = [161, 220, 155] | ||||||
|     const bgColor: [number, number, number] = [27, 27, 27] |     const bgColor: [number, number, number] = [27, 27, 27] | ||||||
|     const middlePixelIsColor = async (color: [number, number, number]) => { |     const middlePixelIsColor = async (color: [number, number, number]) => { | ||||||
|       return u.getGreatestPixDiff({ x: 600, y: 250 }, color) |       return u.getGreatestPixDiff({ x: 600, y: 250 }, color) | ||||||
|  | |||||||
| @ -7,6 +7,8 @@ try { | |||||||
|     .split('\n') |     .split('\n') | ||||||
|     .filter((line) => line && line.length > 1) |     .filter((line) => line && line.length > 1) | ||||||
|     .forEach((line) => { |     .forEach((line) => { | ||||||
|  |       // Allow line comments. | ||||||
|  |       if (line.trimStart().startsWith('#')) return | ||||||
|       const [key, value] = line.split('=') |       const [key, value] = line.split('=') | ||||||
|       // prefer env vars over secrets file |       // prefer env vars over secrets file | ||||||
|       secrets[key] = process.env[key] || (value as any).replaceAll('"', '') |       secrets[key] = process.env[key] || (value as any).replaceAll('"', '') | ||||||
|  | |||||||
| @ -943,6 +943,110 @@ sketch002 = startSketchOn(extrude001, 'END') | |||||||
| `.replace(/\s/g, '') | `.replace(/\s/g, '') | ||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|  |   /* TODO: once we fix bug turn on. | ||||||
|  |    test('empty-scene default-planes act as expected when spaces in file', async ({ | ||||||
|  |     page, | ||||||
|  |     browserName, | ||||||
|  |   }) => { | ||||||
|  |  | ||||||
|  |     const u = await getUtils(page) | ||||||
|  |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|  |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|  |     await u.openDebugPanel() | ||||||
|  |     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||||
|  |     await u.closeDebugPanel() | ||||||
|  |  | ||||||
|  |     const XYPlanePoint = { x: 774, y: 116 } as const | ||||||
|  |     const unHoveredColor: [number, number, number] = [47, 47, 93] | ||||||
|  |     expect( | ||||||
|  |       await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor) | ||||||
|  |     ).toBeLessThan(8) | ||||||
|  |  | ||||||
|  |     await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y) | ||||||
|  |     await page.waitForTimeout(200) | ||||||
|  |  | ||||||
|  |     // color should not change for having been hovered | ||||||
|  |     expect( | ||||||
|  |       await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor) | ||||||
|  |     ).toBeLessThan(8) | ||||||
|  |  | ||||||
|  |     await u.openAndClearDebugPanel() | ||||||
|  |  | ||||||
|  |     // Fill with spaces | ||||||
|  |     await u.codeLocator.fill(`                | ||||||
|  | `) | ||||||
|  |  | ||||||
|  |     await u.openDebugPanel() | ||||||
|  |     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||||
|  |     await u.closeDebugPanel() | ||||||
|  |  | ||||||
|  |     expect( | ||||||
|  |       await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor) | ||||||
|  |     ).toBeLessThan(8) | ||||||
|  |  | ||||||
|  |     await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y) | ||||||
|  |     await page.waitForTimeout(200) | ||||||
|  |  | ||||||
|  |     // color should not change for having been hovered | ||||||
|  |     expect( | ||||||
|  |       await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor) | ||||||
|  |     ).toBeLessThan(8) | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   test('empty-scene default-planes act as expected when only code comments in file', async ({ | ||||||
|  |     page, | ||||||
|  |     browserName, | ||||||
|  |   }) => { | ||||||
|  |  | ||||||
|  |     const u = await getUtils(page) | ||||||
|  |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|  |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|  |     await u.openDebugPanel() | ||||||
|  |     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||||
|  |     await u.closeDebugPanel() | ||||||
|  |  | ||||||
|  |     const XYPlanePoint = { x: 774, y: 116 } as const | ||||||
|  |     const unHoveredColor: [number, number, number] = [47, 47, 93] | ||||||
|  |     expect( | ||||||
|  |       await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor) | ||||||
|  |     ).toBeLessThan(8) | ||||||
|  |  | ||||||
|  |     await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y) | ||||||
|  |     await page.waitForTimeout(200) | ||||||
|  |  | ||||||
|  |     // color should not change for having been hovered | ||||||
|  |     expect( | ||||||
|  |       await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor) | ||||||
|  |     ).toBeLessThan(8) | ||||||
|  |  | ||||||
|  |     await u.openAndClearDebugPanel() | ||||||
|  |  | ||||||
|  |     // Fill with spaces | ||||||
|  |     await u.codeLocator.fill(`// this is a code comments ya nerds | ||||||
|  | `) | ||||||
|  |  | ||||||
|  |     await u.openDebugPanel() | ||||||
|  |     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||||
|  |     await u.closeDebugPanel() | ||||||
|  |  | ||||||
|  |     expect( | ||||||
|  |       await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor) | ||||||
|  |     ).toBeLessThan(8) | ||||||
|  |  | ||||||
|  |     await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y) | ||||||
|  |     await page.waitForTimeout(200) | ||||||
|  |  | ||||||
|  |     // color should not change for having been hovered | ||||||
|  |     expect( | ||||||
|  |       await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor) | ||||||
|  |     ).toBeLessThan(8) | ||||||
|  |   })*/ | ||||||
|  |  | ||||||
|   test('empty-scene default-planes act as expected', async ({ |   test('empty-scene default-planes act as expected', async ({ | ||||||
|     page, |     page, | ||||||
|     browserName, |     browserName, | ||||||
|  | |||||||
| Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB | 
| Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB | 
| Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 50 KiB | 
| Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 51 KiB | 
| Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB | 
| Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB | 
| Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB | 
| Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB | 
| Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB | 
| Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB | 
| Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB | 
| @ -26,7 +26,17 @@ test.describe('Testing constraints', () => { | |||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     const PUR = 400 / 37.5 //pixeltoUnitRatio |     // constants and locators | ||||||
|  |     const lengthValue = { | ||||||
|  |       old: '20', | ||||||
|  |       new: '25', | ||||||
|  |     } | ||||||
|  |     const cmdBarKclInput = page | ||||||
|  |       .getByTestId('cmd-bar-arg-value') | ||||||
|  |       .getByRole('textbox') | ||||||
|  |     const cmdBarSubmitButton = page.getByRole('button', { | ||||||
|  |       name: 'arrow right Continue', | ||||||
|  |     }) | ||||||
|     await page.setViewportSize({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await u.waitForAuthSkipAppStart() |     await u.waitForAuthSkipAppStart() | ||||||
| @ -36,26 +46,26 @@ test.describe('Testing constraints', () => { | |||||||
|     await u.closeDebugPanel() |     await u.closeDebugPanel() | ||||||
|  |  | ||||||
|     // Click the line of code for line. |     // Click the line of code for line. | ||||||
|     await page.getByText(`line([0, 20], %)`).click() // TODO remove this and reinstate // await topHorzSegmentClick() |     // TODO remove this and reinstate `await topHorzSegmentClick()` | ||||||
|  |     await page.getByText(`line([0, ${lengthValue.old}], %)`).click() | ||||||
|     await page.waitForTimeout(100) |     await page.waitForTimeout(100) | ||||||
|  |  | ||||||
|     // enter sketch again |     // enter sketch again | ||||||
|     await page.getByRole('button', { name: 'Edit Sketch' }).click() |     await page.getByRole('button', { name: 'Edit Sketch' }).click() | ||||||
|     await page.waitForTimeout(500) // wait for animation |     await page.waitForTimeout(500) // wait for animation | ||||||
|  |  | ||||||
|     const startXPx = 500 |  | ||||||
|     await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10) |  | ||||||
|     await page.keyboard.down('Shift') |  | ||||||
|     await page.mouse.click(834, 244) |  | ||||||
|     await page.keyboard.up('Shift') |  | ||||||
|  |  | ||||||
|     await page |     await page | ||||||
|       .getByRole('button', { name: 'dimension Length', exact: true }) |       .getByRole('button', { name: 'dimension Length', exact: true }) | ||||||
|       .click() |       .click() | ||||||
|     await page.getByText('Add constraining value').click() |     await expect(cmdBarKclInput).toHaveText('20') | ||||||
|  |     await cmdBarKclInput.fill(lengthValue.new) | ||||||
|  |     await expect( | ||||||
|  |       page.getByText(`Can't calculate`), | ||||||
|  |       `Something went wrong with the KCL expression evaluation` | ||||||
|  |     ).not.toBeVisible() | ||||||
|  |     await cmdBarSubmitButton.click() | ||||||
|  |  | ||||||
|     await expect(page.locator('.cm-content')).toHaveText( |     await expect(page.locator('.cm-content')).toHaveText( | ||||||
|       `length001 = 20sketch001 = startSketchOn('XY')  |> startProfileAt([-10, -10], %)  |> line([20, 0], %)  |> angledLine([90, length001], %)  |> xLine(-20, %)` |       `length001 = ${lengthValue.new}sketch001 = startSketchOn('XY')  |> startProfileAt([-10, -10], %)  |> line([20, 0], %)  |> angledLine([90, length001], %)  |> xLine(-20, %)` | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     // Make sure we didn't pop out of sketch mode. |     // Make sure we didn't pop out of sketch mode. | ||||||
| @ -66,7 +76,6 @@ test.describe('Testing constraints', () => { | |||||||
|     await page.waitForTimeout(500) // wait for animation |     await page.waitForTimeout(500) // wait for animation | ||||||
|  |  | ||||||
|     // Exit sketch |     // Exit sketch | ||||||
|     await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10) |  | ||||||
|     await page.keyboard.press('Escape') |     await page.keyboard.press('Escape') | ||||||
|     await expect( |     await expect( | ||||||
|       page.getByRole('button', { name: 'Exit Sketch' }) |       page.getByRole('button', { name: 'Exit Sketch' }) | ||||||
| @ -524,7 +533,7 @@ part002 = startSketchOn('XZ') | |||||||
|       }) |       }) | ||||||
|     } |     } | ||||||
|   }) |   }) | ||||||
|   test.describe('Test Angle/Length constraint single selection', () => { |   test.describe('Test Angle constraint single selection', () => { | ||||||
|     const cases = [ |     const cases = [ | ||||||
|       { |       { | ||||||
|         testName: 'Angle - Add variable', |         testName: 'Angle - Add variable', | ||||||
| @ -538,18 +547,6 @@ part002 = startSketchOn('XZ') | |||||||
|         constraint: 'angle', |         constraint: 'angle', | ||||||
|         value: '83, 78.33', |         value: '83, 78.33', | ||||||
|       }, |       }, | ||||||
|       { |  | ||||||
|         testName: 'Length - Add variable', |  | ||||||
|         addVariable: true, |  | ||||||
|         constraint: 'length', |  | ||||||
|         value: '83, length001', |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         testName: 'Length - No variable', |  | ||||||
|         addVariable: false, |  | ||||||
|         constraint: 'length', |  | ||||||
|         value: '83, 78.33', |  | ||||||
|       }, |  | ||||||
|     ] as const |     ] as const | ||||||
|     for (const { testName, addVariable, value, constraint } of cases) { |     for (const { testName, addVariable, value, constraint } of cases) { | ||||||
|       test(`${testName}`, async ({ page }) => { |       test(`${testName}`, async ({ page }) => { | ||||||
| @ -608,6 +605,90 @@ part002 = startSketchOn('XZ') | |||||||
|       }) |       }) | ||||||
|     } |     } | ||||||
|   }) |   }) | ||||||
|  |   test.describe('Test Length constraint single selection', () => { | ||||||
|  |     const cases = [ | ||||||
|  |       { | ||||||
|  |         testName: 'Length - Add variable', | ||||||
|  |         addVariable: true, | ||||||
|  |         constraint: 'length', | ||||||
|  |         value: '83, length001', | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         testName: 'Length - No variable', | ||||||
|  |         addVariable: false, | ||||||
|  |         constraint: 'length', | ||||||
|  |         value: '83, 78.33', | ||||||
|  |       }, | ||||||
|  |     ] as const | ||||||
|  |     for (const { testName, addVariable, value, constraint } of cases) { | ||||||
|  |       test(`${testName}`, async ({ page }) => { | ||||||
|  |         // constants and locators | ||||||
|  |         const cmdBarKclInput = page | ||||||
|  |           .getByTestId('cmd-bar-arg-value') | ||||||
|  |           .getByRole('textbox') | ||||||
|  |         const cmdBarKclVariableNameInput = | ||||||
|  |           page.getByPlaceholder('Variable name') | ||||||
|  |         const cmdBarSubmitButton = page.getByRole('button', { | ||||||
|  |           name: 'arrow right Continue', | ||||||
|  |         }) | ||||||
|  |  | ||||||
|  |         await page.addInitScript(async () => { | ||||||
|  |           localStorage.setItem( | ||||||
|  |             'persistCode', | ||||||
|  |             `yo = 5 | ||||||
|  | part001 = startSketchOn('XZ') | ||||||
|  |   |> startProfileAt([-7.54, -26.74], %) | ||||||
|  |   |> line([74.36, 130.4], %) | ||||||
|  |   |> line([78.92, -120.11], %) | ||||||
|  |   |> line([9.16, 77.79], %) | ||||||
|  |   |> line([51.19, 48.97], %) | ||||||
|  | part002 = startSketchOn('XZ') | ||||||
|  |   |> startProfileAt([299.05, 231.45], %) | ||||||
|  |   |> xLine(-425.34, %, $seg_what) | ||||||
|  |   |> yLine(-264.06, %) | ||||||
|  |   |> xLine(segLen(seg_what), %) | ||||||
|  |   |> lineTo([profileStartX(%), profileStartY(%)], %)` | ||||||
|  |           ) | ||||||
|  |         }) | ||||||
|  |         const u = await getUtils(page) | ||||||
|  |         await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|  |         await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|  |         await page.getByText('line([74.36, 130.4], %)').click() | ||||||
|  |         await page.getByRole('button', { name: 'Edit Sketch' }).click() | ||||||
|  |  | ||||||
|  |         const line3 = await u.getSegmentBodyCoords( | ||||||
|  |           `[data-overlay-index="${2}"]` | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         await page.mouse.click(line3.x, line3.y) | ||||||
|  |         await page | ||||||
|  |           .getByRole('button', { | ||||||
|  |             name: 'Length: open menu', | ||||||
|  |           }) | ||||||
|  |           .click() | ||||||
|  |         await page.getByTestId('dropdown-constraint-' + constraint).click() | ||||||
|  |  | ||||||
|  |         if (!addVariable) { | ||||||
|  |           await test.step(`Clear the variable input`, async () => { | ||||||
|  |             await cmdBarKclVariableNameInput.clear() | ||||||
|  |             await cmdBarKclVariableNameInput.press('Backspace') | ||||||
|  |           }) | ||||||
|  |         } | ||||||
|  |         await expect(cmdBarKclInput).toHaveText('78.33') | ||||||
|  |         await cmdBarSubmitButton.click() | ||||||
|  |  | ||||||
|  |         const changedCode = `|> angledLine([${value}], %)` | ||||||
|  |         await expect(page.locator('.cm-content')).toContainText(changedCode) | ||||||
|  |         // checking active assures the cursor is where it should be | ||||||
|  |         await expect(page.locator('.cm-activeLine')).toHaveText(changedCode) | ||||||
|  |  | ||||||
|  |         // checking the count of the overlays is a good proxy check that the client sketch scene is in a good state | ||||||
|  |         await expect(page.getByTestId('segment-overlay')).toHaveCount(4) | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|   test.describe('Many segments - no modal constraints', () => { |   test.describe('Many segments - no modal constraints', () => { | ||||||
|     const cases = [ |     const cases = [ | ||||||
|       { |       { | ||||||
| @ -868,6 +949,15 @@ part002 = startSketchOn('XZ') | |||||||
|   |> line([3.13, -2.4], %)` |   |> line([3.13, -2.4], %)` | ||||||
|       ) |       ) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|  |     // constants and locators | ||||||
|  |     const cmdBarKclInput = page | ||||||
|  |       .getByTestId('cmd-bar-arg-value') | ||||||
|  |       .getByRole('textbox') | ||||||
|  |     const cmdBarSubmitButton = page.getByRole('button', { | ||||||
|  |       name: 'arrow right Continue', | ||||||
|  |     }) | ||||||
|  |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.setViewportSize({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
| @ -928,8 +1018,8 @@ part002 = startSketchOn('XZ') | |||||||
|     // await page.getByRole('button', { name: 'length', exact: true }).click() |     // await page.getByRole('button', { name: 'length', exact: true }).click() | ||||||
|     await page.getByTestId('dropdown-constraint-length').click() |     await page.getByTestId('dropdown-constraint-length').click() | ||||||
|  |  | ||||||
|     await page.getByLabel('length Value').fill('10') |     await cmdBarKclInput.fill('10') | ||||||
|     await page.getByRole('button', { name: 'Add constraining value' }).click() |     await cmdBarSubmitButton.click() | ||||||
|  |  | ||||||
|     activeLinesContent = await page.locator('.cm-activeLine').all() |     activeLinesContent = await page.locator('.cm-activeLine').all() | ||||||
|     await expect(activeLinesContent[0]).toHaveText(`|> xLine(length001, %)`) |     await expect(activeLinesContent[0]).toHaveText(`|> xLine(length001, %)`) | ||||||
|  | |||||||
| @ -23,7 +23,7 @@ test.describe('Test toggling perspective', () => { | |||||||
|       y: screenHeight * 0.4, |       y: screenHeight * 0.4, | ||||||
|     } |     } | ||||||
|     const backgroundColor: [number, number, number] = [29, 29, 29] |     const backgroundColor: [number, number, number] = [29, 29, 29] | ||||||
|     const xzPlaneColor: [number, number, number] = [50, 50, 99] |     const xzPlaneColor: [number, number, number] = [82, 55, 96] | ||||||
|     const locationToHaveColor = async (color: [number, number, number]) => { |     const locationToHaveColor = async (color: [number, number, number]) => { | ||||||
|       return u.getGreatestPixDiff(checkedScreenLocation, color) |       return u.getGreatestPixDiff(checkedScreenLocation, color) | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -91,7 +91,14 @@ test.describe('Testing segment overlays', () => { | |||||||
|           await page.getByTestId('constraint-symbol-popover').count() |           await page.getByTestId('constraint-symbol-popover').count() | ||||||
|         ).toBeGreaterThan(0) |         ).toBeGreaterThan(0) | ||||||
|         await unconstrainedLocator.click() |         await unconstrainedLocator.click() | ||||||
|         await page.getByText('Add variable').click() |         await expect( | ||||||
|  |           page.getByTestId('cmd-bar-arg-value').getByRole('textbox') | ||||||
|  |         ).toBeFocused() | ||||||
|  |         await page | ||||||
|  |           .getByRole('button', { | ||||||
|  |             name: 'arrow right Continue', | ||||||
|  |           }) | ||||||
|  |           .click() | ||||||
|         await expect(page.locator('.cm-content')).toContainText(expectFinal) |         await expect(page.locator('.cm-content')).toContainText(expectFinal) | ||||||
|       } |       } | ||||||
|  |  | ||||||
| @ -151,7 +158,14 @@ test.describe('Testing segment overlays', () => { | |||||||
|           await page.getByTestId('constraint-symbol-popover').count() |           await page.getByTestId('constraint-symbol-popover').count() | ||||||
|         ).toBeGreaterThan(0) |         ).toBeGreaterThan(0) | ||||||
|         await unconstrainedLocator.click() |         await unconstrainedLocator.click() | ||||||
|         await page.getByText('Add variable').click() |         await expect( | ||||||
|  |           page.getByTestId('cmd-bar-arg-value').getByRole('textbox') | ||||||
|  |         ).toBeFocused() | ||||||
|  |         await page | ||||||
|  |           .getByRole('button', { | ||||||
|  |             name: 'arrow right Continue', | ||||||
|  |           }) | ||||||
|  |           .click() | ||||||
|         await expect(page.locator('.cm-content')).toContainText( |         await expect(page.locator('.cm-content')).toContainText( | ||||||
|           expectAfterUnconstrained |           expectAfterUnconstrained | ||||||
|         ) |         ) | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								package.json
									
									
									
									
									
								
							
							
						
						| @ -81,6 +81,7 @@ | |||||||
|     "simpleserver": "yarn pretest && http-server ./public --cors -p 3000", |     "simpleserver": "yarn pretest && http-server ./public --cors -p 3000", | ||||||
|     "simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &", |     "simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &", | ||||||
|     "simpleserver:bg": "yarn pretest && http-server ./public --cors -p 3000 &", |     "simpleserver:bg": "yarn pretest && http-server ./public --cors -p 3000 &", | ||||||
|  |     "simpleserver:stop": "kill-port 3000", | ||||||
|     "fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages", |     "fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages", | ||||||
|     "fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages", |     "fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages", | ||||||
|     "fetch:wasm": "./get-latest-wasm-bundle.sh", |     "fetch:wasm": "./get-latest-wasm-bundle.sh", | ||||||
| @ -95,6 +96,8 @@ | |||||||
|     "files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json", |     "files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json", | ||||||
|     "files:set-notes": "./scripts/set-files-notes.sh", |     "files:set-notes": "./scripts/set-files-notes.sh", | ||||||
|     "files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh", |     "files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh", | ||||||
|  |     "files:invalidate-bucket": "./scripts/invalidate-files-bucket.sh", | ||||||
|  |     "files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly", | ||||||
|     "postinstall": "yarn fetch:samples && yarn xstate:typegen && ./node_modules/.bin/electron-rebuild", |     "postinstall": "yarn fetch:samples && yarn xstate:typegen && ./node_modules/.bin/electron-rebuild", | ||||||
|     "xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\"", |     "xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\"", | ||||||
|     "make:dev": "make dev", |     "make:dev": "make dev", | ||||||
| @ -158,6 +161,7 @@ | |||||||
|     "@electron/rebuild": "^3.6.0", |     "@electron/rebuild": "^3.6.0", | ||||||
|     "@iarna/toml": "^2.2.5", |     "@iarna/toml": "^2.2.5", | ||||||
|     "@lezer/generator": "^1.7.1", |     "@lezer/generator": "^1.7.1", | ||||||
|  |     "@nabla/vite-plugin-eslint": "^2.0.5", | ||||||
|     "@playwright/test": "^1.46.1", |     "@playwright/test": "^1.46.1", | ||||||
|     "@testing-library/jest-dom": "^5.14.1", |     "@testing-library/jest-dom": "^5.14.1", | ||||||
|     "@testing-library/react": "^15.0.2", |     "@testing-library/react": "^15.0.2", | ||||||
| @ -170,7 +174,7 @@ | |||||||
|     "@types/pixelmatch": "^5.2.6", |     "@types/pixelmatch": "^5.2.6", | ||||||
|     "@types/pngjs": "^6.0.4", |     "@types/pngjs": "^6.0.4", | ||||||
|     "@types/react": "^18.3.4", |     "@types/react": "^18.3.4", | ||||||
|     "@types/react-dom": "^18.2.25", |     "@types/react-dom": "^18.3.1", | ||||||
|     "@types/react-modal": "^3.16.3", |     "@types/react-modal": "^3.16.3", | ||||||
|     "@types/three": "^0.163.0", |     "@types/three": "^0.163.0", | ||||||
|     "@types/ua-parser-js": "^0.7.39", |     "@types/ua-parser-js": "^0.7.39", | ||||||
| @ -192,7 +196,7 @@ | |||||||
|     "eslint-plugin-css-modules": "^2.12.0", |     "eslint-plugin-css-modules": "^2.12.0", | ||||||
|     "eslint-plugin-import": "^2.30.0", |     "eslint-plugin-import": "^2.30.0", | ||||||
|     "eslint-plugin-suggest-no-throw": "^1.0.0", |     "eslint-plugin-suggest-no-throw": "^1.0.0", | ||||||
|     "happy-dom": "^15.10.2", |     "happy-dom": "^15.11.7", | ||||||
|     "http-server": "^14.1.1", |     "http-server": "^14.1.1", | ||||||
|     "husky": "^9.1.5", |     "husky": "^9.1.5", | ||||||
|     "kill-port": "^2.0.1", |     "kill-port": "^2.0.1", | ||||||
| @ -207,12 +211,11 @@ | |||||||
|     "ts-node": "^10.0.0", |     "ts-node": "^10.0.0", | ||||||
|     "typescript": "^5.7.2", |     "typescript": "^5.7.2", | ||||||
|     "vite": "^5.4.6", |     "vite": "^5.4.6", | ||||||
|     "vite-plugin-eslint": "^1.8.1", |  | ||||||
|     "vite-plugin-package-version": "^1.1.0", |     "vite-plugin-package-version": "^1.1.0", | ||||||
|     "vite-tsconfig-paths": "^4.3.2", |     "vite-tsconfig-paths": "^4.3.2", | ||||||
|     "vitest": "^1.6.0", |     "vitest": "^1.6.0", | ||||||
|     "vitest-webgl-canvas-mock": "^1.1.0", |     "vitest-webgl-canvas-mock": "^1.1.0", | ||||||
|     "wasm-pack": "^0.13.0", |     "wasm-pack": "^0.13.1", | ||||||
|     "ws": "^8.17.0", |     "ws": "^8.17.0", | ||||||
|     "yarn": "^1.22.22" |     "yarn": "^1.22.22" | ||||||
|   } |   } | ||||||
|  | |||||||
							
								
								
									
										5
									
								
								scripts/get-nightly-changelog.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @ -0,0 +1,5 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | echo "## What's Changed" | ||||||
|  | git log ${PREVIOUS_TAG}..HEAD --oneline --pretty=format:%s | grep -v Bump | awk '{print "* "toupper(substr($0,0,1))substr($0,2)}' | ||||||
|  | echo "" | ||||||
|  | echo "**Full Changelog**: https://github.com/KittyCAD/modeling-app/compare/${PREVIOUS_TAG}...${TAG}" | ||||||
							
								
								
									
										11
									
								
								scripts/invalidate-files-bucket.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @ -0,0 +1,11 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | base_dir="/releases/modeling-app" | ||||||
|  | if [[ $1 = "--nightly" ]]; then | ||||||
|  |     base_dir="/releases/modeling-app/nightly" | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | echo "Invalidating json and yml files at $base_dir in the download bucket" | ||||||
|  | gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/last_download.json" --async | ||||||
|  | gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/latest-linux-arm64.yml" --async | ||||||
|  | gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/latest-mac.yml" --async | ||||||
|  | gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/latest.yml" --async | ||||||
| @ -200,7 +200,10 @@ function CoreDump() { | |||||||
|     () => new CoreDumpManager(engineCommandManager, codeManager, token), |     () => new CoreDumpManager(engineCommandManager, codeManager, token), | ||||||
|     [] |     [] | ||||||
|   ) |   ) | ||||||
|   useHotkeyWrapper(['mod + shift + .'], () => { |   // TODO: revisit once progress is made on upstream issue | ||||||
|  |   // https://github.com/JohannesKlauss/react-hotkeys-hook/issues/1064 | ||||||
|  |   // const hotkey = process.platform !== 'linux' ? 'mod + shift + .' : 'mod + shift + >' | ||||||
|  |   useHotkeyWrapper(['mod + shift + .', 'mod + shift + >'], () => { | ||||||
|     toast |     toast | ||||||
|       .promise( |       .promise( | ||||||
|         coreDump(coreDumpManager, true), |         coreDump(coreDumpManager, true), | ||||||
|  | |||||||
| @ -155,7 +155,6 @@ export class CameraControls { | |||||||
|       this.camera.zoom = camProps.zoom || 1 |       this.camera.zoom = camProps.zoom || 1 | ||||||
|     } |     } | ||||||
|     this.camera.updateProjectionMatrix() |     this.camera.updateProjectionMatrix() | ||||||
|     console.log('doing this thing', camProps) |  | ||||||
|     this.update(true) |     this.update(true) | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @ -273,14 +272,26 @@ export class CameraControls { | |||||||
|         camSettings.center.y, |         camSettings.center.y, | ||||||
|         camSettings.center.z |         camSettings.center.z | ||||||
|       ) |       ) | ||||||
|       const quat = new Quaternion( |       const orientation = new Quaternion( | ||||||
|         camSettings.orientation.x, |         camSettings.orientation.x, | ||||||
|         camSettings.orientation.y, |         camSettings.orientation.y, | ||||||
|         camSettings.orientation.z, |         camSettings.orientation.z, | ||||||
|         camSettings.orientation.w |         camSettings.orientation.w | ||||||
|       ).invert() |       ).invert() | ||||||
|  |  | ||||||
|       this.camera.up.copy(new Vector3(0, 1, 0).applyQuaternion(quat)) |       const newUp = new Vector3( | ||||||
|  |         camSettings.up.x, | ||||||
|  |         camSettings.up.y, | ||||||
|  |         camSettings.up.z | ||||||
|  |       ) | ||||||
|  |       this.camera.quaternion.set( | ||||||
|  |         orientation.x, | ||||||
|  |         orientation.y, | ||||||
|  |         orientation.z, | ||||||
|  |         orientation.w | ||||||
|  |       ) | ||||||
|  |       this.camera.up.copy(newUp) | ||||||
|  |       this.camera.updateProjectionMatrix() | ||||||
|       if (this.camera instanceof PerspectiveCamera && camSettings.ortho) { |       if (this.camera instanceof PerspectiveCamera && camSettings.ortho) { | ||||||
|         this.useOrthographicCamera() |         this.useOrthographicCamera() | ||||||
|       } |       } | ||||||
| @ -1164,7 +1175,7 @@ export class CameraControls { | |||||||
|       this.camera.updateProjectionMatrix() |       this.camera.updateProjectionMatrix() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (this.syncDirection === 'clientToEngine' || forceUpdate) |     if (this.syncDirection === 'clientToEngine' || forceUpdate) { | ||||||
|       this.throttledUpdateEngineCamera({ |       this.throttledUpdateEngineCamera({ | ||||||
|         quaternion: this.camera.quaternion, |         quaternion: this.camera.quaternion, | ||||||
|         position: this.camera.position, |         position: this.camera.position, | ||||||
| @ -1172,6 +1183,7 @@ export class CameraControls { | |||||||
|         isPerspective: this.isPerspective, |         isPerspective: this.isPerspective, | ||||||
|         target: this.target, |         target: this.target, | ||||||
|       }) |       }) | ||||||
|  |     } | ||||||
|     this.deferReactUpdate(this.reactCameraProperties) |     this.deferReactUpdate(this.reactCameraProperties) | ||||||
|     Object.values(this._camChangeCallbacks).forEach((cb) => cb()) |     Object.values(this._camChangeCallbacks).forEach((cb) => cb()) | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -29,6 +29,9 @@ import { | |||||||
|   Expr, |   Expr, | ||||||
|   parse, |   parse, | ||||||
|   recast, |   recast, | ||||||
|  |   defaultSourceRange, | ||||||
|  |   resultIsOk, | ||||||
|  |   ProgramMemory, | ||||||
| } from 'lang/wasm' | } from 'lang/wasm' | ||||||
| import { CustomIcon, CustomIconName } from 'components/CustomIcon' | import { CustomIcon, CustomIconName } from 'components/CustomIcon' | ||||||
| import { ConstrainInfo } from 'lang/std/stdTypes' | import { ConstrainInfo } from 'lang/std/stdTypes' | ||||||
| @ -412,14 +415,15 @@ export async function deleteSegment({ | |||||||
|   if (err(modifiedAst)) return Promise.reject(modifiedAst) |   if (err(modifiedAst)) return Promise.reject(modifiedAst) | ||||||
|  |  | ||||||
|   const newCode = recast(modifiedAst) |   const newCode = recast(modifiedAst) | ||||||
|   modifiedAst = parse(newCode) |   const pResult = parse(newCode) | ||||||
|   if (err(modifiedAst)) return Promise.reject(modifiedAst) |   if (err(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult) | ||||||
|  |   modifiedAst = pResult.program | ||||||
|  |  | ||||||
|   const testExecute = await executeAst({ |   const testExecute = await executeAst({ | ||||||
|     ast: modifiedAst, |     ast: modifiedAst, | ||||||
|     idGenerator: kclManager.execState.idGenerator, |  | ||||||
|     useFakeExecutor: true, |  | ||||||
|     engineCommandManager: engineCommandManager, |     engineCommandManager: engineCommandManager, | ||||||
|  |     // We make sure to send an empty program memory to denote we mean mock mode. | ||||||
|  |     programMemoryOverride: ProgramMemory.empty(), | ||||||
|   }) |   }) | ||||||
|   if (testExecute.errors.length) { |   if (testExecute.errors.length) { | ||||||
|     toast.error('Segment tag used outside of current Sketch. Could not delete.') |     toast.error('Segment tag used outside of current Sketch. Could not delete.') | ||||||
| @ -501,7 +505,8 @@ const ConstraintSymbol = ({ | |||||||
|   constrainInfo: ConstrainInfo |   constrainInfo: ConstrainInfo | ||||||
|   verticalPosition: 'top' | 'bottom' |   verticalPosition: 'top' | 'bottom' | ||||||
| }) => { | }) => { | ||||||
|   const { context, send } = useModelingContext() |   const { commandBarSend } = useCommandsContext() | ||||||
|  |   const { context } = useModelingContext() | ||||||
|   const varNameMap: { |   const varNameMap: { | ||||||
|     [key in ConstrainInfo['type']]: { |     [key in ConstrainInfo['type']]: { | ||||||
|       varName: string |       varName: string | ||||||
| @ -590,7 +595,9 @@ const ConstraintSymbol = ({ | |||||||
|   if (err(_node)) return |   if (err(_node)) return | ||||||
|   const node = _node.node |   const node = _node.node | ||||||
|  |  | ||||||
|   const range: SourceRange = node ? [node.start, node.end] : [0, 0] |   const range: SourceRange = node | ||||||
|  |     ? [node.start, node.end, true] | ||||||
|  |     : defaultSourceRange() | ||||||
|  |  | ||||||
|   if (_type === 'intersectionTag') return null |   if (_type === 'intersectionTag') return null | ||||||
|  |  | ||||||
| @ -612,25 +619,34 @@ const ConstraintSymbol = ({ | |||||||
|           editorManager.setHighlightRange([range]) |           editorManager.setHighlightRange([range]) | ||||||
|         }} |         }} | ||||||
|         onMouseLeave={() => { |         onMouseLeave={() => { | ||||||
|           editorManager.setHighlightRange([[0, 0]]) |           editorManager.setHighlightRange([defaultSourceRange()]) | ||||||
|         }} |         }} | ||||||
|         // disabled={isConstrained || !convertToVarEnabled} |         // disabled={isConstrained || !convertToVarEnabled} | ||||||
|         // disabled={implicitDesc} TODO why does this change styles that are hard to override? |         // disabled={implicitDesc} TODO why does this change styles that are hard to override? | ||||||
|         onClick={toSync(async () => { |         onClick={toSync(async () => { | ||||||
|           if (!isConstrained) { |           if (!isConstrained) { | ||||||
|             send({ |             commandBarSend({ | ||||||
|               type: 'Convert to variable', |               type: 'Find and select command', | ||||||
|               data: { |               data: { | ||||||
|                 pathToNode, |                 name: 'Constrain with named value', | ||||||
|                 variableName: varName, |                 groupId: 'modeling', | ||||||
|  |                 argDefaultValues: { | ||||||
|  |                   currentValue: { | ||||||
|  |                     pathToNode, | ||||||
|  |                     variableName: varName, | ||||||
|  |                     valueText: value, | ||||||
|  |                   }, | ||||||
|  |                 }, | ||||||
|               }, |               }, | ||||||
|             }) |             }) | ||||||
|           } else if (isConstrained) { |           } else if (isConstrained) { | ||||||
|             try { |             try { | ||||||
|               const parsed = parse(recast(kclManager.ast)) |               const pResult = parse(recast(kclManager.ast)) | ||||||
|               if (trap(parsed)) return Promise.reject(parsed) |               if (trap(pResult) || !resultIsOk(pResult)) | ||||||
|  |                 return Promise.reject(pResult) | ||||||
|  |  | ||||||
|               const _node1 = getNodeFromPath<CallExpression>( |               const _node1 = getNodeFromPath<CallExpression>( | ||||||
|                 parsed, |                 pResult.program!, | ||||||
|                 pathToNode, |                 pathToNode, | ||||||
|                 'CallExpression', |                 'CallExpression', | ||||||
|                 true |                 true | ||||||
|  | |||||||
| @ -48,6 +48,9 @@ import { | |||||||
|   VariableDeclarator, |   VariableDeclarator, | ||||||
|   sketchFromKclValue, |   sketchFromKclValue, | ||||||
|   sketchFromKclValueOptional, |   sketchFromKclValueOptional, | ||||||
|  |   defaultSourceRange, | ||||||
|  |   sourceRangeFromRust, | ||||||
|  |   resultIsOk, | ||||||
| } from 'lang/wasm' | } from 'lang/wasm' | ||||||
| import { | import { | ||||||
|   engineCommandManager, |   engineCommandManager, | ||||||
| @ -495,10 +498,9 @@ export class SceneEntities { | |||||||
|  |  | ||||||
|     const { execState } = await executeAst({ |     const { execState } = await executeAst({ | ||||||
|       ast: truncatedAst, |       ast: truncatedAst, | ||||||
|       useFakeExecutor: true, |  | ||||||
|       engineCommandManager: this.engineCommandManager, |       engineCommandManager: this.engineCommandManager, | ||||||
|  |       // We make sure to send an empty program memory to denote we mean mock mode. | ||||||
|       programMemoryOverride, |       programMemoryOverride, | ||||||
|       idGenerator: kclManager.execState.idGenerator, |  | ||||||
|     }) |     }) | ||||||
|     const programMemory = execState.memory |     const programMemory = execState.memory | ||||||
|     const sketch = sketchFromPathToNode({ |     const sketch = sketchFromPathToNode({ | ||||||
| @ -530,7 +532,7 @@ export class SceneEntities { | |||||||
|  |  | ||||||
|     const segPathToNode = getNodePathFromSourceRange( |     const segPathToNode = getNodePathFromSourceRange( | ||||||
|       maybeModdedAst, |       maybeModdedAst, | ||||||
|       sketch.start.__geoMeta.sourceRange |       sourceRangeFromRust(sketch.start.__geoMeta.sourceRange) | ||||||
|     ) |     ) | ||||||
|     if (sketch?.paths?.[0]?.type !== 'Circle') { |     if (sketch?.paths?.[0]?.type !== 'Circle') { | ||||||
|       const _profileStart = createProfileStartHandle({ |       const _profileStart = createProfileStartHandle({ | ||||||
| @ -552,7 +554,7 @@ export class SceneEntities { | |||||||
|     sketch.paths.forEach((segment, index) => { |     sketch.paths.forEach((segment, index) => { | ||||||
|       let segPathToNode = getNodePathFromSourceRange( |       let segPathToNode = getNodePathFromSourceRange( | ||||||
|         maybeModdedAst, |         maybeModdedAst, | ||||||
|         segment.__geoMeta.sourceRange |         sourceRangeFromRust(segment.__geoMeta.sourceRange) | ||||||
|       ) |       ) | ||||||
|       if ( |       if ( | ||||||
|         draftExpressionsIndices && |         draftExpressionsIndices && | ||||||
| @ -561,12 +563,12 @@ export class SceneEntities { | |||||||
|         const previousSegment = sketch.paths[index - 1] || sketch.start |         const previousSegment = sketch.paths[index - 1] || sketch.start | ||||||
|         const previousSegmentPathToNode = getNodePathFromSourceRange( |         const previousSegmentPathToNode = getNodePathFromSourceRange( | ||||||
|           maybeModdedAst, |           maybeModdedAst, | ||||||
|           previousSegment.__geoMeta.sourceRange |           sourceRangeFromRust(previousSegment.__geoMeta.sourceRange) | ||||||
|         ) |         ) | ||||||
|         const bodyIndex = previousSegmentPathToNode[1][0] |         const bodyIndex = previousSegmentPathToNode[1][0] | ||||||
|         segPathToNode = getNodePathFromSourceRange( |         segPathToNode = getNodePathFromSourceRange( | ||||||
|           truncatedAst, |           truncatedAst, | ||||||
|           segment.__geoMeta.sourceRange |           sourceRangeFromRust(segment.__geoMeta.sourceRange) | ||||||
|         ) |         ) | ||||||
|         segPathToNode[1][0] = bodyIndex |         segPathToNode[1][0] = bodyIndex | ||||||
|       } |       } | ||||||
| @ -575,7 +577,10 @@ export class SceneEntities { | |||||||
|         index <= draftExpressionsIndices.end && |         index <= draftExpressionsIndices.end && | ||||||
|         index >= draftExpressionsIndices.start |         index >= draftExpressionsIndices.start | ||||||
|       const isSelected = selectionRanges?.graphSelections.some((selection) => |       const isSelected = selectionRanges?.graphSelections.some((selection) => | ||||||
|         isOverlap(selection?.codeRef?.range, segment.__geoMeta.sourceRange) |         isOverlap( | ||||||
|  |           selection?.codeRef?.range, | ||||||
|  |           sourceRangeFromRust(segment.__geoMeta.sourceRange) | ||||||
|  |         ) | ||||||
|       ) |       ) | ||||||
|  |  | ||||||
|       let seg: Group |       let seg: Group | ||||||
| @ -657,13 +662,11 @@ export class SceneEntities { | |||||||
|   } |   } | ||||||
|   updateAstAndRejigSketch = async ( |   updateAstAndRejigSketch = async ( | ||||||
|     sketchPathToNode: PathToNode, |     sketchPathToNode: PathToNode, | ||||||
|     modifiedAst: Node<Program> | Error, |     modifiedAst: Node<Program>, | ||||||
|     forward: [number, number, number], |     forward: [number, number, number], | ||||||
|     up: [number, number, number], |     up: [number, number, number], | ||||||
|     origin: [number, number, number] |     origin: [number, number, number] | ||||||
|   ) => { |   ) => { | ||||||
|     if (err(modifiedAst)) return modifiedAst |  | ||||||
|  |  | ||||||
|     const nextAst = await kclManager.updateAst(modifiedAst, false) |     const nextAst = await kclManager.updateAst(modifiedAst, false) | ||||||
|     await this.tearDownSketch({ removeAxis: false }) |     await this.tearDownSketch({ removeAxis: false }) | ||||||
|     sceneInfra.resetMouseListeners() |     sceneInfra.resetMouseListeners() | ||||||
| @ -698,8 +701,7 @@ export class SceneEntities { | |||||||
|       'VariableDeclaration' |       'VariableDeclaration' | ||||||
|     ) |     ) | ||||||
|     if (trap(_node1)) return Promise.reject(_node1) |     if (trap(_node1)) return Promise.reject(_node1) | ||||||
|     const variableDeclarationName = |     const variableDeclarationName = _node1.node?.declaration.id?.name || '' | ||||||
|       _node1.node?.declarations?.[0]?.id?.name || '' |  | ||||||
|  |  | ||||||
|     const sg = sketchFromKclValue( |     const sg = sketchFromKclValue( | ||||||
|       kclManager.programMemory.get(variableDeclarationName), |       kclManager.programMemory.get(variableDeclarationName), | ||||||
| @ -721,8 +723,9 @@ export class SceneEntities { | |||||||
|       pathToNode: sketchPathToNode, |       pathToNode: sketchPathToNode, | ||||||
|     }) |     }) | ||||||
|     if (trap(mod)) return Promise.reject(mod) |     if (trap(mod)) return Promise.reject(mod) | ||||||
|     const modifiedAst = parse(recast(mod.modifiedAst)) |     const pResult = parse(recast(mod.modifiedAst)) | ||||||
|     if (trap(modifiedAst)) return Promise.reject(modifiedAst) |     if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult) | ||||||
|  |     const modifiedAst = pResult.program | ||||||
|  |  | ||||||
|     const draftExpressionsIndices = { start: index, end: index } |     const draftExpressionsIndices = { start: index, end: index } | ||||||
|  |  | ||||||
| @ -898,10 +901,9 @@ export class SceneEntities { | |||||||
|       'VariableDeclaration' |       'VariableDeclaration' | ||||||
|     ) |     ) | ||||||
|     if (trap(_node1)) return Promise.reject(_node1) |     if (trap(_node1)) return Promise.reject(_node1) | ||||||
|     const variableDeclarationName = |     const variableDeclarationName = _node1.node?.declaration.id?.name || '' | ||||||
|       _node1.node?.declarations?.[0]?.id?.name || '' |     const startSketchOn = _node1.node?.declaration | ||||||
|     const startSketchOn = _node1.node?.declarations |     const startSketchOnInit = startSketchOn?.init | ||||||
|     const startSketchOnInit = startSketchOn?.[0]?.init |  | ||||||
|  |  | ||||||
|     const tags: [string, string, string] = [ |     const tags: [string, string, string] = [ | ||||||
|       findUniqueName(_ast, 'rectangleSegmentA'), |       findUniqueName(_ast, 'rectangleSegmentA'), | ||||||
| @ -909,14 +911,14 @@ export class SceneEntities { | |||||||
|       findUniqueName(_ast, 'rectangleSegmentC'), |       findUniqueName(_ast, 'rectangleSegmentC'), | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|     startSketchOn[0].init = createPipeExpression([ |     startSketchOn.init = createPipeExpression([ | ||||||
|       startSketchOnInit, |       startSketchOnInit, | ||||||
|       ...getRectangleCallExpressions(rectangleOrigin, tags), |       ...getRectangleCallExpressions(rectangleOrigin, tags), | ||||||
|     ]) |     ]) | ||||||
|  |  | ||||||
|     let _recastAst = parse(recast(_ast)) |     const pResult = parse(recast(_ast)) | ||||||
|     if (trap(_recastAst)) return Promise.reject(_recastAst) |     if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult) | ||||||
|     _ast = _recastAst |     _ast = pResult.program | ||||||
|  |  | ||||||
|     const { programMemoryOverride, truncatedAst } = await this.setupSketch({ |     const { programMemoryOverride, truncatedAst } = await this.setupSketch({ | ||||||
|       sketchPathToNode, |       sketchPathToNode, | ||||||
| @ -939,7 +941,7 @@ export class SceneEntities { | |||||||
|           'VariableDeclaration' |           'VariableDeclaration' | ||||||
|         ) |         ) | ||||||
|         if (trap(_node)) return Promise.reject(_node) |         if (trap(_node)) return Promise.reject(_node) | ||||||
|         const sketchInit = _node.node?.declarations?.[0]?.init |         const sketchInit = _node.node?.declaration.init | ||||||
|  |  | ||||||
|         const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0] |         const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0] | ||||||
|         const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1] |         const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1] | ||||||
| @ -950,10 +952,9 @@ export class SceneEntities { | |||||||
|  |  | ||||||
|         const { execState } = await executeAst({ |         const { execState } = await executeAst({ | ||||||
|           ast: truncatedAst, |           ast: truncatedAst, | ||||||
|           useFakeExecutor: true, |  | ||||||
|           engineCommandManager: this.engineCommandManager, |           engineCommandManager: this.engineCommandManager, | ||||||
|  |           // We make sure to send an empty program memory to denote we mean mock mode. | ||||||
|           programMemoryOverride, |           programMemoryOverride, | ||||||
|           idGenerator: kclManager.execState.idGenerator, |  | ||||||
|         }) |         }) | ||||||
|         const programMemory = execState.memory |         const programMemory = execState.memory | ||||||
|         this.sceneProgramMemory = programMemory |         this.sceneProgramMemory = programMemory | ||||||
| @ -989,7 +990,7 @@ export class SceneEntities { | |||||||
|           'VariableDeclaration' |           'VariableDeclaration' | ||||||
|         ) |         ) | ||||||
|         if (trap(_node)) return |         if (trap(_node)) return | ||||||
|         const sketchInit = _node.node?.declarations?.[0]?.init |         const sketchInit = _node.node?.declaration.init | ||||||
|  |  | ||||||
|         if (sketchInit.type !== 'PipeExpression') { |         if (sketchInit.type !== 'PipeExpression') { | ||||||
|           return |           return | ||||||
| @ -998,9 +999,10 @@ export class SceneEntities { | |||||||
|         updateRectangleSketch(sketchInit, x, y, tags[0]) |         updateRectangleSketch(sketchInit, x, y, tags[0]) | ||||||
|  |  | ||||||
|         const newCode = recast(_ast) |         const newCode = recast(_ast) | ||||||
|         let _recastAst = parse(newCode) |         const pResult = parse(newCode) | ||||||
|         if (trap(_recastAst)) return |         if (trap(pResult) || !resultIsOk(pResult)) | ||||||
|         _ast = _recastAst |           return Promise.reject(pResult) | ||||||
|  |         _ast = pResult.program | ||||||
|  |  | ||||||
|         // Update the primary AST and unequip the rectangle tool |         // Update the primary AST and unequip the rectangle tool | ||||||
|         await kclManager.executeAstMock(_ast) |         await kclManager.executeAstMock(_ast) | ||||||
| @ -1013,10 +1015,9 @@ export class SceneEntities { | |||||||
|  |  | ||||||
|         const { execState } = await executeAst({ |         const { execState } = await executeAst({ | ||||||
|           ast: _ast, |           ast: _ast, | ||||||
|           useFakeExecutor: true, |  | ||||||
|           engineCommandManager: this.engineCommandManager, |           engineCommandManager: this.engineCommandManager, | ||||||
|  |           // We make sure to send an empty program memory to denote we mean mock mode. | ||||||
|           programMemoryOverride, |           programMemoryOverride, | ||||||
|           idGenerator: kclManager.execState.idGenerator, |  | ||||||
|         }) |         }) | ||||||
|         const programMemory = execState.memory |         const programMemory = execState.memory | ||||||
|  |  | ||||||
| @ -1055,10 +1056,9 @@ export class SceneEntities { | |||||||
|     if (trap(_node1)) return Promise.reject(_node1) |     if (trap(_node1)) return Promise.reject(_node1) | ||||||
|  |  | ||||||
|     // startSketchOn already exists |     // startSketchOn already exists | ||||||
|     const variableDeclarationName = |     const variableDeclarationName = _node1.node?.declaration.id?.name || '' | ||||||
|       _node1.node?.declarations?.[0]?.id?.name || '' |     const startSketchOn = _node1.node?.declaration | ||||||
|     const startSketchOn = _node1.node?.declarations |     const startSketchOnInit = startSketchOn?.init | ||||||
|     const startSketchOnInit = startSketchOn?.[0]?.init |  | ||||||
|  |  | ||||||
|     const tags: [string, string, string] = [ |     const tags: [string, string, string] = [ | ||||||
|       findUniqueName(_ast, 'rectangleSegmentA'), |       findUniqueName(_ast, 'rectangleSegmentA'), | ||||||
| @ -1066,14 +1066,14 @@ export class SceneEntities { | |||||||
|       findUniqueName(_ast, 'rectangleSegmentC'), |       findUniqueName(_ast, 'rectangleSegmentC'), | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|     startSketchOn[0].init = createPipeExpression([ |     startSketchOn.init = createPipeExpression([ | ||||||
|       startSketchOnInit, |       startSketchOnInit, | ||||||
|       ...getRectangleCallExpressions(rectangleOrigin, tags), |       ...getRectangleCallExpressions(rectangleOrigin, tags), | ||||||
|     ]) |     ]) | ||||||
|  |  | ||||||
|     let _recastAst = parse(recast(_ast)) |     const pResult = parse(recast(_ast)) | ||||||
|     if (trap(_recastAst)) return Promise.reject(_recastAst) |     if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult) | ||||||
|     _ast = _recastAst |     _ast = pResult.program | ||||||
|  |  | ||||||
|     const { programMemoryOverride, truncatedAst } = await this.setupSketch({ |     const { programMemoryOverride, truncatedAst } = await this.setupSketch({ | ||||||
|       sketchPathToNode, |       sketchPathToNode, | ||||||
| @ -1096,7 +1096,7 @@ export class SceneEntities { | |||||||
|           'VariableDeclaration' |           'VariableDeclaration' | ||||||
|         ) |         ) | ||||||
|         if (trap(_node)) return Promise.reject(_node) |         if (trap(_node)) return Promise.reject(_node) | ||||||
|         const sketchInit = _node.node?.declarations?.[0]?.init |         const sketchInit = _node.node?.declaration.init | ||||||
|  |  | ||||||
|         const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0] |         const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0] | ||||||
|         const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1] |         const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1] | ||||||
| @ -1114,10 +1114,9 @@ export class SceneEntities { | |||||||
|  |  | ||||||
|         const { execState } = await executeAst({ |         const { execState } = await executeAst({ | ||||||
|           ast: truncatedAst, |           ast: truncatedAst, | ||||||
|           useFakeExecutor: true, |  | ||||||
|           engineCommandManager: this.engineCommandManager, |           engineCommandManager: this.engineCommandManager, | ||||||
|  |           // We make sure to send an empty program memory to denote we mean mock mode. | ||||||
|           programMemoryOverride, |           programMemoryOverride, | ||||||
|           idGenerator: kclManager.execState.idGenerator, |  | ||||||
|         }) |         }) | ||||||
|         const programMemory = execState.memory |         const programMemory = execState.memory | ||||||
|         this.sceneProgramMemory = programMemory |         this.sceneProgramMemory = programMemory | ||||||
| @ -1153,7 +1152,7 @@ export class SceneEntities { | |||||||
|           'VariableDeclaration' |           'VariableDeclaration' | ||||||
|         ) |         ) | ||||||
|         if (trap(_node)) return |         if (trap(_node)) return | ||||||
|         const sketchInit = _node.node?.declarations?.[0]?.init |         const sketchInit = _node.node?.declaration.init | ||||||
|  |  | ||||||
|         if (sketchInit.type === 'PipeExpression') { |         if (sketchInit.type === 'PipeExpression') { | ||||||
|           updateCenterRectangleSketch( |           updateCenterRectangleSketch( | ||||||
| @ -1165,9 +1164,10 @@ export class SceneEntities { | |||||||
|             rectangleOrigin[1] |             rectangleOrigin[1] | ||||||
|           ) |           ) | ||||||
|  |  | ||||||
|           let _recastAst = parse(recast(_ast)) |           const pResult = parse(recast(_ast)) | ||||||
|           if (trap(_recastAst)) return |           if (trap(pResult) || !resultIsOk(pResult)) | ||||||
|           _ast = _recastAst |             return Promise.reject(pResult) | ||||||
|  |           _ast = pResult.program | ||||||
|  |  | ||||||
|           // Update the primary AST and unequip the rectangle tool |           // Update the primary AST and unequip the rectangle tool | ||||||
|           await kclManager.executeAstMock(_ast) |           await kclManager.executeAstMock(_ast) | ||||||
| @ -1180,10 +1180,9 @@ export class SceneEntities { | |||||||
|  |  | ||||||
|           const { execState } = await executeAst({ |           const { execState } = await executeAst({ | ||||||
|             ast: _ast, |             ast: _ast, | ||||||
|             useFakeExecutor: true, |  | ||||||
|             engineCommandManager: this.engineCommandManager, |             engineCommandManager: this.engineCommandManager, | ||||||
|  |             // We make sure to send an empty program memory to denote we mean mock mode. | ||||||
|             programMemoryOverride, |             programMemoryOverride, | ||||||
|             idGenerator: kclManager.execState.idGenerator, |  | ||||||
|           }) |           }) | ||||||
|           const programMemory = execState.memory |           const programMemory = execState.memory | ||||||
|  |  | ||||||
| @ -1222,12 +1221,11 @@ export class SceneEntities { | |||||||
|       'VariableDeclaration' |       'VariableDeclaration' | ||||||
|     ) |     ) | ||||||
|     if (trap(_node1)) return Promise.reject(_node1) |     if (trap(_node1)) return Promise.reject(_node1) | ||||||
|     const variableDeclarationName = |     const variableDeclarationName = _node1.node?.declaration.id?.name || '' | ||||||
|       _node1.node?.declarations?.[0]?.id?.name || '' |     const startSketchOn = _node1.node?.declaration | ||||||
|     const startSketchOn = _node1.node?.declarations |     const startSketchOnInit = startSketchOn?.init | ||||||
|     const startSketchOnInit = startSketchOn?.[0]?.init |  | ||||||
|  |  | ||||||
|     startSketchOn[0].init = createPipeExpression([ |     startSketchOn.init = createPipeExpression([ | ||||||
|       startSketchOnInit, |       startSketchOnInit, | ||||||
|       createCallExpressionStdLib('circle', [ |       createCallExpressionStdLib('circle', [ | ||||||
|         createObjectExpression({ |         createObjectExpression({ | ||||||
| @ -1241,9 +1239,9 @@ export class SceneEntities { | |||||||
|       ]), |       ]), | ||||||
|     ]) |     ]) | ||||||
|  |  | ||||||
|     let _recastAst = parse(recast(_ast)) |     const pResult = parse(recast(_ast)) | ||||||
|     if (trap(_recastAst)) return Promise.reject(_recastAst) |     if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult) | ||||||
|     _ast = _recastAst |     _ast = pResult.program | ||||||
|  |  | ||||||
|     // do a quick mock execution to get the program memory up-to-date |     // do a quick mock execution to get the program memory up-to-date | ||||||
|     await kclManager.executeAstMock(_ast) |     await kclManager.executeAstMock(_ast) | ||||||
| @ -1269,7 +1267,7 @@ export class SceneEntities { | |||||||
|         ) |         ) | ||||||
|         let modded = structuredClone(truncatedAst) |         let modded = structuredClone(truncatedAst) | ||||||
|         if (trap(_node)) return |         if (trap(_node)) return | ||||||
|         const sketchInit = _node.node?.declarations?.[0]?.init |         const sketchInit = _node.node.declaration.init | ||||||
|  |  | ||||||
|         const x = (args.intersectionPoint.twoD.x || 0) - circleCenter[0] |         const x = (args.intersectionPoint.twoD.x || 0) - circleCenter[0] | ||||||
|         const y = (args.intersectionPoint.twoD.y || 0) - circleCenter[1] |         const y = (args.intersectionPoint.twoD.y || 0) - circleCenter[1] | ||||||
| @ -1299,10 +1297,9 @@ export class SceneEntities { | |||||||
|  |  | ||||||
|         const { execState } = await executeAst({ |         const { execState } = await executeAst({ | ||||||
|           ast: modded, |           ast: modded, | ||||||
|           useFakeExecutor: true, |  | ||||||
|           engineCommandManager: this.engineCommandManager, |           engineCommandManager: this.engineCommandManager, | ||||||
|  |           // We make sure to send an empty program memory to denote we mean mock mode. | ||||||
|           programMemoryOverride, |           programMemoryOverride, | ||||||
|           idGenerator: kclManager.execState.idGenerator, |  | ||||||
|         }) |         }) | ||||||
|         const programMemory = execState.memory |         const programMemory = execState.memory | ||||||
|         this.sceneProgramMemory = programMemory |         this.sceneProgramMemory = programMemory | ||||||
| @ -1338,7 +1335,7 @@ export class SceneEntities { | |||||||
|           'VariableDeclaration' |           'VariableDeclaration' | ||||||
|         ) |         ) | ||||||
|         if (trap(_node)) return |         if (trap(_node)) return | ||||||
|         const sketchInit = _node.node?.declarations?.[0]?.init |         const sketchInit = _node.node?.declaration.init | ||||||
|  |  | ||||||
|         let modded = structuredClone(_ast) |         let modded = structuredClone(_ast) | ||||||
|         if (sketchInit.type === 'PipeExpression') { |         if (sketchInit.type === 'PipeExpression') { | ||||||
| @ -1365,9 +1362,10 @@ export class SceneEntities { | |||||||
|  |  | ||||||
|           const newCode = recast(modded) |           const newCode = recast(modded) | ||||||
|           if (err(newCode)) return |           if (err(newCode)) return | ||||||
|           let _recastAst = parse(newCode) |           const pResult = parse(newCode) | ||||||
|           if (trap(_recastAst)) return Promise.reject(_recastAst) |           if (trap(pResult) || !resultIsOk(pResult)) | ||||||
|           _ast = _recastAst |             return Promise.reject(pResult) | ||||||
|  |           _ast = pResult.program | ||||||
|  |  | ||||||
|           // Update the primary AST and unequip the rectangle tool |           // Update the primary AST and unequip the rectangle tool | ||||||
|           await kclManager.executeAstMock(_ast) |           await kclManager.executeAstMock(_ast) | ||||||
| @ -1660,7 +1658,7 @@ export class SceneEntities { | |||||||
|         kclManager.programMemory, |         kclManager.programMemory, | ||||||
|         { |         { | ||||||
|           type: 'sourceRange', |           type: 'sourceRange', | ||||||
|           sourceRange: [node.start, node.end], |           sourceRange: [node.start, node.end, true], | ||||||
|         }, |         }, | ||||||
|         getChangeSketchInput() |         getChangeSketchInput() | ||||||
|       ) |       ) | ||||||
| @ -1683,10 +1681,9 @@ export class SceneEntities { | |||||||
|         codeManager.updateCodeEditor(code) |         codeManager.updateCodeEditor(code) | ||||||
|       const { execState } = await executeAst({ |       const { execState } = await executeAst({ | ||||||
|         ast: truncatedAst, |         ast: truncatedAst, | ||||||
|         useFakeExecutor: true, |  | ||||||
|         engineCommandManager: this.engineCommandManager, |         engineCommandManager: this.engineCommandManager, | ||||||
|  |         // We make sure to send an empty program memory to denote we mean mock mode. | ||||||
|         programMemoryOverride, |         programMemoryOverride, | ||||||
|         idGenerator: kclManager.execState.idGenerator, |  | ||||||
|       }) |       }) | ||||||
|       const programMemory = execState.memory |       const programMemory = execState.memory | ||||||
|       this.sceneProgramMemory = programMemory |       this.sceneProgramMemory = programMemory | ||||||
| @ -1750,7 +1747,7 @@ export class SceneEntities { | |||||||
|   ): (() => SegmentOverlayPayload | null) => { |   ): (() => SegmentOverlayPayload | null) => { | ||||||
|     const segPathToNode = getNodePathFromSourceRange( |     const segPathToNode = getNodePathFromSourceRange( | ||||||
|       modifiedAst, |       modifiedAst, | ||||||
|       segment.__geoMeta.sourceRange |       sourceRangeFromRust(segment.__geoMeta.sourceRange) | ||||||
|     ) |     ) | ||||||
|     const sgPaths = sketch.paths |     const sgPaths = sketch.paths | ||||||
|     const originalPathToNodeStr = JSON.stringify(segPathToNode) |     const originalPathToNodeStr = JSON.stringify(segPathToNode) | ||||||
| @ -1901,8 +1898,10 @@ export class SceneEntities { | |||||||
|           SEGMENT_BODIES_PLUS_PROFILE_START |           SEGMENT_BODIES_PLUS_PROFILE_START | ||||||
|         ) |         ) | ||||||
|         if (parent?.userData?.pathToNode) { |         if (parent?.userData?.pathToNode) { | ||||||
|           const updatedAst = parse(recast(kclManager.ast)) |           const pResult = parse(recast(kclManager.ast)) | ||||||
|           if (trap(updatedAst)) return |           if (trap(pResult) || !resultIsOk(pResult)) | ||||||
|  |             return Promise.reject(pResult) | ||||||
|  |           const updatedAst = pResult.program | ||||||
|           const _node = getNodeFromPath<Node<CallExpression>>( |           const _node = getNodeFromPath<Node<CallExpression>>( | ||||||
|             updatedAst, |             updatedAst, | ||||||
|             parent.userData.pathToNode, |             parent.userData.pathToNode, | ||||||
| @ -1910,7 +1909,7 @@ export class SceneEntities { | |||||||
|           ) |           ) | ||||||
|           if (trap(_node, { suppress: true })) return |           if (trap(_node, { suppress: true })) return | ||||||
|           const node = _node.node |           const node = _node.node | ||||||
|           editorManager.setHighlightRange([[node.start, node.end]]) |           editorManager.setHighlightRange([[node.start, node.end, true]]) | ||||||
|           const yellow = 0xffff00 |           const yellow = 0xffff00 | ||||||
|           colorSegment(selected, yellow) |           colorSegment(selected, yellow) | ||||||
|           const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE) |           const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE) | ||||||
| @ -1955,10 +1954,10 @@ export class SceneEntities { | |||||||
|             }) |             }) | ||||||
|           return |           return | ||||||
|         } |         } | ||||||
|         editorManager.setHighlightRange([[0, 0]]) |         editorManager.setHighlightRange([defaultSourceRange()]) | ||||||
|       }, |       }, | ||||||
|       onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => { |       onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => { | ||||||
|         editorManager.setHighlightRange([[0, 0]]) |         editorManager.setHighlightRange([defaultSourceRange()]) | ||||||
|         const parent = getParentGroup( |         const parent = getParentGroup( | ||||||
|           selected, |           selected, | ||||||
|           SEGMENT_BODIES_PLUS_PROFILE_START |           SEGMENT_BODIES_PLUS_PROFILE_START | ||||||
| @ -2057,7 +2056,7 @@ function prepareTruncatedMemoryAndAst( | |||||||
|     'VariableDeclaration' |     'VariableDeclaration' | ||||||
|   ) |   ) | ||||||
|   if (err(_node)) return _node |   if (err(_node)) return _node | ||||||
|   const variableDeclarationName = _node.node?.declarations?.[0]?.id?.name || '' |   const variableDeclarationName = _node.node?.declaration.id?.name || '' | ||||||
|   const sg = sketchFromKclValue( |   const sg = sketchFromKclValue( | ||||||
|     programMemory.get(variableDeclarationName), |     programMemory.get(variableDeclarationName), | ||||||
|     variableDeclarationName |     variableDeclarationName | ||||||
| @ -2082,28 +2081,30 @@ function prepareTruncatedMemoryAndAst( | |||||||
|       ]) |       ]) | ||||||
|     } |     } | ||||||
|     ;( |     ;( | ||||||
|       (_ast.body[bodyIndex] as VariableDeclaration).declarations[0] |       (_ast.body[bodyIndex] as VariableDeclaration).declaration | ||||||
|         .init as PipeExpression |         .init as PipeExpression | ||||||
|     ).body.push(newSegment) |     ).body.push(newSegment) | ||||||
|     // update source ranges to section we just added. |     // update source ranges to section we just added. | ||||||
|     // hacks like this wouldn't be needed if the AST put pathToNode info in memory/sketch segments |     // hacks like this wouldn't be needed if the AST put pathToNode info in memory/sketch segments | ||||||
|     const updatedSrcRangeAst = parse(recast(_ast)) // get source ranges correct since unfortunately we still rely on them |     const pResult = parse(recast(_ast)) // get source ranges correct since unfortunately we still rely on them | ||||||
|     if (err(updatedSrcRangeAst)) return updatedSrcRangeAst |     if (trap(pResult) || !resultIsOk(pResult)) | ||||||
|  |       return Error('Unexpected compilation error') | ||||||
|  |     const updatedSrcRangeAst = pResult.program | ||||||
|  |  | ||||||
|     const lastPipeItem = ( |     const lastPipeItem = ( | ||||||
|       (updatedSrcRangeAst.body[bodyIndex] as VariableDeclaration) |       (updatedSrcRangeAst.body[bodyIndex] as VariableDeclaration).declaration | ||||||
|         .declarations[0].init as PipeExpression |         .init as PipeExpression | ||||||
|     ).body.slice(-1)[0] |     ).body.slice(-1)[0] | ||||||
|  |  | ||||||
|     ;( |     ;( | ||||||
|       (_ast.body[bodyIndex] as VariableDeclaration).declarations[0] |       (_ast.body[bodyIndex] as VariableDeclaration).declaration | ||||||
|         .init as PipeExpression |         .init as PipeExpression | ||||||
|     ).body.slice(-1)[0].start = lastPipeItem.start |     ).body.slice(-1)[0].start = lastPipeItem.start | ||||||
|  |  | ||||||
|     _ast.end = lastPipeItem.end |     _ast.end = lastPipeItem.end | ||||||
|     const varDec = _ast.body[bodyIndex] as Node<VariableDeclaration> |     const varDec = _ast.body[bodyIndex] as Node<VariableDeclaration> | ||||||
|     varDec.end = lastPipeItem.end |     varDec.end = lastPipeItem.end | ||||||
|     const declarator = varDec.declarations[0] |     const declarator = varDec.declaration | ||||||
|     declarator.end = lastPipeItem.end |     declarator.end = lastPipeItem.end | ||||||
|     const init = declarator.init as Node<PipeExpression> |     const init = declarator.init as Node<PipeExpression> | ||||||
|     init.end = lastPipeItem.end |     init.end = lastPipeItem.end | ||||||
| @ -2140,7 +2141,7 @@ function prepareTruncatedMemoryAndAst( | |||||||
|     if (node.type !== 'VariableDeclaration') { |     if (node.type !== 'VariableDeclaration') { | ||||||
|       continue |       continue | ||||||
|     } |     } | ||||||
|     const name = node.declarations[0].id.name |     const name = node.declaration.id.name | ||||||
|     const memoryItem = programMemory.get(name) |     const memoryItem = programMemory.get(name) | ||||||
|     if (!memoryItem) { |     if (!memoryItem) { | ||||||
|       continue |       continue | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ import { useEffect, useRef, useState } from 'react' | |||||||
| import { trap } from 'lib/trap' | import { trap } from 'lib/trap' | ||||||
| import { codeToIdSelections } from 'lib/selections' | import { codeToIdSelections } from 'lib/selections' | ||||||
| import { codeRefFromRange } from 'lang/std/artifactGraph' | import { codeRefFromRange } from 'lang/std/artifactGraph' | ||||||
|  | import { defaultSourceRange } from 'lang/wasm' | ||||||
|  |  | ||||||
| export function AstExplorer() { | export function AstExplorer() { | ||||||
|   const { context } = useModelingContext() |   const { context } = useModelingContext() | ||||||
| @ -46,7 +47,7 @@ export function AstExplorer() { | |||||||
|       <div |       <div | ||||||
|         className="h-full relative" |         className="h-full relative" | ||||||
|         onMouseLeave={(e) => { |         onMouseLeave={(e) => { | ||||||
|           editorManager.setHighlightRange([[0, 0]]) |           editorManager.setHighlightRange([defaultSourceRange()]) | ||||||
|         }} |         }} | ||||||
|       > |       > | ||||||
|         <pre className="text-xs"> |         <pre className="text-xs"> | ||||||
| @ -115,15 +116,19 @@ function DisplayObj({ | |||||||
|         hasCursor ? 'bg-violet-100/80 dark:bg-violet-100/25' : '' |         hasCursor ? 'bg-violet-100/80 dark:bg-violet-100/25' : '' | ||||||
|       }`} |       }`} | ||||||
|       onMouseEnter={(e) => { |       onMouseEnter={(e) => { | ||||||
|         editorManager.setHighlightRange([[obj?.start || 0, obj.end]]) |         editorManager.setHighlightRange([[obj?.start || 0, obj.end, true]]) | ||||||
|         e.stopPropagation() |         e.stopPropagation() | ||||||
|       }} |       }} | ||||||
|       onMouseMove={(e) => { |       onMouseMove={(e) => { | ||||||
|         e.stopPropagation() |         e.stopPropagation() | ||||||
|         editorManager.setHighlightRange([[obj?.start || 0, obj.end]]) |         editorManager.setHighlightRange([[obj?.start || 0, obj.end, true]]) | ||||||
|       }} |       }} | ||||||
|       onClick={(e) => { |       onClick={(e) => { | ||||||
|         const range: [number, number] = [obj?.start || 0, obj.end || 0] |         const range: [number, number, boolean] = [ | ||||||
|  |           obj?.start || 0, | ||||||
|  |           obj.end || 0, | ||||||
|  |           true, | ||||||
|  |         ] | ||||||
|         const idInfo = codeToIdSelections([ |         const idInfo = codeToIdSelections([ | ||||||
|           { codeRef: codeRefFromRange(range, kclManager.ast) }, |           { codeRef: codeRefFromRange(range, kclManager.ast) }, | ||||||
|         ])[0] |         ])[0] | ||||||
|  | |||||||
| @ -1,5 +1,11 @@ | |||||||
| import { useEffect, useState, useRef } from 'react' | import { useEffect, useState, useRef } from 'react' | ||||||
| import { parse, BinaryPart, Expr, ProgramMemory } from '../lang/wasm' | import { | ||||||
|  |   parse, | ||||||
|  |   BinaryPart, | ||||||
|  |   Expr, | ||||||
|  |   ProgramMemory, | ||||||
|  |   resultIsOk, | ||||||
|  | } from '../lang/wasm' | ||||||
| import { | import { | ||||||
|   createIdentifier, |   createIdentifier, | ||||||
|   createLiteral, |   createLiteral, | ||||||
| @ -141,8 +147,9 @@ export function useCalc({ | |||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     try { |     try { | ||||||
|       const code = `const __result__ = ${value}` |       const code = `const __result__ = ${value}` | ||||||
|       const ast = parse(code) |       const pResult = parse(code) | ||||||
|       if (trap(ast)) return |       if (trap(pResult) || !resultIsOk(pResult)) return | ||||||
|  |       const ast = pResult.program | ||||||
|       const _programMem: ProgramMemory = ProgramMemory.empty() |       const _programMem: ProgramMemory = ProgramMemory.empty() | ||||||
|       for (const { key, value } of availableVarInfo.variables) { |       for (const { key, value } of availableVarInfo.variables) { | ||||||
|         const error = _programMem.set(key, { |         const error = _programMem.set(key, { | ||||||
| @ -156,18 +163,17 @@ export function useCalc({ | |||||||
|       executeAst({ |       executeAst({ | ||||||
|         ast, |         ast, | ||||||
|         engineCommandManager, |         engineCommandManager, | ||||||
|         useFakeExecutor: true, |         // We make sure to send an empty program memory to denote we mean mock mode. | ||||||
|         programMemoryOverride: kclManager.programMemory.clone(), |         programMemoryOverride: kclManager.programMemory.clone(), | ||||||
|         idGenerator: kclManager.execState.idGenerator, |  | ||||||
|       }).then(({ execState }) => { |       }).then(({ execState }) => { | ||||||
|         const resultDeclaration = ast.body.find( |         const resultDeclaration = ast.body.find( | ||||||
|           (a) => |           (a) => | ||||||
|             a.type === 'VariableDeclaration' && |             a.type === 'VariableDeclaration' && | ||||||
|             a.declarations?.[0]?.id?.name === '__result__' |             a.declaration.id?.name === '__result__' | ||||||
|         ) |         ) | ||||||
|         const init = |         const init = | ||||||
|           resultDeclaration?.type === 'VariableDeclaration' && |           resultDeclaration?.type === 'VariableDeclaration' && | ||||||
|           resultDeclaration?.declarations?.[0]?.init |           resultDeclaration?.declaration.init | ||||||
|         const result = execState.memory?.get('__result__')?.value |         const result = execState.memory?.get('__result__')?.value | ||||||
|         setCalcResult(typeof result === 'number' ? String(result) : 'NAN') |         setCalcResult(typeof result === 'number' ? String(result) : 'NAN') | ||||||
|         init && setValueNode(init) |         init && setValueNode(init) | ||||||
|  | |||||||
| @ -8,11 +8,16 @@ import { getSystemTheme } from 'lib/theme' | |||||||
| import { useCalculateKclExpression } from 'lib/useCalculateKclExpression' | import { useCalculateKclExpression } from 'lib/useCalculateKclExpression' | ||||||
| import { roundOff } from 'lib/utils' | import { roundOff } from 'lib/utils' | ||||||
| import { varMentions } from 'lib/varCompletionExtension' | import { varMentions } from 'lib/varCompletionExtension' | ||||||
| import { useEffect, useRef, useState } from 'react' | import { useEffect, useMemo, useRef, useState } from 'react' | ||||||
| import { useHotkeys } from 'react-hotkeys-hook' | import { useHotkeys } from 'react-hotkeys-hook' | ||||||
| import styles from './CommandBarKclInput.module.css' | import styles from './CommandBarKclInput.module.css' | ||||||
| import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst' | import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst' | ||||||
| import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor' | import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor' | ||||||
|  | import { useSelector } from '@xstate/react' | ||||||
|  |  | ||||||
|  | const machineContextSelector = (snapshot?: { | ||||||
|  |   context: Record<string, unknown> | ||||||
|  | }) => snapshot?.context | ||||||
|  |  | ||||||
| function CommandBarKclInput({ | function CommandBarKclInput({ | ||||||
|   arg, |   arg, | ||||||
| @ -31,12 +36,44 @@ function CommandBarKclInput({ | |||||||
|     arg.name |     arg.name | ||||||
|   ] as KclCommandValue | undefined |   ] as KclCommandValue | undefined | ||||||
|   const { settings } = useSettingsAuthContext() |   const { settings } = useSettingsAuthContext() | ||||||
|   const defaultValue = (arg.defaultValue as string) || '' |   const argMachineContext = useSelector( | ||||||
|  |     arg.machineActor, | ||||||
|  |     machineContextSelector | ||||||
|  |   ) | ||||||
|  |   const defaultValue = useMemo( | ||||||
|  |     () => | ||||||
|  |       arg.defaultValue | ||||||
|  |         ? arg.defaultValue instanceof Function | ||||||
|  |           ? arg.defaultValue(commandBarState.context, argMachineContext) | ||||||
|  |           : arg.defaultValue | ||||||
|  |         : '', | ||||||
|  |     [arg.defaultValue, commandBarState.context, argMachineContext] | ||||||
|  |   ) | ||||||
|  |   const initialVariableName = useMemo(() => { | ||||||
|  |     // Use the configured variable name if it exists | ||||||
|  |     if (arg.variableName !== undefined) { | ||||||
|  |       return arg.variableName instanceof Function | ||||||
|  |         ? arg.variableName(commandBarState.context, argMachineContext) | ||||||
|  |         : arg.variableName | ||||||
|  |     } | ||||||
|  |     // or derive it from the previously set value or the argument name | ||||||
|  |     return previouslySetValue && 'variableName' in previouslySetValue | ||||||
|  |       ? previouslySetValue.variableName | ||||||
|  |       : arg.name | ||||||
|  |   }, [ | ||||||
|  |     arg.variableName, | ||||||
|  |     commandBarState.context, | ||||||
|  |     argMachineContext, | ||||||
|  |     arg.name, | ||||||
|  |     previouslySetValue, | ||||||
|  |   ]) | ||||||
|   const [value, setValue] = useState( |   const [value, setValue] = useState( | ||||||
|     previouslySetValue?.valueText || defaultValue || '' |     previouslySetValue?.valueText || defaultValue || '' | ||||||
|   ) |   ) | ||||||
|   const [createNewVariable, setCreateNewVariable] = useState( |   const [createNewVariable, setCreateNewVariable] = useState( | ||||||
|     previouslySetValue && 'variableName' in previouslySetValue |     (previouslySetValue && 'variableName' in previouslySetValue) || | ||||||
|  |       arg.createVariableByDefault || | ||||||
|  |       false | ||||||
|   ) |   ) | ||||||
|   const [canSubmit, setCanSubmit] = useState(true) |   const [canSubmit, setCanSubmit] = useState(true) | ||||||
|   useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' })) |   useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' })) | ||||||
| @ -52,10 +89,7 @@ function CommandBarKclInput({ | |||||||
|     isNewVariableNameUnique, |     isNewVariableNameUnique, | ||||||
|   } = useCalculateKclExpression({ |   } = useCalculateKclExpression({ | ||||||
|     value, |     value, | ||||||
|     initialVariableName: |     initialVariableName, | ||||||
|       previouslySetValue && 'variableName' in previouslySetValue |  | ||||||
|         ? previouslySetValue.variableName |  | ||||||
|         : arg.name, |  | ||||||
|   }) |   }) | ||||||
|   const varMentionData: Completion[] = prevVariables.map((v) => ({ |   const varMentionData: Completion[] = prevVariables.map((v) => ({ | ||||||
|     label: v.key, |     label: v.key, | ||||||
|  | |||||||
| @ -266,6 +266,7 @@ const FileTreeItem = ({ | |||||||
|       // Let the lsp servers know we closed a file. |       // Let the lsp servers know we closed a file. | ||||||
|       onFileClose(currentFile?.path || null, project?.path || null) |       onFileClose(currentFile?.path || null, project?.path || null) | ||||||
|       onFileOpen(fileOrDir.path, project?.path || null) |       onFileOpen(fileOrDir.path, project?.path || null) | ||||||
|  |       kclManager.switchedFiles = true | ||||||
|  |  | ||||||
|       // Open kcl files |       // Open kcl files | ||||||
|       navigate(`${PATHS.FILE}/${encodeURIComponent(fileOrDir.path)}`) |       navigate(`${PATHS.FILE}/${encodeURIComponent(fileOrDir.path)}`) | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import { APP_VERSION } from 'routes/Settings' | import { APP_VERSION, RELEASE_URL } from 'routes/Settings' | ||||||
| import { CustomIcon } from 'components/CustomIcon' | import { CustomIcon } from 'components/CustomIcon' | ||||||
| import Tooltip from 'components/Tooltip' | import Tooltip from 'components/Tooltip' | ||||||
| import { PATHS } from 'lib/paths' | import { PATHS } from 'lib/paths' | ||||||
| @ -72,10 +72,8 @@ export function LowerRightControls({ | |||||||
|       <menu className="flex items-center justify-end gap-3 pointer-events-auto"> |       <menu className="flex items-center justify-end gap-3 pointer-events-auto"> | ||||||
|         {!location.pathname.startsWith(PATHS.HOME) && <ModelStateIndicator />} |         {!location.pathname.startsWith(PATHS.HOME) && <ModelStateIndicator />} | ||||||
|         <a |         <a | ||||||
|           onClick={openExternalBrowserIfDesktop( |           onClick={openExternalBrowserIfDesktop(RELEASE_URL)} | ||||||
|             `https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}` |           href={RELEASE_URL} | ||||||
|           )} |  | ||||||
|           href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`} |  | ||||||
|           target="_blank" |           target="_blank" | ||||||
|           rel="noopener noreferrer" |           rel="noopener noreferrer" | ||||||
|           className={'!no-underline font-mono text-xs ' + linkOverrideClassName} |           className={'!no-underline font-mono text-xs ' + linkOverrideClassName} | ||||||
|  | |||||||
| @ -41,7 +41,10 @@ import { | |||||||
|   angleBetweenInfo, |   angleBetweenInfo, | ||||||
|   applyConstraintAngleBetween, |   applyConstraintAngleBetween, | ||||||
| } from './Toolbar/SetAngleBetween' | } from './Toolbar/SetAngleBetween' | ||||||
| import { applyConstraintAngleLength } from './Toolbar/setAngleLength' | import { | ||||||
|  |   applyConstraintAngleLength, | ||||||
|  |   applyConstraintLength, | ||||||
|  | } from './Toolbar/setAngleLength' | ||||||
| import { | import { | ||||||
|   canSweepSelection, |   canSweepSelection, | ||||||
|   handleSelectionBatch, |   handleSelectionBatch, | ||||||
| @ -50,6 +53,9 @@ import { | |||||||
|   isSketchPipe, |   isSketchPipe, | ||||||
|   Selections, |   Selections, | ||||||
|   updateSelections, |   updateSelections, | ||||||
|  |   canLoftSelection, | ||||||
|  |   canRevolveSelection, | ||||||
|  |   canShellSelection, | ||||||
| } from 'lib/selections' | } from 'lib/selections' | ||||||
| import { applyConstraintIntersect } from './Toolbar/Intersect' | import { applyConstraintIntersect } from './Toolbar/Intersect' | ||||||
| import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance' | import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance' | ||||||
| @ -61,13 +67,15 @@ import { | |||||||
|   getSketchOrientationDetails, |   getSketchOrientationDetails, | ||||||
| } from 'clientSideScene/sceneEntities' | } from 'clientSideScene/sceneEntities' | ||||||
| import { | import { | ||||||
|   moveValueIntoNewVariablePath, |   insertNamedConstant, | ||||||
|  |   replaceValueAtNodePath, | ||||||
|   sketchOnExtrudedFace, |   sketchOnExtrudedFace, | ||||||
|   sketchOnOffsetPlane, |   sketchOnOffsetPlane, | ||||||
|   startSketchOnDefault, |   startSketchOnDefault, | ||||||
| } from 'lang/modifyAst' | } from 'lang/modifyAst' | ||||||
| import { Program, parse, recast } from 'lang/wasm' | import { PathToNode, Program, parse, recast, resultIsOk } from 'lang/wasm' | ||||||
| import { | import { | ||||||
|  |   doesSceneHaveExtrudedSketch, | ||||||
|   doesSceneHaveSweepableSketch, |   doesSceneHaveSweepableSketch, | ||||||
|   getNodePathFromSourceRange, |   getNodePathFromSourceRange, | ||||||
|   isSingleCursorInPipe, |   isSingleCursorInPipe, | ||||||
| @ -78,11 +86,10 @@ import toast from 'react-hot-toast' | |||||||
| import { EditorSelection, Transaction } from '@codemirror/state' | import { EditorSelection, Transaction } from '@codemirror/state' | ||||||
| import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom' | import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom' | ||||||
| import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls' | import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls' | ||||||
| import { getVarNameModal } from 'hooks/useToolbarGuards' |  | ||||||
| import { err, reportRejection, trap } from 'lib/trap' | import { err, reportRejection, trap } from 'lib/trap' | ||||||
| import { useCommandsContext } from 'hooks/useCommandsContext' | import { useCommandsContext } from 'hooks/useCommandsContext' | ||||||
| import { modelingMachineEvent } from 'editor/manager' | import { modelingMachineEvent } from 'editor/manager' | ||||||
| import { hasValidFilletSelection } from 'lang/modifyAst/addFillet' | import { hasValidEdgeTreatmentSelection } from 'lang/modifyAst/addEdgeTreatment' | ||||||
| import { | import { | ||||||
|   ExportIntent, |   ExportIntent, | ||||||
|   EngineConnectionStateType, |   EngineConnectionStateType, | ||||||
| @ -569,6 +576,59 @@ export const ModelingMachineProvider = ({ | |||||||
|           if (err(canSweep)) return false |           if (err(canSweep)) return false | ||||||
|           return canSweep |           return canSweep | ||||||
|         }, |         }, | ||||||
|  |         'has valid revolve selection': ({ context: { selectionRanges } }) => { | ||||||
|  |           // A user can begin extruding if they either have 1+ faces selected or nothing selected | ||||||
|  |           // TODO: I believe this guard only allows for extruding a single face at a time | ||||||
|  |           const hasNoSelection = | ||||||
|  |             selectionRanges.graphSelections.length === 0 || | ||||||
|  |             isRangeBetweenCharacters(selectionRanges) || | ||||||
|  |             isSelectionLastLine(selectionRanges, codeManager.code) | ||||||
|  |  | ||||||
|  |           if (hasNoSelection) { | ||||||
|  |             // they have no selection, we should enable the button | ||||||
|  |             // so they can select the face through the cmdbar | ||||||
|  |             // BUT only if there's extrudable geometry | ||||||
|  |             return doesSceneHaveSweepableSketch(kclManager.ast) | ||||||
|  |           } | ||||||
|  |           if (!isSketchPipe(selectionRanges)) return false | ||||||
|  |  | ||||||
|  |           const canSweep = canRevolveSelection(selectionRanges) | ||||||
|  |           if (err(canSweep)) return false | ||||||
|  |           return canSweep | ||||||
|  |         }, | ||||||
|  |         'has valid loft selection': ({ context: { selectionRanges } }) => { | ||||||
|  |           const hasNoSelection = | ||||||
|  |             selectionRanges.graphSelections.length === 0 || | ||||||
|  |             isRangeBetweenCharacters(selectionRanges) || | ||||||
|  |             isSelectionLastLine(selectionRanges, codeManager.code) | ||||||
|  |  | ||||||
|  |           if (hasNoSelection) { | ||||||
|  |             const count = 2 | ||||||
|  |             return doesSceneHaveSweepableSketch(kclManager.ast, count) | ||||||
|  |           } | ||||||
|  |  | ||||||
|  |           const canLoft = canLoftSelection(selectionRanges) | ||||||
|  |           if (err(canLoft)) return false | ||||||
|  |           return canLoft | ||||||
|  |         }, | ||||||
|  |         'has valid shell selection': ({ | ||||||
|  |           context: { selectionRanges }, | ||||||
|  |           event, | ||||||
|  |         }) => { | ||||||
|  |           const hasNoSelection = | ||||||
|  |             selectionRanges.graphSelections.length === 0 || | ||||||
|  |             isRangeBetweenCharacters(selectionRanges) || | ||||||
|  |             isSelectionLastLine(selectionRanges, codeManager.code) | ||||||
|  |  | ||||||
|  |           if (hasNoSelection) { | ||||||
|  |             return doesSceneHaveExtrudedSketch(kclManager.ast) | ||||||
|  |           } | ||||||
|  |  | ||||||
|  |           const canShell = canShellSelection(selectionRanges) | ||||||
|  |           console.log('canShellSelection', canShellSelection(selectionRanges)) | ||||||
|  |           if (err(canShell)) return false | ||||||
|  |           return canShell | ||||||
|  |         }, | ||||||
|         'has valid selection for deletion': ({ |         'has valid selection for deletion': ({ | ||||||
|           context: { selectionRanges }, |           context: { selectionRanges }, | ||||||
|         }) => { |         }) => { | ||||||
| @ -576,8 +636,10 @@ export const ModelingMachineProvider = ({ | |||||||
|           if (selectionRanges.graphSelections.length <= 0) return false |           if (selectionRanges.graphSelections.length <= 0) return false | ||||||
|           return true |           return true | ||||||
|         }, |         }, | ||||||
|         'has valid fillet selection': ({ context: { selectionRanges } }) => { |         'has valid edge treatment selection': ({ | ||||||
|           return hasValidFilletSelection({ |           context: { selectionRanges }, | ||||||
|  |         }) => { | ||||||
|  |           return hasValidEdgeTreatmentSelection({ | ||||||
|             selectionRanges, |             selectionRanges, | ||||||
|             ast: kclManager.ast, |             ast: kclManager.ast, | ||||||
|             code: codeManager.code, |             code: codeManager.code, | ||||||
| @ -594,15 +656,11 @@ export const ModelingMachineProvider = ({ | |||||||
|           ) |           ) | ||||||
|         }, |         }, | ||||||
|         'Has exportable geometry': () => { |         'Has exportable geometry': () => { | ||||||
|           if ( |           if (!kclManager.hasErrors() && kclManager.ast.body.length > 0) | ||||||
|             kclManager.kclErrors.length === 0 && |  | ||||||
|             kclManager.ast.body.length > 0 |  | ||||||
|           ) |  | ||||||
|             return true |             return true | ||||||
|           else { |           else { | ||||||
|             let errorMessage = 'Unable to Export ' |             let errorMessage = 'Unable to Export ' | ||||||
|             if (kclManager.kclErrors.length > 0) |             if (kclManager.hasErrors()) errorMessage += 'due to KCL Errors' | ||||||
|               errorMessage += 'due to KCL Errors' |  | ||||||
|             else if (kclManager.ast.body.length === 0) |             else if (kclManager.ast.body.length === 0) | ||||||
|               errorMessage += 'due to Empty Scene' |               errorMessage += 'due to Empty Scene' | ||||||
|             console.error(errorMessage) |             console.error(errorMessage) | ||||||
| @ -720,7 +778,11 @@ export const ModelingMachineProvider = ({ | |||||||
|                 constraint: 'setHorzDistance', |                 constraint: 'setHorzDistance', | ||||||
|                 selectionRanges, |                 selectionRanges, | ||||||
|               }) |               }) | ||||||
|             const _modifiedAst = parse(recast(modifiedAst)) |             const pResult = parse(recast(modifiedAst)) | ||||||
|  |             if (trap(pResult) || !resultIsOk(pResult)) | ||||||
|  |               return Promise.reject(new Error('Unexpected compilation error')) | ||||||
|  |             const _modifiedAst = pResult.program | ||||||
|  |  | ||||||
|             if (!sketchDetails) |             if (!sketchDetails) | ||||||
|               return Promise.reject(new Error('No sketch details')) |               return Promise.reject(new Error('No sketch details')) | ||||||
|             const updatedPathToNode = updatePathToNodeFromMap( |             const updatedPathToNode = updatePathToNodeFromMap( | ||||||
| @ -761,7 +823,10 @@ export const ModelingMachineProvider = ({ | |||||||
|                 constraint: 'setVertDistance', |                 constraint: 'setVertDistance', | ||||||
|                 selectionRanges, |                 selectionRanges, | ||||||
|               }) |               }) | ||||||
|             const _modifiedAst = parse(recast(modifiedAst)) |             const pResult = parse(recast(modifiedAst)) | ||||||
|  |             if (trap(pResult) || !resultIsOk(pResult)) | ||||||
|  |               return Promise.reject(new Error('Unexpected compilation error')) | ||||||
|  |             const _modifiedAst = pResult.program | ||||||
|             if (!sketchDetails) |             if (!sketchDetails) | ||||||
|               return Promise.reject(new Error('No sketch details')) |               return Promise.reject(new Error('No sketch details')) | ||||||
|             const updatedPathToNode = updatePathToNodeFromMap( |             const updatedPathToNode = updatePathToNodeFromMap( | ||||||
| @ -809,7 +874,10 @@ export const ModelingMachineProvider = ({ | |||||||
|                   selectionRanges, |                   selectionRanges, | ||||||
|                   angleOrLength: 'setAngle', |                   angleOrLength: 'setAngle', | ||||||
|                 })) |                 })) | ||||||
|             const _modifiedAst = parse(recast(modifiedAst)) |             const pResult = parse(recast(modifiedAst)) | ||||||
|  |             if (trap(pResult) || !resultIsOk(pResult)) | ||||||
|  |               return Promise.reject(new Error('Unexpected compilation error')) | ||||||
|  |             const _modifiedAst = pResult.program | ||||||
|             if (err(_modifiedAst)) return Promise.reject(_modifiedAst) |             if (err(_modifiedAst)) return Promise.reject(_modifiedAst) | ||||||
|  |  | ||||||
|             if (!sketchDetails) |             if (!sketchDetails) | ||||||
| @ -845,13 +913,22 @@ export const ModelingMachineProvider = ({ | |||||||
|             } |             } | ||||||
|           } |           } | ||||||
|         ), |         ), | ||||||
|         'Get length info': fromPromise( |         astConstrainLength: fromPromise( | ||||||
|           async ({ input: { selectionRanges, sketchDetails } }) => { |           async ({ | ||||||
|             const { modifiedAst, pathToNodeMap } = |             input: { selectionRanges, sketchDetails, lengthValue }, | ||||||
|               await applyConstraintAngleLength({ |           }) => { | ||||||
|                 selectionRanges, |             if (!lengthValue) | ||||||
|               }) |               return Promise.reject(new Error('No length value')) | ||||||
|             const _modifiedAst = parse(recast(modifiedAst)) |             const constraintResult = await applyConstraintLength({ | ||||||
|  |               selectionRanges, | ||||||
|  |               length: lengthValue, | ||||||
|  |             }) | ||||||
|  |             if (err(constraintResult)) return Promise.reject(constraintResult) | ||||||
|  |             const { modifiedAst, pathToNodeMap } = constraintResult | ||||||
|  |             const pResult = parse(recast(modifiedAst)) | ||||||
|  |             if (trap(pResult) || !resultIsOk(pResult)) | ||||||
|  |               return Promise.reject(new Error('Unexpected compilation error')) | ||||||
|  |             const _modifiedAst = pResult.program | ||||||
|             if (!sketchDetails) |             if (!sketchDetails) | ||||||
|               return Promise.reject(new Error('No sketch details')) |               return Promise.reject(new Error('No sketch details')) | ||||||
|             const updatedPathToNode = updatePathToNodeFromMap( |             const updatedPathToNode = updatePathToNodeFromMap( | ||||||
| @ -891,7 +968,10 @@ export const ModelingMachineProvider = ({ | |||||||
|               await applyConstraintIntersect({ |               await applyConstraintIntersect({ | ||||||
|                 selectionRanges, |                 selectionRanges, | ||||||
|               }) |               }) | ||||||
|             const _modifiedAst = parse(recast(modifiedAst)) |             const pResult = parse(recast(modifiedAst)) | ||||||
|  |             if (trap(pResult) || !resultIsOk(pResult)) | ||||||
|  |               return Promise.reject(new Error('Unexpected compilation error')) | ||||||
|  |             const _modifiedAst = pResult.program | ||||||
|             if (!sketchDetails) |             if (!sketchDetails) | ||||||
|               return Promise.reject(new Error('No sketch details')) |               return Promise.reject(new Error('No sketch details')) | ||||||
|             const updatedPathToNode = updatePathToNodeFromMap( |             const updatedPathToNode = updatePathToNodeFromMap( | ||||||
| @ -932,7 +1012,10 @@ export const ModelingMachineProvider = ({ | |||||||
|                 constraint: 'xAbs', |                 constraint: 'xAbs', | ||||||
|                 selectionRanges, |                 selectionRanges, | ||||||
|               }) |               }) | ||||||
|             const _modifiedAst = parse(recast(modifiedAst)) |             const pResult = parse(recast(modifiedAst)) | ||||||
|  |             if (trap(pResult) || !resultIsOk(pResult)) | ||||||
|  |               return Promise.reject(new Error('Unexpected compilation error')) | ||||||
|  |             const _modifiedAst = pResult.program | ||||||
|             if (!sketchDetails) |             if (!sketchDetails) | ||||||
|               return Promise.reject(new Error('No sketch details')) |               return Promise.reject(new Error('No sketch details')) | ||||||
|             const updatedPathToNode = updatePathToNodeFromMap( |             const updatedPathToNode = updatePathToNodeFromMap( | ||||||
| @ -973,7 +1056,10 @@ export const ModelingMachineProvider = ({ | |||||||
|                 constraint: 'yAbs', |                 constraint: 'yAbs', | ||||||
|                 selectionRanges, |                 selectionRanges, | ||||||
|               }) |               }) | ||||||
|             const _modifiedAst = parse(recast(modifiedAst)) |             const pResult = parse(recast(modifiedAst)) | ||||||
|  |             if (trap(pResult) || !resultIsOk(pResult)) | ||||||
|  |               return Promise.reject(new Error('Unexpected compilation error')) | ||||||
|  |             const _modifiedAst = pResult.program | ||||||
|             if (!sketchDetails) |             if (!sketchDetails) | ||||||
|               return Promise.reject(new Error('No sketch details')) |               return Promise.reject(new Error('No sketch details')) | ||||||
|             const updatedPathToNode = updatePathToNodeFromMap( |             const updatedPathToNode = updatePathToNodeFromMap( | ||||||
| @ -1007,33 +1093,88 @@ export const ModelingMachineProvider = ({ | |||||||
|             } |             } | ||||||
|           } |           } | ||||||
|         ), |         ), | ||||||
|         'Get convert to variable info': fromPromise( |         'Apply named value constraint': fromPromise( | ||||||
|           async ({ input: { selectionRanges, sketchDetails, data } }) => { |           async ({ input: { selectionRanges, sketchDetails, data } }) => { | ||||||
|             if (!sketchDetails) |             if (!sketchDetails) { | ||||||
|               return Promise.reject(new Error('No sketch details')) |               return Promise.reject(new Error('No sketch details')) | ||||||
|             const { variableName } = await getVarNameModal({ |             } | ||||||
|               valueName: data?.variableName || 'var', |             if (!data) { | ||||||
|             }) |               return Promise.reject(new Error('No data from command flow')) | ||||||
|             let parsed = parse(recast(kclManager.ast)) |             } | ||||||
|             if (trap(parsed)) return Promise.reject(parsed) |             let pResult = parse(recast(kclManager.ast)) | ||||||
|             parsed = parsed as Node<Program> |             if (trap(pResult) || !resultIsOk(pResult)) | ||||||
|  |               return Promise.reject(new Error('Unexpected compilation error')) | ||||||
|  |             let parsed = pResult.program | ||||||
|  |  | ||||||
|             const { modifiedAst: _modifiedAst, pathToReplacedNode } = |             let result: { | ||||||
|               moveValueIntoNewVariablePath( |               modifiedAst: Node<Program> | ||||||
|                 parsed, |               pathToReplaced: PathToNode | null | ||||||
|                 kclManager.programMemory, |             } = { | ||||||
|                 data?.pathToNode || [], |               modifiedAst: parsed, | ||||||
|                 variableName |               pathToReplaced: null, | ||||||
|  |             } | ||||||
|  |             // If the user provided a constant name, | ||||||
|  |             // we need to insert the named constant | ||||||
|  |             // and then replace the node with the constant's name. | ||||||
|  |             if ('variableName' in data.namedValue) { | ||||||
|  |               const astAfterReplacement = replaceValueAtNodePath({ | ||||||
|  |                 ast: parsed, | ||||||
|  |                 pathToNode: data.currentValue.pathToNode, | ||||||
|  |                 newExpressionString: data.namedValue.variableName, | ||||||
|  |               }) | ||||||
|  |               if (trap(astAfterReplacement)) { | ||||||
|  |                 return Promise.reject(astAfterReplacement) | ||||||
|  |               } | ||||||
|  |               const parseResultAfterInsertion = parse( | ||||||
|  |                 recast( | ||||||
|  |                   insertNamedConstant({ | ||||||
|  |                     node: astAfterReplacement.modifiedAst, | ||||||
|  |                     newExpression: data.namedValue, | ||||||
|  |                   }) | ||||||
|  |                 ) | ||||||
|               ) |               ) | ||||||
|             parsed = parse(recast(_modifiedAst)) |               if ( | ||||||
|  |                 trap(parseResultAfterInsertion) || | ||||||
|  |                 !resultIsOk(parseResultAfterInsertion) | ||||||
|  |               ) | ||||||
|  |                 return Promise.reject(parseResultAfterInsertion) | ||||||
|  |               result = { | ||||||
|  |                 modifiedAst: parseResultAfterInsertion.program, | ||||||
|  |                 pathToReplaced: astAfterReplacement.pathToReplaced, | ||||||
|  |               } | ||||||
|  |             } else if ('valueText' in data.namedValue) { | ||||||
|  |               // If they didn't provide a constant name, | ||||||
|  |               // just replace the node with the value. | ||||||
|  |               const astAfterReplacement = replaceValueAtNodePath({ | ||||||
|  |                 ast: parsed, | ||||||
|  |                 pathToNode: data.currentValue.pathToNode, | ||||||
|  |                 newExpressionString: data.namedValue.valueText, | ||||||
|  |               }) | ||||||
|  |               if (trap(astAfterReplacement)) { | ||||||
|  |                 return Promise.reject(astAfterReplacement) | ||||||
|  |               } | ||||||
|  |               // The `replacer` function returns a pathToNode that assumes | ||||||
|  |               // an identifier is also being inserted into the AST, creating an off-by-one error. | ||||||
|  |               // This corrects that error, but TODO we should fix this upstream | ||||||
|  |               // to avoid this kind of error in the future. | ||||||
|  |               astAfterReplacement.pathToReplaced[1][0] = | ||||||
|  |                 (astAfterReplacement.pathToReplaced[1][0] as number) - 1 | ||||||
|  |               result = astAfterReplacement | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             pResult = parse(recast(result.modifiedAst)) | ||||||
|  |             if (trap(pResult) || !resultIsOk(pResult)) | ||||||
|  |               return Promise.reject(new Error('Unexpected compilation error')) | ||||||
|  |             parsed = pResult.program | ||||||
|  |  | ||||||
|             if (trap(parsed)) return Promise.reject(parsed) |             if (trap(parsed)) return Promise.reject(parsed) | ||||||
|             parsed = parsed as Node<Program> |             parsed = parsed as Node<Program> | ||||||
|             if (!pathToReplacedNode) |             if (!result.pathToReplaced) | ||||||
|               return Promise.reject(new Error('No path to replaced node')) |               return Promise.reject(new Error('No path to replaced node')) | ||||||
|  |  | ||||||
|             const updatedAst = |             const updatedAst = | ||||||
|               await sceneEntitiesManager.updateAstAndRejigSketch( |               await sceneEntitiesManager.updateAstAndRejigSketch( | ||||||
|                 pathToReplacedNode || [], |                 result.pathToReplaced || [], | ||||||
|                 parsed, |                 parsed, | ||||||
|                 sketchDetails.zAxis, |                 sketchDetails.zAxis, | ||||||
|                 sketchDetails.yAxis, |                 sketchDetails.yAxis, | ||||||
| @ -1046,7 +1187,7 @@ export const ModelingMachineProvider = ({ | |||||||
|             ) |             ) | ||||||
|  |  | ||||||
|             const selection = updateSelections( |             const selection = updateSelections( | ||||||
|               { 0: pathToReplacedNode }, |               { 0: result.pathToReplaced }, | ||||||
|               selectionRanges, |               selectionRanges, | ||||||
|               updatedAst.newAst |               updatedAst.newAst | ||||||
|             ) |             ) | ||||||
| @ -1054,7 +1195,7 @@ export const ModelingMachineProvider = ({ | |||||||
|             return { |             return { | ||||||
|               selectionType: 'completeSelection', |               selectionType: 'completeSelection', | ||||||
|               selection, |               selection, | ||||||
|               updatedPathToNode: pathToReplacedNode, |               updatedPathToNode: result.pathToReplaced, | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|         ), |         ), | ||||||
|  | |||||||
| @ -76,7 +76,7 @@ export const ModelingPane = ({ | |||||||
|   return ( |   return ( | ||||||
|     <section |     <section | ||||||
|       {...props} |       {...props} | ||||||
|       title={title && typeof title === 'string' ? title : ''} |       aria-label={title && typeof title === 'string' ? title : ''} | ||||||
|       data-testid={detailsTestId} |       data-testid={detailsTestId} | ||||||
|       id={id} |       id={id} | ||||||
|       className={ |       className={ | ||||||
|  | |||||||
| @ -40,7 +40,9 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => { | |||||||
|         <Menu.Items className="absolute right-0 left-auto w-72 flex flex-col gap-1 divide-y divide-chalkboard-20 dark:divide-chalkboard-70 align-stretch px-0 py-1 bg-chalkboard-10 dark:bg-chalkboard-100 rounded-sm shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50"> |         <Menu.Items className="absolute right-0 left-auto w-72 flex flex-col gap-1 divide-y divide-chalkboard-20 dark:divide-chalkboard-70 align-stretch px-0 py-1 bg-chalkboard-10 dark:bg-chalkboard-100 rounded-sm shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50"> | ||||||
|           <Menu.Item> |           <Menu.Item> | ||||||
|             <button |             <button | ||||||
|               onClick={() => kclManager.format()} |               onClick={() => { | ||||||
|  |                 kclManager.format().catch(reportRejection) | ||||||
|  |               }} | ||||||
|               className={styles.button} |               className={styles.button} | ||||||
|             > |             > | ||||||
|               <span>Format code</span> |               <span>Format code</span> | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| import { processMemory } from './MemoryPane' | import { processMemory } from './MemoryPane' | ||||||
| import { enginelessExecutor } from '../../../lib/testHelpers' | import { enginelessExecutor } from '../../../lib/testHelpers' | ||||||
| import { initPromise, parse, ProgramMemory } from '../../../lang/wasm' | import { assertParse, initPromise, ProgramMemory } from '../../../lang/wasm' | ||||||
|  |  | ||||||
| beforeAll(async () => { | beforeAll(async () => { | ||||||
|   await initPromise |   await initPromise | ||||||
| @ -28,12 +28,16 @@ describe('processMemory', () => { | |||||||
|     |> lineTo([0.98, 5.16], %) |     |> lineTo([0.98, 5.16], %) | ||||||
|     |> lineTo([2.15, 4.32], %) |     |> lineTo([2.15, 4.32], %) | ||||||
|     // |> rx(90, %)` |     // |> rx(90, %)` | ||||||
|     const ast = parse(code) |     const ast = assertParse(code) | ||||||
|     const execState = await enginelessExecutor(ast, ProgramMemory.empty()) |     const execState = await enginelessExecutor(ast, ProgramMemory.empty()) | ||||||
|     const output = processMemory(execState.memory) |     const output = processMemory(execState.memory) | ||||||
|     expect(output.myVar).toEqual(5) |     expect(output.myVar).toEqual(5) | ||||||
|     expect(output.otherVar).toEqual(3) |     expect(output.otherVar).toEqual(3) | ||||||
|     expect(output).toEqual({ |     expect(output).toEqual({ | ||||||
|  |       HALF_TURN: 180, | ||||||
|  |       QUARTER_TURN: 90, | ||||||
|  |       THREE_QUARTER_TURN: 270, | ||||||
|  |       ZERO: 0, | ||||||
|       myVar: 5, |       myVar: 5, | ||||||
|       myFn: '__function(a)__', |       myFn: '__function(a)__', | ||||||
|       otherVar: 3, |       otherVar: 3, | ||||||
|  | |||||||
| @ -90,7 +90,7 @@ export const sidebarPanes: SidebarPane[] = [ | |||||||
|     keybinding: 'Shift + C', |     keybinding: 'Shift + C', | ||||||
|     showBadge: { |     showBadge: { | ||||||
|       value: ({ kclContext }) => { |       value: ({ kclContext }) => { | ||||||
|         return kclContext.errors.length |         return kclContext.diagnostics.length | ||||||
|       }, |       }, | ||||||
|       onClick: (e) => { |       onClick: (e) => { | ||||||
|         e.preventDefault() |         e.preventDefault() | ||||||
|  | |||||||
| @ -53,7 +53,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) { | |||||||
|       settings: settings.context, |       settings: settings.context, | ||||||
|       platform: getPlatformString(), |       platform: getPlatformString(), | ||||||
|     }), |     }), | ||||||
|     [kclContext.errors, settings.context] |     [kclContext.diagnostics, settings.context] | ||||||
|   ) |   ) | ||||||
|  |  | ||||||
|   const sidebarActions: SidebarAction[] = [ |   const sidebarActions: SidebarAction[] = [ | ||||||
|  | |||||||
| @ -10,7 +10,7 @@ import { APP_NAME } from 'lib/constants' | |||||||
| import { useCommandsContext } from 'hooks/useCommandsContext' | import { useCommandsContext } from 'hooks/useCommandsContext' | ||||||
| import { CustomIcon } from './CustomIcon' | import { CustomIcon } from './CustomIcon' | ||||||
| import { useLspContext } from './LspProvider' | import { useLspContext } from './LspProvider' | ||||||
| import { engineCommandManager } from 'lib/singletons' | import { engineCommandManager, kclManager } from 'lib/singletons' | ||||||
| import { MachineManagerContext } from 'components/MachineManagerProvider' | import { MachineManagerContext } from 'components/MachineManagerProvider' | ||||||
| import usePlatform from 'hooks/usePlatform' | import usePlatform from 'hooks/usePlatform' | ||||||
| import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath' | import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath' | ||||||
| @ -68,8 +68,7 @@ function AppLogoLink({ | |||||||
|       data-testid="app-logo" |       data-testid="app-logo" | ||||||
|       onClick={() => { |       onClick={() => { | ||||||
|         onProjectClose(file || null, project?.path || null, false) |         onProjectClose(file || null, project?.path || null, false) | ||||||
|         // Clear the scene and end the session. |         kclManager.switchedFiles = true | ||||||
|         engineCommandManager.endSession() |  | ||||||
|       }} |       }} | ||||||
|       to={PATHS.HOME} |       to={PATHS.HOME} | ||||||
|       className={wrapperClassName + ' hover:before:brightness-110'} |       className={wrapperClassName + ' hover:before:brightness-110'} | ||||||
| @ -190,8 +189,7 @@ function ProjectMenuPopover({ | |||||||
|           className: !isDesktop() ? 'hidden' : '', |           className: !isDesktop() ? 'hidden' : '', | ||||||
|           onClick: () => { |           onClick: () => { | ||||||
|             onProjectClose(file || null, project?.path || null, true) |             onProjectClose(file || null, project?.path || null, true) | ||||||
|             // Clear the scene and end the session. |             kclManager.switchedFiles = true | ||||||
|             engineCommandManager.endSession() |  | ||||||
|           }, |           }, | ||||||
|         }, |         }, | ||||||
|       ].filter( |       ].filter( | ||||||
|  | |||||||
| @ -10,7 +10,7 @@ interface AllKeybindingsFieldsProps {} | |||||||
|  |  | ||||||
| export const AllKeybindingsFields = forwardRef( | export const AllKeybindingsFields = forwardRef( | ||||||
|   ( |   ( | ||||||
|     props: AllKeybindingsFieldsProps, |     _props: AllKeybindingsFieldsProps, | ||||||
|     scrollRef: ForwardedRef<HTMLDivElement> |     scrollRef: ForwardedRef<HTMLDivElement> | ||||||
|   ) => { |   ) => { | ||||||
|     // This is how we will get the interaction map from the context |     // This is how we will get the interaction map from the context | ||||||
| @ -25,7 +25,7 @@ export const AllKeybindingsFields = forwardRef( | |||||||
|             .map(([category, categoryItems]) => ( |             .map(([category, categoryItems]) => ( | ||||||
|               <div className="flex flex-col gap-4 px-2 pr-4"> |               <div className="flex flex-col gap-4 px-2 pr-4"> | ||||||
|                 <h2 |                 <h2 | ||||||
|                   id={`category-${category}`} |                   id={`category-${category.replaceAll(/\s/g, '-')}`} | ||||||
|                   className="text-xl mt-6 first-of-type:mt-0 capitalize font-bold" |                   className="text-xl mt-6 first-of-type:mt-0 capitalize font-bold" | ||||||
|                 > |                 > | ||||||
|                   {category} |                   {category} | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ import { isDesktop } from 'lib/isDesktop' | |||||||
| import { ActionButton } from 'components/ActionButton' | import { ActionButton } from 'components/ActionButton' | ||||||
| import { SettingsFieldInput } from './SettingsFieldInput' | import { SettingsFieldInput } from './SettingsFieldInput' | ||||||
| import toast from 'react-hot-toast' | import toast from 'react-hot-toast' | ||||||
| import { APP_VERSION, PACKAGE_NAME } from 'routes/Settings' | import { APP_VERSION, IS_NIGHTLY, RELEASE_URL } from 'routes/Settings' | ||||||
| import { PATHS } from 'lib/paths' | import { PATHS } from 'lib/paths' | ||||||
| import { | import { | ||||||
|   createAndOpenNewTutorialProject, |   createAndOpenNewTutorialProject, | ||||||
| @ -246,10 +246,8 @@ export const AllSettingsFields = forwardRef( | |||||||
|                   to inject the version from package.json */} |                   to inject the version from package.json */} | ||||||
|               App version {APP_VERSION}.{' '} |               App version {APP_VERSION}.{' '} | ||||||
|               <a |               <a | ||||||
|                 onClick={openExternalBrowserIfDesktop( |                 onClick={openExternalBrowserIfDesktop(RELEASE_URL)} | ||||||
|                   `https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}` |                 href={RELEASE_URL} | ||||||
|                 )} |  | ||||||
|                 href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`} |  | ||||||
|                 target="_blank" |                 target="_blank" | ||||||
|                 rel="noopener noreferrer" |                 rel="noopener noreferrer" | ||||||
|               > |               > | ||||||
| @ -271,7 +269,7 @@ export const AllSettingsFields = forwardRef( | |||||||
|               , and start a discussion if you don't see it! Your feedback will |               , and start a discussion if you don't see it! Your feedback will | ||||||
|               help us prioritize what to build next. |               help us prioritize what to build next. | ||||||
|             </p> |             </p> | ||||||
|             {PACKAGE_NAME.indexOf('-nightly') === -1 && ( |             {!IS_NIGHTLY && ( | ||||||
|               <p className="max-w-2xl mt-6"> |               <p className="max-w-2xl mt-6"> | ||||||
|                 Want to experience the latest and (hopefully) greatest from our |                 Want to experience the latest and (hopefully) greatest from our | ||||||
|                 main development branch?{' '} |                 main development branch?{' '} | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ export function KeybindingsSectionsList({ | |||||||
|             key={category} |             key={category} | ||||||
|             onClick={() => |             onClick={() => | ||||||
|               scrollRef.current |               scrollRef.current | ||||||
|                 ?.querySelector(`#category-${category}`) |                 ?.querySelector(`#category-${category.replaceAll(/\s/g, '-')}`) | ||||||
|                 ?.scrollIntoView({ |                 ?.scrollIntoView({ | ||||||
|                   block: 'center', |                   block: 'center', | ||||||
|                   behavior: 'smooth', |                   behavior: 'smooth', | ||||||
|  | |||||||
| @ -40,7 +40,10 @@ export function removeConstrainingValuesInfo({ | |||||||
|         otherSelections: [], |         otherSelections: [], | ||||||
|         graphSelections: nodes.map( |         graphSelections: nodes.map( | ||||||
|           (node): Selection => ({ |           (node): Selection => ({ | ||||||
|             codeRef: codeRefFromRange([node.start, node.end], kclManager.ast), |             codeRef: codeRefFromRange( | ||||||
|  |               [node.start, node.end, true], | ||||||
|  |               kclManager.ast | ||||||
|  |             ), | ||||||
|           }) |           }) | ||||||
|         ), |         ), | ||||||
|       } |       } | ||||||
|  | |||||||
| @ -22,6 +22,7 @@ import { removeDoubleNegatives } from '../AvailableVarsHelpers' | |||||||
| import { normaliseAngle } from '../../lib/utils' | import { normaliseAngle } from '../../lib/utils' | ||||||
| import { kclManager } from 'lib/singletons' | import { kclManager } from 'lib/singletons' | ||||||
| import { err } from 'lib/trap' | import { err } from 'lib/trap' | ||||||
|  | import { KclCommandValue } from 'lib/commandTypes' | ||||||
|  |  | ||||||
| const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal) | const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal) | ||||||
|  |  | ||||||
| @ -63,6 +64,57 @@ export function angleLengthInfo({ | |||||||
|   return { enabled, transforms } |   return { enabled, transforms } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export async function applyConstraintLength({ | ||||||
|  |   length, | ||||||
|  |   selectionRanges, | ||||||
|  | }: { | ||||||
|  |   length: KclCommandValue | ||||||
|  |   selectionRanges: Selections | ||||||
|  | }) { | ||||||
|  |   const ast = kclManager.ast | ||||||
|  |   const angleLength = angleLengthInfo({ selectionRanges }) | ||||||
|  |   if (err(angleLength)) return angleLength | ||||||
|  |   const { transforms } = angleLength | ||||||
|  |  | ||||||
|  |   let distanceExpression: Expr = length.valueAst | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * To be "constrained", the value must be a binary expression, a named value, or a function call. | ||||||
|  |    * If it has a variable name, we need to insert a variable declaration at the correct index. | ||||||
|  |    */ | ||||||
|  |   if ( | ||||||
|  |     'variableName' in length && | ||||||
|  |     length.variableName && | ||||||
|  |     length.insertIndex !== undefined | ||||||
|  |   ) { | ||||||
|  |     const newBody = [...ast.body] | ||||||
|  |     newBody.splice(length.insertIndex, 0, length.variableDeclarationAst) | ||||||
|  |     ast.body = newBody | ||||||
|  |     distanceExpression = createIdentifier(length.variableName) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!isExprBinaryPart(distanceExpression)) { | ||||||
|  |     return new Error('Invalid valueNode, is not a BinaryPart') | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   const retval = transformAstSketchLines({ | ||||||
|  |     ast, | ||||||
|  |     selectionRanges, | ||||||
|  |     transformInfos: transforms, | ||||||
|  |     programMemory: kclManager.programMemory, | ||||||
|  |     referenceSegName: '', | ||||||
|  |     forceValueUsedInTransform: distanceExpression, | ||||||
|  |   }) | ||||||
|  |   if (err(retval)) return Promise.reject(retval) | ||||||
|  |  | ||||||
|  |   const { modifiedAst: _modifiedAst, pathToNodeMap } = retval | ||||||
|  |  | ||||||
|  |   return { | ||||||
|  |     modifiedAst: _modifiedAst, | ||||||
|  |     pathToNodeMap, | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| export async function applyConstraintAngleLength({ | export async function applyConstraintAngleLength({ | ||||||
|   selectionRanges, |   selectionRanges, | ||||||
|   angleOrLength = 'setLength', |   angleOrLength = 'setLength', | ||||||
|  | |||||||
| @ -139,7 +139,9 @@ export default class EditorManager { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   setHighlightRange(range: Array<Selection['codeRef']['range']>): void { |   setHighlightRange(range: Array<Selection['codeRef']['range']>): void { | ||||||
|     this._highlightRange = range |     this._highlightRange = range.map((s): [number, number] => { | ||||||
|  |       return [s[0], s[1]] | ||||||
|  |     }) | ||||||
|  |  | ||||||
|     const selectionsWithSafeEnds = range.map((s): [number, number] => { |     const selectionsWithSafeEnds = range.map((s): [number, number] => { | ||||||
|       const safeEnd = Math.min(s[1], this._editorView?.state.doc.length || s[1]) |       const safeEnd = Math.min(s[1], this._editorView?.state.doc.length || s[1]) | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ import { | |||||||
| import { err, reportRejection } from 'lib/trap' | import { err, reportRejection } from 'lib/trap' | ||||||
| import { DefaultPlaneStr, getFaceDetails } from 'clientSideScene/sceneEntities' | import { DefaultPlaneStr, getFaceDetails } from 'clientSideScene/sceneEntities' | ||||||
| import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst' | import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst' | ||||||
| import { CallExpression } from 'lang/wasm' | import { CallExpression, defaultSourceRange } from 'lang/wasm' | ||||||
| import { EdgeCutInfo, ExtrudeFacePlane } from 'machines/modelingMachine' | import { EdgeCutInfo, ExtrudeFacePlane } from 'machines/modelingMachine' | ||||||
|  |  | ||||||
| export function useEngineConnectionSubscriptions() { | export function useEngineConnectionSubscriptions() { | ||||||
| @ -46,7 +46,7 @@ export function useEngineConnectionSubscriptions() { | |||||||
|           (editorManager.highlightRange[0][0] !== 0 && |           (editorManager.highlightRange[0][0] !== 0 && | ||||||
|             editorManager.highlightRange[0][1] !== 0) |             editorManager.highlightRange[0][1] !== 0) | ||||||
|         ) { |         ) { | ||||||
|           editorManager.setHighlightRange([[0, 0]]) |           editorManager.setHighlightRange([defaultSourceRange()]) | ||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
|     }) |     }) | ||||||
| @ -201,7 +201,7 @@ export function useEngineConnectionSubscriptions() { | |||||||
|               const { z_axis, y_axis, origin } = faceInfo |               const { z_axis, y_axis, origin } = faceInfo | ||||||
|               const sketchPathToNode = getNodePathFromSourceRange( |               const sketchPathToNode = getNodePathFromSourceRange( | ||||||
|                 kclManager.ast, |                 kclManager.ast, | ||||||
|                 err(codeRef) ? [0, 0] : codeRef.range |                 err(codeRef) ? defaultSourceRange() : codeRef.range | ||||||
|               ) |               ) | ||||||
|  |  | ||||||
|               const getEdgeCutMeta = (): null | EdgeCutInfo => { |               const getEdgeCutMeta = (): null | EdgeCutInfo => { | ||||||
|  | |||||||
| @ -24,6 +24,8 @@ export function useConvertToVariable(range?: SourceRange) { | |||||||
|   }, [enable]) |   }, [enable]) | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|  |     // Return early if there are no selection ranges for whatever reason | ||||||
|  |     if (!context.selectionRanges) return | ||||||
|     const parsed = ast |     const parsed = ast | ||||||
|  |  | ||||||
|     const meta = isNodeSafeToReplace( |     const meta = isNodeSafeToReplace( | ||||||
|  | |||||||
| @ -1,15 +1,15 @@ | |||||||
| import { KCLError } from './errors' |  | ||||||
| import { createContext, useContext, useEffect, useState } from 'react' | import { createContext, useContext, useEffect, useState } from 'react' | ||||||
| import { type IndexLoaderData } from 'lib/types' | import { type IndexLoaderData } from 'lib/types' | ||||||
| import { useLoaderData } from 'react-router-dom' | import { useLoaderData } from 'react-router-dom' | ||||||
| import { codeManager, kclManager } from 'lib/singletons' | import { codeManager, kclManager } from 'lib/singletons' | ||||||
|  | import { Diagnostic } from '@codemirror/lint' | ||||||
|  |  | ||||||
| const KclContext = createContext({ | const KclContext = createContext({ | ||||||
|   code: codeManager?.code || '', |   code: codeManager?.code || '', | ||||||
|   programMemory: kclManager?.programMemory, |   programMemory: kclManager?.programMemory, | ||||||
|   ast: kclManager?.ast, |   ast: kclManager?.ast, | ||||||
|   isExecuting: kclManager?.isExecuting, |   isExecuting: kclManager?.isExecuting, | ||||||
|   errors: kclManager?.kclErrors, |   diagnostics: kclManager?.diagnostics, | ||||||
|   logs: kclManager?.logs, |   logs: kclManager?.logs, | ||||||
|   wasmInitFailed: kclManager?.wasmInitFailed, |   wasmInitFailed: kclManager?.wasmInitFailed, | ||||||
| }) | }) | ||||||
| @ -32,7 +32,7 @@ export function KclContextProvider({ | |||||||
|   const [programMemory, setProgramMemory] = useState(kclManager.programMemory) |   const [programMemory, setProgramMemory] = useState(kclManager.programMemory) | ||||||
|   const [ast, setAst] = useState(kclManager.ast) |   const [ast, setAst] = useState(kclManager.ast) | ||||||
|   const [isExecuting, setIsExecuting] = useState(false) |   const [isExecuting, setIsExecuting] = useState(false) | ||||||
|   const [errors, setErrors] = useState<KCLError[]>([]) |   const [diagnostics, setErrors] = useState<Diagnostic[]>([]) | ||||||
|   const [logs, setLogs] = useState<string[]>([]) |   const [logs, setLogs] = useState<string[]>([]) | ||||||
|   const [wasmInitFailed, setWasmInitFailed] = useState(false) |   const [wasmInitFailed, setWasmInitFailed] = useState(false) | ||||||
|  |  | ||||||
| @ -57,7 +57,7 @@ export function KclContextProvider({ | |||||||
|         programMemory, |         programMemory, | ||||||
|         ast, |         ast, | ||||||
|         isExecuting, |         isExecuting, | ||||||
|         errors, |         diagnostics, | ||||||
|         logs, |         logs, | ||||||
|         wasmInitFailed, |         wasmInitFailed, | ||||||
|       }} |       }} | ||||||
|  | |||||||
| @ -1,6 +1,10 @@ | |||||||
| import { executeAst, lintAst } from 'lang/langHelpers' | import { executeAst, lintAst } from 'lang/langHelpers' | ||||||
| import { Selections } from 'lib/selections' | import { Selections } from 'lib/selections' | ||||||
| import { KCLError, kclErrorsToDiagnostics } from './errors' | import { | ||||||
|  |   KCLError, | ||||||
|  |   complilationErrorsToDiagnostics, | ||||||
|  |   kclErrorsToDiagnostics, | ||||||
|  | } from './errors' | ||||||
| import { uuidv4 } from 'lib/utils' | import { uuidv4 } from 'lib/utils' | ||||||
| import { EngineCommandManager } from './std/engineConnection' | import { EngineCommandManager } from './std/engineConnection' | ||||||
| import { err } from 'lib/trap' | import { err } from 'lib/trap' | ||||||
| @ -8,6 +12,7 @@ import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants' | |||||||
|  |  | ||||||
| import { | import { | ||||||
|   CallExpression, |   CallExpression, | ||||||
|  |   clearSceneAndBustCache, | ||||||
|   emptyExecState, |   emptyExecState, | ||||||
|   ExecState, |   ExecState, | ||||||
|   initPromise, |   initPromise, | ||||||
| @ -51,11 +56,12 @@ export class KclManager { | |||||||
|   private _programMemory: ProgramMemory = ProgramMemory.empty() |   private _programMemory: ProgramMemory = ProgramMemory.empty() | ||||||
|   lastSuccessfulProgramMemory: ProgramMemory = ProgramMemory.empty() |   lastSuccessfulProgramMemory: ProgramMemory = ProgramMemory.empty() | ||||||
|   private _logs: string[] = [] |   private _logs: string[] = [] | ||||||
|   private _lints: Diagnostic[] = [] |   private _diagnostics: Diagnostic[] = [] | ||||||
|   private _kclErrors: KCLError[] = [] |  | ||||||
|   private _isExecuting = false |   private _isExecuting = false | ||||||
|   private _executeIsStale: ExecuteArgs | null = null |   private _executeIsStale: ExecuteArgs | null = null | ||||||
|   private _wasmInitFailed = true |   private _wasmInitFailed = true | ||||||
|  |   private _hasErrors = false | ||||||
|  |   private _switchedFiles = false | ||||||
|  |  | ||||||
|   engineCommandManager: EngineCommandManager |   engineCommandManager: EngineCommandManager | ||||||
|  |  | ||||||
| @ -63,7 +69,7 @@ export class KclManager { | |||||||
|   private _astCallBack: (arg: Node<Program>) => void = () => {} |   private _astCallBack: (arg: Node<Program>) => void = () => {} | ||||||
|   private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {} |   private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {} | ||||||
|   private _logsCallBack: (arg: string[]) => void = () => {} |   private _logsCallBack: (arg: string[]) => void = () => {} | ||||||
|   private _kclErrorsCallBack: (arg: KCLError[]) => void = () => {} |   private _kclErrorsCallBack: (errors: Diagnostic[]) => void = () => {} | ||||||
|   private _wasmInitFailedCallback: (arg: boolean) => void = () => {} |   private _wasmInitFailedCallback: (arg: boolean) => void = () => {} | ||||||
|   private _executeCallback: () => void = () => {} |   private _executeCallback: () => void = () => {} | ||||||
|  |  | ||||||
| @ -75,6 +81,10 @@ export class KclManager { | |||||||
|     this._astCallBack(ast) |     this._astCallBack(ast) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   set switchedFiles(switchedFiles: boolean) { | ||||||
|  |     this._switchedFiles = switchedFiles | ||||||
|  |   } | ||||||
|  |  | ||||||
|   get programMemory() { |   get programMemory() { | ||||||
|     return this._programMemory |     return this._programMemory | ||||||
|   } |   } | ||||||
| @ -84,7 +94,7 @@ export class KclManager { | |||||||
|     this._programMemoryCallBack(programMemory) |     this._programMemoryCallBack(programMemory) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   set execState(execState) { |   private set execState(execState) { | ||||||
|     this._execState = execState |     this._execState = execState | ||||||
|     this.programMemory = execState.memory |     this.programMemory = execState.memory | ||||||
|   } |   } | ||||||
| @ -101,38 +111,28 @@ export class KclManager { | |||||||
|     this._logsCallBack(logs) |     this._logsCallBack(logs) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   get lints() { |   get diagnostics() { | ||||||
|     return this._lints |     return this._diagnostics | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   set lints(lints) { |   set diagnostics(ds) { | ||||||
|     if (lints === this._lints) return |     if (ds === this._diagnostics) return | ||||||
|     this._lints = lints |     this._diagnostics = ds | ||||||
|     // Run the lints through the diagnostics. |  | ||||||
|     this.kclErrors = this._kclErrors |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   get kclErrors() { |  | ||||||
|     return this._kclErrors |  | ||||||
|   } |  | ||||||
|   set kclErrors(kclErrors) { |  | ||||||
|     if (kclErrors === this._kclErrors && this.lints.length === 0) return |  | ||||||
|     this._kclErrors = kclErrors |  | ||||||
|     this.setDiagnosticsForCurrentErrors() |     this.setDiagnosticsForCurrentErrors() | ||||||
|     this._kclErrorsCallBack(kclErrors) |   } | ||||||
|  |  | ||||||
|  |   addDiagnostics(ds: Diagnostic[]) { | ||||||
|  |     if (ds.length === 0) return | ||||||
|  |     this.diagnostics = this.diagnostics.concat(ds) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   hasErrors(): boolean { | ||||||
|  |     return this._hasErrors | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   setDiagnosticsForCurrentErrors() { |   setDiagnosticsForCurrentErrors() { | ||||||
|     let diagnostics = kclErrorsToDiagnostics(this.kclErrors) |     editorManager?.setDiagnostics(this.diagnostics) | ||||||
|     if (this.lints.length > 0) { |     this._kclErrorsCallBack(this.diagnostics) | ||||||
|       diagnostics = diagnostics.concat(this.lints) |  | ||||||
|     } |  | ||||||
|     editorManager?.setDiagnostics(diagnostics) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   addKclErrors(kclErrors: KCLError[]) { |  | ||||||
|     if (kclErrors.length === 0) return |  | ||||||
|     this.kclErrors = this.kclErrors.concat(kclErrors) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   get isExecuting() { |   get isExecuting() { | ||||||
| @ -172,8 +172,12 @@ export class KclManager { | |||||||
|     this.engineCommandManager = engineCommandManager |     this.engineCommandManager = engineCommandManager | ||||||
|  |  | ||||||
|     // eslint-disable-next-line @typescript-eslint/no-floating-promises |     // eslint-disable-next-line @typescript-eslint/no-floating-promises | ||||||
|     this.ensureWasmInit().then(() => { |     this.ensureWasmInit().then(async () => { | ||||||
|       this.ast = this.safeParse(codeManager.code) || this.ast |       await this.safeParse(codeManager.code).then((ast) => { | ||||||
|  |         if (ast) { | ||||||
|  |           this.ast = ast | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @ -188,7 +192,7 @@ export class KclManager { | |||||||
|     setProgramMemory: (arg: ProgramMemory) => void |     setProgramMemory: (arg: ProgramMemory) => void | ||||||
|     setAst: (arg: Node<Program>) => void |     setAst: (arg: Node<Program>) => void | ||||||
|     setLogs: (arg: string[]) => void |     setLogs: (arg: string[]) => void | ||||||
|     setKclErrors: (arg: KCLError[]) => void |     setKclErrors: (errors: Diagnostic[]) => void | ||||||
|     setIsExecuting: (arg: boolean) => void |     setIsExecuting: (arg: boolean) => void | ||||||
|     setWasmInitFailed: (arg: boolean) => void |     setWasmInitFailed: (arg: boolean) => void | ||||||
|   }) { |   }) { | ||||||
| @ -217,18 +221,48 @@ export class KclManager { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   safeParse(code: string): Node<Program> | null { |   // (jess) I'm not in love with this, but it ensures we clear the scene and | ||||||
|     const ast = parse(code) |   // bust the cache on | ||||||
|     this.lints = [] |   // errors from parsing when opening new files. | ||||||
|     this.kclErrors = [] |   // Why not just clear the cache on all parse errors, you ask? well its actually | ||||||
|     if (!err(ast)) return ast |   // really nice to keep the cache on parse errors within the same file, and | ||||||
|     const kclerror: KCLError = ast as KCLError |   // only bust on engine errors esp if they take a long time to execute and | ||||||
|  |   // you hit the wrong key! | ||||||
|  |   private async checkIfSwitchedFilesShouldClear() { | ||||||
|  |     // If we were switching files and we hit an error on parse we need to bust | ||||||
|  |     // the cache and clear the scene. | ||||||
|  |     if (this._hasErrors && this._switchedFiles) { | ||||||
|  |       await clearSceneAndBustCache(this.engineCommandManager) | ||||||
|  |     } else if (this._switchedFiles) { | ||||||
|  |       // Reset the switched files boolean. | ||||||
|  |       this._switchedFiles = false | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|     this.addKclErrors([kclerror]) |   async safeParse(code: string): Promise<Node<Program> | null> { | ||||||
|     // TODO: re-eval if session should end? |     const result = parse(code) | ||||||
|     if (kclerror.msg === 'file is empty') |     this.diagnostics = [] | ||||||
|       this.engineCommandManager?.endSession() |     this._hasErrors = false | ||||||
|     return null |  | ||||||
|  |     if (err(result)) { | ||||||
|  |       const kclerror: KCLError = result as KCLError | ||||||
|  |       this.diagnostics = kclErrorsToDiagnostics([kclerror]) | ||||||
|  |       this._hasErrors = true | ||||||
|  |  | ||||||
|  |       await this.checkIfSwitchedFilesShouldClear() | ||||||
|  |       return null | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     this.addDiagnostics(complilationErrorsToDiagnostics(result.errors)) | ||||||
|  |     this.addDiagnostics(complilationErrorsToDiagnostics(result.warnings)) | ||||||
|  |     if (result.errors.length > 0) { | ||||||
|  |       this._hasErrors = true | ||||||
|  |  | ||||||
|  |       await this.checkIfSwitchedFilesShouldClear() | ||||||
|  |       return null | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return result.program | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   async ensureWasmInit() { |   async ensureWasmInit() { | ||||||
| @ -267,19 +301,16 @@ export class KclManager { | |||||||
|     this._cancelTokens.set(currentExecutionId, false) |     this._cancelTokens.set(currentExecutionId, false) | ||||||
|  |  | ||||||
|     this.isExecuting = true |     this.isExecuting = true | ||||||
|     // Make sure we clear before starting again. End session will do this. |  | ||||||
|     this.engineCommandManager?.endSession() |  | ||||||
|     await this.ensureWasmInit() |     await this.ensureWasmInit() | ||||||
|     const { logs, errors, execState, isInterrupted } = await executeAst({ |     const { logs, errors, execState, isInterrupted } = await executeAst({ | ||||||
|       ast, |       ast, | ||||||
|       idGenerator: this.execState.idGenerator, |  | ||||||
|       engineCommandManager: this.engineCommandManager, |       engineCommandManager: this.engineCommandManager, | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     // Program was not interrupted, setup the scene |     // Program was not interrupted, setup the scene | ||||||
|     // Do not send send scene commands if the program was interrupted, go to clean up |     // Do not send send scene commands if the program was interrupted, go to clean up | ||||||
|     if (!isInterrupted) { |     if (!isInterrupted) { | ||||||
|       this.lints = await lintAst({ ast: ast }) |       this.addDiagnostics(await lintAst({ ast: ast })) | ||||||
|  |  | ||||||
|       sceneInfra.modelingSend({ type: 'code edit during sketch' }) |       sceneInfra.modelingSend({ type: 'code edit during sketch' }) | ||||||
|       setSelectionFilterToDefault(execState.memory, this.engineCommandManager) |       setSelectionFilterToDefault(execState.memory, this.engineCommandManager) | ||||||
| @ -321,9 +352,7 @@ export class KclManager { | |||||||
|  |  | ||||||
|     this.logs = logs |     this.logs = logs | ||||||
|     // Do not add the errors since the program was interrupted and the error is not a real KCL error |     // Do not add the errors since the program was interrupted and the error is not a real KCL error | ||||||
|     this.addKclErrors(isInterrupted ? [] : errors) |     this.addDiagnostics(isInterrupted ? [] : kclErrorsToDiagnostics(errors)) | ||||||
|     // Reset the next ID index so that we reuse the previous IDs next time. |  | ||||||
|     execState.idGenerator.nextId = 0 |  | ||||||
|     this.execState = execState |     this.execState = execState | ||||||
|     if (!errors.length) { |     if (!errors.length) { | ||||||
|       this.lastSuccessfulProgramMemory = execState.memory |       this.lastSuccessfulProgramMemory = execState.memory | ||||||
| @ -355,7 +384,7 @@ export class KclManager { | |||||||
|       console.error(newCode) |       console.error(newCode) | ||||||
|       return |       return | ||||||
|     } |     } | ||||||
|     const newAst = this.safeParse(newCode) |     const newAst = await this.safeParse(newCode) | ||||||
|     if (!newAst) { |     if (!newAst) { | ||||||
|       this.clearAst() |       this.clearAst() | ||||||
|       return |       return | ||||||
| @ -364,13 +393,13 @@ export class KclManager { | |||||||
|  |  | ||||||
|     const { logs, errors, execState } = await executeAst({ |     const { logs, errors, execState } = await executeAst({ | ||||||
|       ast: newAst, |       ast: newAst, | ||||||
|       idGenerator: this.execState.idGenerator, |  | ||||||
|       engineCommandManager: this.engineCommandManager, |       engineCommandManager: this.engineCommandManager, | ||||||
|       useFakeExecutor: true, |       // We make sure to send an empty program memory to denote we mean mock mode. | ||||||
|  |       programMemoryOverride: ProgramMemory.empty(), | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     this._logs = logs |     this._logs = logs | ||||||
|     this._kclErrors = errors |     this.addDiagnostics(kclErrorsToDiagnostics(errors)) | ||||||
|     this._execState = execState |     this._execState = execState | ||||||
|     this._programMemory = execState.memory |     this._programMemory = execState.memory | ||||||
|     if (!errors.length) { |     if (!errors.length) { | ||||||
| @ -398,7 +427,7 @@ export class KclManager { | |||||||
|           ...artifact, |           ...artifact, | ||||||
|           codeRef: { |           codeRef: { | ||||||
|             ...artifact.codeRef, |             ...artifact.codeRef, | ||||||
|             range: [node.start, node.end], |             range: [node.start, node.end, true], | ||||||
|           }, |           }, | ||||||
|         }) |         }) | ||||||
|       } |       } | ||||||
| @ -410,7 +439,7 @@ export class KclManager { | |||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
|   async executeCode(zoomToFit?: boolean): Promise<void> { |   async executeCode(zoomToFit?: boolean): Promise<void> { | ||||||
|     const ast = this.safeParse(codeManager.code) |     const ast = await this.safeParse(codeManager.code) | ||||||
|     if (!ast) { |     if (!ast) { | ||||||
|       this.clearAst() |       this.clearAst() | ||||||
|       return |       return | ||||||
| @ -418,9 +447,9 @@ export class KclManager { | |||||||
|     this.ast = { ...ast } |     this.ast = { ...ast } | ||||||
|     return this.executeAst({ zoomToFit }) |     return this.executeAst({ zoomToFit }) | ||||||
|   } |   } | ||||||
|   format() { |   async format() { | ||||||
|     const originalCode = codeManager.code |     const originalCode = codeManager.code | ||||||
|     const ast = this.safeParse(originalCode) |     const ast = await this.safeParse(originalCode) | ||||||
|     if (!ast) { |     if (!ast) { | ||||||
|       this.clearAst() |       this.clearAst() | ||||||
|       return |       return | ||||||
| @ -460,7 +489,7 @@ export class KclManager { | |||||||
|     const newCode = recast(ast) |     const newCode = recast(ast) | ||||||
|     if (err(newCode)) return Promise.reject(newCode) |     if (err(newCode)) return Promise.reject(newCode) | ||||||
|  |  | ||||||
|     const astWithUpdatedSource = this.safeParse(newCode) |     const astWithUpdatedSource = await this.safeParse(newCode) | ||||||
|     if (!astWithUpdatedSource) return Promise.reject(new Error('bad ast')) |     if (!astWithUpdatedSource) return Promise.reject(new Error('bad ast')) | ||||||
|     let returnVal: Selections | undefined = undefined |     let returnVal: Selections | undefined = undefined | ||||||
|  |  | ||||||
| @ -490,7 +519,7 @@ export class KclManager { | |||||||
|         if (start && end) { |         if (start && end) { | ||||||
|           returnVal.graphSelections.push({ |           returnVal.graphSelections.push({ | ||||||
|             codeRef: { |             codeRef: { | ||||||
|               range: [start, end], |               range: [start, end, true], | ||||||
|               pathToNode: path, |               pathToNode: path, | ||||||
|             }, |             }, | ||||||
|           }) |           }) | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import { parse, initPromise } from './wasm' | import { assertParse, initPromise } from './wasm' | ||||||
| import { enginelessExecutor } from '../lib/testHelpers' | import { enginelessExecutor } from '../lib/testHelpers' | ||||||
|  |  | ||||||
| beforeAll(async () => { | beforeAll(async () => { | ||||||
| @ -14,7 +14,7 @@ const mySketch001 = startSketchOn('XY') | |||||||
|   |> lineTo([-1.59, -1.54], %) |   |> lineTo([-1.59, -1.54], %) | ||||||
|   |> lineTo([0.46, -5.82], %) |   |> lineTo([0.46, -5.82], %) | ||||||
|   // |> rx(45, %)` |   // |> rx(45, %)` | ||||||
|     const execState = await enginelessExecutor(parse(code)) |     const execState = await enginelessExecutor(assertParse(code)) | ||||||
|     // @ts-ignore |     // @ts-ignore | ||||||
|     const sketch001 = execState.memory.get('mySketch001') |     const sketch001 = execState.memory.get('mySketch001') | ||||||
|     expect(sketch001).toEqual({ |     expect(sketch001).toEqual({ | ||||||
| @ -67,7 +67,7 @@ const mySketch001 = startSketchOn('XY') | |||||||
|   |> lineTo([0.46, -5.82], %) |   |> lineTo([0.46, -5.82], %) | ||||||
|   // |> rx(45, %) |   // |> rx(45, %) | ||||||
|   |> extrude(2, %)` |   |> extrude(2, %)` | ||||||
|     const execState = await enginelessExecutor(parse(code)) |     const execState = await enginelessExecutor(assertParse(code)) | ||||||
|     // @ts-ignore |     // @ts-ignore | ||||||
|     const sketch001 = execState.memory.get('mySketch001') |     const sketch001 = execState.memory.get('mySketch001') | ||||||
|     expect(sketch001).toEqual({ |     expect(sketch001).toEqual({ | ||||||
| @ -147,7 +147,7 @@ const sk2 = startSketchOn('XY') | |||||||
|   |> extrude(2, %) |   |> extrude(2, %) | ||||||
|  |  | ||||||
| ` | ` | ||||||
|     const execState = await enginelessExecutor(parse(code)) |     const execState = await enginelessExecutor(assertParse(code)) | ||||||
|     const programMemory = execState.memory |     const programMemory = execState.memory | ||||||
|     // @ts-ignore |     // @ts-ignore | ||||||
|     const geos = [programMemory.get('theExtrude'), programMemory.get('sk2')] |     const geos = [programMemory.get('theExtrude'), programMemory.get('sk2')] | ||||||
|  | |||||||
| @ -8,20 +8,14 @@ describe('test kclErrToDiagnostic', () => { | |||||||
|         message: '', |         message: '', | ||||||
|         kind: 'semantic', |         kind: 'semantic', | ||||||
|         msg: 'Semantic error', |         msg: 'Semantic error', | ||||||
|         sourceRanges: [ |         sourceRange: [0, 1, true], | ||||||
|           [0, 1, 0], |  | ||||||
|           [2, 3, 0], |  | ||||||
|         ], |  | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         name: '', |         name: '', | ||||||
|         message: '', |         message: '', | ||||||
|         kind: 'type', |         kind: 'type', | ||||||
|         msg: 'Type error', |         msg: 'Type error', | ||||||
|         sourceRanges: [ |         sourceRange: [4, 5, true], | ||||||
|           [4, 5, 0], |  | ||||||
|           [6, 7, 0], |  | ||||||
|         ], |  | ||||||
|       }, |       }, | ||||||
|     ] |     ] | ||||||
|     const diagnostics = kclErrorsToDiagnostics(errors) |     const diagnostics = kclErrorsToDiagnostics(errors) | ||||||
| @ -32,24 +26,12 @@ describe('test kclErrToDiagnostic', () => { | |||||||
|         message: 'Semantic error', |         message: 'Semantic error', | ||||||
|         severity: 'error', |         severity: 'error', | ||||||
|       }, |       }, | ||||||
|       { |  | ||||||
|         from: 2, |  | ||||||
|         to: 3, |  | ||||||
|         message: 'Semantic error', |  | ||||||
|         severity: 'error', |  | ||||||
|       }, |  | ||||||
|       { |       { | ||||||
|         from: 4, |         from: 4, | ||||||
|         to: 5, |         to: 5, | ||||||
|         message: 'Type error', |         message: 'Type error', | ||||||
|         severity: 'error', |         severity: 'error', | ||||||
|       }, |       }, | ||||||
|       { |  | ||||||
|         from: 6, |  | ||||||
|         to: 7, |  | ||||||
|         message: 'Type error', |  | ||||||
|         severity: 'error', |  | ||||||
|       }, |  | ||||||
|     ]) |     ]) | ||||||
|   }) |   }) | ||||||
| }) | }) | ||||||
|  | |||||||
| @ -1,88 +1,90 @@ | |||||||
| import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError' | import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError' | ||||||
|  | import { CompilationError } from 'wasm-lib/kcl/bindings/CompilationError' | ||||||
| import { Diagnostic as CodeMirrorDiagnostic } from '@codemirror/lint' | import { Diagnostic as CodeMirrorDiagnostic } from '@codemirror/lint' | ||||||
| import { posToOffset } from '@kittycad/codemirror-lsp-client' | import { posToOffset } from '@kittycad/codemirror-lsp-client' | ||||||
| import { Diagnostic as LspDiagnostic } from 'vscode-languageserver-protocol' | import { Diagnostic as LspDiagnostic } from 'vscode-languageserver-protocol' | ||||||
| import { Text } from '@codemirror/state' | import { Text } from '@codemirror/state' | ||||||
|  | import { EditorView } from 'codemirror' | ||||||
| const TOP_LEVEL_MODULE_ID = 0 | import { SourceRange } from 'lang/wasm' | ||||||
|  |  | ||||||
| type ExtractKind<T> = T extends { kind: infer K } ? K : never | type ExtractKind<T> = T extends { kind: infer K } ? K : never | ||||||
| export class KCLError extends Error { | export class KCLError extends Error { | ||||||
|   kind: ExtractKind<RustKclError> | 'name' |   kind: ExtractKind<RustKclError> | 'name' | ||||||
|   sourceRanges: [number, number, number][] |   sourceRange: SourceRange | ||||||
|   msg: string |   msg: string | ||||||
|  |  | ||||||
|   constructor( |   constructor( | ||||||
|     kind: ExtractKind<RustKclError> | 'name', |     kind: ExtractKind<RustKclError> | 'name', | ||||||
|     msg: string, |     msg: string, | ||||||
|     sourceRanges: [number, number, number][] |     sourceRange: SourceRange | ||||||
|   ) { |   ) { | ||||||
|     super() |     super() | ||||||
|     this.kind = kind |     this.kind = kind | ||||||
|     this.msg = msg |     this.msg = msg | ||||||
|     this.sourceRanges = sourceRanges |     this.sourceRange = sourceRange | ||||||
|     Object.setPrototypeOf(this, KCLError.prototype) |     Object.setPrototypeOf(this, KCLError.prototype) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| export class KCLLexicalError extends KCLError { | export class KCLLexicalError extends KCLError { | ||||||
|   constructor(msg: string, sourceRanges: [number, number, number][]) { |   constructor(msg: string, sourceRange: SourceRange) { | ||||||
|     super('lexical', msg, sourceRanges) |     super('lexical', msg, sourceRange) | ||||||
|     Object.setPrototypeOf(this, KCLSyntaxError.prototype) |     Object.setPrototypeOf(this, KCLSyntaxError.prototype) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| export class KCLInternalError extends KCLError { | export class KCLInternalError extends KCLError { | ||||||
|   constructor(msg: string, sourceRanges: [number, number, number][]) { |   constructor(msg: string, sourceRange: SourceRange) { | ||||||
|     super('internal', msg, sourceRanges) |     super('internal', msg, sourceRange) | ||||||
|     Object.setPrototypeOf(this, KCLSyntaxError.prototype) |     Object.setPrototypeOf(this, KCLSyntaxError.prototype) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| export class KCLSyntaxError extends KCLError { | export class KCLSyntaxError extends KCLError { | ||||||
|   constructor(msg: string, sourceRanges: [number, number, number][]) { |   constructor(msg: string, sourceRange: SourceRange) { | ||||||
|     super('syntax', msg, sourceRanges) |     super('syntax', msg, sourceRange) | ||||||
|     Object.setPrototypeOf(this, KCLSyntaxError.prototype) |     Object.setPrototypeOf(this, KCLSyntaxError.prototype) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| export class KCLSemanticError extends KCLError { | export class KCLSemanticError extends KCLError { | ||||||
|   constructor(msg: string, sourceRanges: [number, number, number][]) { |   constructor(msg: string, sourceRange: SourceRange) { | ||||||
|     super('semantic', msg, sourceRanges) |     super('semantic', msg, sourceRange) | ||||||
|     Object.setPrototypeOf(this, KCLSemanticError.prototype) |     Object.setPrototypeOf(this, KCLSemanticError.prototype) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| export class KCLTypeError extends KCLError { | export class KCLTypeError extends KCLError { | ||||||
|   constructor(msg: string, sourceRanges: [number, number, number][]) { |   constructor(msg: string, sourceRange: SourceRange) { | ||||||
|     super('type', msg, sourceRanges) |     super('type', msg, sourceRange) | ||||||
|     Object.setPrototypeOf(this, KCLTypeError.prototype) |     Object.setPrototypeOf(this, KCLTypeError.prototype) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| export class KCLUnimplementedError extends KCLError { | export class KCLUnimplementedError extends KCLError { | ||||||
|   constructor(msg: string, sourceRanges: [number, number, number][]) { |   constructor(msg: string, sourceRange: SourceRange) { | ||||||
|     super('unimplemented', msg, sourceRanges) |     super('unimplemented', msg, sourceRange) | ||||||
|     Object.setPrototypeOf(this, KCLUnimplementedError.prototype) |     Object.setPrototypeOf(this, KCLUnimplementedError.prototype) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| export class KCLUnexpectedError extends KCLError { | export class KCLUnexpectedError extends KCLError { | ||||||
|   constructor(msg: string, sourceRanges: [number, number, number][]) { |   constructor(msg: string, sourceRange: SourceRange) { | ||||||
|     super('unexpected', msg, sourceRanges) |     super('unexpected', msg, sourceRange) | ||||||
|     Object.setPrototypeOf(this, KCLUnexpectedError.prototype) |     Object.setPrototypeOf(this, KCLUnexpectedError.prototype) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| export class KCLValueAlreadyDefined extends KCLError { | export class KCLValueAlreadyDefined extends KCLError { | ||||||
|   constructor(key: string, sourceRanges: [number, number, number][]) { |   constructor(key: string, sourceRange: SourceRange) { | ||||||
|     super('name', `Key ${key} was already defined elsewhere`, sourceRanges) |     super('name', `Key ${key} was already defined elsewhere`, sourceRange) | ||||||
|     Object.setPrototypeOf(this, KCLValueAlreadyDefined.prototype) |     Object.setPrototypeOf(this, KCLValueAlreadyDefined.prototype) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| export class KCLUndefinedValueError extends KCLError { | export class KCLUndefinedValueError extends KCLError { | ||||||
|   constructor(key: string, sourceRanges: [number, number, number][]) { |   constructor(key: string, sourceRange: SourceRange) { | ||||||
|     super('name', `Key ${key} has not been defined`, sourceRanges) |     super('name', `Key ${key} has not been defined`, sourceRange) | ||||||
|     Object.setPrototypeOf(this, KCLUndefinedValueError.prototype) |     Object.setPrototypeOf(this, KCLUndefinedValueError.prototype) | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @ -99,27 +101,14 @@ export function lspDiagnosticsToKclErrors( | |||||||
|     .flatMap( |     .flatMap( | ||||||
|       ({ range, message }) => |       ({ range, message }) => | ||||||
|         new KCLError('unexpected', message, [ |         new KCLError('unexpected', message, [ | ||||||
|           [ |           posToOffset(doc, range.start)!, | ||||||
|             posToOffset(doc, range.start)!, |           posToOffset(doc, range.end)!, | ||||||
|             posToOffset(doc, range.end)!, |           true, | ||||||
|             TOP_LEVEL_MODULE_ID, |  | ||||||
|           ], |  | ||||||
|         ]) |         ]) | ||||||
|     ) |     ) | ||||||
|     .filter(({ sourceRanges }) => { |  | ||||||
|       const [from, to, moduleId] = sourceRanges[0] |  | ||||||
|       return ( |  | ||||||
|         from !== null && |  | ||||||
|         to !== null && |  | ||||||
|         from !== undefined && |  | ||||||
|         to !== undefined && |  | ||||||
|         // Filter out errors that are not from the top-level module. |  | ||||||
|         moduleId === TOP_LEVEL_MODULE_ID |  | ||||||
|       ) |  | ||||||
|     }) |  | ||||||
|     .sort((a, b) => { |     .sort((a, b) => { | ||||||
|       const c = a.sourceRanges[0][0] |       const c = a.sourceRange[0] | ||||||
|       const d = b.sourceRanges[0][0] |       const d = b.sourceRange[0] | ||||||
|       switch (true) { |       switch (true) { | ||||||
|         case c < d: |         case c < d: | ||||||
|           return -1 |           return -1 | ||||||
| @ -137,17 +126,48 @@ export function lspDiagnosticsToKclErrors( | |||||||
| export function kclErrorsToDiagnostics( | export function kclErrorsToDiagnostics( | ||||||
|   errors: KCLError[] |   errors: KCLError[] | ||||||
| ): CodeMirrorDiagnostic[] { | ): CodeMirrorDiagnostic[] { | ||||||
|   return errors?.flatMap((err) => { |   return errors | ||||||
|     const sourceRanges: CodeMirrorDiagnostic[] = err.sourceRanges |     ?.filter((err) => err.sourceRange[2]) | ||||||
|       // Filter out errors that are not from the top-level module. |     .map((err) => { | ||||||
|       .filter(([_start, _end, moduleId]) => moduleId === TOP_LEVEL_MODULE_ID) |       return { | ||||||
|       .map(([from, to]) => { |         from: err.sourceRange[0], | ||||||
|         return { from, to, message: err.msg, severity: 'error' } |         to: err.sourceRange[1], | ||||||
|       }) |         message: err.msg, | ||||||
|     // Make sure we didn't filter out all the source ranges. |         severity: 'error', | ||||||
|     if (sourceRanges.length === 0) { |       } | ||||||
|       sourceRanges.push({ from: 0, to: 0, message: err.msg, severity: 'error' }) |     }) | ||||||
|     } | } | ||||||
|     return sourceRanges |  | ||||||
|   }) | export function complilationErrorsToDiagnostics( | ||||||
|  |   errors: CompilationError[] | ||||||
|  | ): CodeMirrorDiagnostic[] { | ||||||
|  |   return errors | ||||||
|  |     ?.filter((err) => err.sourceRange[2] === 0) | ||||||
|  |     .map((err) => { | ||||||
|  |       let severity: any = 'error' | ||||||
|  |       if (err.severity === 'Warning') { | ||||||
|  |         severity = 'warning' | ||||||
|  |       } | ||||||
|  |       let actions | ||||||
|  |       const suggestion = err.suggestion | ||||||
|  |       if (suggestion) { | ||||||
|  |         actions = [ | ||||||
|  |           { | ||||||
|  |             name: suggestion.title, | ||||||
|  |             apply: (view: EditorView, from: number, to: number) => { | ||||||
|  |               view.dispatch({ | ||||||
|  |                 changes: { from, to, insert: suggestion.insert }, | ||||||
|  |               }) | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |         ] | ||||||
|  |       } | ||||||
|  |       return { | ||||||
|  |         from: err.sourceRange[0], | ||||||
|  |         to: err.sourceRange[1], | ||||||
|  |         message: err.message, | ||||||
|  |         severity, | ||||||
|  |         actions, | ||||||
|  |       } | ||||||
|  |     }) | ||||||
| } | } | ||||||
|  | |||||||
