Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
d68d7a7e00 | |||
b135b97de6 | |||
de5885ce0b | |||
ad7c544754 | |||
4d77875bdc | |||
3377923dcb | |||
c6005660c8 | |||
66e62c6037 | |||
0a4a517bb4 |
2
.github/workflows/playwright.yml
vendored
2
.github/workflows/playwright.yml
vendored
@ -79,7 +79,7 @@ jobs:
|
|||||||
|
|
||||||
playwright-macos:
|
playwright-macos:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
runs-on: macos-latest
|
runs-on: macos-14
|
||||||
needs: playwright-ubuntu
|
needs: playwright-ubuntu
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
@ -68,7 +68,7 @@ test('Basic sketch', async ({ page }) => {
|
|||||||
`const part001 = startSketchOn('-XZ')`
|
`const part001 = startSketchOn('-XZ')`
|
||||||
)
|
)
|
||||||
|
|
||||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
|
||||||
|
|
||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
@ -367,6 +367,7 @@ test('Auto complete works', async ({ page }) => {
|
|||||||
await page.keyboard.type(' |> startProfi')
|
await page.keyboard.type(' |> startProfi')
|
||||||
// expect there be a single auto complete option that we can just hit enter on
|
// expect there be a single auto complete option that we can just hit enter on
|
||||||
await expect(page.locator('.cm-completionLabel')).toBeVisible()
|
await expect(page.locator('.cm-completionLabel')).toBeVisible()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page.keyboard.press('Enter') // accepting the auto complete, not a new line
|
await page.keyboard.press('Enter') // accepting the auto complete, not a new line
|
||||||
|
|
||||||
await page.keyboard.type('([0,0], %)')
|
await page.keyboard.type('([0,0], %)')
|
||||||
@ -576,7 +577,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
await page.waitForTimeout(200)
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
// enter sketch again
|
// enter sketch again
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
await page.waitForTimeout(700) // wait for animation
|
await page.waitForTimeout(700) // wait for animation
|
||||||
|
|
||||||
// hover again and check it works
|
// hover again and check it works
|
||||||
@ -811,7 +812,7 @@ const part002 = startSketchOn('XY')
|
|||||||
|
|
||||||
test('ProgramMemory can be serialised', async ({ page, context }) => {
|
test('ProgramMemory can be serialised', async ({ page, context }) => {
|
||||||
const u = getUtils(page)
|
const u = getUtils(page)
|
||||||
await context.addInitScript(async (token) => {
|
await context.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`const part = startSketchOn('XY')
|
`const part = startSketchOn('XY')
|
||||||
@ -847,3 +848,103 @@ test('ProgramMemory can be serialised', async ({ page, context }) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('edit selections', async ({ page, context }) => {
|
||||||
|
const u = getUtils(page)
|
||||||
|
const selectionsSnippets = {
|
||||||
|
extrudeAndEditBlocked: '|> startProfileAt([10.81, 32.99], %)',
|
||||||
|
extrudeAndEditBlockedInFunction: '|> startProfileAt(pos, %)',
|
||||||
|
extrudeAndEditAllowed: '|> startProfileAt([15.72, 4.7], %)',
|
||||||
|
editOnly: '|> startProfileAt([15.79, -14.6], %)',
|
||||||
|
}
|
||||||
|
await context.addInitScript(
|
||||||
|
async ({
|
||||||
|
extrudeAndEditBlocked,
|
||||||
|
extrudeAndEditBlockedInFunction,
|
||||||
|
extrudeAndEditAllowed,
|
||||||
|
editOnly,
|
||||||
|
}: any) => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const part001 = startSketchOn('-XZ')
|
||||||
|
${extrudeAndEditBlocked}
|
||||||
|
|> line([25.96, 2.93], %)
|
||||||
|
|> line([5.25, -5.72], %)
|
||||||
|
|> line([-2.01, -10.35], %)
|
||||||
|
|> line([-27.65, -2.78], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5, %)
|
||||||
|
const part002 = startSketchOn('-XZ')
|
||||||
|
${extrudeAndEditAllowed}
|
||||||
|
|> line([10.32, 6.47], %)
|
||||||
|
|> line([9.71, -6.16], %)
|
||||||
|
|> line([-3.08, -9.86], %)
|
||||||
|
|> line([-12.02, -1.54], %)
|
||||||
|
|> close(%)
|
||||||
|
const part003 = startSketchOn('-XZ')
|
||||||
|
${editOnly}
|
||||||
|
|> line([27.55, -1.65], %)
|
||||||
|
|> line([4.95, -8], %)
|
||||||
|
|> line([-20.38, -10.12], %)
|
||||||
|
|> line([-15.79, 17.08], %)
|
||||||
|
|
||||||
|
fn yohey = (pos) => {
|
||||||
|
const part004 = startSketchOn('-XZ')
|
||||||
|
${extrudeAndEditBlockedInFunction}
|
||||||
|
|> line([27.55, -1.65], %)
|
||||||
|
|> line([4.95, -10.53], %)
|
||||||
|
|> line([-20.38, -8], %)
|
||||||
|
|> line([-15.79, 17.08], %)
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
yohey([15.79, -34.6])
|
||||||
|
`
|
||||||
|
)
|
||||||
|
},
|
||||||
|
selectionsSnippets
|
||||||
|
)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await page.goto('/')
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// wait for execution done
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await page.getByText(selectionsSnippets.extrudeAndEditBlocked).click()
|
||||||
|
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
|
).not.toBeVisible()
|
||||||
|
|
||||||
|
await page.getByText(selectionsSnippets.extrudeAndEditAllowed).click()
|
||||||
|
await expect(page.getByRole('button', { name: 'Extrude' })).not.toBeDisabled()
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
|
).not.toBeDisabled()
|
||||||
|
|
||||||
|
await page.getByText(selectionsSnippets.editOnly).click()
|
||||||
|
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
|
).not.toBeDisabled()
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByText(selectionsSnippets.extrudeAndEditBlockedInFunction)
|
||||||
|
.click()
|
||||||
|
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
|
).not.toBeVisible()
|
||||||
|
|
||||||
|
// selecting an editable sketch but clicking "start sktech" should start a new sketch and not edit the existing one
|
||||||
|
await page.getByText(selectionsSnippets.extrudeAndEditAllowed).click()
|
||||||
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
await page.mouse.click(700, 200)
|
||||||
|
// expect main content to contain `part005` i.e. started a new sketch
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
/part005 = startSketchOn\('-XZ'\)/
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@ -419,3 +419,61 @@ test('extrude on each default plane should be stable', async ({
|
|||||||
await runSnapshotsForOtherPlanes('YZ')
|
await runSnapshotsForOtherPlanes('YZ')
|
||||||
await runSnapshotsForOtherPlanes('-YZ')
|
await runSnapshotsForOtherPlanes('-YZ')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Draft segments should look right', async ({ page }) => {
|
||||||
|
const u = getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.goto('/')
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await u.openDebugPanel()
|
||||||
|
|
||||||
|
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
|
||||||
|
|
||||||
|
// click on "Start Sketch" button
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
await u.doAndWaitForImageDiff(
|
||||||
|
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
||||||
|
200
|
||||||
|
)
|
||||||
|
|
||||||
|
// select a plane
|
||||||
|
await page.mouse.click(700, 200)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`const part001 = startSketchOn('-XZ')`
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
|
||||||
|
|
||||||
|
const startXPx = 600
|
||||||
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
|
const startAt = '[23.89, -32.23]'
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|
|> startProfileAt(${startAt}, %)`)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
await page.mouse.move(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
|
await expect(page).toHaveScreenshot({
|
||||||
|
maxDiffPixels: 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
const num = 24.11
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|
|> startProfileAt(${startAt}, %)
|
||||||
|
|> line([${num}, 0], %)`)
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||||
|
|
||||||
|
await page.mouse.move(startXPx + PUR * 30, 500 - PUR * 20, { steps: 10 })
|
||||||
|
|
||||||
|
await expect(page).toHaveScreenshot({
|
||||||
|
maxDiffPixels: 100,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "untitled-app",
|
"name": "untitled-app",
|
||||||
"version": "0.15.0",
|
"version": "0.15.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.10.2",
|
"@codemirror/autocomplete": "^6.10.2",
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "zoo-modeling-app",
|
"productName": "zoo-modeling-app",
|
||||||
"version": "0.15.0"
|
"version": "0.15.1"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
|
@ -5,6 +5,8 @@ import { useModelingContext } from 'hooks/useModelingContext'
|
|||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { ActionButton } from 'components/ActionButton'
|
import { ActionButton } from 'components/ActionButton'
|
||||||
import usePlatform from 'hooks/usePlatform'
|
import usePlatform from 'hooks/usePlatform'
|
||||||
|
import { isSingleCursorInPipe } from 'lang/queryAst'
|
||||||
|
import { kclManager } from 'lang/KclSingleton'
|
||||||
|
|
||||||
export const Toolbar = () => {
|
export const Toolbar = () => {
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
@ -13,14 +15,15 @@ export const Toolbar = () => {
|
|||||||
const toolbarButtonsRef = useRef<HTMLUListElement>(null)
|
const toolbarButtonsRef = useRef<HTMLUListElement>(null)
|
||||||
const bgClassName =
|
const bgClassName =
|
||||||
'group-enabled:group-hover:bg-energy-10 group-pressed:bg-energy-10 dark:group-enabled:group-hover:bg-chalkboard-80 dark:group-pressed:bg-chalkboard-80'
|
'group-enabled:group-hover:bg-energy-10 group-pressed:bg-energy-10 dark:group-enabled:group-hover:bg-chalkboard-80 dark:group-pressed:bg-chalkboard-80'
|
||||||
const pathId = useMemo(
|
const pathId = useMemo(() => {
|
||||||
() =>
|
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast)) {
|
||||||
isCursorInSketchCommandRange(
|
return false
|
||||||
|
}
|
||||||
|
return isCursorInSketchCommandRange(
|
||||||
engineCommandManager.artifactMap,
|
engineCommandManager.artifactMap,
|
||||||
context.selectionRanges
|
context.selectionRanges
|
||||||
),
|
|
||||||
[engineCommandManager.artifactMap, context.selectionRanges]
|
|
||||||
)
|
)
|
||||||
|
}, [engineCommandManager.artifactMap, context.selectionRanges])
|
||||||
|
|
||||||
function handleToolbarButtonsWheelEvent(ev: WheelEvent<HTMLSpanElement>) {
|
function handleToolbarButtonsWheelEvent(ev: WheelEvent<HTMLSpanElement>) {
|
||||||
const span = toolbarButtonsRef.current
|
const span = toolbarButtonsRef.current
|
||||||
@ -50,7 +53,9 @@ export const Toolbar = () => {
|
|||||||
<li className="contents">
|
<li className="contents">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => send({ type: 'Enter sketch' })}
|
onClick={() =>
|
||||||
|
send({ type: 'Enter sketch', data: { forceNewSketch: true } })
|
||||||
|
}
|
||||||
icon={{
|
icon={{
|
||||||
icon: 'sketch',
|
icon: 'sketch',
|
||||||
bgClassName,
|
bgClassName,
|
||||||
|
@ -332,6 +332,7 @@ class SceneEntities {
|
|||||||
if (!draftSegment) {
|
if (!draftSegment) {
|
||||||
sceneInfra.setCallbacks({
|
sceneInfra.setCallbacks({
|
||||||
onDrag: (args) => {
|
onDrag: (args) => {
|
||||||
|
if (args.event.which !== 1) return
|
||||||
this.onDragSegment({
|
this.onDragSegment({
|
||||||
...args,
|
...args,
|
||||||
sketchPathToNode,
|
sketchPathToNode,
|
||||||
@ -339,6 +340,7 @@ class SceneEntities {
|
|||||||
},
|
},
|
||||||
onMove: () => {},
|
onMove: () => {},
|
||||||
onClick: (args) => {
|
onClick: (args) => {
|
||||||
|
if (args?.event.which !== 1) return
|
||||||
if (!args || !args.object) {
|
if (!args || !args.object) {
|
||||||
sceneInfra.modelingSend({
|
sceneInfra.modelingSend({
|
||||||
type: 'Set selection',
|
type: 'Set selection',
|
||||||
@ -396,6 +398,7 @@ class SceneEntities {
|
|||||||
onDrag: () => {},
|
onDrag: () => {},
|
||||||
onClick: async (args) => {
|
onClick: async (args) => {
|
||||||
if (!args) return
|
if (!args) return
|
||||||
|
if (args.event.which !== 1) return
|
||||||
const { intersection2d } = args
|
const { intersection2d } = args
|
||||||
if (!intersection2d) return
|
if (!intersection2d) return
|
||||||
|
|
||||||
@ -792,6 +795,7 @@ class SceneEntities {
|
|||||||
},
|
},
|
||||||
onClick: (args) => {
|
onClick: (args) => {
|
||||||
if (!args || !args.object) return
|
if (!args || !args.object) return
|
||||||
|
if (args.event.which !== 1) return
|
||||||
const { object, intersection } = args
|
const { object, intersection } = args
|
||||||
const type = object?.userData?.type || ''
|
const type = object?.userData?.type || ''
|
||||||
const posNorm = Number(intersection.normal?.z) > 0
|
const posNorm = Number(intersection.normal?.z) > 0
|
||||||
|
@ -121,8 +121,8 @@ export const CustomIcon = ({
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fillRule="evenodd"
|
||||||
clip-rule="evenodd"
|
clipRule="evenodd"
|
||||||
d="M6.5 3H7L13 3L13.5 3V3.5V4.00001L15.5 4.00002L16 4.00002V4.50002V10.0351C15.6905 9.85609 15.3548 9.71733 15 9.62602V5.00002L13.5 5.00001V6.50001V7.00001L13 7.00001L7 7.00001L6.5 7.00001V6.50001V5.00001L5 5.00001V16H10.8773C11.2024 16.4055 11.6047 16.7463 12.062 17H4.5H4V16.5V4.50001V4.00001L4.5 4.00001L6.5 4.00001V3.5V3ZM15.938 17C15.9588 16.9885 15.9794 16.9768 16 16.9649V17H15.938ZM7.5 4V4.50001V6.00001L12.5 6.00001V4.50001V4L7.5 4ZM13 9H7V8H13V9ZM15.6855 11.5L13.2101 14.8005L12.2071 13.7975L11.5 14.5046L12.9107 15.9153L13.6642 15.8617L16.4855 12.1L15.6855 11.5Z"
|
d="M6.5 3H7L13 3L13.5 3V3.5V4.00001L15.5 4.00002L16 4.00002V4.50002V10.0351C15.6905 9.85609 15.3548 9.71733 15 9.62602V5.00002L13.5 5.00001V6.50001V7.00001L13 7.00001L7 7.00001L6.5 7.00001V6.50001V5.00001L5 5.00001V16H10.8773C11.2024 16.4055 11.6047 16.7463 12.062 17H4.5H4V16.5V4.50001V4.00001L4.5 4.00001L6.5 4.00001V3.5V3ZM15.938 17C15.9588 16.9885 15.9794 16.9768 16 16.9649V17H15.938ZM7.5 4V4.50001V6.00001L12.5 6.00001V4.50001V4L7.5 4ZM13 9H7V8H13V9ZM15.6855 11.5L13.2101 14.8005L12.2071 13.7975L11.5 14.5046L12.9107 15.9153L13.6642 15.8617L16.4855 12.1L15.6855 11.5Z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
@ -137,8 +137,8 @@ export const CustomIcon = ({
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fillRule="evenodd"
|
||||||
clip-rule="evenodd"
|
clipRule="evenodd"
|
||||||
d="M6.5 3H7L13 3L13.5 3V3.5V4.00001L15.5 4.00002L16 4.00002V4.50002V10.0351C15.6905 9.85609 15.3548 9.71733 15 9.62602V5.00002L13.5 5.00001V6.50001V7.00001L13 7.00001L7 7.00001L6.5 7.00001V6.50001V5.00001L5 5.00001V16H10.8773C11.2024 16.4055 11.6047 16.7463 12.062 17H4.5H4V16.5V4.50001V4.00001L4.5 4.00001L6.5 4.00001V3.5V3ZM15.938 17C15.9588 16.9885 15.9794 16.9768 16 16.9649V17H15.938ZM7.5 4V4.50001V6.00001L12.5 6.00001V4.50001V4L7.5 4ZM13 9H7V8H13V9ZM13.5 11V13H11.5V14H13.5V16H14.5V14H16.5V13H14.5V11H13.5Z"
|
d="M6.5 3H7L13 3L13.5 3V3.5V4.00001L15.5 4.00002L16 4.00002V4.50002V10.0351C15.6905 9.85609 15.3548 9.71733 15 9.62602V5.00002L13.5 5.00001V6.50001V7.00001L13 7.00001L7 7.00001L6.5 7.00001V6.50001V5.00001L5 5.00001V16H10.8773C11.2024 16.4055 11.6047 16.7463 12.062 17H4.5H4V16.5V4.50001V4.00001L4.5 4.00001L6.5 4.00001V3.5V3ZM15.938 17C15.9588 16.9885 15.9794 16.9768 16 16.9649V17H15.938ZM7.5 4V4.50001V6.00001L12.5 6.00001V4.50001V4L7.5 4ZM13 9H7V8H13V9ZM13.5 11V13H11.5V14H13.5V16H14.5V14H16.5V13H14.5V11H13.5Z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
@ -295,8 +295,8 @@ export const CustomIcon = ({
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fillRule="evenodd"
|
||||||
clip-rule="evenodd"
|
clipRule="evenodd"
|
||||||
d="M14 10.5H6V9.5H14V10.5Z"
|
d="M14 10.5H6V9.5H14V10.5Z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
@ -343,8 +343,8 @@ export const CustomIcon = ({
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fillRule="evenodd"
|
||||||
clip-rule="evenodd"
|
clipRule="evenodd"
|
||||||
d="M18 9.64741C17.1925 8.24871 16.0344 7.08457 14.6399 6.26971C13.2455 5.45486 11.6628 5.01742 10.0478 5.00051C8.4328 4.9836 6.84127 5.38779 5.43006 6.17326C4.01884 6.95873 2.83666 8.09837 2 9.47985L2.76881 9.94546C3.52456 8.69756 4.59243 7.66813 5.86718 6.95862C7.14193 6.2491 8.57955 5.88399 10.0384 5.89927C11.4972 5.91455 12.9269 6.30968 14.1865 7.04574C15.4461 7.7818 16.4922 8.83337 17.2216 10.0968L18 9.64741ZM15.2155 11.0953C14.6772 10.1628 13.9051 9.3867 12.9755 8.84347C12.0459 8.30023 10.9907 8.00861 9.91406 7.99733C8.8374 7.98606 7.77638 8.25552 6.83557 8.77917C5.89476 9.30281 5.10664 10.0626 4.54887 10.9836L5.34391 11.4651C5.81802 10.6822 6.48792 10.0364 7.28761 9.59132C8.0873 9.14622 8.98916 8.91718 9.90432 8.92676C10.8195 8.93635 11.7164 9.18423 12.5065 9.64598C13.2967 10.1077 13.953 10.7674 14.4106 11.56L15.2155 11.0953ZM10 14C10.8284 14 11.5 13.3284 11.5 12.5C11.5 11.6716 10.8284 11 10 11C9.17157 11 8.5 11.6716 8.5 12.5C8.5 13.3284 9.17157 14 10 14Z"
|
d="M18 9.64741C17.1925 8.24871 16.0344 7.08457 14.6399 6.26971C13.2455 5.45486 11.6628 5.01742 10.0478 5.00051C8.4328 4.9836 6.84127 5.38779 5.43006 6.17326C4.01884 6.95873 2.83666 8.09837 2 9.47985L2.76881 9.94546C3.52456 8.69756 4.59243 7.66813 5.86718 6.95862C7.14193 6.2491 8.57955 5.88399 10.0384 5.89927C11.4972 5.91455 12.9269 6.30968 14.1865 7.04574C15.4461 7.7818 16.4922 8.83337 17.2216 10.0968L18 9.64741ZM15.2155 11.0953C14.6772 10.1628 13.9051 9.3867 12.9755 8.84347C12.0459 8.30023 10.9907 8.00861 9.91406 7.99733C8.8374 7.98606 7.77638 8.25552 6.83557 8.77917C5.89476 9.30281 5.10664 10.0626 4.54887 10.9836L5.34391 11.4651C5.81802 10.6822 6.48792 10.0364 7.28761 9.59132C8.0873 9.14622 8.98916 8.91718 9.90432 8.92676C10.8195 8.93635 11.7164 9.18423 12.5065 9.64598C13.2967 10.1077 13.953 10.7674 14.4106 11.56L15.2155 11.0953ZM10 14C10.8284 14 11.5 13.3284 11.5 12.5C11.5 11.6716 10.8284 11 10 11C9.17157 11 8.5 11.6716 8.5 12.5C8.5 13.3284 9.17157 14 10 14Z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
@ -359,8 +359,8 @@ export const CustomIcon = ({
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fillRule="evenodd"
|
||||||
clip-rule="evenodd"
|
clipRule="evenodd"
|
||||||
d="M4.35352 5.39647L14.253 15.296L14.9601 14.5889L5.06062 4.68936L4.35352 5.39647ZM12.5065 9.64599C11.9609 9.32713 11.3643 9.11025 10.746 9.00341L9.74058 7.99796C9.79835 7.99694 9.85618 7.99674 9.91406 7.99735C10.9907 8.00862 12.0459 8.30025 12.9755 8.84348C13.9051 9.38672 14.6772 10.1628 15.2155 11.0953L14.4106 11.56C13.953 10.7674 13.2967 10.1077 12.5065 9.64599ZM6.48788 8.98789L7.16295 9.66297C6.41824 10.1045 5.79317 10.7233 5.34391 11.4651L4.54887 10.9836C5.03646 10.1785 5.70009 9.49656 6.48788 8.98789ZM10.0384 5.89928C9.3134 5.89169 8.59366 5.97804 7.89655 6.15392L7.16867 5.42605C8.09637 5.13507 9.06776 4.99026 10.0478 5.00052C11.6628 5.01744 13.2455 5.45488 14.6399 6.26973C16.0344 7.08458 17.1925 8.24872 18 9.64742L17.2216 10.0968C16.4922 8.83338 15.4461 7.78181 14.1865 7.04575C12.9269 6.3097 11.4972 5.91456 10.0384 5.89928ZM5.00782 7.50783L4.36522 6.86524C3.42033 7.57557 2.61639 8.46208 2 9.47986L2.76881 9.94547C3.34775 8.98952 4.10986 8.16177 5.00782 7.50783ZM10 14C10.4142 14 10.7892 13.8321 11.0607 13.5607L8.93934 11.4394C8.66789 11.7108 8.5 12.0858 8.5 12.5C8.5 13.3284 9.17157 14 10 14Z"
|
d="M4.35352 5.39647L14.253 15.296L14.9601 14.5889L5.06062 4.68936L4.35352 5.39647ZM12.5065 9.64599C11.9609 9.32713 11.3643 9.11025 10.746 9.00341L9.74058 7.99796C9.79835 7.99694 9.85618 7.99674 9.91406 7.99735C10.9907 8.00862 12.0459 8.30025 12.9755 8.84348C13.9051 9.38672 14.6772 10.1628 15.2155 11.0953L14.4106 11.56C13.953 10.7674 13.2967 10.1077 12.5065 9.64599ZM6.48788 8.98789L7.16295 9.66297C6.41824 10.1045 5.79317 10.7233 5.34391 11.4651L4.54887 10.9836C5.03646 10.1785 5.70009 9.49656 6.48788 8.98789ZM10.0384 5.89928C9.3134 5.89169 8.59366 5.97804 7.89655 6.15392L7.16867 5.42605C8.09637 5.13507 9.06776 4.99026 10.0478 5.00052C11.6628 5.01744 13.2455 5.45488 14.6399 6.26973C16.0344 7.08458 17.1925 8.24872 18 9.64742L17.2216 10.0968C16.4922 8.83338 15.4461 7.78181 14.1865 7.04575C12.9269 6.3097 11.4972 5.91456 10.0384 5.89928ZM5.00782 7.50783L4.36522 6.86524C3.42033 7.57557 2.61639 8.46208 2 9.47986L2.76881 9.94547C3.34775 8.98952 4.10986 8.16177 5.00782 7.50783ZM10 14C10.4142 14 10.7892 13.8321 11.0607 13.5607L8.93934 11.4394C8.66789 11.7108 8.5 12.0858 8.5 12.5C8.5 13.3284 9.17157 14 10 14Z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
|
@ -37,6 +37,7 @@ import { sceneInfra } from 'clientSideScene/sceneInfra'
|
|||||||
import { getSketchQuaternion } from 'clientSideScene/sceneEntities'
|
import { getSketchQuaternion } from 'clientSideScene/sceneEntities'
|
||||||
import { startSketchOnDefault } from 'lang/modifyAst'
|
import { startSketchOnDefault } from 'lang/modifyAst'
|
||||||
import { Program } from 'lang/wasm'
|
import { Program } from 'lang/wasm'
|
||||||
|
import { isSingleCursorInPipe } from 'lang/queryAst'
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -182,7 +183,10 @@ export const ModelingMachineProvider = ({
|
|||||||
|
|
||||||
return canExtrudeSelection(selectionRanges)
|
return canExtrudeSelection(selectionRanges)
|
||||||
},
|
},
|
||||||
'Selection is one face': ({ selectionRanges }) => {
|
'Selection is on face': ({ selectionRanges }, { data }) => {
|
||||||
|
if (data?.forceNewSketch) return false
|
||||||
|
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast))
|
||||||
|
return false
|
||||||
return !!isCursorInSketchCommandRange(
|
return !!isCursorInSketchCommandRange(
|
||||||
engineCommandManager.artifactMap,
|
engineCommandManager.artifactMap,
|
||||||
selectionRanges
|
selectionRanges
|
||||||
@ -199,6 +203,7 @@ export const ModelingMachineProvider = ({
|
|||||||
await kclManager.executeAstMock(newAst, { updates: 'code' })
|
await kclManager.executeAstMock(newAst, { updates: 'code' })
|
||||||
sceneInfra.setCallbacks({
|
sceneInfra.setCallbacks({
|
||||||
onClick: () => {},
|
onClick: () => {},
|
||||||
|
onDrag: () => {},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
'animate-to-face': async (_, { data: { plane, normal } }) => {
|
'animate-to-face': async (_, { data: { plane, normal } }) => {
|
||||||
|
@ -3,9 +3,9 @@ import ReactCodeMirror, {
|
|||||||
ViewUpdate,
|
ViewUpdate,
|
||||||
keymap,
|
keymap,
|
||||||
} from '@uiw/react-codemirror'
|
} from '@uiw/react-codemirror'
|
||||||
import { FromServer, IntoServer } from 'editor/lsp/codec'
|
import { FromServer, IntoServer } from 'editor/plugins/lsp/codec'
|
||||||
import Server from '../editor/lsp/server'
|
import Server from '../editor/plugins/lsp/server'
|
||||||
import Client from '../editor/lsp/client'
|
import Client from '../editor/plugins/lsp/client'
|
||||||
import { TEST } from 'env'
|
import { TEST } from 'env'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
@ -15,8 +15,8 @@ import { useMemo, useRef } from 'react'
|
|||||||
import { linter, lintGutter } from '@codemirror/lint'
|
import { linter, lintGutter } from '@codemirror/lint'
|
||||||
import { useStore } from 'useStore'
|
import { useStore } from 'useStore'
|
||||||
import { processCodeMirrorRanges } from 'lib/selections'
|
import { processCodeMirrorRanges } from 'lib/selections'
|
||||||
import { LanguageServerClient } from 'editor/lsp'
|
import { LanguageServerClient } from 'editor/plugins/lsp'
|
||||||
import kclLanguage from 'editor/lsp/language'
|
import kclLanguage from 'editor/plugins/lsp/kcl/language'
|
||||||
import { EditorView, lineHighlightField } from 'editor/highlightextension'
|
import { EditorView, lineHighlightField } from 'editor/highlightextension'
|
||||||
import { roundOff } from 'lib/utils'
|
import { roundOff } from 'lib/utils'
|
||||||
import { kclErrToDiagnostic } from 'lang/errors'
|
import { kclErrToDiagnostic } from 'lang/errors'
|
||||||
@ -27,7 +27,9 @@ import { engineCommandManager } from '../lang/std/engineConnection'
|
|||||||
import { kclManager, useKclContext } from 'lang/KclSingleton'
|
import { kclManager, useKclContext } from 'lang/KclSingleton'
|
||||||
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
||||||
import { sceneInfra } from 'clientSideScene/sceneInfra'
|
import { sceneInfra } from 'clientSideScene/sceneInfra'
|
||||||
import { copilotBundle } from 'editor/copilot'
|
import { copilotPlugin } from 'editor/plugins/lsp/copilot'
|
||||||
|
import { isTauri } from 'lib/isTauri'
|
||||||
|
import type * as LSP from 'vscode-languageserver-protocol'
|
||||||
|
|
||||||
export const editorShortcutMeta = {
|
export const editorShortcutMeta = {
|
||||||
formatCode: {
|
formatCode: {
|
||||||
@ -40,6 +42,15 @@ export const editorShortcutMeta = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
|
||||||
|
// We only use workspace folders in Tauri since that is where we use more than
|
||||||
|
// one file.
|
||||||
|
if (isTauri()) {
|
||||||
|
return [{ uri: 'file://', name: 'ProjectRoot' }]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
export const TextEditor = ({
|
export const TextEditor = ({
|
||||||
theme,
|
theme,
|
||||||
}: {
|
}: {
|
||||||
@ -91,7 +102,7 @@ export const TextEditor = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const lspClient = new LanguageServerClient({ client })
|
const lspClient = new LanguageServerClient({ client, name: 'kcl' })
|
||||||
return { lspClient }
|
return { lspClient }
|
||||||
}, [setIsKclLspServerReady])
|
}, [setIsKclLspServerReady])
|
||||||
|
|
||||||
@ -107,7 +118,7 @@ export const TextEditor = ({
|
|||||||
const lsp = kclLanguage({
|
const lsp = kclLanguage({
|
||||||
// When we have more than one file, we'll need to change this.
|
// When we have more than one file, we'll need to change this.
|
||||||
documentUri: `file:///we-just-have-one-file-for-now.kcl`,
|
documentUri: `file:///we-just-have-one-file-for-now.kcl`,
|
||||||
workspaceFolders: null,
|
workspaceFolders: getWorkspaceFolders(),
|
||||||
client: kclLspClient,
|
client: kclLspClient,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -128,7 +139,7 @@ export const TextEditor = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const lspClient = new LanguageServerClient({ client })
|
const lspClient = new LanguageServerClient({ client, name: 'copilot' })
|
||||||
return { lspClient }
|
return { lspClient }
|
||||||
}, [setIsCopilotLspServerReady])
|
}, [setIsCopilotLspServerReady])
|
||||||
|
|
||||||
@ -141,10 +152,10 @@ export const TextEditor = ({
|
|||||||
let plugin = null
|
let plugin = null
|
||||||
if (isCopilotLspServerReady && !TEST) {
|
if (isCopilotLspServerReady && !TEST) {
|
||||||
// Set up the lsp plugin.
|
// Set up the lsp plugin.
|
||||||
const lsp = copilotBundle({
|
const lsp = copilotPlugin({
|
||||||
// When we have more than one file, we'll need to change this.
|
// When we have more than one file, we'll need to change this.
|
||||||
documentUri: `file:///we-just-have-one-file-for-now.kcl`,
|
documentUri: `file:///we-just-have-one-file-for-now.kcl`,
|
||||||
workspaceFolders: null,
|
workspaceFolders: getWorkspaceFolders(),
|
||||||
client: copilotLspClient,
|
client: copilotLspClient,
|
||||||
allowHTMLContent: true,
|
allowHTMLContent: true,
|
||||||
})
|
})
|
||||||
|
@ -65,6 +65,7 @@ export default class Client extends jsrpc.JSONRPCServerAndClient {
|
|||||||
afterInitializedHooks: (() => Promise<void>)[] = []
|
afterInitializedHooks: (() => Promise<void>)[] = []
|
||||||
#fromServer: FromServer
|
#fromServer: FromServer
|
||||||
private serverCapabilities: LSP.ServerCapabilities<any> = {}
|
private serverCapabilities: LSP.ServerCapabilities<any> = {}
|
||||||
|
private notifyFn: ((message: LSP.NotificationMessage) => void) | null = null
|
||||||
|
|
||||||
constructor(fromServer: FromServer, intoServer: IntoServer) {
|
constructor(fromServer: FromServer, intoServer: IntoServer) {
|
||||||
super(
|
super(
|
||||||
@ -167,9 +168,15 @@ export default class Client extends jsrpc.JSONRPCServerAndClient {
|
|||||||
return this.serverCapabilities
|
return this.serverCapabilities
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setNotifyFn(fn: (message: LSP.NotificationMessage) => void): void {
|
||||||
|
this.notifyFn = fn
|
||||||
|
}
|
||||||
|
|
||||||
async processNotifications(): Promise<void> {
|
async processNotifications(): Promise<void> {
|
||||||
for await (const notification of this.#fromServer.notifications) {
|
for await (const notification of this.#fromServer.notifications) {
|
||||||
await this.receiveAndSend(notification)
|
if (this.notifyFn) {
|
||||||
|
this.notifyFn(notification)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -11,30 +11,20 @@ import {
|
|||||||
Annotation,
|
Annotation,
|
||||||
EditorState,
|
EditorState,
|
||||||
Extension,
|
Extension,
|
||||||
Facet,
|
|
||||||
Prec,
|
Prec,
|
||||||
StateEffect,
|
StateEffect,
|
||||||
StateField,
|
StateField,
|
||||||
Transaction,
|
Transaction,
|
||||||
} from '@codemirror/state'
|
} from '@codemirror/state'
|
||||||
import { completionStatus } from '@codemirror/autocomplete'
|
import { completionStatus } from '@codemirror/autocomplete'
|
||||||
import { docPathFacet, offsetToPos, posToOffset } from 'editor/lsp/util'
|
import { offsetToPos, posToOffset } from 'editor/plugins/lsp/util'
|
||||||
import { LanguageServerPlugin } from 'editor/lsp/plugin'
|
import { LanguageServerOptions, LanguageServerClient } from 'editor/plugins/lsp'
|
||||||
import { LanguageServerOptions } from 'editor/lsp/plugin'
|
import {
|
||||||
import { LanguageServerClient } from 'editor/lsp'
|
LanguageServerPlugin,
|
||||||
|
documentUri,
|
||||||
// Create Facet for the current docPath
|
languageId,
|
||||||
export const docPath = Facet.define<string, string>({
|
workspaceFolders,
|
||||||
combine(value: readonly string[]) {
|
} from 'editor/plugins/lsp/plugin'
|
||||||
return value[value.length - 1]
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const relDocPath = Facet.define<string, string>({
|
|
||||||
combine(value: readonly string[]) {
|
|
||||||
return value[value.length - 1]
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const ghostMark = Decoration.mark({ class: 'cm-ghostText' })
|
const ghostMark = Decoration.mark({ class: 'cm-ghostText' })
|
||||||
|
|
||||||
@ -361,9 +351,9 @@ const completionRequester = (client: LanguageServerClient) => {
|
|||||||
const pos = state.selection.main.head
|
const pos = state.selection.main.head
|
||||||
const source = state.doc.toString()
|
const source = state.doc.toString()
|
||||||
|
|
||||||
const path = state.facet(docPath)
|
const dUri = state.facet(documentUri)
|
||||||
const relativePath = state.facet(relDocPath)
|
const path = dUri.split('/').pop()!
|
||||||
const languageId = 'kcl'
|
const relativePath = dUri.replace('file://', '')
|
||||||
|
|
||||||
// Set a new timeout to request completion
|
// Set a new timeout to request completion
|
||||||
timeout = setTimeout(async () => {
|
timeout = setTimeout(async () => {
|
||||||
@ -378,9 +368,9 @@ const completionRequester = (client: LanguageServerClient) => {
|
|||||||
indentSize: 1,
|
indentSize: 1,
|
||||||
insertSpaces: true,
|
insertSpaces: true,
|
||||||
path,
|
path,
|
||||||
uri: `file://${path}`,
|
uri: dUri,
|
||||||
relativePath,
|
relativePath,
|
||||||
languageId,
|
languageId: state.facet(languageId),
|
||||||
position: offsetToPos(state.doc, pos),
|
position: offsetToPos(state.doc, pos),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -483,21 +473,24 @@ const completionRequester = (client: LanguageServerClient) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function copilotServer(options: LanguageServerOptions) {
|
export const copilotPlugin = (options: LanguageServerOptions): Extension => {
|
||||||
let plugin: LanguageServerPlugin
|
let plugin: LanguageServerPlugin | null = null
|
||||||
return ViewPlugin.define(
|
|
||||||
(view) =>
|
|
||||||
(plugin = new LanguageServerPlugin(view, options.allowHTMLContent))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const copilotBundle = (options: LanguageServerOptions): Extension => [
|
return [
|
||||||
docPath.of(options.documentUri.split('/').pop()!),
|
documentUri.of(options.documentUri),
|
||||||
docPathFacet.of(options.documentUri.split('/').pop()!),
|
languageId.of('kcl'),
|
||||||
relDocPath.of(options.documentUri.replace('file://', '')),
|
workspaceFolders.of(options.workspaceFolders),
|
||||||
|
ViewPlugin.define(
|
||||||
|
(view) =>
|
||||||
|
(plugin = new LanguageServerPlugin(
|
||||||
|
options.client,
|
||||||
|
view,
|
||||||
|
options.allowHTMLContent
|
||||||
|
))
|
||||||
|
),
|
||||||
completionDecoration,
|
completionDecoration,
|
||||||
Prec.highest(completionPlugin(options.client)),
|
Prec.highest(completionPlugin(options.client)),
|
||||||
Prec.highest(viewCompletionPlugin(options.client)),
|
Prec.highest(viewCompletionPlugin(options.client)),
|
||||||
completionRequester(options.client),
|
completionRequester(options.client),
|
||||||
copilotServer(options),
|
|
||||||
]
|
]
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import type * as LSP from 'vscode-languageserver-protocol'
|
import type * as LSP from 'vscode-languageserver-protocol'
|
||||||
import Client from './client'
|
import Client from './client'
|
||||||
import { LanguageServerPlugin } from './plugin'
|
import { SemanticToken, deserializeTokens } from './kcl/semantic_tokens'
|
||||||
import { SemanticToken, deserializeTokens } from './semantic_tokens'
|
import { LanguageServerPlugin } from 'editor/plugins/lsp/plugin'
|
||||||
|
|
||||||
export interface CopilotGetCompletionsParams {
|
export interface CopilotGetCompletionsParams {
|
||||||
doc: {
|
doc: {
|
||||||
@ -90,26 +90,22 @@ interface LSPNotifyMap {
|
|||||||
'textDocument/didOpen': LSP.DidOpenTextDocumentParams
|
'textDocument/didOpen': LSP.DidOpenTextDocumentParams
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server to client
|
|
||||||
interface LSPEventMap {
|
|
||||||
'textDocument/publishDiagnostics': LSP.PublishDiagnosticsParams
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Notification = {
|
|
||||||
[key in keyof LSPEventMap]: {
|
|
||||||
jsonrpc: '2.0'
|
|
||||||
id?: null | undefined
|
|
||||||
method: key
|
|
||||||
params: LSPEventMap[key]
|
|
||||||
}
|
|
||||||
}[keyof LSPEventMap]
|
|
||||||
|
|
||||||
export interface LanguageServerClientOptions {
|
export interface LanguageServerClientOptions {
|
||||||
client: Client
|
client: Client
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LanguageServerOptions {
|
||||||
|
// We assume this is the main project directory, we are currently working in.
|
||||||
|
workspaceFolders: LSP.WorkspaceFolder[]
|
||||||
|
documentUri: string
|
||||||
|
allowHTMLContent: boolean
|
||||||
|
client: LanguageServerClient
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LanguageServerClient {
|
export class LanguageServerClient {
|
||||||
private client: Client
|
private client: Client
|
||||||
|
private name: string
|
||||||
|
|
||||||
public ready: boolean
|
public ready: boolean
|
||||||
|
|
||||||
@ -124,6 +120,7 @@ export class LanguageServerClient {
|
|||||||
constructor(options: LanguageServerClientOptions) {
|
constructor(options: LanguageServerClientOptions) {
|
||||||
this.plugins = []
|
this.plugins = []
|
||||||
this.client = options.client
|
this.client = options.client
|
||||||
|
this.name = options.name
|
||||||
|
|
||||||
this.ready = false
|
this.ready = false
|
||||||
|
|
||||||
@ -133,11 +130,16 @@ export class LanguageServerClient {
|
|||||||
|
|
||||||
async initialize() {
|
async initialize() {
|
||||||
// Start the client in the background.
|
// Start the client in the background.
|
||||||
|
this.client.setNotifyFn(this.processNotifications.bind(this))
|
||||||
this.client.start()
|
this.client.start()
|
||||||
|
|
||||||
this.ready = true
|
this.ready = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getName(): string {
|
||||||
|
return this.name
|
||||||
|
}
|
||||||
|
|
||||||
getServerCapabilities(): LSP.ServerCapabilities<any> {
|
getServerCapabilities(): LSP.ServerCapabilities<any> {
|
||||||
return this.client.getServerCapabilities()
|
return this.client.getServerCapabilities()
|
||||||
}
|
}
|
||||||
@ -156,6 +158,11 @@ export class LanguageServerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async updateSemanticTokens(uri: string) {
|
async updateSemanticTokens(uri: string) {
|
||||||
|
const serverCapabilities = this.getServerCapabilities()
|
||||||
|
if (!serverCapabilities.semanticTokensProvider) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure we can only run, if we aren't already running.
|
// Make sure we can only run, if we aren't already running.
|
||||||
if (!this.isUpdatingSemanticTokens) {
|
if (!this.isUpdatingSemanticTokens) {
|
||||||
this.isUpdatingSemanticTokens = true
|
this.isUpdatingSemanticTokens = true
|
||||||
@ -180,10 +187,18 @@ export class LanguageServerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async textDocumentHover(params: LSP.HoverParams) {
|
async textDocumentHover(params: LSP.HoverParams) {
|
||||||
|
const serverCapabilities = this.getServerCapabilities()
|
||||||
|
if (!serverCapabilities.hoverProvider) {
|
||||||
|
return
|
||||||
|
}
|
||||||
return await this.request('textDocument/hover', params)
|
return await this.request('textDocument/hover', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
async textDocumentCompletion(params: LSP.CompletionParams) {
|
async textDocumentCompletion(params: LSP.CompletionParams) {
|
||||||
|
const serverCapabilities = this.getServerCapabilities()
|
||||||
|
if (!serverCapabilities.completionProvider) {
|
||||||
|
return
|
||||||
|
}
|
||||||
return await this.request('textDocument/completion', params)
|
return await this.request('textDocument/completion', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,11 +249,12 @@ export class LanguageServerClient {
|
|||||||
async acceptCompletion(params: CopilotAcceptCompletionParams) {
|
async acceptCompletion(params: CopilotAcceptCompletionParams) {
|
||||||
return await this.request('notifyAccepted', params)
|
return await this.request('notifyAccepted', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
async rejectCompletions(params: CopilotRejectCompletionParams) {
|
async rejectCompletions(params: CopilotRejectCompletionParams) {
|
||||||
return await this.request('notifyRejected', params)
|
return await this.request('notifyRejected', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
private processNotification(notification: Notification) {
|
private processNotifications(notification: LSP.NotificationMessage) {
|
||||||
for (const plugin of this.plugins) plugin.processNotification(notification)
|
for (const plugin of this.plugins) plugin.processNotification(notification)
|
||||||
}
|
}
|
||||||
}
|
}
|
75
src/editor/plugins/lsp/kcl/index.ts
Normal file
75
src/editor/plugins/lsp/kcl/index.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { autocompletion } from '@codemirror/autocomplete'
|
||||||
|
import { Extension } from '@codemirror/state'
|
||||||
|
import { ViewPlugin, hoverTooltip, tooltips } from '@codemirror/view'
|
||||||
|
import { CompletionTriggerKind } from 'vscode-languageserver-protocol'
|
||||||
|
import { offsetToPos } from 'editor/plugins/lsp/util'
|
||||||
|
import { LanguageServerOptions } from 'editor/plugins/lsp'
|
||||||
|
import {
|
||||||
|
LanguageServerPlugin,
|
||||||
|
documentUri,
|
||||||
|
languageId,
|
||||||
|
workspaceFolders,
|
||||||
|
} from 'editor/plugins/lsp/plugin'
|
||||||
|
|
||||||
|
export function kclPlugin(options: LanguageServerOptions): Extension {
|
||||||
|
let plugin: LanguageServerPlugin | null = null
|
||||||
|
|
||||||
|
return [
|
||||||
|
documentUri.of(options.documentUri),
|
||||||
|
languageId.of('kcl'),
|
||||||
|
workspaceFolders.of(options.workspaceFolders),
|
||||||
|
ViewPlugin.define(
|
||||||
|
(view) =>
|
||||||
|
(plugin = new LanguageServerPlugin(
|
||||||
|
options.client,
|
||||||
|
view,
|
||||||
|
options.allowHTMLContent
|
||||||
|
))
|
||||||
|
),
|
||||||
|
hoverTooltip(
|
||||||
|
(view, pos) =>
|
||||||
|
plugin?.requestHoverTooltip(view, offsetToPos(view.state.doc, pos)) ??
|
||||||
|
null
|
||||||
|
),
|
||||||
|
tooltips({
|
||||||
|
position: 'absolute',
|
||||||
|
}),
|
||||||
|
autocompletion({
|
||||||
|
override: [
|
||||||
|
async (context) => {
|
||||||
|
if (plugin == null) return null
|
||||||
|
|
||||||
|
const { state, pos, explicit } = context
|
||||||
|
const line = state.doc.lineAt(pos)
|
||||||
|
let trigKind: CompletionTriggerKind = CompletionTriggerKind.Invoked
|
||||||
|
let trigChar: string | undefined
|
||||||
|
if (
|
||||||
|
!explicit &&
|
||||||
|
plugin.client
|
||||||
|
.getServerCapabilities()
|
||||||
|
.completionProvider?.triggerCharacters?.includes(
|
||||||
|
line.text[pos - line.from - 1]
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
trigKind = CompletionTriggerKind.TriggerCharacter
|
||||||
|
trigChar = line.text[pos - line.from - 1]
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
trigKind === CompletionTriggerKind.Invoked &&
|
||||||
|
!context.matchBefore(/\w+$/)
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return await plugin.requestCompletion(
|
||||||
|
context,
|
||||||
|
offsetToPos(state.doc, pos),
|
||||||
|
{
|
||||||
|
triggerKind: trigKind,
|
||||||
|
triggerCharacter: trigChar,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
}
|
@ -5,8 +5,8 @@ import {
|
|||||||
defineLanguageFacet,
|
defineLanguageFacet,
|
||||||
LanguageSupport,
|
LanguageSupport,
|
||||||
} from '@codemirror/language'
|
} from '@codemirror/language'
|
||||||
import { LanguageServerClient } from '.'
|
import { LanguageServerClient } from 'editor/plugins/lsp'
|
||||||
import { kclPlugin } from './plugin'
|
import { kclPlugin } from '.'
|
||||||
import type * as LSP from 'vscode-languageserver-protocol'
|
import type * as LSP from 'vscode-languageserver-protocol'
|
||||||
import { parser as jsParser } from '@lezer/javascript'
|
import { parser as jsParser } from '@lezer/javascript'
|
||||||
import { EditorState } from '@uiw/react-codemirror'
|
import { EditorState } from '@uiw/react-codemirror'
|
||||||
@ -14,7 +14,7 @@ import { EditorState } from '@uiw/react-codemirror'
|
|||||||
const data = defineLanguageFacet({})
|
const data = defineLanguageFacet({})
|
||||||
|
|
||||||
export interface LanguageOptions {
|
export interface LanguageOptions {
|
||||||
workspaceFolders: LSP.WorkspaceFolder[] | null
|
workspaceFolders: LSP.WorkspaceFolder[]
|
||||||
documentUri: string
|
documentUri: string
|
||||||
client: LanguageServerClient
|
client: LanguageServerClient
|
||||||
}
|
}
|
@ -9,8 +9,8 @@ import {
|
|||||||
NodeType,
|
NodeType,
|
||||||
NodeSet,
|
NodeSet,
|
||||||
} from '@lezer/common'
|
} from '@lezer/common'
|
||||||
import { LanguageServerClient } from '.'
|
import { LanguageServerClient } from 'editor/plugins/lsp'
|
||||||
import { posToOffset } from 'editor/lsp/util'
|
import { posToOffset } from 'editor/plugins/lsp/util'
|
||||||
import { SemanticToken } from './semantic_tokens'
|
import { SemanticToken } from './semantic_tokens'
|
||||||
import { DocInput } from '@codemirror/language'
|
import { DocInput } from '@codemirror/language'
|
||||||
import { tags, styleTags } from '@lezer/highlight'
|
import { tags, styleTags } from '@lezer/highlight'
|
@ -1,13 +1,7 @@
|
|||||||
import { autocompletion, completeFromList } from '@codemirror/autocomplete'
|
import { completeFromList } from '@codemirror/autocomplete'
|
||||||
import { setDiagnostics } from '@codemirror/lint'
|
import { setDiagnostics } from '@codemirror/lint'
|
||||||
import { Facet } from '@codemirror/state'
|
import { Facet } from '@codemirror/state'
|
||||||
import {
|
import { EditorView, Tooltip } from '@codemirror/view'
|
||||||
EditorView,
|
|
||||||
ViewPlugin,
|
|
||||||
Tooltip,
|
|
||||||
hoverTooltip,
|
|
||||||
tooltips,
|
|
||||||
} from '@codemirror/view'
|
|
||||||
import {
|
import {
|
||||||
DiagnosticSeverity,
|
DiagnosticSeverity,
|
||||||
CompletionItemKind,
|
CompletionItemKind,
|
||||||
@ -23,9 +17,17 @@ import type {
|
|||||||
import type { PublishDiagnosticsParams } from 'vscode-languageserver-protocol'
|
import type { PublishDiagnosticsParams } from 'vscode-languageserver-protocol'
|
||||||
import type { ViewUpdate, PluginValue } from '@codemirror/view'
|
import type { ViewUpdate, PluginValue } from '@codemirror/view'
|
||||||
import type * as LSP from 'vscode-languageserver-protocol'
|
import type * as LSP from 'vscode-languageserver-protocol'
|
||||||
import { LanguageServerClient, Notification } from '.'
|
import { LanguageServerClient } from 'editor/plugins/lsp'
|
||||||
import { Marked } from '@ts-stack/markdown'
|
import { Marked } from '@ts-stack/markdown'
|
||||||
import { offsetToPos, posToOffset } from 'editor/lsp/util'
|
import { posToOffset } from 'editor/plugins/lsp/util'
|
||||||
|
|
||||||
|
const useLast = (values: readonly any[]) => values.reduce((_, v) => v, '')
|
||||||
|
export const documentUri = Facet.define<string, string>({ combine: useLast })
|
||||||
|
export const languageId = Facet.define<string, string>({ combine: useLast })
|
||||||
|
export const workspaceFolders = Facet.define<
|
||||||
|
LSP.WorkspaceFolder[],
|
||||||
|
LSP.WorkspaceFolder[]
|
||||||
|
>({ combine: useLast })
|
||||||
|
|
||||||
const changesDelay = 500
|
const changesDelay = 500
|
||||||
|
|
||||||
@ -33,31 +35,22 @@ const CompletionItemKindMap = Object.fromEntries(
|
|||||||
Object.entries(CompletionItemKind).map(([key, value]) => [value, key])
|
Object.entries(CompletionItemKind).map(([key, value]) => [value, key])
|
||||||
) as Record<CompletionItemKind, string>
|
) as Record<CompletionItemKind, string>
|
||||||
|
|
||||||
const useLast = (values: readonly any[]) => values.reduce((_, v) => v, '')
|
|
||||||
const documentUri = Facet.define<string, string>({ combine: useLast })
|
|
||||||
const languageId = Facet.define<string, string>({ combine: useLast })
|
|
||||||
const client = Facet.define<LanguageServerClient, LanguageServerClient>({
|
|
||||||
combine: useLast,
|
|
||||||
})
|
|
||||||
|
|
||||||
export interface LanguageServerOptions {
|
|
||||||
workspaceFolders: LSP.WorkspaceFolder[] | null
|
|
||||||
documentUri: string
|
|
||||||
allowHTMLContent: boolean
|
|
||||||
client: LanguageServerClient
|
|
||||||
}
|
|
||||||
|
|
||||||
export class LanguageServerPlugin implements PluginValue {
|
export class LanguageServerPlugin implements PluginValue {
|
||||||
public client: LanguageServerClient
|
public client: LanguageServerClient
|
||||||
|
|
||||||
private documentUri: string
|
private documentUri: string
|
||||||
private languageId: string
|
private languageId: string
|
||||||
|
private workspaceFolders: LSP.WorkspaceFolder[]
|
||||||
private documentVersion: number
|
private documentVersion: number
|
||||||
|
|
||||||
constructor(private view: EditorView, private allowHTMLContent: boolean) {
|
constructor(
|
||||||
this.client = this.view.state.facet(client)
|
client: LanguageServerClient,
|
||||||
|
private view: EditorView,
|
||||||
|
private allowHTMLContent: boolean
|
||||||
|
) {
|
||||||
|
this.client = client
|
||||||
this.documentUri = this.view.state.facet(documentUri)
|
this.documentUri = this.view.state.facet(documentUri)
|
||||||
this.languageId = this.view.state.facet(languageId)
|
this.languageId = this.view.state.facet(languageId)
|
||||||
|
this.workspaceFolders = this.view.state.facet(workspaceFolders)
|
||||||
this.documentVersion = 0
|
this.documentVersion = 0
|
||||||
|
|
||||||
this.client.attachPlugin(this)
|
this.client.attachPlugin(this)
|
||||||
@ -238,11 +231,28 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
return completeFromList(options)(context)
|
return completeFromList(options)(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
processNotification(notification: Notification) {
|
processNotification(notification: LSP.NotificationMessage) {
|
||||||
try {
|
try {
|
||||||
switch (notification.method) {
|
switch (notification.method) {
|
||||||
case 'textDocument/publishDiagnostics':
|
case 'textDocument/publishDiagnostics':
|
||||||
this.processDiagnostics(notification.params)
|
this.processDiagnostics(
|
||||||
|
notification.params as PublishDiagnosticsParams
|
||||||
|
)
|
||||||
|
break
|
||||||
|
case 'window/logMessage':
|
||||||
|
console.log(
|
||||||
|
'[lsp] [window/logMessage]',
|
||||||
|
this.client.getName(),
|
||||||
|
notification.params
|
||||||
|
)
|
||||||
|
break
|
||||||
|
case 'window/showMessage':
|
||||||
|
console.log(
|
||||||
|
'[lsp] [window/showMessage]',
|
||||||
|
this.client.getName(),
|
||||||
|
notification.params
|
||||||
|
)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
@ -284,65 +294,6 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function kclPlugin(options: LanguageServerOptions) {
|
|
||||||
let plugin: LanguageServerPlugin | null = null
|
|
||||||
|
|
||||||
return [
|
|
||||||
client.of(options.client),
|
|
||||||
documentUri.of(options.documentUri),
|
|
||||||
languageId.of('kcl'),
|
|
||||||
ViewPlugin.define(
|
|
||||||
(view) =>
|
|
||||||
(plugin = new LanguageServerPlugin(view, options.allowHTMLContent))
|
|
||||||
),
|
|
||||||
hoverTooltip(
|
|
||||||
(view, pos) =>
|
|
||||||
plugin?.requestHoverTooltip(view, offsetToPos(view.state.doc, pos)) ??
|
|
||||||
null
|
|
||||||
),
|
|
||||||
tooltips({
|
|
||||||
position: 'absolute',
|
|
||||||
}),
|
|
||||||
autocompletion({
|
|
||||||
override: [
|
|
||||||
async (context) => {
|
|
||||||
if (plugin == null) return null
|
|
||||||
|
|
||||||
const { state, pos, explicit } = context
|
|
||||||
const line = state.doc.lineAt(pos)
|
|
||||||
let trigKind: CompletionTriggerKind = CompletionTriggerKind.Invoked
|
|
||||||
let trigChar: string | undefined
|
|
||||||
if (
|
|
||||||
!explicit &&
|
|
||||||
plugin.client
|
|
||||||
.getServerCapabilities()
|
|
||||||
.completionProvider?.triggerCharacters?.includes(
|
|
||||||
line.text[pos - line.from - 1]
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
trigKind = CompletionTriggerKind.TriggerCharacter
|
|
||||||
trigChar = line.text[pos - line.from - 1]
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
trigKind === CompletionTriggerKind.Invoked &&
|
|
||||||
!context.matchBefore(/\w+$/)
|
|
||||||
) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return await plugin.requestCompletion(
|
|
||||||
context,
|
|
||||||
offsetToPos(state.doc, pos),
|
|
||||||
{
|
|
||||||
triggerKind: trigKind,
|
|
||||||
triggerCharacter: trigChar,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatContents(
|
function formatContents(
|
||||||
contents: LSP.MarkupContent | LSP.MarkedString | LSP.MarkedString[]
|
contents: LSP.MarkupContent | LSP.MarkedString | LSP.MarkedString[]
|
||||||
): string {
|
): string {
|
@ -34,6 +34,8 @@ const ServerCapabilitiesProviders: IMethodServerCapabilityProviderDictionary = {
|
|||||||
'textDocument/foldingRange': 'foldingRangeProvider',
|
'textDocument/foldingRange': 'foldingRangeProvider',
|
||||||
'textDocument/declaration': 'declarationProvider',
|
'textDocument/declaration': 'declarationProvider',
|
||||||
'textDocument/executeCommand': 'executeCommandProvider',
|
'textDocument/executeCommand': 'executeCommandProvider',
|
||||||
|
'textDocument/semanticTokens/full': 'semanticTokensProvider',
|
||||||
|
'textDocument/publishDiagnostics': 'diagnosticsProvider',
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerServerCapability(
|
function registerServerCapability(
|
@ -3,8 +3,9 @@ import init, {
|
|||||||
InitOutput,
|
InitOutput,
|
||||||
kcl_lsp_run,
|
kcl_lsp_run,
|
||||||
ServerConfig,
|
ServerConfig,
|
||||||
} from '../../wasm-lib/pkg/wasm_lib'
|
} from 'wasm-lib/pkg/wasm_lib'
|
||||||
import { FromServer, IntoServer } from './codec'
|
import { FromServer, IntoServer } from './codec'
|
||||||
|
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
||||||
|
|
||||||
export default class Server {
|
export default class Server {
|
||||||
readonly initOutput: InitOutput
|
readonly initOutput: InitOutput
|
||||||
@ -31,7 +32,11 @@ export default class Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async start(type_: 'kcl' | 'copilot', token?: string): Promise<void> {
|
async start(type_: 'kcl' | 'copilot', token?: string): Promise<void> {
|
||||||
const config = new ServerConfig(this.#intoServer, this.#fromServer)
|
const config = new ServerConfig(
|
||||||
|
this.#intoServer,
|
||||||
|
this.#fromServer,
|
||||||
|
fileSystemManager
|
||||||
|
)
|
||||||
if (type_ === 'copilot') {
|
if (type_ === 'copilot') {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new Error('auth token is required for copilot')
|
throw new Error('auth token is required for copilot')
|
@ -1,4 +1,4 @@
|
|||||||
import { Facet, Text } from '@codemirror/state'
|
import { Text } from '@codemirror/state'
|
||||||
|
|
||||||
export function posToOffset(
|
export function posToOffset(
|
||||||
doc: Text,
|
doc: Text,
|
||||||
@ -17,7 +17,3 @@ export function offsetToPos(doc: Text, offset: number) {
|
|||||||
character: offset - line.from,
|
character: offset - line.from,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const docPathFacet = Facet.define<string, string>({
|
|
||||||
combine: (values) => values[values.length - 1],
|
|
||||||
})
|
|
@ -228,7 +228,17 @@ class KclManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeAst(ast: Program = this._ast, updateCode = false) {
|
private _cancelTokens: Map<number, boolean> = new Map()
|
||||||
|
|
||||||
|
async executeAst(
|
||||||
|
ast: Program = this._ast,
|
||||||
|
updateCode = false,
|
||||||
|
executionId?: number
|
||||||
|
) {
|
||||||
|
console.trace('executeAst')
|
||||||
|
const currentExecutionId = executionId || Date.now()
|
||||||
|
this._cancelTokens.set(currentExecutionId, false)
|
||||||
|
|
||||||
await this.ensureWasmInit()
|
await this.ensureWasmInit()
|
||||||
this.isExecuting = true
|
this.isExecuting = true
|
||||||
const { logs, errors, programMemory } = await executeAst({
|
const { logs, errors, programMemory } = await executeAst({
|
||||||
@ -236,6 +246,11 @@ class KclManager {
|
|||||||
engineCommandManager: this.engineCommandManager,
|
engineCommandManager: this.engineCommandManager,
|
||||||
})
|
})
|
||||||
this.isExecuting = false
|
this.isExecuting = false
|
||||||
|
// Check the cancellation token for this execution before applying side effects
|
||||||
|
if (this._cancelTokens.get(currentExecutionId)) {
|
||||||
|
this._cancelTokens.delete(currentExecutionId)
|
||||||
|
return
|
||||||
|
}
|
||||||
this.logs = logs
|
this.logs = logs
|
||||||
this.kclErrors = errors
|
this.kclErrors = errors
|
||||||
this.programMemory = programMemory
|
this.programMemory = programMemory
|
||||||
@ -248,6 +263,7 @@ class KclManager {
|
|||||||
type: 'execution-done',
|
type: 'execution-done',
|
||||||
data: null,
|
data: null,
|
||||||
})
|
})
|
||||||
|
this._cancelTokens.delete(currentExecutionId)
|
||||||
}
|
}
|
||||||
async executeAstMock(
|
async executeAstMock(
|
||||||
ast: Program = this._ast,
|
ast: Program = this._ast,
|
||||||
@ -295,7 +311,13 @@ class KclManager {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
async executeCode(code?: string) {
|
async executeCode(code?: string, executionId?: number) {
|
||||||
|
const currentExecutionId = executionId || Date.now()
|
||||||
|
this._cancelTokens.set(currentExecutionId, false)
|
||||||
|
if (this._cancelTokens.get(currentExecutionId)) {
|
||||||
|
this._cancelTokens.delete(currentExecutionId)
|
||||||
|
return
|
||||||
|
}
|
||||||
await this.ensureWasmInit()
|
await this.ensureWasmInit()
|
||||||
await this?.engineCommandManager?.waitForReady
|
await this?.engineCommandManager?.waitForReady
|
||||||
const result = await executeCode({
|
const result = await executeCode({
|
||||||
@ -304,6 +326,11 @@ class KclManager {
|
|||||||
lastAst: this._ast,
|
lastAst: this._ast,
|
||||||
force: false,
|
force: false,
|
||||||
})
|
})
|
||||||
|
// Check the cancellation token for this execution before applying side effects
|
||||||
|
if (this._cancelTokens.get(currentExecutionId)) {
|
||||||
|
this._cancelTokens.delete(currentExecutionId)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (!result.isChange) return
|
if (!result.isChange) return
|
||||||
const { logs, errors, programMemory, ast } = result
|
const { logs, errors, programMemory, ast } = result
|
||||||
this.logs = logs
|
this.logs = logs
|
||||||
@ -311,6 +338,12 @@ class KclManager {
|
|||||||
this.programMemory = programMemory
|
this.programMemory = programMemory
|
||||||
this.ast = ast
|
this.ast = ast
|
||||||
if (code) this.code = code
|
if (code) this.code = code
|
||||||
|
this._cancelTokens.delete(currentExecutionId)
|
||||||
|
}
|
||||||
|
cancelAllExecutions() {
|
||||||
|
this._cancelTokens.forEach((_, key) => {
|
||||||
|
this._cancelTokens.set(key, true)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
setCode(code: string, shouldWriteFile = true) {
|
setCode(code: string, shouldWriteFile = true) {
|
||||||
if (shouldWriteFile) {
|
if (shouldWriteFile) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ToolTip } from '../useStore'
|
import { ToolTip } from '../useStore'
|
||||||
import { Selection } from 'lib/selections'
|
import { Selection, Selections } from 'lib/selections'
|
||||||
import {
|
import {
|
||||||
BinaryExpression,
|
BinaryExpression,
|
||||||
Program,
|
Program,
|
||||||
@ -558,3 +558,24 @@ export function hasExtrudeSketchGroup({
|
|||||||
const varValue = programMemory?.root[varName]
|
const varValue = programMemory?.root[varName]
|
||||||
return varValue?.type === 'ExtrudeGroup' || varValue?.type === 'SketchGroup'
|
return varValue?.type === 'ExtrudeGroup' || varValue?.type === 'SketchGroup'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isSingleCursorInPipe(
|
||||||
|
selectionRanges: Selections,
|
||||||
|
ast: Program
|
||||||
|
) {
|
||||||
|
if (selectionRanges.codeBasedSelections.length !== 1) return false
|
||||||
|
if (
|
||||||
|
doesPipeHaveCallExp({
|
||||||
|
ast,
|
||||||
|
selection: selectionRanges.codeBasedSelections[0],
|
||||||
|
calleeName: 'extrude',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
const selection = selectionRanges.codeBasedSelections[0]
|
||||||
|
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
|
||||||
|
const nodeTypes = pathToNode.map(([, type]) => type)
|
||||||
|
if (nodeTypes.includes('FunctionExpression')) return false
|
||||||
|
if (nodeTypes.includes('PipeExpression')) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
import { readBinaryFile, exists as tauriExists } from '@tauri-apps/api/fs'
|
import {
|
||||||
|
readDir,
|
||||||
|
readBinaryFile,
|
||||||
|
exists as tauriExists,
|
||||||
|
} from '@tauri-apps/api/fs'
|
||||||
import { isTauri } from 'lib/isTauri'
|
import { isTauri } from 'lib/isTauri'
|
||||||
import { join } from '@tauri-apps/api/path'
|
import { join } from '@tauri-apps/api/path'
|
||||||
|
|
||||||
@ -53,6 +57,30 @@ class FileSystemManager {
|
|||||||
return tauriExists(file)
|
return tauriExists(file)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAllFiles(path: string): Promise<string[] | void> {
|
||||||
|
// Using local file system only works from Tauri.
|
||||||
|
if (!isTauri()) {
|
||||||
|
throw new Error(
|
||||||
|
'This function can only be called from a Tauri application'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return join(this.dir, path)
|
||||||
|
.catch((error) => {
|
||||||
|
throw new Error(`Error joining dir: ${error}`)
|
||||||
|
})
|
||||||
|
.then((p) => {
|
||||||
|
readDir(p, { recursive: true })
|
||||||
|
.catch((error) => {
|
||||||
|
throw new Error(`Error reading dir: ${error}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
.then((files) => {
|
||||||
|
return files.map((file) => file.path)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fileSystemManager = new FileSystemManager()
|
export const fileSystemManager = new FileSystemManager()
|
||||||
|
@ -9,7 +9,11 @@ import { SelectionRange } from '@uiw/react-codemirror'
|
|||||||
import { isOverlap } from 'lib/utils'
|
import { isOverlap } from 'lib/utils'
|
||||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||||
import { Program } from 'lang/wasm'
|
import { Program } from 'lang/wasm'
|
||||||
import { doesPipeHaveCallExp, getNodeFromPath } from 'lang/queryAst'
|
import {
|
||||||
|
doesPipeHaveCallExp,
|
||||||
|
getNodeFromPath,
|
||||||
|
isSingleCursorInPipe,
|
||||||
|
} from 'lang/queryAst'
|
||||||
import { CommandArgument } from './commandTypes'
|
import { CommandArgument } from './commandTypes'
|
||||||
import {
|
import {
|
||||||
STRAIGHT_SEGMENT,
|
STRAIGHT_SEGMENT,
|
||||||
@ -455,6 +459,7 @@ function resetAndSetEngineEntitySelectionCmds(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isSketchPipe(selectionRanges: Selections) {
|
export function isSketchPipe(selectionRanges: Selections) {
|
||||||
|
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast)) return false
|
||||||
return isCursorInSketchCommandRange(
|
return isCursorInSketchCommandRange(
|
||||||
engineCommandManager.artifactMap,
|
engineCommandManager.artifactMap,
|
||||||
selectionRanges
|
selectionRanges
|
||||||
|
@ -37,7 +37,10 @@ export type Events =
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const TOKEN_PERSIST_KEY = 'TOKEN_PERSIST_KEY'
|
export const TOKEN_PERSIST_KEY = 'TOKEN_PERSIST_KEY'
|
||||||
const persistedToken = localStorage?.getItem(TOKEN_PERSIST_KEY) || ''
|
const persistedToken =
|
||||||
|
localStorage?.getItem(TOKEN_PERSIST_KEY) ||
|
||||||
|
getCookie('__Secure-next-auth.session-token') ||
|
||||||
|
''
|
||||||
|
|
||||||
export const authMachine = createMachine<UserContext, Events>(
|
export const authMachine = createMachine<UserContext, Events>(
|
||||||
{
|
{
|
||||||
@ -135,3 +138,23 @@ async function getUser(context: UserContext) {
|
|||||||
|
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCookie(cname: string): string {
|
||||||
|
if (isTauri()) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = cname + '='
|
||||||
|
let decodedCookie = decodeURIComponent(document.cookie)
|
||||||
|
let ca = decodedCookie.split(';')
|
||||||
|
for (let i = 0; i < ca.length; i++) {
|
||||||
|
let c = ca[i]
|
||||||
|
while (c.charAt(0) === ' ') {
|
||||||
|
c = c.substring(1)
|
||||||
|
}
|
||||||
|
if (c.indexOf(name) === 0) {
|
||||||
|
return c.substring(name.length, c.length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
@ -71,7 +71,12 @@ export type SetSelections =
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ModelingMachineEvent =
|
export type ModelingMachineEvent =
|
||||||
| { type: 'Enter sketch' }
|
| {
|
||||||
|
type: 'Enter sketch'
|
||||||
|
data?: {
|
||||||
|
forceNewSketch?: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: 'Select default plane'
|
type: 'Select default plane'
|
||||||
data: { plane: DefaultPlaneStr; normal: [number, number, number] }
|
data: { plane: DefaultPlaneStr; normal: [number, number, number] }
|
||||||
@ -114,7 +119,7 @@ export type MoveDesc = { line: number; snippet: string }
|
|||||||
|
|
||||||
export const modelingMachine = createMachine(
|
export const modelingMachine = createMachine(
|
||||||
{
|
{
|
||||||
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0AdgCsAZgB04gEyjhADnEA2GgoUAWJQBoQAT0QBGGuICckmoZkbTM42YWKAvk91oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBFtpGhlxDRphGg1ZURlhXQMEcRLDSVF5UwV84XEK8Rc3dCw8KElsCEwwHz9A4NCuXAjeGI5B3kTBDOFJYWVlQ2UlY3UrQsQFUQVy0Q00w3FLGVNTBtcQdxb8ds7ugFFcdjAAJ0CAaz9yAAthqNG4iZCdKiWZpcQKEyHTKmTLrYrCGb5DRaGHKBGiYzKRoXZqeNodLoEB5PV6wD7sb5UQyRZisMbcQEIQRLGiSdRmfIqUxLCpw5QqcrQ4SGTTWUx5bGXPE3Ql3PjsZ4AVwwv1psXGCSEMjkkhkNFE-MNBxo4vEcM22wqOQxikMKwUktxrUk3nJ32IZEomFdnx+9BGdIBmoQdoUykkxmEyIhELMsjhobKoYUdsOIq2jo8zp9FK+LrdXwAkrcegEgl0BuF-X9AxrQIlDHspPzRKYbAsttYzfojCYZBYaEs5uCaCZuZmrm0c99877i4TkCQPn0oABbMCPfwANxenHIJEwquitYZwaspiT1U0c00qQKPZDGgOKVHcgk+SUGgn0uned-8+6RdlyCNcNwCL5UGebAAC9uHYA8j3+Ot+CMJFJFUMN9TtfI0RkBNxDtcoNHkSx+WMCoZG-bMC1nXMAOIbhYAVEg8H8CCoNgx4D38CBsCYz0wEQk94nrVDdgsJsTU-C91DhNJuQsCpRFHYiLwOKjrl-WjvnoohGOY1id2ePduN4-iKEE6s1XpESUJDGQlkkcUZE2RYnzUPY5M5cprBFDQEWEBzdg0qcaP-Es9NwJjnhY3B-AAQQAIW8fwAA0hPVU9RPszZJByMRG1Oc80jk8UNCcg1FFfVtajOJos00sKC10-SYtYpKUoATQymzGSsXLrDsZQ5EMA0DTwh9DnUUElMWNQoy-c4pWo31tKLCLWti-wyCgLoeqDbKDhyaQlCveQU2EUw5OUxEtnkdE0ROYQQrWtaWqigy4q6fB2D9Glj0y2yGxKGYVANE0Dn2UQ5IWbY5jbQxLujFRnqWp1GtW8LCUi6KtqYF58dwXjyEVTASFeMz4Is-bkIbbVthsIrrEsTDlDklNyuFKpTARaxkTEF6tKx7occ+tjIJguCD0wPRtpwKAhisgHerPBz+wtLQJGIjJ+QTDFw2OLZ-MCsR+TqnEGtCzHmo2j62rioyTMwGW5ewBWaayuyrEuvLZEccVFjMDEE2GkEth5qolmORxzeWjHcze23cdY2BcBIJh-HYVA0o9oGjAIqQHJHBQeeqMw5OyfttQNeZVC2QLKLRy3XuFhi7a21P08z7PuqVpDPeB59MjIhFxGFbI5MbGZdkcOQLyjZQckFpq5yTsWwAAR0VbjvqgX7c7644pAxY2ef1OQx4TWpWRyExR31ZQDVMZfrdX7HNtYphyel6g++EvrZAzFSEXUapwnxRgTKcIBpQFgnEyGoUQL8E6t1FvbfwzwwCrlQDufw5AP6PFgAfVWWh0JWGIsNTQo0dAPhFARCwixbDZBKGoYaSCZytwAEpgEEGAPgIRFRPCIYdOoUgDiGFOKkdIYhg4PnUP2dIqQ0S1yUAsNhf4bayi3tgDOAAZPAYBu6oEPH-QGfVUSCnqLYMQ1CiiBXDMiAi2o1CKPHE3ScLcNH3C0RnKmMBHjYG4uTcg3chF2SGtscEBoCKP0UCIhMxgyg3jrrE5Ei81GSHigAdxYuBCWnF4KYB4nxKmlB-B4AAGaoAIBAbgYB2i4C3KgD4kgYDsEEOxSWXFMCCAqagUJiQ0jZDyhoReJpahPVqHJJ85hNguX2MoWwlV0lZJyeLDiUtCmUwEmU3AlSCAvGeJBSQTAybsEqc8VcLS-DtLyRsnpuy+kmJVtlQZII7TIlTMRFyPM5Lgn7NkQco46g4UMMs7JHBty7mwPuTZxTtm9OqbU+pjTmmtMEI7aFB57mVP6YgfY-YDi2hMGicR+QvJKDZGIOQdhSiBTBasjFMKinmVKQig5RyTkkDOZBS5aLGVYt6bihA+K2T6jEPPI2Xk9gRkfqoNIcj2T0ohR1NKOy9k1NwHUvAKK6lopIAAI1gIIPg2LHn-X7nnYV0TZjDVfBfLYpVhoRgyE+Reshhq1CVQEFVqU1VVPZc8Y5pzzm8uuQao1JrBVPIOmEgiMwcjgiUK2fUWhSqLGkFsR+BFshRmfm4n8NEVnKuSv4TqfrEWauRU03VYbDWCD0KaoV8yM3yAqLYKBxE4SHDRHlbmyktiHGCvmlaCci3epLWWtlzxDmBs5dyi5Vy2nhvrY26NtM8VxosDhMeDl+Suq7W5CwCz5jV0JY3eq7itJju2vgLo5aNVaoadWxdggdpdFXea-+wZ9ig0sAcPIrYA7XQNE5Qdf6KhhgRF6m9u0DFTpnUGrlIaX1vu4VGz9pjv0EThtkfqVg1Dz2uojCM-6shyHPmiaDu9fr3qRdq59aLqNfA-QGTDLyCIghyPJcBew7ww1yoOWlKhEZWCoxuPeXxy0BsQ-O0NbSmMsZrGx2N6bjAmC2Isa8E0igOWlSelE-l7oGmg-jZ4hNiak3JsykpcGHkVsfTql9pnzPQss88QQWyLKKesjGgZasKpKGsMNBy1RoYPjmSCMejZ5iAYA7HdGVtR3goCM5jcFmyYUzhRZKT06OXBp5U5gmaXXMZY81lyg3nla+bxf58UgWRnaijmFnTAVpDChGY-E4cX0l6M1YYzApY+gVjiE2vU5gnx6hFJkdMJVws8wjHVk01hMLVB6-o-rRJvGZx2mBAJhSgkhLXQPPFCJyqjTMKOdQ3M2YPh5vGkGjgsiuIvQW1avWDFZyMZIQsuAOAEFGxIaQ3aITpn8pfB8JL0Il0uhzfUjhQXDvjjOd7-Xvu-fYP96krHnlhJctsE4NhFDKT9ioOE8MnKNgcHUbkpo1t9c+5gSQuAeUHkG+WEII2juWuW-j1suxhTQ7DGTmEGbSULL-XaPNL2R3I-WwzyQAA5bOAAFVAeB2CwAIPFCAEBAjwWMv4Fg6um3zHKjCMabYXKDg0HyBZvsI5ZEUKddJP2-tCoSfYzYWQVDnxNDbmhYI8pKIcoC3TCPpdI7zK7jHVIMM44bFhNkMIYQjNkKORsCZjiJM2OkBYunThYkR4lmcAAVHb-jAnPGCVnNn-ROdx+q8K4a5gFlKFNEt44menxsjcnqSwxEMxF9emX-Au3K-V6qXcLbKOGcm+RL7TImxxSDm04gNs4ZeZpBItbyjQ+tKKiJtnfixlfwAHlcD2arc0+K3gS+CAPzUwQx-2Bn8Vg39dIYoQVQWhiem+o4RGyzALRhguRkJPhqL+DM7+DlIkCUA9DDY8RgAwGkwpZkyapCqCCFQnTE4YiIzEQERXQQ46htjcjRZHAVCowR5tBkDYCrhcqtDdyG5oHdAPpX51I0F0FPCCBZyCAwGUBCoVCXiL4ArAKwhEERJWDMLfJRjqAvQcH0H4CMGcqaq17DaDBNqBQkbUqtgYQQIPgzwRh1DDRqb8g1ByG-acEMFZz+C8LFIMFki+iX70bNLyFcE8EOG5gCGPykKcgnDahpAIhwhPiRZ5AQiLx2ANaaB04faoBfZK7+Cq7q6a6kAWTGLv7HbCpja9piCHA5CIwmBwinDqziKKATbupVAuDnDM4YDwBRBxxQDY6N6CAg7SByCxIYSaA2JCDiQQjTI8x6gORIyIJD4EhgCNEf7MgkKVQ8zgjETXjmgpjlBTTnaSGWBS4WyXoFjjEZHcgMxe7Eq+5thdr9gLKVCHBRaOD8jpLCzbGWoHAnC6iLwphjyhZ1DxLJAHBjypDVAXi-HQYdL5KmTla2aVK3F9RhgMwZClzCg1CpC-LJDAhQjzBWLh4bGvZJYMpQpMqeasoPJglnhyIyrp4iLN6GBeTKRbpPgcwxYYjQY+p+r4mHQlxSBqCOIF52CjiEE6ZFR5SVSYRzSdh0kToMlKbx5GAlz9ihF5G9Gr7FCDggjVwUGthyLrH1GvTXqoYik+Yf4ijijoRQgJpVB5CnDXRqBsjyoVCqBzBb5iY-SSa9KMlewlw3zqaPwpgF7zAwwCiXTiKXFDFXF76FrJaG5FZEwlZWY4kgmoCOkNibAb7cjKS3jtglByS2izC+mtijSVCF5UGvQz6xGYAxl4r2AWC1SbA2DqBLAAGLGnB3Y1AlC7ColqlaT5lfbR5FnCqjRlCZAlDllVAQl8gfHzDpCp4p4LLRGo7M4XIHgdlijxqjzHDOKyCylZBlDtjiKlAuSmETny7xGJEEKznzCIinCyCNgZDQg3a2IIhsh-IUTuqKDnpoky5R7o4dnkQt7azMzghmAZ40IihJgigpjGjpgOiBmrQj5+KcDj7dyzmAG8yGguSVBcm9h5CPFhg8hZC-7DG5n76H567kwv4Fjn5vkLDxp6gLCEqSEAFbDoSXYqAxa3zYVPmR6QHZx8FjGilNF7BhzpDiKqB1CXTDR8g6hPj2D9paB+zmG0EKFQBKHMEkVSA8zi42DyD4aXmIDDThgmDqBzAzxTSUHMXUEWEyWMG2FMT2FbGcU6mOC6gIiYRjzcg0pdpzCCj2AQhRiBQXiLQuBAA */
|
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0AdgCsAZgB04gEyjhADnEA2GgoUAWJQBoQAT0QBGGuICckmoZkbTM42YWKAvk91oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBFtpGhlxDRphGg1ZURlhXQMEcRLDSVF5UwV84XEK8Rc3dCw8KElsCEwwHz9A4NCuXAjeGI5B3kTBDOFJYWVlQ2UlY3UrQsQFUQVy0Q00w3FLGVNTBtcQdxb8ds7ugFFcdjAAJ0CAaz9yAAthqNG4iZCdKiWZpcQKEyHTKmTLrYrCGb5DRaGHKBGiYzKRoXZqeNodLoEB5PV6wD7sb5UQyRZisMbcQEIQRLGiSdRmfIqUxLCpw5QqcrQ4SGTTWUx5bGXPE3Ql3PjsZ4AVwwv1psXGCSEMjkkhkNFE-MNBxo4vEcM22wqOQxikMKwUktxrUk3nJ32IZEomFdnx+9BGdIBmoQdoUykkxmEyIhELMsjhobKoYUdsOIq2jo8zp9FK+LrdXwAkrcegEgl0BuF-X9AxrQIlDHspPzRKYbAsttYzfojCYZBYaEs5uCaCZuZmrm0c99877i4TkCQPn0oABbMCPfwANxenHIJEwquitYZwaspiT1U0c00qQKPZDGgOKVHcgk+SUGgn0uned-8+6RdlyCNcNwCL5UGebAAC9uHYA8j3+Ot+CMJFJFUMN9TtfI0RkBNxDtcoNHkSx+WMCoZG-bMC1nXMAOIbhYAVEg8H8CCoNgx4D38CBsCYz0wEQk94nrVDdgsJsTU-C91DhNJuQsCpRFHYiLwOKjrl-WjvnoohGOY1id2ePduN4-iKEE6s1XpESUJDGQlkkcUZE2RYnzUPY5M5cprBFDQEWEBzdg0qcaP-Es9NwJjnhY3B-AAQQAIW8fwAA0hPVU9RPszZJByMRG1Oc80jk8UNCcg1FFfVtajOJos00sKC10-SYtYpKUoATQymzGSsXLrDsZQ5EMA0DTwh9DnUUElMWNQoy-c4pWo31tKLCLWti-wyCgLoeqDbKDhyaQlCveQU2EUw5OUxEtnkdE0ROYQQrWtaWqigy4q6fB2D9Glj0y2yGxKGYVANE0Dn2UQ5IWbY5jbQxLujFRnqWp1GtW8LCUi6KtqYF58dwXjyEVTASFeMz4Is-bkIbbVthsIrrEsTDlDklNyuFKpTARaxkTEF6tKx7occ+tjIJguCD0wPRtpwKAhisgHerPBz+wtLQJGIjJ+QTDFw2OLZ-MCsR+TqnEGtCzHmo2j62rioyTMwGW5ewBWaayuyrEuvLZEccVFjMDEE2GkEth5qolmORxzeWjHcze23cdY2BcBIJh-HYVA0o9oGjAIqQHJHBQeeqMw5OyfttQNeZVC2QLKLRy3XuFhi7a21P08z7PuqVpDPeB59MjIhFxGFbI5MbGZdkcOQLyjZQckFpq5yTsWwAAR0VbjvqgX7c7644pAxY2ef1OQx4TWpWRyExR31ZQDVMZfrdX7HNtYphyel6g++EvrZAzFSEXUapwnxRgTKcIBpQFgnEyGoUQL8E6t1FvbfwzwwCrlQDufw5AP6PFgAfVWWh0JWGIsNTQo0dAPhFARCwixbDZBKGoYaSCZytwAEpgEEGAPgIRFRPCIYdOoUgDiGFOKkdIYhg4PnUP2dIqQ0S1yUAsNhf4bayi3tgDOAAZPAYBu6oEPH-QGfVUSCnqLYMQ1CiiBXDMiAi2o1CKPHE3ScLcNH3C0RnKmMBHjYG4uTcg3chF2SGtscEBoCKP0UCIhMxgyg3jrrE5Ei81GSHigAdxYuBCWnF4KYB4nxKmlB-B4AAGaoAIBAbgYB2i4C3KgD4kgYDsEEOxSWXFMCCAqagUJiQ0jZDyhoReJpahPVqHJJ85hNguX2MoWwlV0lZJyeLDiUtCmUwEmU3AlSCAvGeJBSQTAybsEqc8VcLS-DtLyRsnpuy+kmJVtlQZII7TIlTMRFyPM5Lgn7NkQco46g4UMMs7JHBty7mwPuTZxTtm9OqbU+pjTmmtMEI7aFB57mVP6YgfY-YDi2hMGicR+QvJKDZGIOQdhSiBTBasjFMKinmVKQig5RyTkkDOZBS5aLGVYt6bihA+K2T6jEPPI2Xk9gRkfqoNIcj2T0ohR1NKOy9k1NwHUvAKK6lopIAAI1gIIPg2LHn-X7nnYV0TZjDVfBfLYpVhoRgyE+Reshhq1CVQEFVqU1VVPZc8Y5pzzm8uuQao1JrBVPIOmEgiMwcjgiUK2fUWhSqLGkFsR+BFshRmfm4n8NEVnKuSv4TqfrEWauRU03VYbDWCD0KaoV8yM3yAqLYKBxE4SHDRHlbmyktiHGCvmlaCci3epLWWtlzxDmBs5dyi5Vy2nhvrY26NtM8VxosDhMeDl+Suq7W5CwCz5jV0JY3eq7itJju2vgLo5aNVaoadWxdggdpdFXea-+wZ9ig0sAcPIrYA7XQNE5Qdf6KhhgRF6m9u0DFTpnUGrlIaX1vu4VGz9pjv0EThtkfqVg1Dz2uojCM-6shyHPmiaDu9fr3qRdq59aLqNfA-QGTDLyCIghyPJcBew7ww1yoOWlKhEZWCoxuPeXxy0BsQ-O0NbSmMsZrGx2N6bjAmC2Isa8E0igOWlSelE-l7oGmg-jZ4hNiak3JsykpcGHkVsfTql9pnzPQss88QQWyLKKesjGgZasKpKGsMNBy1RoYPjmSCMejZ5iAYA7HdGVtR3goCM5jcFmyYUzhRZKT06OXBp5U5gmaXXMZY81lyg3nla+bxf58UgWRnaijmFnTAVpDChGY-E4cX0l6M1YYzApY+gVjiE2vU5gnx6hFJkdMJVws8wjHVk01hMLVB6-o-rRJvGZx2mBAJhSgkhLXQPPFCJyqjTMKOdQ3M2YPh5vGkGjgsiuIvQW1avWDFZyMZIQsuAOAEFGxIaQ3aITpn8pfB8JL0Il0uhzfUjhQXDvjjOd7-Xvu-fYP96krHnlhJctsE4NhFDKT9ioOE8MnKNgcHUbkpo1t9c+5gSQuAeUHkG+WEII2juWuW-j1suxhTQ7DGTmEGbSULL-XaPNL2R3I-WwzyQAA5bOAAFVAeB2CwAIPFCAEBAjwWMv4Fg6um3zHKjCMabYXKDg0HyBZvsI5ZEUKdOnH3UBfaV-4VX6vNekAssYjDOO-Mml7WIQ4OREYmDhKcdW4jFATfdVUdJP2-tCoSfYzYWQVDnxNDbmhYI8pKIcoC3TCPpdI7zMnjHVIA-VZDFhNkMIYQjNkKORsCZjiJM2OkBYunThYkR4lmcAAVHb-jAnPGCVnNn-ROc1-XcK4a5gFlKFNEt447enxsjcnqSwxEMwD9eiP-Au3x+T6qXcLbKOGcm+RL7TImxxSDm04gNs4ZeZpBItbyjB+tKKiJtnfiYyX8AAeVwHsyrWaXim8CH0ED-xqUEEAPYBAMVjn2OxDFkCkBGRMGIhNHv1hAfFbG2Fcgg0JUZkQR-wLH8GZ38HKRIEoB6GGx4jAFoNJhSzJk1SFUEEKhOmJwxERmIgIiughx1DbG5GiyOAqFRjLzaDIGwFXC5VaG7kN3YO6AfQgLqVkPkKeEECzkEFoMoCFQqEvDwIyGAXwKKAoWkCsGYW+SjHUBek0IUPwCUM5U1Wn2G0GCbUChI2pVbAwggQIJIX-WGjU35BqAcN+y0MUKzn8F4WKUULJF9HAPo2aUcO0N0MSNzEMMflIU5BOG1DSARDhCfEizyAhEXjsAa00BcHOGZwwHgCiDjigGx1r0EBB2kDkFiQwk0BsSEHEghF4z1CWE2C0AdAPwJDABaPn2ZBIUqh5nBGImvHNBTHKCmnO2sMsClwtkvQLCmLQO5AZgz2JWzzbC7X7AWS2FbzHnkCWGe22Ne2QU8T2MtQOBOF1EXhTDHlCzqHiWSAODHlSGqAvGBOgw6XyVMnK1s0qWeL6jDAZgyFLmFBqFSF+WSGBChHmCsVL3uJlzzGvX5VhRZShNQBhLPDkRlSuIkEX0MC8mUi3SfA5hiwxGgx9T9VJMOhLikDUEcT7zsFHCEJ0yKjykqkwjmk7BZInTZKU0DyMBLn7DKPDwGOf2KEHBBGrkkMIKBOg1QylJ83nxFHFHQihATSqDyFOGujUDZHlQqFUDmA-zEx+kk16XZK9hLhvnU0fhTD73mBhgFEunEUcEenIXi2bivWS0NyKyJhKys081ZQeRdIbE2Df25GUlvHbBKDkltFmADNbFGkqH72kNeivzd0wATLxXsAsFqhGKqDhLhA01AyqCklSGb3IMLK0mLK+0rzLOFVGjKEyBKGrPUCWD5D+PmHSGbybwWRd1R2ZwuQPG7LFHjVHmOGcVkGVKyDKHbHEVKBcjCOnPlw9y9wIQXPmERFOFkEbFMMuhu1sQRDZD+QondUUHPRxPLzRw4G7PIiX21mZnBDMDbxoRFCTBFBTGNHTDGLbJoiPz8U4FP27gXKNlmDsRUBckqAFN7DyHeLDB5CyAxDkHSTgIAP1yQILFAM-NtSBxhBckdxcm7CKANHKjqDzPNzsB6LUSoOzn0MmOlNaL2DDnSHEVUDqGvOVNDjyjtHUxTUCwgtfJkMiKcKgBcJUPIqkB5nFxsHkHwxvMQGGnDBMHUDmBnimikNkskDSOiOzjiKYgSN2J4v1McF1AREwjHm5BpS7TmEFHsAhCjECgvEWhcCAA */
|
||||||
id: 'Modeling',
|
id: 'Modeling',
|
||||||
|
|
||||||
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
|
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
|
||||||
@ -153,7 +158,7 @@ export const modelingMachine = createMachine(
|
|||||||
'Enter sketch': [
|
'Enter sketch': [
|
||||||
{
|
{
|
||||||
target: 'animating to existing sketch',
|
target: 'animating to existing sketch',
|
||||||
cond: 'Selection is one face',
|
cond: 'Selection is on face',
|
||||||
actions: ['set sketch metadata'],
|
actions: ['set sketch metadata'],
|
||||||
},
|
},
|
||||||
'Sketch no face',
|
'Sketch no face',
|
||||||
@ -166,6 +171,8 @@ export const modelingMachine = createMachine(
|
|||||||
internal: true,
|
internal: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
entry: 'reset client scene mouse handlers',
|
||||||
},
|
},
|
||||||
|
|
||||||
Sketch: {
|
Sketch: {
|
||||||
@ -803,6 +810,7 @@ export const modelingMachine = createMachine(
|
|||||||
sceneInfra.setCallbacks({
|
sceneInfra.setCallbacks({
|
||||||
onClick: async (args) => {
|
onClick: async (args) => {
|
||||||
if (!args) return
|
if (!args) return
|
||||||
|
if (args.event.which !== 1) return
|
||||||
const { intersection2d } = args
|
const { intersection2d } = args
|
||||||
if (!intersection2d || !sketchPathToNode) return
|
if (!intersection2d || !sketchPathToNode) return
|
||||||
const { modifiedAst } = addStartProfileAt(
|
const { modifiedAst } = addStartProfileAt(
|
||||||
@ -818,6 +826,17 @@ export const modelingMachine = createMachine(
|
|||||||
},
|
},
|
||||||
'add axis n grid': ({ sketchPathToNode }) =>
|
'add axis n grid': ({ sketchPathToNode }) =>
|
||||||
sceneEntitiesManager.createSketchAxis(sketchPathToNode || []),
|
sceneEntitiesManager.createSketchAxis(sketchPathToNode || []),
|
||||||
|
'reset client scene mouse handlers': () => {
|
||||||
|
// when not in sketch mode we don't need any mouse listeners
|
||||||
|
// (note the orbit controls are always active though)
|
||||||
|
sceneInfra.setCallbacks({
|
||||||
|
onClick: () => {},
|
||||||
|
onDrag: () => {},
|
||||||
|
onMouseEnter: () => {},
|
||||||
|
onMouseLeave: () => {},
|
||||||
|
onMove: () => {},
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// end actions
|
// end actions
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ import { sep } from '@tauri-apps/api/path'
|
|||||||
import { homeCommandBarConfig } from 'lib/commandBarConfigs/homeCommandConfig'
|
import { homeCommandBarConfig } from 'lib/commandBarConfigs/homeCommandConfig'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import { isTauri } from 'lib/isTauri'
|
import { isTauri } from 'lib/isTauri'
|
||||||
|
import { kclManager } from 'lang/KclSingleton'
|
||||||
|
|
||||||
// This route only opens in the Tauri desktop context for now,
|
// This route only opens in the Tauri desktop context for now,
|
||||||
// as defined in Router.tsx, so we can use the Tauri APIs and types.
|
// as defined in Router.tsx, so we can use the Tauri APIs and types.
|
||||||
@ -55,6 +56,7 @@ const Home = () => {
|
|||||||
// during the loading of the home page. This is wrapped
|
// during the loading of the home page. This is wrapped
|
||||||
// in a single-use effect to avoid a potential infinite loop.
|
// in a single-use effect to avoid a potential infinite loop.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
kclManager.cancelAllExecutions()
|
||||||
if (newDefaultDirectory) {
|
if (newDefaultDirectory) {
|
||||||
sendToSettings({
|
sendToSettings({
|
||||||
type: 'Set Default Directory',
|
type: 'Set Default Directory',
|
||||||
|
@ -53,4 +53,38 @@ impl FileSystem for FileManager {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_all_files<P: AsRef<std::path::Path>>(
|
||||||
|
&self,
|
||||||
|
path: P,
|
||||||
|
source_range: crate::executor::SourceRange,
|
||||||
|
) -> Result<Vec<std::path::PathBuf>, crate::errors::KclError> {
|
||||||
|
let mut files = vec![];
|
||||||
|
let mut stack = vec![path.as_ref().to_path_buf()];
|
||||||
|
|
||||||
|
while let Some(path) = stack.pop() {
|
||||||
|
if !path.is_dir() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut read_dir = tokio::fs::read_dir(&path).await.map_err(|e| {
|
||||||
|
KclError::Engine(KclErrorDetails {
|
||||||
|
message: format!("Failed to read directory `{}`: {}", path.display(), e),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
while let Ok(Some(entry)) = read_dir.next_entry().await {
|
||||||
|
let path = entry.path();
|
||||||
|
if path.is_dir() {
|
||||||
|
// Iterate over the directory.
|
||||||
|
stack.push(path);
|
||||||
|
} else {
|
||||||
|
files.push(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(files)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,4 +28,11 @@ pub trait FileSystem: Clone {
|
|||||||
path: P,
|
path: P,
|
||||||
source_range: crate::executor::SourceRange,
|
source_range: crate::executor::SourceRange,
|
||||||
) -> Result<bool, crate::errors::KclError>;
|
) -> Result<bool, crate::errors::KclError>;
|
||||||
|
|
||||||
|
/// Get all the files in a directory recursively.
|
||||||
|
async fn get_all_files<P: AsRef<std::path::Path>>(
|
||||||
|
&self,
|
||||||
|
path: P,
|
||||||
|
source_range: crate::executor::SourceRange,
|
||||||
|
) -> Result<Vec<std::path::PathBuf>, crate::errors::KclError>;
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,9 @@ extern "C" {
|
|||||||
|
|
||||||
#[wasm_bindgen(method, js_name = exists, catch)]
|
#[wasm_bindgen(method, js_name = exists, catch)]
|
||||||
fn exists(this: &FileSystemManager, path: String) -> Result<js_sys::Promise, js_sys::Error>;
|
fn exists(this: &FileSystemManager, path: String) -> Result<js_sys::Promise, js_sys::Error>;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, js_name = getAllFiles, catch)]
|
||||||
|
fn get_all_files(this: &FileSystemManager, path: String) -> Result<js_sys::Promise, js_sys::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -31,6 +34,9 @@ impl FileManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for FileManager {}
|
||||||
|
unsafe impl Sync for FileManager {}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl FileSystem for FileManager {
|
impl FileSystem for FileManager {
|
||||||
async fn read<P: AsRef<std::path::Path>>(
|
async fn read<P: AsRef<std::path::Path>>(
|
||||||
@ -112,4 +118,53 @@ impl FileSystem for FileManager {
|
|||||||
|
|
||||||
Ok(it_exists)
|
Ok(it_exists)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_all_files<P: AsRef<std::path::Path>>(
|
||||||
|
&self,
|
||||||
|
path: P,
|
||||||
|
source_range: crate::executor::SourceRange,
|
||||||
|
) -> Result<Vec<std::path::PathBuf>, crate::errors::KclError> {
|
||||||
|
let promise = self
|
||||||
|
.manager
|
||||||
|
.get_all_files(
|
||||||
|
path.as_ref()
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
KclError::Engine(KclErrorDetails {
|
||||||
|
message: "Failed to convert path to string".to_string(),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
})?
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
.map_err(|e| {
|
||||||
|
KclError::Engine(KclErrorDetails {
|
||||||
|
message: e.to_string().into(),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let value = wasm_bindgen_futures::JsFuture::from(promise).await.map_err(|e| {
|
||||||
|
KclError::Engine(KclErrorDetails {
|
||||||
|
message: format!("Failed to wait for promise from javascript: {:?}", e),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let s = value.as_string().ok_or_else(|| {
|
||||||
|
KclError::Engine(KclErrorDetails {
|
||||||
|
message: format!("Failed to get string from response from javascript: `{:?}`", value),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let files: Vec<String> = serde_json::from_str(&s).map_err(|e| {
|
||||||
|
KclError::Engine(KclErrorDetails {
|
||||||
|
message: format!("Failed to parse json from javascript: `{}` `{:?}`", s, e),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(files.into_iter().map(|s| std::path::PathBuf::from(s)).collect())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ pub mod engine;
|
|||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod executor;
|
pub mod executor;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
|
pub mod lsp;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod server;
|
|
||||||
pub mod std;
|
pub mod std;
|
||||||
pub mod token;
|
pub mod token;
|
||||||
|
@ -13,6 +13,8 @@ use tower_lsp::lsp_types::{
|
|||||||
pub trait Backend {
|
pub trait Backend {
|
||||||
fn client(&self) -> tower_lsp::Client;
|
fn client(&self) -> tower_lsp::Client;
|
||||||
|
|
||||||
|
fn fs(&self) -> crate::fs::FileManager;
|
||||||
|
|
||||||
/// Get the current code map.
|
/// Get the current code map.
|
||||||
fn current_code_map(&self) -> DashMap<String, String>;
|
fn current_code_map(&self) -> DashMap<String, String>;
|
||||||
|
|
@ -6,7 +6,7 @@ use std::{
|
|||||||
sync::{Mutex, RwLock},
|
sync::{Mutex, RwLock},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::server::copilot::types::CopilotCompletionResponse;
|
use crate::lsp::copilot::types::CopilotCompletionResponse;
|
||||||
|
|
||||||
// if file changes, keep the cache.
|
// if file changes, keep the cache.
|
||||||
// if line number is different for an existing file, clean.
|
// if line number is different for an existing file, clean.
|
@ -23,7 +23,7 @@ use tower_lsp::{
|
|||||||
LanguageServer,
|
LanguageServer,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::server::{
|
use crate::lsp::{
|
||||||
backend::Backend as _,
|
backend::Backend as _,
|
||||||
copilot::types::{CopilotCompletionResponse, CopilotEditorInfo, CopilotLspCompletionParams, DocParams},
|
copilot::types::{CopilotCompletionResponse, CopilotEditorInfo, CopilotLspCompletionParams, DocParams},
|
||||||
};
|
};
|
||||||
@ -42,6 +42,8 @@ impl Success {
|
|||||||
pub struct Backend {
|
pub struct Backend {
|
||||||
/// The client is used to send notifications and requests to the client.
|
/// The client is used to send notifications and requests to the client.
|
||||||
pub client: tower_lsp::Client,
|
pub client: tower_lsp::Client,
|
||||||
|
/// The file system client to use.
|
||||||
|
pub fs: crate::fs::FileManager,
|
||||||
/// Current code.
|
/// Current code.
|
||||||
pub current_code_map: DashMap<String, String>,
|
pub current_code_map: DashMap<String, String>,
|
||||||
/// The token is used to authenticate requests to the API server.
|
/// The token is used to authenticate requests to the API server.
|
||||||
@ -54,11 +56,15 @@ pub struct Backend {
|
|||||||
|
|
||||||
// Implement the shared backend trait for the language server.
|
// Implement the shared backend trait for the language server.
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl crate::server::backend::Backend for Backend {
|
impl crate::lsp::backend::Backend for Backend {
|
||||||
fn client(&self) -> tower_lsp::Client {
|
fn client(&self) -> tower_lsp::Client {
|
||||||
self.client.clone()
|
self.client.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fs(&self) -> crate::fs::FileManager {
|
||||||
|
self.fs.clone()
|
||||||
|
}
|
||||||
|
|
||||||
fn current_code_map(&self) -> DashMap<String, String> {
|
fn current_code_map(&self) -> DashMap<String, String> {
|
||||||
self.current_code_map.clone()
|
self.current_code_map.clone()
|
||||||
}
|
}
|
||||||
@ -125,15 +131,15 @@ impl Backend {
|
|||||||
let pos = params.doc.position;
|
let pos = params.doc.position;
|
||||||
let uri = params.doc.uri.to_string();
|
let uri = params.doc.uri.to_string();
|
||||||
let rope = ropey::Rope::from_str(¶ms.doc.source);
|
let rope = ropey::Rope::from_str(¶ms.doc.source);
|
||||||
let offset = crate::server::util::position_to_offset(pos, &rope).unwrap_or_default();
|
let offset = crate::lsp::util::position_to_offset(pos, &rope).unwrap_or_default();
|
||||||
|
|
||||||
Ok(DocParams {
|
Ok(DocParams {
|
||||||
uri: uri.to_string(),
|
uri: uri.to_string(),
|
||||||
pos,
|
pos,
|
||||||
language: params.doc.language_id.to_string(),
|
language: params.doc.language_id.to_string(),
|
||||||
prefix: crate::server::util::get_text_before(offset, &rope).unwrap_or_default(),
|
prefix: crate::lsp::util::get_text_before(offset, &rope).unwrap_or_default(),
|
||||||
suffix: crate::server::util::get_text_after(offset, &rope).unwrap_or_default(),
|
suffix: crate::lsp::util::get_text_after(offset, &rope).unwrap_or_default(),
|
||||||
line_before: crate::server::util::get_line_before(pos, &rope).unwrap_or_default(),
|
line_before: crate::lsp::util::get_line_before(pos, &rope).unwrap_or_default(),
|
||||||
rope,
|
rope,
|
||||||
})
|
})
|
||||||
}
|
}
|
@ -29,7 +29,7 @@ use tower_lsp::{
|
|||||||
Client, LanguageServer,
|
Client, LanguageServer,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{ast::types::VariableKind, executor::SourceRange, parser::PIPE_OPERATOR, server::backend::Backend as _};
|
use crate::{ast::types::VariableKind, executor::SourceRange, lsp::backend::Backend as _, parser::PIPE_OPERATOR};
|
||||||
|
|
||||||
/// A subcommand for running the server.
|
/// A subcommand for running the server.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@ -48,6 +48,8 @@ pub struct Server {
|
|||||||
pub struct Backend {
|
pub struct Backend {
|
||||||
/// The client for the backend.
|
/// The client for the backend.
|
||||||
pub client: Client,
|
pub client: Client,
|
||||||
|
/// The file system client to use.
|
||||||
|
pub fs: crate::fs::FileManager,
|
||||||
/// The stdlib completions for the language.
|
/// The stdlib completions for the language.
|
||||||
pub stdlib_completions: HashMap<String, CompletionItem>,
|
pub stdlib_completions: HashMap<String, CompletionItem>,
|
||||||
/// The stdlib signatures for the language.
|
/// The stdlib signatures for the language.
|
||||||
@ -70,11 +72,15 @@ pub struct Backend {
|
|||||||
|
|
||||||
// Implement the shared backend trait for the language server.
|
// Implement the shared backend trait for the language server.
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl crate::server::backend::Backend for Backend {
|
impl crate::lsp::backend::Backend for Backend {
|
||||||
fn client(&self) -> Client {
|
fn client(&self) -> Client {
|
||||||
self.client.clone()
|
self.client.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fs(&self) -> crate::fs::FileManager {
|
||||||
|
self.fs.clone()
|
||||||
|
}
|
||||||
|
|
||||||
fn current_code_map(&self) -> DashMap<String, String> {
|
fn current_code_map(&self) -> DashMap<String, String> {
|
||||||
self.current_code_map.clone()
|
self.current_code_map.clone()
|
||||||
}
|
}
|
@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
mod backend;
|
mod backend;
|
||||||
pub mod copilot;
|
pub mod copilot;
|
||||||
pub mod lsp;
|
pub mod kcl;
|
||||||
mod util;
|
mod util;
|
@ -129,16 +129,22 @@ pub fn recast_wasm(json_str: &str) -> Result<JsValue, JsError> {
|
|||||||
pub struct ServerConfig {
|
pub struct ServerConfig {
|
||||||
into_server: js_sys::AsyncIterator,
|
into_server: js_sys::AsyncIterator,
|
||||||
from_server: web_sys::WritableStream,
|
from_server: web_sys::WritableStream,
|
||||||
|
fs: kcl_lib::fs::wasm::FileSystemManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl ServerConfig {
|
impl ServerConfig {
|
||||||
#[wasm_bindgen(constructor)]
|
#[wasm_bindgen(constructor)]
|
||||||
pub fn new(into_server: js_sys::AsyncIterator, from_server: web_sys::WritableStream) -> Self {
|
pub fn new(
|
||||||
|
into_server: js_sys::AsyncIterator,
|
||||||
|
from_server: web_sys::WritableStream,
|
||||||
|
fs: kcl_lib::fs::wasm::FileSystemManager,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
into_server,
|
into_server,
|
||||||
from_server,
|
from_server,
|
||||||
|
fs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,17 +162,19 @@ pub async fn kcl_lsp_run(config: ServerConfig) -> Result<(), JsValue> {
|
|||||||
let ServerConfig {
|
let ServerConfig {
|
||||||
into_server,
|
into_server,
|
||||||
from_server,
|
from_server,
|
||||||
|
fs,
|
||||||
} = config;
|
} = config;
|
||||||
|
|
||||||
let stdlib = kcl_lib::std::StdLib::new();
|
let stdlib = kcl_lib::std::StdLib::new();
|
||||||
let stdlib_completions = kcl_lib::server::lsp::get_completions_from_stdlib(&stdlib).map_err(|e| e.to_string())?;
|
let stdlib_completions = kcl_lib::lsp::kcl::get_completions_from_stdlib(&stdlib).map_err(|e| e.to_string())?;
|
||||||
let stdlib_signatures = kcl_lib::server::lsp::get_signatures_from_stdlib(&stdlib).map_err(|e| e.to_string())?;
|
let stdlib_signatures = kcl_lib::lsp::kcl::get_signatures_from_stdlib(&stdlib).map_err(|e| e.to_string())?;
|
||||||
// We can unwrap here because we know the tokeniser is valid, since
|
// We can unwrap here because we know the tokeniser is valid, since
|
||||||
// we have a test for it.
|
// we have a test for it.
|
||||||
let token_types = kcl_lib::token::TokenType::all_semantic_token_types().unwrap();
|
let token_types = kcl_lib::token::TokenType::all_semantic_token_types().unwrap();
|
||||||
|
|
||||||
let (service, socket) = LspService::new(|client| kcl_lib::server::lsp::Backend {
|
let (service, socket) = LspService::new(|client| kcl_lib::lsp::kcl::Backend {
|
||||||
client,
|
client,
|
||||||
|
fs: kcl_lib::fs::FileManager::new(fs),
|
||||||
stdlib_completions,
|
stdlib_completions,
|
||||||
stdlib_signatures,
|
stdlib_signatures,
|
||||||
token_types,
|
token_types,
|
||||||
@ -211,24 +219,24 @@ pub async fn copilot_lsp_run(config: ServerConfig, token: String) -> Result<(),
|
|||||||
let ServerConfig {
|
let ServerConfig {
|
||||||
into_server,
|
into_server,
|
||||||
from_server,
|
from_server,
|
||||||
|
fs,
|
||||||
} = config;
|
} = config;
|
||||||
|
|
||||||
let (service, socket) = LspService::build(|client| kcl_lib::server::copilot::Backend {
|
let (service, socket) = LspService::build(|client| kcl_lib::lsp::copilot::Backend {
|
||||||
client,
|
client,
|
||||||
|
fs: kcl_lib::fs::FileManager::new(fs),
|
||||||
current_code_map: Default::default(),
|
current_code_map: Default::default(),
|
||||||
editor_info: Arc::new(RwLock::new(
|
editor_info: Arc::new(RwLock::new(kcl_lib::lsp::copilot::types::CopilotEditorInfo::default())),
|
||||||
kcl_lib::server::copilot::types::CopilotEditorInfo::default(),
|
cache: kcl_lib::lsp::copilot::cache::CopilotCache::new(),
|
||||||
)),
|
|
||||||
cache: kcl_lib::server::copilot::cache::CopilotCache::new(),
|
|
||||||
token,
|
token,
|
||||||
})
|
})
|
||||||
.custom_method("setEditorInfo", kcl_lib::server::copilot::Backend::set_editor_info)
|
.custom_method("setEditorInfo", kcl_lib::lsp::copilot::Backend::set_editor_info)
|
||||||
.custom_method(
|
.custom_method(
|
||||||
"getCompletions",
|
"getCompletions",
|
||||||
kcl_lib::server::copilot::Backend::get_completions_cycling,
|
kcl_lib::lsp::copilot::Backend::get_completions_cycling,
|
||||||
)
|
)
|
||||||
.custom_method("notifyAccepted", kcl_lib::server::copilot::Backend::accept_completions)
|
.custom_method("notifyAccepted", kcl_lib::lsp::copilot::Backend::accept_completions)
|
||||||
.custom_method("notifyRejected", kcl_lib::server::copilot::Backend::reject_completions)
|
.custom_method("notifyRejected", kcl_lib::lsp::copilot::Backend::reject_completions)
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
let input = wasm_bindgen_futures::stream::JsStream::from(into_server);
|
let input = wasm_bindgen_futures::stream::JsStream::from(into_server);
|
||||||
|
Reference in New Issue
Block a user