Compare commits

...

36 Commits

Author SHA1 Message Date
64f0f5b773 Cut release v0.26.3 (#4427)
* Cut release v0.26.3

* Support new electron-builder env variable requirements for building in release mode
2024-11-07 15:51:49 -05:00
f452f9bf00 Bump anyhow from 1.0.92 to 1.0.93 in /src/wasm-lib (#4417)
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.92 to 1.0.93.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.92...1.0.93)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-07 10:30:05 -06:00
97705234c6 Fix KCL source ranges to know which source file they point to (#4418)
* Add ts_rs feature to work with indexmap

* Add feature for schemars to work with indexmap

* Add module ID to intern module paths

* Update code to use new source range with three fields

* Update generated files

* Update docs

* Fix wasm

* Fix TS code to use new SourceRange

* Fix TS tests to use new SourceRange and moduleId

* Fix formatting

* Fix to filter errors and source ranges to only show the top-level module

* Fix to reuse module IDs

* Fix to disallow empty path for import

* Revert unneeded Self change

* Rename field to be clearer

* Fix parser tests

* Update snapshots

* Change to not serialize module_id of 0

* Update snapshots after adding default module_id

* Move module_id functions to separate module

* Fix tests for console errors

* Proposal: module ID = 0 gets skipped when serializing tokens too (#4422)

Just like in AST nodes.

Also I think "is_top_level" communicates intention better than is_default

---------

Co-authored-by: Adam Chalmers <adam.chalmers@zoo.dev>
2024-11-07 11:23:41 -05:00
30dfc167d3 Update insta snapshots (#4423)
No meaningful changes, they just added a field to the frontmatter
2024-11-07 11:20:10 -05:00
d8105627c0 Change Dependabot PR frequency to weekly (#4424) 2024-11-07 16:04:21 +00:00
6b7fac3642 Bump happy-dom from 15.10.1 to 15.10.2 (#4409)
Bumps [happy-dom](https://github.com/capricorn86/happy-dom) from 15.10.1 to 15.10.2.
- [Release notes](https://github.com/capricorn86/happy-dom/releases)
- [Commits](https://github.com/capricorn86/happy-dom/compare/v15.10.1...v15.10.2)

---
updated-dependencies:
- dependency-name: happy-dom
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-07 04:54:00 +00:00
35805916aa Fixing directory/file selection logic to deselect folders properly and always hightlight files. (#4408)
* File tree acts as VS Code's file tree

* Adjust test for new expectations

* Remove screenshot

* Actually remove this screenshot

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* fix: fixed logic for selection

* fix: always show the current file

---------

Co-authored-by: 49lf <ircsurfer33@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-06 15:04:22 -05:00
4a4400e979 File tree to act more like VS Code's file tree (#4392)
* File tree acts as VS Code's file tree

* Adjust test for new expectations

* Remove screenshot

* Actually remove this screenshot

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-06 13:32:06 -06:00
efd1f288b9 Bump @types/ws from 8.5.12 to 8.5.13 (#4395)
Bumps [@types/ws](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/ws) from 8.5.12 to 8.5.13.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/ws)

---
updated-dependencies:
- dependency-name: "@types/ws"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-06 13:25:06 -05:00
0337ab9cff Bump url from 2.5.2 to 2.5.3 in /src/wasm-lib (#4399)
Bumps [url](https://github.com/servo/rust-url) from 2.5.2 to 2.5.3.
- [Release notes](https://github.com/servo/rust-url/releases)
- [Commits](https://github.com/servo/rust-url/compare/v2.5.2...v2.5.3)

---
updated-dependencies:
- dependency-name: url
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-06 13:05:33 -05:00
f0dda692f6 Bump thiserror from 1.0.65 to 2.0.0 in /src/wasm-lib (#4397)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.65 to 2.0.0.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.65...2.0.0)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-06 11:36:58 -05:00
2ce0c59d08 Bump pyo3 from 0.22.5 to 0.22.6 in /src/wasm-lib (#4398)
Bumps [pyo3](https://github.com/pyo3/pyo3) from 0.22.5 to 0.22.6.
- [Release notes](https://github.com/pyo3/pyo3/releases)
- [Changelog](https://github.com/PyO3/pyo3/blob/v0.22.6/CHANGELOG.md)
- [Commits](https://github.com/pyo3/pyo3/compare/v0.22.5...v0.22.6)

---
updated-dependencies:
- dependency-name: pyo3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-06 11:25:49 -05:00
393b43d485 Bump insta from 1.41.0 to 1.41.1 in /src/wasm-lib (#4400)
Bumps [insta](https://github.com/mitsuhiko/insta) from 1.41.0 to 1.41.1.
- [Release notes](https://github.com/mitsuhiko/insta/releases)
- [Changelog](https://github.com/mitsuhiko/insta/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mitsuhiko/insta/compare/1.41.0...1.41.1)

---
updated-dependencies:
- dependency-name: insta
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-06 11:25:24 -05:00
4fbcde8773 Bump validator from 0.18.1 to 0.19.0 in /src/wasm-lib (#4396)
Bumps [validator](https://github.com/Keats/validator) from 0.18.1 to 0.19.0.
- [Changelog](https://github.com/Keats/validator/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Keats/validator/commits)

---
updated-dependencies:
- dependency-name: validator
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-06 11:22:21 -05:00
12d444fa69 Bump uuid from 9.0.1 to 11.0.2 (#4393)
Bumps [uuid](https://github.com/uuidjs/uuid) from 9.0.1 to 11.0.2.
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v9.0.1...v11.0.2)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-06 11:21:20 -05:00
683b4488af Bump @electron-forge/maker-zip from 7.4.0 to 7.5.0 (#4394)
Bumps [@electron-forge/maker-zip](https://github.com/electron/forge) from 7.4.0 to 7.5.0.
- [Release notes](https://github.com/electron/forge/releases)
- [Changelog](https://github.com/electron/forge/blob/main/CHANGELOG.md)
- [Commits](https://github.com/electron/forge/compare/v7.4.0...v7.5.0)

---
updated-dependencies:
- dependency-name: "@electron-forge/maker-zip"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-06 11:21:03 -05:00
e1c1e07046 Bump happy-dom from 14.12.3 to 15.10.1 (#4404)
Bumps [happy-dom](https://github.com/capricorn86/happy-dom) from 14.12.3 to 15.10.1.
- [Release notes](https://github.com/capricorn86/happy-dom/releases)
- [Commits](https://github.com/capricorn86/happy-dom/compare/v14.12.3...v15.10.1)

---
updated-dependencies:
- dependency-name: happy-dom
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-06 11:20:41 -05:00
984420c155 Internal fix: make expandPath not assume path has associated sweep (#4386)
* Add a test that shows current error within `expandPath`

* Make `expandPath` not assume there is an associated sweep artifact

* Look at this (photo)Graph *in the voice of Nickelback*

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-06 00:32:05 -05:00
7bad60dfa3 Bump google-github-actions/upload-cloud-storage from 2.2.0 to 2.2.1 (#4364)
Bumps [google-github-actions/upload-cloud-storage](https://github.com/google-github-actions/upload-cloud-storage) from 2.2.0 to 2.2.1.
- [Release notes](https://github.com/google-github-actions/upload-cloud-storage/releases)
- [Changelog](https://github.com/google-github-actions/upload-cloud-storage/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google-github-actions/upload-cloud-storage/compare/v2.2.0...v2.2.1)

---
updated-dependencies:
- dependency-name: google-github-actions/upload-cloud-storage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: 49fl <ircsurfer33@gmail.com>
2024-11-05 21:44:20 +00:00
aaca88220c Bump kittycad-modeling-cmds from 0.2.71 to 0.2.72 in /src/wasm-lib (#4356)
Bumps [kittycad-modeling-cmds](https://github.com/KittyCAD/modeling-api) from 0.2.71 to 0.2.72.
- [Commits](https://github.com/KittyCAD/modeling-api/compare/kittycad-modeling-cmds-0.2.71...kittycad-modeling-cmds-0.2.72)

---
updated-dependencies:
- dependency-name: kittycad-modeling-cmds
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-05 21:35:42 +00:00
360384e8c8 implement a simple startSketchOn / offsetPlane lint rule (#4384) 2024-11-05 16:33:52 -05:00
ab2ad1313f Bump @electron-forge/maker-wix from 7.4.0 to 7.5.0 (#4033)
Bumps [@electron-forge/maker-wix](https://github.com/electron/forge) from 7.4.0 to 7.5.0.
- [Release notes](https://github.com/electron/forge/releases)
- [Changelog](https://github.com/electron/forge/blob/main/CHANGELOG.md)
- [Commits](https://github.com/electron/forge/compare/v7.4.0...v7.5.0)

---
updated-dependencies:
- dependency-name: "@electron-forge/maker-wix"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: 49fl <ircsurfer33@gmail.com>
2024-11-05 16:06:23 -05:00
897205acc2 KCL: More ways to reference paths (#4387)
Adds new stdlib functions segStart, segStartX, segStartY, segEnd

Part of <https://github.com/KittyCAD/modeling-app/issues/4382>
2024-11-05 14:10:35 -06:00
862ca1124e chore: implementing kclsamples in stand alone unit tests (#4358)
* chore: implementing kclsamples in stand alone unit tests

* fix: fmt, lint, and tsc

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* fix: fixed program memory and test file pattern. Don't know how to exclude though?

* fix: trying to fix the exclude logic

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* fix: bump CI

* fix:typo

* fix: had conflicting filters ope, now fixed

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: 49fl <ircsurfer33@gmail.com>
2024-11-05 14:10:00 -05:00
d9981d9d7b Bump insta from 1.40.0 to 1.41.0 in /src/wasm-lib (#4331)
Bumps [insta](https://github.com/mitsuhiko/insta) from 1.40.0 to 1.41.0.
- [Release notes](https://github.com/mitsuhiko/insta/releases)
- [Changelog](https://github.com/mitsuhiko/insta/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mitsuhiko/insta/compare/1.40.0...1.41.0)

---
updated-dependencies:
- dependency-name: insta
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: 49fl <ircsurfer33@gmail.com>
2024-11-05 12:43:18 -05:00
8df0581831 Bump electron-updater from 6.3.0 to 6.3.9 (#4093)
Bumps [electron-updater](https://github.com/electron-userland/electron-builder/tree/HEAD/packages/electron-updater) from 6.3.0 to 6.3.9.
- [Release notes](https://github.com/electron-userland/electron-builder/releases)
- [Changelog](https://github.com/electron-userland/electron-builder/blob/master/packages/electron-updater/CHANGELOG.md)
- [Commits](https://github.com/electron-userland/electron-builder/commits/electron-updater@6.3.9/packages/electron-updater)

---
updated-dependencies:
- dependency-name: electron-updater
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: 49fl <ircsurfer33@gmail.com>
2024-11-05 11:50:25 -05:00
54e6358df1 Bump reqwest from 0.12.8 to 0.12.9 in /src/wasm-lib (#4346)
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.12.8 to 0.12.9.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.12.8...v0.12.9)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: 49fl <ircsurfer33@gmail.com>
2024-11-05 11:49:23 -05:00
daf20a978d Bump @codemirror/language from 6.10.2 to 6.10.3 (#4357)
Bumps [@codemirror/language](https://github.com/codemirror/language) from 6.10.2 to 6.10.3.
- [Changelog](https://github.com/codemirror/language/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/language/compare/6.10.2...6.10.3)

---
updated-dependencies:
- dependency-name: "@codemirror/language"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: 49fl <ircsurfer33@gmail.com>
2024-11-05 15:19:37 +00:00
8e64798dda Bump google-github-actions/auth from 2.1.6 to 2.1.7 (#4363)
Bumps [google-github-actions/auth](https://github.com/google-github-actions/auth) from 2.1.6 to 2.1.7.
- [Release notes](https://github.com/google-github-actions/auth/releases)
- [Changelog](https://github.com/google-github-actions/auth/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google-github-actions/auth/compare/v2.1.6...v2.1.7)

---
updated-dependencies:
- dependency-name: google-github-actions/auth
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: 49fl <ircsurfer33@gmail.com>
2024-11-05 14:50:14 +00:00
a1ceb4fa47 Bump google-github-actions/setup-gcloud from 2.1.0 to 2.1.2 (#4365)
Bumps [google-github-actions/setup-gcloud](https://github.com/google-github-actions/setup-gcloud) from 2.1.0 to 2.1.2.
- [Release notes](https://github.com/google-github-actions/setup-gcloud/releases)
- [Changelog](https://github.com/google-github-actions/setup-gcloud/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google-github-actions/setup-gcloud/compare/v2.1.0...v2.1.2)

---
updated-dependencies:
- dependency-name: google-github-actions/setup-gcloud
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: 49fl <ircsurfer33@gmail.com>
2024-11-05 14:25:36 +00:00
2db8d13051 Back to regular updater-test URL (#4332)
* Back to regular updater-test URL

* Test: build release

* Revert "Test: build release"

This reverts commit 7ed98cc9ed.
2024-11-05 13:46:07 +00:00
aceb8052e2 Bump syn from 2.0.85 to 2.0.87 in /src/wasm-lib (#4379)
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.85 to 2.0.87.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.85...2.0.87)

---
updated-dependencies:
- dependency-name: syn
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: 49fl <ircsurfer33@gmail.com>
2024-11-04 23:30:01 -05:00
62fae1e93b Bump anyhow from 1.0.91 to 1.0.92 in /src/wasm-lib (#4378)
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.91 to 1.0.92.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.91...1.0.92)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: 49fl <ircsurfer33@gmail.com>
2024-11-04 23:29:33 -05:00
2abfbb9788 Move tests from no_visuals into simulation tests (#4367)
Now you can properly inspect program memory for the no_visuals tests, instead of relying on a lot of KCL asserts.
2024-11-05 02:34:22 +00:00
ad1cd56891 Deflake project settings override on desktop (#4370) 2024-10-31 21:42:52 -04:00
26951364cf KCL: More simulation tests (#4359)
Demonstrate simulation tests where we don't care about visuals, e.g. the double-map test. 

The `just new-sim-test` now accepts an optional argument, `render_to_png` which can be either  "true" or "false" (defaults to "true"). Tests like double_map that don't render anything can use false, rather than rendering an empty PNG with nothing in it.

This means the [tests under `no_visuals/`](https://github.com/KittyCAD/modeling-app/tree/v0.26.2/src/wasm-lib/tests/executor/inputs/no_visuals) can be entirely replaced by simulation tests. This is much better! For example, I moved `double_map.kcl` from a no_visuals test to a simulation test. Here's the file:

```
fn increment = (i) => {
  return i + 1
}

xs = [0..2]
ys = xs
  |> map(%, increment)
  |> map(%, increment)
```

Previously the `no_visuals` test just checked that the program ran successfully without panicking. Now the simulation test lets you see the value of `xs` and `ys` and immediately see they're correct. If our map logic changes (for example, we have an off-by-one error and don't apply the `map` to the last element) it'll show up in the program memory snapshot.
2024-10-31 11:43:14 -05:00
283 changed files with 31914 additions and 2073 deletions

View File

@ -4,9 +4,9 @@ set -euo pipefail
if [[ ! -f "test-results/.last-run.json" ]]; then if [[ ! -f "test-results/.last-run.json" ]]; then
# if no last run artifact, than run plawright normally # if no last run artifact, than run plawright normally
echo "run playwright normally" echo "run playwright normally"
if [[ "$3" == "ubuntu-latest" ]]; then if [[ "$3" == ubuntu-latest* ]]; then
yarn test:playwright:browser:chrome:ubuntu -- --shard=$1/$2 || true yarn test:playwright:browser:chrome:ubuntu -- --shard=$1/$2 || true
elif [[ "$3" == "windows-latest" ]]; then elif [[ "$3" == windows-latest* ]]; then
yarn test:playwright:browser:chrome:windows -- --shard=$1/$2 || true yarn test:playwright:browser:chrome:windows -- --shard=$1/$2 || true
else else
echo "Do not run playwright. Unable to detect os runtime." echo "Do not run playwright. Unable to detect os runtime."
@ -26,9 +26,9 @@ while [[ $retry -le $max_retrys ]]; do
if [[ $failed_tests -gt 0 ]]; then if [[ $failed_tests -gt 0 ]]; then
echo "retried=true" >>$GITHUB_OUTPUT echo "retried=true" >>$GITHUB_OUTPUT
echo "run playwright with last failed tests and retry $retry" echo "run playwright with last failed tests and retry $retry"
if [[ "$3" == "ubuntu-latest" ]]; then if [[ "$3" == ubuntu-latest* ]]; then
yarn test:playwright:browser:chrome:ubuntu -- --last-failed || true yarn test:playwright:browser:chrome:ubuntu -- --last-failed || true
elif [[ "$3" == "windows-latest" ]]; then elif [[ "$3" == windows-latest* ]]; then
yarn test:playwright:browser:chrome:windows -- --last-failed || true yarn test:playwright:browser:chrome:windows -- --last-failed || true
else else
echo "Do not run playwright. Unable to detect os runtime." echo "Do not run playwright. Unable to detect os runtime."

View File

@ -4,11 +4,11 @@ set -euo pipefail
if [[ ! -f "test-results/.last-run.json" ]]; then if [[ ! -f "test-results/.last-run.json" ]]; then
# if no last run artifact, than run plawright normally # if no last run artifact, than run plawright normally
echo "run playwright normally" echo "run playwright normally"
if [[ "$1" == "ubuntu-latest" ]]; then if [[ "$1" == ubuntu-latest* ]]; then
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu || true xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu || true
elif [[ "$1" == "windows-latest" ]]; then elif [[ "$1" == windows-latest* ]]; then
yarn test:playwright:electron:windows || true yarn test:playwright:electron:windows || true
elif [[ "$1" == "macos-14" ]]; then elif [[ "$1" == macos-14* ]]; then
yarn test:playwright:electron:macos || true yarn test:playwright:electron:macos || true
else else
echo "Do not run playwright. Unable to detect os runtime." echo "Do not run playwright. Unable to detect os runtime."
@ -28,11 +28,11 @@ while [[ $retry -le $max_retrys ]]; do
if [[ $failed_tests -gt 0 ]]; then if [[ $failed_tests -gt 0 ]]; then
echo "retried=true" >>$GITHUB_OUTPUT echo "retried=true" >>$GITHUB_OUTPUT
echo "run playwright with last failed tests and retry $retry" echo "run playwright with last failed tests and retry $retry"
if [[ "$1" == "ubuntu-latest" ]]; then if [[ "$1" == ubuntu-latest* ]]; then
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu -- --last-failed || true xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu -- --last-failed || true
elif [[ "$1" == "windows-latest" ]]; then elif [[ "$1" == windows-latest* ]]; then
yarn test:playwright:electron:windows -- --last-failed || true yarn test:playwright:electron:windows -- --last-failed || true
elif [[ "$1" == "macos-14" ]]; then elif [[ "$1" == macos-14* ]]; then
yarn test:playwright:electron:macos -- --last-failed || true yarn test:playwright:electron:macos -- --last-failed || true
else else
echo "Do not run playwright. Unable to detect os runtime." echo "Do not run playwright. Unable to detect os runtime."

View File

@ -8,21 +8,21 @@ updates:
- package-ecosystem: 'npm' # See documentation for possible values - package-ecosystem: 'npm' # See documentation for possible values
directory: '/' # Location of package manifests directory: '/' # Location of package manifests
schedule: schedule:
interval: 'daily' interval: 'weekly'
reviewers: reviewers:
- franknoirot - franknoirot
- irev-dev - irev-dev
- package-ecosystem: 'github-actions' # See documentation for possible values - package-ecosystem: 'github-actions' # See documentation for possible values
directory: '/' # Location of package manifests directory: '/' # Location of package manifests
schedule: schedule:
interval: 'daily' interval: 'weekly'
reviewers: reviewers:
- adamchalmers - adamchalmers
- jessfraz - jessfraz
- package-ecosystem: 'cargo' # See documentation for possible values - package-ecosystem: 'cargo' # See documentation for possible values
directory: '/src/wasm-lib/' # Location of package manifests directory: '/src/wasm-lib/' # Location of package manifests
schedule: schedule:
interval: 'daily' interval: 'weekly'
reviewers: reviewers:
- adamchalmers - adamchalmers
- jessfraz - jessfraz

View File

@ -85,7 +85,7 @@ jobs:
- name: Prepare electron-builder.yml file for updater test - name: Prepare electron-builder.yml file for updater test
if: ${{ env.CUT_RELEASE_PR == 'true' }} if: ${{ env.CUT_RELEASE_PR == 'true' }}
run: | run: |
yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/updater-test-release-notes"' electron-builder.yml yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/updater-test"' electron-builder.yml
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
if: ${{ env.CUT_RELEASE_PR == 'true' }} if: ${{ env.CUT_RELEASE_PR == 'true' }}
@ -181,6 +181,7 @@ jobs:
- name: Build the app (release) - name: Build the app (release)
if: ${{ env.BUILD_RELEASE == 'true' }} if: ${{ env.BUILD_RELEASE == 'true' }}
env: env:
PUBLISH_FOR_PULL_REQUEST: true
APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
@ -360,17 +361,17 @@ jobs:
run: "ls -R out" run: "ls -R out"
- name: Authenticate to Google Cloud - name: Authenticate to Google Cloud
uses: 'google-github-actions/auth@v2.1.6' uses: 'google-github-actions/auth@v2.1.7'
with: with:
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}' credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
- name: Set up Google Cloud SDK - name: Set up Google Cloud SDK
uses: google-github-actions/setup-gcloud@v2.1.0 uses: google-github-actions/setup-gcloud@v2.1.2
with: with:
project_id: ${{ env.GOOGLE_CLOUD_PROJECT_ID }} project_id: ${{ env.GOOGLE_CLOUD_PROJECT_ID }}
- name: Upload release files to public bucket - name: Upload release files to public bucket
uses: google-github-actions/upload-cloud-storage@v2.2.0 uses: google-github-actions/upload-cloud-storage@v2.2.1
with: with:
path: out path: out
glob: 'Zoo*' glob: 'Zoo*'
@ -378,7 +379,7 @@ jobs:
destination: ${{ env.BUCKET_DIR }} destination: ${{ env.BUCKET_DIR }}
- name: Upload update endpoint to public bucket - name: Upload update endpoint to public bucket
uses: google-github-actions/upload-cloud-storage@v2.2.0 uses: google-github-actions/upload-cloud-storage@v2.2.1
with: with:
path: out path: out
glob: 'latest*' glob: 'latest*'
@ -386,7 +387,7 @@ jobs:
destination: ${{ env.BUCKET_DIR }} destination: ${{ env.BUCKET_DIR }}
- name: Upload download endpoint to public bucket - name: Upload download endpoint to public bucket
uses: google-github-actions/upload-cloud-storage@v2.2.0 uses: google-github-actions/upload-cloud-storage@v2.2.1
with: with:
path: last_download.json path: last_download.json
destination: ${{ env.BUCKET_DIR }} destination: ${{ env.BUCKET_DIR }}

View File

@ -39,7 +39,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ubuntu-latest, windows-latest] os: [ubuntu-latest-8-cores, windows-latest-8-cores]
shardIndex: [1, 2, 3, 4] shardIndex: [1, 2, 3, 4]
shardTotal: [4] shardTotal: [4]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@ -227,7 +227,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ubuntu-latest, windows-latest, macos-14] os: [ubuntu-latest-8-cores, windows-latest-8-cores, macos-14-large]
timeout-minutes: 60 timeout-minutes: 60
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
needs: check-rust-changes needs: check-rust-changes
@ -287,7 +287,7 @@ jobs:
brew install gnu-sed brew install gnu-sed
echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH
- name: Install vector - name: Install vector
if: ${{ !startsWith(matrix.os, 'windows') }} if: ${{ startsWith(matrix.os, 'ubuntu') }}
shell: bash shell: bash
run: | run: |
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh

View File

@ -81,6 +81,31 @@ jobs:
- name: Run codespell - name: Run codespell
run: codespell --config .codespellrc # Edit this file to tweak the typo list and other configuration. run: codespell --config .codespellrc # Edit this file to tweak the typo list and other configuration.
yarn-unit-test-kcl-samples:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- run: yarn install
- run: yarn build:wasm
- run: yarn simpleserver:bg
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
- name: Install Chromium Browser
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: yarn playwright install chromium --with-deps
- name: run unit tests for kcl samples
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: yarn test:unit:kcl-samples
env:
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
yarn-unit-test: yarn-unit-test:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@ -84,9 +84,13 @@ layout: manual
* [`rem`](kcl/rem) * [`rem`](kcl/rem)
* [`revolve`](kcl/revolve) * [`revolve`](kcl/revolve)
* [`segAng`](kcl/segAng) * [`segAng`](kcl/segAng)
* [`segEnd`](kcl/segEnd)
* [`segEndX`](kcl/segEndX) * [`segEndX`](kcl/segEndX)
* [`segEndY`](kcl/segEndY) * [`segEndY`](kcl/segEndY)
* [`segLen`](kcl/segLen) * [`segLen`](kcl/segLen)
* [`segStart`](kcl/segStart)
* [`segStartX`](kcl/segStartX)
* [`segStartY`](kcl/segStartY)
* [`shell`](kcl/shell) * [`shell`](kcl/shell)
* [`sin`](kcl/sin) * [`sin`](kcl/sin)
* [`sqrt`](kcl/sqrt) * [`sqrt`](kcl/sqrt)

53
docs/kcl/segEnd.md Normal file

File diff suppressed because one or more lines are too long

56
docs/kcl/segStart.md Normal file

File diff suppressed because one or more lines are too long

43
docs/kcl/segStartX.md Normal file

File diff suppressed because one or more lines are too long

44
docs/kcl/segStartY.md Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -632,16 +632,18 @@ test.describe('Editor tests', () => {
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
// this test might be brittle as we add and remove functions
// but should also be easy to update.
// tests clicking on an option, selection the first option // tests clicking on an option, selection the first option
// and arrowing down to an option // and arrowing down to an option
await u.codeLocator.click() await u.codeLocator.click()
await page.keyboard.type('sketch001 = start') await page.keyboard.type('sketch001 = start')
// expect there to be six auto complete options // expect there to be some auto complete options
await expect(page.locator('.cm-completionLabel')).toHaveCount(8) // exact number depends on the KCL stdlib, so let's just check it's > 0 for now.
await expect(async () => {
const children = await page.locator('.cm-completionLabel').count()
expect(children).toBeGreaterThan(0)
}).toPass()
// this makes sure we can accept a completion with click // this makes sure we can accept a completion with click
await page.getByText('startSketchOn').click() await page.getByText('startSketchOn').click()
await page.keyboard.type("'XZ'") await page.keyboard.type("'XZ'")
@ -985,7 +987,7 @@ test.describe('Editor tests', () => {
|> extrude(5, %)`) |> extrude(5, %)`)
}) })
test( test.fixme(
`Can use the import stdlib function on a local OBJ file`, `Can use the import stdlib function on a local OBJ file`,
{ tag: '@electron' }, { tag: '@electron' },
async ({ browserName }, testInfo) => { async ({ browserName }, testInfo) => {

View File

@ -26,10 +26,6 @@ test.describe('integrations tests', () => {
'Creating a new file or switching file while in sketchMode should exit sketchMode', 'Creating a new file or switching file while in sketchMode should exit sketchMode',
{ tag: '@electron' }, { tag: '@electron' },
async ({ tronApp, homePage, scene, editor, toolbar }) => { async ({ tronApp, homePage, scene, editor, toolbar }) => {
test.skip(
process.platform === 'win32',
'windows times out will waiting for the execution indicator?'
)
await tronApp.initialise({ await tronApp.initialise({
fixtures: { homePage, scene, editor, toolbar }, fixtures: { homePage, scene, editor, toolbar },
folderSetupFn: async (dir) => { folderSetupFn: async (dir) => {
@ -55,7 +51,6 @@ test.describe('integrations tests', () => {
sortBy: 'last-modified-desc', sortBy: 'last-modified-desc',
}) })
await homePage.openProject('test-sample') await homePage.openProject('test-sample')
// windows times out here, hence the skip above
await scene.waitForExecutionDone() await scene.waitForExecutionDone()
}) })
await test.step('enter sketch mode', async () => { await test.step('enter sketch mode', async () => {
@ -71,10 +66,13 @@ test.describe('integrations tests', () => {
await toolbar.editSketch() await toolbar.editSketch()
await expect(toolbar.exitSketchBtn).toBeVisible() await expect(toolbar.exitSketchBtn).toBeVisible()
}) })
const fileName = 'Untitled.kcl'
await test.step('check sketch mode is exited when creating new file', async () => { await test.step('check sketch mode is exited when creating new file', async () => {
await toolbar.fileTreeBtn.click() await toolbar.fileTreeBtn.click()
await toolbar.expectFileTreeState(['main.kcl']) await toolbar.expectFileTreeState(['main.kcl'])
await toolbar.createFile({ wait: true })
await toolbar.createFile({ fileName, waitForToastToDisappear: true })
// check we're out of sketch mode // check we're out of sketch mode
await expect(toolbar.exitSketchBtn).not.toBeVisible() await expect(toolbar.exitSketchBtn).not.toBeVisible()
@ -93,10 +91,10 @@ test.describe('integrations tests', () => {
}) })
await toolbar.editSketch() await toolbar.editSketch()
await expect(toolbar.exitSketchBtn).toBeVisible() await expect(toolbar.exitSketchBtn).toBeVisible()
await toolbar.expectFileTreeState(['main.kcl', 'Untitled.kcl']) await toolbar.expectFileTreeState(['main.kcl', fileName])
}) })
await test.step('check sketch mode is exited when opening a different file', async () => { await test.step('check sketch mode is exited when opening a different file', async () => {
await toolbar.openFile('untitled.kcl', { wait: false }) await toolbar.openFile(fileName, { wait: false })
// check we're out of sketch mode // check we're out of sketch mode
await expect(toolbar.exitSketchBtn).not.toBeVisible() await expect(toolbar.exitSketchBtn).not.toBeVisible()
@ -109,7 +107,7 @@ test.describe('when using the file tree to', () => {
const fromFile = 'main.kcl' const fromFile = 'main.kcl'
const toFile = 'hello.kcl' const toFile = 'hello.kcl'
test( test.fixme(
`rename ${fromFile} to ${toFile}, and doesn't crash on reload and settings load`, `rename ${fromFile} to ${toFile}, and doesn't crash on reload and settings load`,
{ tag: '@electron' }, { tag: '@electron' },
async ({ browser: _, tronApp }, testInfo) => { async ({ browser: _, tronApp }, testInfo) => {
@ -157,7 +155,7 @@ test.describe('when using the file tree to', () => {
} }
) )
test( test.fixme(
`create many new untitled files they increment their names`, `create many new untitled files they increment their names`,
{ tag: '@electron' }, { tag: '@electron' },
async ({ browser: _, tronApp }, testInfo) => { async ({ browser: _, tronApp }, testInfo) => {
@ -298,7 +296,7 @@ test.describe('when using the file tree to', () => {
} }
) )
test( test.fixme(
'loading small file, then large, then back to small', 'loading small file, then large, then back to small',
{ {
tag: '@electron', tag: '@electron',

View File

@ -195,7 +195,7 @@ export class SceneFixture {
} }
waitForExecutionDone = async () => { waitForExecutionDone = async () => {
await expect(this.exeIndicator).toBeVisible() await expect(this.exeIndicator).toBeVisible({ timeout: 30000 })
} }
expectPixelColor = async ( expectPixelColor = async (

View File

@ -16,6 +16,7 @@ export class ToolbarFixture {
fileCreateToast!: Locator fileCreateToast!: Locator
filePane!: Locator filePane!: Locator
exeIndicator!: Locator exeIndicator!: Locator
treeInputField!: Locator
constructor(page: Page) { constructor(page: Page) {
this.page = page this.page = page
@ -31,6 +32,7 @@ export class ToolbarFixture {
this.editSketchBtn = page.getByText('Edit Sketch') this.editSketchBtn = page.getByText('Edit Sketch')
this.fileTreeBtn = page.locator('[id="files-button-holder"]') this.fileTreeBtn = page.locator('[id="files-button-holder"]')
this.createFileBtn = page.getByTestId('create-file-button') this.createFileBtn = page.getByTestId('create-file-button')
this.treeInputField = page.getByTestId('tree-input-field')
this.filePane = page.locator('#files-pane') this.filePane = page.locator('#files-pane')
this.fileCreateToast = page.getByText('Successfully created') this.fileCreateToast = page.getByText('Successfully created')
@ -59,10 +61,15 @@ export class ToolbarFixture {
expectFileTreeState = async (expected: string[]) => { expectFileTreeState = async (expected: string[]) => {
await expect.poll(this._serialiseFileTree).toEqual(expected) await expect.poll(this._serialiseFileTree).toEqual(expected)
} }
createFile = async ({ wait }: { wait: boolean } = { wait: false }) => { createFile = async (args: {
fileName: string
waitForToastToDisappear: boolean
}) => {
await this.createFileBtn.click() await this.createFileBtn.click()
await this.treeInputField.fill(args.fileName)
await this.treeInputField.press('Enter')
await expect(this.fileCreateToast).toBeVisible() await expect(this.fileCreateToast).toBeVisible()
if (wait) { if (args.waitForToastToDisappear) {
await this.fileCreateToast.waitFor({ state: 'detached' }) await this.fileCreateToast.waitFor({ state: 'detached' })
} }
} }

View File

@ -18,7 +18,7 @@ export const isErrorWhitelisted = (exception: Error) => {
{ {
name: '"{"kind"', name: '"{"kind"',
message: message:
'"engine","sourceRanges":[[0,0]],"msg":"Failed to get string from response from engine: `JsValue(undefined)`"}"', '"engine","sourceRanges":[[0,0,0]],"msg":"Failed to get string from response from engine: `JsValue(undefined)`"}"',
stack: '', stack: '',
foundInSpec: 'e2e/playwright/testing-settings.spec.ts', foundInSpec: 'e2e/playwright/testing-settings.spec.ts',
project: 'Google Chrome', project: 'Google Chrome',
@ -156,8 +156,8 @@ export const isErrorWhitelisted = (exception: Error) => {
{ {
name: 'Unhandled Promise Rejection', name: 'Unhandled Promise Rejection',
message: message:
'{"kind":"engine","sourceRanges":[[0,0]],"msg":"Failed to get string from response from engine: `JsValue(undefined)`"}', '{"kind":"engine","sourceRanges":[[0,0,0]],"msg":"Failed to get string from response from engine: `JsValue(undefined)`"}',
stack: `Unhandled Promise Rejection: {"kind":"engine","sourceRanges":[[0,0]],"msg":"Failed to get string from response from engine: \`JsValue(undefined)\`"} stack: `Unhandled Promise Rejection: {"kind":"engine","sourceRanges":[[0,0,0]],"msg":"Failed to get string from response from engine: \`JsValue(undefined)\`"}
at unknown (http://localhost:3000/src/lang/std/engineConnection.ts:1245:26)`, at unknown (http://localhost:3000/src/lang/std/engineConnection.ts:1245:26)`,
foundInSpec: foundInSpec:
'e2e/playwright/onboarding-tests.spec.ts Click through each onboarding step', 'e2e/playwright/onboarding-tests.spec.ts Click through each onboarding step',
@ -253,7 +253,7 @@ export const isErrorWhitelisted = (exception: Error) => {
{ {
name: '{"kind"', name: '{"kind"',
stack: ``, stack: ``,
message: `engine","sourceRanges":[[0,0]],"msg":"Failed to wait for promise from engine: JsValue(\\"Force interrupt, executionIsStale, new AST requested\\")"}`, message: `engine","sourceRanges":[[0,0,0]],"msg":"Failed to wait for promise from engine: JsValue(\\"Force interrupt, executionIsStale, new AST requested\\")"}`,
project: 'Google Chrome', project: 'Google Chrome',
foundInSpec: 'e2e/playwright/testing-settings.spec.ts', foundInSpec: 'e2e/playwright/testing-settings.spec.ts',
}, },

View File

@ -854,7 +854,7 @@ test(
} }
) )
test( test.fixme(
'Deleting projects, can delete individual project, can still create projects after deleting all', 'Deleting projects, can delete individual project, can still create projects after deleting all',
{ tag: '@electron' }, { tag: '@electron' },
async ({ browserName }, testInfo) => { async ({ browserName }, testInfo) => {
@ -1490,7 +1490,6 @@ test(
'function_sketch.kcl', 'function_sketch.kcl',
'function_sketch_with_position.kcl', 'function_sketch_with_position.kcl',
'global-tags.kcl', 'global-tags.kcl',
'helix_ccw.kcl',
'helix_defaults.kcl', 'helix_defaults.kcl',
'helix_defaults_negative_extrude.kcl', 'helix_defaults_negative_extrude.kcl',
'helix_with_length.kcl', 'helix_with_length.kcl',
@ -1670,7 +1669,8 @@ test(
} }
) )
test( // Flaky
test.fixme(
'Original project name persist after onboarding', 'Original project name persist after onboarding',
{ tag: '@electron' }, { tag: '@electron' },
async ({ browserName }, testInfo) => { async ({ browserName }, testInfo) => {

View File

@ -1031,7 +1031,7 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
}) })
}) })
test('theme persists', async ({ page, context }) => { test.fixme('theme persists', async ({ page, context }) => {
const u = await getUtils(page) const u = await getUtils(page)
await context.addInitScript(async () => { await context.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -318,6 +318,7 @@ test.describe('Testing settings', () => {
timeout: 5_000, timeout: 5_000,
}) })
.toContain(`themeColor = "${userThemeColor}"`) .toContain(`themeColor = "${userThemeColor}"`)
// Only close the button after we've confirmed
}) })
await test.step('Set project theme color', async () => { await test.step('Set project theme color', async () => {
@ -344,14 +345,13 @@ test.describe('Testing settings', () => {
await test.step('Refresh the application and see project setting applied', async () => { await test.step('Refresh the application and see project setting applied', async () => {
// Make sure we're done navigating before we reload // Make sure we're done navigating before we reload
await expect(settingsCloseButton).not.toBeVisible() await expect(settingsCloseButton).not.toBeVisible()
await page.reload({ waitUntil: 'domcontentloaded' })
await page.reload({ waitUntil: 'domcontentloaded' })
await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor) await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor)
}) })
await test.step(`Navigate back to the home view and see user setting applied`, async () => { await test.step(`Navigate back to the home view and see user setting applied`, async () => {
await logoLink.click() await logoLink.click()
await page.screenshot({ path: 'out.png' })
await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor) await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor)
}) })
@ -415,7 +415,7 @@ test.describe('Testing settings', () => {
) )
// It was much easier to test the logo color than the background stream color. // It was much easier to test the logo color than the background stream color.
test( test.fixme(
'user settings reload on external change, on project and modeling view', 'user settings reload on external change, on project and modeling view',
{ tag: '@electron' }, { tag: '@electron' },
async ({ browserName }, testInfo) => { async ({ browserName }, testInfo) => {

View File

@ -256,181 +256,186 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
).not.toBeVisible() ).not.toBeVisible()
}) })
test('Basic default modeling and sketch hotkeys work', async ({ page }) => { test.fixme(
const u = await getUtils(page) 'Basic default modeling and sketch hotkeys work',
async ({ page }) => {
const u = await getUtils(page)
// This test can run long if it takes a little too long to load // This test can run long if it takes a little too long to load
// the engine. // the engine.
test.setTimeout(90000) test.setTimeout(90000)
// This test has a weird bug on ubuntu // This test has a weird bug on ubuntu
test.skip( // Funny, it's flaking on Windows too :). I think there is just something
process.platform === 'linux', // actually wrong.
'weird playwright bug on ubuntu https://github.com/KittyCAD/modeling-app/issues/2444' test.skip(
) process.platform === 'linux',
// Load the app with the code pane open 'weird playwright bug on ubuntu https://github.com/KittyCAD/modeling-app/issues/2444'
)
// Load the app with the code pane open
await test.step(`Set up test`, async () => { await test.step(`Set up test`, async () => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'store', 'store',
JSON.stringify({ JSON.stringify({
state: { state: {
openPanes: ['code'], openPanes: ['code'],
}, },
version: 0, version: 0,
})
)
})
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 codePane = page.locator('.cm-content')
const lineButton = page.getByRole('button', {
name: 'line Line',
exact: true,
})
const arcButton = page.getByRole('button', {
name: 'arc Tangential Arc',
exact: true,
})
const extrudeButton = page.getByRole('button', { name: 'Extrude' })
const commandBarComboBox = page.getByPlaceholder('Search commands')
const exitSketchButton = page.getByRole('button', { name: 'Exit Sketch' })
await test.step(`Type code with modeling hotkeys, shouldn't fire`, async () => {
await codePane.click()
await page.keyboard.type('//')
await page.keyboard.press('s')
await expect(commandBarComboBox).not.toBeVisible()
await page.keyboard.press('e')
await expect(commandBarComboBox).not.toBeVisible()
await expect(codePane).toHaveText('//se')
})
// Blur focus from the code editor, use the s command to sketch
await test.step(`Blur editor focus, enter sketch`, async () => {
/**
* TODO: There is a bug somewhere that causes this test to fail
* if you toggle the codePane closed before your trigger the
* start of the sketch.
* and a separate Safari-only bug that causes the test to fail
* if the pane is open the entire test. The maintainer of CodeMirror
* has pinpointed this to the unusual browser behavior:
* https://discuss.codemirror.net/t/how-to-force-unfocus-of-the-codemirror-element-in-safari/8095/3
*/
await blurCodeEditor()
await page.waitForTimeout(1000)
await page.keyboard.press('s')
await page.waitForTimeout(1000)
await page.mouse.move(800, 300, { steps: 5 })
await page.mouse.click(800, 300)
await page.waitForTimeout(1000)
await expect(lineButton).toHaveAttribute('aria-pressed', 'true', {
timeout: 15_000,
})
})
// Use some sketch hotkeys to create a sketch (l and a for now)
await test.step(`Incomplete sketch with hotkeys`, async () => {
await test.step(`Draw a line`, async () => {
await page.mouse.move(700, 200, { steps: 5 })
await page.mouse.click(700, 200)
await page.mouse.move(800, 250, { steps: 5 })
await page.mouse.click(800, 250)
})
await test.step(`Unequip line tool`, async () => {
await page.keyboard.press('l')
await expect(lineButton).not.toHaveAttribute('aria-pressed', 'true')
})
await test.step(`Draw a tangential arc`, async () => {
await page.keyboard.press('a')
await expect(arcButton).toHaveAttribute('aria-pressed', 'true', {
timeout: 10_000,
}) })
) await page.mouse.move(1000, 100, { steps: 5 })
await page.mouse.click(1000, 100)
})
await test.step(`Unequip with escape, equip line tool`, async () => {
await page.keyboard.press('Escape')
await page.keyboard.press('l')
await page.waitForTimeout(50)
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
})
}) })
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 codePane = page.locator('.cm-content') await test.step(`Type code with sketch hotkeys, shouldn't fire`, async () => {
const lineButton = page.getByRole('button', { // Since there's code now, we have to get to the end of the line
name: 'line Line', await page.locator('.cm-line').last().click()
exact: true, await page.keyboard.down('ControlOrMeta')
}) await page.keyboard.press('ArrowRight')
const arcButton = page.getByRole('button', { await page.keyboard.up('ControlOrMeta')
name: 'arc Tangential Arc',
exact: true,
})
const extrudeButton = page.getByRole('button', { name: 'Extrude' })
const commandBarComboBox = page.getByPlaceholder('Search commands')
const exitSketchButton = page.getByRole('button', { name: 'Exit Sketch' })
await test.step(`Type code with modeling hotkeys, shouldn't fire`, async () => { await page.keyboard.press('Enter')
await codePane.click() await page.keyboard.type('//')
await page.keyboard.type('//') await page.keyboard.press('l')
await page.keyboard.press('s') await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
await expect(commandBarComboBox).not.toBeVisible() await page.keyboard.press('a')
await page.keyboard.press('e') await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
await expect(commandBarComboBox).not.toBeVisible() await expect(codePane).toContainText('//la')
await expect(codePane).toHaveText('//se') await page.keyboard.press('Backspace')
}) await page.keyboard.press('Backspace')
await page.keyboard.press('Backspace')
// Blur focus from the code editor, use the s command to sketch await page.keyboard.press('Backspace')
await test.step(`Blur editor focus, enter sketch`, async () => {
/**
* TODO: There is a bug somewhere that causes this test to fail
* if you toggle the codePane closed before your trigger the
* start of the sketch.
* and a separate Safari-only bug that causes the test to fail
* if the pane is open the entire test. The maintainer of CodeMirror
* has pinpointed this to the unusual browser behavior:
* https://discuss.codemirror.net/t/how-to-force-unfocus-of-the-codemirror-element-in-safari/8095/3
*/
await blurCodeEditor()
await page.waitForTimeout(1000)
await page.keyboard.press('s')
await page.waitForTimeout(1000)
await page.mouse.move(800, 300, { steps: 5 })
await page.mouse.click(800, 300)
await page.waitForTimeout(1000)
await expect(lineButton).toHaveAttribute('aria-pressed', 'true', {
timeout: 15_000,
}) })
})
// Use some sketch hotkeys to create a sketch (l and a for now) await test.step(`Close profile and exit sketch`, async () => {
await test.step(`Incomplete sketch with hotkeys`, async () => { await blurCodeEditor()
await test.step(`Draw a line`, async () => {
await page.mouse.move(700, 200, { steps: 5 }) await page.mouse.move(700, 200, { steps: 5 })
await page.mouse.click(700, 200) await page.mouse.click(700, 200)
await page.mouse.move(800, 250, { steps: 5 }) // On close it will unequip the line tool.
await page.mouse.click(800, 250) await expect(lineButton).toHaveAttribute('aria-pressed', 'false')
}) await expect(exitSketchButton).toBeEnabled()
await test.step(`Unequip line tool`, async () => {
await page.keyboard.press('l')
await expect(lineButton).not.toHaveAttribute('aria-pressed', 'true')
})
await test.step(`Draw a tangential arc`, async () => {
await page.keyboard.press('a')
await expect(arcButton).toHaveAttribute('aria-pressed', 'true', {
timeout: 10_000,
})
await page.mouse.move(1000, 100, { steps: 5 })
await page.mouse.click(1000, 100)
})
await test.step(`Unequip with escape, equip line tool`, async () => {
await page.keyboard.press('Escape') await page.keyboard.press('Escape')
await page.keyboard.press('l') await expect(
await page.waitForTimeout(50) page.getByRole('button', { name: 'Exit Sketch' })
await expect(lineButton).toHaveAttribute('aria-pressed', 'true') ).not.toBeVisible()
}) })
})
await test.step(`Type code with sketch hotkeys, shouldn't fire`, async () => { // Extrude with e
// Since there's code now, we have to get to the end of the line await test.step(`Extrude the sketch`, async () => {
await page.locator('.cm-line').last().click() await page.mouse.click(750, 150)
await page.keyboard.down('ControlOrMeta') await blurCodeEditor()
await page.keyboard.press('ArrowRight') await expect(extrudeButton).toBeEnabled()
await page.keyboard.up('ControlOrMeta') await page.keyboard.press('e')
await page.waitForTimeout(500)
await page.keyboard.press('Enter') await page.mouse.move(800, 200, { steps: 5 })
await page.keyboard.type('//') await page.mouse.click(800, 200)
await page.keyboard.press('l') await expect(page.getByRole('button', { name: 'Continue' })).toBeVisible({
await expect(lineButton).toHaveAttribute('aria-pressed', 'true') timeout: 20_000,
await page.keyboard.press('a') })
await expect(lineButton).toHaveAttribute('aria-pressed', 'true') await page.getByRole('button', { name: 'Continue' }).click()
await expect(codePane).toContainText('//la') await expect(
await page.keyboard.press('Backspace') page.getByRole('button', { name: 'Submit command' })
await page.keyboard.press('Backspace') ).toBeVisible()
await page.keyboard.press('Backspace') await page.getByRole('button', { name: 'Submit command' }).click()
await page.keyboard.press('Backspace') await expect(page.locator('.cm-content')).toContainText('extrude(')
})
await test.step(`Close profile and exit sketch`, async () => {
await blurCodeEditor()
await page.mouse.move(700, 200, { steps: 5 })
await page.mouse.click(700, 200)
// On close it will unequip the line tool.
await expect(lineButton).toHaveAttribute('aria-pressed', 'false')
await expect(exitSketchButton).toBeEnabled()
await page.keyboard.press('Escape')
await expect(
page.getByRole('button', { name: 'Exit Sketch' })
).not.toBeVisible()
})
// Extrude with e
await test.step(`Extrude the sketch`, async () => {
await page.mouse.click(750, 150)
await blurCodeEditor()
await expect(extrudeButton).toBeEnabled()
await page.keyboard.press('e')
await page.waitForTimeout(500)
await page.mouse.move(800, 200, { steps: 5 })
await page.mouse.click(800, 200)
await expect(page.getByRole('button', { name: 'Continue' })).toBeVisible({
timeout: 20_000,
}) })
await page.getByRole('button', { name: 'Continue' }).click()
await expect(
page.getByRole('button', { name: 'Submit command' })
).toBeVisible()
await page.getByRole('button', { name: 'Submit command' }).click()
await expect(page.locator('.cm-content')).toContainText('extrude(')
})
// await codePaneButton.click() // await codePaneButton.click()
// await expect(u.codeLocator).not.toBeVisible() // await expect(u.codeLocator).not.toBeVisible()
/** /**
* work-around: to stop `keyboard.press()` from typing in the editor even when it should be blurred * work-around: to stop `keyboard.press()` from typing in the editor even when it should be blurred
*/ */
async function blurCodeEditor() { async function blurCodeEditor() {
await page.getByRole('button', { name: 'Commands' }).click() await page.getByRole('button', { name: 'Commands' }).click()
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.keyboard.press('Escape') await page.keyboard.press('Escape')
await page.waitForTimeout(100) await page.waitForTimeout(100)
}
} }
}) )
test('Delete key does not navigate back', async ({ page }) => { test('Delete key does not navigate back', async ({ page }) => {
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })

View File

@ -1,6 +1,6 @@
{ {
"name": "zoo-modeling-app", "name": "zoo-modeling-app",
"version": "0.26.2", "version": "0.26.3",
"private": true, "private": true,
"productName": "Zoo Modeling App", "productName": "Zoo Modeling App",
"author": { "author": {
@ -14,7 +14,7 @@
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.17.0", "@codemirror/autocomplete": "^6.17.0",
"@codemirror/commands": "^6.6.0", "@codemirror/commands": "^6.6.0",
"@codemirror/language": "^6.10.2", "@codemirror/language": "^6.10.3",
"@codemirror/lint": "^6.8.1", "@codemirror/lint": "^6.8.1",
"@codemirror/search": "^6.5.6", "@codemirror/search": "^6.5.6",
"@codemirror/state": "^6.4.1", "@codemirror/state": "^6.4.1",
@ -40,7 +40,7 @@
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"decamelize": "^6.0.0", "decamelize": "^6.0.0",
"electron-squirrel-startup": "^1.0.1", "electron-squirrel-startup": "^1.0.1",
"electron-updater": "^6.3.0", "electron-updater": "^6.3.9",
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"html2canvas-pro": "^1.5.8", "html2canvas-pro": "^1.5.8",
"isomorphic-fetch": "^3.0.0", "isomorphic-fetch": "^3.0.0",
@ -60,7 +60,7 @@
"sketch-helpers": "^0.0.4", "sketch-helpers": "^0.0.4",
"three": "^0.166.1", "three": "^0.166.1",
"ua-parser-js": "^1.0.37", "ua-parser-js": "^1.0.37",
"uuid": "^9.0.1", "uuid": "^11.0.2",
"vscode-jsonrpc": "^8.2.1", "vscode-jsonrpc": "^8.2.1",
"vscode-languageserver-protocol": "^3.17.5", "vscode-languageserver-protocol": "^3.17.5",
"vscode-uri": "^3.0.8", "vscode-uri": "^3.0.8",
@ -105,7 +105,8 @@
"tronb:package": "electron-builder --config electron-builder.yml", "tronb:package": "electron-builder --config electron-builder.yml",
"test-setup": "yarn install && yarn build:wasm", "test-setup": "yarn install && yarn build:wasm",
"test": "vitest --mode development", "test": "vitest --mode development",
"test:unit": "vitest run --mode development", "test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts",
"test:unit:kcl-samples": "vitest run --mode development ./src/lang/kclSamples.test.ts",
"test:playwright:browser:chrome": "playwright test --project='Google Chrome' --config=playwright.ci.config.ts --grep-invert='@snapshot|@electron'", "test:playwright:browser:chrome": "playwright test --project='Google Chrome' --config=playwright.ci.config.ts --grep-invert='@snapshot|@electron'",
"test:playwright:browser:chrome:windows": "playwright test --project=\"Google Chrome\" --config=playwright.ci.config.ts --grep-invert=\"@snapshot|@electron|@skipWin\"", "test:playwright:browser:chrome:windows": "playwright test --project=\"Google Chrome\" --config=playwright.ci.config.ts --grep-invert=\"@snapshot|@electron|@skipWin\"",
"test:playwright:browser:chrome:ubuntu": "playwright test --project='Google Chrome' --config=playwright.ci.config.ts --grep-invert='@snapshot|@electron|@skipLinux'", "test:playwright:browser:chrome:ubuntu": "playwright test --project='Google Chrome' --config=playwright.ci.config.ts --grep-invert='@snapshot|@electron|@skipLinux'",
@ -117,7 +118,8 @@
"test:playwright:electron:windows:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipWin", "test:playwright:electron:windows:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipWin",
"test:playwright:electron:macos:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipMacos", "test:playwright:electron:macos:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipMacos",
"test:playwright:electron:ubuntu:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipLinux", "test:playwright:electron:ubuntu:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipLinux",
"test:unit:local": "yarn simpleserver:bg && yarn test:unit; kill-port 3000" "test:unit:local": "yarn simpleserver:bg && yarn test:unit; kill-port 3000",
"test:unit:kcl-samples:local": "yarn simpleserver:bg && yarn test:unit:kcl-samples; kill-port 3000"
}, },
"prettier": { "prettier": {
"trailingComma": "es5", "trailingComma": "es5",
@ -144,8 +146,8 @@
"@electron-forge/maker-deb": "^7.4.0", "@electron-forge/maker-deb": "^7.4.0",
"@electron-forge/maker-rpm": "^7.4.0", "@electron-forge/maker-rpm": "^7.4.0",
"@electron-forge/maker-squirrel": "^7.4.0", "@electron-forge/maker-squirrel": "^7.4.0",
"@electron-forge/maker-wix": "^7.4.0", "@electron-forge/maker-wix": "^7.5.0",
"@electron-forge/maker-zip": "^7.4.0", "@electron-forge/maker-zip": "^7.5.0",
"@electron-forge/plugin-auto-unpack-natives": "^7.4.0", "@electron-forge/plugin-auto-unpack-natives": "^7.4.0",
"@electron-forge/plugin-fuses": "^7.4.0", "@electron-forge/plugin-fuses": "^7.4.0",
"@electron-forge/plugin-vite": "^7.4.0", "@electron-forge/plugin-vite": "^7.4.0",
@ -171,7 +173,7 @@
"@types/ua-parser-js": "^0.7.39", "@types/ua-parser-js": "^0.7.39",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"@types/wicg-file-system-access": "^2023.10.5", "@types/wicg-file-system-access": "^2023.10.5",
"@types/ws": "^8.5.10", "@types/ws": "^8.5.13",
"@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0", "@typescript-eslint/parser": "^5.0.0",
"@vitejs/plugin-react": "^4.3.0", "@vitejs/plugin-react": "^4.3.0",
@ -187,7 +189,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": "^14.3.10", "happy-dom": "^15.10.2",
"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",

View File

@ -6,10 +6,10 @@ import { Dispatch, useCallback, useRef, useState } from 'react'
import { useNavigate, useRouteLoaderData } from 'react-router-dom' import { useNavigate, useRouteLoaderData } from 'react-router-dom'
import { Disclosure } from '@headlessui/react' import { Disclosure } from '@headlessui/react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faChevronRight } from '@fortawesome/free-solid-svg-icons' import { faChevronRight, faPencil } from '@fortawesome/free-solid-svg-icons'
import { useFileContext } from 'hooks/useFileContext' import { useFileContext } from 'hooks/useFileContext'
import styles from './FileTree.module.css' import styles from './FileTree.module.css'
import { sortProject } from 'lib/desktopFS' import { sortFilesAndDirectories } from 'lib/desktopFS'
import { FILE_EXT } from 'lib/constants' import { FILE_EXT } from 'lib/constants'
import { CustomIcon } from './CustomIcon' import { CustomIcon } from './CustomIcon'
import { codeManager, kclManager } from 'lib/singletons' import { codeManager, kclManager } from 'lib/singletons'
@ -27,6 +27,36 @@ function getIndentationCSS(level: number) {
return `calc(1rem * ${level + 1})` return `calc(1rem * ${level + 1})`
} }
function TreeEntryInput(props: {
level: number
onSubmit: (value: string) => void
}) {
const [value, setValue] = useState('')
const onKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key !== 'Enter') return
props.onSubmit(value)
}
return (
<label>
<span className="sr-only">Entry input</span>
<input
data-testid="tree-input-field"
type="text"
autoFocus
autoCapitalize="off"
autoCorrect="off"
className="w-full py-1 bg-transparent text-chalkboard-100 placeholder:text-chalkboard-70 dark:text-chalkboard-10 dark:placeholder:text-chalkboard-50 focus:outline-none focus:ring-0"
onBlur={() => props.onSubmit(value)}
onChange={(e) => setValue(e.target.value)}
onKeyPress={onKeyPress}
style={{ paddingInlineStart: getIndentationCSS(props.level) }}
value={value}
/>
</label>
)
}
function RenameForm({ function RenameForm({
fileOrDir, fileOrDir,
onSubmit, onSubmit,
@ -113,23 +143,44 @@ function DeleteFileTreeItemDialog({
} }
const FileTreeItem = ({ const FileTreeItem = ({
parentDir,
project, project,
currentFile, currentFile,
lastDirectoryClicked,
fileOrDir, fileOrDir,
onNavigateToFile, onNavigateToFile,
onClickDirectory,
onCreateFile,
onCreateFolder,
newTreeEntry,
level = 0, level = 0,
treeSelection,
setTreeSelection,
}: { }: {
parentDir: FileEntry | undefined
project?: IndexLoaderData['project'] project?: IndexLoaderData['project']
currentFile?: IndexLoaderData['file'] currentFile?: IndexLoaderData['file']
lastDirectoryClicked?: FileEntry
fileOrDir: FileEntry fileOrDir: FileEntry
onNavigateToFile?: () => void onNavigateToFile?: () => void
onClickDirectory: (
open: boolean,
path: FileEntry,
parentDir: FileEntry | undefined
) => void
onCreateFile: (name: string) => void
onCreateFolder: (name: string) => void
newTreeEntry: TreeEntry
level?: number level?: number
treeSelection: FileEntry | undefined
setTreeSelection: Dispatch<React.SetStateAction<FileEntry | undefined>>
}) => { }) => {
const { send: fileSend, context: fileContext } = useFileContext() const { send: fileSend, context: fileContext } = useFileContext()
const { onFileOpen, onFileClose } = useLspContext() const { onFileOpen, onFileClose } = useLspContext()
const navigate = useNavigate() const navigate = useNavigate()
const [isConfirmingDelete, setIsConfirmingDelete] = useState(false) const [isConfirmingDelete, setIsConfirmingDelete] = useState(false)
const isCurrentFile = fileOrDir.path === currentFile?.path const isCurrentFile = fileOrDir.path === currentFile?.path
const isFileOrDirHighlighted = treeSelection?.path === fileOrDir?.path
const itemRef = useRef(null) const itemRef = useRef(null)
// Since every file or directory gets its own FileTreeItem, we can do this. // Since every file or directory gets its own FileTreeItem, we can do this.
@ -138,15 +189,15 @@ const FileTreeItem = ({
// the ReactNodes are destroyed, so is this listener :) // the ReactNodes are destroyed, so is this listener :)
useFileSystemWatcher( useFileSystemWatcher(
async (eventType, path) => { async (eventType, path) => {
// Prevents a cyclic read / write causing editor problems such as
// misplaced cursor positions.
if (codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher) {
codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher = false
return
}
// Don't try to read a file that was removed. // Don't try to read a file that was removed.
if (isCurrentFile && eventType !== 'unlink') { if (isCurrentFile && eventType !== 'unlink') {
// Prevents a cyclic read / write causing editor problems such as
// misplaced cursor positions.
if (codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher) {
codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher = false
return
}
let code = await window.electron.readFile(path, { encoding: 'utf-8' }) let code = await window.electron.readFile(path, { encoding: 'utf-8' })
code = normalizeLineEndings(code) code = normalizeLineEndings(code)
codeManager.updateCodeStateEditor(code) codeManager.updateCodeStateEditor(code)
@ -156,6 +207,10 @@ const FileTreeItem = ({
[fileOrDir.path] [fileOrDir.path]
) )
const showNewTreeEntry =
newTreeEntry !== undefined &&
fileOrDir.path === fileContext.selectedDirectory.path
const isRenaming = fileContext.itemsBeingRenamed.includes(fileOrDir.path) const isRenaming = fileContext.itemsBeingRenamed.includes(fileOrDir.path)
const removeCurrentItemFromRenaming = useCallback( const removeCurrentItemFromRenaming = useCallback(
() => () =>
@ -179,13 +234,6 @@ const FileTreeItem = ({
}) })
}, [fileContext.itemsBeingRenamed, fileOrDir.path, fileSend]) }, [fileContext.itemsBeingRenamed, fileOrDir.path, fileSend])
const clickDirectory = () => {
fileSend({
type: 'Set selected directory',
directory: fileOrDir,
})
}
function handleKeyUp(e: React.KeyboardEvent<HTMLButtonElement>) { function handleKeyUp(e: React.KeyboardEvent<HTMLButtonElement>) {
if (e.metaKey && e.key === 'Backspace') { if (e.metaKey && e.key === 'Backspace') {
// Open confirmation dialog // Open confirmation dialog
@ -194,11 +242,13 @@ const FileTreeItem = ({
// Show the renaming form // Show the renaming form
addCurrentItemToRenaming() addCurrentItemToRenaming()
} else if (e.code === 'Space') { } else if (e.code === 'Space') {
handleClick() void handleClick()
} }
} }
function handleClick() { async function handleClick() {
setTreeSelection(fileOrDir)
if (fileOrDir.children !== null) return // Don't open directories if (fileOrDir.children !== null) return // Don't open directories
if (fileOrDir.name?.endsWith(FILE_EXT) === false && project?.path) { if (fileOrDir.name?.endsWith(FILE_EXT) === false && project?.path) {
@ -208,12 +258,10 @@ const FileTreeItem = ({
`import("${fileOrDir.path.replace(project.path, '.')}")\n` + `import("${fileOrDir.path.replace(project.path, '.')}")\n` +
codeManager.code codeManager.code
) )
// eslint-disable-next-line @typescript-eslint/no-floating-promises await codeManager.writeToFile()
codeManager.writeToFile()
// Prevent seeing the model built one piece at a time when changing files // Prevent seeing the model built one piece at a time when changing files
// eslint-disable-next-line @typescript-eslint/no-floating-promises await kclManager.executeCode(true)
kclManager.executeCode(true)
} else { } else {
// 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)
@ -222,16 +270,19 @@ const FileTreeItem = ({
// Open kcl files // Open kcl files
navigate(`${PATHS.FILE}/${encodeURIComponent(fileOrDir.path)}`) navigate(`${PATHS.FILE}/${encodeURIComponent(fileOrDir.path)}`)
} }
onNavigateToFile?.() onNavigateToFile?.()
} }
// The below handles both the "root" of all directories and all subs. It's
// why some code is duplicated.
return ( return (
<div className="contents" data-testid="file-tree-item" ref={itemRef}> <div className="contents" data-testid="file-tree-item" ref={itemRef}>
{fileOrDir.children === null ? ( {fileOrDir.children === null ? (
<li <li
className={ className={
'group m-0 p-0 border-solid border-0 hover:bg-primary/5 focus-within:bg-primary/5 dark:hover:bg-primary/20 dark:focus-within:bg-primary/20 ' + 'group m-0 p-0 border-solid border-0 hover:bg-primary/5 focus-within:bg-primary/5 dark:hover:bg-primary/20 dark:focus-within:bg-primary/20 ' +
(isCurrentFile (isFileOrDirHighlighted || isCurrentFile
? '!bg-primary/10 !text-primary dark:!bg-primary/20 dark:!text-inherit' ? '!bg-primary/10 !text-primary dark:!bg-primary/20 dark:!text-inherit'
: '') : '')
} }
@ -242,7 +293,7 @@ const FileTreeItem = ({
style={{ paddingInlineStart: getIndentationCSS(level) }} style={{ paddingInlineStart: getIndentationCSS(level) }}
onClick={(e) => { onClick={(e) => {
e.currentTarget.focus() e.currentTarget.focus()
handleClick() void handleClick()
}} }}
onKeyUp={handleKeyUp} onKeyUp={handleKeyUp}
> >
@ -268,14 +319,13 @@ const FileTreeItem = ({
<Disclosure.Button <Disclosure.Button
className={ className={
' group border-none text-sm rounded-none p-0 m-0 flex items-center justify-start w-full py-0.5 hover:text-primary hover:bg-primary/5 dark:hover:text-inherit dark:hover:bg-primary/10' + ' group border-none text-sm rounded-none p-0 m-0 flex items-center justify-start w-full py-0.5 hover:text-primary hover:bg-primary/5 dark:hover:text-inherit dark:hover:bg-primary/10' +
(fileContext.selectedDirectory.path.includes(fileOrDir.path) (isFileOrDirHighlighted ? ' ui-open:bg-primary/10' : '')
? ' ui-open:bg-primary/10'
: '')
} }
style={{ paddingInlineStart: getIndentationCSS(level) }} style={{ paddingInlineStart: getIndentationCSS(level) }}
onClick={(e) => e.currentTarget.focus()} onClick={(e) => {
onClickCapture={clickDirectory} e.stopPropagation()
onFocusCapture={clickDirectory} onClickDirectory(open, fileOrDir, parentDir)
}}
onKeyDown={(e) => e.key === 'Enter' && e.preventDefault()} onKeyDown={(e) => e.key === 'Enter' && e.preventDefault()}
onKeyUp={handleKeyUp} onKeyUp={handleKeyUp}
> >
@ -317,35 +367,69 @@ const FileTreeItem = ({
> >
<ul <ul
className="m-0 p-0" className="m-0 p-0"
onClickCapture={(e) => { onClick={(e) => {
fileSend({ e.stopPropagation()
type: 'Set selected directory', onClickDirectory(open, fileOrDir, parentDir)
directory: fileOrDir,
})
}} }}
onFocusCapture={(e) =>
fileSend({
type: 'Set selected directory',
directory: fileOrDir,
})
}
> >
{fileOrDir.children?.map((child) => ( {showNewTreeEntry && (
<FileTreeItem <div
fileOrDir={child} className="flex items-center"
project={project} style={{
currentFile={currentFile} paddingInlineStart: getIndentationCSS(level + 1),
onNavigateToFile={onNavigateToFile} }}
level={level + 1} >
key={level + '-' + child.path} <FontAwesomeIcon
/> icon={faPencil}
))} className="inline-block mr-2 m-0 p-0 w-2 h-2"
/>
<TreeEntryInput
level={-1}
onSubmit={(value: string) =>
newTreeEntry === 'file'
? onCreateFile(value)
: onCreateFolder(value)
}
/>
</div>
)}
{sortFilesAndDirectories(fileOrDir.children || []).map(
(child) => (
<FileTreeItem
parentDir={fileOrDir}
fileOrDir={child}
project={project}
currentFile={currentFile}
onCreateFile={onCreateFile}
onCreateFolder={onCreateFolder}
newTreeEntry={newTreeEntry}
lastDirectoryClicked={lastDirectoryClicked}
onClickDirectory={onClickDirectory}
onNavigateToFile={onNavigateToFile}
level={level + 1}
key={level + '-' + child.path}
treeSelection={treeSelection}
setTreeSelection={setTreeSelection}
/>
)
)}
{!showNewTreeEntry && fileOrDir.children?.length === 0 && (
<div
className="flex items-center text-chalkboard-50"
style={{
paddingInlineStart: getIndentationCSS(level + 1),
}}
>
<div>No files</div>
</div>
)}
</ul> </ul>
</Disclosure.Panel> </Disclosure.Panel>
</div> </div>
)} )}
</Disclosure> </Disclosure>
)} )}
{isConfirmingDelete && ( {isConfirmingDelete && (
<DeleteFileTreeItemDialog <DeleteFileTreeItemDialog
fileOrDir={fileOrDir} fileOrDir={fileOrDir}
@ -409,27 +493,15 @@ interface FileTreeProps {
) => void ) => void
} }
export const FileTreeMenu = () => { export const FileTreeMenu = ({
const { send } = useFileContext() onCreateFile,
const { send: modelingSend } = useModelingContext() onCreateFolder,
}: {
function createFile() { onCreateFile: () => void
send({ onCreateFolder: () => void
type: 'Create file', }) => {
data: { name: '', makeDir: false, shouldSetToRename: true }, useHotkeyWrapper(['mod + n'], onCreateFile)
}) useHotkeyWrapper(['mod + shift + n'], onCreateFolder)
modelingSend({ type: 'Cancel' })
}
function createFolder() {
send({
type: 'Create file',
data: { name: '', makeDir: true, shouldSetToRename: true },
})
}
useHotkeyWrapper(['mod + n'], createFile)
useHotkeyWrapper(['mod + shift + n'], createFolder)
return ( return (
<> <>
@ -442,7 +514,7 @@ export const FileTreeMenu = () => {
bgClassName: 'bg-transparent', bgClassName: 'bg-transparent',
}} }}
className="!p-0 !bg-transparent hover:text-primary border-transparent hover:border-primary !outline-none" className="!p-0 !bg-transparent hover:text-primary border-transparent hover:border-primary !outline-none"
onClick={createFile} onClick={onCreateFile}
> >
<Tooltip position="bottom-right" delay={750}> <Tooltip position="bottom-right" delay={750}>
Create file Create file
@ -458,7 +530,7 @@ export const FileTreeMenu = () => {
bgClassName: 'bg-transparent', bgClassName: 'bg-transparent',
}} }}
className="!p-0 !bg-transparent hover:text-primary border-transparent hover:border-primary !outline-none" className="!p-0 !bg-transparent hover:text-primary border-transparent hover:border-primary !outline-none"
onClick={createFolder} onClick={onCreateFolder}
> >
<Tooltip position="bottom-right" delay={750}> <Tooltip position="bottom-right" delay={750}>
Create folder Create folder
@ -468,30 +540,110 @@ export const FileTreeMenu = () => {
) )
} }
type TreeEntry = 'file' | 'folder' | undefined
export const useFileTreeOperations = () => {
const { send } = useFileContext()
const { send: modelingSend } = useModelingContext()
// As long as this is undefined, a new "file tree entry prompt" is not shown.
const [newTreeEntry, setNewTreeEntry] = useState<TreeEntry>(undefined)
function createFile(args: { dryRun: boolean; name?: string }) {
if (args.dryRun) {
setNewTreeEntry('file')
return
}
// Clear so that the entry prompt goes away.
setNewTreeEntry(undefined)
if (!args.name) return
send({
type: 'Create file',
data: { name: args.name, makeDir: false, shouldSetToRename: false },
})
modelingSend({ type: 'Cancel' })
}
function createFolder(args: { dryRun: boolean; name?: string }) {
if (args.dryRun) {
setNewTreeEntry('folder')
return
}
setNewTreeEntry(undefined)
if (!args.name) return
send({
type: 'Create file',
data: { name: args.name, makeDir: true, shouldSetToRename: false },
})
}
return {
createFile,
createFolder,
newTreeEntry,
}
}
export const FileTree = ({ export const FileTree = ({
className = '', className = '',
onNavigateToFile: closePanel, onNavigateToFile: closePanel,
}: FileTreeProps) => { }: FileTreeProps) => {
const { createFile, createFolder, newTreeEntry } = useFileTreeOperations()
return ( return (
<div className={className}> <div className={className}>
<div className="flex items-center gap-1 px-4 py-1 bg-chalkboard-20/40 dark:bg-chalkboard-80/50 border-b border-b-chalkboard-30 dark:border-b-chalkboard-80"> <div className="flex items-center gap-1 px-4 py-1 bg-chalkboard-20/40 dark:bg-chalkboard-80/50 border-b border-b-chalkboard-30 dark:border-b-chalkboard-80">
<h2 className="flex-1 m-0 p-0 text-sm mono">Files</h2> <h2 className="flex-1 m-0 p-0 text-sm mono">Files</h2>
<FileTreeMenu /> <FileTreeMenu
onCreateFile={() => createFile({ dryRun: true })}
onCreateFolder={() => createFolder({ dryRun: true })}
/>
</div> </div>
<FileTreeInner onNavigateToFile={closePanel} /> <FileTreeInner
onNavigateToFile={closePanel}
newTreeEntry={newTreeEntry}
onCreateFile={(name: string) => createFile({ dryRun: false, name })}
onCreateFolder={(name: string) => createFolder({ dryRun: false, name })}
/>
</div> </div>
) )
} }
export const FileTreeInner = ({ export const FileTreeInner = ({
onNavigateToFile, onNavigateToFile,
onCreateFile,
onCreateFolder,
newTreeEntry,
}: { }: {
onCreateFile: (name: string) => void
onCreateFolder: (name: string) => void
newTreeEntry: TreeEntry
onNavigateToFile?: () => void onNavigateToFile?: () => void
}) => { }) => {
const loaderData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData const loaderData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
const { send: fileSend, context: fileContext } = useFileContext() const { send: fileSend, context: fileContext } = useFileContext()
const { send: modelingSend } = useModelingContext() const { send: modelingSend } = useModelingContext()
const [lastDirectoryClicked, setLastDirectoryClicked] = useState<
FileEntry | undefined
>(undefined)
const [treeSelection, setTreeSelection] = useState<FileEntry | undefined>(
loaderData.file
)
const onNavigateToFile_ = () => {
// Reset modeling state when navigating to a new file
onNavigateToFile?.()
modelingSend({ type: 'Cancel' })
}
// Refresh the file tree when there are changes. // Refresh the file tree when there are changes.
useFileSystemWatcher( useFileSystemWatcher(
async (eventType, path) => { async (eventType, path) => {
@ -501,6 +653,13 @@ export const FileTreeInner = ({
const isCurrentFile = loaderData.file?.path === path const isCurrentFile = loaderData.file?.path === path
const hasChanged = eventType === 'change' const hasChanged = eventType === 'change'
if (isCurrentFile && hasChanged) return if (isCurrentFile && hasChanged) return
// If it's a settings file we wrote to already from the app ignore it.
if (codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher) {
codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher = false
return
}
fileSend({ type: 'Refresh' }) fileSend({ type: 'Refresh' })
}, },
[loaderData?.project?.path, fileContext.selectedDirectory.path].filter( [loaderData?.project?.path, fileContext.selectedDirectory.path].filter(
@ -508,33 +667,81 @@ export const FileTreeInner = ({
) )
) )
const clickDirectory = () => { const onTreeEntryInputSubmit = (value: string) => {
if (newTreeEntry === 'file') {
onCreateFile(value)
onNavigateToFile_()
} else {
onCreateFolder(value)
}
}
const onClickDirectory = (
open_: boolean,
fileOrDir: FileEntry,
parentDir: FileEntry | undefined
) => {
// open true is closed... it's broken. Save me. I've inverted it here for
// sanity.
const open = !open_
const target = open ? fileOrDir : parentDir
// We're at the root, can't select anything further
if (!target) return
setTreeSelection(target)
setLastDirectoryClicked(target)
fileSend({ fileSend({
type: 'Set selected directory', type: 'Set selected directory',
directory: fileContext.project, directory: target,
}) })
} }
const showNewTreeEntry =
newTreeEntry !== undefined &&
fileContext.selectedDirectory.path === loaderData.project?.path
return ( return (
<div <div className="relative">
className="overflow-auto pb-12 absolute inset-0" <div
data-testid="file-pane-scroll-container" className="overflow-auto pb-12 absolute inset-0"
> data-testid="file-pane-scroll-container"
<ul className="m-0 p-0 text-sm" onClickCapture={clickDirectory}> >
{sortProject(fileContext.project?.children || []).map((fileOrDir) => ( <ul className="m-0 p-0 text-sm">
<FileTreeItem {showNewTreeEntry && (
project={fileContext.project} <div
currentFile={loaderData?.file} className="flex items-center"
fileOrDir={fileOrDir} style={{ paddingInlineStart: getIndentationCSS(0) }}
onNavigateToFile={() => { >
// Reset modeling state when navigating to a new file <FontAwesomeIcon
modelingSend({ type: 'Cancel' }) icon={faPencil}
onNavigateToFile?.() className="inline-block mr-2 m-0 p-0 w-2 h-2"
}} />
key={fileOrDir.path} <TreeEntryInput level={-1} onSubmit={onTreeEntryInputSubmit} />
/> </div>
))} )}
</ul> {sortFilesAndDirectories(fileContext.project?.children || []).map(
(fileOrDir) => (
<FileTreeItem
parentDir={fileContext.project}
project={fileContext.project}
currentFile={loaderData?.file}
lastDirectoryClicked={lastDirectoryClicked}
fileOrDir={fileOrDir}
onCreateFile={onCreateFile}
onCreateFolder={onCreateFolder}
newTreeEntry={newTreeEntry}
onClickDirectory={onClickDirectory}
onNavigateToFile={onNavigateToFile_}
key={fileOrDir.path}
treeSelection={treeSelection}
setTreeSelection={setTreeSelection}
/>
)
)}
</ul>
</div>
</div> </div>
) )
} }

View File

@ -158,36 +158,39 @@ export const ModelingMachineProvider = ({
'enable copilot': () => { 'enable copilot': () => {
editorManager.setCopilotEnabled(true) editorManager.setCopilotEnabled(true)
}, },
'sketch exit execute': ({ context: { store } }) => { // tsc reports this typing as perfectly fine, but eslint is complaining.
;(async () => { // It's actually nonsensical, so I'm quieting.
// When cancelling the sketch mode we should disable sketch mode within the engine. // eslint-disable-next-line @typescript-eslint/no-misused-promises
await engineCommandManager.sendSceneCommand({ 'sketch exit execute': async ({
type: 'modeling_cmd_req', context: { store },
cmd_id: uuidv4(), }): Promise<void> => {
cmd: { type: 'sketch_mode_disable' }, // When cancelling the sketch mode we should disable sketch mode within the engine.
}) await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'sketch_mode_disable' },
})
sceneInfra.camControls.syncDirection = 'clientToEngine' sceneInfra.camControls.syncDirection = 'clientToEngine'
if (cameraProjection.current === 'perspective') { if (cameraProjection.current === 'perspective') {
await sceneInfra.camControls.snapToPerspectiveBeforeHandingBackControlToEngine() await sceneInfra.camControls.snapToPerspectiveBeforeHandingBackControlToEngine()
} }
sceneInfra.camControls.syncDirection = 'engineToClient' sceneInfra.camControls.syncDirection = 'engineToClient'
store.videoElement?.pause() store.videoElement?.pause()
kclManager return kclManager
.executeCode() .executeCode()
.then(() => { .then(() => {
if (engineCommandManager.engineConnection?.idleMode) return if (engineCommandManager.engineConnection?.idleMode) return
store.videoElement?.play().catch((e) => { store.videoElement?.play().catch((e) => {
console.warn('Video playing was prevented', e) console.warn('Video playing was prevented', e)
})
}) })
.catch(reportRejection) })
})().catch(reportRejection) .catch(reportRejection)
}, },
'Set mouse state': assign(({ context, event }) => { 'Set mouse state': assign(({ context, event }) => {
if (event.type !== 'Set mouse state') return {} if (event.type !== 'Set mouse state') return {}

View File

@ -48,7 +48,7 @@ export const ModelingPaneHeader = ({
bgClassName: 'bg-transparent dark:bg-transparent', bgClassName: 'bg-transparent dark:bg-transparent',
}} }}
className="!p-0 !bg-transparent hover:text-primary border-transparent dark:!border-transparent hover:!border-primary dark:hover:!border-chalkboard-70 !outline-none" className="!p-0 !bg-transparent hover:text-primary border-transparent dark:!border-transparent hover:!border-primary dark:hover:!border-chalkboard-70 !outline-none"
onClick={onClose} onClick={() => onClose()}
> >
<Tooltip position="bottom-right" delay={750}> <Tooltip position="bottom-right" delay={750}>
Close Close
@ -59,14 +59,12 @@ export const ModelingPaneHeader = ({
} }
export const ModelingPane = ({ export const ModelingPane = ({
title,
icon,
id, id,
children, children,
className, className,
Menu,
detailsTestId, detailsTestId,
onClose, onClose,
title,
...props ...props
}: ModelingPaneProps) => { }: ModelingPaneProps) => {
const { settings } = useSettingsAuthContext() const { settings } = useSettingsAuthContext()
@ -78,6 +76,7 @@ export const ModelingPane = ({
return ( return (
<section <section
{...props} {...props}
title={title && typeof title === 'string' ? title : ''}
data-testid={detailsTestId} data-testid={detailsTestId}
id={id} id={id}
className={ className={
@ -88,14 +87,7 @@ export const ModelingPane = ({
(className || '') (className || '')
} }
> >
<ModelingPaneHeader {children}
id={id}
icon={icon}
title={title}
Menu={Menu}
onClose={onClose}
/>
<div className="relative w-full">{children}</div>
</section> </section>
) )
} }

View File

@ -5,16 +5,18 @@ import { CamDebugSettings } from 'clientSideScene/ClientSideSceneComp'
export const DebugPane = () => { export const DebugPane = () => {
return ( return (
<section <div className="relative">
data-testid="debug-panel" <section
className="absolute inset-0 p-2 box-border overflow-auto" data-testid="debug-panel"
> className="absolute inset-0 p-2 box-border overflow-auto"
<div className="flex flex-col"> >
<EngineCommands /> <div className="flex flex-col">
<CamDebugSettings /> <EngineCommands />
<AstExplorer /> <CamDebugSettings />
<DebugFeatureTree /> <AstExplorer />
</div> <DebugFeatureTree />
</section> </div>
</section>
</div>
) )
} }

View File

@ -174,27 +174,31 @@ export const KclEditorPane = () => {
const initialCode = useRef(codeManager.code) const initialCode = useRef(codeManager.code)
return ( return (
<div <div className="relative">
id="code-mirror-override" <div
className={'absolute inset-0 ' + (cursorBlinking.current ? 'blink' : '')} id="code-mirror-override"
> className={
<CodeEditor 'absolute inset-0 ' + (cursorBlinking.current ? 'blink' : '')
initialDocValue={initialCode.current} }
extensions={editorExtensions} >
theme={theme} <CodeEditor
onCreateEditor={(_editorView) => { initialDocValue={initialCode.current}
if (_editorView === null) return extensions={editorExtensions}
theme={theme}
onCreateEditor={(_editorView) => {
if (_editorView === null) return
editorManager.setEditorView(_editorView) editorManager.setEditorView(_editorView)
// On first load of this component, ensure we show the current errors // On first load of this component, ensure we show the current errors
// in the editor. // in the editor.
// Make sure we don't add them twice. // Make sure we don't add them twice.
if (diagnosticCount(_editorView.state) === 0) { if (diagnosticCount(_editorView.state) === 0) {
kclManager.setDiagnosticsForCurrentErrors() kclManager.setDiagnosticsForCurrentErrors()
} }
}} }}
/> />
</div>
</div> </div>
) )
} }

View File

@ -43,14 +43,14 @@ describe('processMemory', () => {
tag: null, tag: null,
id: expect.any(String), id: expect.any(String),
faceId: expect.any(String), faceId: expect.any(String),
sourceRange: [170, 194], sourceRange: [170, 194, 0],
}, },
{ {
type: 'extrudePlane', type: 'extrudePlane',
tag: null, tag: null,
id: expect.any(String), id: expect.any(String),
faceId: expect.any(String), faceId: expect.any(String),
sourceRange: [202, 230], sourceRange: [202, 230, 0],
}, },
], ],
theSketch: [ theSketch: [

View File

@ -2,11 +2,17 @@ import { IconDefinition, faBugSlash } from '@fortawesome/free-solid-svg-icons'
import { KclEditorMenu } from 'components/ModelingSidebar/ModelingPanes/KclEditorMenu' import { KclEditorMenu } from 'components/ModelingSidebar/ModelingPanes/KclEditorMenu'
import { CustomIconName } from 'components/CustomIcon' import { CustomIconName } from 'components/CustomIcon'
import { KclEditorPane } from 'components/ModelingSidebar/ModelingPanes/KclEditorPane' import { KclEditorPane } from 'components/ModelingSidebar/ModelingPanes/KclEditorPane'
import { ModelingPaneHeader } from 'components/ModelingSidebar/ModelingPane'
import { MouseEventHandler, ReactNode } from 'react' import { MouseEventHandler, ReactNode } from 'react'
import { MemoryPane, MemoryPaneMenu } from './MemoryPane' import { MemoryPane, MemoryPaneMenu } from './MemoryPane'
import { LogsPane } from './LoggingPanes' import { LogsPane } from './LoggingPanes'
import { DebugPane } from './DebugPane' import { DebugPane } from './DebugPane'
import { FileTreeInner, FileTreeMenu, FileTreeRoot } from 'components/FileTree' import {
FileTreeInner,
FileTreeMenu,
FileTreeRoot,
useFileTreeOperations,
} from 'components/FileTree'
import { useKclContext } from 'lang/KclProvider' import { useKclContext } from 'lang/KclProvider'
import { editorManager } from 'lib/singletons' import { editorManager } from 'lib/singletons'
import { ContextFrom } from 'xstate' import { ContextFrom } from 'xstate'
@ -38,20 +44,19 @@ interface PaneCallbackProps {
export type SidebarPane = { export type SidebarPane = {
id: SidebarType id: SidebarType
title: ReactNode sidebarName: string
sidebarName?: string
icon: CustomIconName | IconDefinition icon: CustomIconName | IconDefinition
keybinding: string keybinding: string
Content: ReactNode | React.FC Content: React.FC<{ id: SidebarType; onClose: () => void }>
Menu?: ReactNode | React.FC
hide?: boolean | ((props: PaneCallbackProps) => boolean) hide?: boolean | ((props: PaneCallbackProps) => boolean)
showBadge?: BadgeInfo showBadge?: BadgeInfo
} }
export type SidebarAction = { export type SidebarAction = {
id: string id: string
title: ReactNode sidebarName: string
icon: CustomIconName icon: CustomIconName
title: ReactNode
iconClassName?: string // Just until we get rid of FontAwesome icons iconClassName?: string // Just until we get rid of FontAwesome icons
keybinding: string keybinding: string
action: () => void action: () => void
@ -59,14 +64,30 @@ export type SidebarAction = {
disable?: () => string | undefined disable?: () => string | undefined
} }
// For now a lot of icons are the same but the reality is they could totally
// be different, like an icon based on some data for the pane, or the icon
// changes to be a spinning loader on loading.
export const sidebarPanes: SidebarPane[] = [ export const sidebarPanes: SidebarPane[] = [
{ {
id: 'code', id: 'code',
title: 'KCL Code',
icon: 'code', icon: 'code',
Content: KclEditorPane, sidebarName: 'KCL Code',
Content: (props: { id: SidebarType; onClose: () => void }) => {
return (
<>
<ModelingPaneHeader
id={props.id}
icon="code"
title="KCL Code"
Menu={<KclEditorMenu />}
onClose={props.onClose}
/>
<KclEditorPane />
</>
)
},
keybinding: 'Shift + C', keybinding: 'Shift + C',
Menu: KclEditorMenu,
showBadge: { showBadge: {
value: ({ kclContext }) => { value: ({ kclContext }) => {
return kclContext.errors.length return kclContext.errors.length
@ -79,34 +100,96 @@ export const sidebarPanes: SidebarPane[] = [
}, },
{ {
id: 'files', id: 'files',
title: <FileTreeRoot />,
sidebarName: 'Project Files',
icon: 'folder', icon: 'folder',
Content: FileTreeInner, sidebarName: 'Project Files',
Content: (props: { id: SidebarType; onClose: () => void }) => {
const { createFile, createFolder, newTreeEntry } = useFileTreeOperations()
return (
<>
<ModelingPaneHeader
id={props.id}
icon="folder"
title={<FileTreeRoot />}
Menu={
<FileTreeMenu
onCreateFile={() => createFile({ dryRun: true })}
onCreateFolder={() => createFolder({ dryRun: true })}
/>
}
onClose={props.onClose}
/>
<FileTreeInner
onCreateFile={(name: string) => createFile({ dryRun: false, name })}
onCreateFolder={(name: string) =>
createFolder({ dryRun: false, name })
}
newTreeEntry={newTreeEntry}
/>
</>
)
},
keybinding: 'Shift + F', keybinding: 'Shift + F',
Menu: FileTreeMenu,
hide: ({ platform }) => platform === 'web', hide: ({ platform }) => platform === 'web',
}, },
{ {
id: 'variables', id: 'variables',
title: 'Variables',
icon: 'make-variable', icon: 'make-variable',
Content: MemoryPane, sidebarName: 'Variables',
Menu: MemoryPaneMenu, Content: (props: { id: SidebarType; onClose: () => void }) => {
return (
<>
<ModelingPaneHeader
id={props.id}
icon="make-variable"
title="Variables"
Menu={<MemoryPaneMenu />}
onClose={props.onClose}
/>
<MemoryPane />
</>
)
},
keybinding: 'Shift + V', keybinding: 'Shift + V',
}, },
{ {
id: 'logs', id: 'logs',
title: 'Logs',
icon: 'logs', icon: 'logs',
Content: LogsPane, sidebarName: 'Logs',
Content: (props: { id: SidebarType; onClose: () => void }) => {
return (
<>
<ModelingPaneHeader
id={props.id}
icon="logs"
title="Logs"
Menu={null}
onClose={props.onClose}
/>
<LogsPane />
</>
)
},
keybinding: 'Shift + L', keybinding: 'Shift + L',
}, },
{ {
id: 'debug', id: 'debug',
title: 'Debug',
icon: faBugSlash, icon: faBugSlash,
Content: DebugPane, sidebarName: 'Debug',
Content: (props: { id: SidebarType; onClose: () => void }) => {
return (
<>
<ModelingPaneHeader
id={props.id}
icon={faBugSlash}
title="Debug"
Menu={null}
onClose={props.onClose}
/>
<DebugPane />
</>
)
},
keybinding: 'Shift + D', keybinding: 'Shift + D',
hide: ({ settings }) => !settings.modeling.showDebugPanel.current, hide: ({ settings }) => !settings.modeling.showDebugPanel.current,
}, },

View File

@ -1,11 +0,0 @@
.grid {
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: 1fr 1fr;
row-gap: 0.25rem;
align-items: stretch;
position: relative;
padding-block: 1px;
max-width: 100%;
flex: 1 1 0;
}

View File

@ -5,14 +5,12 @@ import {
useCallback, useCallback,
useEffect, useEffect,
useMemo, useMemo,
ReactNode,
useContext, useContext,
} from 'react' } from 'react'
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
import { SidebarAction, SidebarType, sidebarPanes } from './ModelingPanes' import { SidebarAction, SidebarType, sidebarPanes } from './ModelingPanes'
import Tooltip from 'components/Tooltip' import Tooltip from 'components/Tooltip'
import { ActionIcon } from 'components/ActionIcon' import { ActionIcon } from 'components/ActionIcon'
import styles from './ModelingSidebar.module.css'
import { ModelingPane } from './ModelingPane' import { ModelingPane } from './ModelingPane'
import { isDesktop } from 'lib/isDesktop' import { isDesktop } from 'lib/isDesktop'
import { useModelingContext } from 'hooks/useModelingContext' import { useModelingContext } from 'hooks/useModelingContext'
@ -62,6 +60,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
{ {
id: 'export', id: 'export',
title: 'Export part', title: 'Export part',
sidebarName: 'Export part',
icon: 'floppyDiskArrow', icon: 'floppyDiskArrow',
keybinding: 'Ctrl + Shift + E', keybinding: 'Ctrl + Shift + E',
action: () => action: () =>
@ -73,6 +72,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
{ {
id: 'make', id: 'make',
title: 'Make part', title: 'Make part',
sidebarName: 'Make part',
icon: 'printer3d', icon: 'printer3d',
keybinding: 'Ctrl + Shift + M', keybinding: 'Ctrl + Shift + M',
// eslint-disable-next-line @typescript-eslint/no-misused-promises // eslint-disable-next-line @typescript-eslint/no-misused-promises
@ -182,7 +182,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
bottomRight: 'hidden', bottomRight: 'hidden',
}} }}
> >
<div id="app-sidebar" className={styles.grid + ' flex-1'}> <div id="app-sidebar" className="flex flex-row h-full">
<ul <ul
className={ className={
(context.store?.openPanes.length === 0 ? 'rounded-r ' : '') + (context.store?.openPanes.length === 0 ? 'rounded-r ' : '') +
@ -220,7 +220,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
key={action.id} key={action.id}
paneConfig={{ paneConfig={{
id: action.id, id: action.id,
title: action.title, sidebarName: action.sidebarName,
icon: action.icon, icon: action.icon,
keybinding: action.keybinding, keybinding: action.keybinding,
iconClassName: action.iconClassName, iconClassName: action.iconClassName,
@ -237,10 +237,8 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
<ul <ul
id="pane-section" id="pane-section"
className={ className={
'ml-[-1px] col-start-2 col-span-1 flex flex-col gap-2 ' + 'ml-[-1px] col-start-2 col-span-1 flex flex-col items-stretch gap-2 ' +
(context.store?.openPanes.length >= 1 (context.store?.openPanes.length >= 1 ? `w-full` : `hidden`)
? `row-start-1 row-end-3`
: `hidden`)
} }
> >
{filteredPanes {filteredPanes
@ -249,13 +247,15 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
<ModelingPane <ModelingPane
key={pane.id} key={pane.id}
icon={pane.icon} icon={pane.icon}
title={pane.sidebarName}
onClose={() => {}}
id={`${pane.id}-pane`} id={`${pane.id}-pane`}
title={pane.title}
Menu={pane.Menu}
onClose={() => togglePane(pane.id)}
> >
{pane.Content instanceof Function ? ( {pane.Content instanceof Function ? (
<pane.Content /> <pane.Content
id={pane.id}
onClose={() => togglePane(pane.id)}
/>
) : ( ) : (
pane.Content pane.Content
)} )}
@ -271,8 +271,7 @@ interface ModelingPaneButtonProps
extends React.HTMLAttributes<HTMLButtonElement> { extends React.HTMLAttributes<HTMLButtonElement> {
paneConfig: { paneConfig: {
id: string id: string
title: ReactNode sidebarName: string
sidebarName?: string
icon: CustomIconName | IconDefinition icon: CustomIconName | IconDefinition
keybinding: string keybinding: string
iconClassName?: string iconClassName?: string
@ -301,10 +300,7 @@ function ModelingPaneButton({
<button <button
className="group pointer-events-auto flex items-center justify-center border-transparent dark:border-transparent disabled:!border-transparent p-0 m-0 rounded-sm !outline-0 focus-visible:border-primary" className="group pointer-events-auto flex items-center justify-center border-transparent dark:border-transparent disabled:!border-transparent p-0 m-0 rounded-sm !outline-0 focus-visible:border-primary"
onClick={onClick} onClick={onClick}
name={ name={paneConfig.sidebarName}
paneConfig.sidebarName ??
(typeof paneConfig.title === 'string' ? paneConfig.title : '')
}
data-testid={paneConfig.id + '-pane-button'} data-testid={paneConfig.id + '-pane-button'}
disabled={disabledText !== undefined} disabled={disabledText !== undefined}
aria-disabled={disabledText !== undefined} aria-disabled={disabledText !== undefined}
@ -320,7 +316,7 @@ function ModelingPaneButton({
} }
/> />
<span className="sr-only"> <span className="sr-only">
{paneConfig.sidebarName ?? paneConfig.title} {paneConfig.sidebarName}
{paneIsOpen !== undefined ? ` pane` : ''} {paneIsOpen !== undefined ? ` pane` : ''}
</span> </span>
<Tooltip <Tooltip
@ -329,7 +325,7 @@ function ModelingPaneButton({
hoverOnly hoverOnly
> >
<span className="flex-1"> <span className="flex-1">
{paneConfig.sidebarName ?? paneConfig.title} {paneConfig.sidebarName}
{disabledText !== undefined ? ` (${disabledText})` : ''} {disabledText !== undefined ? ` (${disabledText})` : ''}
{paneIsOpen !== undefined ? ` pane` : ''} {paneIsOpen !== undefined ? ` pane` : ''}
</span> </span>

View File

@ -41,6 +41,7 @@ import { reportRejection } from 'lib/trap'
import { getAppSettingsFilePath } from 'lib/desktop' import { getAppSettingsFilePath } from 'lib/desktop'
import { isDesktop } from 'lib/isDesktop' import { isDesktop } from 'lib/isDesktop'
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher' import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
import { codeManager } from 'lib/singletons'
type MachineContext<T extends AnyStateMachine> = { type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T> state: StateFrom<T>
@ -200,13 +201,13 @@ export const SettingsAuthProviderBase = ({
console.error('Error executing AST after settings change', e) console.error('Error executing AST after settings change', e)
} }
}, },
persistSettings: ({ context, event }) => { async persistSettings({ context, event }) {
// Without this, when a user changes the file, it'd // Without this, when a user changes the file, it'd
// create a detection loop with the file-system watcher. // create a detection loop with the file-system watcher.
if (event.doNotPersist) return if (event.doNotPersist) return
// eslint-disable-next-line @typescript-eslint/no-floating-promises codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher = true
saveSettings(context, loadedProject?.project?.path) return saveSettings(context, loadedProject?.project?.path)
}, },
}, },
}), }),
@ -220,7 +221,7 @@ export const SettingsAuthProviderBase = ({
}, []) }, [])
useFileSystemWatcher( useFileSystemWatcher(
async () => { async (eventType: string) => {
// If there is a projectPath but it no longer exists it means // If there is a projectPath but it no longer exists it means
// it was exterally removed. If we let the code past this condition // it was exterally removed. If we let the code past this condition
// execute it will recreate the directory due to code in // execute it will recreate the directory due to code in
@ -234,6 +235,9 @@ export const SettingsAuthProviderBase = ({
} }
} }
// Only reload if there are changes. Ignore everything else.
if (eventType !== 'change') return
const data = await loadAndValidateSettings(loadedProject?.project?.path) const data = await loadAndValidateSettings(loadedProject?.project?.path)
settingsSend({ settingsSend({
type: 'Set all settings', type: 'Set all settings',

View File

@ -96,10 +96,10 @@ export class KclPlugin implements PluginValue {
const newCode = viewUpdate.state.doc.toString() const newCode = viewUpdate.state.doc.toString()
codeManager.code = newCode codeManager.code = newCode
// eslint-disable-next-line @typescript-eslint/no-floating-promises
codeManager.writeToFile()
this.scheduleUpdateDoc() void codeManager.writeToFile().then(() => {
this.scheduleUpdateDoc()
})
} }
scheduleUpdateDoc() { scheduleUpdateDoc() {

View File

@ -26,6 +26,7 @@ export function useRefreshSettings(routeId: string = PATHS.INDEX) {
ctx.settings.send({ ctx.settings.send({
type: 'Set all settings', type: 'Set all settings',
settings: routeData, settings: routeData,
doNotPersist: true,
}) })
}, []) }, [])
} }

View File

@ -38,6 +38,7 @@ export class KclManager {
body: [], body: [],
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0,
nonCodeMeta: { nonCodeMeta: {
nonCodeNodes: {}, nonCodeNodes: {},
startNodes: [], startNodes: [],
@ -204,6 +205,7 @@ export class KclManager {
body: [], body: [],
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0,
nonCodeMeta: { nonCodeMeta: {
nonCodeNodes: {}, nonCodeNodes: {},
startNodes: [], startNodes: [],
@ -429,13 +431,9 @@ export class KclManager {
// Update the code state and the editor. // Update the code state and the editor.
codeManager.updateCodeStateEditor(code) codeManager.updateCodeStateEditor(code)
// Write back to the file system.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
codeManager.writeToFile()
// execute the code. // Write back to the file system.
// eslint-disable-next-line @typescript-eslint/no-floating-promises void codeManager.writeToFile().then(() => this.executeCode())
this.executeCode()
} }
// There's overlapping responsibility between updateAst and executeAst. // There's overlapping responsibility between updateAst and executeAst.
// updateAst was added as it was used a lot before xState migration so makes the port easier. // updateAst was added as it was used a lot before xState migration so makes the port easier.

View File

@ -1903,6 +1903,6 @@ describe('parsing errors', () => {
const error = result as KCLError const error = result as KCLError
expect(error.kind).toBe('syntax') expect(error.kind).toBe('syntax')
expect(error.msg).toBe('Unexpected token: (') expect(error.msg).toBe('Unexpected token: (')
expect(error.sourceRanges).toEqual([[27, 28]]) expect(error.sourceRanges).toEqual([[27, 28, 0]])
}) })
}) })

View File

@ -19,7 +19,7 @@ const mySketch001 = startSketchOn('XY')
const sketch001 = execState.memory.get('mySketch001') const sketch001 = execState.memory.get('mySketch001')
expect(sketch001).toEqual({ expect(sketch001).toEqual({
type: 'UserVal', type: 'UserVal',
__meta: [{ sourceRange: [46, 71] }], __meta: [{ sourceRange: [46, 71, 0] }],
value: { value: {
type: 'Sketch', type: 'Sketch',
on: expect.any(Object), on: expect.any(Object),
@ -29,7 +29,7 @@ const mySketch001 = startSketchOn('XY')
tag: null, tag: null,
__geoMeta: { __geoMeta: {
id: expect.any(String), id: expect.any(String),
sourceRange: [46, 71], sourceRange: [46, 71, 0],
}, },
}, },
paths: [ paths: [
@ -39,7 +39,7 @@ const mySketch001 = startSketchOn('XY')
to: [-1.59, -1.54], to: [-1.59, -1.54],
from: [0, 0], from: [0, 0],
__geoMeta: { __geoMeta: {
sourceRange: [77, 102], sourceRange: [77, 102, 0],
id: expect.any(String), id: expect.any(String),
}, },
}, },
@ -49,13 +49,13 @@ const mySketch001 = startSketchOn('XY')
from: [-1.59, -1.54], from: [-1.59, -1.54],
tag: null, tag: null,
__geoMeta: { __geoMeta: {
sourceRange: [108, 132], sourceRange: [108, 132, 0],
id: expect.any(String), id: expect.any(String),
}, },
}, },
], ],
id: expect.any(String), id: expect.any(String),
__meta: [{ sourceRange: [46, 71] }], __meta: [{ sourceRange: [46, 71, 0] }],
}, },
}) })
}) })
@ -80,14 +80,14 @@ const mySketch001 = startSketchOn('XY')
faceId: expect.any(String), faceId: expect.any(String),
tag: null, tag: null,
id: expect.any(String), id: expect.any(String),
sourceRange: [77, 102], sourceRange: [77, 102, 0],
}, },
{ {
type: 'extrudePlane', type: 'extrudePlane',
faceId: expect.any(String), faceId: expect.any(String),
tag: null, tag: null,
id: expect.any(String), id: expect.any(String),
sourceRange: [108, 132], sourceRange: [108, 132, 0],
}, },
], ],
sketch: { sketch: {
@ -104,7 +104,7 @@ const mySketch001 = startSketchOn('XY')
tag: null, tag: null,
__geoMeta: { __geoMeta: {
id: expect.any(String), id: expect.any(String),
sourceRange: [77, 102], sourceRange: [77, 102, 0],
}, },
}, },
{ {
@ -114,7 +114,7 @@ const mySketch001 = startSketchOn('XY')
tag: null, tag: null,
__geoMeta: { __geoMeta: {
id: expect.any(String), id: expect.any(String),
sourceRange: [108, 132], sourceRange: [108, 132, 0],
}, },
}, },
], ],
@ -122,7 +122,7 @@ const mySketch001 = startSketchOn('XY')
height: 2, height: 2,
startCapId: expect.any(String), startCapId: expect.any(String),
endCapId: expect.any(String), endCapId: expect.any(String),
__meta: [{ sourceRange: [46, 71] }], __meta: [{ sourceRange: [46, 71, 0] }],
}) })
}) })
test('sketch extrude and sketch on one of the faces', async () => { test('sketch extrude and sketch on one of the faces', async () => {
@ -162,7 +162,7 @@ const sk2 = startSketchOn('XY')
faceId: expect.any(String), faceId: expect.any(String),
tag: null, tag: null,
id: expect.any(String), id: expect.any(String),
sourceRange: [69, 89], sourceRange: [69, 89, 0],
}, },
{ {
type: 'extrudePlane', type: 'extrudePlane',
@ -174,14 +174,14 @@ const sk2 = startSketchOn('XY')
value: 'p', value: 'p',
}, },
id: expect.any(String), id: expect.any(String),
sourceRange: [95, 117], sourceRange: [95, 117, 0],
}, },
{ {
type: 'extrudePlane', type: 'extrudePlane',
faceId: expect.any(String), faceId: expect.any(String),
tag: null, tag: null,
id: expect.any(String), id: expect.any(String),
sourceRange: [123, 142], sourceRange: [123, 142, 0],
}, },
], ],
sketch: { sketch: {
@ -194,7 +194,7 @@ const sk2 = startSketchOn('XY')
p: { p: {
__meta: [ __meta: [
{ {
sourceRange: [114, 116], sourceRange: [114, 116, 0],
}, },
], ],
type: 'TagIdentifier', type: 'TagIdentifier',
@ -210,7 +210,7 @@ const sk2 = startSketchOn('XY')
tag: null, tag: null,
__geoMeta: { __geoMeta: {
id: expect.any(String), id: expect.any(String),
sourceRange: [69, 89], sourceRange: [69, 89, 0],
}, },
}, },
{ {
@ -225,7 +225,7 @@ const sk2 = startSketchOn('XY')
}, },
__geoMeta: { __geoMeta: {
id: expect.any(String), id: expect.any(String),
sourceRange: [95, 117], sourceRange: [95, 117, 0],
}, },
}, },
{ {
@ -235,7 +235,7 @@ const sk2 = startSketchOn('XY')
tag: null, tag: null,
__geoMeta: { __geoMeta: {
id: expect.any(String), id: expect.any(String),
sourceRange: [123, 142], sourceRange: [123, 142, 0],
}, },
}, },
], ],
@ -243,7 +243,7 @@ const sk2 = startSketchOn('XY')
height: 2, height: 2,
startCapId: expect.any(String), startCapId: expect.any(String),
endCapId: expect.any(String), endCapId: expect.any(String),
__meta: [{ sourceRange: [38, 63] }], __meta: [{ sourceRange: [38, 63, 0] }],
}, },
{ {
type: 'Solid', type: 'Solid',
@ -254,7 +254,7 @@ const sk2 = startSketchOn('XY')
faceId: expect.any(String), faceId: expect.any(String),
tag: null, tag: null,
id: expect.any(String), id: expect.any(String),
sourceRange: [373, 393], sourceRange: [373, 393, 0],
}, },
{ {
type: 'extrudePlane', type: 'extrudePlane',
@ -266,14 +266,14 @@ const sk2 = startSketchOn('XY')
value: 'o', value: 'o',
}, },
id: expect.any(String), id: expect.any(String),
sourceRange: [399, 420], sourceRange: [399, 420, 0],
}, },
{ {
type: 'extrudePlane', type: 'extrudePlane',
faceId: expect.any(String), faceId: expect.any(String),
tag: null, tag: null,
id: expect.any(String), id: expect.any(String),
sourceRange: [426, 445], sourceRange: [426, 445, 0],
}, },
], ],
sketch: { sketch: {
@ -286,7 +286,7 @@ const sk2 = startSketchOn('XY')
o: { o: {
__meta: [ __meta: [
{ {
sourceRange: [417, 419], sourceRange: [417, 419, 0],
}, },
], ],
type: 'TagIdentifier', type: 'TagIdentifier',
@ -302,7 +302,7 @@ const sk2 = startSketchOn('XY')
tag: null, tag: null,
__geoMeta: { __geoMeta: {
id: expect.any(String), id: expect.any(String),
sourceRange: [373, 393], sourceRange: [373, 393, 0],
}, },
}, },
{ {
@ -317,7 +317,7 @@ const sk2 = startSketchOn('XY')
}, },
__geoMeta: { __geoMeta: {
id: expect.any(String), id: expect.any(String),
sourceRange: [399, 420], sourceRange: [399, 420, 0],
}, },
}, },
{ {
@ -327,7 +327,7 @@ const sk2 = startSketchOn('XY')
tag: null, tag: null,
__geoMeta: { __geoMeta: {
id: expect.any(String), id: expect.any(String),
sourceRange: [426, 445], sourceRange: [426, 445, 0],
}, },
}, },
], ],
@ -335,7 +335,7 @@ const sk2 = startSketchOn('XY')
height: 2, height: 2,
startCapId: expect.any(String), startCapId: expect.any(String),
endCapId: expect.any(String), endCapId: expect.any(String),
__meta: [{ sourceRange: [342, 367] }], __meta: [{ sourceRange: [342, 367, 0] }],
}, },
]) ])
}) })

View File

@ -121,20 +121,28 @@ export default class CodeManager {
// Only write our buffer contents to file once per second. Any faster // Only write our buffer contents to file once per second. Any faster
// and file-system watchers which read, will receive empty data during // and file-system watchers which read, will receive empty data during
// writes. // writes.
clearTimeout(this.timeoutWriter) clearTimeout(this.timeoutWriter)
this.writeCausedByAppCheckedInFileTreeFileSystemWatcher = true this.writeCausedByAppCheckedInFileTreeFileSystemWatcher = true
this.timeoutWriter = setTimeout(() => {
// Wait one event loop to give a chance for params to be set return new Promise((resolve, reject) => {
// Save the file to disk this.timeoutWriter = setTimeout(() => {
this._currentFilePath && if (!this._currentFilePath)
return reject(new Error('currentFilePath not set'))
// Wait one event loop to give a chance for params to be set
// Save the file to disk
window.electron window.electron
.writeFile(this._currentFilePath, this.code ?? '') .writeFile(this._currentFilePath, this.code ?? '')
.then(resolve)
.catch((err: Error) => { .catch((err: Error) => {
// TODO: add tracing per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254) // TODO: add tracing per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254)
console.error('error saving file', err) console.error('error saving file', err)
toast.error('Error saving file, please check file permissions') toast.error('Error saving file, please check file permissions')
reject(err)
}) })
}, 1000) }, 1000)
})
} else { } else {
safeLSSetItem(PERSIST_CODE_KEY, this.code) safeLSSetItem(PERSIST_CODE_KEY, this.code)
} }

View File

@ -9,8 +9,8 @@ describe('test kclErrToDiagnostic', () => {
kind: 'semantic', kind: 'semantic',
msg: 'Semantic error', msg: 'Semantic error',
sourceRanges: [ sourceRanges: [
[0, 1], [0, 1, 0],
[2, 3], [2, 3, 0],
], ],
}, },
{ {
@ -19,8 +19,8 @@ describe('test kclErrToDiagnostic', () => {
kind: 'type', kind: 'type',
msg: 'Type error', msg: 'Type error',
sourceRanges: [ sourceRanges: [
[4, 5], [4, 5, 0],
[6, 7], [6, 7, 0],
], ],
}, },
] ]

View File

@ -4,15 +4,17 @@ 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'
const TOP_LEVEL_MODULE_ID = 0
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][] sourceRanges: [number, number, number][]
msg: string msg: string
constructor( constructor(
kind: ExtractKind<RustKclError> | 'name', kind: ExtractKind<RustKclError> | 'name',
msg: string, msg: string,
sourceRanges: [number, number][] sourceRanges: [number, number, number][]
) { ) {
super() super()
this.kind = kind this.kind = kind
@ -23,63 +25,63 @@ export class KCLError extends Error {
} }
export class KCLLexicalError extends KCLError { export class KCLLexicalError extends KCLError {
constructor(msg: string, sourceRanges: [number, number][]) { constructor(msg: string, sourceRanges: [number, number, number][]) {
super('lexical', msg, sourceRanges) super('lexical', msg, sourceRanges)
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][]) { constructor(msg: string, sourceRanges: [number, number, number][]) {
super('internal', msg, sourceRanges) super('internal', msg, sourceRanges)
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][]) { constructor(msg: string, sourceRanges: [number, number, number][]) {
super('syntax', msg, sourceRanges) super('syntax', msg, sourceRanges)
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][]) { constructor(msg: string, sourceRanges: [number, number, number][]) {
super('semantic', msg, sourceRanges) super('semantic', msg, sourceRanges)
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][]) { constructor(msg: string, sourceRanges: [number, number, number][]) {
super('type', msg, sourceRanges) super('type', msg, sourceRanges)
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][]) { constructor(msg: string, sourceRanges: [number, number, number][]) {
super('unimplemented', msg, sourceRanges) super('unimplemented', msg, sourceRanges)
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][]) { constructor(msg: string, sourceRanges: [number, number, number][]) {
super('unexpected', msg, sourceRanges) super('unexpected', msg, sourceRanges)
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][]) { constructor(key: string, sourceRanges: [number, number, number][]) {
super('name', `Key ${key} was already defined elsewhere`, sourceRanges) super('name', `Key ${key} was already defined elsewhere`, sourceRanges)
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][]) { constructor(key: string, sourceRanges: [number, number, number][]) {
super('name', `Key ${key} has not been defined`, sourceRanges) super('name', `Key ${key} has not been defined`, sourceRanges)
Object.setPrototypeOf(this, KCLUndefinedValueError.prototype) Object.setPrototypeOf(this, KCLUndefinedValueError.prototype)
} }
@ -97,13 +99,22 @@ export function lspDiagnosticsToKclErrors(
.flatMap( .flatMap(
({ range, message }) => ({ range, message }) =>
new KCLError('unexpected', message, [ new KCLError('unexpected', message, [
[posToOffset(doc, range.start)!, posToOffset(doc, range.end)!], [
posToOffset(doc, range.start)!,
posToOffset(doc, range.end)!,
TOP_LEVEL_MODULE_ID,
],
]) ])
) )
.filter(({ sourceRanges }) => { .filter(({ sourceRanges }) => {
const [from, to] = sourceRanges[0] const [from, to, moduleId] = sourceRanges[0]
return ( return (
from !== null && to !== null && from !== undefined && to !== undefined 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) => {
@ -127,8 +138,16 @@ export function kclErrorsToDiagnostics(
errors: KCLError[] errors: KCLError[]
): CodeMirrorDiagnostic[] { ): CodeMirrorDiagnostic[] {
return errors?.flatMap((err) => { return errors?.flatMap((err) => {
return err.sourceRanges.map(([from, to]) => { const sourceRanges: CodeMirrorDiagnostic[] = err.sourceRanges
return { from, to, message: err.msg, severity: 'error' } // Filter out errors that are not from the top-level module.
}) .filter(([_start, _end, moduleId]) => moduleId === TOP_LEVEL_MODULE_ID)
.map(([from, to]) => {
return { from, to, message: err.msg, severity: 'error' }
})
// Make sure we didn't filter out all the source ranges.
if (sourceRanges.length === 0) {
sourceRanges.push({ from: 0, to: 0, message: err.msg, severity: 'error' })
}
return sourceRanges
}) })
} }

View File

@ -65,7 +65,7 @@ const newVar = myVar + 1`
to: [0, 2], to: [0, 2],
from: [0, 0], from: [0, 0],
__geoMeta: { __geoMeta: {
sourceRange: [72, 97], sourceRange: [72, 97, 0],
id: expect.any(String), id: expect.any(String),
}, },
tag: { tag: {
@ -81,7 +81,7 @@ const newVar = myVar + 1`
from: [0, 2], from: [0, 2],
tag: null, tag: null,
__geoMeta: { __geoMeta: {
sourceRange: [103, 119], sourceRange: [103, 119, 0],
id: expect.any(String), id: expect.any(String),
}, },
}, },
@ -90,7 +90,7 @@ const newVar = myVar + 1`
to: [5, -1], to: [5, -1],
from: [2, 3], from: [2, 3],
__geoMeta: { __geoMeta: {
sourceRange: [125, 154], sourceRange: [125, 154, 0],
id: expect.any(String), id: expect.any(String),
}, },
tag: { tag: {
@ -160,14 +160,14 @@ const newVar = myVar + 1`
tag: null, tag: null,
__geoMeta: { __geoMeta: {
id: expect.any(String), id: expect.any(String),
sourceRange: [39, 63], sourceRange: [39, 63, 0],
}, },
}, },
tags: { tags: {
myPath: { myPath: {
__meta: [ __meta: [
{ {
sourceRange: [109, 116], sourceRange: [109, 116, 0],
}, },
], ],
type: 'TagIdentifier', type: 'TagIdentifier',
@ -182,7 +182,7 @@ const newVar = myVar + 1`
from: [0, 0], from: [0, 0],
tag: null, tag: null,
__geoMeta: { __geoMeta: {
sourceRange: [69, 85], sourceRange: [69, 85, 0],
id: expect.any(String), id: expect.any(String),
}, },
}, },
@ -191,7 +191,7 @@ const newVar = myVar + 1`
to: [0, 1], to: [0, 1],
from: [1, 1], from: [1, 1],
__geoMeta: { __geoMeta: {
sourceRange: [91, 117], sourceRange: [91, 117, 0],
id: expect.any(String), id: expect.any(String),
}, },
tag: { tag: {
@ -207,15 +207,15 @@ const newVar = myVar + 1`
from: [0, 1], from: [0, 1],
tag: null, tag: null,
__geoMeta: { __geoMeta: {
sourceRange: [123, 139], sourceRange: [123, 139, 0],
id: expect.any(String), id: expect.any(String),
}, },
}, },
], ],
id: expect.any(String), id: expect.any(String),
__meta: [{ sourceRange: [39, 63] }], __meta: [{ sourceRange: [39, 63, 0] }],
}, },
__meta: [{ sourceRange: [39, 63] }], __meta: [{ sourceRange: [39, 63, 0] }],
}) })
}) })
it('execute array expression', async () => { it('execute array expression', async () => {
@ -229,7 +229,7 @@ const newVar = myVar + 1`
value: 3, value: 3,
__meta: [ __meta: [
{ {
sourceRange: [14, 15], sourceRange: [14, 15, 0],
}, },
], ],
}) })
@ -238,7 +238,7 @@ const newVar = myVar + 1`
value: [1, '2', 3, 9], value: [1, '2', 3, 9],
__meta: [ __meta: [
{ {
sourceRange: [27, 49], sourceRange: [27, 49, 0],
}, },
], ],
}) })
@ -257,7 +257,7 @@ const newVar = myVar + 1`
value: { aStr: 'str', anum: 2, identifier: 3, binExp: 9 }, value: { aStr: 'str', anum: 2, identifier: 3, binExp: 9 },
__meta: [ __meta: [
{ {
sourceRange: [27, 83], sourceRange: [27, 83, 0],
}, },
], ],
}) })
@ -272,7 +272,7 @@ const newVar = myVar + 1`
value: '123', value: '123',
__meta: [ __meta: [
{ {
sourceRange: [41, 50], sourceRange: [41, 50, 0],
}, },
], ],
}) })
@ -426,7 +426,7 @@ const theExtrude = startSketchOn('XY')
new KCLError( new KCLError(
'undefined_value', 'undefined_value',
'memory item key `myVarZ` is not defined', 'memory item key `myVarZ` is not defined',
[[129, 135]] [[129, 135, 0]]
) )
) )
}) })

View File

@ -0,0 +1,87 @@
import { parse, initPromise, programMemoryInit } from './wasm'
import { enginelessExecutor } from '../lib/testHelpers'
import { assert } from 'vitest'
// These unit tests makes web requests to a public github repository.
interface KclSampleFile {
file: string
title: string
filename: string
description: string
}
beforeAll(async () => {
await initPromise
})
// Only used to actually fetch an older version of KCL code that will break in the parser.
/* eslint-disable @typescript-eslint/no-unused-vars */
async function getBrokenSampleCodeForLocalTesting() {
const result = await fetch(
'https://raw.githubusercontent.com/KittyCAD/kcl-samples/5ccd04a1773ebdbfd02684057917ce5dbe0eaab3/80-20-rail.kcl'
)
const text = await result.text()
return text
}
async function getKclSampleCodeFromGithub(file: string): Promise<string> {
const result = await fetch(
`https://raw.githubusercontent.com/KittyCAD/kcl-samples/refs/heads/main/${file}/${file}.kcl`
)
const text = await result.text()
return text
}
async function getFileNamesFromManifestJSON(): Promise<KclSampleFile[]> {
const result = await fetch(
'https://raw.githubusercontent.com/KittyCAD/kcl-samples/refs/heads/main/manifest.json'
)
const json = await result.json()
json.forEach((file: KclSampleFile) => {
const filenameWithoutExtension = file.file.split('.')[0]
file.filename = filenameWithoutExtension
})
return json
}
// Value to use across all tests!
let files: KclSampleFile[] = []
describe('Test KCL Samples from public Github repository', () => {
describe('When parsing source code', () => {
// THIS RUNS ACROSS OTHER TESTS!
it('should fetch files', async () => {
files = await getFileNamesFromManifestJSON()
})
// Run through all of the files in the manifest json. This will allow us to be automatically updated
// with the latest changes in github. We won't be hard coding the filenames
it(
'should run through all the files',
async () => {
for (let i = 0; i < files.length; i++) {
const file: KclSampleFile = files[i]
const code = await getKclSampleCodeFromGithub(file.filename)
const parsed = parse(code)
assert(!(parsed instanceof Error))
}
},
files.length * 1000
)
})
describe('when performing enginelessExecutor', () => {
it(
'should run through all the files',
async () => {
for (let i = 0; i < files.length; i++) {
const file: KclSampleFile = files[i]
const code = await getKclSampleCodeFromGithub(file.filename)
const parsed = parse(code)
assert(!(parsed instanceof Error))
await enginelessExecutor(parsed, programMemoryInit())
}
},
files.length * 1000
)
})
})

View File

@ -101,15 +101,15 @@ describe('Testing findUniqueName', () => {
it('should find a unique name', () => { it('should find a unique name', () => {
const result = findUniqueName( const result = findUniqueName(
JSON.stringify([ JSON.stringify([
{ type: 'Identifier', name: 'yo01', start: 0, end: 0 }, { type: 'Identifier', name: 'yo01', start: 0, end: 0, moduleId: 0 },
{ type: 'Identifier', name: 'yo02', start: 0, end: 0 }, { type: 'Identifier', name: 'yo02', start: 0, end: 0, moduleId: 0 },
{ type: 'Identifier', name: 'yo03', start: 0, end: 0 }, { type: 'Identifier', name: 'yo03', start: 0, end: 0, moduleId: 0 },
{ type: 'Identifier', name: 'yo04', start: 0, end: 0 }, { type: 'Identifier', name: 'yo04', start: 0, end: 0, moduleId: 0 },
{ type: 'Identifier', name: 'yo05', start: 0, end: 0 }, { type: 'Identifier', name: 'yo05', start: 0, end: 0, moduleId: 0 },
{ type: 'Identifier', name: 'yo06', start: 0, end: 0 }, { type: 'Identifier', name: 'yo06', start: 0, end: 0, moduleId: 0 },
{ type: 'Identifier', name: 'yo07', start: 0, end: 0 }, { type: 'Identifier', name: 'yo07', start: 0, end: 0, moduleId: 0 },
{ type: 'Identifier', name: 'yo08', start: 0, end: 0 }, { type: 'Identifier', name: 'yo08', start: 0, end: 0, moduleId: 0 },
{ type: 'Identifier', name: 'yo09', start: 0, end: 0 }, { type: 'Identifier', name: 'yo09', start: 0, end: 0, moduleId: 0 },
] satisfies Node<Identifier>[]), ] satisfies Node<Identifier>[]),
'yo', 'yo',
2 2
@ -124,6 +124,7 @@ describe('Testing addSketchTo', () => {
body: [], body: [],
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0,
nonCodeMeta: { nonCodeNodes: {}, startNodes: [] }, nonCodeMeta: { nonCodeNodes: {}, startNodes: [] },
}, },
'yz' 'yz'

View File

@ -242,6 +242,7 @@ export function mutateObjExpProp(
value: updateWith, value: updateWith,
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0,
}) })
} }
} }
@ -577,6 +578,7 @@ export function createLiteral(value: string | number): Node<Literal> {
type: 'Literal', type: 'Literal',
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0,
value, value,
raw: `${value}`, raw: `${value}`,
} }
@ -587,6 +589,7 @@ export function createTagDeclarator(value: string): Node<TagDeclarator> {
type: 'TagDeclarator', type: 'TagDeclarator',
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0,
value, value,
} }
@ -597,6 +600,7 @@ export function createIdentifier(name: string): Node<Identifier> {
type: 'Identifier', type: 'Identifier',
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0,
name, name,
} }
@ -607,6 +611,7 @@ export function createPipeSubstitution(): Node<PipeSubstitution> {
type: 'PipeSubstitution', type: 'PipeSubstitution',
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0,
} }
} }
@ -618,10 +623,12 @@ export function createCallExpressionStdLib(
type: 'CallExpression', type: 'CallExpression',
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0,
callee: { callee: {
type: 'Identifier', type: 'Identifier',
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0,
name, name,
}, },
@ -638,10 +645,12 @@ export function createCallExpression(
type: 'CallExpression', type: 'CallExpression',
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0,
callee: { callee: {
type: 'Identifier', type: 'Identifier',
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0,
name, name,
}, },
@ -657,6 +666,7 @@ export function createArrayExpression(
type: 'ArrayExpression', type: 'ArrayExpression',
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0,
nonCodeMeta: nonCodeMetaEmpty(), nonCodeMeta: nonCodeMetaEmpty(),
elements, elements,
@ -670,6 +680,7 @@ export function createPipeExpression(
type: 'PipeExpression', type: 'PipeExpression',
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0,
body, body,
nonCodeMeta: nonCodeMetaEmpty(), nonCodeMeta: nonCodeMetaEmpty(),
@ -686,12 +697,14 @@ export function createVariableDeclaration(
type: 'VariableDeclaration', type: 'VariableDeclaration',
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0,
declarations: [ declarations: [
{ {
type: 'VariableDeclarator', type: 'VariableDeclarator',
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0,
id: createIdentifier(varName), id: createIdentifier(varName),
init, init,
@ -709,12 +722,14 @@ export function createObjectExpression(properties: {
type: 'ObjectExpression', type: 'ObjectExpression',
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0,
nonCodeMeta: nonCodeMetaEmpty(), nonCodeMeta: nonCodeMetaEmpty(),
properties: Object.entries(properties).map(([key, value]) => ({ properties: Object.entries(properties).map(([key, value]) => ({
type: 'ObjectProperty', type: 'ObjectProperty',
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0,
key: createIdentifier(key), key: createIdentifier(key),
value, value,
@ -730,6 +745,7 @@ export function createUnaryExpression(
type: 'UnaryExpression', type: 'UnaryExpression',
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0,
operator, operator,
argument, argument,
@ -745,6 +761,7 @@ export function createBinaryExpression([left, operator, right]: [
type: 'BinaryExpression', type: 'BinaryExpression',
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0,
operator, operator,
left, left,

View File

@ -13,6 +13,7 @@ Map {
"range": [ "range": [
37, 37,
64, 64,
0,
], ],
}, },
"pathIds": [ "pathIds": [
@ -31,6 +32,7 @@ Map {
"range": [ "range": [
37, 37,
64, 64,
0,
], ],
}, },
"planeId": "UUID", "planeId": "UUID",
@ -56,6 +58,7 @@ Map {
"range": [ "range": [
70, 70,
86, 86,
0,
], ],
}, },
"edgeIds": [ "edgeIds": [
@ -77,6 +80,7 @@ Map {
"range": [ "range": [
92, 92,
119, 119,
0,
], ],
}, },
"edgeCutId": "UUID", "edgeCutId": "UUID",
@ -99,6 +103,7 @@ Map {
"range": [ "range": [
125, 125,
150, 150,
0,
], ],
}, },
"edgeIds": [ "edgeIds": [
@ -120,6 +125,7 @@ Map {
"range": [ "range": [
156, 156,
203, 203,
0,
], ],
}, },
"edgeIds": [ "edgeIds": [
@ -141,6 +147,7 @@ Map {
"range": [ "range": [
209, 209,
217, 217,
0,
], ],
}, },
"edgeIds": [], "edgeIds": [],
@ -162,6 +169,7 @@ Map {
"range": [ "range": [
231, 231,
254, 254,
0,
], ],
}, },
"edgeIds": [ "edgeIds": [
@ -289,6 +297,7 @@ Map {
"range": [ "range": [
260, 260,
299, 299,
0,
], ],
}, },
"consumedEdgeId": "UUID", "consumedEdgeId": "UUID",
@ -307,6 +316,7 @@ Map {
"range": [ "range": [
350, 350,
377, 377,
0,
], ],
}, },
"planeId": "UUID", "planeId": "UUID",
@ -331,6 +341,7 @@ Map {
"range": [ "range": [
383, 383,
398, 398,
0,
], ],
}, },
"edgeIds": [ "edgeIds": [
@ -352,6 +363,7 @@ Map {
"range": [ "range": [
404, 404,
420, 420,
0,
], ],
}, },
"edgeIds": [ "edgeIds": [
@ -373,6 +385,7 @@ Map {
"range": [ "range": [
426, 426,
473, 473,
0,
], ],
}, },
"edgeIds": [ "edgeIds": [
@ -394,6 +407,7 @@ Map {
"range": [ "range": [
479, 479,
487, 487,
0,
], ],
}, },
"edgeIds": [], "edgeIds": [],
@ -415,6 +429,7 @@ Map {
"range": [ "range": [
501, 501,
522, 522,
0,
], ],
}, },
"edgeIds": [ "edgeIds": [

View File

@ -49,6 +49,26 @@ sketch002 = startSketchOn(extrude001, seg02)
extrude002 = extrude(5, sketch002) extrude002 = extrude(5, sketch002)
` `
const exampleCodeNo3D = `sketch003 = startSketchOn('YZ')
|> startProfileAt([5.82, 0], %)
|> angledLine([180, 11.54], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001) - 90,
8.21
], %, $rectangleSegmentB001)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %, $rectangleSegmentC001)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
sketch004 = startSketchOn('-XZ')
|> startProfileAt([0, 14.36], %)
|> line([15.49, 0.05], %)
|> tangentialArcTo([0, 0], %)
|> tangentialArcTo([-6.8, 8.17], %)
`
const sketchOnFaceOnFaceEtc = `sketch001 = startSketchOn('XZ') const sketchOnFaceOnFaceEtc = `sketch001 = startSketchOn('XZ')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> line([4, 8], %) |> line([4, 8], %)
@ -83,6 +103,7 @@ extrude004 = extrude(3, sketch004)
const codeToWriteCacheFor = { const codeToWriteCacheFor = {
exampleCode1, exampleCode1,
sketchOnFaceOnFaceEtc, sketchOnFaceOnFaceEtc,
exampleCodeNo3D,
} as const } as const
type CodeKey = keyof typeof codeToWriteCacheFor type CodeKey = keyof typeof codeToWriteCacheFor
@ -236,6 +257,69 @@ describe('testing createArtifactGraph', () => {
await GraphTheGraph(theMap, 2000, 2000, 'exampleCode1.png') await GraphTheGraph(theMap, 2000, 2000, 'exampleCode1.png')
}, 20000) }, 20000)
}) })
describe(`code with sketches but no extrusions or other 3D elements`, () => {
let ast: Program
let theMap: ReturnType<typeof createArtifactGraph>
it(`setup`, () => {
// putting this logic in here because describe blocks runs before beforeAll has finished
const {
orderedCommands,
responseMap,
ast: _ast,
} = getCommands('exampleCodeNo3D')
ast = _ast
theMap = createArtifactGraph({ orderedCommands, responseMap, ast })
})
it('there should be two planes, one for each sketch path', () => {
const planes = [...filterArtifacts({ types: ['plane'] }, theMap)].map(
(plane) => expandPlane(plane[1], theMap)
)
expect(planes).toHaveLength(2)
planes.forEach((path) => {
expect(path.type).toBe('plane')
})
})
it('there should be two paths, one on each plane', () => {
const paths = [...filterArtifacts({ types: ['path'] }, theMap)].map(
(path) => expandPath(path[1], theMap)
)
expect(paths).toHaveLength(2)
paths.forEach((path) => {
if (err(path)) throw path
expect(path.type).toBe('path')
})
})
it(`there should be 1 solid2D, just for the first closed path`, () => {
const solid2Ds = [...filterArtifacts({ types: ['solid2D'] }, theMap)]
expect(solid2Ds).toHaveLength(1)
})
it('there should be no extrusions', () => {
const extrusions = [...filterArtifacts({ types: ['sweep'] }, theMap)].map(
(extrusion) => expandSweep(extrusion[1], theMap)
)
expect(extrusions).toHaveLength(0)
})
it('there should be 8 segments, 4 + 1 (close) from the first sketch and 3 from the second', () => {
const segments = [...filterArtifacts({ types: ['segment'] }, theMap)].map(
(segment) => expandSegment(segment[1], theMap)
)
expect(segments).toHaveLength(8)
})
it('screenshot graph', async () => {
// Ostensibly this takes a screen shot of the graph of the artifactGraph
// but it's it also tests that all of the id links are correct because if one
// of the edges refers to a non-existent node, the graph will throw.
// further more we can check that each edge is bi-directional, if it's not
// by checking the arrow heads going both ways, on the graph.
await GraphTheGraph(theMap, 2000, 2000, 'exampleCodeNo3D.png')
}, 20000)
})
}) })
describe('capture graph of sketchOnFaceOnFace...', () => { describe('capture graph of sketchOnFaceOnFace...', () => {
@ -457,7 +541,10 @@ async function GraphTheGraph(
`./src/lang/std/artifactMapGraphs/${imageName}` `./src/lang/std/artifactMapGraphs/${imageName}`
) )
// chop the top 30 pixels off the image // chop the top 30 pixels off the image
const originalImg = PNG.sync.read(fs.readFileSync(originalImgPath)) const originalImgExists = fs.existsSync(originalImgPath)
const originalImg = originalImgExists
? PNG.sync.read(fs.readFileSync(originalImgPath))
: null
// const img1Data = new Uint8Array(img1.data) // const img1Data = new Uint8Array(img1.data)
// const img1DataChopped = img1Data.slice(30 * img1.width * 4) // const img1DataChopped = img1Data.slice(30 * img1.width * 4)
// img1.data = Buffer.from(img1DataChopped) // img1.data = Buffer.from(img1DataChopped)
@ -468,10 +555,10 @@ async function GraphTheGraph(
const newImageDataChopped = newImageData.slice(30 * newImage.width * 4) const newImageDataChopped = newImageData.slice(30 * newImage.width * 4)
newImage.data = Buffer.from(newImageDataChopped) newImage.data = Buffer.from(newImageDataChopped)
const { width, height } = originalImg const { width, height } = originalImg ?? newImage
const diff = new PNG({ width, height }) const diff = new PNG({ width, height })
const imageSizeDifferent = originalImg.data.length !== newImage.data.length const imageSizeDifferent = originalImg?.data.length !== newImage.data.length
let numDiffPixels = 0 let numDiffPixels = 0
if (!imageSizeDifferent) { if (!imageSizeDifferent) {
numDiffPixels = pixelmatch( numDiffPixels = pixelmatch(
@ -523,7 +610,7 @@ describe('testing getArtifactsToUpdate', () => {
sweepId: '', sweepId: '',
codeRef: { codeRef: {
pathToNode: [['body', '']], pathToNode: [['body', '']],
range: [37, 64], range: [37, 64, 0],
}, },
}, },
]) ])
@ -535,7 +622,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceIds: [], surfaceIds: [],
edgeIds: [], edgeIds: [],
codeRef: { codeRef: {
range: [231, 254], range: [231, 254, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },
@ -545,7 +632,7 @@ describe('testing getArtifactsToUpdate', () => {
planeId: expect.any(String), planeId: expect.any(String),
sweepId: expect.any(String), sweepId: expect.any(String),
codeRef: { codeRef: {
range: [37, 64], range: [37, 64, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
solid2dId: expect.any(String), solid2dId: expect.any(String),
@ -558,7 +645,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceId: '', surfaceId: '',
edgeIds: [], edgeIds: [],
codeRef: { codeRef: {
range: [70, 86], range: [70, 86, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },
@ -568,7 +655,7 @@ describe('testing getArtifactsToUpdate', () => {
planeId: expect.any(String), planeId: expect.any(String),
sweepId: expect.any(String), sweepId: expect.any(String),
codeRef: { codeRef: {
range: [37, 64], range: [37, 64, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
solid2dId: expect.any(String), solid2dId: expect.any(String),
@ -582,7 +669,7 @@ describe('testing getArtifactsToUpdate', () => {
edgeIds: [], edgeIds: [],
surfaceId: '', surfaceId: '',
codeRef: { codeRef: {
range: [260, 299], range: [260, 299, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },
@ -592,7 +679,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceId: expect.any(String), surfaceId: expect.any(String),
edgeIds: expect.any(Array), edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [92, 119], range: [92, 119, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
edgeCutId: expect.any(String), edgeCutId: expect.any(String),
@ -612,7 +699,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceId: expect.any(String), surfaceId: expect.any(String),
edgeIds: expect.any(Array), edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [156, 203], range: [156, 203, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },
@ -623,7 +710,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: expect.any(Array), edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [231, 254], range: [231, 254, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },
@ -640,7 +727,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceId: expect.any(String), surfaceId: expect.any(String),
edgeIds: expect.any(Array), edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [125, 150], range: [125, 150, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },
@ -651,7 +738,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: expect.any(Array), edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [231, 254], range: [231, 254, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },
@ -668,7 +755,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceId: expect.any(String), surfaceId: expect.any(String),
edgeIds: expect.any(Array), edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [92, 119], range: [92, 119, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
edgeCutId: expect.any(String), edgeCutId: expect.any(String),
@ -680,7 +767,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: expect.any(Array), edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [231, 254], range: [231, 254, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },
@ -697,7 +784,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceId: expect.any(String), surfaceId: expect.any(String),
edgeIds: expect.any(Array), edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [70, 86], range: [70, 86, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },
@ -708,7 +795,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: expect.any(Array), edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [231, 254], range: [231, 254, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },
@ -726,7 +813,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: expect.any(Array), edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [231, 254], range: [231, 254, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },
@ -744,7 +831,7 @@ describe('testing getArtifactsToUpdate', () => {
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: expect.any(Array), edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [231, 254], range: [231, 254, 0],
pathToNode: [['body', '']], pathToNode: [['body', '']],
}, },
}, },

View File

@ -36,9 +36,12 @@ interface solid2D {
} }
export interface PathArtifactRich { export interface PathArtifactRich {
type: 'path' type: 'path'
/** A path must always lie on a plane */
plane: PlaneArtifact | WallArtifact plane: PlaneArtifact | WallArtifact
/** A path must always contain 0 or more segments */
segments: Array<SegmentArtifact> segments: Array<SegmentArtifact>
sweep: SweepArtifact /** A path may not result in a sweep artifact */
sweep?: SweepArtifact
codeRef: CommonCommandProperties codeRef: CommonCommandProperties
} }
@ -587,13 +590,15 @@ export function expandPath(
{ keys: path.segIds, types: ['segment'] }, { keys: path.segIds, types: ['segment'] },
artifactGraph artifactGraph
) )
const sweep = getArtifactOfTypes( const sweep = path.sweepId
{ ? getArtifactOfTypes(
key: path.sweepId, {
types: ['sweep'], key: path.sweepId,
}, types: ['sweep'],
artifactGraph },
) artifactGraph
)
: undefined
const plane = getArtifactOfTypes( const plane = getArtifactOfTypes(
{ key: path.planeId, types: ['plane', 'wall'] }, { key: path.planeId, types: ['plane', 'wall'] },
artifactGraph artifactGraph

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

View File

@ -1823,11 +1823,13 @@ export const updateStartProfileAtArgs: SketchLineHelper['updateArgs'] = ({
modifiedAst: { modifiedAst: {
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0,
body: [], body: [],
nonCodeMeta: { nonCodeMeta: {
start: 0, start: 0,
end: 0, end: 0,
moduleId: 0,
startNodes: [], startNodes: [],
nonCodeNodes: [], nonCodeNodes: [],
}, },

View File

@ -120,8 +120,8 @@ const initialise = async () => {
export const initPromise = initialise() export const initPromise = initialise()
export const rangeTypeFix = (ranges: number[][]): [number, number][] => export const rangeTypeFix = (ranges: number[][]): [number, number, number][] =>
ranges.map(([start, end]) => [start, end]) ranges.map(([start, end, moduleId]) => [start, end, moduleId])
export const parse = (code: string | Error): Node<Program> | Error => { export const parse = (code: string | Error): Node<Program> | Error => {
if (err(code)) return code if (err(code)) return code

View File

@ -5,7 +5,6 @@ import {
INDEX_IDENTIFIER, INDEX_IDENTIFIER,
MAX_PADDING, MAX_PADDING,
ONBOARDING_PROJECT_NAME, ONBOARDING_PROJECT_NAME,
PROJECT_ENTRYPOINT,
} from 'lib/constants' } from 'lib/constants'
import { bracket } from './exampleKcl' import { bracket } from './exampleKcl'
import { PATHS } from './paths' import { PATHS } from './paths'
@ -22,36 +21,20 @@ export const isHidden = (fileOrDir: FileEntry) =>
export const isDir = (fileOrDir: FileEntry) => export const isDir = (fileOrDir: FileEntry) =>
'children' in fileOrDir && fileOrDir.children !== undefined 'children' in fileOrDir && fileOrDir.children !== undefined
// Deeply sort the files and directories in a project like VS Code does: // Shallow sort the files and directories
// The main.kcl file is always first, then files, then directories
// Files and directories are sorted alphabetically // Files and directories are sorted alphabetically
export function sortProject(project: FileEntry[]): FileEntry[] { export function sortFilesAndDirectories(files: FileEntry[]): FileEntry[] {
const sortedProject = project.sort((a, b) => { return files.sort((a, b) => {
if (a.name === PROJECT_ENTRYPOINT) { if (a.children === null && b.children !== null) {
return -1
} else if (b.name === PROJECT_ENTRYPOINT) {
return 1 return 1
} else if (a.children === null && b.children !== null) {
return -1
} else if (a.children !== null && b.children === null) { } else if (a.children !== null && b.children === null) {
return 1 return -1
} else if (a.name && b.name) { } else if (a.name && b.name) {
return a.name.localeCompare(b.name) return a.name.localeCompare(b.name)
} else { } else {
return 0 return 0
} }
}) })
return sortedProject.map((fileOrDir: FileEntry) => {
if ('children' in fileOrDir && fileOrDir.children !== null) {
return {
...fileOrDir,
children: sortProject(fileOrDir.children || []),
}
} else {
return fileOrDir
}
})
} }
// create a regex to match the project name // create a regex to match the project name

View File

@ -178,6 +178,7 @@ export async function loadAndValidateSettings(
if (err(appSettingsPayload)) return Promise.reject(appSettingsPayload) if (err(appSettingsPayload)) return Promise.reject(appSettingsPayload)
let settingsNext = createSettings() let settingsNext = createSettings()
// Because getting the default directory is async, we need to set it after // Because getting the default directory is async, we need to set it after
if (onDesktop) { if (onDesktop) {
settings.app.projectDirectory.default = await getInitialDefaultDir() settings.app.projectDirectory.default = await getInitialDefaultDir()

485
src/wasm-lib/Cargo.lock generated
View File

@ -121,9 +121,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.91" version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
dependencies = [ dependencies = [
"backtrace", "backtrace",
] ]
@ -165,7 +165,7 @@ dependencies = [
"futures-sink", "futures-sink",
"log", "log",
"pin-project-lite", "pin-project-lite",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -176,7 +176,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -187,7 +187,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -204,7 +204,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -465,7 +465,7 @@ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -656,7 +656,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -667,7 +667,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -722,7 +722,7 @@ checksum = "4078275de501a61ceb9e759d37bdd3d7210e654dbc167ac1a3678ef4435ed57b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
"synstructure", "synstructure",
] ]
@ -751,7 +751,7 @@ dependencies = [
"rustfmt-wrapper", "rustfmt-wrapper",
"serde", "serde",
"serde_tokenstream", "serde_tokenstream",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -762,7 +762,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -789,7 +789,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -827,7 +827,7 @@ checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -988,7 +988,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -1084,7 +1084,7 @@ dependencies = [
"inflections", "inflections",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -1140,7 +1140,7 @@ dependencies = [
"pest_derive", "pest_derive",
"serde", "serde",
"serde_json", "serde_json",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -1377,6 +1377,124 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "icu_collections"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locid"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_locid_transform"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
dependencies = [
"displaydoc",
"icu_locid",
"icu_locid_transform_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_locid_transform_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
[[package]]
name = "icu_normalizer"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"write16",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
[[package]]
name = "icu_properties"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locid_transform",
"icu_properties_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
[[package]]
name = "icu_provider"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
dependencies = [
"displaydoc",
"icu_locid",
"icu_provider_macros",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_provider_macros"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]] [[package]]
name = "ident_case" name = "ident_case"
version = "1.0.1" version = "1.0.1"
@ -1385,12 +1503,23 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]] [[package]]
name = "idna" name = "idna"
version = "0.5.0" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
dependencies = [ dependencies = [
"unicode-bidi", "idna_adapter",
"unicode-normalization", "smallvec",
"utf8_iter",
]
[[package]]
name = "idna_adapter"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
dependencies = [
"icu_normalizer",
"icu_properties",
] ]
[[package]] [[package]]
@ -1414,7 +1543,7 @@ dependencies = [
"image", "image",
"itertools 0.12.1", "itertools 0.12.1",
"rayon", "rayon",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -1453,9 +1582,9 @@ checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a"
[[package]] [[package]]
name = "insta" name = "insta"
version = "1.41.0" version = "1.41.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f72d3e19488cf7d8ea52d2fc0f8754fc933398b337cd3cbdb28aaeb35159ef" checksum = "7e9ffc4d4892617c50a928c52b2961cb5174b6fc6ebf252b2fac9d21955c48b8"
dependencies = [ dependencies = [
"console", "console",
"lazy_static", "lazy_static",
@ -1587,7 +1716,7 @@ dependencies = [
"serde_json", "serde_json",
"sha2", "sha2",
"tabled", "tabled",
"thiserror", "thiserror 2.0.0",
"tokio", "tokio",
"tokio-tungstenite", "tokio-tungstenite",
"toml", "toml",
@ -1614,7 +1743,7 @@ dependencies = [
"pretty_assertions", "pretty_assertions",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -1677,7 +1806,7 @@ dependencies = [
"serde_bytes", "serde_bytes",
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
"thiserror", "thiserror 1.0.68",
"tokio", "tokio",
"tracing", "tracing",
"url", "url",
@ -1686,9 +1815,9 @@ dependencies = [
[[package]] [[package]]
name = "kittycad-modeling-cmds" name = "kittycad-modeling-cmds"
version = "0.2.71" version = "0.2.72"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6d2160dcb0e5373b1242a760dbf17fb9c12de523c410c11b145381c852b377b" checksum = "e41880dbe19df3150992988f438c9ba9022ea822d6e4e5ed53d28965de198ec7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -1718,7 +1847,7 @@ dependencies = [
"kittycad-modeling-cmds-macros-impl", "kittycad-modeling-cmds-macros-impl",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -1729,7 +1858,7 @@ checksum = "6607507a8a0e4273b943179f0a3ef8e90712308d1d3095246040c29cfdbf985b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -1774,6 +1903,12 @@ version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "litemap"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.12" version = "0.4.12"
@ -2016,7 +2151,7 @@ dependencies = [
"js-sys", "js-sys",
"once_cell", "once_cell",
"pin-project-lite", "pin-project-lite",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -2034,7 +2169,7 @@ dependencies = [
"opentelemetry", "opentelemetry",
"percent-encoding", "percent-encoding",
"rand 0.8.5", "rand 0.8.5",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -2129,7 +2264,7 @@ dependencies = [
"regex", "regex",
"regex-syntax 0.8.5", "regex-syntax 0.8.5",
"structmeta", "structmeta",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -2143,7 +2278,7 @@ dependencies = [
"regex", "regex",
"regex-syntax 0.8.5", "regex-syntax 0.8.5",
"structmeta", "structmeta",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -2159,7 +2294,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9"
dependencies = [ dependencies = [
"memchr", "memchr",
"thiserror", "thiserror 1.0.68",
"ucd-trie", "ucd-trie",
] ]
@ -2183,7 +2318,7 @@ dependencies = [
"pest_meta", "pest_meta",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -2215,7 +2350,7 @@ dependencies = [
"serde", "serde",
"serde_derive", "serde_derive",
"strum", "strum",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -2241,7 +2376,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -2352,6 +2487,28 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "proc-macro-error2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.89" version = "1.0.89"
@ -2363,9 +2520,9 @@ dependencies = [
[[package]] [[package]]
name = "pyo3" name = "pyo3"
version = "0.22.5" version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d922163ba1f79c04bc49073ba7b32fd5a8d3b76a87c955921234b8e77333c51" checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"indoc", "indoc",
@ -2381,9 +2538,9 @@ dependencies = [
[[package]] [[package]]
name = "pyo3-build-config" name = "pyo3-build-config"
version = "0.22.5" version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc38c5feeb496c8321091edf3d63e9a6829eab4b863b4a6a65f26f3e9cc6b179" checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"target-lexicon", "target-lexicon",
@ -2391,9 +2548,9 @@ dependencies = [
[[package]] [[package]]
name = "pyo3-ffi" name = "pyo3-ffi"
version = "0.22.5" version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94845622d88ae274d2729fcefc850e63d7a3ddff5e3ce11bd88486db9f1d357d" checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636"
dependencies = [ dependencies = [
"libc", "libc",
"pyo3-build-config", "pyo3-build-config",
@ -2401,27 +2558,27 @@ dependencies = [
[[package]] [[package]]
name = "pyo3-macros" name = "pyo3-macros"
version = "0.22.5" version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e655aad15e09b94ffdb3ce3d217acf652e26bbc37697ef012f5e5e348c716e5e" checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"pyo3-macros-backend", "pyo3-macros-backend",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
name = "pyo3-macros-backend" name = "pyo3-macros-backend"
version = "0.22.5" version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae1e3f09eecd94618f60a455a23def79f79eba4dc561a97324bf9ac8c6df30ce" checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe"
dependencies = [ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
"pyo3-build-config", "pyo3-build-config",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -2446,7 +2603,7 @@ dependencies = [
"rustc-hash", "rustc-hash",
"rustls", "rustls",
"socket2", "socket2",
"thiserror", "thiserror 1.0.68",
"tokio", "tokio",
"tracing", "tracing",
] ]
@ -2463,7 +2620,7 @@ dependencies = [
"rustc-hash", "rustc-hash",
"rustls", "rustls",
"slab", "slab",
"thiserror", "thiserror 1.0.68",
"tinyvec", "tinyvec",
"tracing", "tracing",
] ]
@ -2650,9 +2807,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.12.8" version = "0.12.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"bytes", "bytes",
@ -2717,7 +2874,7 @@ dependencies = [
"http 1.1.0", "http 1.1.0",
"reqwest", "reqwest",
"serde", "serde",
"thiserror", "thiserror 1.0.68",
"tower-service", "tower-service",
] ]
@ -2814,7 +2971,7 @@ checksum = "f1adc9dfed5cc999077978cc7163b9282c5751c8d39827c4ea8c8c220ca5a440"
dependencies = [ dependencies = [
"serde", "serde",
"tempfile", "tempfile",
"thiserror", "thiserror 1.0.68",
"toml", "toml",
"toolchain_find", "toolchain_find",
] ]
@ -2926,6 +3083,7 @@ dependencies = [
"chrono", "chrono",
"dyn-clone", "dyn-clone",
"indexmap 1.9.3", "indexmap 1.9.3",
"indexmap 2.6.0",
"schemars_derive", "schemars_derive",
"serde", "serde",
"serde_json", "serde_json",
@ -2942,7 +3100,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde_derive_internals", "serde_derive_internals",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -3006,7 +3164,7 @@ checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -3017,7 +3175,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -3041,7 +3199,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -3062,7 +3220,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde", "serde",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -3178,6 +3336,12 @@ version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]] [[package]]
name = "str_indices" name = "str_indices"
version = "0.4.3" version = "0.4.3"
@ -3199,7 +3363,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"structmeta-derive", "structmeta-derive",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -3210,7 +3374,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -3232,7 +3396,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustversion", "rustversion",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -3254,9 +3418,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.85" version = "2.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -3280,7 +3444,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -3343,22 +3507,42 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.65" version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl 1.0.68",
]
[[package]]
name = "thiserror"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15291287e9bff1bc6f9ff3409ed9af665bec7a5fc8ac079ea96be07bca0e2668"
dependencies = [
"thiserror-impl 2.0.0",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.65" version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
]
[[package]]
name = "thiserror-impl"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22efd00f33f93fa62848a7cab956c3d38c8d43095efda1decfc2b3a5dc0b8972"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
] ]
[[package]] [[package]]
@ -3402,6 +3586,16 @@ dependencies = [
"time-core", "time-core",
] ]
[[package]]
name = "tinystr"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]] [[package]]
name = "tinytemplate" name = "tinytemplate"
version = "1.2.1" version = "1.2.1"
@ -3454,7 +3648,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -3596,7 +3790,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -3624,7 +3818,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -3690,9 +3884,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a2f31991cee3dce1ca4f929a8a04fdd11fd8801aac0f2030b0fa8a0a3fef6b9" checksum = "3a2f31991cee3dce1ca4f929a8a04fdd11fd8801aac0f2030b0fa8a0a3fef6b9"
dependencies = [ dependencies = [
"chrono", "chrono",
"indexmap 2.6.0",
"lazy_static", "lazy_static",
"serde_json", "serde_json",
"thiserror", "thiserror 1.0.68",
"ts-rs-macros", "ts-rs-macros",
"url", "url",
"uuid", "uuid",
@ -3706,7 +3901,7 @@ checksum = "0ea0b99e8ec44abd6f94a18f28f7934437809dd062820797c52401298116f70e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
"termcolor", "termcolor",
] ]
@ -3726,7 +3921,7 @@ dependencies = [
"rustls", "rustls",
"rustls-pki-types", "rustls-pki-types",
"sha1", "sha1",
"thiserror", "thiserror 1.0.68",
"utf-8", "utf-8",
] ]
@ -3763,27 +3958,12 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "unicode-bidi"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.13" version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "unicode-normalization"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
dependencies = [
"tinyvec",
]
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.12.0" version = "1.12.0"
@ -3816,9 +3996,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]] [[package]]
name = "url" name = "url"
version = "2.5.2" version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada"
dependencies = [ dependencies = [
"form_urlencoded", "form_urlencoded",
"idna", "idna",
@ -3838,6 +4018,18 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf16_iter"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.2.2" version = "0.2.2"
@ -3857,9 +4049,9 @@ dependencies = [
[[package]] [[package]]
name = "validator" name = "validator"
version = "0.18.1" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db79c75af171630a3148bd3e6d7c4f42b6a9a014c2945bc5ed0020cbb8d9478e" checksum = "d0b4a29d8709210980a09379f27ee31549b73292c87ab9899beee1c0d3be6303"
dependencies = [ dependencies = [
"idna", "idna",
"once_cell", "once_cell",
@ -3873,16 +4065,16 @@ dependencies = [
[[package]] [[package]]
name = "validator_derive" name = "validator_derive"
version = "0.18.2" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df0bcf92720c40105ac4b2dda2a4ea3aa717d4d6a862cc217da653a4bd5c6b10" checksum = "bac855a2ce6f843beb229757e6e570a42e837bcb15e5f449dd48d5747d41bf77"
dependencies = [ dependencies = [
"darling", "darling",
"once_cell", "once_cell",
"proc-macro-error", "proc-macro-error2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
] ]
[[package]] [[package]]
@ -3944,7 +4136,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -3979,7 +4171,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -4303,6 +4495,18 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "write16"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
[[package]]
name = "writeable"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]] [[package]]
name = "wyz" name = "wyz"
version = "0.5.1" version = "0.5.1"
@ -4327,6 +4531,30 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]]
name = "yoke"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"synstructure",
]
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.7.35" version = "0.7.35"
@ -4345,7 +4573,28 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.87",
]
[[package]]
name = "zerofrom"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"synstructure",
] ]
[[package]] [[package]]
@ -4354,6 +4603,28 @@ version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
[[package]]
name = "zerovec"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]] [[package]]
name = "zip" name = "zip"
version = "2.2.0" version = "2.2.0"
@ -4366,5 +4637,5 @@ dependencies = [
"displaydoc", "displaydoc",
"indexmap 2.6.0", "indexmap 2.6.0",
"memchr", "memchr",
"thiserror", "thiserror 1.0.68",
] ]

View File

@ -76,7 +76,7 @@ members = [
[workspace.dependencies] [workspace.dependencies]
http = "1" http = "1"
kittycad = { version = "0.3.25", default-features = false, features = ["js", "requests"] } kittycad = { version = "0.3.25", default-features = false, features = ["js", "requests"] }
kittycad-modeling-cmds = { version = "0.2.71", features = ["websocket"] } kittycad-modeling-cmds = { version = "0.2.72", features = ["websocket"] }
[[test]] [[test]]
name = "executor" name = "executor"

View File

@ -20,10 +20,10 @@ quote = "1"
regex = "1.11" regex = "1.11"
serde = { version = "1.0.214", features = ["derive"] } serde = { version = "1.0.214", features = ["derive"] }
serde_tokenstream = "0.2" serde_tokenstream = "0.2"
syn = { version = "2.0.85", features = ["full"] } syn = { version = "2.0.87", features = ["full"] }
[dev-dependencies] [dev-dependencies]
anyhow = "1.0.91" anyhow = "1.0.93"
expectorate = "1.1.0" expectorate = "1.1.0"
pretty_assertions = "1.4.1" pretty_assertions = "1.4.1"
rustfmt-wrapper = "0.2.1" rustfmt-wrapper = "0.2.1"

View File

@ -173,9 +173,7 @@ fn do_stdlib_inner(
quote! { quote! {
let code_blocks = vec![#(#cb),*]; let code_blocks = vec![#(#cb),*];
code_blocks.iter().map(|cb| { code_blocks.iter().map(|cb| {
let tokens = crate::token::lexer(cb).unwrap(); let program = crate::parser::top_level_parse(cb).unwrap();
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default(); let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false; options.insert_final_newline = false;
@ -750,9 +748,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
quote! { quote! {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn #test_name_mock() { async fn #test_name_mock() {
let tokens = crate::token::lexer(#code_block).unwrap(); let program = crate::parser::top_level_parse(#code_block).unwrap();
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default(); let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await.unwrap())), engine: std::sync::Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await.unwrap())),

View File

@ -2,9 +2,7 @@
mod test_examples_someFn { mod test_examples_someFn {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_someFn0() { async fn test_mock_example_someFn0() {
let tokens = crate::token::lexer("someFn()").unwrap(); let program = crate::parser::top_level_parse("someFn()").unwrap();
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default(); let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
@ -113,9 +111,7 @@ impl crate::docs::StdLibFn for SomeFn {
code_blocks code_blocks
.iter() .iter()
.map(|cb| { .map(|cb| {
let tokens = crate::token::lexer(cb).unwrap(); let program = crate::parser::top_level_parse(cb).unwrap();
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default(); let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false; options.insert_final_newline = false;
program.recast(&options, 0) program.recast(&options, 0)

View File

@ -2,9 +2,7 @@
mod test_examples_someFn { mod test_examples_someFn {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_someFn0() { async fn test_mock_example_someFn0() {
let tokens = crate::token::lexer("someFn()").unwrap(); let program = crate::parser::top_level_parse("someFn()").unwrap();
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default(); let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
@ -113,9 +111,7 @@ impl crate::docs::StdLibFn for SomeFn {
code_blocks code_blocks
.iter() .iter()
.map(|cb| { .map(|cb| {
let tokens = crate::token::lexer(cb).unwrap(); let program = crate::parser::top_level_parse(cb).unwrap();
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default(); let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false; options.insert_final_newline = false;
program.recast(&options, 0) program.recast(&options, 0)

View File

@ -2,9 +2,9 @@
mod test_examples_show { mod test_examples_show {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show0() { async fn test_mock_example_show0() {
let tokens = crate::token::lexer("This is another code block.\nyes sirrr.\nshow").unwrap(); let program =
let parser = crate::parser::Parser::new(tokens); crate::parser::top_level_parse("This is another code block.\nyes sirrr.\nshow")
let program = parser.ast().unwrap(); .unwrap();
let id_generator = crate::executor::IdGenerator::default(); let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
@ -36,9 +36,8 @@ mod test_examples_show {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show1() { async fn test_mock_example_show1() {
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nshow").unwrap(); let program =
let parser = crate::parser::Parser::new(tokens); crate::parser::top_level_parse("This is code.\nIt does other shit.\nshow").unwrap();
let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default(); let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
@ -150,9 +149,7 @@ impl crate::docs::StdLibFn for Show {
code_blocks code_blocks
.iter() .iter()
.map(|cb| { .map(|cb| {
let tokens = crate::token::lexer(cb).unwrap(); let program = crate::parser::top_level_parse(cb).unwrap();
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default(); let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false; options.insert_final_newline = false;
program.recast(&options, 0) program.recast(&options, 0)

View File

@ -2,9 +2,8 @@
mod test_examples_show { mod test_examples_show {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show0() { async fn test_mock_example_show0() {
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nshow").unwrap(); let program =
let parser = crate::parser::Parser::new(tokens); crate::parser::top_level_parse("This is code.\nIt does other shit.\nshow").unwrap();
let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default(); let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
@ -113,9 +112,7 @@ impl crate::docs::StdLibFn for Show {
code_blocks code_blocks
.iter() .iter()
.map(|cb| { .map(|cb| {
let tokens = crate::token::lexer(cb).unwrap(); let program = crate::parser::top_level_parse(cb).unwrap();
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default(); let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false; options.insert_final_newline = false;
program.recast(&options, 0) program.recast(&options, 0)

View File

@ -2,10 +2,9 @@
mod test_examples_my_func { mod test_examples_my_func {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_my_func0() { async fn test_mock_example_my_func0() {
let tokens = let program =
crate::token::lexer("This is another code block.\nyes sirrr.\nmyFunc").unwrap(); crate::parser::top_level_parse("This is another code block.\nyes sirrr.\nmyFunc")
let parser = crate::parser::Parser::new(tokens); .unwrap();
let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default(); let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
@ -37,9 +36,8 @@ mod test_examples_my_func {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_my_func1() { async fn test_mock_example_my_func1() {
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nmyFunc").unwrap(); let program =
let parser = crate::parser::Parser::new(tokens); crate::parser::top_level_parse("This is code.\nIt does other shit.\nmyFunc").unwrap();
let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default(); let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
@ -151,9 +149,7 @@ impl crate::docs::StdLibFn for MyFunc {
code_blocks code_blocks
.iter() .iter()
.map(|cb| { .map(|cb| {
let tokens = crate::token::lexer(cb).unwrap(); let program = crate::parser::top_level_parse(cb).unwrap();
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default(); let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false; options.insert_final_newline = false;
program.recast(&options, 0) program.recast(&options, 0)

View File

@ -2,10 +2,9 @@
mod test_examples_line_to { mod test_examples_line_to {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_line_to0() { async fn test_mock_example_line_to0() {
let tokens = let program =
crate::token::lexer("This is another code block.\nyes sirrr.\nlineTo").unwrap(); crate::parser::top_level_parse("This is another code block.\nyes sirrr.\nlineTo")
let parser = crate::parser::Parser::new(tokens); .unwrap();
let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default(); let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
@ -37,9 +36,8 @@ mod test_examples_line_to {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_line_to1() { async fn test_mock_example_line_to1() {
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nlineTo").unwrap(); let program =
let parser = crate::parser::Parser::new(tokens); crate::parser::top_level_parse("This is code.\nIt does other shit.\nlineTo").unwrap();
let program = parser.ast().unwrap();
let id_generator = crate::executor::IdGenerator::default(); let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
@ -159,9 +157,7 @@ impl crate::docs::StdLibFn for LineTo {
code_blocks code_blocks
.iter() .iter()
.map(|cb| { .map(|cb| {
let tokens = crate::token::lexer(cb).unwrap(); let program = crate::parser::top_level_parse(cb).unwrap();
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default(); let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false; options.insert_final_newline = false;
program.recast(&options, 0) program.recast(&options, 0)

Some files were not shown because too many files have changed in this diff Show More