refactor code storage (#2144)
* refactor code storage Signed-off-by: Jess Frazelle <github@jessfraz.com> * typo Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * for now dont do onupdate its lagging the editor Signed-off-by: Jess Frazelle <github@jessfraz.com> * way smaller delay Signed-off-by: Jess Frazelle <github@jessfraz.com> * turn abck on on update Signed-off-by: Jess Frazelle <github@jessfraz.com> * dont be fancy Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix linter Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * empty * empty * good things Signed-off-by: Jess Frazelle <github@jessfraz.com> * empty * empty * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> * make less flakey Signed-off-by: Jess Frazelle <github@jessfraz.com> * go abck to errors for now Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com>
@ -560,7 +560,7 @@ test('Auto complete works', async ({ page }) => {
|
|||||||
// expect there to be three auto complete options
|
// expect there to be three auto complete options
|
||||||
await expect(page.locator('.cm-completionLabel')).toHaveCount(3)
|
await expect(page.locator('.cm-completionLabel')).toHaveCount(3)
|
||||||
await page.getByText('startSketchOn').click()
|
await page.getByText('startSketchOn').click()
|
||||||
await page.keyboard.type("'XY'")
|
await page.keyboard.type("'XZ'")
|
||||||
await page.keyboard.press('Tab')
|
await page.keyboard.press('Tab')
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
await page.keyboard.type(' |> startProfi')
|
await page.keyboard.type(' |> startProfi')
|
||||||
@ -570,6 +570,7 @@ test('Auto complete works', async ({ page }) => {
|
|||||||
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.press('Tab')
|
await page.keyboard.press('Tab')
|
||||||
|
await page.keyboard.type('12')
|
||||||
await page.keyboard.press('Tab')
|
await page.keyboard.press('Tab')
|
||||||
await page.keyboard.press('Tab')
|
await page.keyboard.press('Tab')
|
||||||
await page.keyboard.press('Tab')
|
await page.keyboard.press('Tab')
|
||||||
@ -592,8 +593,8 @@ test('Auto complete works', async ({ page }) => {
|
|||||||
await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
|
await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('XY')
|
.toHaveText(`const part001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([3.14, 3.14], %)
|
|> startProfileAt([3.14, 12], %)
|
||||||
|> xLine(5, %) // lin`)
|
|> xLine(5, %) // lin`)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1239,7 +1240,7 @@ fn yohey = (pos) => {
|
|||||||
},
|
},
|
||||||
selectionsSnippets
|
selectionsSnippets
|
||||||
)
|
)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 1000 })
|
||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
@ -336,10 +336,7 @@ const part001 = startSketchOn('-XZ')
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test('extrude on each default plane should be stable', async ({
|
const extrudeDefaultPlane = async (context: any, page: any, plane: string) => {
|
||||||
page,
|
|
||||||
context,
|
|
||||||
}) => {
|
|
||||||
await context.addInitScript(async () => {
|
await context.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'SETTINGS_PERSIST_KEY',
|
'SETTINGS_PERSIST_KEY',
|
||||||
@ -356,8 +353,8 @@ test('extrude on each default plane should be stable', async ({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
const u = getUtils(page)
|
|
||||||
const makeCode = (plane = 'XY') => `const part001 = startSketchOn('${plane}')
|
const code = `const part001 = startSketchOn('${plane}')
|
||||||
|> startProfileAt([7.00, 4.40], %)
|
|> startProfileAt([7.00, 4.40], %)
|
||||||
|> line([6.60, -0.20], %)
|
|> line([6.60, -0.20], %)
|
||||||
|> line([2.80, 5.00], %)
|
|> line([2.80, 5.00], %)
|
||||||
@ -366,9 +363,11 @@ test('extrude on each default plane should be stable', async ({
|
|||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(10.00, %)
|
|> extrude(10.00, %)
|
||||||
`
|
`
|
||||||
await context.addInitScript(async (code) => {
|
await page.addInitScript(async (code: string) => {
|
||||||
localStorage.setItem('persistCode', code)
|
localStorage.setItem('persistCode', code)
|
||||||
}, makeCode('XY'))
|
})
|
||||||
|
|
||||||
|
const u = getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
@ -378,14 +377,11 @@ test('extrude on each default plane should be stable', async ({
|
|||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.clearAndCloseDebugPanel()
|
await u.clearAndCloseDebugPanel()
|
||||||
await page.waitForTimeout(200)
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
const runSnapshotsForOtherPlanes = async (plane = 'XY') => {
|
|
||||||
// clear code
|
// clear code
|
||||||
await u.removeCurrentCode()
|
await u.removeCurrentCode()
|
||||||
// add makeCode('XZ')
|
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await u.doAndWaitForImageDiff(
|
await u.doAndWaitForImageDiff(
|
||||||
() => page.locator('.cm-content').fill(makeCode(plane)),
|
() => page.locator('.cm-content').fill(code),
|
||||||
200
|
200
|
||||||
)
|
)
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
@ -398,14 +394,30 @@ test('extrude on each default plane should be stable', async ({
|
|||||||
})
|
})
|
||||||
await u.openKclCodePanel()
|
await u.openKclCodePanel()
|
||||||
}
|
}
|
||||||
await runSnapshotsForOtherPlanes('XY')
|
test.describe('extrude on default planes should be stable', () => {
|
||||||
await runSnapshotsForOtherPlanes('-XY')
|
test('XY', async ({ page, context }) => {
|
||||||
|
await extrudeDefaultPlane(context, page, 'XY')
|
||||||
|
})
|
||||||
|
|
||||||
await runSnapshotsForOtherPlanes('XZ')
|
test('XZ', async ({ page, context }) => {
|
||||||
await runSnapshotsForOtherPlanes('-XZ')
|
await extrudeDefaultPlane(context, page, 'XZ')
|
||||||
|
})
|
||||||
|
|
||||||
await runSnapshotsForOtherPlanes('YZ')
|
test('YZ', async ({ page, context }) => {
|
||||||
await runSnapshotsForOtherPlanes('-YZ')
|
await extrudeDefaultPlane(context, page, 'YZ')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('-XY', async ({ page, context }) => {
|
||||||
|
await extrudeDefaultPlane(context, page, '-XY')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('-XZ', async ({ page, context }) => {
|
||||||
|
await extrudeDefaultPlane(context, page, '-XZ')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('-YZ', async ({ page, context }) => {
|
||||||
|
await extrudeDefaultPlane(context, page, '-YZ')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Draft segments should look right', async ({ page, context }) => {
|
test('Draft segments should look right', async ({ page, context }) => {
|
||||||
|
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 44 KiB |
@ -52,7 +52,12 @@ import {
|
|||||||
VariableDeclaration,
|
VariableDeclaration,
|
||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { engineCommandManager, kclManager, sceneInfra } from 'lib/singletons'
|
import {
|
||||||
|
engineCommandManager,
|
||||||
|
kclManager,
|
||||||
|
sceneInfra,
|
||||||
|
codeManager,
|
||||||
|
} from 'lib/singletons'
|
||||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||||
import { executeAst, useStore } from 'useStore'
|
import { executeAst, useStore } from 'useStore'
|
||||||
import {
|
import {
|
||||||
@ -542,7 +547,7 @@ export class SceneEntities {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await kclManager.executeAstMock(modifiedAst, { updates: 'code' })
|
await kclManager.executeAstMock(modifiedAst)
|
||||||
this.setUpDraftSegment(
|
this.setUpDraftSegment(
|
||||||
sketchPathToNode,
|
sketchPathToNode,
|
||||||
forward,
|
forward,
|
||||||
@ -637,9 +642,7 @@ export class SceneEntities {
|
|||||||
spliceBetween: true,
|
spliceBetween: true,
|
||||||
})
|
})
|
||||||
addingNewSegmentStatus = 'pending'
|
addingNewSegmentStatus = 'pending'
|
||||||
await kclManager.executeAstMock(mod.modifiedAst, {
|
await kclManager.executeAstMock(mod.modifiedAst)
|
||||||
updates: 'code',
|
|
||||||
})
|
|
||||||
await this.tearDownSketch({ removeAxis: false })
|
await this.tearDownSketch({ removeAxis: false })
|
||||||
this.setupSketch({
|
this.setupSketch({
|
||||||
sketchPathToNode: pathToNode,
|
sketchPathToNode: pathToNode,
|
||||||
@ -784,9 +787,9 @@ export class SceneEntities {
|
|||||||
;(async () => {
|
;(async () => {
|
||||||
const code = recast(modifiedAst)
|
const code = recast(modifiedAst)
|
||||||
if (!draftInfo)
|
if (!draftInfo)
|
||||||
// don't want to mode the user's code yet as they have't committed to the change yet
|
// don't want to mod the user's code yet as they have't committed to the change yet
|
||||||
// plus this would be the truncated ast being recast, it would be wrong
|
// plus this would be the truncated ast being recast, it would be wrong
|
||||||
kclManager.setCode(code, false)
|
codeManager.updateCodeStateEditor(code)
|
||||||
const { programMemory } = await executeAst({
|
const { programMemory } = await executeAst({
|
||||||
ast: truncatedAst,
|
ast: truncatedAst,
|
||||||
useFakeExecutor: true,
|
useFakeExecutor: true,
|
||||||
|
@ -13,7 +13,7 @@ import styles from './FileTree.module.css'
|
|||||||
import { sortProject } from 'lib/tauriFS'
|
import { sortProject } from 'lib/tauriFS'
|
||||||
import { FILE_EXT } from 'lib/constants'
|
import { FILE_EXT } from 'lib/constants'
|
||||||
import { CustomIcon } from './CustomIcon'
|
import { CustomIcon } from './CustomIcon'
|
||||||
import { kclManager } from 'lib/singletons'
|
import { codeManager, kclManager } from 'lib/singletons'
|
||||||
import { useDocumentHasFocus } from 'hooks/useDocumentHasFocus'
|
import { useDocumentHasFocus } from 'hooks/useDocumentHasFocus'
|
||||||
import { useLspContext } from './LspProvider'
|
import { useLspContext } from './LspProvider'
|
||||||
|
|
||||||
@ -171,10 +171,13 @@ const FileTreeItem = ({
|
|||||||
|
|
||||||
if (fileOrDir.name?.endsWith(FILE_EXT) === false && project?.path) {
|
if (fileOrDir.name?.endsWith(FILE_EXT) === false && project?.path) {
|
||||||
// Import non-kcl files
|
// Import non-kcl files
|
||||||
kclManager.setCodeAndExecute(
|
// We want to update both the state and editor here.
|
||||||
|
codeManager.updateCodeStateEditor(
|
||||||
`import("${fileOrDir.path.replace(project.path, '.')}")\n` +
|
`import("${fileOrDir.path.replace(project.path, '.')}")\n` +
|
||||||
kclManager.code
|
codeManager.code
|
||||||
)
|
)
|
||||||
|
codeManager.writeToFile()
|
||||||
|
kclManager.executeCode(true)
|
||||||
} else {
|
} else {
|
||||||
// Let the lsp servers know we closed a file.
|
// Let the lsp servers know we closed a file.
|
||||||
onFileClose(currentFile?.path || null, project?.path || null)
|
onFileClose(currentFile?.path || null, project?.path || null)
|
||||||
|
@ -12,8 +12,12 @@ import { SetSelections, modelingMachine } from 'machines/modelingMachine'
|
|||||||
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||||
import { kclManager, sceneInfra, engineCommandManager } from 'lib/singletons'
|
import {
|
||||||
import { useKclContext } from 'lang/KclProvider'
|
kclManager,
|
||||||
|
sceneInfra,
|
||||||
|
engineCommandManager,
|
||||||
|
codeManager,
|
||||||
|
} from 'lib/singletons'
|
||||||
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
|
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
|
||||||
import {
|
import {
|
||||||
angleBetweenInfo,
|
angleBetweenInfo,
|
||||||
@ -38,7 +42,7 @@ import {
|
|||||||
getSketchQuaternion,
|
getSketchQuaternion,
|
||||||
} from 'clientSideScene/sceneEntities'
|
} from 'clientSideScene/sceneEntities'
|
||||||
import { sketchOnExtrudedFace, startSketchOnDefault } from 'lang/modifyAst'
|
import { sketchOnExtrudedFace, startSketchOnDefault } from 'lang/modifyAst'
|
||||||
import { Program, coreDump, parse } from 'lang/wasm'
|
import { Program, coreDump } from 'lang/wasm'
|
||||||
import { getNodePathFromSourceRange, isSingleCursorInPipe } from 'lang/queryAst'
|
import { getNodePathFromSourceRange, isSingleCursorInPipe } from 'lang/queryAst'
|
||||||
import { TEST } from 'env'
|
import { TEST } from 'env'
|
||||||
import { exportFromEngine } from 'lib/exportFromEngine'
|
import { exportFromEngine } from 'lib/exportFromEngine'
|
||||||
@ -74,7 +78,6 @@ export const ModelingMachineProvider = ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
} = useSettingsAuthContext()
|
} = useSettingsAuthContext()
|
||||||
const { code } = useKclContext()
|
|
||||||
const token = auth?.context?.token
|
const token = auth?.context?.token
|
||||||
const streamRef = useRef<HTMLDivElement>(null)
|
const streamRef = useRef<HTMLDivElement>(null)
|
||||||
useSetupEngineManager(streamRef, token, theme.current)
|
useSetupEngineManager(streamRef, token, theme.current)
|
||||||
@ -117,11 +120,7 @@ export const ModelingMachineProvider = ({
|
|||||||
{
|
{
|
||||||
actions: {
|
actions: {
|
||||||
'sketch exit execute': () => {
|
'sketch exit execute': () => {
|
||||||
try {
|
kclManager.executeCode(true)
|
||||||
kclManager.executeAst(parse(kclManager.code))
|
|
||||||
} catch (e) {
|
|
||||||
kclManager.executeAst()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
'Set mouse state': assign({
|
'Set mouse state': assign({
|
||||||
mouseState: (_, event) => event.data,
|
mouseState: (_, event) => event.data,
|
||||||
@ -270,7 +269,8 @@ export const ModelingMachineProvider = ({
|
|||||||
if (selectionRanges.codeBasedSelections.length < 1) return false
|
if (selectionRanges.codeBasedSelections.length < 1) return false
|
||||||
const isPipe = isSketchPipe(selectionRanges)
|
const isPipe = isSketchPipe(selectionRanges)
|
||||||
|
|
||||||
if (isSelectionLastLine(selectionRanges, code)) return true
|
if (isSelectionLastLine(selectionRanges, codeManager.code))
|
||||||
|
return true
|
||||||
if (!isPipe) return false
|
if (!isPipe) return false
|
||||||
|
|
||||||
return canExtrudeSelection(selectionRanges)
|
return canExtrudeSelection(selectionRanges)
|
||||||
@ -294,7 +294,7 @@ export const ModelingMachineProvider = ({
|
|||||||
const varDecIndex = sketchDetails.sketchPathToNode[1][0]
|
const varDecIndex = sketchDetails.sketchPathToNode[1][0]
|
||||||
// remove body item at varDecIndex
|
// remove body item at varDecIndex
|
||||||
newAst.body = newAst.body.filter((_, i) => i !== varDecIndex)
|
newAst.body = newAst.body.filter((_, i) => i !== varDecIndex)
|
||||||
await kclManager.executeAstMock(newAst, { updates: 'code' })
|
await kclManager.executeAstMock(newAst)
|
||||||
sceneInfra.setCallbacks({
|
sceneInfra.setCallbacks({
|
||||||
onClick: () => {},
|
onClick: () => {},
|
||||||
onDrag: () => {},
|
onDrag: () => {},
|
||||||
@ -309,7 +309,7 @@ export const ModelingMachineProvider = ({
|
|||||||
kclManager.programMemory,
|
kclManager.programMemory,
|
||||||
data.cap
|
data.cap
|
||||||
)
|
)
|
||||||
await kclManager.executeAstMock(modifiedAst, { updates: 'code' })
|
await kclManager.executeAstMock(modifiedAst)
|
||||||
|
|
||||||
const forward = new Vector3(...data.zAxis)
|
const forward = new Vector3(...data.zAxis)
|
||||||
const up = new Vector3(...data.yAxis)
|
const up = new Vector3(...data.yAxis)
|
||||||
|
@ -57,10 +57,6 @@ import {
|
|||||||
completionKeymap,
|
completionKeymap,
|
||||||
hasNextSnippetField,
|
hasNextSnippetField,
|
||||||
} from '@codemirror/autocomplete'
|
} from '@codemirror/autocomplete'
|
||||||
import {
|
|
||||||
NetworkHealthState,
|
|
||||||
useNetworkStatus,
|
|
||||||
} from 'components/NetworkHealthIndicator'
|
|
||||||
import { kclErrorsToDiagnostics } from 'lang/errors'
|
import { kclErrorsToDiagnostics } from 'lang/errors'
|
||||||
|
|
||||||
export const editorShortcutMeta = {
|
export const editorShortcutMeta = {
|
||||||
@ -86,16 +82,14 @@ export const KclEditorPane = () => {
|
|||||||
setEditorView: s.setEditorView,
|
setEditorView: s.setEditorView,
|
||||||
isShiftDown: s.isShiftDown,
|
isShiftDown: s.isShiftDown,
|
||||||
}))
|
}))
|
||||||
const { code, errors } = useKclContext()
|
const { editorCode, errors } = useKclContext()
|
||||||
const lastEvent = useRef({ event: '', time: Date.now() })
|
const lastEvent = useRef({ event: '', time: Date.now() })
|
||||||
const { copilotLSP, kclLSP } = useLspContext()
|
const { copilotLSP, kclLSP } = useLspContext()
|
||||||
const { overallState } = useNetworkStatus()
|
|
||||||
const isNetworkOkay = overallState === NetworkHealthState.Ok
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window === 'undefined') return
|
if (typeof window === 'undefined') return
|
||||||
const onlineCallback = () => kclManager.setCodeAndExecute(kclManager.code)
|
const onlineCallback = () => kclManager.executeCode(true)
|
||||||
window.addEventListener('online', onlineCallback)
|
window.addEventListener('online', onlineCallback)
|
||||||
return () => window.removeEventListener('online', onlineCallback)
|
return () => window.removeEventListener('online', onlineCallback)
|
||||||
}, [])
|
}, [])
|
||||||
@ -126,19 +120,6 @@ export const KclEditorPane = () => {
|
|||||||
const { enable: convertEnabled, handleClick: convertCallback } =
|
const { enable: convertEnabled, handleClick: convertCallback } =
|
||||||
useConvertToVariable()
|
useConvertToVariable()
|
||||||
|
|
||||||
const onChange = async (newCode: string) => {
|
|
||||||
// If we are just fucking around in a snippet, return early and don't
|
|
||||||
// trigger stuff below that might cause the component to re-render.
|
|
||||||
// Otherwise we will not be able to tab thru the snippet portions.
|
|
||||||
// We explicitly dont check HasPrevSnippetField because we always add
|
|
||||||
// a ${} to the end of the function so that's fine.
|
|
||||||
if (editorView && hasNextSnippetField(editorView.state)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNetworkOkay) kclManager.setCodeAndExecute(newCode)
|
|
||||||
else kclManager.setCode(newCode)
|
|
||||||
}
|
|
||||||
const lastSelection = useRef('')
|
const lastSelection = useRef('')
|
||||||
const onUpdate = (viewUpdate: ViewUpdate) => {
|
const onUpdate = (viewUpdate: ViewUpdate) => {
|
||||||
// If we are just fucking around in a snippet, return early and don't
|
// If we are just fucking around in a snippet, return early and don't
|
||||||
@ -307,7 +288,13 @@ export const KclEditorPane = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return extensions
|
return extensions
|
||||||
}, [kclLSP, textWrapping.current, cursorBlinking.current, convertCallback])
|
}, [
|
||||||
|
kclLSP,
|
||||||
|
copilotLSP,
|
||||||
|
textWrapping.current,
|
||||||
|
cursorBlinking.current,
|
||||||
|
convertCallback,
|
||||||
|
])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -315,9 +302,8 @@ export const KclEditorPane = () => {
|
|||||||
className={'absolute inset-0 ' + (cursorBlinking.current ? 'blink' : '')}
|
className={'absolute inset-0 ' + (cursorBlinking.current ? 'blink' : '')}
|
||||||
>
|
>
|
||||||
<ReactCodeMirror
|
<ReactCodeMirror
|
||||||
value={code}
|
value={editorCode}
|
||||||
extensions={editorExtensions}
|
extensions={editorExtensions}
|
||||||
onChange={onChange}
|
|
||||||
onUpdate={onUpdate}
|
onUpdate={onUpdate}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
onCreateEditor={(_editorView) => setEditorView(_editorView)}
|
onCreateEditor={(_editorView) => setEditorView(_editorView)}
|
||||||
|
@ -138,7 +138,7 @@ export const SettingsAuthProviderBase = ({
|
|||||||
id: `${event.type}.success`,
|
id: `${event.type}.success`,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
'Execute AST': () => kclManager.executeAst(),
|
'Execute AST': () => kclManager.executeCode(true),
|
||||||
persistSettings: (context) =>
|
persistSettings: (context) =>
|
||||||
saveSettings(context, loadedProject?.project?.path),
|
saveSettings(context, loadedProject?.project?.path),
|
||||||
},
|
},
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
import {
|
import { completeFromList, snippetCompletion } from '@codemirror/autocomplete'
|
||||||
completeFromList,
|
|
||||||
hasNextSnippetField,
|
|
||||||
snippetCompletion,
|
|
||||||
} from '@codemirror/autocomplete'
|
|
||||||
import { setDiagnostics } from '@codemirror/lint'
|
import { setDiagnostics } from '@codemirror/lint'
|
||||||
import { Facet } from '@codemirror/state'
|
import { Facet } from '@codemirror/state'
|
||||||
import { EditorView, Tooltip } from '@codemirror/view'
|
import { EditorView, Tooltip } from '@codemirror/view'
|
||||||
@ -25,7 +21,7 @@ import { LanguageServerClient } from 'editor/plugins/lsp'
|
|||||||
import { Marked } from '@ts-stack/markdown'
|
import { Marked } from '@ts-stack/markdown'
|
||||||
import { posToOffset } from 'editor/plugins/lsp/util'
|
import { posToOffset } from 'editor/plugins/lsp/util'
|
||||||
import { Program, ProgramMemory } from 'lang/wasm'
|
import { Program, ProgramMemory } from 'lang/wasm'
|
||||||
import { kclManager } from 'lib/singletons'
|
import { codeManager, kclManager } from 'lib/singletons'
|
||||||
import type { UnitLength } from 'wasm-lib/kcl/bindings/UnitLength'
|
import type { UnitLength } from 'wasm-lib/kcl/bindings/UnitLength'
|
||||||
import { UpdateUnitsResponse } from 'wasm-lib/kcl/bindings/UpdateUnitsResponse'
|
import { UpdateUnitsResponse } from 'wasm-lib/kcl/bindings/UpdateUnitsResponse'
|
||||||
import { UpdateCanExecuteResponse } from 'wasm-lib/kcl/bindings/UpdateCanExecuteResponse'
|
import { UpdateCanExecuteResponse } from 'wasm-lib/kcl/bindings/UpdateCanExecuteResponse'
|
||||||
@ -53,6 +49,7 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
private foldingRanges: LSP.FoldingRange[] | null = null
|
private foldingRanges: LSP.FoldingRange[] | null = null
|
||||||
private _defferer = deferExecution((code: string) => {
|
private _defferer = deferExecution((code: string) => {
|
||||||
try {
|
try {
|
||||||
|
// Update the state (not the editor) with the new code.
|
||||||
this.client.textDocumentDidChange({
|
this.client.textDocumentDidChange({
|
||||||
textDocument: {
|
textDocument: {
|
||||||
uri: this.documentUri,
|
uri: this.documentUri,
|
||||||
@ -83,21 +80,16 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
update({ docChanged, state }: ViewUpdate) {
|
update({ docChanged }: ViewUpdate) {
|
||||||
if (!docChanged) return
|
if (!docChanged) return
|
||||||
|
|
||||||
// If we are just fucking around in a snippet, return early and don't
|
const newCode = this.view.state.doc.toString()
|
||||||
// trigger stuff below that might cause the component to re-render.
|
|
||||||
// Otherwise we will not be able to tab thru the snippet portions.
|
|
||||||
// We explicitly dont check HasPrevSnippetField because we always add
|
|
||||||
// a ${} to the end of the function so that's fine.
|
|
||||||
// We only care about this for the 'kcl' plugin.
|
|
||||||
if (this.client.name === 'kcl' && hasNextSnippetField(state)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
codeManager.code = newCode
|
||||||
|
codeManager.writeToFile()
|
||||||
|
kclManager.executeCode()
|
||||||
this.sendChange({
|
this.sendChange({
|
||||||
documentText: this.view.state.doc.toString(),
|
documentText: newCode,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,17 +114,6 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
async sendChange({ documentText }: { documentText: string }) {
|
async sendChange({ documentText }: { documentText: string }) {
|
||||||
if (!this.client.ready) return
|
if (!this.client.ready) return
|
||||||
|
|
||||||
if (documentText.length > 5000) {
|
|
||||||
// Clear out the text it thinks we have, large documents will throw a stack error.
|
|
||||||
// This is obviously not a good fix but it works for now til we figure
|
|
||||||
// out the stack limits in wasm and also rewrite the parser.
|
|
||||||
// Since this is only for hover and completions it will be fine,
|
|
||||||
// completions will still work for stdlib but hover will not.
|
|
||||||
// That seems like a fine trade-off for a working editor for the time
|
|
||||||
// being.
|
|
||||||
documentText = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
this._defferer(documentText)
|
this._defferer(documentText)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,17 +353,19 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
return completeFromList(options)(context)
|
return completeFromList(options)(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
processNotification(notification: LSP.NotificationMessage) {
|
async processNotification(notification: LSP.NotificationMessage) {
|
||||||
try {
|
try {
|
||||||
switch (notification.method) {
|
switch (notification.method) {
|
||||||
case 'textDocument/publishDiagnostics':
|
case 'textDocument/publishDiagnostics':
|
||||||
const params = notification.params as PublishDiagnosticsParams
|
const params = notification.params as PublishDiagnosticsParams
|
||||||
this.processDiagnostics(params)
|
this.processDiagnostics(params)
|
||||||
// Update the kcl errors pane.
|
// Update the kcl errors pane.
|
||||||
/*kclManager.kclErrors = lspDiagnosticsToKclErrors(
|
/*if (!kclManager.isExecuting) {
|
||||||
|
kclManager.kclErrors = lspDiagnosticsToKclErrors(
|
||||||
this.view.state.doc,
|
this.view.state.doc,
|
||||||
params.diagnostics
|
params.diagnostics
|
||||||
)*/
|
)
|
||||||
|
}*/
|
||||||
break
|
break
|
||||||
case 'window/logMessage':
|
case 'window/logMessage':
|
||||||
console.log(
|
console.log(
|
||||||
@ -402,9 +385,17 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
// The server has updated the AST, we should update elsewhere.
|
// The server has updated the AST, we should update elsewhere.
|
||||||
let updatedAst = notification.params as Program
|
let updatedAst = notification.params as Program
|
||||||
console.log('[lsp]: Updated AST', updatedAst)
|
console.log('[lsp]: Updated AST', updatedAst)
|
||||||
// Since we aren't using the lsp server for executing the program
|
// Update the ast when we are not already executing.
|
||||||
// we don't update the ast here.
|
/* if (!kclManager.isExecuting) {
|
||||||
//kclManager.ast = updatedAst
|
kclManager.ast = updatedAst
|
||||||
|
// Execute the ast.
|
||||||
|
console.log('[lsp]: executing ast')
|
||||||
|
await kclManager.executeAst(updatedAst)
|
||||||
|
console.log('[lsp]: executed ast', kclManager.kclErrors)
|
||||||
|
let diagnostics = kclErrorsToDiagnostics(kclManager.kclErrors)
|
||||||
|
this.view.dispatch(setDiagnostics(this.view.state, diagnostics))
|
||||||
|
console.log('[lsp]: updated diagnostics')
|
||||||
|
}*/
|
||||||
|
|
||||||
// Update the folding ranges, since the AST has changed.
|
// Update the folding ranges, since the AST has changed.
|
||||||
// This is a hack since codemirror does not support async foldService.
|
// This is a hack since codemirror does not support async foldService.
|
||||||
|
@ -3,7 +3,7 @@ import { useStore } from '../useStore'
|
|||||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||||
import { deferExecution } from 'lib/utils'
|
import { deferExecution } from 'lib/utils'
|
||||||
import { Themes } from 'lib/theme'
|
import { Themes } from 'lib/theme'
|
||||||
import { makeDefaultPlanes, parse } from 'lang/wasm'
|
import { makeDefaultPlanes } from 'lang/wasm'
|
||||||
|
|
||||||
export function useSetupEngineManager(
|
export function useSetupEngineManager(
|
||||||
streamRef: React.RefObject<HTMLDivElement>,
|
streamRef: React.RefObject<HTMLDivElement>,
|
||||||
@ -40,9 +40,10 @@ export function useSetupEngineManager(
|
|||||||
setIsStreamReady,
|
setIsStreamReady,
|
||||||
width: quadWidth,
|
width: quadWidth,
|
||||||
height: quadHeight,
|
height: quadHeight,
|
||||||
executeCode: (code?: string) => {
|
executeCode: () => {
|
||||||
const _ast = parse(code || kclManager.code)
|
// We only want to execute the code here that we already have set.
|
||||||
return kclManager.executeAst(_ast, true)
|
// Nothing else.
|
||||||
|
return kclManager.executeCode(true)
|
||||||
},
|
},
|
||||||
token,
|
token,
|
||||||
theme,
|
theme,
|
||||||
|
@ -3,10 +3,11 @@ import { createContext, useContext, useEffect, useState } from 'react'
|
|||||||
import { type IndexLoaderData } from 'lib/types'
|
import { type IndexLoaderData } from 'lib/types'
|
||||||
import { useLoaderData } from 'react-router-dom'
|
import { useLoaderData } from 'react-router-dom'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import { kclManager } from 'lib/singletons'
|
import { codeManager, kclManager } from 'lib/singletons'
|
||||||
|
|
||||||
const KclContext = createContext({
|
const KclContext = createContext({
|
||||||
code: kclManager?.code || '',
|
code: codeManager?.code || '',
|
||||||
|
editorCode: codeManager?.code || '',
|
||||||
programMemory: kclManager?.programMemory,
|
programMemory: kclManager?.programMemory,
|
||||||
ast: kclManager?.ast,
|
ast: kclManager?.ast,
|
||||||
isExecuting: kclManager?.isExecuting,
|
isExecuting: kclManager?.isExecuting,
|
||||||
@ -27,7 +28,10 @@ export function KclContextProvider({
|
|||||||
// If we try to use this component anywhere but under the paths.FILE route it will fail
|
// If we try to use this component anywhere but under the paths.FILE route it will fail
|
||||||
// Because useLoaderData assumes we are on within it's context.
|
// Because useLoaderData assumes we are on within it's context.
|
||||||
const { code: loadedCode } = useLoaderData() as IndexLoaderData
|
const { code: loadedCode } = useLoaderData() as IndexLoaderData
|
||||||
const [code, setCode] = useState(loadedCode || kclManager.code)
|
// Both the code state and the editor state start off with the same code.
|
||||||
|
const [code, setCode] = useState(loadedCode || codeManager.code)
|
||||||
|
const [editorCode, setEditorCode] = useState(code)
|
||||||
|
|
||||||
const [programMemory, setProgramMemory] = useState(kclManager.programMemory)
|
const [programMemory, setProgramMemory] = useState(kclManager.programMemory)
|
||||||
const [ast, setAst] = useState(kclManager.ast)
|
const [ast, setAst] = useState(kclManager.ast)
|
||||||
const [isExecuting, setIsExecuting] = useState(false)
|
const [isExecuting, setIsExecuting] = useState(false)
|
||||||
@ -36,8 +40,11 @@ export function KclContextProvider({
|
|||||||
const [wasmInitFailed, setWasmInitFailed] = useState(false)
|
const [wasmInitFailed, setWasmInitFailed] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
kclManager.registerCallBacks({
|
codeManager.registerCallBacks({
|
||||||
setCode,
|
setCode,
|
||||||
|
setEditorCode,
|
||||||
|
})
|
||||||
|
kclManager.registerCallBacks({
|
||||||
setProgramMemory,
|
setProgramMemory,
|
||||||
setAst,
|
setAst,
|
||||||
setLogs,
|
setLogs,
|
||||||
@ -49,12 +56,13 @@ export function KclContextProvider({
|
|||||||
|
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
kclManager.setParams(params)
|
codeManager.setParams(params)
|
||||||
}, [params])
|
}, [params])
|
||||||
return (
|
return (
|
||||||
<KclContext.Provider
|
<KclContext.Provider
|
||||||
value={{
|
value={{
|
||||||
code,
|
code,
|
||||||
|
editorCode,
|
||||||
programMemory,
|
programMemory,
|
||||||
ast,
|
ast,
|
||||||
isExecuting,
|
isExecuting,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { executeAst, executeCode } from 'useStore'
|
import { executeAst } from 'useStore'
|
||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
import { KCLError } from './errors'
|
import { KCLError } from './errors'
|
||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
@ -16,17 +16,10 @@ import {
|
|||||||
SketchGroup,
|
SketchGroup,
|
||||||
ExtrudeGroup,
|
ExtrudeGroup,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { bracket } from 'lib/exampleKcl'
|
|
||||||
import { getNodeFromPath } from './queryAst'
|
import { getNodeFromPath } from './queryAst'
|
||||||
import { Params } from 'react-router-dom'
|
import { codeManager } from 'lib/singletons'
|
||||||
import { isTauri } from 'lib/isTauri'
|
|
||||||
import { writeTextFile } from '@tauri-apps/plugin-fs'
|
|
||||||
import { toast } from 'react-hot-toast'
|
|
||||||
|
|
||||||
const PERSIST_CODE_TOKEN = 'persistCode'
|
|
||||||
|
|
||||||
export class KclManager {
|
export class KclManager {
|
||||||
private _code = bracket
|
|
||||||
private _ast: Program = {
|
private _ast: Program = {
|
||||||
body: [],
|
body: [],
|
||||||
start: 0,
|
start: 0,
|
||||||
@ -44,7 +37,6 @@ export class KclManager {
|
|||||||
private _kclErrors: KCLError[] = []
|
private _kclErrors: KCLError[] = []
|
||||||
private _isExecuting = false
|
private _isExecuting = false
|
||||||
private _wasmInitFailed = true
|
private _wasmInitFailed = true
|
||||||
private _params: Params<string> = {}
|
|
||||||
|
|
||||||
engineCommandManager: EngineCommandManager
|
engineCommandManager: EngineCommandManager
|
||||||
private _defferer = deferExecution((code: string) => {
|
private _defferer = deferExecution((code: string) => {
|
||||||
@ -62,7 +54,6 @@ export class KclManager {
|
|||||||
}, 600)
|
}, 600)
|
||||||
|
|
||||||
private _isExecutingCallback: (arg: boolean) => void = () => {}
|
private _isExecutingCallback: (arg: boolean) => void = () => {}
|
||||||
private _codeCallBack: (arg: string) => void = () => {}
|
|
||||||
private _astCallBack: (arg: Program) => void = () => {}
|
private _astCallBack: (arg: Program) => void = () => {}
|
||||||
private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {}
|
private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {}
|
||||||
private _logsCallBack: (arg: string[]) => void = () => {}
|
private _logsCallBack: (arg: string[]) => void = () => {}
|
||||||
@ -78,28 +69,6 @@ export class KclManager {
|
|||||||
this._astCallBack(ast)
|
this._astCallBack(ast)
|
||||||
}
|
}
|
||||||
|
|
||||||
get code() {
|
|
||||||
return this._code
|
|
||||||
}
|
|
||||||
set code(code) {
|
|
||||||
this._code = code
|
|
||||||
this._codeCallBack(code)
|
|
||||||
if (isTauri()) {
|
|
||||||
setTimeout(() => {
|
|
||||||
// Wait one event loop to give a chance for params to be set
|
|
||||||
// Save the file to disk
|
|
||||||
this._params.id &&
|
|
||||||
writeTextFile(this._params.id, code).catch((err) => {
|
|
||||||
// TODO: add tracing per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254)
|
|
||||||
console.error('error saving file', err)
|
|
||||||
toast.error('Error saving file, please check file permissions')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
safteLSSetItem(PERSIST_CODE_TOKEN, code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get programMemory() {
|
get programMemory() {
|
||||||
return this._programMemory
|
return this._programMemory
|
||||||
}
|
}
|
||||||
@ -140,38 +109,15 @@ export class KclManager {
|
|||||||
this._wasmInitFailedCallback(wasmInitFailed)
|
this._wasmInitFailedCallback(wasmInitFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
setParams(params: Params<string>) {
|
|
||||||
this._params = params
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(engineCommandManager: EngineCommandManager) {
|
constructor(engineCommandManager: EngineCommandManager) {
|
||||||
this.engineCommandManager = engineCommandManager
|
this.engineCommandManager = engineCommandManager
|
||||||
|
|
||||||
if (isTauri()) {
|
|
||||||
this.code = ''
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const storedCode = safeLSGetItem(PERSIST_CODE_TOKEN) || ''
|
|
||||||
// TODO #819 remove zustand persistence logic in a few months
|
|
||||||
// short term migration, shouldn't make a difference for tauri app users
|
|
||||||
// anyway since that's filesystem based.
|
|
||||||
const zustandStore = JSON.parse(safeLSGetItem('store') || '{}')
|
|
||||||
if (storedCode === null && zustandStore?.state?.code) {
|
|
||||||
this.code = zustandStore.state.code
|
|
||||||
safteLSSetItem(PERSIST_CODE_TOKEN, this._code)
|
|
||||||
zustandStore.state.code = ''
|
|
||||||
safteLSSetItem('store', JSON.stringify(zustandStore))
|
|
||||||
} else if (storedCode === null) {
|
|
||||||
this.code = bracket
|
|
||||||
} else {
|
|
||||||
this.code = storedCode
|
|
||||||
}
|
|
||||||
this.ensureWasmInit().then(() => {
|
this.ensureWasmInit().then(() => {
|
||||||
this.ast = this.safeParse(this.code) || this.ast
|
this.ast = this.safeParse(codeManager.code) || this.ast
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
registerCallBacks({
|
registerCallBacks({
|
||||||
setCode,
|
|
||||||
setProgramMemory,
|
setProgramMemory,
|
||||||
setAst,
|
setAst,
|
||||||
setLogs,
|
setLogs,
|
||||||
@ -179,7 +125,6 @@ export class KclManager {
|
|||||||
setIsExecuting,
|
setIsExecuting,
|
||||||
setWasmInitFailed,
|
setWasmInitFailed,
|
||||||
}: {
|
}: {
|
||||||
setCode: (arg: string) => void
|
|
||||||
setProgramMemory: (arg: ProgramMemory) => void
|
setProgramMemory: (arg: ProgramMemory) => void
|
||||||
setAst: (arg: Program) => void
|
setAst: (arg: Program) => void
|
||||||
setLogs: (arg: string[]) => void
|
setLogs: (arg: string[]) => void
|
||||||
@ -187,7 +132,6 @@ export class KclManager {
|
|||||||
setIsExecuting: (arg: boolean) => void
|
setIsExecuting: (arg: boolean) => void
|
||||||
setWasmInitFailed: (arg: boolean) => void
|
setWasmInitFailed: (arg: boolean) => void
|
||||||
}) {
|
}) {
|
||||||
this._codeCallBack = setCode
|
|
||||||
this._programMemoryCallBack = setProgramMemory
|
this._programMemoryCallBack = setProgramMemory
|
||||||
this._astCallBack = setAst
|
this._astCallBack = setAst
|
||||||
this._logsCallBack = setLogs
|
this._logsCallBack = setLogs
|
||||||
@ -227,12 +171,11 @@ export class KclManager {
|
|||||||
|
|
||||||
private _cancelTokens: Map<number, boolean> = new Map()
|
private _cancelTokens: Map<number, boolean> = new Map()
|
||||||
|
|
||||||
async executeAst(
|
// This NEVER updates the code, if you want to update the code DO NOT add to
|
||||||
ast: Program = this._ast,
|
// this function, too many other things that don't want it exist.
|
||||||
updateCode = false,
|
// just call to codeManager from wherever you want in other files.
|
||||||
executionId?: number
|
async executeAst(ast: Program = this._ast, executionId?: number) {
|
||||||
) {
|
await this?.engineCommandManager?.waitForReady
|
||||||
if (!this.engineCommandManager.engineConnection?.isReady()) return
|
|
||||||
const currentExecutionId = executionId || Date.now()
|
const currentExecutionId = executionId || Date.now()
|
||||||
this._cancelTokens.set(currentExecutionId, false)
|
this._cancelTokens.set(currentExecutionId, false)
|
||||||
|
|
||||||
@ -253,9 +196,6 @@ export class KclManager {
|
|||||||
this.kclErrors = errors
|
this.kclErrors = errors
|
||||||
this.programMemory = programMemory
|
this.programMemory = programMemory
|
||||||
this.ast = { ...ast }
|
this.ast = { ...ast }
|
||||||
if (updateCode) {
|
|
||||||
this.code = recast(ast)
|
|
||||||
}
|
|
||||||
this._executeCallback()
|
this._executeCallback()
|
||||||
this.engineCommandManager.addCommandLog({
|
this.engineCommandManager.addCommandLog({
|
||||||
type: 'execution-done',
|
type: 'execution-done',
|
||||||
@ -263,22 +203,24 @@ export class KclManager {
|
|||||||
})
|
})
|
||||||
this._cancelTokens.delete(currentExecutionId)
|
this._cancelTokens.delete(currentExecutionId)
|
||||||
}
|
}
|
||||||
|
// NOTE: this always updates the code state and editor.
|
||||||
|
// DO NOT CALL THIS from codemirror ever.
|
||||||
async executeAstMock(
|
async executeAstMock(
|
||||||
ast: Program = this._ast,
|
ast: Program = this._ast,
|
||||||
{
|
{
|
||||||
updates,
|
updates,
|
||||||
}: {
|
}: {
|
||||||
updates: 'none' | 'code' | 'codeAndArtifactRanges'
|
updates: 'none' | 'artifactRanges'
|
||||||
} = { updates: 'none' }
|
} = { updates: 'none' }
|
||||||
) {
|
) {
|
||||||
await this.ensureWasmInit()
|
await this.ensureWasmInit()
|
||||||
const newCode = recast(ast)
|
const newCode = recast(ast)
|
||||||
const newAst = this.safeParse(newCode)
|
const newAst = this.safeParse(newCode)
|
||||||
if (!newAst) return
|
if (!newAst) return
|
||||||
|
codeManager.updateCodeStateEditor(newCode)
|
||||||
|
// Write the file to disk.
|
||||||
|
await codeManager.writeToFile()
|
||||||
await this?.engineCommandManager?.waitForReady
|
await this?.engineCommandManager?.waitForReady
|
||||||
if (updates !== 'none') {
|
|
||||||
this.setCode(recast(ast))
|
|
||||||
}
|
|
||||||
this._ast = { ...newAst }
|
this._ast = { ...newAst }
|
||||||
|
|
||||||
const { logs, errors, programMemory } = await executeAst({
|
const { logs, errors, programMemory } = await executeAst({
|
||||||
@ -289,7 +231,7 @@ export class KclManager {
|
|||||||
this._logs = logs
|
this._logs = logs
|
||||||
this._kclErrors = errors
|
this._kclErrors = errors
|
||||||
this._programMemory = programMemory
|
this._programMemory = programMemory
|
||||||
if (updates !== 'codeAndArtifactRanges') return
|
if (updates !== 'artifactRanges') return
|
||||||
Object.entries(this.engineCommandManager.artifactMap).forEach(
|
Object.entries(this.engineCommandManager.artifactMap).forEach(
|
||||||
([commandId, artifact]) => {
|
([commandId, artifact]) => {
|
||||||
if (!artifact.pathToNode) return
|
if (!artifact.pathToNode) return
|
||||||
@ -309,79 +251,32 @@ export class KclManager {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
executeCode = async (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?.engineCommandManager?.waitForReady
|
|
||||||
const result = await executeCode({
|
|
||||||
engineCommandManager: this.engineCommandManager,
|
|
||||||
code: code || this._code,
|
|
||||||
lastAst: this._ast,
|
|
||||||
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
|
|
||||||
const { logs, errors, programMemory, ast } = result
|
|
||||||
enterEditMode(programMemory, this.engineCommandManager)
|
|
||||||
this.logs = logs
|
|
||||||
this.kclErrors = errors
|
|
||||||
this.programMemory = programMemory
|
|
||||||
this.ast = ast
|
|
||||||
if (code) this.code = code
|
|
||||||
this._cancelTokens.delete(currentExecutionId)
|
|
||||||
}
|
|
||||||
cancelAllExecutions() {
|
cancelAllExecutions() {
|
||||||
this._cancelTokens.forEach((_, key) => {
|
this._cancelTokens.forEach((_, key) => {
|
||||||
this._cancelTokens.set(key, true)
|
this._cancelTokens.set(key, true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
setCode(code: string, shouldWriteFile = true) {
|
executeCode(force?: boolean) {
|
||||||
if (shouldWriteFile) {
|
// If we want to force it we don't want to defer it.
|
||||||
// use the normal code setter
|
if (!force) return this._defferer(codeManager.code)
|
||||||
this.code = code
|
return this.executeAst()
|
||||||
return
|
|
||||||
}
|
|
||||||
this._code = code
|
|
||||||
this._codeCallBack(code)
|
|
||||||
}
|
|
||||||
setCodeAndExecute(code: string, shouldWriteFile = true) {
|
|
||||||
this.setCode(code, shouldWriteFile)
|
|
||||||
if (code.trim()) {
|
|
||||||
this._defferer(code)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this._ast = {
|
|
||||||
body: [],
|
|
||||||
start: 0,
|
|
||||||
end: 0,
|
|
||||||
nonCodeMeta: {
|
|
||||||
nonCodeNodes: {},
|
|
||||||
start: [],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
this._programMemory = {
|
|
||||||
root: {},
|
|
||||||
return: null,
|
|
||||||
}
|
|
||||||
this.engineCommandManager.endSession()
|
|
||||||
}
|
}
|
||||||
format() {
|
format() {
|
||||||
const ast = this.safeParse(this.code)
|
const originalCode = codeManager.code
|
||||||
|
const ast = this.safeParse(originalCode)
|
||||||
if (!ast) return
|
if (!ast) return
|
||||||
this.code = recast(ast)
|
const code = recast(ast)
|
||||||
|
if (originalCode === code) return
|
||||||
|
|
||||||
|
// Update the code state and the editor.
|
||||||
|
codeManager.updateCodeStateEditor(code)
|
||||||
|
// Write back to the file system.
|
||||||
|
codeManager.writeToFile()
|
||||||
}
|
}
|
||||||
// There's overlapping responsibility between updateAst and executeAst.
|
// There's overlapping responsibility between updateAst and executeAst.
|
||||||
// updateAst was added as it was used a lot before xState migration so makes the port easier.
|
// updateAst was added as it was used a lot before xState migration so makes the port easier.
|
||||||
// but should probably have think about which of the function to keep
|
// but should probably have think about which of the function to keep
|
||||||
|
// This always updates the code state and editor and writes to the file system.
|
||||||
async updateAst(
|
async updateAst(
|
||||||
ast: Program,
|
ast: Program,
|
||||||
execute: boolean,
|
execute: boolean,
|
||||||
@ -414,12 +309,17 @@ export class KclManager {
|
|||||||
|
|
||||||
if (execute) {
|
if (execute) {
|
||||||
// Call execute on the set ast.
|
// Call execute on the set ast.
|
||||||
await this.executeAst(astWithUpdatedSource, true)
|
// Update the code state and editor.
|
||||||
|
codeManager.updateCodeStateEditor(newCode)
|
||||||
|
// Write the file to disk.
|
||||||
|
await codeManager.writeToFile()
|
||||||
|
await this.executeAst(astWithUpdatedSource)
|
||||||
} else {
|
} else {
|
||||||
// When we don't re-execute, we still want to update the program
|
// When we don't re-execute, we still want to update the program
|
||||||
// memory with the new ast. So we will hit the mock executor
|
// memory with the new ast. So we will hit the mock executor
|
||||||
// instead.
|
// instead..
|
||||||
await this.executeAstMock(astWithUpdatedSource, { updates: 'code' })
|
// Execute ast mock will update the code state and editor.
|
||||||
|
await this.executeAstMock(astWithUpdatedSource)
|
||||||
}
|
}
|
||||||
return returnVal
|
return returnVal
|
||||||
}
|
}
|
||||||
@ -443,16 +343,6 @@ export class KclManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function safeLSGetItem(key: string) {
|
|
||||||
if (typeof window === 'undefined') return null
|
|
||||||
return localStorage?.getItem(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
function safteLSSetItem(key: string, value: string) {
|
|
||||||
if (typeof window === 'undefined') return
|
|
||||||
localStorage?.setItem(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
function enterEditMode(
|
function enterEditMode(
|
||||||
programMemory: ProgramMemory,
|
programMemory: ProgramMemory,
|
||||||
engineCommandManager: EngineCommandManager
|
engineCommandManager: EngineCommandManager
|
||||||
|
115
src/lang/codeManager.ts
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// A little class for updating the code state when we need to and explicitly
|
||||||
|
// NOT updating the code state when we don't need to.
|
||||||
|
// This prevents re-renders of the codemirror editor, when typing.
|
||||||
|
import { bracket } from 'lib/exampleKcl'
|
||||||
|
import { isTauri } from 'lib/isTauri'
|
||||||
|
import { writeTextFile } from '@tauri-apps/plugin-fs'
|
||||||
|
import toast from 'react-hot-toast'
|
||||||
|
import { Params } from 'react-router-dom'
|
||||||
|
|
||||||
|
const PERSIST_CODE_TOKEN = 'persistCode'
|
||||||
|
|
||||||
|
export default class CodeManager {
|
||||||
|
private _code: string = bracket
|
||||||
|
private _updateState: (arg: string) => void = () => {}
|
||||||
|
private _updateEditor: (arg: string) => void = () => {}
|
||||||
|
private _params: Params<string> = {}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (isTauri()) {
|
||||||
|
this.code = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const storedCode = safeLSGetItem(PERSIST_CODE_TOKEN) || ''
|
||||||
|
// TODO #819 remove zustand persistence logic in a few months
|
||||||
|
// short term migration, shouldn't make a difference for tauri app users
|
||||||
|
// anyway since that's filesystem based.
|
||||||
|
const zustandStore = JSON.parse(safeLSGetItem('store') || '{}')
|
||||||
|
if (storedCode === null && zustandStore?.state?.code) {
|
||||||
|
this.code = zustandStore.state.code
|
||||||
|
zustandStore.state.code = ''
|
||||||
|
safeLSSetItem('store', JSON.stringify(zustandStore))
|
||||||
|
} else if (storedCode === null) {
|
||||||
|
this.code = bracket
|
||||||
|
} else {
|
||||||
|
this.code = storedCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set code(code: string) {
|
||||||
|
this._code = code
|
||||||
|
}
|
||||||
|
|
||||||
|
get code(): string {
|
||||||
|
return this._code
|
||||||
|
}
|
||||||
|
|
||||||
|
registerCallBacks({
|
||||||
|
setCode,
|
||||||
|
setEditorCode,
|
||||||
|
}: {
|
||||||
|
setCode: (arg: string) => void
|
||||||
|
setEditorCode: (arg: string) => void
|
||||||
|
}) {
|
||||||
|
this._updateState = setCode
|
||||||
|
this._updateEditor = setEditorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
setParams(params: Params<string>) {
|
||||||
|
this._params = params
|
||||||
|
}
|
||||||
|
|
||||||
|
// This updates the code state and calls the updateState function.
|
||||||
|
updateCodeState(code: string): void {
|
||||||
|
if (this._code !== code) {
|
||||||
|
this.code = code
|
||||||
|
this._updateState(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the code in the editor.
|
||||||
|
updateCodeEditor(code: string): void {
|
||||||
|
if (this._code !== code) {
|
||||||
|
this.code = code
|
||||||
|
this._updateEditor(code)
|
||||||
|
}
|
||||||
|
this._updateEditor(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the code, state, and the code the code mirror editor sees.
|
||||||
|
updateCodeStateEditor(code: string): void {
|
||||||
|
if (this._code !== code) {
|
||||||
|
this.code = code
|
||||||
|
this._updateState(code)
|
||||||
|
this._updateEditor(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async writeToFile() {
|
||||||
|
if (isTauri()) {
|
||||||
|
setTimeout(() => {
|
||||||
|
// Wait one event loop to give a chance for params to be set
|
||||||
|
// Save the file to disk
|
||||||
|
this._params.id &&
|
||||||
|
writeTextFile(this._params.id, this.code).catch((err) => {
|
||||||
|
// TODO: add tracing per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254)
|
||||||
|
console.error('error saving file', err)
|
||||||
|
toast.error('Error saving file, please check file permissions')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
safeLSSetItem(PERSIST_CODE_TOKEN, this.code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeLSGetItem(key: string) {
|
||||||
|
if (typeof window === 'undefined') return null
|
||||||
|
return localStorage?.getItem(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeLSSetItem(key: string, value: string) {
|
||||||
|
if (typeof window === 'undefined') return
|
||||||
|
localStorage?.setItem(key, value)
|
||||||
|
}
|
@ -932,7 +932,7 @@ export class EngineCommandManager {
|
|||||||
setIsStreamReady: (isStreamReady: boolean) => void
|
setIsStreamReady: (isStreamReady: boolean) => void
|
||||||
width: number
|
width: number
|
||||||
height: number
|
height: number
|
||||||
executeCode: (code?: string, force?: boolean) => void
|
executeCode: () => void
|
||||||
token?: string
|
token?: string
|
||||||
makeDefaultPlanes: () => Promise<DefaultPlanes>
|
makeDefaultPlanes: () => Promise<DefaultPlanes>
|
||||||
theme?: Themes
|
theme?: Themes
|
||||||
@ -1007,7 +1007,7 @@ export class EngineCommandManager {
|
|||||||
this.initPlanes().then(() => {
|
this.initPlanes().then(() => {
|
||||||
this.resolveReady()
|
this.resolveReady()
|
||||||
setIsStreamReady(true)
|
setIsStreamReady(true)
|
||||||
executeCode(undefined, true)
|
executeCode()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
|
@ -22,7 +22,7 @@ import {
|
|||||||
import makeUrlPathRelative from './makeUrlPathRelative'
|
import makeUrlPathRelative from './makeUrlPathRelative'
|
||||||
import { join, sep } from '@tauri-apps/api/path'
|
import { join, sep } from '@tauri-apps/api/path'
|
||||||
import { readTextFile, stat } from '@tauri-apps/plugin-fs'
|
import { readTextFile, stat } from '@tauri-apps/plugin-fs'
|
||||||
import { kclManager } from 'lib/singletons'
|
import { codeManager, kclManager } from 'lib/singletons'
|
||||||
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
||||||
import { invoke } from '@tauri-apps/api/core'
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
|
|
||||||
@ -100,7 +100,9 @@ export const fileLoader: LoaderFunction = async ({
|
|||||||
const children = await invoke<FileEntry[]>('read_dir_recursive', {
|
const children = await invoke<FileEntry[]>('read_dir_recursive', {
|
||||||
path: projectPath,
|
path: projectPath,
|
||||||
})
|
})
|
||||||
kclManager.setCodeAndExecute(code, false)
|
// Update both the state and the editor's code.
|
||||||
|
codeManager.updateCodeStateEditor(code)
|
||||||
|
kclManager.executeCode(true)
|
||||||
|
|
||||||
// Set the file system manager to the project path
|
// Set the file system manager to the project path
|
||||||
// So that WASM gets an updated path for operations
|
// So that WASM gets an updated path for operations
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import {
|
import {
|
||||||
|
codeManager,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
kclManager,
|
kclManager,
|
||||||
sceneEntitiesManager,
|
sceneEntitiesManager,
|
||||||
@ -142,7 +143,7 @@ export function getEventForSegmentSelection(
|
|||||||
// previous drags don't update ast for efficiency reasons
|
// previous drags don't update ast for efficiency reasons
|
||||||
// So we want to make sure we have and updated ast with
|
// So we want to make sure we have and updated ast with
|
||||||
// accurate source ranges
|
// accurate source ranges
|
||||||
const updatedAst = parse(kclManager.code)
|
const updatedAst = parse(codeManager.code)
|
||||||
const node = getNodeFromPath<CallExpression>(
|
const node = getNodeFromPath<CallExpression>(
|
||||||
updatedAst,
|
updatedAst,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
@ -192,7 +193,7 @@ export function handleSelectionBatch({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
codeMirrorSelection: EditorSelection.create(
|
codeMirrorSelection: EditorSelection.create(
|
||||||
[EditorSelection.cursor(kclManager.code.length)],
|
[EditorSelection.cursor(codeManager.code.length)],
|
||||||
0
|
0
|
||||||
),
|
),
|
||||||
engineEvents,
|
engineEvents,
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import { SceneEntities } from 'clientSideScene/sceneEntities'
|
import { SceneEntities } from 'clientSideScene/sceneEntities'
|
||||||
import { SceneInfra } from 'clientSideScene/sceneInfra'
|
import { SceneInfra } from 'clientSideScene/sceneInfra'
|
||||||
import { KclManager } from 'lang/KclSingleton'
|
import { KclManager } from 'lang/KclSingleton'
|
||||||
|
import CodeManager from 'lang/codeManager'
|
||||||
import { EngineCommandManager } from 'lang/std/engineConnection'
|
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||||
|
|
||||||
|
export const codeManager = new CodeManager()
|
||||||
|
|
||||||
export const engineCommandManager = new EngineCommandManager()
|
export const engineCommandManager = new EngineCommandManager()
|
||||||
|
|
||||||
export const kclManager = new KclManager(engineCommandManager)
|
export const kclManager = new KclManager(engineCommandManager)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { OnboardingButtons, useDismiss } from '.'
|
import { OnboardingButtons, useDismiss } from '.'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { bracket } from 'lib/exampleKcl'
|
import { bracket } from 'lib/exampleKcl'
|
||||||
import { kclManager } from 'lib/singletons'
|
import { codeManager, kclManager } from 'lib/singletons'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import { APP_NAME } from 'lib/constants'
|
import { APP_NAME } from 'lib/constants'
|
||||||
import { onboardingPaths } from './paths'
|
import { onboardingPaths } from './paths'
|
||||||
@ -11,12 +11,11 @@ export default function FutureWork() {
|
|||||||
const dismiss = useDismiss()
|
const dismiss = useDismiss()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// We do want to update both the state and editor here.
|
||||||
|
codeManager.updateCodeStateEditor(bracket)
|
||||||
if (kclManager.engineCommandManager.engineConnection?.isReady()) {
|
if (kclManager.engineCommandManager.engineConnection?.isReady()) {
|
||||||
// If the engine is ready, promptly execute the loaded code
|
// If the engine is ready, promptly execute the loaded code
|
||||||
kclManager.setCodeAndExecute(bracket)
|
kclManager.executeCode(true)
|
||||||
} else {
|
|
||||||
// Otherwise, just set the code and wait for the connection to complete
|
|
||||||
kclManager.setCode(bracket)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
send({ type: 'Cancel' }) // in case the user hit 'Next' while still in sketch mode
|
send({ type: 'Cancel' }) // in case the user hit 'Next' while still in sketch mode
|
||||||
|
@ -18,7 +18,7 @@ import { isTauri } from 'lib/isTauri'
|
|||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { paths } from 'lib/paths'
|
import { paths } from 'lib/paths'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { kclManager } from 'lib/singletons'
|
import { codeManager, kclManager } from 'lib/singletons'
|
||||||
import { join } from '@tauri-apps/api/path'
|
import { join } from '@tauri-apps/api/path'
|
||||||
import { APP_NAME, PROJECT_ENTRYPOINT } from 'lib/constants'
|
import { APP_NAME, PROJECT_ENTRYPOINT } from 'lib/constants'
|
||||||
|
|
||||||
@ -70,7 +70,9 @@ function OnboardingWithNewFile() {
|
|||||||
className="mt-6"
|
className="mt-6"
|
||||||
dismiss={dismiss}
|
dismiss={dismiss}
|
||||||
next={() => {
|
next={() => {
|
||||||
kclManager.setCodeAndExecute(bracket)
|
// We do want to update both the state and editor here.
|
||||||
|
codeManager.updateCodeStateEditor(bracket)
|
||||||
|
kclManager.executeCode(true)
|
||||||
next()
|
next()
|
||||||
}}
|
}}
|
||||||
nextText="Overwrite code and continue"
|
nextText="Overwrite code and continue"
|
||||||
@ -93,7 +95,7 @@ function OnboardingWithNewFile() {
|
|||||||
dismiss={dismiss}
|
dismiss={dismiss}
|
||||||
next={() => {
|
next={() => {
|
||||||
void createAndOpenNewProject()
|
void createAndOpenNewProject()
|
||||||
kclManager.setCode(bracket, false)
|
codeManager.updateCodeStateEditor(bracket)
|
||||||
dismiss()
|
dismiss()
|
||||||
}}
|
}}
|
||||||
nextText="Make a new project"
|
nextText="Make a new project"
|
||||||
@ -122,10 +124,11 @@ export default function Introduction() {
|
|||||||
: ''
|
: ''
|
||||||
const dismiss = useDismiss()
|
const dismiss = useDismiss()
|
||||||
const next = useNextClick(onboardingPaths.CAMERA)
|
const next = useNextClick(onboardingPaths.CAMERA)
|
||||||
const isStarterCode = kclManager.code === '' || kclManager.code === bracket
|
const currentCode = codeManager.code
|
||||||
|
const isStarterCode = currentCode === '' || currentCode === bracket
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (kclManager.code === '') kclManager.setCode(bracket)
|
if (codeManager.code === '') codeManager.updateCodeStateEditor(bracket)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return isStarterCode ? (
|
return isStarterCode ? (
|
||||||
|
@ -2,7 +2,7 @@ import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
|||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import { useStore } from 'useStore'
|
import { useStore } from 'useStore'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { kclManager } from 'lib/singletons'
|
import { codeManager, kclManager } from 'lib/singletons'
|
||||||
|
|
||||||
export default function Sketching() {
|
export default function Sketching() {
|
||||||
const buttonDownInStream = useStore((s) => s.buttonDownInStream)
|
const buttonDownInStream = useStore((s) => s.buttonDownInStream)
|
||||||
@ -10,12 +10,11 @@ export default function Sketching() {
|
|||||||
const next = useNextClick(onboardingPaths.FUTURE_WORK)
|
const next = useNextClick(onboardingPaths.FUTURE_WORK)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// We do want to update both the state and editor here.
|
||||||
|
codeManager.updateCodeStateEditor('')
|
||||||
if (kclManager.engineCommandManager.engineConnection?.isReady()) {
|
if (kclManager.engineCommandManager.engineConnection?.isReady()) {
|
||||||
// If the engine is ready, promptly execute the loaded code
|
// If the engine is ready, promptly execute the loaded code
|
||||||
kclManager.setCodeAndExecute('')
|
kclManager.executeCode(true)
|
||||||
} else {
|
|
||||||
// Otherwise, just set the code and wait for the connection to complete
|
|
||||||
kclManager.setCode('')
|
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ import { create } from 'zustand'
|
|||||||
import { persist } from 'zustand/middleware'
|
import { persist } from 'zustand/middleware'
|
||||||
import { addLineHighlight, EditorView } from './editor/highlightextension'
|
import { addLineHighlight, EditorView } from './editor/highlightextension'
|
||||||
import {
|
import {
|
||||||
parse,
|
|
||||||
Program,
|
Program,
|
||||||
_executor,
|
_executor,
|
||||||
ProgramMemory,
|
ProgramMemory,
|
||||||
@ -175,74 +174,6 @@ export const useStore = create<StoreState>()(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
export async function executeCode({
|
|
||||||
engineCommandManager,
|
|
||||||
code,
|
|
||||||
lastAst,
|
|
||||||
force,
|
|
||||||
}: {
|
|
||||||
code: string
|
|
||||||
lastAst: Program
|
|
||||||
engineCommandManager: EngineCommandManager
|
|
||||||
force?: boolean
|
|
||||||
}): Promise<
|
|
||||||
| {
|
|
||||||
logs: string[]
|
|
||||||
errors: KCLError[]
|
|
||||||
programMemory: ProgramMemory
|
|
||||||
ast: Program
|
|
||||||
isChange: true
|
|
||||||
}
|
|
||||||
| { isChange: false }
|
|
||||||
> {
|
|
||||||
let ast: Program
|
|
||||||
try {
|
|
||||||
ast = parse(code)
|
|
||||||
} catch (e) {
|
|
||||||
let errors: KCLError[] = []
|
|
||||||
let logs: string[] = [JSON.stringify(e)]
|
|
||||||
if (e instanceof KCLError) {
|
|
||||||
errors = [e]
|
|
||||||
logs = []
|
|
||||||
if (e.msg === 'file is empty') engineCommandManager.endSession()
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
isChange: true,
|
|
||||||
logs,
|
|
||||||
errors,
|
|
||||||
programMemory: {
|
|
||||||
root: {},
|
|
||||||
return: null,
|
|
||||||
},
|
|
||||||
ast: {
|
|
||||||
start: 0,
|
|
||||||
end: 0,
|
|
||||||
body: [],
|
|
||||||
nonCodeMeta: {
|
|
||||||
nonCodeNodes: {},
|
|
||||||
start: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check if the ast we have is equal to the ast in the storage.
|
|
||||||
// If it is, we don't need to update the ast.
|
|
||||||
if (JSON.stringify(ast) === JSON.stringify(lastAst) && !force)
|
|
||||||
return { isChange: false }
|
|
||||||
|
|
||||||
const { logs, errors, programMemory } = await executeAst({
|
|
||||||
ast,
|
|
||||||
engineCommandManager,
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
ast,
|
|
||||||
logs,
|
|
||||||
errors,
|
|
||||||
programMemory,
|
|
||||||
isChange: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function executeAst({
|
export async function executeAst({
|
||||||
ast,
|
ast,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
@ -268,10 +199,6 @@ export async function executeAst({
|
|||||||
: _executor(ast, programMemoryInit(), engineCommandManager, false))
|
: _executor(ast, programMemoryInit(), engineCommandManager, false))
|
||||||
|
|
||||||
await engineCommandManager.waitForAllCommands()
|
await engineCommandManager.waitForAllCommands()
|
||||||
engineCommandManager.addCommandLog({
|
|
||||||
type: 'execution-done',
|
|
||||||
data: null,
|
|
||||||
})
|
|
||||||
return {
|
return {
|
||||||
logs: [],
|
logs: [],
|
||||||
errors: [],
|
errors: [],
|
||||||
|