Compare commits
23 Commits
v0.23.0
...
kurt-skip-
Author | SHA1 | Date | |
---|---|---|---|
0d7049d90f | |||
8ebe78c664 | |||
a85c1a9375 | |||
5701616f3e | |||
846acaba2f | |||
0a524d42f6 | |||
fe28527ef9 | |||
0e8d0083c4 | |||
4f4167b247 | |||
fbc2e9d02c | |||
33b15e818b | |||
6cebb84ae0 | |||
85403e47e4 | |||
0dfee64e3b | |||
6370d45f94 | |||
fb3e922180 | |||
1257ec0327 | |||
08e9fe2e52 | |||
7cec1d45fe | |||
93710bc8f2 | |||
87e7e9447f | |||
8be113d284 | |||
7cfc927d5c |
1
.github/workflows/ci.yml
vendored
@ -138,6 +138,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
if: github.event_name == 'schedule'
|
||||
|
||||
- name: Copy updated .json files
|
||||
if: github.event_name == 'schedule'
|
||||
|
26
README.md
@ -124,36 +124,20 @@ Before you submit a contribution PR to this repo, please ensure that:
|
||||
|
||||
## Release a new version
|
||||
|
||||
1. Bump the versions in the .json files by creating a `Cut release v{x}.{y}.{z}` PR, committing the changes from
|
||||
1. Bump the versions by running `./make-realease.sh` while on a fresh pull of main
|
||||
|
||||
```bash
|
||||
VERSION=x.y.z yarn run bump-jsons
|
||||
```
|
||||
|
||||
Alternatively you can try the experimental `make-release.sh` bash script that will create the branch with the updated json files for you.
|
||||
That will create the branch with the updated json files for you.
|
||||
run `./make-release.sh` for a patch update
|
||||
run `./make-release.sh "minor"` for minor
|
||||
run `./make-release.sh "major"` for major
|
||||
|
||||
The PR may serve as a place to discuss the human-readable changelog and extra QA. A quick way of getting PR's merged since the last bump is to [use this PR filter](https://github.com/KittyCAD/modeling-app/pulls?q=is%3Apr+sort%3Aupdated-desc+is%3Amerged+), open up the browser console and paste in the following
|
||||
After it runs you should just need to push the push the branch and open a PR (it will suggest a changelog for you too, delete any that are not user facing)
|
||||
|
||||
```typescript
|
||||
console.log(
|
||||
'- ' +
|
||||
Array.from(
|
||||
document.querySelectorAll('[data-hovercard-type="pull_request"]')
|
||||
).map((a) => `[${a.innerText}](${a.href})`).join(`
|
||||
- `)
|
||||
)
|
||||
```
|
||||
|
||||
grab the md list and delete any that are older than the last bump
|
||||
The PR may serve as a place to discuss the human-readable changelog and extra QA.
|
||||
|
||||
2. Merge the PR
|
||||
|
||||
3. Create a new release and tag pointing to the bump version commit using semantic versioning `v{x}.{y}.{z}`
|
||||
|
||||
4. A new Action kicks in at https://github.com/KittyCAD/modeling-app/actions, uploading artifacts to the release
|
||||
3. Profit (A new Action kicks in at https://github.com/KittyCAD/modeling-app/actions if the PR was correctly named)
|
||||
|
||||
## Fuzzing the parser
|
||||
|
||||
|
@ -121,6 +121,9 @@ const extrusion = extrude(5, sketch001)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -282,6 +285,9 @@ const extrusion = extrude(5, sketch001)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -125,6 +125,9 @@ const extrusion = extrude(5, sketch001)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -286,6 +289,9 @@ const extrusion = extrude(5, sketch001)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -126,6 +126,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -287,6 +290,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -476,6 +482,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -637,6 +646,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -125,6 +125,9 @@ const extrusion = extrude(10, sketch001)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -286,6 +289,9 @@ const extrusion = extrude(10, sketch001)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -475,6 +481,9 @@ const extrusion = extrude(10, sketch001)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -636,6 +645,9 @@ const extrusion = extrude(10, sketch001)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -133,6 +133,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -294,6 +297,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -483,6 +489,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -644,6 +653,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -124,6 +124,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -285,6 +288,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -474,6 +480,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -635,6 +644,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -124,6 +124,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -285,6 +288,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -474,6 +480,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -635,6 +644,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -135,6 +135,9 @@ const exampleSketch = startSketchOn('XZ')
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -296,6 +299,9 @@ const exampleSketch = startSketchOn('XZ')
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -485,6 +491,9 @@ const exampleSketch = startSketchOn('XZ')
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -646,6 +655,9 @@ const exampleSketch = startSketchOn('XZ')
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -130,6 +130,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -291,6 +294,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -480,6 +486,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -641,6 +650,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -225,6 +225,9 @@ const mountingPlate = extrude(thickness, mountingPlateSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -532,6 +535,9 @@ const mountingPlate = extrude(thickness, mountingPlateSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -123,6 +123,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -410,6 +413,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -599,6 +605,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -760,6 +769,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -125,6 +125,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -286,6 +289,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -475,6 +481,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -636,6 +645,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -150,6 +150,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -311,6 +314,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
type: "sketchGroup",
|
||||
// The paths in the sketch group.
|
||||
@ -580,6 +586,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -225,6 +225,9 @@ const mountingPlate = extrude(thickness, mountingPlateSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -524,6 +527,9 @@ const mountingPlate = extrude(thickness, mountingPlateSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -215,6 +215,9 @@ const revolution = startSketchOn(box, "revolveAxis")
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -211,6 +211,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -211,6 +211,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -213,6 +213,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -213,6 +213,9 @@ const part001 = startSketchOn('XY')
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -512,6 +515,9 @@ const part001 = startSketchOn('XY')
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -136,6 +136,9 @@ const example = extrude(1, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -297,6 +300,9 @@ const example = extrude(1, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
type: "sketchGroup",
|
||||
// The paths in the sketch group.
|
||||
@ -479,6 +485,9 @@ const example = extrude(1, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -640,6 +649,9 @@ const example = extrude(1, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -821,6 +833,9 @@ const example = extrude(1, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -982,6 +997,9 @@ const example = extrude(1, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -116,6 +116,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -277,6 +280,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -116,6 +116,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -277,6 +280,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -129,6 +129,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -290,6 +293,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -479,6 +485,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -640,6 +649,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -116,6 +116,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -277,6 +280,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -466,6 +472,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -627,6 +636,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -134,6 +134,9 @@ const example = extrude(1, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -295,6 +298,9 @@ const example = extrude(1, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
type: "sketchGroup",
|
||||
// The paths in the sketch group.
|
||||
|
@ -217,6 +217,9 @@ const example = extrude(-5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -127,6 +127,9 @@ const example = extrude(1, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -288,6 +291,9 @@ const example = extrude(1, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
type: "sketchGroup",
|
||||
// The paths in the sketch group.
|
||||
|
@ -215,6 +215,9 @@ const example = extrude(1, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -217,6 +217,9 @@ let vase = layer()
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -117,6 +117,9 @@ const sketch001 = startSketchOn('XY')
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -278,6 +281,9 @@ const sketch001 = startSketchOn('XY')
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -112,6 +112,9 @@ const sketch001 = startSketchOn('XY')
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -273,6 +276,9 @@ const sketch001 = startSketchOn('XY')
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -111,6 +111,9 @@ const sketch001 = startSketchOn('XY')
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -272,6 +275,9 @@ const sketch001 = startSketchOn('XY')
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -247,6 +247,9 @@ uuid |
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -408,6 +411,9 @@ uuid |
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -673,6 +679,9 @@ uuid |
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -124,6 +124,9 @@ const example = extrude(4, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -285,6 +288,9 @@ const example = extrude(4, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -122,6 +122,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -283,6 +286,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -123,6 +123,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -284,6 +287,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -121,6 +121,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -282,6 +285,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -213,6 +213,9 @@ shell({ faces: ['end'], thickness: 0.25 }, firstSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -512,6 +515,9 @@ shell({ faces: ['end'], thickness: 0.25 }, firstSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -195,6 +195,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -442,6 +445,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -603,6 +609,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -142,6 +142,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -303,6 +306,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -264,6 +264,9 @@ const a1 = startSketchOn({
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -539,6 +542,9 @@ const a1 = startSketchOn({
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
5655
docs/kcl/std.json
@ -125,6 +125,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -286,6 +289,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -475,6 +481,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -636,6 +645,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -116,6 +116,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -277,6 +280,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -466,6 +472,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -627,6 +636,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -74,6 +74,107 @@ You can nest expressions in parenthesis as well:
|
||||
let myMathExpression = 3 + (1 * 2 / (3 - 7))
|
||||
```
|
||||
|
||||
Please if you find any issues using any of the above expressions or syntax
|
||||
## Tags
|
||||
|
||||
Tags are used to give a name (tag) to a specific path.
|
||||
|
||||
### Tag Declaration
|
||||
|
||||
The syntax for declaring a tag is `$myTag` you would use it in the following
|
||||
way:
|
||||
|
||||
```
|
||||
startSketchOn('XZ')
|
||||
|> startProfileAt(origin, %)
|
||||
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001, %) - 90,
|
||||
196.99
|
||||
], %, $rectangleSegmentB001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001, %),
|
||||
-segLen(rectangleSegmentA001, %)
|
||||
], %, $rectangleSegmentC001)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
```
|
||||
|
||||
### Tag Identifier
|
||||
|
||||
As per the example above you can use the tag identifier to get a reference to the
|
||||
tagged object. The syntax for this is `myTag`.
|
||||
|
||||
In the example above we use the tag identifier to get the angle of the segment
|
||||
`segAng(rectangleSegmentA001, %)`.
|
||||
|
||||
|
||||
### Tag Scope
|
||||
|
||||
Tags are scoped globally if in the root context meaning in this example you can
|
||||
use the tag `rectangleSegmentA001` in any function or expression in the file.
|
||||
|
||||
However if the code was written like this:
|
||||
|
||||
```
|
||||
fn rect = (origin) => {
|
||||
return startSketchOn('XZ')
|
||||
|> startProfileAt(origin, %)
|
||||
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001, %) - 90,
|
||||
196.99
|
||||
], %, $rectangleSegmentB001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001, %),
|
||||
-segLen(rectangleSegmentA001, %)
|
||||
], %, $rectangleSegmentC001)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
}
|
||||
|
||||
rect([0, 0])
|
||||
rect([20, 0])
|
||||
```
|
||||
|
||||
Those tags would only be available in the `rect` function and not globally.
|
||||
|
||||
However you likely want to use those tags somewhere outside the `rect` function.
|
||||
|
||||
Tags are accessible through the sketch group they are declared in.
|
||||
For example the following code works.
|
||||
|
||||
```
|
||||
fn rect = (origin) => {
|
||||
return startSketchOn('XZ')
|
||||
|> startProfileAt(origin, %)
|
||||
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001, %) - 90,
|
||||
196.99
|
||||
], %, $rectangleSegmentB001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001, %),
|
||||
-segLen(rectangleSegmentA001, %)
|
||||
], %, $rectangleSegmentC001)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
}
|
||||
|
||||
rect([0, 0])
|
||||
const myRect = rect([20, 0])
|
||||
|
||||
myRect
|
||||
|> extrude(10, %)
|
||||
|> fillet({radius: 0.5, tags: [myRect.tags.rectangleSegmentA001]}, %)
|
||||
```
|
||||
|
||||
See how we use the tag `rectangleSegmentA001` in the `fillet` function outside
|
||||
the `rect` function. This is because the `rect` function is returning the
|
||||
sketch group that contains the tags.
|
||||
|
||||
|
||||
---
|
||||
|
||||
If you find any issues using any of the above expressions or syntax,
|
||||
please file an issue with the `ast` label on the [modeling-app
|
||||
repo](https://github.com/KittyCAD/modeling-app/issues/new).
|
||||
|
@ -119,6 +119,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -280,6 +283,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -469,6 +475,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -630,6 +639,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -119,6 +119,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -280,6 +283,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -469,6 +475,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -630,6 +639,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -117,6 +117,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -278,6 +281,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -467,6 +473,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -628,6 +637,9 @@ const example = extrude(10, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -115,6 +115,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -276,6 +279,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -465,6 +471,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
@ -626,6 +635,9 @@ const example = extrude(5, exampleSketch)
|
||||
},
|
||||
// The to point.
|
||||
to: [number, number],
|
||||
},
|
||||
// Tag identifiers that have been declared in this sketch group.
|
||||
tags: {
|
||||
},
|
||||
// The paths in the sketch group.
|
||||
value: [{
|
||||
|
@ -52,7 +52,24 @@ const commonPoints = {
|
||||
// num2: 19.19,
|
||||
}
|
||||
|
||||
// Utilities for writing tests that depend on test values
|
||||
test.afterEach(async ({ context, page }, testInfo) => {
|
||||
if (testInfo.status === 'skipped') return
|
||||
if (testInfo.status === 'failed') return
|
||||
|
||||
const u = await getUtils(page)
|
||||
// Kill the network so shutdown happens properly
|
||||
await u.emulateNetworkConditions({
|
||||
offline: true,
|
||||
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||
latency: 0,
|
||||
downloadThroughput: -1,
|
||||
uploadThroughput: -1,
|
||||
})
|
||||
|
||||
// It seems it's best to give the browser about 3s to close things
|
||||
// It's not super reliable but we have no real other choice for now
|
||||
await page.waitForTimeout(3000)
|
||||
})
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
// wait for Vite preview server to be up
|
||||
@ -78,7 +95,7 @@ test.beforeEach(async ({ context, page }) => {
|
||||
await page.emulateMedia({ reducedMotion: 'reduce' })
|
||||
})
|
||||
|
||||
test.setTimeout(60000)
|
||||
test.setTimeout(120000)
|
||||
|
||||
async function doBasicSketch(page: Page, openPanes: string[]) {
|
||||
const u = await getUtils(page)
|
||||
@ -600,7 +617,11 @@ test.describe('Editor tests', () => {
|
||||
// TODO: Jess needs to fix this but you have to mod the code to get them to show
|
||||
// up, its an annoying codemirror thing.
|
||||
await page.locator('.cm-content').click()
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
const foldGutterFoldLine = page.locator('[title="Fold line"]')
|
||||
@ -746,12 +767,12 @@ test.describe('Editor tests', () => {
|
||||
await page.keyboard.press('ArrowRight')
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-info')).toBeVisible()
|
||||
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-info')
|
||||
await expect(
|
||||
page.getByText('Identifiers must be lowerCamelCase')
|
||||
page.getByText('Identifiers must be lowerCamelCase').first()
|
||||
).toBeVisible()
|
||||
|
||||
// select the line that's causing the error and delete it
|
||||
@ -856,16 +877,19 @@ test.describe('Editor tests', () => {
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type('const topAng = 42')
|
||||
await page.keyboard.press('ArrowLeft')
|
||||
await page.keyboard.press('ArrowRight')
|
||||
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
await expect(page.locator('.cm-lintRange.cm-lintRange-error')).toBeVisible()
|
||||
await expect(
|
||||
page.locator('.cm-lintRange.cm-lintRange-error').first()
|
||||
).toBeVisible()
|
||||
|
||||
await page.locator('.cm-lintRange.cm-lintRange-error').hover()
|
||||
await expect(page.locator('.cm-diagnosticText')).toBeVisible()
|
||||
await expect(page.getByText('Cannot redefine `topAng`')).toBeVisible()
|
||||
await expect(page.locator('.cm-diagnosticText').first()).toBeVisible()
|
||||
await expect(
|
||||
page.getByText('Cannot redefine `topAng`').first()
|
||||
).toBeVisible()
|
||||
|
||||
const secondTopAng = await page.getByText('topAng').first()
|
||||
const secondTopAng = page.getByText('topAng').first()
|
||||
await secondTopAng?.dblclick()
|
||||
await page.keyboard.type('otherAng')
|
||||
|
||||
@ -929,7 +953,9 @@ test.describe('Editor tests', () => {
|
||||
// error in gutter
|
||||
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
||||
await page.hover('.cm-lint-marker-error:first-child')
|
||||
await expect(page.getByText('Expected 2 arguments, got 3')).toBeVisible()
|
||||
await expect(
|
||||
page.getByText('Expected 2 arguments, got 3').first()
|
||||
).toBeVisible()
|
||||
|
||||
// Make sure there are two diagnostics
|
||||
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(2)
|
||||
@ -965,22 +991,16 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await page.goto('/')
|
||||
await u.waitForPageLoad()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
await expect(
|
||||
page.getByText(
|
||||
'sketch profile must lie entirely on one side of the revolution axis'
|
||||
)
|
||||
).toBeVisible()
|
||||
const searchText =
|
||||
'sketch profile must lie entirely on one side of the revolution axis'
|
||||
await expect(page.getByText(searchText).first()).toBeVisible()
|
||||
})
|
||||
test.describe('Autocomplete works', () => {
|
||||
test('with enter/click to accept the completion', async ({ page }) => {
|
||||
@ -1831,7 +1851,6 @@ test.describe('Copilot ghost text', () => {
|
||||
// We wanna make sure the code saves.
|
||||
await page.waitForTimeout(800)
|
||||
|
||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||
await page.waitForTimeout(500)
|
||||
await page.keyboard.press('Enter')
|
||||
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||
@ -1854,7 +1873,8 @@ test.describe('Copilot ghost text', () => {
|
||||
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
// TODO when we make codemirror a widget, we can test this.
|
||||
//await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
|
||||
test('delete in code rejects the suggestion', async ({ page }) => {
|
||||
@ -2921,7 +2941,7 @@ const sketch002 = startSketchOn(launderExtrudeThroughVar, seg02)
|
||||
await expect(page.getByTestId('hover-highlight').first()).not.toBeVisible()
|
||||
|
||||
await page.mouse.move(flatExtrusionFace[0], flatExtrusionFace[1])
|
||||
await expect(page.getByTestId('hover-highlight')).toHaveCount(19) // multiple lines
|
||||
await expect(page.getByTestId('hover-highlight')).toHaveCount(5) // multiple lines
|
||||
await page.mouse.move(nothing[0], nothing[1])
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.getByTestId('hover-highlight').first()).not.toBeVisible()
|
||||
@ -3061,7 +3081,7 @@ const part001 = startSketchOn('XZ')
|
||||
await page.mouse.move(pos[0], pos[1], { steps: 5 })
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible()
|
||||
await expect(page.getByTestId('hover-highlight').first()).toHaveText(
|
||||
removeAfterFirstParenthesis(expectedCode)
|
||||
expectedCode
|
||||
)
|
||||
// hover over segment, click it and check the cursor has move to the right place
|
||||
await page.mouse.click(pos[0], pos[1])
|
||||
@ -3703,16 +3723,14 @@ test.describe('Regression tests', () => {
|
||||
// Make sure it's not a link
|
||||
await expect(zooLogo).not.toHaveAttribute('href')
|
||||
})
|
||||
test.fixme(
|
||||
'Position _ Is Out Of Range... regression test',
|
||||
async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const exampleSketch = startSketchOn("XZ")
|
||||
test('Position _ Is Out Of Range... regression test', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const exampleSketch = startSketchOn("XZ")
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> angledLine({ angle: 50, length: 45 }, %)
|
||||
|> yLineTo(0, %)
|
||||
@ -3721,51 +3739,59 @@ test.describe('Regression tests', () => {
|
||||
|
||||
const example = extrude(5, exampleSketch)
|
||||
shell({ faces: ['end'], thickness: 0.25 }, exampleSketch)`
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await page.goto('/')
|
||||
await u.waitForPageLoad()
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
await page.waitForTimeout(200)
|
||||
// expect it still to be there (sometimes it just clears for a bit?)
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
await expect(page.getByText('Unexpected token').first()).toBeVisible()
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
await expect(page.getByText('Unexpected token').first()).toBeVisible()
|
||||
|
||||
// Okay execution finished, let's start editing text below the error.
|
||||
await u.codeLocator.click()
|
||||
// Go to the end of the editor
|
||||
// This bug happens when there is a diagnostic in the editor and you try to
|
||||
// edit text below it.
|
||||
// Or delete a huge chunk of text and then try to edit below it.
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.down('Shift')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.up('Shift')
|
||||
await page.keyboard.press('Backspace')
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
// Okay execution finished, let's start editing text below the error.
|
||||
await u.codeLocator.click()
|
||||
// Go to the end of the editor
|
||||
// This bug happens when there is a diagnostic in the editor and you try to
|
||||
// edit text below it.
|
||||
// Or delete a huge chunk of text and then try to edit below it.
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.down('Shift')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.up('Shift')
|
||||
await page.keyboard.press('Backspace')
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type('thing: "blah"', { delay: 100 })
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type('thing: "blah"', { delay: 100 })
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('ArrowLeft')
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const exampleSketch = startSketchOn("XZ")
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toContainText(`const exampleSketch = startSketchOn("XZ")
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> angledLine({ angle: 50, length: 45 }, %)
|
||||
|> yLineTo(0, %)
|
||||
|> close(%)
|
||||
|
||||
|
||||
thing: "blah"`)
|
||||
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
}
|
||||
)
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Sketch tests', () => {
|
||||
@ -3856,6 +3882,31 @@ test.describe('Sketch tests', () => {
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
).toBeVisible()
|
||||
})
|
||||
test('Can exit selection of face', async ({ page }) => {
|
||||
// Load the app with the code panes
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem('persistCode', ``)
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Exit Sketch' })
|
||||
).toBeVisible()
|
||||
|
||||
await expect(
|
||||
page.getByText('click plane or face to sketch on')
|
||||
).toBeVisible()
|
||||
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeVisible()
|
||||
})
|
||||
test.describe('Can edit segments by dragging their handles', () => {
|
||||
const doEditSegmentsByDraggingHandle = async (
|
||||
page: Page,
|
||||
@ -6671,6 +6722,11 @@ ${extraLine ? 'const myVar = segLen(seg01, part001)' : ''}`
|
||||
|
||||
test.describe('Test network and connection issues', () => {
|
||||
test('simulate network down and network little widget', async ({ page }) => {
|
||||
const browserType = page.context().browser()?.browserType().name()
|
||||
test.skip(
|
||||
browserType !== 'chromium',
|
||||
'emulateNetworkConditions only works in chromium'
|
||||
)
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
@ -6741,6 +6797,11 @@ test.describe('Test network and connection issues', () => {
|
||||
})
|
||||
|
||||
test('Engine disconnect & reconnect in sketch mode', async ({ page }) => {
|
||||
const browserType = page.context().browser()?.browserType().name()
|
||||
test.skip(
|
||||
browserType !== 'chromium',
|
||||
'emulateNetworkConditions only works in chromium'
|
||||
)
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
@ -7100,6 +7161,40 @@ test.describe('Testing Gizmo', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('Units menu', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const unitsMenuButton = page.getByRole('button', {
|
||||
name: 'Current Units',
|
||||
exact: false,
|
||||
})
|
||||
await expect(unitsMenuButton).toBeVisible()
|
||||
await expect(unitsMenuButton).toContainText('in')
|
||||
|
||||
await unitsMenuButton.click()
|
||||
const millimetersButton = page.getByRole('button', { name: 'Millimeters' })
|
||||
|
||||
await expect(millimetersButton).toBeVisible()
|
||||
await millimetersButton.click()
|
||||
|
||||
// Look out for the toast message
|
||||
const toastMessage = page.getByText(
|
||||
`Set default unit to "mm" for this project`
|
||||
)
|
||||
await expect(toastMessage).toBeVisible()
|
||||
|
||||
// Verify that the popover has closed
|
||||
await expect(millimetersButton).not.toBeAttached()
|
||||
|
||||
// Verify that the button label has updated
|
||||
await expect(unitsMenuButton).toContainText('mm')
|
||||
})
|
||||
|
||||
test('Successful export shows a success toast', async ({ page }) => {
|
||||
// FYI this test doesn't work with only engine running locally
|
||||
// And you will need to have the KittyCAD CLI installed
|
||||
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 29 KiB |
@ -232,6 +232,7 @@ export async function getUtils(page: Page) {
|
||||
|
||||
return {
|
||||
waitForAuthSkipAppStart: () => waitForAuthAndLsp(page),
|
||||
waitForPageLoad: () => waitForPageLoad(page),
|
||||
removeCurrentCode: () => removeCurrentCode(page),
|
||||
sendCustomCmd: (cmd: EngineCommand) => sendCustomCmd(page, cmd),
|
||||
updateCamPosition: async (xyz: [number, number, number]) => {
|
||||
@ -311,9 +312,9 @@ export async function getUtils(page: Page) {
|
||||
fullPage: true,
|
||||
})
|
||||
const screenshot = await PNG.sync.read(buffer)
|
||||
// most likely related to pixel density but the screenshots for webkit are 2x the size
|
||||
// there might be a more robust way of doing this.
|
||||
const pixMultiplier = browserType === 'webkit' ? 2 : 1
|
||||
const pixMultiplier: number = await page.evaluate(
|
||||
'window.devicePixelRatio'
|
||||
)
|
||||
const index =
|
||||
(screenshot.width * coords.y * pixMultiplier +
|
||||
coords.x * pixMultiplier) *
|
||||
@ -376,11 +377,13 @@ export async function getUtils(page: Page) {
|
||||
emulateNetworkConditions: async (
|
||||
networkOptions: Protocol.Network.emulateNetworkConditionsParameters
|
||||
) => {
|
||||
// Skip on non-Chromium browsers, since we need to use the CDP.
|
||||
test.skip(
|
||||
cdpSession === null,
|
||||
'Network emulation is only supported in Chromium'
|
||||
)
|
||||
if (browserType !== 'chromium') {
|
||||
console.warn('emulateNetworkConditions will not work on this browser')
|
||||
}
|
||||
if (cdpSession === null) {
|
||||
// Use a fail safe if we can't simulate disconnect (on Safari)
|
||||
return page.evaluate('window.tearDown()')
|
||||
}
|
||||
|
||||
cdpSession?.send('Network.emulateNetworkConditions', networkOptions)
|
||||
},
|
||||
|
24
package.json
@ -1,14 +1,15 @@
|
||||
{
|
||||
"name": "untitled-app",
|
||||
"version": "0.23.0",
|
||||
"version": "0.23.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.16.3",
|
||||
"@codemirror/autocomplete": "^6.17.0",
|
||||
"@codemirror/commands": "^6.6.0",
|
||||
"@codemirror/language": "^6.10.2",
|
||||
"@codemirror/lint": "^6.8.1",
|
||||
"@codemirror/search": "^6.5.6",
|
||||
"@codemirror/state": "^6.4.1",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@csstools/postcss-oklab-function": "^3.0.16",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.5.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
||||
@ -19,23 +20,22 @@
|
||||
"@kittycad/lib": "^0.0.69",
|
||||
"@react-hook/resize-observer": "^2.0.1",
|
||||
"@replit/codemirror-interact": "^6.3.1",
|
||||
"@tauri-apps/api": "2.0.0-beta.12",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-fs": "^2.0.0-beta.5",
|
||||
"@tauri-apps/plugin-http": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-os": "^2.0.0-beta.3",
|
||||
"@tauri-apps/plugin-process": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-shell": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-updater": "^2.0.0-beta.3",
|
||||
"@tauri-apps/api": "^2.0.0-beta.14",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.0-beta.6",
|
||||
"@tauri-apps/plugin-fs": "^2.0.0-beta.6",
|
||||
"@tauri-apps/plugin-http": "^2.0.0-beta.7",
|
||||
"@tauri-apps/plugin-os": "^2.0.0-beta.6",
|
||||
"@tauri-apps/plugin-process": "^2.0.0-beta.6",
|
||||
"@tauri-apps/plugin-shell": "^2.0.0-beta.7",
|
||||
"@tauri-apps/plugin-updater": "^2.0.0-beta.6",
|
||||
"@ts-stack/markdown": "^1.5.0",
|
||||
"@tweenjs/tween.js": "^23.1.1",
|
||||
"@uiw/react-codemirror": "^4.21.25",
|
||||
"@xstate/inspect": "^0.8.0",
|
||||
"@xstate/react": "^3.2.2",
|
||||
"codemirror": "^6.0.1",
|
||||
"decamelize": "^6.0.0",
|
||||
"fuse.js": "^7.0.0",
|
||||
"html2canvas-pro": "^1.5.1",
|
||||
"html2canvas-pro": "^1.5.2",
|
||||
"json-rpc-2.0": "^1.6.0",
|
||||
"jszip": "^3.10.1",
|
||||
"re-resizable": "^6.9.11",
|
||||
|
@ -1,10 +1,7 @@
|
||||
import { autocompletion } from '@codemirror/autocomplete'
|
||||
import { foldService, syntaxTree } from '@codemirror/language'
|
||||
import { foldService } from '@codemirror/language'
|
||||
import { Extension, EditorState } from '@codemirror/state'
|
||||
import { ViewPlugin } from '@codemirror/view'
|
||||
|
||||
import { CompletionTriggerKind } from 'vscode-languageserver-protocol'
|
||||
|
||||
import {
|
||||
docPathFacet,
|
||||
LanguageServerPlugin,
|
||||
@ -13,7 +10,6 @@ import {
|
||||
workspaceFolders,
|
||||
LanguageServerOptions,
|
||||
} from './plugin/lsp'
|
||||
import { offsetToPos } from './plugin/util'
|
||||
|
||||
export type { LanguageServerClientOptions } from './client'
|
||||
export { LanguageServerClient } from './client'
|
||||
@ -24,14 +20,15 @@ export {
|
||||
LspWorkerEventType,
|
||||
} from './client/codec'
|
||||
export type { LanguageServerOptions } from './plugin/lsp'
|
||||
export type { TransactionInfo, RelevantUpdate } from './plugin/annotations'
|
||||
export { updateInfo, TransactionAnnotation } from './plugin/annotations'
|
||||
export {
|
||||
LanguageServerPlugin,
|
||||
LanguageServerPluginSpec,
|
||||
docPathFacet,
|
||||
languageId,
|
||||
workspaceFolders,
|
||||
lspSemanticTokensEvent,
|
||||
lspDiagnosticsEvent,
|
||||
lspFormatCodeEvent,
|
||||
} from './plugin/lsp'
|
||||
export { posToOffset, offsetToPos } from './plugin/util'
|
||||
|
||||
@ -51,63 +48,10 @@ export function lspPlugin(options: LanguageServerOptions): Extension {
|
||||
if (plugin == null) return null
|
||||
// Get the folding ranges from the language server.
|
||||
// Since this is async we directly need to update the folding ranges after.
|
||||
return plugin?.foldingRange(lineStart, lineEnd)
|
||||
const range = plugin?.foldingRange(lineStart, lineEnd)
|
||||
return range
|
||||
}),
|
||||
]
|
||||
|
||||
if (options.client.getServerCapabilities().completionProvider) {
|
||||
ext.push(
|
||||
autocompletion({
|
||||
defaultKeymap: false,
|
||||
override: [
|
||||
async (context) => {
|
||||
if (plugin === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
const { state, pos, explicit } = context
|
||||
|
||||
let nodeBefore = syntaxTree(state).resolveInner(pos, -1)
|
||||
if (
|
||||
nodeBefore.name === 'BlockComment' ||
|
||||
nodeBefore.name === 'LineComment'
|
||||
)
|
||||
return null
|
||||
|
||||
const line = state.doc.lineAt(pos)
|
||||
let trigKind: CompletionTriggerKind = CompletionTriggerKind.Invoked
|
||||
let trigChar: string | undefined
|
||||
if (
|
||||
!explicit &&
|
||||
plugin.client
|
||||
.getServerCapabilities()
|
||||
.completionProvider?.triggerCharacters?.includes(
|
||||
line.text[pos - line.from - 1]
|
||||
)
|
||||
) {
|
||||
trigKind = CompletionTriggerKind.TriggerCharacter
|
||||
trigChar = line.text[pos - line.from - 1]
|
||||
}
|
||||
if (
|
||||
trigKind === CompletionTriggerKind.Invoked &&
|
||||
!context.matchBefore(/\w+$/)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
return await plugin.requestCompletion(
|
||||
context,
|
||||
offsetToPos(state.doc, pos),
|
||||
{
|
||||
triggerKind: trigKind,
|
||||
triggerCharacter: trigChar,
|
||||
}
|
||||
)
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return ext
|
||||
}
|
||||
|
@ -1,131 +0,0 @@
|
||||
import { hasNextSnippetField, pickedCompletion } from '@codemirror/autocomplete'
|
||||
import { Annotation, Transaction } from '@codemirror/state'
|
||||
import type { ViewUpdate } from '@codemirror/view'
|
||||
|
||||
export enum LspAnnotation {
|
||||
SemanticTokens = 'semantic-tokens',
|
||||
FormatCode = 'format-code',
|
||||
Diagnostics = 'diagnostics',
|
||||
}
|
||||
|
||||
const lspEvent = Annotation.define<LspAnnotation>()
|
||||
export const lspSemanticTokensEvent = lspEvent.of(LspAnnotation.SemanticTokens)
|
||||
export const lspFormatCodeEvent = lspEvent.of(LspAnnotation.FormatCode)
|
||||
export const lspDiagnosticsEvent = lspEvent.of(LspAnnotation.Diagnostics)
|
||||
|
||||
export enum TransactionAnnotation {
|
||||
Remote = 'remote',
|
||||
UserSelect = 'user.select',
|
||||
UserInput = 'user.input',
|
||||
UserMove = 'user.move',
|
||||
UserDelete = 'user.delete',
|
||||
UserUndo = 'user.undo',
|
||||
UserRedo = 'user.redo',
|
||||
|
||||
SemanticTokens = 'SemanticTokens',
|
||||
FormatCode = 'FormatCode',
|
||||
Diagnostics = 'Diagnostics',
|
||||
|
||||
PickedCompletion = 'PickedCompletion',
|
||||
}
|
||||
|
||||
export interface TransactionInfo {
|
||||
annotations: TransactionAnnotation[]
|
||||
time: number | null
|
||||
docChanged: boolean
|
||||
addToHistory: boolean
|
||||
inSnippet: boolean
|
||||
transaction: Transaction
|
||||
}
|
||||
|
||||
export const updateInfo = (update: ViewUpdate): TransactionInfo[] => {
|
||||
let transactionInfos: TransactionInfo[] = []
|
||||
|
||||
for (const tr of update.transactions) {
|
||||
let annotations: TransactionAnnotation[] = []
|
||||
|
||||
if (tr.isUserEvent('select')) {
|
||||
annotations.push(TransactionAnnotation.UserSelect)
|
||||
}
|
||||
|
||||
if (tr.isUserEvent('input')) {
|
||||
annotations.push(TransactionAnnotation.UserInput)
|
||||
}
|
||||
if (tr.isUserEvent('delete')) {
|
||||
annotations.push(TransactionAnnotation.UserDelete)
|
||||
}
|
||||
if (tr.isUserEvent('undo')) {
|
||||
annotations.push(TransactionAnnotation.UserUndo)
|
||||
}
|
||||
if (tr.isUserEvent('redo')) {
|
||||
annotations.push(TransactionAnnotation.UserRedo)
|
||||
}
|
||||
if (tr.isUserEvent('move')) {
|
||||
annotations.push(TransactionAnnotation.UserMove)
|
||||
}
|
||||
|
||||
if (tr.annotation(pickedCompletion) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.PickedCompletion)
|
||||
}
|
||||
|
||||
if (tr.annotation(lspSemanticTokensEvent.type) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.SemanticTokens)
|
||||
}
|
||||
|
||||
if (tr.annotation(lspFormatCodeEvent.type) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.FormatCode)
|
||||
}
|
||||
|
||||
if (tr.annotation(lspDiagnosticsEvent.type) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.Diagnostics)
|
||||
}
|
||||
|
||||
if (tr.annotation(Transaction.remote) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.Remote)
|
||||
}
|
||||
|
||||
transactionInfos.push({
|
||||
annotations,
|
||||
time: tr.annotation(Transaction.time) || null,
|
||||
docChanged: tr.docChanged,
|
||||
addToHistory: tr.annotation(Transaction.addToHistory) || false,
|
||||
inSnippet: hasNextSnippetField(update.state),
|
||||
transaction: tr,
|
||||
})
|
||||
}
|
||||
|
||||
return transactionInfos
|
||||
}
|
||||
|
||||
export interface RelevantUpdate {
|
||||
overall: boolean
|
||||
userSelect: boolean
|
||||
time: number | null
|
||||
}
|
||||
|
||||
export const relevantUpdate = (update: ViewUpdate): RelevantUpdate => {
|
||||
const infos = updateInfo(update)
|
||||
// Make sure we are not in a snippet
|
||||
if (infos.some((info) => info.inSnippet)) {
|
||||
return {
|
||||
overall: false,
|
||||
userSelect: false,
|
||||
time: null,
|
||||
}
|
||||
}
|
||||
return {
|
||||
overall: infos.some(
|
||||
(info) =>
|
||||
info.docChanged ||
|
||||
info.annotations.includes(TransactionAnnotation.UserInput) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserDelete) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserUndo) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserRedo) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserMove)
|
||||
),
|
||||
userSelect: infos.some((info) =>
|
||||
info.annotations.includes(TransactionAnnotation.UserSelect)
|
||||
),
|
||||
time: infos.length ? infos[0].time : null,
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import {
|
||||
acceptCompletion,
|
||||
autocompletion,
|
||||
clearSnippet,
|
||||
closeCompletion,
|
||||
hasNextSnippetField,
|
||||
@ -8,10 +9,17 @@ import {
|
||||
prevSnippetField,
|
||||
startCompletion,
|
||||
} from '@codemirror/autocomplete'
|
||||
import { Prec } from '@codemirror/state'
|
||||
import { EditorView, keymap, KeyBinding } from '@codemirror/view'
|
||||
import { Prec, Extension } from '@codemirror/state'
|
||||
import { EditorView, keymap, KeyBinding, ViewPlugin } from '@codemirror/view'
|
||||
|
||||
import { CompletionItemKind } from 'vscode-languageserver-protocol'
|
||||
import {
|
||||
CompletionItemKind,
|
||||
CompletionTriggerKind,
|
||||
} from 'vscode-languageserver-protocol'
|
||||
|
||||
import { LanguageServerPlugin } from './lsp'
|
||||
import { offsetToPos } from './util'
|
||||
import { syntaxTree } from '@codemirror/language'
|
||||
|
||||
export const CompletionItemKindMap = Object.fromEntries(
|
||||
Object.entries(CompletionItemKind).map(([key, value]) => [value, key])
|
||||
@ -46,6 +54,59 @@ const lspAutocompleteKeymap: readonly KeyBinding[] = [
|
||||
},
|
||||
]
|
||||
|
||||
export const lspAutocompleteKeymapExt = Prec.highest(
|
||||
keymap.computeN([], () => [lspAutocompleteKeymap])
|
||||
)
|
||||
const lspAutocompleteKeymapExt = Prec.highest(keymap.of(lspAutocompleteKeymap))
|
||||
|
||||
export default function lspAutocompleteExt(
|
||||
plugin: ViewPlugin<LanguageServerPlugin>
|
||||
): Extension {
|
||||
return [
|
||||
lspAutocompleteKeymapExt,
|
||||
autocompletion({
|
||||
defaultKeymap: false,
|
||||
override: [
|
||||
async (context) => {
|
||||
const { state, pos, explicit, view } = context
|
||||
let value = view?.plugin(plugin)
|
||||
if (!value) return null
|
||||
|
||||
let nodeBefore = syntaxTree(state).resolveInner(pos, -1)
|
||||
if (
|
||||
nodeBefore.name === 'BlockComment' ||
|
||||
nodeBefore.name === 'LineComment'
|
||||
)
|
||||
return null
|
||||
|
||||
const line = state.doc.lineAt(pos)
|
||||
let trigKind: CompletionTriggerKind = CompletionTriggerKind.Invoked
|
||||
let trigChar: string | undefined
|
||||
if (
|
||||
!explicit &&
|
||||
value.client
|
||||
.getServerCapabilities()
|
||||
.completionProvider?.triggerCharacters?.includes(
|
||||
line.text[pos - line.from - 1]
|
||||
)
|
||||
) {
|
||||
trigKind = CompletionTriggerKind.TriggerCharacter
|
||||
trigChar = line.text[pos - line.from - 1]
|
||||
}
|
||||
if (
|
||||
trigKind === CompletionTriggerKind.Invoked &&
|
||||
!context.matchBefore(/\w+$/)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
return await value.requestCompletion(
|
||||
context,
|
||||
offsetToPos(state.doc, pos),
|
||||
{
|
||||
triggerKind: trigKind,
|
||||
triggerCharacter: trigChar,
|
||||
}
|
||||
)
|
||||
},
|
||||
],
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
@ -4,7 +4,13 @@ import type {
|
||||
CompletionResult,
|
||||
} from '@codemirror/autocomplete'
|
||||
import { completeFromList, snippetCompletion } from '@codemirror/autocomplete'
|
||||
import { Facet, StateEffect, Extension, Transaction } from '@codemirror/state'
|
||||
import {
|
||||
Facet,
|
||||
StateEffect,
|
||||
Extension,
|
||||
Transaction,
|
||||
Annotation,
|
||||
} from '@codemirror/state'
|
||||
import type {
|
||||
ViewUpdate,
|
||||
PluginValue,
|
||||
@ -22,15 +28,10 @@ import {
|
||||
import { URI } from 'vscode-uri'
|
||||
|
||||
import { LanguageServerClient } from '../client'
|
||||
import {
|
||||
lspSemanticTokensEvent,
|
||||
lspFormatCodeEvent,
|
||||
relevantUpdate,
|
||||
} from './annotations'
|
||||
import { CompletionItemKindMap } from './autocomplete'
|
||||
import { addToken, SemanticToken } from './semantic-tokens'
|
||||
import { deferExecution, posToOffset, formatMarkdownContents } from './util'
|
||||
import { lspAutocompleteKeymapExt } from './autocomplete'
|
||||
import lspAutocompleteExt from './autocomplete'
|
||||
import lspHoverExt from './hover'
|
||||
import lspFormatExt from './format'
|
||||
import lspIndentExt from './indent'
|
||||
@ -47,6 +48,17 @@ export const workspaceFolders = Facet.define<
|
||||
LSP.WorkspaceFolder[]
|
||||
>({ combine: useLast })
|
||||
|
||||
export enum LspAnnotation {
|
||||
SemanticTokens = 'semantic-tokens',
|
||||
FormatCode = 'format-code',
|
||||
Diagnostics = 'diagnostics',
|
||||
}
|
||||
|
||||
const lspEvent = Annotation.define<LspAnnotation>()
|
||||
export const lspSemanticTokensEvent = lspEvent.of(LspAnnotation.SemanticTokens)
|
||||
export const lspFormatCodeEvent = lspEvent.of(LspAnnotation.FormatCode)
|
||||
export const lspDiagnosticsEvent = lspEvent.of(LspAnnotation.Diagnostics)
|
||||
|
||||
export interface LanguageServerOptions {
|
||||
// We assume this is the main project directory, we are currently working in.
|
||||
workspaceFolders: LSP.WorkspaceFolder[]
|
||||
@ -131,11 +143,6 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
}
|
||||
|
||||
update(viewUpdate: ViewUpdate) {
|
||||
const isRelevant = relevantUpdate(viewUpdate)
|
||||
if (!isRelevant.overall) {
|
||||
return
|
||||
}
|
||||
|
||||
// If the doc didn't change we can return early.
|
||||
if (!viewUpdate.docChanged) {
|
||||
return
|
||||
@ -284,19 +291,16 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
},
|
||||
})
|
||||
|
||||
if (!result) return null
|
||||
if (!result || !result.length) return null
|
||||
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
const { range, newText } = result[i]
|
||||
this.view.dispatch({
|
||||
changes: {
|
||||
from: posToOffset(this.view.state.doc, range.start)!,
|
||||
to: posToOffset(this.view.state.doc, range.end)!,
|
||||
insert: newText,
|
||||
},
|
||||
annotations: [lspFormatCodeEvent, Transaction.addToHistory.of(true)],
|
||||
})
|
||||
}
|
||||
this.view.dispatch({
|
||||
changes: result.map(({ range, newText }) => ({
|
||||
from: posToOffset(this.view.state.doc, range.start)!,
|
||||
to: posToOffset(this.view.state.doc, range.end)!,
|
||||
insert: newText,
|
||||
})),
|
||||
annotations: lspFormatCodeEvent,
|
||||
})
|
||||
}
|
||||
|
||||
async requestCompletion(
|
||||
@ -552,7 +556,7 @@ export class LanguageServerPluginSpec
|
||||
{
|
||||
provide(plugin: ViewPlugin<LanguageServerPlugin>): Extension {
|
||||
return [
|
||||
lspAutocompleteKeymapExt,
|
||||
lspAutocompleteExt(plugin),
|
||||
lspFormatExt(plugin),
|
||||
lspHoverExt(plugin),
|
||||
lspIndentExt(),
|
||||
|
@ -4,7 +4,7 @@ import { EditorView, Decoration, DecorationSet } from '@codemirror/view'
|
||||
|
||||
import { Tag, tags } from '@lezer/highlight'
|
||||
|
||||
import { lspSemanticTokensEvent } from './annotations'
|
||||
import { lspSemanticTokensEvent } from './lsp'
|
||||
|
||||
export interface SemanticToken {
|
||||
from: number
|
||||
|
541
src-tauri/Cargo.lock
generated
@ -11,27 +11,27 @@ rust-version = "1.70"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.0.0-beta.13", features = [] }
|
||||
tauri-build = { version = "2.0.0-beta.18", features = [] }
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
kcl-lib = { version = "0.1.53", path = "../src/wasm-lib/kcl" }
|
||||
kittycad = "0.3.5"
|
||||
kittycad = "0.3.7"
|
||||
log = "0.4.21"
|
||||
oauth2 = "4.4.2"
|
||||
serde_json = "1.0"
|
||||
tauri = { version = "2.0.0-beta.15", features = [ "devtools", "unstable"] }
|
||||
tauri-plugin-cli = { version = "2.0.0-beta.3" }
|
||||
tauri-plugin-deep-link = { version = "2.0.0-beta.3" }
|
||||
tauri = { version = "2.0.0-beta.23", features = [ "devtools", "unstable"] }
|
||||
tauri-plugin-cli = { version = "2.0.0-beta.7" }
|
||||
tauri-plugin-deep-link = { version = "2.0.0-beta.8" }
|
||||
tauri-plugin-dialog = { version = "2.0.0-beta.6" }
|
||||
tauri-plugin-fs = { version = "2.0.0-beta.9" }
|
||||
tauri-plugin-http = { version = "2.0.0-beta.6" }
|
||||
tauri-plugin-log = { version = "2.0.0-beta.4" }
|
||||
tauri-plugin-os = { version = "2.0.0-beta.2" }
|
||||
tauri-plugin-persisted-scope = { version = "2.0.0-beta.7" }
|
||||
tauri-plugin-process = { version = "2.0.0-beta.2" }
|
||||
tauri-plugin-shell = { version = "2.0.0-beta.2" }
|
||||
tauri-plugin-updater = { version = "2.0.0-beta.4" }
|
||||
tauri-plugin-fs = { version = "2.0.0-beta.10" }
|
||||
tauri-plugin-http = { version = "2.0.0-beta.11" }
|
||||
tauri-plugin-log = { version = "2.0.0-beta.7" }
|
||||
tauri-plugin-os = { version = "2.0.0-beta.7" }
|
||||
tauri-plugin-persisted-scope = { version = "2.0.0-beta.10" }
|
||||
tauri-plugin-process = { version = "2.0.0-beta.7" }
|
||||
tauri-plugin-shell = { version = "2.0.0-beta.8" }
|
||||
tauri-plugin-updater = { version = "2.0.0-beta.9" }
|
||||
tokio = { version = "1.37.0", features = ["time", "fs", "process"] }
|
||||
toml = "0.8.2"
|
||||
url = "2.5.0"
|
||||
|
@ -63,16 +63,22 @@
|
||||
"subcommands": {}
|
||||
},
|
||||
"deep-link": {
|
||||
"domains": [
|
||||
"mobile": [
|
||||
{
|
||||
"host": "app.zoo.dev"
|
||||
}
|
||||
]
|
||||
],
|
||||
"desktop": {
|
||||
"schemes": [
|
||||
"zoo",
|
||||
"zoo-modeling-app"
|
||||
]
|
||||
}
|
||||
},
|
||||
"shell": {
|
||||
"open": true
|
||||
}
|
||||
},
|
||||
"productName": "Zoo Modeling App",
|
||||
"version": "0.23.0"
|
||||
"version": "0.23.1"
|
||||
}
|
||||
|
10
src/App.tsx
@ -25,7 +25,7 @@ import ModalContainer from 'react-modal-promise'
|
||||
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
||||
import Gizmo from 'components/Gizmo'
|
||||
import { CoreDumpManager } from 'lib/coredump'
|
||||
import { useAppState } from 'AppState'
|
||||
import { UnitsMenu } from 'components/UnitsMenu'
|
||||
|
||||
export function App() {
|
||||
useRefreshSettings(paths.FILE + 'SETTINGS')
|
||||
@ -45,17 +45,12 @@ export function App() {
|
||||
|
||||
useHotKeyListener()
|
||||
const { context } = useModelingContext()
|
||||
const { setAppState } = useAppState()
|
||||
|
||||
useEffect(() => {
|
||||
setAppState({ htmlRef: ref })
|
||||
}, [ref])
|
||||
|
||||
const { auth, settings } = useSettingsAuthContext()
|
||||
const token = auth?.context?.token
|
||||
|
||||
const coreDumpManager = useMemo(
|
||||
() => new CoreDumpManager(engineCommandManager, ref, token),
|
||||
() => new CoreDumpManager(engineCommandManager, token),
|
||||
[]
|
||||
)
|
||||
|
||||
@ -133,6 +128,7 @@ export function App() {
|
||||
<Stream />
|
||||
{/* <CamToggle /> */}
|
||||
<LowerRightControls coreDumpManager={coreDumpManager}>
|
||||
<UnitsMenu />
|
||||
<Gizmo />
|
||||
</LowerRightControls>
|
||||
</div>
|
||||
|
@ -10,12 +10,10 @@ Please do not fill this up with junk.
|
||||
|
||||
interface AppState {
|
||||
isStreamReady: boolean
|
||||
htmlRef: React.RefObject<HTMLDivElement> | null
|
||||
setAppState: (newAppState: Partial<AppState>) => void
|
||||
}
|
||||
|
||||
const AppStateContext = createContext<AppState>({
|
||||
htmlRef: null,
|
||||
isStreamReady: false,
|
||||
setAppState: () => {},
|
||||
})
|
||||
@ -24,7 +22,6 @@ export const useAppState = () => useContext(AppStateContext)
|
||||
|
||||
export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [appState, _setAppState] = useState<AppState>({
|
||||
htmlRef: null,
|
||||
isStreamReady: false,
|
||||
setAppState: () => {},
|
||||
})
|
||||
@ -34,7 +31,6 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<AppStateContext.Provider
|
||||
value={{
|
||||
htmlRef: appState.htmlRef,
|
||||
isStreamReady: appState.isStreamReady,
|
||||
setAppState,
|
||||
}}
|
||||
|
@ -40,7 +40,7 @@ import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
||||
import toast from 'react-hot-toast'
|
||||
import { coreDump } from 'lang/wasm'
|
||||
import { useMemo } from 'react'
|
||||
import { AppStateProvider, useAppState } from 'AppState'
|
||||
import { AppStateProvider } from 'AppState'
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
@ -180,9 +180,8 @@ export const Router = () => {
|
||||
function CoreDump() {
|
||||
const { auth } = useSettingsAuthContext()
|
||||
const token = auth?.context?.token
|
||||
const { htmlRef } = useAppState()
|
||||
const coreDumpManager = useMemo(
|
||||
() => new CoreDumpManager(engineCommandManager, htmlRef, token),
|
||||
() => new CoreDumpManager(engineCommandManager, token),
|
||||
[]
|
||||
)
|
||||
useHotkeyWrapper(['meta + shift + .'], () => {
|
||||
|
@ -12,6 +12,10 @@ import { ActionButtonDropdown } from 'components/ActionButtonDropdown'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import { useAppState } from 'AppState'
|
||||
import {
|
||||
canRectangleTool,
|
||||
isEditingExistingSketch,
|
||||
} from 'machines/modelingMachine'
|
||||
|
||||
export function Toolbar({
|
||||
className = '',
|
||||
@ -46,29 +50,48 @@ export function Toolbar({
|
||||
isExecuting ||
|
||||
!isStreamReady
|
||||
|
||||
const disableLineButton =
|
||||
state.matches('Sketch.Rectangle tool.Awaiting second corner') ||
|
||||
disableAllButtons
|
||||
useHotkeys(
|
||||
'l',
|
||||
() =>
|
||||
state.matches('Sketch.Line tool')
|
||||
? send('CancelSketch')
|
||||
: send('Equip Line tool'),
|
||||
{ enabled: !disableAllButtons, scopes: ['sketch'] }
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'line',
|
||||
}),
|
||||
{ enabled: !disableLineButton, scopes: ['sketch'] }
|
||||
)
|
||||
const disableTangentialArc =
|
||||
(!isEditingExistingSketch(context) &&
|
||||
!state.matches('Sketch.Tangential arc to')) ||
|
||||
disableAllButtons
|
||||
useHotkeys(
|
||||
'a',
|
||||
() =>
|
||||
state.matches('Sketch.Tangential arc to')
|
||||
? send('CancelSketch')
|
||||
: send('Equip tangential arc to'),
|
||||
{ enabled: !disableAllButtons, scopes: ['sketch'] }
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'tangentialArc',
|
||||
}),
|
||||
{ enabled: !disableTangentialArc, scopes: ['sketch'] }
|
||||
)
|
||||
const disableRectangle =
|
||||
(!canRectangleTool(context) && !state.matches('Sketch.Rectangle tool')) ||
|
||||
disableAllButtons
|
||||
useHotkeys(
|
||||
'r',
|
||||
() =>
|
||||
state.matches('Sketch.Rectangle tool')
|
||||
? send('CancelSketch')
|
||||
: send('Equip rectangle tool'),
|
||||
{ enabled: !disableAllButtons, scopes: ['sketch'] }
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'rectangle',
|
||||
}),
|
||||
{ enabled: !disableRectangle, scopes: ['sketch'] }
|
||||
)
|
||||
useHotkeys(
|
||||
's',
|
||||
@ -81,7 +104,7 @@ export function Toolbar({
|
||||
useHotkeys(
|
||||
'esc',
|
||||
() =>
|
||||
state.matches('Sketch.SketchIdle')
|
||||
['Sketch no face', 'Sketch.SketchIdle'].some(state.matches)
|
||||
? send('Cancel')
|
||||
: send('CancelSketch'),
|
||||
{ enabled: !disableAllButtons, scopes: ['sketch'] }
|
||||
@ -224,6 +247,11 @@ export function Toolbar({
|
||||
</ActionButton>
|
||||
</li>
|
||||
)}
|
||||
{state.matches('Sketch no face') && (
|
||||
<li className="contents">
|
||||
<div className="mx-2 text-sm">click plane or face to sketch on</div>
|
||||
</li>
|
||||
)}
|
||||
{state.matches('Sketch') && !state.matches('idle') && (
|
||||
<>
|
||||
<li className="contents" key="line-button">
|
||||
@ -233,7 +261,10 @@ export function Toolbar({
|
||||
onClick={() =>
|
||||
state?.matches('Sketch.Line tool')
|
||||
? send('CancelSketch')
|
||||
: send('Equip Line tool')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'line',
|
||||
})
|
||||
}
|
||||
aria-pressed={state?.matches('Sketch.Line tool')}
|
||||
iconStart={{
|
||||
@ -241,7 +272,7 @@ export function Toolbar({
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
disabled={disableAllButtons}
|
||||
disabled={disableLineButton}
|
||||
>
|
||||
Line
|
||||
<Tooltip
|
||||
@ -260,7 +291,10 @@ export function Toolbar({
|
||||
onClick={() =>
|
||||
state.matches('Sketch.Tangential arc to')
|
||||
? send('CancelSketch')
|
||||
: send('Equip tangential arc to')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'tangentialArc',
|
||||
})
|
||||
}
|
||||
aria-pressed={state.matches('Sketch.Tangential arc to')}
|
||||
iconStart={{
|
||||
@ -268,11 +302,7 @@ export function Toolbar({
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
disabled={
|
||||
(!state.can('Equip tangential arc to') &&
|
||||
!state.matches('Sketch.Tangential arc to')) ||
|
||||
disableAllButtons
|
||||
}
|
||||
disabled={disableTangentialArc}
|
||||
>
|
||||
Tangential Arc
|
||||
<Tooltip
|
||||
@ -291,7 +321,10 @@ export function Toolbar({
|
||||
onClick={() =>
|
||||
state.matches('Sketch.Rectangle tool')
|
||||
? send('CancelSketch')
|
||||
: send('Equip rectangle tool')
|
||||
: send({
|
||||
type: 'change tool',
|
||||
data: 'rectangle',
|
||||
})
|
||||
}
|
||||
aria-pressed={state.matches('Sketch.Rectangle tool')}
|
||||
iconStart={{
|
||||
@ -299,13 +332,9 @@ export function Toolbar({
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
disabled={
|
||||
(!state.can('Equip rectangle tool') &&
|
||||
!state.matches('Sketch.Rectangle tool')) ||
|
||||
disableAllButtons
|
||||
}
|
||||
disabled={disableRectangle}
|
||||
title={
|
||||
state.can('Equip rectangle tool')
|
||||
canRectangleTool(context)
|
||||
? 'Rectangle'
|
||||
: 'Can only be used when a sketch is empty currently'
|
||||
}
|
||||
|
@ -804,7 +804,7 @@ export class SceneEntities {
|
||||
|
||||
// Update the primary AST and unequip the rectangle tool
|
||||
await kclManager.executeAstMock(_ast)
|
||||
sceneInfra.modelingSend({ type: 'CancelSketch' })
|
||||
sceneInfra.modelingSend({ type: 'Finish rectangle' })
|
||||
|
||||
const { programMemory } = await executeAst({
|
||||
ast: _ast,
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Completion } from '@codemirror/autocomplete'
|
||||
import { EditorState, EditorView, useCodeMirror } from '@uiw/react-codemirror'
|
||||
import { EditorView, ViewUpdate } from '@codemirror/view'
|
||||
import { EditorState } from '@codemirror/state'
|
||||
import { CustomIcon } from 'components/CustomIcon'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
@ -12,6 +13,7 @@ import { useEffect, useRef, useState } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import styles from './CommandBarKclInput.module.css'
|
||||
import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst'
|
||||
import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor'
|
||||
|
||||
function CommandBarKclInput({
|
||||
arg,
|
||||
@ -63,9 +65,7 @@ function CommandBarKclInput({
|
||||
|
||||
const { setContainer } = useCodeMirror({
|
||||
container: editorRef.current,
|
||||
value,
|
||||
indentWithTab: false,
|
||||
basicSetup: false,
|
||||
initialDocValue: value,
|
||||
autoFocus: true,
|
||||
selection: {
|
||||
anchor: 0,
|
||||
@ -74,7 +74,6 @@ function CommandBarKclInput({
|
||||
? previouslySetValue.valueText.length
|
||||
: defaultValue.length,
|
||||
},
|
||||
accessKey: 'command-bar',
|
||||
theme:
|
||||
settings.context.app.theme.current === 'system'
|
||||
? getSystemTheme()
|
||||
@ -96,8 +95,12 @@ function CommandBarKclInput({
|
||||
}
|
||||
return tr
|
||||
}),
|
||||
EditorView.updateListener.of((vu: ViewUpdate) => {
|
||||
if (vu.docChanged) {
|
||||
setValue(vu.state.doc.toString())
|
||||
}
|
||||
}),
|
||||
],
|
||||
onChange: (newValue) => setValue(newValue),
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -175,7 +175,11 @@ const FileTreeItem = ({
|
||||
codeManager.code
|
||||
)
|
||||
codeManager.writeToFile()
|
||||
kclManager.executeCode(true, true)
|
||||
|
||||
kclManager.isFirstRender = true
|
||||
kclManager.executeCode(true, true).then(() => {
|
||||
kclManager.isFirstRender = false
|
||||
})
|
||||
} else {
|
||||
// Let the lsp servers know we closed a file.
|
||||
onFileClose(currentFile?.path || null, project?.path || null)
|
||||
|
@ -119,7 +119,7 @@ export default function Gizmo() {
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
aria-label="View orientation gizmo"
|
||||
className="grid place-content-center rounded-full overflow-hidden border border-solid border-primary/50 pointer-events-auto"
|
||||
className="grid place-content-center rounded-full overflow-hidden border border-solid border-primary/50 pointer-events-auto bg-chalkboard-10/70 dark:bg-chalkboard-100/80 backdrop-blur-sm"
|
||||
>
|
||||
<canvas ref={canvasRef} />
|
||||
<ContextMenu
|
||||
|
@ -53,9 +53,8 @@ import {
|
||||
sketchOnExtrudedFace,
|
||||
startSketchOnDefault,
|
||||
} from 'lang/modifyAst'
|
||||
import { Program, VariableDeclaration, parse, recast } from 'lang/wasm'
|
||||
import { Program, parse, recast } from 'lang/wasm'
|
||||
import {
|
||||
getNodeFromPath,
|
||||
getNodePathFromSourceRange,
|
||||
hasExtrudableGeometry,
|
||||
isSingleCursorInPipe,
|
||||
@ -64,7 +63,7 @@ import { TEST } from 'env'
|
||||
import { exportFromEngine } from 'lib/exportFromEngine'
|
||||
import { Models } from '@kittycad/lib/dist/types/src'
|
||||
import toast from 'react-hot-toast'
|
||||
import { EditorSelection, Transaction } from '@uiw/react-codemirror'
|
||||
import { EditorSelection, Transaction } from '@codemirror/state'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
||||
import { getVarNameModal } from 'hooks/useToolbarGuards'
|
||||
@ -128,7 +127,7 @@ export const ModelingMachineProvider = ({
|
||||
'enable copilot': () => {
|
||||
editorManager.setCopilotEnabled(true)
|
||||
},
|
||||
'sketch exit execute': () => {
|
||||
'sketch exit execute': ({ store }) => {
|
||||
;(async () => {
|
||||
await sceneInfra.camControls.snapToPerspectiveBeforeHandingBackControlToEngine()
|
||||
|
||||
@ -162,7 +161,12 @@ export const ModelingMachineProvider = ({
|
||||
})
|
||||
}
|
||||
|
||||
kclManager.executeCode(true)
|
||||
store.videoElement?.pause()
|
||||
kclManager.executeCode(true).then(() => {
|
||||
if (engineCommandManager.engineConnection?.freezeFrame) return
|
||||
|
||||
store.videoElement?.play()
|
||||
})
|
||||
})()
|
||||
},
|
||||
'Set mouse state': assign({
|
||||
@ -438,17 +442,6 @@ export const ModelingMachineProvider = ({
|
||||
if (selectionRanges.codeBasedSelections.length <= 0) return false
|
||||
return true
|
||||
},
|
||||
'Sketch is empty': ({ sketchDetails }) => {
|
||||
const node = getNodeFromPath<VariableDeclaration>(
|
||||
kclManager.ast,
|
||||
sketchDetails?.sketchPathToNode || [],
|
||||
'VariableDeclaration'
|
||||
)
|
||||
// This should not be returning false, and it should be caught
|
||||
// but we need to simulate old behavior to move on.
|
||||
if (err(node)) return false
|
||||
return node.node?.declarations?.[0]?.init.type !== 'PipeExpression'
|
||||
},
|
||||
'Selection is on face': ({ selectionRanges }, { data }) => {
|
||||
if (data?.forceNewSketch) return false
|
||||
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast))
|
||||
|
171
src/components/ModelingSidebar/ModelingPanes/CodeEditor.tsx
Normal file
@ -0,0 +1,171 @@
|
||||
import React, {
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
} from 'react'
|
||||
import {
|
||||
EditorState,
|
||||
EditorStateConfig,
|
||||
Extension,
|
||||
StateEffect,
|
||||
} from '@codemirror/state'
|
||||
import { EditorView } from '@codemirror/view'
|
||||
import { oneDark } from '@codemirror/theme-one-dark'
|
||||
|
||||
//reference: https://github.com/sachinraja/rodemirror/blob/main/src/use-first-render.ts
|
||||
const useFirstRender = () => {
|
||||
const firstRender = useRef(true)
|
||||
|
||||
useEffect(() => {
|
||||
firstRender.current = false
|
||||
}, [])
|
||||
|
||||
return firstRender.current
|
||||
}
|
||||
|
||||
const defaultLightThemeOption = EditorView.theme(
|
||||
{
|
||||
'&': {
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
},
|
||||
{
|
||||
dark: false,
|
||||
}
|
||||
)
|
||||
|
||||
interface CodeEditorRef {
|
||||
editor?: HTMLDivElement | null
|
||||
view?: EditorView
|
||||
state?: EditorState
|
||||
}
|
||||
|
||||
interface CodeEditorProps {
|
||||
onCreateEditor?: (view: EditorView | null) => void
|
||||
initialDocValue?: EditorStateConfig['doc']
|
||||
extensions?: Extension
|
||||
theme: 'light' | 'dark'
|
||||
autoFocus?: boolean
|
||||
selection?: EditorStateConfig['selection']
|
||||
}
|
||||
|
||||
interface UseCodeMirror extends CodeEditorProps {
|
||||
container?: HTMLDivElement | null
|
||||
}
|
||||
|
||||
const CodeEditor = forwardRef<CodeEditorRef, CodeEditorProps>((props, ref) => {
|
||||
const {
|
||||
onCreateEditor,
|
||||
extensions = [],
|
||||
initialDocValue,
|
||||
theme,
|
||||
autoFocus = false,
|
||||
selection,
|
||||
} = props
|
||||
const editor = useRef<HTMLDivElement>(null)
|
||||
|
||||
const { view, state, container } = useCodeMirror({
|
||||
container: editor.current,
|
||||
onCreateEditor,
|
||||
extensions,
|
||||
initialDocValue,
|
||||
theme,
|
||||
autoFocus,
|
||||
selection,
|
||||
})
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({ editor: editor.current, view: view, state: state }),
|
||||
[editor, container, view, state]
|
||||
)
|
||||
|
||||
return <div ref={editor}></div>
|
||||
})
|
||||
|
||||
export function useCodeMirror(props: UseCodeMirror) {
|
||||
const {
|
||||
onCreateEditor,
|
||||
extensions = [],
|
||||
initialDocValue,
|
||||
theme,
|
||||
autoFocus = false,
|
||||
selection,
|
||||
} = props
|
||||
|
||||
const [container, setContainer] = useState<HTMLDivElement | null>()
|
||||
const [view, setView] = useState<EditorView>()
|
||||
const [state, setState] = useState<EditorState>()
|
||||
|
||||
const isFirstRender = useFirstRender()
|
||||
|
||||
const targetExtensions = useMemo(() => {
|
||||
let exts = Array.isArray(extensions) ? extensions : []
|
||||
if (theme === 'dark') {
|
||||
exts = [...exts, oneDark]
|
||||
} else if (theme === 'light') {
|
||||
exts = [...exts, defaultLightThemeOption]
|
||||
}
|
||||
|
||||
return exts
|
||||
}, [extensions, theme])
|
||||
|
||||
useEffect(() => {
|
||||
if (container && !state) {
|
||||
const config = {
|
||||
doc: initialDocValue,
|
||||
selection,
|
||||
extensions: [...Array.of(extensions)],
|
||||
}
|
||||
const stateCurrent = EditorState.create(config)
|
||||
setState(stateCurrent)
|
||||
if (!view) {
|
||||
const viewCurrent = new EditorView({
|
||||
state: stateCurrent,
|
||||
parent: container,
|
||||
})
|
||||
setView(viewCurrent)
|
||||
onCreateEditor && onCreateEditor(viewCurrent)
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
if (view) {
|
||||
setState(undefined)
|
||||
setView(undefined)
|
||||
}
|
||||
}
|
||||
}, [container, state])
|
||||
|
||||
useEffect(() => setContainer(props.container), [props.container])
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (view) {
|
||||
view.destroy()
|
||||
setView(undefined)
|
||||
}
|
||||
},
|
||||
[view]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (autoFocus && view) {
|
||||
view.focus()
|
||||
}
|
||||
}, [autoFocus, view])
|
||||
|
||||
useEffect(() => {
|
||||
if (view && !isFirstRender) {
|
||||
view.dispatch({
|
||||
effects: StateEffect.reconfigure.of(targetExtensions),
|
||||
})
|
||||
}
|
||||
}, [targetExtensions])
|
||||
|
||||
return { view, setView, container, setContainer, state, setState }
|
||||
}
|
||||
|
||||
export default CodeEditor
|
@ -1,4 +1,3 @@
|
||||
import ReactCodeMirror from '@uiw/react-codemirror'
|
||||
import { TEST } from 'env'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { Themes, getSystemTheme } from 'lib/theme'
|
||||
@ -43,6 +42,7 @@ import {
|
||||
closeBracketsKeymap,
|
||||
completionKeymap,
|
||||
} from '@codemirror/autocomplete'
|
||||
import CodeEditor from './CodeEditor'
|
||||
|
||||
export const editorShortcutMeta = {
|
||||
formatCode: {
|
||||
@ -185,15 +185,15 @@ export const KclEditorPane = () => {
|
||||
id="code-mirror-override"
|
||||
className={'absolute inset-0 ' + (cursorBlinking.current ? 'blink' : '')}
|
||||
>
|
||||
<ReactCodeMirror
|
||||
value={initialCode.current}
|
||||
<CodeEditor
|
||||
initialDocValue={initialCode.current}
|
||||
extensions={editorExtensions}
|
||||
theme={theme}
|
||||
onCreateEditor={(_editorView) => {
|
||||
if (_editorView === null) return
|
||||
|
||||
editorManager.setEditorView(_editorView)
|
||||
}}
|
||||
indentWithTab={false}
|
||||
basicSetup={false}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
@ -6,14 +6,12 @@ import React, { useMemo } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import Tooltip from './Tooltip'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useAppState } from 'AppState'
|
||||
|
||||
export const RefreshButton = ({ children }: React.PropsWithChildren) => {
|
||||
const { auth } = useSettingsAuthContext()
|
||||
const token = auth?.context?.token
|
||||
const { htmlRef } = useAppState()
|
||||
const coreDumpManager = useMemo(
|
||||
() => new CoreDumpManager(engineCommandManager, htmlRef, token),
|
||||
() => new CoreDumpManager(engineCommandManager, token),
|
||||
[]
|
||||
)
|
||||
|
||||
|
@ -175,7 +175,12 @@ export const SettingsAuthProviderBase = ({
|
||||
id: `${event.type}.success`,
|
||||
})
|
||||
},
|
||||
'Execute AST': () => kclManager.executeCode(true, true),
|
||||
'Execute AST': () => {
|
||||
kclManager.isFirstRender = true
|
||||
kclManager.executeCode(true, true).then(() => {
|
||||
kclManager.isFirstRender = false
|
||||
})
|
||||
},
|
||||
},
|
||||
services: {
|
||||
'Persist settings': (context) =>
|
||||
|
@ -8,14 +8,17 @@ import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||
import { ClientSideScene } from 'clientSideScene/ClientSideSceneComp'
|
||||
import { butName } from 'lib/cameraControls'
|
||||
import { sendSelectEventToEngine } from 'lib/selections'
|
||||
import { kclManager, engineCommandManager } from 'lib/singletons'
|
||||
|
||||
export const Stream = () => {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [isFirstRender, setIsFirstRender] = useState(kclManager.isFirstRender)
|
||||
const [clickCoords, setClickCoords] = useState<{ x: number; y: number }>()
|
||||
const videoRef = useRef<HTMLVideoElement>(null)
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const { state, send, context } = useModelingContext()
|
||||
const { overallState } = useNetworkContext()
|
||||
const [isFreezeFrame, setIsFreezeFrame] = useState(false)
|
||||
|
||||
const isNetworkOkay =
|
||||
overallState === NetworkHealthState.Ok ||
|
||||
@ -47,12 +50,71 @@ export const Stream = () => {
|
||||
globalThis?.window?.document?.addEventListener('paste', handlePaste, {
|
||||
capture: true,
|
||||
})
|
||||
return () =>
|
||||
|
||||
// Teardown everything if we go hidden or reconnect
|
||||
if (globalThis?.window?.document) {
|
||||
globalThis.window.document.onvisibilitychange = () => {
|
||||
if (globalThis.window.document.visibilityState === 'hidden') {
|
||||
videoRef.current?.pause()
|
||||
setIsFreezeFrame(true)
|
||||
window.requestAnimationFrame(() => {
|
||||
engineCommandManager.engineConnection?.tearDown({ freeze: true })
|
||||
})
|
||||
} else {
|
||||
engineCommandManager.engineConnection?.connect(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const IDLE_TIME_MS = 1000 * 20
|
||||
let timeoutIdIdle: ReturnType<typeof setTimeout> | undefined = undefined
|
||||
|
||||
const onIdle = () => {
|
||||
videoRef.current?.pause()
|
||||
setIsFreezeFrame(true)
|
||||
kclManager.isFirstRender = true
|
||||
setIsFirstRender(true)
|
||||
// Give video time to pause
|
||||
window.requestAnimationFrame(() => {
|
||||
engineCommandManager.engineConnection?.tearDown({ freeze: true })
|
||||
})
|
||||
}
|
||||
const onAnyInput = () => {
|
||||
if (!engineCommandManager.engineConnection?.isReady()) {
|
||||
engineCommandManager.engineConnection?.connect(true)
|
||||
}
|
||||
clearTimeout(timeoutIdIdle)
|
||||
timeoutIdIdle = setTimeout(onIdle, IDLE_TIME_MS)
|
||||
}
|
||||
|
||||
globalThis?.window?.document?.addEventListener('keydown', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('mousemove', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('mousedown', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('scroll', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('touchstart', onAnyInput)
|
||||
|
||||
timeoutIdIdle = setTimeout(onIdle, IDLE_TIME_MS)
|
||||
|
||||
return () => {
|
||||
globalThis?.window?.document?.removeEventListener('paste', handlePaste, {
|
||||
capture: true,
|
||||
})
|
||||
globalThis?.window?.document?.removeEventListener('keydown', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener('mousemove', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener('mousedown', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener('scroll', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener(
|
||||
'touchstart',
|
||||
onAnyInput
|
||||
)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setIsFirstRender(kclManager.isFirstRender)
|
||||
if (!kclManager.isFirstRender) videoRef.current?.play()
|
||||
}, [kclManager.isFirstRender])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
typeof window === 'undefined' ||
|
||||
@ -61,7 +123,17 @@ export const Stream = () => {
|
||||
return
|
||||
if (!videoRef.current) return
|
||||
if (!context.store?.mediaStream) return
|
||||
|
||||
// Do not immediately play the stream!
|
||||
videoRef.current.srcObject = context.store.mediaStream
|
||||
videoRef.current.pause()
|
||||
|
||||
send({
|
||||
type: 'Set context',
|
||||
data: {
|
||||
videoElement: videoRef.current,
|
||||
},
|
||||
})
|
||||
}, [context.store?.mediaStream])
|
||||
|
||||
const handleMouseDown: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||
@ -159,17 +231,16 @@ export const Stream = () => {
|
||||
<ClientSideScene
|
||||
cameraControls={settings.context.modeling.mouseControls.current}
|
||||
/>
|
||||
{!isNetworkOkay && !isLoading && (
|
||||
{(!isNetworkOkay || isLoading || isFirstRender) && !isFreezeFrame && (
|
||||
<div className="text-center absolute inset-0">
|
||||
<Loading>
|
||||
<span data-testid="loading-stream">Stream disconnected...</span>
|
||||
</Loading>
|
||||
</div>
|
||||
)}
|
||||
{isLoading && (
|
||||
<div className="text-center absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||
<Loading>
|
||||
<span data-testid="loading-stream">Loading stream...</span>
|
||||
{!isNetworkOkay && !isLoading ? (
|
||||
<span data-testid="loading-stream">Stream disconnected...</span>
|
||||
) : !isLoading && isFirstRender ? (
|
||||
<span data-testid="loading-stream">Building scene...</span>
|
||||
) : (
|
||||
<span data-testid="loading-stream">Loading stream...</span>
|
||||
)}
|
||||
</Loading>
|
||||
</div>
|
||||
)}
|
||||
|
54
src/components/UnitsMenu.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import { Popover } from '@headlessui/react'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { baseUnitLabels, baseUnitsUnion } from 'lib/settings/settingsTypes'
|
||||
|
||||
export function UnitsMenu() {
|
||||
const { settings } = useSettingsAuthContext()
|
||||
return (
|
||||
<Popover className="relative pointer-events-auto">
|
||||
{({ close }) => (
|
||||
<>
|
||||
<Popover.Button
|
||||
className={`flex items-center gap-2 px-3 py-1
|
||||
text-xs text-primary bg-chalkboard-10/70 dark:bg-chalkboard-100/80 backdrop-blur-sm
|
||||
border !border-primary/50 rounded-full`}
|
||||
>
|
||||
<div className="w-4 h-[1px] bg-primary relative">
|
||||
<div className="absolute w-[1px] h-[1em] bg-primary left-0 top-1/2 -translate-y-1/2"></div>
|
||||
<div className="absolute w-[1px] h-[1em] bg-primary right-0 top-1/2 -translate-y-1/2"></div>
|
||||
</div>
|
||||
<span className="sr-only">Current units are: </span>
|
||||
{settings.context.modeling.defaultUnit.current}
|
||||
</Popover.Button>
|
||||
<Popover.Panel
|
||||
className={`absolute bottom-full right-0 mb-2 w-48 bg-chalkboard-10 dark:bg-chalkboard-90
|
||||
border border-solid border-chalkboard-10 dark:border-chalkboard-90 rounded
|
||||
shadow-lg`}
|
||||
>
|
||||
<ul className="relative flex flex-col gap-0.5 items-stretch content-stretch">
|
||||
{baseUnitsUnion.map((unit) => (
|
||||
<li key={unit} className="contents">
|
||||
<button
|
||||
className="flex items-center gap-2 py-1 px-2 cursor-pointer hover:bg-chalkboard-20 dark:hover:bg-chalkboard-80 border-none text-left"
|
||||
onClick={() => {
|
||||
settings.send({
|
||||
type: 'set.modeling.defaultUnit',
|
||||
data: {
|
||||
level: 'project',
|
||||
value: unit,
|
||||
},
|
||||
})
|
||||
close()
|
||||
}}
|
||||
>
|
||||
{baseUnitLabels[unit]}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Popover.Panel>
|
||||
</>
|
||||
)}
|
||||
</Popover>
|
||||
)
|
||||
}
|
@ -222,11 +222,7 @@ export default class EditorManager {
|
||||
return
|
||||
}
|
||||
|
||||
const ignoreEvents: ModelingMachineEvent['type'][] = [
|
||||
'Equip Line tool',
|
||||
'Equip tangential arc to',
|
||||
'Equip rectangle tool',
|
||||
]
|
||||
const ignoreEvents: ModelingMachineEvent['type'][] = ['change tool']
|
||||
|
||||
if (!this._modelingEvent) {
|
||||
return
|
||||
|
@ -23,17 +23,12 @@ import {
|
||||
} from '@codemirror/state'
|
||||
import { completionStatus } from '@codemirror/autocomplete'
|
||||
import {
|
||||
TransactionAnnotation,
|
||||
offsetToPos,
|
||||
posToOffset,
|
||||
LanguageServerOptions,
|
||||
LanguageServerClient,
|
||||
docPathFacet,
|
||||
languageId,
|
||||
TransactionInfo,
|
||||
updateInfo,
|
||||
RelevantUpdate,
|
||||
lspPlugin,
|
||||
} from '@kittycad/codemirror-lsp-client'
|
||||
import { deferExecution } from 'lib/utils'
|
||||
import { CopilotLspCompletionParams } from 'wasm-lib/kcl/bindings/CopilotLspCompletionParams'
|
||||
@ -98,11 +93,6 @@ const completionDecoration = StateField.define<CompletionState>({
|
||||
return state
|
||||
}
|
||||
|
||||
// We only care about transactions with effects.
|
||||
if (!transaction.effects) {
|
||||
return state
|
||||
}
|
||||
|
||||
for (const effect of transaction.effects) {
|
||||
if (effect.is(addSuggestion)) {
|
||||
// When adding a suggestion, we set th ghostText
|
||||
@ -198,95 +188,68 @@ const completionDecoration = StateField.define<CompletionState>({
|
||||
),
|
||||
})
|
||||
|
||||
export const relevantUpdate = (update: ViewUpdate): RelevantUpdate => {
|
||||
const infos = updateInfo(update)
|
||||
|
||||
// Make sure we are not in a snippet
|
||||
if (infos.some((info: TransactionInfo) => info.inSnippet)) {
|
||||
return {
|
||||
overall: false,
|
||||
userSelect: false,
|
||||
time: null,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
overall: infos.some(
|
||||
(info: TransactionInfo) =>
|
||||
info.transaction.annotation(copilotPluginEvent.type) !== undefined ||
|
||||
info.annotations.includes(TransactionAnnotation.UserSelect) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserInput) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserDelete) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserUndo) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserRedo) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserMove)
|
||||
),
|
||||
userSelect: infos.some((info: TransactionInfo) =>
|
||||
info.annotations.includes(TransactionAnnotation.UserSelect)
|
||||
),
|
||||
time: infos.length ? infos[0].time : null,
|
||||
}
|
||||
}
|
||||
|
||||
// A view plugin that requests completions from the server after a delay
|
||||
export class CompletionRequester implements PluginValue {
|
||||
private client: LanguageServerClient
|
||||
private lastPos: number = 0
|
||||
private viewUpdate: ViewUpdate | null = null
|
||||
|
||||
private queuedUids: string[] = []
|
||||
|
||||
private _deffererCodeUpdate = deferExecution(() => {
|
||||
if (this.viewUpdate === null) {
|
||||
return
|
||||
}
|
||||
|
||||
this.requestCompletions()
|
||||
}, changesDelay)
|
||||
|
||||
private _deffererUserSelect = deferExecution(() => {
|
||||
if (this.viewUpdate === null) {
|
||||
return
|
||||
}
|
||||
|
||||
this.rejectSuggestionCommand()
|
||||
}, changesDelay)
|
||||
|
||||
constructor(client: LanguageServerClient) {
|
||||
constructor(readonly view: EditorView, client: LanguageServerClient) {
|
||||
this.client = client
|
||||
}
|
||||
|
||||
update(viewUpdate: ViewUpdate) {
|
||||
this.viewUpdate = viewUpdate
|
||||
|
||||
const isRelevant = relevantUpdate(viewUpdate)
|
||||
if (!isRelevant.overall) {
|
||||
return
|
||||
}
|
||||
|
||||
// If we have a user select event, we want to clear the ghost text.
|
||||
if (isRelevant.userSelect) {
|
||||
this._deffererUserSelect(true)
|
||||
return
|
||||
}
|
||||
|
||||
// Make sure we are in a state where we can request completions.
|
||||
if (!editorManager.copilotEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
this.lastPos = this.viewUpdate.state.selection.main.head
|
||||
this._deffererCodeUpdate(true)
|
||||
let isUserSelect = false
|
||||
let isRelevant = false
|
||||
for (const tr of viewUpdate.transactions) {
|
||||
if (tr.isUserEvent('select')) {
|
||||
isUserSelect = true
|
||||
break
|
||||
} else if (tr.isUserEvent('input')) {
|
||||
isRelevant = true
|
||||
} else if (tr.isUserEvent('delete')) {
|
||||
isRelevant = true
|
||||
} else if (tr.isUserEvent('undo')) {
|
||||
isRelevant = true
|
||||
} else if (tr.isUserEvent('redo')) {
|
||||
isRelevant = true
|
||||
} else if (tr.isUserEvent('move')) {
|
||||
isRelevant = true
|
||||
} else if (tr.annotation(copilotPluginEvent.type) !== undefined) {
|
||||
isRelevant = true
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a user select event, we want to clear the ghost text.
|
||||
if (isUserSelect) {
|
||||
this._deffererUserSelect(true)
|
||||
return
|
||||
}
|
||||
|
||||
if (!isRelevant) {
|
||||
return
|
||||
}
|
||||
|
||||
this.lastPos = this.view.state.selection.main.head
|
||||
if (viewUpdate.docChanged) this._deffererCodeUpdate(true)
|
||||
}
|
||||
|
||||
ghostText(): GhostText | null {
|
||||
if (!this.viewUpdate) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
this.viewUpdate.view.state.field(completionDecoration)?.ghostText || null
|
||||
)
|
||||
return this.view.state.field(completionDecoration)?.ghostText || null
|
||||
}
|
||||
|
||||
containsGhostText(): boolean {
|
||||
@ -294,33 +257,23 @@ export class CompletionRequester implements PluginValue {
|
||||
}
|
||||
|
||||
autocompleting(): boolean {
|
||||
if (!this.viewUpdate) {
|
||||
return false
|
||||
}
|
||||
|
||||
return completionStatus(this.viewUpdate.state) === 'active'
|
||||
return completionStatus(this.view.state) === 'active'
|
||||
}
|
||||
|
||||
notFocused(): boolean {
|
||||
if (!this.viewUpdate) {
|
||||
return true
|
||||
}
|
||||
|
||||
return !this.viewUpdate.view.hasFocus
|
||||
return !this.view.hasFocus
|
||||
}
|
||||
|
||||
async requestCompletions(): Promise<void> {
|
||||
if (
|
||||
this.viewUpdate === null ||
|
||||
this.containsGhostText() ||
|
||||
this.autocompleting() ||
|
||||
this.notFocused() ||
|
||||
!this.viewUpdate.docChanged
|
||||
this.notFocused()
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const pos = this.viewUpdate.state.selection.main.head
|
||||
const pos = this.view.state.selection.main.head
|
||||
|
||||
// Check if the position has changed
|
||||
if (pos !== this.lastPos) {
|
||||
@ -328,7 +281,7 @@ export class CompletionRequester implements PluginValue {
|
||||
}
|
||||
|
||||
// Get the current position and source
|
||||
const state = this.viewUpdate.state
|
||||
const state = this.view.state
|
||||
const dUri = state.facet(docPathFacet)
|
||||
|
||||
// Request completion from the server
|
||||
@ -396,14 +349,14 @@ export class CompletionRequester implements PluginValue {
|
||||
|
||||
// Dispatch an effect to add the suggestion
|
||||
// If the completion starts before the end of the line, check the end of the line with the end of the completion.
|
||||
const line = this.viewUpdate.view.state.doc.lineAt(pos)
|
||||
const line = this.view.state.doc.lineAt(pos)
|
||||
if (line.to !== pos) {
|
||||
const ending = this.viewUpdate.view.state.doc.sliceString(pos, line.to)
|
||||
const ending = this.view.state.doc.sliceString(pos, line.to)
|
||||
if (displayText.endsWith(ending)) {
|
||||
displayText = displayText.slice(0, displayText.length - ending.length)
|
||||
} else if (displayText.includes(ending)) {
|
||||
// Remove the ending
|
||||
this.viewUpdate.view.dispatch({
|
||||
this.view.dispatch({
|
||||
changes: {
|
||||
from: pos,
|
||||
to: line.to,
|
||||
@ -416,7 +369,7 @@ export class CompletionRequester implements PluginValue {
|
||||
}
|
||||
}
|
||||
|
||||
this.viewUpdate.view.dispatch({
|
||||
this.view.dispatch({
|
||||
changes: {
|
||||
from: pos,
|
||||
to: pos,
|
||||
@ -442,10 +395,6 @@ export class CompletionRequester implements PluginValue {
|
||||
}
|
||||
|
||||
acceptSuggestionCommand(): boolean {
|
||||
if (!this.viewUpdate) {
|
||||
return false
|
||||
}
|
||||
|
||||
const ghostText = this.ghostText()
|
||||
if (!ghostText) {
|
||||
return false
|
||||
@ -463,7 +412,7 @@ export class CompletionRequester implements PluginValue {
|
||||
|
||||
const suggestion = ghostText.text
|
||||
|
||||
this.viewUpdate.view.dispatch({
|
||||
this.view.dispatch({
|
||||
changes: {
|
||||
from: ghostTextStart,
|
||||
to: ghostTextEnd,
|
||||
@ -475,7 +424,7 @@ export class CompletionRequester implements PluginValue {
|
||||
|
||||
const tmpTextEnd = replacementEnd - (ghostTextEnd - ghostTextStart)
|
||||
|
||||
this.viewUpdate.view.dispatch({
|
||||
this.view.dispatch({
|
||||
changes: {
|
||||
from: actualTextStart,
|
||||
to: tmpTextEnd,
|
||||
@ -490,10 +439,6 @@ export class CompletionRequester implements PluginValue {
|
||||
}
|
||||
|
||||
rejectSuggestionCommand(): boolean {
|
||||
if (!this.viewUpdate) {
|
||||
return false
|
||||
}
|
||||
|
||||
const ghostText = this.ghostText()
|
||||
if (!ghostText) {
|
||||
return false
|
||||
@ -503,7 +448,7 @@ export class CompletionRequester implements PluginValue {
|
||||
const ghostTextStart = ghostText.displayPos
|
||||
const ghostTextEnd = ghostText.endGhostText
|
||||
|
||||
this.viewUpdate.view.dispatch({
|
||||
this.view.dispatch({
|
||||
changes: {
|
||||
from: ghostTextStart,
|
||||
to: ghostTextEnd,
|
||||
@ -521,10 +466,6 @@ export class CompletionRequester implements PluginValue {
|
||||
}
|
||||
|
||||
sameKeyCommand(key: string) {
|
||||
if (!this.viewUpdate) {
|
||||
return false
|
||||
}
|
||||
|
||||
const ghostText = this.ghostText()
|
||||
if (!ghostText) {
|
||||
return false
|
||||
@ -534,10 +475,10 @@ export class CompletionRequester implements PluginValue {
|
||||
|
||||
// When we type a key that is the same as the first letter of the suggestion, we delete the first letter of the suggestion and carry through with the original keypress
|
||||
const ghostTextStart = ghostText.displayPos
|
||||
const indent = this.viewUpdate.view.state.facet(indentUnit)
|
||||
const indent = this.view.state.facet(indentUnit)
|
||||
|
||||
if (key === tabKey && ghostText.displayText.startsWith(indent)) {
|
||||
this.viewUpdate.view.dispatch({
|
||||
this.view.dispatch({
|
||||
selection: { anchor: ghostTextStart + indent.length },
|
||||
effects: typeFirst.of(indent.length),
|
||||
annotations: [copilotPluginEvent, Transaction.addToHistory.of(false)],
|
||||
@ -551,7 +492,7 @@ export class CompletionRequester implements PluginValue {
|
||||
return this.acceptSuggestionCommand()
|
||||
} else {
|
||||
// Use this to delete the first letter of the suggestion
|
||||
this.viewUpdate.view.dispatch({
|
||||
this.view.dispatch({
|
||||
selection: { anchor: ghostTextStart + 1 },
|
||||
effects: typeFirst.of(1),
|
||||
annotations: [copilotPluginEvent, Transaction.addToHistory.of(false)],
|
||||
@ -598,7 +539,7 @@ export class CompletionRequester implements PluginValue {
|
||||
export const copilotPlugin = (options: LanguageServerOptions): Extension => {
|
||||
let plugin: CompletionRequester | null = null
|
||||
const completionPlugin = ViewPlugin.define(
|
||||
(view) => (plugin = new CompletionRequester(options.client))
|
||||
(view) => (plugin = new CompletionRequester(view, options.client))
|
||||
)
|
||||
|
||||
const domHandlers = EditorView.domEventHandlers({
|
||||
@ -625,8 +566,6 @@ export const copilotPlugin = (options: LanguageServerOptions): Extension => {
|
||||
})
|
||||
|
||||
const rejectSuggestionCommand = (view: EditorView): boolean => {
|
||||
if (view.plugin === null) return false
|
||||
|
||||
// Get the current plugin from the map.
|
||||
const p = view.plugin(completionPlugin)
|
||||
if (p === null) return false
|
||||
@ -681,7 +620,6 @@ export const copilotPlugin = (options: LanguageServerOptions): Extension => {
|
||||
)
|
||||
|
||||
return [
|
||||
lspPlugin(options),
|
||||
completionPlugin,
|
||||
copilotAutocompleteKeymapExt,
|
||||
domHandlers,
|
||||
|
@ -2,12 +2,9 @@ import { Extension } from '@codemirror/state'
|
||||
import { ViewPlugin, PluginValue, ViewUpdate } from '@codemirror/view'
|
||||
import {
|
||||
LanguageServerOptions,
|
||||
updateInfo,
|
||||
TransactionInfo,
|
||||
RelevantUpdate,
|
||||
TransactionAnnotation,
|
||||
LanguageServerClient,
|
||||
lspPlugin,
|
||||
lspFormatCodeEvent,
|
||||
} from '@kittycad/codemirror-lsp-client'
|
||||
import { deferExecution } from 'lib/utils'
|
||||
import { codeManager, editorManager, kclManager } from 'lib/singletons'
|
||||
@ -18,34 +15,6 @@ import { UpdateCanExecuteResponse } from 'wasm-lib/kcl/bindings/UpdateCanExecute
|
||||
|
||||
const changesDelay = 600
|
||||
|
||||
export const relevantUpdate = (update: ViewUpdate): RelevantUpdate => {
|
||||
const infos = updateInfo(update)
|
||||
// Make sure we are not in a snippet
|
||||
if (infos.some((info: TransactionInfo) => info.inSnippet)) {
|
||||
return {
|
||||
overall: false,
|
||||
userSelect: false,
|
||||
time: null,
|
||||
}
|
||||
}
|
||||
return {
|
||||
overall: infos.some(
|
||||
(info: TransactionInfo) =>
|
||||
info.annotations.includes(TransactionAnnotation.UserSelect) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserInput) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserDelete) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserUndo) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserRedo) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserMove) ||
|
||||
info.annotations.includes(TransactionAnnotation.FormatCode)
|
||||
),
|
||||
userSelect: infos.some((info: TransactionInfo) =>
|
||||
info.annotations.includes(TransactionAnnotation.UserSelect)
|
||||
),
|
||||
time: infos.length ? infos[0].time : null,
|
||||
}
|
||||
}
|
||||
|
||||
// A view plugin that requests completions from the server after a delay
|
||||
export class KclPlugin implements PluginValue {
|
||||
private viewUpdate: ViewUpdate | null = null
|
||||
@ -75,18 +44,38 @@ export class KclPlugin implements PluginValue {
|
||||
this.viewUpdate = viewUpdate
|
||||
editorManager.setEditorView(viewUpdate.view)
|
||||
|
||||
const isRelevant = relevantUpdate(viewUpdate)
|
||||
if (!isRelevant.overall) {
|
||||
return
|
||||
let isUserSelect = false
|
||||
let isRelevant = false
|
||||
for (const tr of viewUpdate.transactions) {
|
||||
if (tr.isUserEvent('select')) {
|
||||
isUserSelect = true
|
||||
break
|
||||
} else if (tr.isUserEvent('input')) {
|
||||
isRelevant = true
|
||||
} else if (tr.isUserEvent('delete')) {
|
||||
isRelevant = true
|
||||
} else if (tr.isUserEvent('undo')) {
|
||||
isRelevant = true
|
||||
} else if (tr.isUserEvent('redo')) {
|
||||
isRelevant = true
|
||||
} else if (tr.isUserEvent('move')) {
|
||||
isRelevant = true
|
||||
} else if (tr.annotation(lspFormatCodeEvent.type)) {
|
||||
isRelevant = true
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a user select event, we want to update what parts are
|
||||
// highlighted.
|
||||
if (isRelevant.userSelect) {
|
||||
if (isUserSelect) {
|
||||
this._deffererUserSelect(true)
|
||||
return
|
||||
}
|
||||
|
||||
if (!isRelevant) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!viewUpdate.docChanged) {
|
||||
return
|
||||
}
|
||||
|
@ -46,15 +46,7 @@ class KclLanguage extends Language {
|
||||
|
||||
const parser = new KclParser()
|
||||
|
||||
super(
|
||||
data,
|
||||
// For now let's use the javascript parser.
|
||||
// It works really well and has good syntax highlighting.
|
||||
// We can use our lsp for the rest.
|
||||
parser,
|
||||
[plugin],
|
||||
'kcl'
|
||||
)
|
||||
super(data, parser, [plugin], 'kcl')
|
||||
}
|
||||
}
|
||||
|
||||
|