Compare commits
15 Commits
kcl-0.2.26
...
achalmers/
Author | SHA1 | Date | |
---|---|---|---|
078ffa02b0 | |||
d461b09a4d | |||
9c42c39ba9 | |||
aa3f40e22c | |||
4423ae16dc | |||
1d45bed649 | |||
64aac7bccc | |||
002edeaf19 | |||
5424252dac | |||
30bc85add8 | |||
39a2bd685b | |||
23a3e330f6 | |||
99dd8b87dc | |||
5ff1d9e268 | |||
ce1a37e0bc |
2
.github/workflows/publish-apps-release.yml
vendored
2
.github/workflows/publish-apps-release.yml
vendored
@ -123,7 +123,7 @@ jobs:
|
||||
path: out
|
||||
glob: '*'
|
||||
parent: false
|
||||
destination: 'dl.kittycad.io/releases/modeling-app/test/new-workflow'
|
||||
destination: 'dl.kittycad.io/releases/modeling-app'
|
||||
|
||||
- name: Invalidate bucket cache on latest*.yml and last_download.json files
|
||||
run: |
|
||||
|
@ -96310,6 +96310,14 @@
|
||||
"nonCodeMeta": {
|
||||
"$ref": "#/components/schemas/NonCodeMeta"
|
||||
},
|
||||
"shebang": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Shebang"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -97944,25 +97952,6 @@
|
||||
},
|
||||
"NonCodeValue": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"value"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"shebang"
|
||||
]
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "An inline comment. Here are examples: `1 + 1 // This is an inline comment`. `1 + 1 /* Here's another */`.",
|
||||
"type": "object",
|
||||
@ -98173,6 +98162,28 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Shebang": {
|
||||
"description": "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"content"
|
||||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"ProgramMemory": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -100282,6 +100293,14 @@
|
||||
"nonCodeMeta": {
|
||||
"$ref": "#/components/schemas/NonCodeMeta"
|
||||
},
|
||||
"shebang": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Shebang"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -101916,25 +101935,6 @@
|
||||
},
|
||||
"NonCodeValue": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"value"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"shebang"
|
||||
]
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "An inline comment. Here are examples: `1 + 1 // This is an inline comment`. `1 + 1 /* Here's another */`.",
|
||||
"type": "object",
|
||||
@ -102145,6 +102145,28 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Shebang": {
|
||||
"description": "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"content"
|
||||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"ProgramMemory": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -104258,6 +104280,14 @@
|
||||
"nonCodeMeta": {
|
||||
"$ref": "#/components/schemas/NonCodeMeta"
|
||||
},
|
||||
"shebang": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Shebang"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -105892,25 +105922,6 @@
|
||||
},
|
||||
"NonCodeValue": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"value"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"shebang"
|
||||
]
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "An inline comment. Here are examples: `1 + 1 // This is an inline comment`. `1 + 1 /* Here's another */`.",
|
||||
"type": "object",
|
||||
@ -106121,6 +106132,28 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Shebang": {
|
||||
"description": "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"content"
|
||||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"ProgramMemory": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -137489,6 +137522,14 @@
|
||||
"nonCodeMeta": {
|
||||
"$ref": "#/components/schemas/NonCodeMeta"
|
||||
},
|
||||
"shebang": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Shebang"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -139123,25 +139164,6 @@
|
||||
},
|
||||
"NonCodeValue": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"value"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"shebang"
|
||||
]
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "An inline comment. Here are examples: `1 + 1 // This is an inline comment`. `1 + 1 /* Here's another */`.",
|
||||
"type": "object",
|
||||
@ -139352,6 +139374,28 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Shebang": {
|
||||
"description": "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"content"
|
||||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"ProgramMemory": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -142077,6 +142121,14 @@
|
||||
"nonCodeMeta": {
|
||||
"$ref": "#/components/schemas/NonCodeMeta"
|
||||
},
|
||||
"shebang": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Shebang"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -143711,25 +143763,6 @@
|
||||
},
|
||||
"NonCodeValue": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"value"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"shebang"
|
||||
]
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "An inline comment. Here are examples: `1 + 1 // This is an inline comment`. `1 + 1 /* Here's another */`.",
|
||||
"type": "object",
|
||||
@ -143940,6 +143973,28 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Shebang": {
|
||||
"description": "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"content"
|
||||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"ProgramMemory": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -146666,6 +146721,14 @@
|
||||
"nonCodeMeta": {
|
||||
"$ref": "#/components/schemas/NonCodeMeta"
|
||||
},
|
||||
"shebang": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Shebang"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -148300,25 +148363,6 @@
|
||||
},
|
||||
"NonCodeValue": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"value"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"shebang"
|
||||
]
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "An inline comment. Here are examples: `1 + 1 // This is an inline comment`. `1 + 1 /* Here's another */`.",
|
||||
"type": "object",
|
||||
@ -148529,6 +148573,28 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Shebang": {
|
||||
"description": "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"content"
|
||||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"ProgramMemory": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -150653,6 +150719,14 @@
|
||||
"nonCodeMeta": {
|
||||
"$ref": "#/components/schemas/NonCodeMeta"
|
||||
},
|
||||
"shebang": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Shebang"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -152287,25 +152361,6 @@
|
||||
},
|
||||
"NonCodeValue": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"value"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"shebang"
|
||||
]
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "An inline comment. Here are examples: `1 + 1 // This is an inline comment`. `1 + 1 /* Here's another */`.",
|
||||
"type": "object",
|
||||
@ -152516,6 +152571,28 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Shebang": {
|
||||
"description": "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"content"
|
||||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"ProgramMemory": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -155241,6 +155318,14 @@
|
||||
"nonCodeMeta": {
|
||||
"$ref": "#/components/schemas/NonCodeMeta"
|
||||
},
|
||||
"shebang": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Shebang"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -156875,25 +156960,6 @@
|
||||
},
|
||||
"NonCodeValue": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"value"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"shebang"
|
||||
]
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "An inline comment. Here are examples: `1 + 1 // This is an inline comment`. `1 + 1 /* Here's another */`.",
|
||||
"type": "object",
|
||||
@ -157104,6 +157170,28 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Shebang": {
|
||||
"description": "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"content"
|
||||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"ProgramMemory": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -159213,6 +159301,14 @@
|
||||
"nonCodeMeta": {
|
||||
"$ref": "#/components/schemas/NonCodeMeta"
|
||||
},
|
||||
"shebang": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Shebang"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -160847,25 +160943,6 @@
|
||||
},
|
||||
"NonCodeValue": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"value"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"shebang"
|
||||
]
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "An inline comment. Here are examples: `1 + 1 // This is an inline comment`. `1 + 1 /* Here's another */`.",
|
||||
"type": "object",
|
||||
@ -161076,6 +161153,28 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Shebang": {
|
||||
"description": "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"content"
|
||||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"ProgramMemory": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -163802,6 +163901,14 @@
|
||||
"nonCodeMeta": {
|
||||
"$ref": "#/components/schemas/NonCodeMeta"
|
||||
},
|
||||
"shebang": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Shebang"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -165436,25 +165543,6 @@
|
||||
},
|
||||
"NonCodeValue": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"value"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"shebang"
|
||||
]
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "An inline comment. Here are examples: `1 + 1 // This is an inline comment`. `1 + 1 /* Here's another */`.",
|
||||
"type": "object",
|
||||
@ -165665,6 +165753,28 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Shebang": {
|
||||
"description": "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"content"
|
||||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"ProgramMemory": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
@ -11,23 +11,6 @@ layout: manual
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `shebang`| | No |
|
||||
| `value` |`string`| | No |
|
||||
|
||||
|
||||
----
|
||||
An inline comment. Here are examples: `1 + 1 // This is an inline comment`. `1 + 1 /* Here's another */`.
|
||||
|
||||
**Type:** `object`
|
||||
|
@ -18,6 +18,7 @@ A KCL program top level, or function body.
|
||||
|----------|------|-------------|----------|
|
||||
| `body` |`[` [`BodyItem`](/docs/kcl/types/BodyItem) `]`| | No |
|
||||
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| A KCL program top level, or function body. | No |
|
||||
| `shebang` |[`Shebang`](/docs/kcl/types/Shebang)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
23
docs/kcl/types/Shebang.md
Normal file
23
docs/kcl/types/Shebang.md
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
title: "Shebang"
|
||||
excerpt: "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `content` |`string`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
@ -35,7 +35,7 @@ export class CmdBarFixture {
|
||||
}
|
||||
|
||||
private _serialiseCmdBar = async (): Promise<CmdBarSerialised> => {
|
||||
const reviewForm = await this.page.locator('#review-form')
|
||||
const reviewForm = this.page.locator('#review-form')
|
||||
const getHeaderArgs = async () => {
|
||||
const inputs = await this.page.getByTestId('cmd-bar-input-tab').all()
|
||||
const entries = await Promise.all(
|
||||
|
@ -6,6 +6,7 @@ export class ToolbarFixture {
|
||||
public page: Page
|
||||
|
||||
extrudeButton!: Locator
|
||||
offsetPlaneButton!: Locator
|
||||
startSketchBtn!: Locator
|
||||
lineBtn!: Locator
|
||||
rectangleBtn!: Locator
|
||||
@ -25,6 +26,7 @@ export class ToolbarFixture {
|
||||
reConstruct = (page: Page) => {
|
||||
this.page = page
|
||||
this.extrudeButton = page.getByTestId('extrude')
|
||||
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
||||
this.startSketchBtn = page.getByTestId('sketch')
|
||||
this.lineBtn = page.getByTestId('line')
|
||||
this.rectangleBtn = page.getByTestId('corner-rectangle')
|
||||
|
@ -551,3 +551,53 @@ test(`Verify axis, origin, and horizontal snapping`, async ({
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test(`Offset plane point-and-click`, async ({
|
||||
app,
|
||||
scene,
|
||||
editor,
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
await app.initialise()
|
||||
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 700, y: 150 }
|
||||
const [clickOnXzPlane] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||
const expectedOutput = `plane001 = offsetPlane('XZ', 5)`
|
||||
|
||||
await test.step(`Look for the blue of the XZ plane`, async () => {
|
||||
await scene.expectPixelColor([50, 51, 96], testPoint, 15)
|
||||
})
|
||||
await test.step(`Go through the command bar flow`, async () => {
|
||||
await toolbar.offsetPlaneButton.click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'plane',
|
||||
currentArgValue: '',
|
||||
headerArguments: { Plane: '', Distance: '' },
|
||||
highlightedHeaderArg: 'plane',
|
||||
commandName: 'Offset plane',
|
||||
})
|
||||
await clickOnXzPlane()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'distance',
|
||||
currentArgValue: '5',
|
||||
headerArguments: { Plane: '1 plane', Distance: '' },
|
||||
highlightedHeaderArg: 'distance',
|
||||
commandName: 'Offset plane',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
|
||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||
await editor.expectEditor.toContain(expectedOutput)
|
||||
await editor.expectState({
|
||||
diagnostics: [],
|
||||
activeLines: [expectedOutput],
|
||||
highlightedCode: '',
|
||||
})
|
||||
await scene.expectPixelColor([74, 74, 74], testPoint, 15)
|
||||
})
|
||||
})
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Binary file not shown.
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
@ -56,7 +56,7 @@
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-modal": "^3.16.1",
|
||||
"react-modal-promise": "^1.0.2",
|
||||
"react-router-dom": "^6.27.0",
|
||||
"react-router-dom": "^6.28.0",
|
||||
"sketch-helpers": "^0.0.4",
|
||||
"three": "^0.166.1",
|
||||
"ua-parser-js": "^1.0.37",
|
||||
@ -205,7 +205,7 @@
|
||||
"setimmediate": "^1.0.5",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"ts-node": "^10.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^5.4.6",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-package-version": "^1.1.0",
|
||||
|
@ -24,7 +24,7 @@
|
||||
"@lezer/highlight": "^1.2.0",
|
||||
"@ts-stack/markdown": "^1.5.0",
|
||||
"json-rpc-2.0": "^1.7.0",
|
||||
"typescript": "^5.5.2",
|
||||
"typescript": "^5.7.2",
|
||||
"vscode-languageserver-protocol": "^3.17.5",
|
||||
"vscode-uri": "^3.0.8"
|
||||
},
|
||||
|
@ -26,7 +26,7 @@ export default class StreamDemuxer extends Queue<Uint8Array> {
|
||||
|
||||
private async start(): Promise<void> {
|
||||
let contentLength: null | number = null
|
||||
let buffer = new Uint8Array()
|
||||
let buffer: Uint8Array = new Uint8Array()
|
||||
|
||||
for await (const bytes of this) {
|
||||
buffer = Bytes.append(Uint8Array, buffer, bytes)
|
||||
|
@ -95,6 +95,10 @@ export default class Queue<T>
|
||||
return this
|
||||
}
|
||||
|
||||
[Symbol.asyncDispose](): Promise<void> {
|
||||
return this.close()
|
||||
}
|
||||
|
||||
get locked(): boolean {
|
||||
return this.#stream.locked
|
||||
}
|
||||
|
@ -182,10 +182,10 @@ tslib@^2.3.0:
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0"
|
||||
integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==
|
||||
|
||||
typescript@^5.5.2:
|
||||
version "5.5.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.2.tgz#c26f023cb0054e657ce04f72583ea2d85f8d0507"
|
||||
integrity sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==
|
||||
typescript@^5.7.2:
|
||||
version "5.7.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6"
|
||||
integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==
|
||||
|
||||
undici-types@~5.26.4:
|
||||
version "5.26.5"
|
||||
|
@ -22,7 +22,7 @@ import {
|
||||
import { Coords2d, compareVec2Epsilon2 } from 'lang/std/sketch'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import * as TWEEN from '@tweenjs/tween.js'
|
||||
import { Axis } from 'lib/selections'
|
||||
import { Axis, NonCodeSelection } from 'lib/selections'
|
||||
import { type BaseUnit } from 'lib/settings/settingsTypes'
|
||||
import { CameraControls } from './CameraControls'
|
||||
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||
@ -654,7 +654,7 @@ export class SceneInfra {
|
||||
await this.onClickCallback({ mouseEvent, intersects })
|
||||
}
|
||||
}
|
||||
updateOtherSelectionColors = (otherSelections: Axis[]) => {
|
||||
updateOtherSelectionColors = (otherSelections: NonCodeSelection[]) => {
|
||||
const axisGroup = this.scene.children.find(
|
||||
({ userData }) => userData?.type === AXIS_GROUP
|
||||
)
|
||||
|
@ -1,21 +1,26 @@
|
||||
import { useSelector } from '@xstate/react'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { Artifact } from 'lang/std/artifactGraph'
|
||||
import { CommandArgument } from 'lib/commandTypes'
|
||||
import {
|
||||
canSubmitSelectionArg,
|
||||
getSelectionType,
|
||||
getSelectionCountByType,
|
||||
getSelectionTypeDisplayText,
|
||||
} from 'lib/selections'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import { toSync } from 'lib/utils'
|
||||
import { modelingMachine } from 'machines/modelingMachine'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { StateFrom } from 'xstate'
|
||||
|
||||
const semanticEntityNames: { [key: string]: Array<Artifact['type']> } = {
|
||||
const semanticEntityNames: {
|
||||
[key: string]: Array<Artifact['type'] | 'defaultPlane'>
|
||||
} = {
|
||||
face: ['wall', 'cap', 'solid2D'],
|
||||
edge: ['segment', 'sweepEdge', 'edgeCutEdge'],
|
||||
point: [],
|
||||
plane: ['defaultPlane'],
|
||||
}
|
||||
|
||||
function getSemanticSelectionType(selectionType: Array<Artifact['type']>) {
|
||||
@ -43,21 +48,13 @@ function CommandBarSelectionInput({
|
||||
stepBack: () => void
|
||||
onSubmit: (data: unknown) => void
|
||||
}) {
|
||||
const { code } = useKclContext()
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
||||
const [hasSubmitted, setHasSubmitted] = useState(false)
|
||||
const selection = useSelector(arg.machineActor, selectionSelector)
|
||||
const selectionsByType = useMemo(() => {
|
||||
const selectionRangeEnd = !selection
|
||||
? null
|
||||
: selection?.graphSelections[0]?.codeRef?.range[1]
|
||||
return !selectionRangeEnd || selectionRangeEnd === code.length || !selection
|
||||
? 'none'
|
||||
: !selection
|
||||
? 'none'
|
||||
: getSelectionType(selection)
|
||||
}, [selection, code])
|
||||
return getSelectionCountByType(selection)
|
||||
}, [selection])
|
||||
const canSubmitSelection = useMemo<boolean>(
|
||||
() => canSubmitSelectionArg(selectionsByType, arg),
|
||||
[selectionsByType]
|
||||
@ -67,6 +64,30 @@ function CommandBarSelectionInput({
|
||||
inputRef.current?.focus()
|
||||
}, [selection, inputRef])
|
||||
|
||||
// Show the default planes if the selection type is 'plane'
|
||||
useEffect(() => {
|
||||
if (arg.selectionTypes.includes('plane') && !canSubmitSelection) {
|
||||
toSync(() => {
|
||||
return Promise.all([
|
||||
kclManager.showPlanes(),
|
||||
kclManager.setSelectionFilter(['plane', 'object']),
|
||||
])
|
||||
}, reportRejection)()
|
||||
}
|
||||
|
||||
return () => {
|
||||
toSync(() => {
|
||||
const promises = [
|
||||
new Promise(() => kclManager.defaultSelectionFilter()),
|
||||
]
|
||||
if (!kclManager._isAstEmpty(kclManager.ast)) {
|
||||
promises.push(kclManager.hidePlanes())
|
||||
}
|
||||
return Promise.all(promises)
|
||||
}, reportRejection)()
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Fast-forward through this arg if it's marked as skippable
|
||||
// and we have a valid selection already
|
||||
useEffect(() => {
|
||||
@ -109,11 +130,15 @@ function CommandBarSelectionInput({
|
||||
{arg.warningMessage}
|
||||
</p>
|
||||
)}
|
||||
<span data-testid="cmd-bar-arg-name" className="sr-only">
|
||||
{arg.name}
|
||||
</span>
|
||||
<input
|
||||
id="selection"
|
||||
name="selection"
|
||||
ref={inputRef}
|
||||
required
|
||||
data-testid="cmd-bar-arg-value"
|
||||
placeholder="Select an entity with your mouse"
|
||||
className="absolute inset-0 w-full h-full opacity-0 cursor-default"
|
||||
onKeyDown={(event) => {
|
||||
|
@ -818,15 +818,16 @@ const CustomIconMap = {
|
||||
),
|
||||
plane: (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-label="plane"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M4.92871 5.11391L4.43964 5.00995V4.10898V3.60898V3.10898L4.92871 3.21293L5.41778 3.31689L6.29907 3.50421V4.00421V4.50421L5.41778 4.31689V5.21786L4.92871 5.11391ZM11.8774 4.68991L8.1585 3.89945V4.39945V4.89945L11.8774 5.68991V5.18991V4.68991ZM13.7368 5.08515V5.58515V6.08515L14.6181 6.27247V7.17344L15.1071 7.2774L15.5962 7.38135V6.48038V5.98038V5.48038L15.1071 5.37643L14.6181 5.27247L13.7368 5.08515ZM15.5962 9.28233L15.1071 9.17837L14.6181 9.07441V12.8764L15.1071 12.9803L15.5962 13.0843V9.28233ZM15.5962 14.9852L15.1071 14.8813L14.6181 14.7773V15.6783L13.7368 15.491V15.991V16.491L14.6181 16.6783L15.1071 16.7823L15.5962 16.8862V16.3862V15.8862V14.9852ZM11.8774 16.0957V15.5957V15.0957L8.1585 14.3053V14.8053V15.3053L11.8774 16.0957ZM6.29907 14.91V14.41V13.91L5.41778 13.7227V12.8217L4.92871 12.7178L4.43964 12.6138V13.5148V14.0148V14.5148L4.92871 14.6188L5.41778 14.7227L6.29907 14.91ZM4.43964 10.7129L4.92871 10.8168L5.41778 10.9208V7.11883L4.92871 7.01488L4.43964 6.91092V10.7129Z"
|
||||
d="M10.9781 5.49876L14.6181 6.27247V9.99381L10.9781 9.22011V5.49876ZM10 4.29085L10.9781 4.49876L14.6181 5.27247L14.6182 5.27247L15.5963 5.48038H15.5963V6.48038V10.2017V11.2017L15.5963 11.2017V15.8862V16.8862L14.6181 16.6783L5.41784 14.7227L4.4397 14.5148V13.5148V4.10898V3.10898L5.41784 3.31689L10 4.29085ZM14.6181 10.9938V15.6783L5.41784 13.7227V4.31689L10 5.29085V9.0122V10.0122L10.9781 10.2201L14.6181 10.9938Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
|
@ -317,6 +317,7 @@ export const ModelingMachineProvider = ({
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
let selections: Selections = {
|
||||
graphSelections: [],
|
||||
otherSelections: [],
|
||||
@ -375,7 +376,10 @@ export const ModelingMachineProvider = ({
|
||||
}
|
||||
}
|
||||
|
||||
if (setSelections.selectionType === 'otherSelection') {
|
||||
if (
|
||||
setSelections.selectionType === 'axisSelection' ||
|
||||
setSelections.selectionType === 'defaultPlaneSelection'
|
||||
) {
|
||||
if (editorManager.isShiftDown) {
|
||||
selections = {
|
||||
graphSelections: selectionRanges.graphSelections,
|
||||
@ -387,20 +391,11 @@ export const ModelingMachineProvider = ({
|
||||
otherSelections: [setSelections.selection],
|
||||
}
|
||||
}
|
||||
const { engineEvents, updateSceneObjectColors } =
|
||||
handleSelectionBatch({
|
||||
selections: selections,
|
||||
})
|
||||
engineEvents &&
|
||||
engineEvents.forEach((event) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
engineCommandManager.sendSceneCommand(event)
|
||||
})
|
||||
updateSceneObjectColors()
|
||||
return {
|
||||
selectionRanges: selections,
|
||||
}
|
||||
}
|
||||
|
||||
if (setSelections.selectionType === 'completeSelection') {
|
||||
editorManager.selectRange(setSelections.selection)
|
||||
if (!sketchDetails)
|
||||
|
@ -24,6 +24,7 @@ import { ForwardedRef, forwardRef, useEffect } from 'react'
|
||||
import { useLspContext } from 'components/LspProvider'
|
||||
import { toSync } from 'lib/utils'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||
|
||||
interface AllSettingsFieldsProps {
|
||||
searchParamTab: SettingsLevel
|
||||
@ -245,6 +246,9 @@ export const AllSettingsFields = forwardRef(
|
||||
to inject the version from package.json */}
|
||||
App version {APP_VERSION}.{' '}
|
||||
<a
|
||||
onClick={openExternalBrowserIfDesktop(
|
||||
`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`
|
||||
)}
|
||||
href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
@ -255,6 +259,9 @@ export const AllSettingsFields = forwardRef(
|
||||
<p className="max-w-2xl mt-6">
|
||||
Don't see the feature you want? Check to see if it's on{' '}
|
||||
<a
|
||||
onClick={openExternalBrowserIfDesktop(
|
||||
'https://github.com/KittyCAD/modeling-app/discussions'
|
||||
)}
|
||||
href="https://github.com/KittyCAD/modeling-app/discussions"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
@ -269,6 +276,9 @@ export const AllSettingsFields = forwardRef(
|
||||
Want to experience the latest and (hopefully) greatest from our
|
||||
main development branch?{' '}
|
||||
<a
|
||||
onClick={openExternalBrowserIfDesktop(
|
||||
'https://zoo.dev/modeling-app/download/nightly'
|
||||
)}
|
||||
href="https://zoo.dev/modeling-app/download/nightly"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
import { useRouteLoaderData } from 'react-router-dom'
|
||||
import { PATHS } from 'lib/paths'
|
||||
import { IndexLoaderData } from 'lib/types'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
|
||||
enum StreamState {
|
||||
Playing = 'playing',
|
||||
@ -30,6 +31,7 @@ export const Stream = () => {
|
||||
const videoRef = useRef<HTMLVideoElement>(null)
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const { state, send } = useModelingContext()
|
||||
const { commandBarState } = useCommandsContext()
|
||||
const { mediaStream } = useAppStream()
|
||||
const { overallState, immediateState } = useNetworkContext()
|
||||
const [streamState, setStreamState] = useState(StreamState.Unset)
|
||||
@ -260,7 +262,15 @@ export const Stream = () => {
|
||||
if (!videoRef.current) return
|
||||
// If we're in sketch mode, don't send a engine-side select event
|
||||
if (state.matches('Sketch')) return
|
||||
if (state.matches({ idle: 'showPlanes' })) return
|
||||
// Only respect default plane selection if we're on a selection command argument
|
||||
if (
|
||||
state.matches({ idle: 'showPlanes' }) &&
|
||||
!(
|
||||
commandBarState.matches('Gathering arguments') &&
|
||||
commandBarState.context.currentArgument?.inputType === 'selection'
|
||||
)
|
||||
)
|
||||
return
|
||||
// If we're mousing up from a camera drag, don't send a select event
|
||||
if (sceneInfra.camControls.wasDragging === true) return
|
||||
|
||||
|
@ -169,6 +169,7 @@ export function useEngineConnectionSubscriptions() {
|
||||
pathToNode: artifact.codeRef.pathToNode,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Artifact is likely an extrusion face
|
||||
|
@ -23,6 +23,7 @@ import { codeManager, editorManager, sceneInfra } from 'lib/singletons'
|
||||
import { Diagnostic } from '@codemirror/lint'
|
||||
import { markOnce } from 'lib/performance'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { EntityType_type } from '@kittycad/lib/dist/types/src/models'
|
||||
|
||||
interface ExecuteArgs {
|
||||
ast?: Node<Program>
|
||||
@ -37,6 +38,7 @@ interface ExecuteArgs {
|
||||
export class KclManager {
|
||||
private _ast: Node<Program> = {
|
||||
body: [],
|
||||
shebang: null,
|
||||
start: 0,
|
||||
end: 0,
|
||||
moduleId: 0,
|
||||
@ -204,6 +206,7 @@ export class KclManager {
|
||||
clearAst() {
|
||||
this._ast = {
|
||||
body: [],
|
||||
shebang: null,
|
||||
start: 0,
|
||||
end: 0,
|
||||
moduleId: 0,
|
||||
@ -279,7 +282,7 @@ export class KclManager {
|
||||
this.lints = await lintAst({ ast: ast })
|
||||
|
||||
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
||||
defaultSelectionFilter(execState.memory, this.engineCommandManager)
|
||||
setSelectionFilterToDefault(execState.memory, this.engineCommandManager)
|
||||
|
||||
if (args.zoomToFit) {
|
||||
let zoomObjectId: string | undefined = ''
|
||||
@ -566,8 +569,13 @@ export class KclManager {
|
||||
}
|
||||
return Promise.all(thePromises)
|
||||
}
|
||||
/** TODO: this function is hiding unawaited asynchronous work */
|
||||
defaultSelectionFilter() {
|
||||
defaultSelectionFilter(this.programMemory, this.engineCommandManager)
|
||||
setSelectionFilterToDefault(this.programMemory, this.engineCommandManager)
|
||||
}
|
||||
/** TODO: this function is hiding unawaited asynchronous work */
|
||||
setSelectionFilter(filter: EntityType_type[]) {
|
||||
setSelectionFilter(filter, this.engineCommandManager)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -589,18 +597,35 @@ export class KclManager {
|
||||
}
|
||||
}
|
||||
|
||||
function defaultSelectionFilter(
|
||||
const defaultSelectionFilter: EntityType_type[] = [
|
||||
'face',
|
||||
'edge',
|
||||
'solid2d',
|
||||
'curve',
|
||||
'object',
|
||||
]
|
||||
|
||||
/** TODO: This function is not synchronous but is currently treated as such */
|
||||
function setSelectionFilterToDefault(
|
||||
programMemory: ProgramMemory,
|
||||
engineCommandManager: EngineCommandManager
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
programMemory.hasSketchOrSolid() &&
|
||||
engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'set_selection_filter',
|
||||
filter: ['face', 'edge', 'solid2d', 'curve'],
|
||||
},
|
||||
})
|
||||
setSelectionFilter(defaultSelectionFilter, engineCommandManager)
|
||||
}
|
||||
|
||||
/** TODO: This function is not synchronous but is currently treated as such */
|
||||
function setSelectionFilter(
|
||||
filter: EntityType_type[],
|
||||
engineCommandManager: EngineCommandManager
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'set_selection_filter',
|
||||
filter,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -123,6 +123,7 @@ describe('Testing addSketchTo', () => {
|
||||
const result = addSketchTo(
|
||||
{
|
||||
body: [],
|
||||
shebang: null,
|
||||
start: 0,
|
||||
end: 0,
|
||||
moduleId: 0,
|
||||
|
@ -527,6 +527,45 @@ export function sketchOnExtrudedFace(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Append an offset plane to the AST
|
||||
*/
|
||||
export function addOffsetPlane({
|
||||
node,
|
||||
defaultPlane,
|
||||
offset,
|
||||
}: {
|
||||
node: Node<Program>
|
||||
defaultPlane: DefaultPlaneStr
|
||||
offset: Expr
|
||||
}): { modifiedAst: Node<Program>; pathToNode: PathToNode } {
|
||||
const modifiedAst = structuredClone(node)
|
||||
const newPlaneName = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.PLANE)
|
||||
|
||||
const newPlane = createVariableDeclaration(
|
||||
newPlaneName,
|
||||
createCallExpressionStdLib('offsetPlane', [
|
||||
createLiteral(defaultPlane.toUpperCase()),
|
||||
offset,
|
||||
])
|
||||
)
|
||||
|
||||
modifiedAst.body.push(newPlane)
|
||||
const pathToNode: PathToNode = [
|
||||
['body', ''],
|
||||
[modifiedAst.body.length - 1, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
['0', 'index'],
|
||||
['init', 'VariableDeclarator'],
|
||||
['arguments', 'CallExpression'],
|
||||
[0, 'index'],
|
||||
]
|
||||
return {
|
||||
modifiedAst,
|
||||
pathToNode,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the AST to create a new sketch using the variable declaration
|
||||
* of an offset plane. The new sketch just has to come after the offset
|
||||
|
@ -1823,6 +1823,7 @@ export const updateStartProfileAtArgs: SketchLineHelper['updateArgs'] = ({
|
||||
modifiedAst: {
|
||||
start: 0,
|
||||
end: 0,
|
||||
shebang: null,
|
||||
moduleId: 0,
|
||||
body: [],
|
||||
|
||||
|
@ -40,6 +40,10 @@ export type ModelingCommandSchema = {
|
||||
selection: Selections
|
||||
radius: KclCommandValue
|
||||
}
|
||||
'Offset plane': {
|
||||
plane: Selections
|
||||
distance: KclCommandValue
|
||||
}
|
||||
'change tool': {
|
||||
tool: SketchTool
|
||||
}
|
||||
@ -276,6 +280,24 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
},
|
||||
},
|
||||
},
|
||||
'Offset plane': {
|
||||
description: 'Offset a plane.',
|
||||
icon: 'plane',
|
||||
args: {
|
||||
plane: {
|
||||
inputType: 'selection',
|
||||
selectionTypes: ['plane'],
|
||||
multiple: false,
|
||||
required: true,
|
||||
skip: true,
|
||||
},
|
||||
distance: {
|
||||
inputType: 'kcl',
|
||||
defaultValue: KCL_DEFAULT_LENGTH,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Fillet: {
|
||||
description: 'Fillet edge',
|
||||
icon: 'fillet',
|
||||
|
@ -54,6 +54,7 @@ export const KCL_DEFAULT_CONSTANT_PREFIXES = {
|
||||
EXTRUDE: 'extrude',
|
||||
SEGMENT: 'seg',
|
||||
REVOLVE: 'revolve',
|
||||
PLANE: 'plane',
|
||||
} as const
|
||||
/** The default KCL length expression */
|
||||
export const KCL_DEFAULT_LENGTH = `5`
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
} from 'lang/queryAst'
|
||||
import { CommandArgument } from './commandTypes'
|
||||
import {
|
||||
DefaultPlaneStr,
|
||||
getParentGroup,
|
||||
SEGMENT_BODIES_PLUS_PROFILE_START,
|
||||
} from 'clientSideScene/sceneEntities'
|
||||
@ -46,6 +47,10 @@ export const X_AXIS_UUID = 'ad792545-7fd3-482a-a602-a93924e3055b'
|
||||
export const Y_AXIS_UUID = '680fd157-266f-4b8a-984f-cdf46b8bdf01'
|
||||
|
||||
export type Axis = 'y-axis' | 'x-axis' | 'z-axis'
|
||||
export type DefaultPlaneSelection = {
|
||||
name: DefaultPlaneStr
|
||||
id: string
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Artifact} instead. */
|
||||
type Selection__old =
|
||||
@ -72,9 +77,11 @@ type Selection__old =
|
||||
// TODO this is a temporary measure that well be made redundant with: https://github.com/KittyCAD/modeling-app/pull/3836
|
||||
secondaryRange: SourceRange
|
||||
}
|
||||
export type NonCodeSelection = Axis | DefaultPlaneSelection
|
||||
|
||||
/** @deprecated Use {@link Selection} instead. */
|
||||
export type Selections__old = {
|
||||
otherSelections: Axis[]
|
||||
otherSelections: NonCodeSelection[]
|
||||
codeBasedSelections: Selection__old[]
|
||||
}
|
||||
export interface Selection {
|
||||
@ -82,7 +89,7 @@ export interface Selection {
|
||||
codeRef: CodeRef
|
||||
}
|
||||
export type Selections = {
|
||||
otherSelections: Array<Axis>
|
||||
otherSelections: Array<NonCodeSelection>
|
||||
graphSelections: Array<Selection>
|
||||
}
|
||||
|
||||
@ -172,11 +179,31 @@ export async function getEventForSelectWithPoint({
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'otherSelection',
|
||||
selectionType: 'axisSelection',
|
||||
selection: X_AXIS_UUID === data.entity_id ? 'x-axis' : 'y-axis',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Check for default plane selection
|
||||
const foundDefaultPlane =
|
||||
engineCommandManager.defaultPlanes !== null &&
|
||||
Object.entries(engineCommandManager.defaultPlanes).find(
|
||||
([, plane]) => plane === data.entity_id
|
||||
)
|
||||
if (foundDefaultPlane) {
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'defaultPlaneSelection',
|
||||
selection: {
|
||||
name: foundDefaultPlane[0] as DefaultPlaneStr,
|
||||
id: data.entity_id,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let _artifact = engineCommandManager.artifactGraph.get(data.entity_id)
|
||||
const codeRefs = getCodeRefsByArtifactId(
|
||||
data.entity_id,
|
||||
@ -207,7 +234,7 @@ export function getEventForSegmentSelection(
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'otherSelection',
|
||||
selectionType: 'axisSelection',
|
||||
selection: obj?.userData?.type === X_AXIS ? 'x-axis' : 'y-axis',
|
||||
},
|
||||
}
|
||||
@ -272,7 +299,6 @@ export function handleSelectionBatch({
|
||||
}): {
|
||||
engineEvents: Models['WebSocketRequest_type'][]
|
||||
codeMirrorSelection: EditorSelection
|
||||
otherSelections: Axis[]
|
||||
updateSceneObjectColors: () => void
|
||||
} {
|
||||
const ranges: ReturnType<typeof EditorSelection.cursor>[] = []
|
||||
@ -303,7 +329,6 @@ export function handleSelectionBatch({
|
||||
ranges,
|
||||
selections.graphSelections.length - 1
|
||||
),
|
||||
otherSelections: selections.otherSelections,
|
||||
updateSceneObjectColors: () =>
|
||||
updateSceneObjectColors(selections.graphSelections),
|
||||
}
|
||||
@ -314,7 +339,6 @@ export function handleSelectionBatch({
|
||||
0
|
||||
),
|
||||
engineEvents,
|
||||
otherSelections: selections.otherSelections,
|
||||
updateSceneObjectColors: () =>
|
||||
updateSceneObjectColors(selections.graphSelections),
|
||||
}
|
||||
@ -536,7 +560,8 @@ export function canSweepSelection(selection: Selections) {
|
||||
}
|
||||
|
||||
// This accounts for non-geometry selections under "other"
|
||||
export type ResolvedSelectionType = [Artifact['type'] | 'other', number]
|
||||
export type ResolvedSelectionType = Artifact['type'] | 'other'
|
||||
export type SelectionCountsByType = Map<ResolvedSelectionType, number>
|
||||
|
||||
/**
|
||||
* In the future, I'd like this function to properly return the type of each selected entity based on
|
||||
@ -545,28 +570,48 @@ export type ResolvedSelectionType = [Artifact['type'] | 'other', number]
|
||||
* @param selection
|
||||
* @returns
|
||||
*/
|
||||
export function getSelectionType(
|
||||
export function getSelectionCountByType(
|
||||
selection?: Selections
|
||||
): ResolvedSelectionType[] {
|
||||
if (!selection) return []
|
||||
const selectionsWithArtifacts = selection.graphSelections.filter(
|
||||
(s) => !!s.artifact
|
||||
): SelectionCountsByType | 'none' {
|
||||
const selectionsByType: SelectionCountsByType = new Map()
|
||||
if (
|
||||
!selection ||
|
||||
(!selection.graphSelections.length && !selection.otherSelections.length)
|
||||
)
|
||||
const firstSelection = selectionsWithArtifacts[0]
|
||||
const firstSelectionType = firstSelection?.artifact?.type
|
||||
if (!firstSelectionType) return []
|
||||
const selectionsWithSameType = selectionsWithArtifacts.filter(
|
||||
(s) => s.artifact?.type === firstSelection.artifact?.type
|
||||
)
|
||||
return [[firstSelectionType, selectionsWithSameType.length]]
|
||||
return 'none'
|
||||
|
||||
function incrementOrInitializeSelectionType(type: ResolvedSelectionType) {
|
||||
const count = selectionsByType.get(type) || 0
|
||||
selectionsByType.set(type, count + 1)
|
||||
}
|
||||
|
||||
selection.otherSelections.forEach((selection) => {
|
||||
if (typeof selection === 'string') {
|
||||
incrementOrInitializeSelectionType('other')
|
||||
} else if ('name' in selection) {
|
||||
incrementOrInitializeSelectionType('plane')
|
||||
}
|
||||
})
|
||||
|
||||
selection.graphSelections.forEach((selection) => {
|
||||
if (!selection.artifact) {
|
||||
incrementOrInitializeSelectionType('other')
|
||||
return
|
||||
}
|
||||
incrementOrInitializeSelectionType(selection.artifact.type)
|
||||
})
|
||||
|
||||
return selectionsByType
|
||||
}
|
||||
|
||||
export function getSelectionTypeDisplayText(
|
||||
selection?: Selections
|
||||
): string | null {
|
||||
const selectionsByType = getSelectionType(selection)
|
||||
const selectionsByType = getSelectionCountByType(selection)
|
||||
if (selectionsByType === 'none') return null
|
||||
|
||||
return (selectionsByType as Exclude<typeof selectionsByType, 'none'>)
|
||||
return selectionsByType
|
||||
.entries()
|
||||
.map(
|
||||
// Hack for showing "face" instead of "extrude-wall" in command bar text
|
||||
([type, count]) =>
|
||||
@ -575,16 +620,17 @@ export function getSelectionTypeDisplayText(
|
||||
.replace('solid2D', 'face')
|
||||
.replace('segment', 'face')}${count > 1 ? 's' : ''}`
|
||||
)
|
||||
.toArray()
|
||||
.join(', ')
|
||||
}
|
||||
|
||||
export function canSubmitSelectionArg(
|
||||
selectionsByType: 'none' | ResolvedSelectionType[],
|
||||
selectionsByType: 'none' | Map<ResolvedSelectionType, number>,
|
||||
argument: CommandArgument<unknown> & { inputType: 'selection' }
|
||||
) {
|
||||
return (
|
||||
selectionsByType !== 'none' &&
|
||||
selectionsByType.every(([type, count]) => {
|
||||
selectionsByType.entries().every(([type, count]) => {
|
||||
const foundIndex = argument.selectionTypes.findIndex((s) => s === type)
|
||||
return (
|
||||
foundIndex !== -1 &&
|
||||
|
@ -252,10 +252,15 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
[
|
||||
{
|
||||
id: 'plane-offset',
|
||||
onClick: () =>
|
||||
console.error('Plane through normal not yet implemented'),
|
||||
onClick: ({ commandBarSend }) => {
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'Offset plane', groupId: 'modeling' },
|
||||
})
|
||||
},
|
||||
hotkey: 'O',
|
||||
icon: 'plane',
|
||||
status: 'unavailable',
|
||||
status: 'available',
|
||||
title: 'Offset plane',
|
||||
description: 'Create a plane parallel to an existing plane.',
|
||||
links: [],
|
||||
|
@ -34,6 +34,8 @@ export function useCalculateKclExpression({
|
||||
} {
|
||||
const { programMemory, code } = useKclContext()
|
||||
const { context } = useModelingContext()
|
||||
// If there is no selection, use the end of the code
|
||||
// so all variables are available
|
||||
const selectionRange:
|
||||
| (typeof context)['selectionRanges']['graphSelections'][number]['codeRef']['range']
|
||||
| undefined = context.selectionRanges.graphSelections[0]?.codeRef?.range
|
||||
@ -72,11 +74,12 @@ export function useCalculateKclExpression({
|
||||
}, [programMemory, newVariableName])
|
||||
|
||||
useEffect(() => {
|
||||
if (!programMemory || !selectionRange) return
|
||||
if (!programMemory) return
|
||||
const varInfo = findAllPreviousVariables(
|
||||
kclManager.ast,
|
||||
kclManager.programMemory,
|
||||
selectionRange
|
||||
// If there is no selection, use the end of the code
|
||||
selectionRange || [code.length, code.length]
|
||||
)
|
||||
setAvailableVarInfo(varInfo)
|
||||
}, [kclManager.ast, kclManager.programMemory, selectionRange])
|
||||
|
File diff suppressed because one or more lines are too long
111
src/wasm-lib/Cargo.lock
generated
111
src/wasm-lib/Cargo.lock
generated
@ -228,6 +228,15 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace-ext"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
@ -1625,6 +1634,12 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_ci"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
@ -1708,6 +1723,7 @@ dependencies = [
|
||||
"kittycad-modeling-cmds",
|
||||
"lazy_static",
|
||||
"measurements",
|
||||
"miette",
|
||||
"mime_guess",
|
||||
"parse-display 0.9.1",
|
||||
"pretty_assertions",
|
||||
@ -1971,6 +1987,37 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miette"
|
||||
version = "7.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"backtrace-ext",
|
||||
"cfg-if",
|
||||
"miette-derive",
|
||||
"owo-colors",
|
||||
"supports-color",
|
||||
"supports-hyperlinks",
|
||||
"supports-unicode",
|
||||
"terminal_size",
|
||||
"textwrap",
|
||||
"thiserror 1.0.68",
|
||||
"unicode-width 0.1.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miette-derive"
|
||||
version = "7.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
@ -2163,6 +2210,12 @@ dependencies = [
|
||||
"thiserror 1.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56"
|
||||
|
||||
[[package]]
|
||||
name = "papergrid"
|
||||
version = "0.11.0"
|
||||
@ -3311,6 +3364,12 @@ version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "smawk"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.7"
|
||||
@ -3396,6 +3455,27 @@ version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "supports-color"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9829b314621dfc575df4e409e79f9d6a66a3bd707ab73f23cb4aa3a854ac854f"
|
||||
dependencies = [
|
||||
"is_ci",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "supports-hyperlinks"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c0a1e5168041f5f3ff68ff7d95dcb9c8749df29f6e7e89ada40dd4c9de404ee"
|
||||
|
||||
[[package]]
|
||||
name = "supports-unicode"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
@ -3496,6 +3576,27 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
|
||||
dependencies = [
|
||||
"smawk",
|
||||
"unicode-linebreak",
|
||||
"unicode-width 0.1.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.68"
|
||||
@ -3614,9 +3715,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.40.0"
|
||||
version = "1.41.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998"
|
||||
checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
@ -3955,6 +4056,12 @@ version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-linebreak"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.12.0"
|
||||
|
@ -16,7 +16,7 @@ gloo-utils = "0.2.0"
|
||||
kcl-lib = { path = "kcl" }
|
||||
kittycad.workspace = true
|
||||
serde_json = "1.0.128"
|
||||
tokio = { version = "1.40.0", features = ["sync"] }
|
||||
tokio = { version = "1.41.1", features = ["sync"] }
|
||||
toml = "0.8.19"
|
||||
uuid = { version = "1.11.0", features = ["v4", "js", "serde"] }
|
||||
wasm-bindgen = "0.2.91"
|
||||
@ -29,7 +29,7 @@ kittycad = { workspace = true, default-features = true }
|
||||
kittycad-modeling-cmds = { workspace = true }
|
||||
pretty_assertions = "1.4.1"
|
||||
reqwest = { version = "0.12", default-features = false }
|
||||
tokio = { version = "1.40.0", features = ["rt-multi-thread", "macros", "time"] }
|
||||
tokio = { version = "1.41.1", features = ["rt-multi-thread", "macros", "time"] }
|
||||
twenty-twenty = "0.8"
|
||||
uuid = { version = "1.11.0", features = ["v4", "js", "serde"] }
|
||||
|
||||
|
@ -12,4 +12,4 @@ kcl-lib = { version = "0.2", path = "../kcl" }
|
||||
pico-args = "0.5.0"
|
||||
serde = { version = "1.0.214", features = ["derive"] }
|
||||
serde_json = "1.0.128"
|
||||
tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] }
|
||||
tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread"] }
|
||||
|
@ -19,5 +19,5 @@ indexmap = "2.6.0"
|
||||
kcl-lib = { path = "../kcl" }
|
||||
kittycad = { workspace = true, features = ["clap"] }
|
||||
kittycad-modeling-cmds = { workspace = true }
|
||||
tokio = { version = "1.38", features = ["full", "time", "rt", "tracing"] }
|
||||
tokio = { version = "1.41", features = ["full", "time", "rt", "tracing"] }
|
||||
uuid = { version = "1.11.0", features = ["v4", "js", "serde"] }
|
||||
|
@ -33,6 +33,7 @@ kittycad = { workspace = true }
|
||||
kittycad-modeling-cmds = { workspace = true }
|
||||
lazy_static = "1.5.0"
|
||||
measurements = "0.11.0"
|
||||
miette = "7.2.0"
|
||||
mime_guess = "2.0.5"
|
||||
parse-display = "0.9.1"
|
||||
pyo3 = { version = "0.22.6", optional = true }
|
||||
@ -55,7 +56,7 @@ zip = { version = "2.0.0", default-features = false }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
js-sys = { version = "0.3.72" }
|
||||
tokio = { version = "1.40.0", features = ["sync", "time"] }
|
||||
tokio = { version = "1.41.1", features = ["sync", "time"] }
|
||||
tower-lsp = { version = "0.20.0", default-features = false, features = ["runtime-agnostic"] }
|
||||
wasm-bindgen = "0.2.91"
|
||||
wasm-bindgen-futures = "0.4.44"
|
||||
@ -64,7 +65,7 @@ web-sys = { version = "0.3.72", features = ["console"] }
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
approx = "0.5"
|
||||
bson = { version = "2.13.0", features = ["uuid-1", "chrono"] }
|
||||
tokio = { version = "1.39.3", features = ["full"] }
|
||||
tokio = { version = "1.41.1", features = ["full"] }
|
||||
tokio-tungstenite = { version = "0.24.0", features = ["rustls-tls-native-roots"] }
|
||||
tower-lsp = { version = "0.20.0", features = ["proposed"] }
|
||||
|
||||
@ -89,8 +90,9 @@ iai = "0.1"
|
||||
image = { version = "0.25.5", default-features = false, features = ["png"] }
|
||||
insta = { version = "1.41.1", features = ["json", "filters", "redactions"] }
|
||||
itertools = "0.13.0"
|
||||
miette = { version = "7.2.0", features = ["fancy"] }
|
||||
pretty_assertions = "1.4.1"
|
||||
tokio = { version = "1.40.0", features = ["rt-multi-thread", "macros", "time"] }
|
||||
tokio = { version = "1.41.1", features = ["rt-multi-thread", "macros", "time"] }
|
||||
twenty-twenty = "0.8.0"
|
||||
|
||||
[[bench]]
|
||||
|
@ -65,6 +65,10 @@ impl<T> Node<T> {
|
||||
source_range: SourceRange([self.start, self.end, self.module_id.0 as usize]),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains(&self, pos: usize) -> bool {
|
||||
self.start <= pos && pos <= self.end
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: JsonSchema> schemars::JsonSchema for Node<T> {
|
||||
@ -117,8 +121,12 @@ impl<T> Node<T> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn as_source_range(&self) -> SourceRange {
|
||||
SourceRange([self.start, self.end, self.module_id.as_usize()])
|
||||
}
|
||||
|
||||
pub fn as_source_ranges(&self) -> Vec<SourceRange> {
|
||||
vec![SourceRange([self.start, self.end, self.module_id.as_usize()])]
|
||||
vec![self.as_source_range()]
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,6 +181,8 @@ pub struct Program {
|
||||
pub body: Vec<BodyItem>,
|
||||
#[serde(default, skip_serializing_if = "NonCodeMeta::is_empty")]
|
||||
pub non_code_meta: NonCodeMeta,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub shebang: Option<Node<Shebang>>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
@ -261,19 +271,14 @@ impl Program {
|
||||
}
|
||||
|
||||
pub fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option<Hover> {
|
||||
// Check if we are in the non code meta.
|
||||
if let Some(meta) = self.get_non_code_meta_for_position(pos) {
|
||||
for node in &meta.start_nodes {
|
||||
if node.contains(pos) {
|
||||
// We only care about the shebang.
|
||||
if let NonCodeValue::Shebang { value: _ } = &node.value {
|
||||
let source_range: SourceRange = node.into();
|
||||
return Some(Hover::Comment {
|
||||
value: r#"The `#!` at the start of a script, known as a shebang, specifies the path to the interpreter that should execute the script. This line is not necessary for your `kcl` to run in the modeling-app. You can safely delete it. If you wish to learn more about what you _can_ do with a shebang, read this doc: [zoo.dev/docs/faq/shebang](https://zoo.dev/docs/faq/shebang)."#.to_string(),
|
||||
range: source_range.to_lsp_range(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
// Check if we are in shebang.
|
||||
if let Some(node) = &self.shebang {
|
||||
if node.contains(pos) {
|
||||
let source_range: SourceRange = node.into();
|
||||
return Some(Hover::Comment {
|
||||
value: r#"The `#!` at the start of a script, known as a shebang, specifies the path to the interpreter that should execute the script. This line is not necessary for your `kcl` to run in the modeling-app. You can safely delete it. If you wish to learn more about what you _can_ do with a shebang, read this doc: [zoo.dev/docs/faq/shebang](https://zoo.dev/docs/faq/shebang)."#.to_string(),
|
||||
range: source_range.to_lsp_range(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -528,6 +533,26 @@ impl Program {
|
||||
}
|
||||
}
|
||||
|
||||
/// A shebang.
|
||||
/// This is a special type of comment that is at the top of the file.
|
||||
/// It looks like this:
|
||||
/// ```python,no_run
|
||||
/// #!/usr/bin/env python
|
||||
/// ```
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, ts_rs::TS, JsonSchema, Bake)]
|
||||
#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
|
||||
#[databake(path = kcl_lib::ast::types)]
|
||||
#[ts(export)]
|
||||
pub struct Shebang {
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
impl Shebang {
|
||||
pub fn new(content: String) -> Self {
|
||||
Shebang { content }
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifier of a source file. Uses a u32 to keep the size small.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize, ts_rs::TS, JsonSchema, Bake)]
|
||||
#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
|
||||
@ -948,13 +973,8 @@ pub struct NonCodeNode {
|
||||
}
|
||||
|
||||
impl Node<NonCodeNode> {
|
||||
pub fn contains(&self, pos: usize) -> bool {
|
||||
self.start <= pos && pos <= self.end
|
||||
}
|
||||
|
||||
pub fn format(&self, indentation: &str) -> String {
|
||||
match &self.value {
|
||||
NonCodeValue::Shebang { value } => format!("{}\n\n", value),
|
||||
NonCodeValue::InlineComment {
|
||||
value,
|
||||
style: CommentStyle::Line,
|
||||
@ -994,7 +1014,6 @@ impl Node<NonCodeNode> {
|
||||
impl NonCodeNode {
|
||||
pub fn value(&self) -> String {
|
||||
match &self.value {
|
||||
NonCodeValue::Shebang { value } => value.clone(),
|
||||
NonCodeValue::InlineComment { value, style: _ } => value.clone(),
|
||||
NonCodeValue::BlockComment { value, style: _ } => value.clone(),
|
||||
NonCodeValue::NewLineBlockComment { value, style: _ } => value.clone(),
|
||||
@ -1028,15 +1047,6 @@ impl CommentStyle {
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum NonCodeValue {
|
||||
/// A shebang.
|
||||
/// This is a special type of comment that is at the top of the file.
|
||||
/// It looks like this:
|
||||
/// ```python,no_run
|
||||
/// #!/usr/bin/env python
|
||||
/// ```
|
||||
Shebang {
|
||||
value: String,
|
||||
},
|
||||
/// An inline comment.
|
||||
/// Here are examples:
|
||||
/// `1 + 1 // This is an inline comment`.
|
||||
@ -3338,6 +3348,7 @@ const cylinder = startSketchOn('-XZ')
|
||||
body: Node::no_src(Program {
|
||||
body: Vec::new(),
|
||||
non_code_meta: Default::default(),
|
||||
shebang: None,
|
||||
digest: None,
|
||||
}),
|
||||
return_type: None,
|
||||
@ -3361,6 +3372,7 @@ const cylinder = startSketchOn('-XZ')
|
||||
inner: Program {
|
||||
body: Vec::new(),
|
||||
non_code_meta: Default::default(),
|
||||
shebang: None,
|
||||
digest: None,
|
||||
},
|
||||
start: 0,
|
||||
@ -3388,6 +3400,7 @@ const cylinder = startSketchOn('-XZ')
|
||||
inner: Program {
|
||||
body: Vec::new(),
|
||||
non_code_meta: Default::default(),
|
||||
shebang: None,
|
||||
digest: None,
|
||||
},
|
||||
start: 0,
|
||||
@ -3426,6 +3439,7 @@ const cylinder = startSketchOn('-XZ')
|
||||
inner: Program {
|
||||
body: Vec::new(),
|
||||
non_code_meta: Default::default(),
|
||||
shebang: None,
|
||||
digest: None,
|
||||
},
|
||||
start: 0,
|
||||
|
@ -66,6 +66,9 @@ impl Program {
|
||||
for body_item in slf.body.iter_mut() {
|
||||
hasher.update(body_item.compute_digest());
|
||||
}
|
||||
if let Some(shebang) = &slf.shebang {
|
||||
hasher.update(&shebang.inner.content);
|
||||
}
|
||||
hasher.update(slf.non_code_meta.compute_digest());
|
||||
});
|
||||
}
|
||||
@ -207,9 +210,6 @@ impl ReturnStatement {
|
||||
impl NonCodeNode {
|
||||
compute_digest!(|slf, hasher| {
|
||||
match &slf.value {
|
||||
NonCodeValue::Shebang { value } => {
|
||||
hasher.update(value);
|
||||
}
|
||||
NonCodeValue::InlineComment { value, style } => {
|
||||
hasher.update(value);
|
||||
hasher.update(style.digestable_id());
|
||||
|
@ -54,10 +54,56 @@ pub enum KclError {
|
||||
Internal(KclErrorDetails),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ts_rs::TS, Clone, PartialEq, Eq)]
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[error("{}", self.error.get_message())]
|
||||
pub struct Report {
|
||||
pub error: KclError,
|
||||
pub kcl_source: String,
|
||||
pub filename: String,
|
||||
}
|
||||
|
||||
impl miette::Diagnostic for Report {
|
||||
fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
|
||||
let family = match self.error {
|
||||
KclError::Lexical(_) => "Lexical",
|
||||
KclError::Syntax(_) => "Syntax",
|
||||
KclError::Semantic(_) => "Semantic",
|
||||
KclError::ImportCycle(_) => "ImportCycle",
|
||||
KclError::Type(_) => "Type",
|
||||
KclError::Unimplemented(_) => "Unimplemented",
|
||||
KclError::Unexpected(_) => "Unexpected",
|
||||
KclError::ValueAlreadyDefined(_) => "ValueAlreadyDefined",
|
||||
KclError::UndefinedValue(_) => "UndefinedValue",
|
||||
KclError::InvalidExpression(_) => "InvalidExpression",
|
||||
KclError::Engine(_) => "Engine",
|
||||
KclError::Internal(_) => "Internal",
|
||||
};
|
||||
let error_string = format!("KCL {family} error");
|
||||
Some(Box::new(error_string))
|
||||
}
|
||||
|
||||
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
|
||||
Some(&self.kcl_source)
|
||||
}
|
||||
|
||||
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
|
||||
let iter = self
|
||||
.error
|
||||
.source_ranges()
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(miette::SourceSpan::from)
|
||||
.map(|span| miette::LabeledSpan::new_with_span(None, span));
|
||||
Some(Box::new(iter))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ts_rs::TS, Clone, PartialEq, Eq, thiserror::Error, miette::Diagnostic)]
|
||||
#[error("{message}")]
|
||||
#[ts(export)]
|
||||
pub struct KclErrorDetails {
|
||||
#[serde(rename = "sourceRanges")]
|
||||
#[label(collection, "Errors")]
|
||||
pub source_ranges: Vec<SourceRange>,
|
||||
#[serde(rename = "msg")]
|
||||
pub message: String,
|
||||
|
@ -197,24 +197,17 @@ pub struct Environment {
|
||||
parent: Option<EnvironmentRef>,
|
||||
}
|
||||
|
||||
const NO_META: Vec<Metadata> = Vec::new();
|
||||
|
||||
impl Environment {
|
||||
pub fn root() -> Self {
|
||||
Self {
|
||||
// Prelude
|
||||
bindings: HashMap::from([
|
||||
("ZERO".to_string(), KclValue::from_number(0.0, Default::default())),
|
||||
(
|
||||
"QUARTER_TURN".to_string(),
|
||||
KclValue::from_number(90.0, Default::default()),
|
||||
),
|
||||
(
|
||||
"HALF_TURN".to_string(),
|
||||
KclValue::from_number(180.0, Default::default()),
|
||||
),
|
||||
(
|
||||
"THREE_QUARTER_TURN".to_string(),
|
||||
KclValue::from_number(270.0, Default::default()),
|
||||
),
|
||||
("ZERO".to_string(), KclValue::from_number(0.0, NO_META)),
|
||||
("QUARTER_TURN".to_string(), KclValue::from_number(90.0, NO_META)),
|
||||
("HALF_TURN".to_string(), KclValue::from_number(180.0, NO_META)),
|
||||
("THREE_QUARTER_TURN".to_string(), KclValue::from_number(270.0, NO_META)),
|
||||
]),
|
||||
parent: None,
|
||||
}
|
||||
@ -1020,6 +1013,20 @@ impl From<[usize; 3]> for SourceRange {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SourceRange> for miette::SourceSpan {
|
||||
fn from(source_range: &SourceRange) -> Self {
|
||||
let length = source_range.end() - source_range.start();
|
||||
let start = miette::SourceOffset::from(source_range.start());
|
||||
Self::new(start, length)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SourceRange> for miette::SourceSpan {
|
||||
fn from(source_range: SourceRange) -> Self {
|
||||
Self::from(&source_range)
|
||||
}
|
||||
}
|
||||
|
||||
impl SourceRange {
|
||||
/// Create a new source range.
|
||||
pub fn new(start: usize, end: usize, module_id: ModuleId) -> Self {
|
||||
@ -3177,6 +3184,7 @@ let w = f() + f()
|
||||
inner: crate::ast::types::Program {
|
||||
body: Vec::new(),
|
||||
non_code_meta: Default::default(),
|
||||
shebang: None,
|
||||
digest: None,
|
||||
},
|
||||
start: 0,
|
||||
|
@ -13,6 +13,8 @@ use crate::{
|
||||
ExecState, ExecutorContext, KclError, SourceRange,
|
||||
};
|
||||
|
||||
pub type KclObjectFields = HashMap<String, KclValue>;
|
||||
|
||||
/// Any KCL value.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
@ -49,7 +51,7 @@ pub enum KclValue {
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
Object {
|
||||
value: HashMap<String, KclValue>,
|
||||
value: KclObjectFields,
|
||||
#[serde(rename = "__meta")]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
@ -84,83 +86,6 @@ pub enum KclValue {
|
||||
},
|
||||
}
|
||||
|
||||
impl KclValue {
|
||||
pub(crate) fn metadata(&self) -> Vec<Metadata> {
|
||||
match self {
|
||||
KclValue::Uuid { value: _, meta } => meta.clone(),
|
||||
KclValue::Bool { value: _, meta } => meta.clone(),
|
||||
KclValue::Number { value: _, meta } => meta.clone(),
|
||||
KclValue::Int { value: _, meta } => meta.clone(),
|
||||
KclValue::String { value: _, meta } => meta.clone(),
|
||||
KclValue::Array { value: _, meta } => meta.clone(),
|
||||
KclValue::Object { value: _, meta } => meta.clone(),
|
||||
KclValue::TagIdentifier(x) => x.meta.clone(),
|
||||
KclValue::TagDeclarator(x) => vec![x.metadata()],
|
||||
KclValue::Plane(x) => x.meta.clone(),
|
||||
KclValue::Face(x) => x.meta.clone(),
|
||||
KclValue::Sketch { value } => value.meta.clone(),
|
||||
KclValue::Sketches { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(),
|
||||
KclValue::Solid(x) => x.meta.clone(),
|
||||
KclValue::Solids { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(),
|
||||
KclValue::ImportedGeometry(x) => x.meta.clone(),
|
||||
KclValue::Function { meta, .. } => meta.clone(),
|
||||
KclValue::KclNone { meta, .. } => meta.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_solid_set(&self) -> Result<SolidSet> {
|
||||
match self {
|
||||
KclValue::Solid(e) => Ok(SolidSet::Solid(e.clone())),
|
||||
KclValue::Solids { value } => Ok(SolidSet::Solids(value.clone())),
|
||||
KclValue::Array { value, .. } => {
|
||||
let solids: Vec<_> = value
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, v)| {
|
||||
v.as_solid().map(|v| v.to_owned()).map(Box::new).ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"expected this array to only contain solids, but element {i} was actually {}",
|
||||
v.human_friendly_type()
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
Ok(SolidSet::Solids(solids))
|
||||
}
|
||||
_ => anyhow::bail!("Not a solid or solids: {:?}", self),
|
||||
}
|
||||
}
|
||||
|
||||
/// Human readable type name used in error messages. Should not be relied
|
||||
/// on for program logic.
|
||||
pub(crate) fn human_friendly_type(&self) -> &'static str {
|
||||
match self {
|
||||
KclValue::Uuid { .. } => "Unique ID (uuid)",
|
||||
KclValue::TagDeclarator(_) => "TagDeclarator",
|
||||
KclValue::TagIdentifier(_) => "TagIdentifier",
|
||||
KclValue::Solid(_) => "Solid",
|
||||
KclValue::Solids { .. } => "Solids",
|
||||
KclValue::Sketch { .. } => "Sketch",
|
||||
KclValue::Sketches { .. } => "Sketches",
|
||||
KclValue::ImportedGeometry(_) => "ImportedGeometry",
|
||||
KclValue::Function { .. } => "Function",
|
||||
KclValue::Plane(_) => "Plane",
|
||||
KclValue::Face(_) => "Face",
|
||||
KclValue::Bool { .. } => "boolean (true/false value)",
|
||||
KclValue::Number { .. } => "number",
|
||||
KclValue::Int { .. } => "integer",
|
||||
KclValue::String { .. } => "string (text)",
|
||||
KclValue::Array { .. } => "array (list)",
|
||||
KclValue::Object { .. } => "object",
|
||||
KclValue::KclNone { .. } => "None",
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_function(&self) -> bool {
|
||||
matches!(self, KclValue::Function { .. })
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SketchSet> for KclValue {
|
||||
fn from(sg: SketchSet) -> Self {
|
||||
match sg {
|
||||
@ -249,8 +174,82 @@ impl From<&KclValue> for Vec<SourceRange> {
|
||||
}
|
||||
|
||||
impl KclValue {
|
||||
pub(crate) fn metadata(&self) -> Vec<Metadata> {
|
||||
match self {
|
||||
KclValue::Uuid { value: _, meta } => meta.clone(),
|
||||
KclValue::Bool { value: _, meta } => meta.clone(),
|
||||
KclValue::Number { value: _, meta } => meta.clone(),
|
||||
KclValue::Int { value: _, meta } => meta.clone(),
|
||||
KclValue::String { value: _, meta } => meta.clone(),
|
||||
KclValue::Array { value: _, meta } => meta.clone(),
|
||||
KclValue::Object { value: _, meta } => meta.clone(),
|
||||
KclValue::TagIdentifier(x) => x.meta.clone(),
|
||||
KclValue::TagDeclarator(x) => vec![x.metadata()],
|
||||
KclValue::Plane(x) => x.meta.clone(),
|
||||
KclValue::Face(x) => x.meta.clone(),
|
||||
KclValue::Sketch { value } => value.meta.clone(),
|
||||
KclValue::Sketches { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(),
|
||||
KclValue::Solid(x) => x.meta.clone(),
|
||||
KclValue::Solids { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(),
|
||||
KclValue::ImportedGeometry(x) => x.meta.clone(),
|
||||
KclValue::Function { meta, .. } => meta.clone(),
|
||||
KclValue::KclNone { meta, .. } => meta.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_solid_set(&self) -> Result<SolidSet> {
|
||||
match self {
|
||||
KclValue::Solid(e) => Ok(SolidSet::Solid(e.clone())),
|
||||
KclValue::Solids { value } => Ok(SolidSet::Solids(value.clone())),
|
||||
KclValue::Array { value, .. } => {
|
||||
let solids: Vec<_> = value
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, v)| {
|
||||
v.as_solid().map(|v| v.to_owned()).map(Box::new).ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"expected this array to only contain solids, but element {i} was actually {}",
|
||||
v.human_friendly_type()
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
Ok(SolidSet::Solids(solids))
|
||||
}
|
||||
_ => anyhow::bail!("Not a solid or solids: {:?}", self),
|
||||
}
|
||||
}
|
||||
|
||||
/// Human readable type name used in error messages. Should not be relied
|
||||
/// on for program logic.
|
||||
pub(crate) fn human_friendly_type(&self) -> &'static str {
|
||||
match self {
|
||||
KclValue::Uuid { .. } => "Unique ID (uuid)",
|
||||
KclValue::TagDeclarator(_) => "TagDeclarator",
|
||||
KclValue::TagIdentifier(_) => "TagIdentifier",
|
||||
KclValue::Solid(_) => "Solid",
|
||||
KclValue::Solids { .. } => "Solids",
|
||||
KclValue::Sketch { .. } => "Sketch",
|
||||
KclValue::Sketches { .. } => "Sketches",
|
||||
KclValue::ImportedGeometry(_) => "ImportedGeometry",
|
||||
KclValue::Function { .. } => "Function",
|
||||
KclValue::Plane(_) => "Plane",
|
||||
KclValue::Face(_) => "Face",
|
||||
KclValue::Bool { .. } => "boolean (true/false value)",
|
||||
KclValue::Number { .. } => "number",
|
||||
KclValue::Int { .. } => "integer",
|
||||
KclValue::String { .. } => "string (text)",
|
||||
KclValue::Array { .. } => "array (list)",
|
||||
KclValue::Object { .. } => "object",
|
||||
KclValue::KclNone { .. } => "None",
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_function(&self) -> bool {
|
||||
matches!(self, KclValue::Function { .. })
|
||||
}
|
||||
/// Put the number into a KCL value.
|
||||
pub fn from_number(f: f64, meta: Vec<Metadata>) -> Self {
|
||||
pub const fn from_number(f: f64, meta: Vec<Metadata>) -> Self {
|
||||
Self::Number { value: f, meta }
|
||||
}
|
||||
|
||||
@ -287,7 +286,7 @@ impl KclValue {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_object(&self) -> Option<&HashMap<String, KclValue>> {
|
||||
pub fn as_object(&self) -> Option<&KclObjectFields> {
|
||||
if let KclValue::Object { value, meta: _ } = &self {
|
||||
Some(value)
|
||||
} else {
|
||||
@ -295,7 +294,7 @@ impl KclValue {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_object(self) -> Option<HashMap<String, KclValue>> {
|
||||
pub fn into_object(self) -> Option<KclObjectFields> {
|
||||
if let KclValue::Object { value, meta: _ } = self {
|
||||
Some(value)
|
||||
} else {
|
||||
|
@ -15,9 +15,10 @@ use crate::{
|
||||
CallExpression, CommentStyle, ElseIf, Expr, ExpressionStatement, FnArgPrimitive, FnArgType, FunctionExpression,
|
||||
Identifier, IfExpression, ImportItem, ImportStatement, ItemVisibility, Literal, LiteralIdentifier,
|
||||
LiteralValue, MemberExpression, MemberObject, Node, NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression,
|
||||
ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement, TagDeclarator,
|
||||
ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement, Shebang, TagDeclarator,
|
||||
UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind,
|
||||
},
|
||||
docs::StdLibFn,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::SourceRange,
|
||||
parser::{
|
||||
@ -49,7 +50,6 @@ pub fn run_parser(i: TokenSlice) -> super::ParseResult {
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct ParseContext {
|
||||
pub errors: Vec<ParseError>,
|
||||
#[allow(dead_code)]
|
||||
pub warnings: Vec<ParseError>,
|
||||
}
|
||||
|
||||
@ -75,14 +75,34 @@ impl ParseContext {
|
||||
|
||||
/// Add an error to the current `ParseContext`, panics if there is none.
|
||||
fn err(e: ParseError) {
|
||||
// TODO follow warnings replacement with errors
|
||||
CTXT.with_borrow_mut(|ctxt| ctxt.as_mut().unwrap().errors.push(e));
|
||||
}
|
||||
|
||||
/// Add a warning to the current `ParseContext`, panics if there is none.
|
||||
#[allow(dead_code)]
|
||||
fn warn(mut e: ParseError) {
|
||||
e.severity = error::Severity::Warning;
|
||||
CTXT.with_borrow_mut(|ctxt| ctxt.as_mut().unwrap().warnings.push(e));
|
||||
CTXT.with_borrow_mut(|ctxt| {
|
||||
// Avoid duplicating warnings. This is possible since the parser can try one path, find
|
||||
// a warning, then backtrack and decide not to take that path and try another. This can
|
||||
// happen 'high up the stack', so it's impossible to fix where the warnings are generated.
|
||||
// Ideally we would pass warnings up the call stack rather than use a context object or
|
||||
// have some way to mark warnings as speculative or committed, but I don't think Winnow
|
||||
// is flexible enough for that (or at least, not without significant changes to the
|
||||
// parser).
|
||||
let warnings = &mut ctxt.as_mut().unwrap().warnings;
|
||||
for w in warnings.iter_mut().rev() {
|
||||
if w.source_range == e.source_range {
|
||||
*w = e;
|
||||
return;
|
||||
}
|
||||
|
||||
if w.source_range.start() > e.source_range.end() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
warnings.push(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,11 +142,8 @@ fn expected(what: &'static str) -> StrContext {
|
||||
fn program(i: TokenSlice) -> PResult<Node<Program>> {
|
||||
let shebang = opt(shebang).parse_next(i)?;
|
||||
let mut out: Node<Program> = function_body.parse_next(i)?;
|
||||
out.shebang = shebang;
|
||||
|
||||
// Add the shebang to the non-code meta.
|
||||
if let Some(shebang) = shebang {
|
||||
out.non_code_meta.start_nodes.insert(0, shebang);
|
||||
}
|
||||
// Match original parser behaviour, for now.
|
||||
// Once this is merged and stable, consider changing this as I think it's more accurate
|
||||
// without the -1.
|
||||
@ -514,7 +531,7 @@ fn whitespace(i: TokenSlice) -> PResult<Vec<Token>> {
|
||||
|
||||
/// A shebang is a line at the start of a file that starts with `#!`.
|
||||
/// If the shebang is present it takes up the whole line.
|
||||
fn shebang(i: TokenSlice) -> PResult<Node<NonCodeNode>> {
|
||||
fn shebang(i: TokenSlice) -> PResult<Node<Shebang>> {
|
||||
// Parse the hash and the bang.
|
||||
hash.parse_next(i)?;
|
||||
bang.parse_next(i)?;
|
||||
@ -537,12 +554,7 @@ fn shebang(i: TokenSlice) -> PResult<Node<NonCodeNode>> {
|
||||
opt(whitespace).parse_next(i)?;
|
||||
|
||||
Ok(Node::new(
|
||||
NonCodeNode {
|
||||
value: NonCodeValue::Shebang {
|
||||
value: format!("#!{}", value),
|
||||
},
|
||||
digest: None,
|
||||
},
|
||||
Shebang::new(format!("#!{}", value)),
|
||||
0,
|
||||
tokens.last().unwrap().end,
|
||||
tokens.first().unwrap().module_id,
|
||||
@ -685,7 +697,7 @@ fn object_property(i: TokenSlice) -> PResult<Node<ObjectProperty>> {
|
||||
let key = identifier.context(expected("the property's key (the name or identifier of the property), e.g. in 'height = 4', 'height' is the property key")).parse_next(i)?;
|
||||
ignore_whitespace(i);
|
||||
// Temporarily accept both `:` and `=` for compatibility.
|
||||
alt((colon, equals))
|
||||
let sep = alt((colon, equals))
|
||||
.context(expected(
|
||||
"`=`, which separates the property's key from the value you're setting it to, e.g. 'height = 4'",
|
||||
))
|
||||
@ -696,7 +708,8 @@ fn object_property(i: TokenSlice) -> PResult<Node<ObjectProperty>> {
|
||||
"the value which you're setting the property to, e.g. in 'height: 4', the value is 4",
|
||||
))
|
||||
.parse_next(i)?;
|
||||
Ok(Node {
|
||||
|
||||
let result = Node {
|
||||
start: key.start,
|
||||
end: expr.end(),
|
||||
module_id: key.module_id,
|
||||
@ -705,7 +718,18 @@ fn object_property(i: TokenSlice) -> PResult<Node<ObjectProperty>> {
|
||||
value: expr,
|
||||
digest: None,
|
||||
},
|
||||
})
|
||||
};
|
||||
|
||||
if sep.token_type == TokenType::Colon {
|
||||
ParseContext::warn(ParseError::with_suggestion(
|
||||
sep.into(),
|
||||
Some(result.as_source_range()),
|
||||
"Using `:` to initialize objects is deprecated, prefer using `=`.",
|
||||
Some(" ="),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Match something that separates properties of an object.
|
||||
@ -1048,7 +1072,6 @@ fn noncode_just_after_code(i: TokenSlice) -> PResult<Node<NonCodeNode>> {
|
||||
// There's an empty line between the body item and the comment,
|
||||
// This means the comment is a NewLineBlockComment!
|
||||
let value = match nc.inner.value {
|
||||
NonCodeValue::Shebang { value } => NonCodeValue::Shebang { value },
|
||||
// Change block comments to inline, as discussed above
|
||||
NonCodeValue::BlockComment { value, style } => NonCodeValue::NewLineBlockComment { value, style },
|
||||
// Other variants don't need to change.
|
||||
@ -1069,7 +1092,6 @@ fn noncode_just_after_code(i: TokenSlice) -> PResult<Node<NonCodeNode>> {
|
||||
// There's no newline between the body item and comment,
|
||||
// so if this is a comment, it must be inline with code.
|
||||
let value = match nc.inner.value {
|
||||
NonCodeValue::Shebang { value } => NonCodeValue::Shebang { value },
|
||||
// Change block comments to inline, as discussed above
|
||||
NonCodeValue::BlockComment { value, style } => NonCodeValue::InlineComment { value, style },
|
||||
// Other variants don't need to change.
|
||||
@ -1269,6 +1291,7 @@ pub fn function_body(i: TokenSlice) -> PResult<Node<Program>> {
|
||||
Program {
|
||||
body,
|
||||
non_code_meta,
|
||||
shebang: None,
|
||||
digest: None,
|
||||
},
|
||||
start.0,
|
||||
@ -1919,16 +1942,14 @@ fn double_period(i: TokenSlice) -> PResult<Token> {
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
fn colon(i: TokenSlice) -> PResult<()> {
|
||||
TokenType::Colon.parse_from(i)?;
|
||||
Ok(())
|
||||
fn colon(i: TokenSlice) -> PResult<Token> {
|
||||
TokenType::Colon.parse_from(i)
|
||||
}
|
||||
|
||||
fn equals(i: TokenSlice) -> PResult<()> {
|
||||
fn equals(i: TokenSlice) -> PResult<Token> {
|
||||
one_of((TokenType::Operator, "="))
|
||||
.context(expected("the equals operator, ="))
|
||||
.parse_next(i)?;
|
||||
Ok(())
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
fn question_mark(i: TokenSlice) -> PResult<()> {
|
||||
@ -2071,57 +2092,84 @@ fn binding_name(i: TokenSlice) -> PResult<Node<Identifier>> {
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
fn typecheck_all(std_fn: Box<dyn StdLibFn>, args: &[Expr]) -> PResult<()> {
|
||||
// Type check the arguments.
|
||||
for (i, spec_arg) in std_fn.args(false).iter().enumerate() {
|
||||
let Some(arg) = &args.get(i) else {
|
||||
// The executor checks the number of arguments, so we don't need to check it here.
|
||||
continue;
|
||||
};
|
||||
typecheck(spec_arg, arg)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn typecheck(spec_arg: &crate::docs::StdLibFnArg, arg: &&Expr) -> PResult<()> {
|
||||
match spec_arg.type_.as_ref() {
|
||||
"TagNode" => match &arg {
|
||||
Expr::Identifier(_) => {
|
||||
// These are fine since we want someone to be able to map a variable to a tag declarator.
|
||||
}
|
||||
Expr::TagDeclarator(tag) => {
|
||||
// TODO: Remove this check. It should be redundant.
|
||||
tag.clone()
|
||||
.into_valid_binding_name()
|
||||
.map_err(|e| ErrMode::Cut(ContextError::from(e)))?;
|
||||
}
|
||||
e => {
|
||||
return Err(ErrMode::Cut(
|
||||
KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![SourceRange::from(*arg)],
|
||||
message: format!("Expected a tag declarator like `$name`, found {:?}", e),
|
||||
})
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
},
|
||||
"TagIdentifier" => match &arg {
|
||||
Expr::Identifier(_) => {}
|
||||
Expr::MemberExpression(_) => {}
|
||||
e => {
|
||||
return Err(ErrMode::Cut(
|
||||
KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![SourceRange::from(*arg)],
|
||||
message: format!("Expected a tag identifier like `tagName`, found {:?}", e),
|
||||
})
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fn_call(i: TokenSlice) -> PResult<Node<CallExpression>> {
|
||||
let fn_name = identifier(i)?;
|
||||
opt(whitespace).parse_next(i)?;
|
||||
let _ = terminated(open_paren, opt(whitespace)).parse_next(i)?;
|
||||
let args = arguments(i)?;
|
||||
if let Some(std_fn) = crate::std::get_stdlib_fn(&fn_name.name) {
|
||||
// Type check the arguments.
|
||||
for (i, spec_arg) in std_fn.args(false).iter().enumerate() {
|
||||
let Some(arg) = &args.get(i) else {
|
||||
// The executor checks the number of arguments, so we don't need to check it here.
|
||||
continue;
|
||||
};
|
||||
match spec_arg.type_.as_ref() {
|
||||
"TagNode" => match &arg {
|
||||
Expr::Identifier(_) => {
|
||||
// These are fine since we want someone to be able to map a variable to a tag declarator.
|
||||
}
|
||||
Expr::TagDeclarator(tag) => {
|
||||
// TODO: Remove this check. It should be redundant.
|
||||
tag.clone()
|
||||
.into_valid_binding_name()
|
||||
.map_err(|e| ErrMode::Cut(ContextError::from(e)))?;
|
||||
}
|
||||
e => {
|
||||
return Err(ErrMode::Cut(
|
||||
KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![SourceRange::from(*arg)],
|
||||
message: format!("Expected a tag declarator like `$name`, found {:?}", e),
|
||||
})
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
},
|
||||
"TagIdentifier" => match &arg {
|
||||
Expr::Identifier(_) => {}
|
||||
Expr::MemberExpression(_) => {}
|
||||
e => {
|
||||
return Err(ErrMode::Cut(
|
||||
KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![SourceRange::from(*arg)],
|
||||
message: format!("Expected a tag identifier like `tagName`, found {:?}", e),
|
||||
})
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
typecheck_all(std_fn, &args)?;
|
||||
}
|
||||
let end = preceded(opt(whitespace), close_paren).parse_next(i)?.end;
|
||||
|
||||
// This should really be done with resolved names, but we don't have warning support there
|
||||
// so we'll hack this in here.
|
||||
if fn_name.name == "int" {
|
||||
assert_eq!(args.len(), 1);
|
||||
let mut arg_str = args[0].recast(&crate::FormatOptions::default(), 0, false);
|
||||
if arg_str.contains('.') && !arg_str.ends_with(".0") {
|
||||
arg_str = format!("round({arg_str})");
|
||||
}
|
||||
ParseContext::warn(ParseError::with_suggestion(
|
||||
SourceRange::new(fn_name.start, end, fn_name.module_id),
|
||||
None,
|
||||
"`int` function is deprecated. You may not need it at all. If you need to round, consider `round`, `ceil`, or `floor`.",
|
||||
Some(arg_str),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Node {
|
||||
start: fn_name.start,
|
||||
end,
|
||||
@ -2259,7 +2307,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_comments_in_function2() {
|
||||
let test_program = r#"() => {
|
||||
const yo = { a: { b: { c: '123' } } } /* block
|
||||
const yo = { a = { b = { c = '123' } } } /* block
|
||||
comment */
|
||||
}"#;
|
||||
let tokens = crate::token::lexer(test_program, ModuleId::default()).unwrap();
|
||||
@ -2373,6 +2421,7 @@ const mySk1 = startSketchAt([0, 0])"#;
|
||||
)],
|
||||
digest: None,
|
||||
},
|
||||
shebang: None,
|
||||
digest: None,
|
||||
},
|
||||
7,
|
||||
@ -2418,7 +2467,7 @@ const mySk1 = startSketchAt([0, 0])"#;
|
||||
#[test]
|
||||
fn many_comments() {
|
||||
let test_program = r#"// this is a comment
|
||||
const yo = { a: { b: { c: '123' } } } /* block
|
||||
const yo = { a = { b = { c = '123' } } } /* block
|
||||
comment */
|
||||
|
||||
const key = 'c'
|
||||
@ -2455,8 +2504,8 @@ const mySk1 = startSketchAt([0, 0])"#;
|
||||
},
|
||||
digest: None,
|
||||
},
|
||||
60,
|
||||
82,
|
||||
63,
|
||||
85,
|
||||
module_id,
|
||||
),
|
||||
Node::new(
|
||||
@ -2464,8 +2513,8 @@ const mySk1 = startSketchAt([0, 0])"#;
|
||||
value: NonCodeValue::NewLine,
|
||||
digest: None,
|
||||
},
|
||||
82,
|
||||
86,
|
||||
85,
|
||||
89,
|
||||
module_id,
|
||||
)
|
||||
]),
|
||||
@ -2481,8 +2530,8 @@ const mySk1 = startSketchAt([0, 0])"#;
|
||||
},
|
||||
digest: None,
|
||||
},
|
||||
103,
|
||||
129,
|
||||
106,
|
||||
132,
|
||||
module_id,
|
||||
)]),
|
||||
non_code_meta.non_code_nodes.get(&1),
|
||||
@ -2858,7 +2907,7 @@ const mySk1 = startSketchAt([0, 0])"#;
|
||||
fn test_pipes_on_pipes() {
|
||||
let test_program = include_str!("../../../tests/executor/inputs/pipes_on_pipes.kcl");
|
||||
let tokens = crate::token::lexer(test_program, ModuleId::default()).unwrap();
|
||||
let _actual = program.parse(&tokens).unwrap();
|
||||
let _ = run_parser(&mut &*tokens).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -3110,6 +3159,7 @@ const mySk1 = startSketchAt([0, 0])"#;
|
||||
4,
|
||||
module_id,
|
||||
))],
|
||||
shebang: None,
|
||||
non_code_meta: NonCodeMeta::default(),
|
||||
digest: None,
|
||||
},
|
||||
@ -3128,6 +3178,14 @@ const mySk1 = startSketchAt([0, 0])"#;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_no_err(p: &str) -> (Node<Program>, ParseContext) {
|
||||
let result = crate::parser::top_level_parse(p);
|
||||
let result = result.0.unwrap();
|
||||
assert!(result.1.errors.is_empty());
|
||||
(result.0.unwrap(), result.1)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_err(p: &str, msg: &str, src: [usize; 2]) {
|
||||
let result = crate::parser::top_level_parse(p);
|
||||
@ -3658,6 +3716,28 @@ let myBox = box([0,0], -3, -16, -10)
|
||||
"#;
|
||||
assert_err(some_program_string, "Unexpected token: |>", [57, 59]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn warn_object_expr() {
|
||||
let some_program_string = "{ foo: bar }";
|
||||
let (_, ctxt) = assert_no_err(some_program_string);
|
||||
assert_eq!(ctxt.warnings.len(), 1);
|
||||
assert_eq!(
|
||||
ctxt.warnings[0].apply_suggestion(some_program_string).unwrap(),
|
||||
"{ foo = bar }"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn warn_fn_int() {
|
||||
let some_program_string = r#"int(1.0)
|
||||
int(42.3)"#;
|
||||
let (_, ctxt) = assert_no_err(some_program_string);
|
||||
assert_eq!(ctxt.warnings.len(), 2);
|
||||
let replaced = ctxt.warnings[1].apply_suggestion(some_program_string).unwrap();
|
||||
let replaced = ctxt.warnings[0].apply_suggestion(&replaced).unwrap();
|
||||
assert_eq!(replaced, "1.0\nround(42.3)");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -3673,11 +3753,14 @@ mod snapshot_math_tests {
|
||||
fn $func_name() {
|
||||
let module_id = crate::ast::types::ModuleId::default();
|
||||
let tokens = crate::token::lexer($test_kcl_program, module_id).unwrap();
|
||||
ParseContext::init();
|
||||
|
||||
let actual = match binary_expression.parse(&tokens) {
|
||||
Ok(x) => x,
|
||||
Err(_e) => panic!("could not parse test"),
|
||||
};
|
||||
insta::assert_json_snapshot!(actual);
|
||||
let _ = ParseContext::take();
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -3709,6 +3792,7 @@ mod snapshot_tests {
|
||||
let module_id = crate::ast::types::ModuleId::default();
|
||||
let tokens = crate::token::lexer($test_kcl_program, module_id).unwrap();
|
||||
print_tokens(&tokens);
|
||||
ParseContext::init();
|
||||
let actual = match program.parse(&tokens) {
|
||||
Ok(x) => x,
|
||||
Err(e) => panic!("could not parse test: {e:?}"),
|
||||
@ -3718,6 +3802,7 @@ mod snapshot_tests {
|
||||
settings.bind(|| {
|
||||
insta::assert_json_snapshot!(actual);
|
||||
});
|
||||
let _ = ParseContext::take();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -24,9 +24,11 @@ pub struct ContextError<C = StrContext> {
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ParseError {
|
||||
pub source_range: SourceRange,
|
||||
#[allow(dead_code)]
|
||||
pub context_range: Option<SourceRange>,
|
||||
pub message: String,
|
||||
#[allow(dead_code)]
|
||||
pub suggestion: String,
|
||||
pub suggestion: Option<String>,
|
||||
pub severity: Severity,
|
||||
}
|
||||
|
||||
@ -34,25 +36,38 @@ impl ParseError {
|
||||
pub(super) fn err(source_range: SourceRange, message: impl ToString) -> ParseError {
|
||||
ParseError {
|
||||
source_range,
|
||||
context_range: None,
|
||||
message: message.to_string(),
|
||||
suggestion: String::new(),
|
||||
suggestion: None,
|
||||
severity: Severity::Error,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(super) fn with_suggestion(
|
||||
source_range: SourceRange,
|
||||
context_range: Option<SourceRange>,
|
||||
message: impl ToString,
|
||||
suggestion: impl ToString,
|
||||
suggestion: Option<impl ToString>,
|
||||
) -> ParseError {
|
||||
ParseError {
|
||||
source_range,
|
||||
context_range,
|
||||
message: message.to_string(),
|
||||
suggestion: suggestion.to_string(),
|
||||
suggestion: suggestion.map(|s| s.to_string()),
|
||||
severity: Severity::Error,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn apply_suggestion(&self, src: &str) -> Option<String> {
|
||||
let suggestion = self.suggestion.as_ref()?;
|
||||
Some(format!(
|
||||
"{}{}{}",
|
||||
&src[0..self.source_range.start()],
|
||||
suggestion,
|
||||
&src[self.source_range.end()..]
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseError> for KclError {
|
||||
@ -73,6 +88,8 @@ pub(crate) enum Severity {
|
||||
|
||||
/// Helper enum for the below conversion of Winnow errors into either a parse error or an unexpected
|
||||
/// error.
|
||||
// TODO we should optimise the size of SourceRange and thus ParseError
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub(super) enum ErrorKind {
|
||||
Parse(ParseError),
|
||||
Internal(KclError),
|
||||
|
@ -99,9 +99,30 @@ async fn execute(test_name: &str, render_to_png: bool) {
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
assert_snapshot(test_name, "Error from executing", || {
|
||||
insta::assert_snapshot!("execution_error", e);
|
||||
});
|
||||
match e {
|
||||
crate::errors::ExecError::Kcl(error) => {
|
||||
// Snapshot the KCL error with a fancy graphical report.
|
||||
// This looks like a Cargo compile error, with arrows pointing
|
||||
// to source code, underlines, etc.
|
||||
let report = crate::errors::Report {
|
||||
error,
|
||||
filename: format!("{test_name}.kcl"),
|
||||
kcl_source: read("input.kcl", test_name),
|
||||
};
|
||||
let report = miette::Report::new(report);
|
||||
let report = format!("{:?}", report);
|
||||
|
||||
assert_snapshot(test_name, "Error from executing", || {
|
||||
insta::assert_snapshot!("execution_error", report);
|
||||
});
|
||||
}
|
||||
e => {
|
||||
// These kinds of errors aren't expected to occur. We don't
|
||||
// snapshot them because they indicate there's something wrong
|
||||
// with the Rust test, not with the KCL code being tested.
|
||||
panic!("{e}")
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::{any::type_name, collections::HashMap, num::NonZeroU32};
|
||||
use std::{any::type_name, num::NonZeroU32};
|
||||
|
||||
use anyhow::Result;
|
||||
use kcmc::{websocket::OkWebSocketResponseData, ModelingCmd};
|
||||
@ -11,6 +11,7 @@ use crate::{
|
||||
ExecState, ExecutorContext, ExtrudeSurface, KclValue, Metadata, Sketch, SketchSet, SketchSurface, Solid,
|
||||
SolidSet, SourceRange, TagIdentifier,
|
||||
},
|
||||
kcl_value::KclObjectFields,
|
||||
std::{shapes::SketchOrSurface, sketch::FaceTag, FnAsArg},
|
||||
};
|
||||
|
||||
@ -1169,7 +1170,7 @@ impl<'a> FromKclValue<'a> for super::sketch::PlaneData {
|
||||
}
|
||||
// Case 2: custom plane
|
||||
let obj = arg.as_object()?;
|
||||
let_field_of!(obj, plane, &std::collections::HashMap<String, KclValue>);
|
||||
let_field_of!(obj, plane, &KclObjectFields);
|
||||
let origin = plane.get("origin").and_then(FromKclValue::from_kcl_val).map(Box::new)?;
|
||||
let x_axis = plane
|
||||
.get("xAxis")
|
||||
@ -1359,7 +1360,7 @@ impl<'a> FromKclValue<'a> for super::revolve::AxisAndOrigin {
|
||||
}
|
||||
// Case 2: custom planes.
|
||||
let obj = arg.as_object()?;
|
||||
let_field_of!(obj, custom, &HashMap<String, KclValue>);
|
||||
let_field_of!(obj, custom, &KclObjectFields);
|
||||
let_field_of!(custom, origin);
|
||||
let_field_of!(custom, axis);
|
||||
Some(Self::Custom { axis, origin })
|
||||
@ -1419,7 +1420,7 @@ impl<'a> FromKclValue<'a> for i64 {
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for &'a str {
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<&'a str> {
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||
let KclValue::String { value, meta: _ } = arg else {
|
||||
return None;
|
||||
};
|
||||
@ -1427,8 +1428,8 @@ impl<'a> FromKclValue<'a> for &'a str {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for &'a HashMap<String, KclValue> {
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<&'a HashMap<String, KclValue>> {
|
||||
impl<'a> FromKclValue<'a> for &'a KclObjectFields {
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||
let KclValue::Object { value, meta: _ } = arg else {
|
||||
return None;
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! Standard library patterns.
|
||||
|
||||
use std::{cmp::Ordering, collections::HashMap};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use anyhow::Result;
|
||||
use derive_docs::stdlib;
|
||||
@ -22,6 +22,7 @@ use crate::{
|
||||
ExecState, Geometries, Geometry, KclValue, Point2d, Point3d, Sketch, SketchSet, Solid, SolidSet, SourceRange,
|
||||
},
|
||||
function_param::FunctionParam,
|
||||
kcl_value::KclObjectFields,
|
||||
std::Args,
|
||||
};
|
||||
|
||||
@ -461,7 +462,7 @@ async fn make_transform<'a, T: GeometryTrait>(
|
||||
}
|
||||
|
||||
fn transform_from_obj_fields<T: GeometryTrait>(
|
||||
transform: HashMap<String, KclValue>,
|
||||
transform: KclObjectFields,
|
||||
source_ranges: Vec<SourceRange>,
|
||||
) -> Result<Transform, KclError> {
|
||||
// Apply defaults to the transform.
|
||||
|
@ -130,7 +130,7 @@ async fn inner_circle(
|
||||
},
|
||||
radius: data.radius,
|
||||
center: data.center,
|
||||
ccw: angle_start.to_degrees() < angle_end.to_degrees(),
|
||||
ccw: angle_start < angle_end,
|
||||
};
|
||||
|
||||
let mut new_sketch = sketch.clone();
|
||||
|
@ -1564,7 +1564,7 @@ pub(crate) async fn inner_arc(
|
||||
source_ranges: vec![args.source_range],
|
||||
}));
|
||||
}
|
||||
let ccw = angle_start.to_degrees() < angle_end.to_degrees();
|
||||
let ccw = angle_start < angle_end;
|
||||
|
||||
let id = exec_state.id_generator.next_uuid();
|
||||
|
||||
|
@ -67,6 +67,8 @@ pub enum TokenType {
|
||||
Unknown,
|
||||
/// The ? symbol, used for optional values.
|
||||
QuestionMark,
|
||||
/// The @ symbol.
|
||||
At,
|
||||
}
|
||||
|
||||
/// Most KCL tokens correspond to LSP semantic tokens (but not all).
|
||||
@ -93,6 +95,7 @@ impl TryFrom<TokenType> for SemanticTokenType {
|
||||
| TokenType::DoublePeriod
|
||||
| TokenType::Hash
|
||||
| TokenType::Dollar
|
||||
| TokenType::At
|
||||
| TokenType::Unknown => {
|
||||
anyhow::bail!("unsupported token type: {:?}", token_type)
|
||||
}
|
||||
|
@ -92,6 +92,7 @@ pub fn token(i: &mut Input<'_>) -> PResult<Token> {
|
||||
'}' | ')' | ']' => brace_end,
|
||||
',' => comma,
|
||||
'?' => question_mark,
|
||||
'@' => at,
|
||||
'0'..='9' => number,
|
||||
':' => colon,
|
||||
'.' => alt((number, double_period, period)),
|
||||
@ -268,6 +269,16 @@ fn question_mark(i: &mut Input<'_>) -> PResult<Token> {
|
||||
))
|
||||
}
|
||||
|
||||
fn at(i: &mut Input<'_>) -> PResult<Token> {
|
||||
let (value, range) = '@'.with_span().parse_next(i)?;
|
||||
Ok(Token::from_range(
|
||||
range,
|
||||
i.state.module_id,
|
||||
TokenType::At,
|
||||
value.to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
fn colon(i: &mut Input<'_>) -> PResult<Token> {
|
||||
let (value, range) = ':'.with_span().parse_next(i)?;
|
||||
Ok(Token::from_range(
|
||||
|
@ -13,6 +13,13 @@ use crate::{
|
||||
impl Program {
|
||||
pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
||||
let indentation = options.get_indentation(indentation_level);
|
||||
|
||||
let result = self
|
||||
.shebang
|
||||
.as_ref()
|
||||
.map(|sh| format!("{}\n\n", sh.inner.content))
|
||||
.unwrap_or_default();
|
||||
|
||||
let result = self
|
||||
.body
|
||||
.iter()
|
||||
@ -38,7 +45,7 @@ impl Program {
|
||||
}
|
||||
})
|
||||
.enumerate()
|
||||
.fold(String::new(), |mut output, (index, recast_str)| {
|
||||
.fold(result, |mut output, (index, recast_str)| {
|
||||
let start_string = if index == 0 {
|
||||
// We need to indent.
|
||||
if self.non_code_meta.start_nodes.is_empty() {
|
||||
@ -107,7 +114,7 @@ impl NonCodeValue {
|
||||
fn should_cause_array_newline(&self) -> bool {
|
||||
match self {
|
||||
Self::InlineComment { .. } => false,
|
||||
Self::Shebang { .. } | Self::BlockComment { .. } | Self::NewLineBlockComment { .. } | Self::NewLine => true,
|
||||
Self::BlockComment { .. } | Self::NewLineBlockComment { .. } | Self::NewLine => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,13 @@
|
||||
---
|
||||
source: kcl/src/simulation_tests.rs
|
||||
description: Error from executing argument_error.kcl
|
||||
snapshot_kind: text
|
||||
---
|
||||
type: KclErrorDetails { source_ranges: [SourceRange([34, 35, 0])], message: "Expected an array but found Function" }
|
||||
KCL Type error
|
||||
|
||||
× type: Expected an array but found Function
|
||||
╭─[5:5]
|
||||
4 │
|
||||
5 │ map(f, [0, 1])
|
||||
· ─
|
||||
╰────
|
||||
|
@ -3,4 +3,11 @@ source: kcl/src/simulation_tests.rs
|
||||
description: Error from executing array_elem_push_fail.kcl
|
||||
snapshot_kind: text
|
||||
---
|
||||
undefined value: KclErrorDetails { source_ranges: [SourceRange([48, 54, 0])], message: "The array doesn't have any item at index 3" }
|
||||
KCL UndefinedValue error
|
||||
|
||||
× undefined value: The array doesn't have any item at index 3
|
||||
╭─[3:8]
|
||||
2 │ pushedArr = push(arr, 4)
|
||||
3 │ fail = arr[3]
|
||||
· ──────
|
||||
╰────
|
||||
|
@ -3,4 +3,11 @@ source: kcl/src/simulation_tests.rs
|
||||
description: Error from executing array_index_oob.kcl
|
||||
snapshot_kind: text
|
||||
---
|
||||
undefined value: KclErrorDetails { source_ranges: [SourceRange([13, 19, 0])], message: "The array doesn't have any item at index 0" }
|
||||
KCL UndefinedValue error
|
||||
|
||||
× undefined value: The array doesn't have any item at index 0
|
||||
╭─[2:5]
|
||||
1 │ arr = []
|
||||
2 │ x = arr[0]
|
||||
· ──────
|
||||
╰────
|
||||
|
@ -3,4 +3,10 @@ source: kcl/src/simulation_tests.rs
|
||||
description: Error from executing comparisons_multiple.kcl
|
||||
snapshot_kind: text
|
||||
---
|
||||
semantic: KclErrorDetails { source_ranges: [SourceRange([7, 13, 0])], message: "Expected a number, but found a boolean (true/false value)" }
|
||||
KCL Semantic error
|
||||
|
||||
× semantic: Expected a number, but found a boolean (true/false value)
|
||||
╭────
|
||||
1 │ assert(3 == 3 == 3, "this should not compile")
|
||||
· ──────
|
||||
╰────
|
||||
|
@ -3,4 +3,11 @@ source: kcl/src/simulation_tests.rs
|
||||
description: Error from executing import_constant.kcl
|
||||
snapshot_kind: text
|
||||
---
|
||||
engine: KclErrorDetails { source_ranges: [SourceRange([0, 39, 0])], message: "Failed to read file `export_constant.kcl`: No such file or directory (os error 2)" }
|
||||
KCL Engine error
|
||||
|
||||
× engine: Failed to read file `export_constant.kcl`: No such file or
|
||||
│ directory (os error 2)
|
||||
╭────
|
||||
1 │ import three from "export_constant.kcl"
|
||||
· ───────────────────────────────────────
|
||||
╰────
|
||||
|
@ -3,4 +3,12 @@ source: kcl/src/simulation_tests.rs
|
||||
description: Error from executing import_cycle1.kcl
|
||||
snapshot_kind: text
|
||||
---
|
||||
engine: KclErrorDetails { source_ranges: [SourceRange([0, 35, 0])], message: "Failed to read file `import_cycle2.kcl`: No such file or directory (os error 2)" }
|
||||
KCL Engine error
|
||||
|
||||
× engine: Failed to read file `import_cycle2.kcl`: No such file or directory
|
||||
│ (os error 2)
|
||||
╭─[1:1]
|
||||
1 │ import two from "import_cycle2.kcl"
|
||||
· ───────────────────────────────────
|
||||
2 │
|
||||
╰────
|
||||
|
@ -3,4 +3,11 @@ source: kcl/src/simulation_tests.rs
|
||||
description: Error from executing import_side_effect.kcl
|
||||
snapshot_kind: text
|
||||
---
|
||||
engine: KclErrorDetails { source_ranges: [SourceRange([0, 40, 0])], message: "Failed to read file `export_side_effect.kcl`: No such file or directory (os error 2)" }
|
||||
KCL Engine error
|
||||
|
||||
× engine: Failed to read file `export_side_effect.kcl`: No such file or
|
||||
│ directory (os error 2)
|
||||
╭────
|
||||
1 │ import foo from "export_side_effect.kcl"
|
||||
· ────────────────────────────────────────
|
||||
╰────
|
||||
|
@ -1,7 +1,13 @@
|
||||
---
|
||||
source: kcl/src/simulation_tests.rs
|
||||
assertion_line: 103
|
||||
description: Error from executing invalid_index_fractional.kcl
|
||||
snapshot_kind: text
|
||||
---
|
||||
semantic: KclErrorDetails { source_ranges: [SourceRange([20, 28, 0])], message: "1.2 is not a valid index, indices must be whole numbers >= 0" }
|
||||
KCL Semantic error
|
||||
|
||||
× semantic: 1.2 is not a valid index, indices must be whole numbers >= 0
|
||||
╭─[2:5]
|
||||
1 │ arr = [1, 2, 3]
|
||||
2 │ x = arr[1.2]
|
||||
· ────────
|
||||
╰────
|
||||
|
@ -3,4 +3,11 @@ source: kcl/src/simulation_tests.rs
|
||||
description: Error from executing invalid_index_negative.kcl
|
||||
snapshot_kind: text
|
||||
---
|
||||
semantic: KclErrorDetails { source_ranges: [SourceRange([27, 33, 0])], message: "'-1' is negative, so you can't index an array with it" }
|
||||
KCL Semantic error
|
||||
|
||||
× semantic: '-1' is negative, so you can't index an array with it
|
||||
╭─[3:5]
|
||||
2 │ i = -1
|
||||
3 │ x = arr[i]
|
||||
· ──────
|
||||
╰────
|
||||
|
@ -3,4 +3,12 @@ source: kcl/src/simulation_tests.rs
|
||||
description: Error from executing invalid_index_str.kcl
|
||||
snapshot_kind: text
|
||||
---
|
||||
semantic: KclErrorDetails { source_ranges: [SourceRange([20, 28, 0])], message: "Only integers >= 0 can be used as the index of an array, but you're using a string" }
|
||||
KCL Semantic error
|
||||
|
||||
× semantic: Only integers >= 0 can be used as the index of an array, but
|
||||
│ you're using a string
|
||||
╭─[2:5]
|
||||
1 │ arr = [1, 2, 3]
|
||||
2 │ x = arr["s"]
|
||||
· ────────
|
||||
╰────
|
||||
|
@ -1,7 +1,14 @@
|
||||
---
|
||||
source: kcl/src/simulation_tests.rs
|
||||
assertion_line: 103
|
||||
description: Error from executing invalid_member_object.kcl
|
||||
snapshot_kind: text
|
||||
---
|
||||
semantic: KclErrorDetails { source_ranges: [SourceRange([14, 20, 0])], message: "Only arrays and objects can be indexed, but you're trying to index a number" }
|
||||
KCL Semantic error
|
||||
|
||||
× semantic: Only arrays and objects can be indexed, but you're trying to
|
||||
│ index a number
|
||||
╭─[2:5]
|
||||
1 │ num = 999
|
||||
2 │ x = num[3]
|
||||
· ──────
|
||||
╰────
|
||||
|
@ -3,4 +3,12 @@ source: kcl/src/simulation_tests.rs
|
||||
description: Error from executing invalid_member_object_prop.kcl
|
||||
snapshot_kind: text
|
||||
---
|
||||
semantic: KclErrorDetails { source_ranges: [SourceRange([13, 26, 0])], message: "Only arrays and objects can be indexed, but you're trying to index a boolean (true/false value)" }
|
||||
KCL Semantic error
|
||||
|
||||
× semantic: Only arrays and objects can be indexed, but you're trying to
|
||||
│ index a boolean (true/false value)
|
||||
╭─[2:5]
|
||||
1 │ b = true
|
||||
2 │ x = b["property"]
|
||||
· ─────────────
|
||||
╰────
|
||||
|
@ -1,7 +1,14 @@
|
||||
---
|
||||
source: kcl/src/simulation_tests.rs
|
||||
assertion_line: 116
|
||||
description: Error from executing non_string_key_of_object.kcl
|
||||
snapshot_kind: text
|
||||
---
|
||||
semantic: KclErrorDetails { source_ranges: [SourceRange([26, 32, 0])], message: "Only strings can be used as the property of an object, but you're using a number" }
|
||||
KCL Semantic error
|
||||
|
||||
× semantic: Only strings can be used as the property of an object, but
|
||||
│ you're using a number
|
||||
╭─[2:7]
|
||||
1 │ obj = { key = 123 }
|
||||
2 │ num = obj[3]
|
||||
· ──────
|
||||
╰────
|
||||
|
@ -3,4 +3,11 @@ source: kcl/src/simulation_tests.rs
|
||||
description: Error from executing object_prop_not_found.kcl
|
||||
snapshot_kind: text
|
||||
---
|
||||
undefined value: KclErrorDetails { source_ranges: [SourceRange([15, 25, 0])], message: "Property 'age' not found in object" }
|
||||
KCL UndefinedValue error
|
||||
|
||||
× undefined value: Property 'age' not found in object
|
||||
╭─[2:5]
|
||||
1 │ obj = { }
|
||||
2 │ k = obj["age"]
|
||||
· ──────────
|
||||
╰────
|
||||
|
@ -3,4 +3,12 @@ source: kcl/src/simulation_tests.rs
|
||||
description: Error from executing pipe_substitution_inside_function_called_from_pipeline.kcl
|
||||
snapshot_kind: text
|
||||
---
|
||||
semantic: KclErrorDetails { source_ranges: [SourceRange([106, 107, 0])], message: "cannot use % outside a pipe expression" }
|
||||
KCL Semantic error
|
||||
|
||||
× semantic: cannot use % outside a pipe expression
|
||||
╭─[6:10]
|
||||
5 │
|
||||
6 │ answer = %
|
||||
· ─
|
||||
7 │ |> f(%)
|
||||
╰────
|
||||
|
37
yarn.lock
37
yarn.lock
@ -2274,10 +2274,10 @@
|
||||
"@react-hook/latest" "^1.0.2"
|
||||
"@react-hook/passive-layout-effect" "^1.2.0"
|
||||
|
||||
"@remix-run/router@1.20.0":
|
||||
version "1.20.0"
|
||||
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.20.0.tgz#03554155b45d8b529adf635b2f6ad1165d70d8b4"
|
||||
integrity sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==
|
||||
"@remix-run/router@1.21.0":
|
||||
version "1.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.21.0.tgz#c65ae4262bdcfe415dbd4f64ec87676e4a56e2b5"
|
||||
integrity sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==
|
||||
|
||||
"@replit/codemirror-interact@^6.3.1":
|
||||
version "6.3.1"
|
||||
@ -8126,20 +8126,20 @@ react-refresh@^0.14.2:
|
||||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9"
|
||||
integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==
|
||||
|
||||
react-router-dom@^6.27.0:
|
||||
version "6.27.0"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.27.0.tgz#8d7972a425fd75f91c1e1ff67e47240c5752dc3f"
|
||||
integrity sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==
|
||||
react-router-dom@^6.28.0:
|
||||
version "6.28.0"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.28.0.tgz#f73ebb3490e59ac9f299377062ad1d10a9f579e6"
|
||||
integrity sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==
|
||||
dependencies:
|
||||
"@remix-run/router" "1.20.0"
|
||||
react-router "6.27.0"
|
||||
"@remix-run/router" "1.21.0"
|
||||
react-router "6.28.0"
|
||||
|
||||
react-router@6.27.0:
|
||||
version "6.27.0"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.27.0.tgz#db292474926c814c996c0ff3ef0162d1f9f60ed4"
|
||||
integrity sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==
|
||||
react-router@6.28.0:
|
||||
version "6.28.0"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.28.0.tgz#29247c86d7ba901d7e5a13aa79a96723c3e59d0d"
|
||||
integrity sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==
|
||||
dependencies:
|
||||
"@remix-run/router" "1.20.0"
|
||||
"@remix-run/router" "1.21.0"
|
||||
|
||||
react-textarea-autosize@^8.3.2:
|
||||
version "8.5.3"
|
||||
@ -9369,11 +9369,16 @@ typed-array-length@^1.0.6:
|
||||
is-typed-array "^1.1.13"
|
||||
possible-typed-array-names "^1.0.0"
|
||||
|
||||
typescript@^5.0.0, typescript@^5.3.3:
|
||||
typescript@^5.3.3:
|
||||
version "5.5.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba"
|
||||
integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==
|
||||
|
||||
typescript@^5.7.2:
|
||||
version "5.7.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6"
|
||||
integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==
|
||||
|
||||
ua-parser-js@^1.0.35, ua-parser-js@^1.0.37:
|
||||
version "1.0.38"
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.38.tgz#66bb0c4c0e322fe48edfe6d446df6042e62f25e2"
|
||||
|
Reference in New Issue
Block a user