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>
This commit is contained in:
Jess Frazelle
2024-04-17 20:18:07 -07:00
committed by GitHub
parent 168fed038d
commit 3f8c4e7b5a
32 changed files with 314 additions and 370 deletions

View File

@ -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()

View File

@ -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 }) => {

View File

@ -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,

View File

@ -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)

View File

@ -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)

View File

@ -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)}

View File

@ -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),
}, },

View File

@ -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.

View File

@ -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,

View File

@ -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,

View File

@ -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
View 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)
}

View File

@ -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: () => {

View File

@ -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

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -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 ? (

View File

@ -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('')
} }
}, []) }, [])

View File

@ -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: [],