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
|
||||
await expect(page.locator('.cm-completionLabel')).toHaveCount(3)
|
||||
await page.getByText('startSketchOn').click()
|
||||
await page.keyboard.type("'XY'")
|
||||
await page.keyboard.type("'XZ'")
|
||||
await page.keyboard.press('Tab')
|
||||
await page.keyboard.press('Enter')
|
||||
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('Tab')
|
||||
await page.keyboard.type('12')
|
||||
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-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([3.14, 3.14], %)
|
||||
.toHaveText(`const part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([3.14, 12], %)
|
||||
|> xLine(5, %) // lin`)
|
||||
})
|
||||
|
||||
@ -1239,7 +1240,7 @@ fn yohey = (pos) => {
|
||||
},
|
||||
selectionsSnippets
|
||||
)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 1000 })
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
|
@ -336,10 +336,7 @@ const part001 = startSketchOn('-XZ')
|
||||
}
|
||||
})
|
||||
|
||||
test('extrude on each default plane should be stable', async ({
|
||||
page,
|
||||
context,
|
||||
}) => {
|
||||
const extrudeDefaultPlane = async (context: any, page: any, plane: string) => {
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'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], %)
|
||||
|> line([6.60, -0.20], %)
|
||||
|> line([2.80, 5.00], %)
|
||||
@ -366,9 +363,11 @@ test('extrude on each default plane should be stable', async ({
|
||||
|> close(%)
|
||||
|> extrude(10.00, %)
|
||||
`
|
||||
await context.addInitScript(async (code) => {
|
||||
await page.addInitScript(async (code: string) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
}, makeCode('XY'))
|
||||
})
|
||||
|
||||
const u = getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
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.clearAndCloseDebugPanel()
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
const runSnapshotsForOtherPlanes = async (plane = 'XY') => {
|
||||
// clear code
|
||||
await u.removeCurrentCode()
|
||||
// add makeCode('XZ')
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.doAndWaitForImageDiff(
|
||||
() => page.locator('.cm-content').fill(makeCode(plane)),
|
||||
() => page.locator('.cm-content').fill(code),
|
||||
200
|
||||
)
|
||||
// wait for execution done
|
||||
@ -398,14 +394,30 @@ test('extrude on each default plane should be stable', async ({
|
||||
})
|
||||
await u.openKclCodePanel()
|
||||
}
|
||||
await runSnapshotsForOtherPlanes('XY')
|
||||
await runSnapshotsForOtherPlanes('-XY')
|
||||
test.describe('extrude on default planes should be stable', () => {
|
||||
test('XY', async ({ page, context }) => {
|
||||
await extrudeDefaultPlane(context, page, 'XY')
|
||||
})
|
||||
|
||||
await runSnapshotsForOtherPlanes('XZ')
|
||||
await runSnapshotsForOtherPlanes('-XZ')
|
||||
test('XZ', async ({ page, context }) => {
|
||||
await extrudeDefaultPlane(context, page, 'XZ')
|
||||
})
|
||||
|
||||
await runSnapshotsForOtherPlanes('YZ')
|
||||
await runSnapshotsForOtherPlanes('-YZ')
|
||||
test('YZ', async ({ page, context }) => {
|
||||
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 }) => {
|
||||
|
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,
|
||||
VariableDeclarator,
|
||||
} 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 { executeAst, useStore } from 'useStore'
|
||||
import {
|
||||
@ -542,7 +547,7 @@ export class SceneEntities {
|
||||
return
|
||||
}
|
||||
|
||||
await kclManager.executeAstMock(modifiedAst, { updates: 'code' })
|
||||
await kclManager.executeAstMock(modifiedAst)
|
||||
this.setUpDraftSegment(
|
||||
sketchPathToNode,
|
||||
forward,
|
||||
@ -637,9 +642,7 @@ export class SceneEntities {
|
||||
spliceBetween: true,
|
||||
})
|
||||
addingNewSegmentStatus = 'pending'
|
||||
await kclManager.executeAstMock(mod.modifiedAst, {
|
||||
updates: 'code',
|
||||
})
|
||||
await kclManager.executeAstMock(mod.modifiedAst)
|
||||
await this.tearDownSketch({ removeAxis: false })
|
||||
this.setupSketch({
|
||||
sketchPathToNode: pathToNode,
|
||||
@ -784,9 +787,9 @@ export class SceneEntities {
|
||||
;(async () => {
|
||||
const code = recast(modifiedAst)
|
||||
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
|
||||
kclManager.setCode(code, false)
|
||||
codeManager.updateCodeStateEditor(code)
|
||||
const { programMemory } = await executeAst({
|
||||
ast: truncatedAst,
|
||||
useFakeExecutor: true,
|
||||
|
@ -13,7 +13,7 @@ import styles from './FileTree.module.css'
|
||||
import { sortProject } from 'lib/tauriFS'
|
||||
import { FILE_EXT } from 'lib/constants'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { codeManager, kclManager } from 'lib/singletons'
|
||||
import { useDocumentHasFocus } from 'hooks/useDocumentHasFocus'
|
||||
import { useLspContext } from './LspProvider'
|
||||
|
||||
@ -171,10 +171,13 @@ const FileTreeItem = ({
|
||||
|
||||
if (fileOrDir.name?.endsWith(FILE_EXT) === false && project?.path) {
|
||||
// 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` +
|
||||
kclManager.code
|
||||
codeManager.code
|
||||
)
|
||||
codeManager.writeToFile()
|
||||
kclManager.executeCode(true)
|
||||
} else {
|
||||
// Let the lsp servers know we closed a file.
|
||||
onFileClose(currentFile?.path || null, project?.path || null)
|
||||
|
@ -12,8 +12,12 @@ import { SetSelections, modelingMachine } from 'machines/modelingMachine'
|
||||
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||
import { kclManager, sceneInfra, engineCommandManager } from 'lib/singletons'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import {
|
||||
kclManager,
|
||||
sceneInfra,
|
||||
engineCommandManager,
|
||||
codeManager,
|
||||
} from 'lib/singletons'
|
||||
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
|
||||
import {
|
||||
angleBetweenInfo,
|
||||
@ -38,7 +42,7 @@ import {
|
||||
getSketchQuaternion,
|
||||
} from 'clientSideScene/sceneEntities'
|
||||
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 { TEST } from 'env'
|
||||
import { exportFromEngine } from 'lib/exportFromEngine'
|
||||
@ -74,7 +78,6 @@ export const ModelingMachineProvider = ({
|
||||
},
|
||||
},
|
||||
} = useSettingsAuthContext()
|
||||
const { code } = useKclContext()
|
||||
const token = auth?.context?.token
|
||||
const streamRef = useRef<HTMLDivElement>(null)
|
||||
useSetupEngineManager(streamRef, token, theme.current)
|
||||
@ -117,11 +120,7 @@ export const ModelingMachineProvider = ({
|
||||
{
|
||||
actions: {
|
||||
'sketch exit execute': () => {
|
||||
try {
|
||||
kclManager.executeAst(parse(kclManager.code))
|
||||
} catch (e) {
|
||||
kclManager.executeAst()
|
||||
}
|
||||
kclManager.executeCode(true)
|
||||
},
|
||||
'Set mouse state': assign({
|
||||
mouseState: (_, event) => event.data,
|
||||
@ -270,7 +269,8 @@ export const ModelingMachineProvider = ({
|
||||
if (selectionRanges.codeBasedSelections.length < 1) return false
|
||||
const isPipe = isSketchPipe(selectionRanges)
|
||||
|
||||
if (isSelectionLastLine(selectionRanges, code)) return true
|
||||
if (isSelectionLastLine(selectionRanges, codeManager.code))
|
||||
return true
|
||||
if (!isPipe) return false
|
||||
|
||||
return canExtrudeSelection(selectionRanges)
|
||||
@ -294,7 +294,7 @@ export const ModelingMachineProvider = ({
|
||||
const varDecIndex = sketchDetails.sketchPathToNode[1][0]
|
||||
// remove body item at varDecIndex
|
||||
newAst.body = newAst.body.filter((_, i) => i !== varDecIndex)
|
||||
await kclManager.executeAstMock(newAst, { updates: 'code' })
|
||||
await kclManager.executeAstMock(newAst)
|
||||
sceneInfra.setCallbacks({
|
||||
onClick: () => {},
|
||||
onDrag: () => {},
|
||||
@ -309,7 +309,7 @@ export const ModelingMachineProvider = ({
|
||||
kclManager.programMemory,
|
||||
data.cap
|
||||
)
|
||||
await kclManager.executeAstMock(modifiedAst, { updates: 'code' })
|
||||
await kclManager.executeAstMock(modifiedAst)
|
||||
|
||||
const forward = new Vector3(...data.zAxis)
|
||||
const up = new Vector3(...data.yAxis)
|
||||
|
@ -57,10 +57,6 @@ import {
|
||||
completionKeymap,
|
||||
hasNextSnippetField,
|
||||
} from '@codemirror/autocomplete'
|
||||
import {
|
||||
NetworkHealthState,
|
||||
useNetworkStatus,
|
||||
} from 'components/NetworkHealthIndicator'
|
||||
import { kclErrorsToDiagnostics } from 'lang/errors'
|
||||
|
||||
export const editorShortcutMeta = {
|
||||
@ -86,16 +82,14 @@ export const KclEditorPane = () => {
|
||||
setEditorView: s.setEditorView,
|
||||
isShiftDown: s.isShiftDown,
|
||||
}))
|
||||
const { code, errors } = useKclContext()
|
||||
const { editorCode, errors } = useKclContext()
|
||||
const lastEvent = useRef({ event: '', time: Date.now() })
|
||||
const { copilotLSP, kclLSP } = useLspContext()
|
||||
const { overallState } = useNetworkStatus()
|
||||
const isNetworkOkay = overallState === NetworkHealthState.Ok
|
||||
const navigate = useNavigate()
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return
|
||||
const onlineCallback = () => kclManager.setCodeAndExecute(kclManager.code)
|
||||
const onlineCallback = () => kclManager.executeCode(true)
|
||||
window.addEventListener('online', onlineCallback)
|
||||
return () => window.removeEventListener('online', onlineCallback)
|
||||
}, [])
|
||||
@ -126,19 +120,6 @@ export const KclEditorPane = () => {
|
||||
const { enable: convertEnabled, handleClick: convertCallback } =
|
||||
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 onUpdate = (viewUpdate: ViewUpdate) => {
|
||||
// If we are just fucking around in a snippet, return early and don't
|
||||
@ -307,7 +288,13 @@ export const KclEditorPane = () => {
|
||||
}
|
||||
|
||||
return extensions
|
||||
}, [kclLSP, textWrapping.current, cursorBlinking.current, convertCallback])
|
||||
}, [
|
||||
kclLSP,
|
||||
copilotLSP,
|
||||
textWrapping.current,
|
||||
cursorBlinking.current,
|
||||
convertCallback,
|
||||
])
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -315,9 +302,8 @@ export const KclEditorPane = () => {
|
||||
className={'absolute inset-0 ' + (cursorBlinking.current ? 'blink' : '')}
|
||||
>
|
||||
<ReactCodeMirror
|
||||
value={code}
|
||||
value={editorCode}
|
||||
extensions={editorExtensions}
|
||||
onChange={onChange}
|
||||
onUpdate={onUpdate}
|
||||
theme={theme}
|
||||
onCreateEditor={(_editorView) => setEditorView(_editorView)}
|
||||
|
@ -138,7 +138,7 @@ export const SettingsAuthProviderBase = ({
|
||||
id: `${event.type}.success`,
|
||||
})
|
||||
},
|
||||
'Execute AST': () => kclManager.executeAst(),
|
||||
'Execute AST': () => kclManager.executeCode(true),
|
||||
persistSettings: (context) =>
|
||||
saveSettings(context, loadedProject?.project?.path),
|
||||
},
|
||||
|
@ -1,8 +1,4 @@
|
||||
import {
|
||||
completeFromList,
|
||||
hasNextSnippetField,
|
||||
snippetCompletion,
|
||||
} from '@codemirror/autocomplete'
|
||||
import { completeFromList, snippetCompletion } from '@codemirror/autocomplete'
|
||||
import { setDiagnostics } from '@codemirror/lint'
|
||||
import { Facet } from '@codemirror/state'
|
||||
import { EditorView, Tooltip } from '@codemirror/view'
|
||||
@ -25,7 +21,7 @@ import { LanguageServerClient } from 'editor/plugins/lsp'
|
||||
import { Marked } from '@ts-stack/markdown'
|
||||
import { posToOffset } from 'editor/plugins/lsp/util'
|
||||
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 { UpdateUnitsResponse } from 'wasm-lib/kcl/bindings/UpdateUnitsResponse'
|
||||
import { UpdateCanExecuteResponse } from 'wasm-lib/kcl/bindings/UpdateCanExecuteResponse'
|
||||
@ -53,6 +49,7 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
private foldingRanges: LSP.FoldingRange[] | null = null
|
||||
private _defferer = deferExecution((code: string) => {
|
||||
try {
|
||||
// Update the state (not the editor) with the new code.
|
||||
this.client.textDocumentDidChange({
|
||||
textDocument: {
|
||||
uri: this.documentUri,
|
||||
@ -83,21 +80,16 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
})
|
||||
}
|
||||
|
||||
update({ docChanged, state }: ViewUpdate) {
|
||||
update({ docChanged }: ViewUpdate) {
|
||||
if (!docChanged) return
|
||||
|
||||
// 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.
|
||||
// We only care about this for the 'kcl' plugin.
|
||||
if (this.client.name === 'kcl' && hasNextSnippetField(state)) {
|
||||
return
|
||||
}
|
||||
const newCode = this.view.state.doc.toString()
|
||||
|
||||
codeManager.code = newCode
|
||||
codeManager.writeToFile()
|
||||
kclManager.executeCode()
|
||||
this.sendChange({
|
||||
documentText: this.view.state.doc.toString(),
|
||||
documentText: newCode,
|
||||
})
|
||||
}
|
||||
|
||||
@ -122,17 +114,6 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
async sendChange({ documentText }: { documentText: string }) {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -372,17 +353,19 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
return completeFromList(options)(context)
|
||||
}
|
||||
|
||||
processNotification(notification: LSP.NotificationMessage) {
|
||||
async processNotification(notification: LSP.NotificationMessage) {
|
||||
try {
|
||||
switch (notification.method) {
|
||||
case 'textDocument/publishDiagnostics':
|
||||
const params = notification.params as PublishDiagnosticsParams
|
||||
this.processDiagnostics(params)
|
||||
// Update the kcl errors pane.
|
||||
/*kclManager.kclErrors = lspDiagnosticsToKclErrors(
|
||||
/*if (!kclManager.isExecuting) {
|
||||
kclManager.kclErrors = lspDiagnosticsToKclErrors(
|
||||
this.view.state.doc,
|
||||
params.diagnostics
|
||||
)*/
|
||||
)
|
||||
}*/
|
||||
break
|
||||
case 'window/logMessage':
|
||||
console.log(
|
||||
@ -402,9 +385,17 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
// The server has updated the AST, we should update elsewhere.
|
||||
let updatedAst = notification.params as Program
|
||||
console.log('[lsp]: Updated AST', updatedAst)
|
||||
// Since we aren't using the lsp server for executing the program
|
||||
// we don't update the ast here.
|
||||
//kclManager.ast = updatedAst
|
||||
// Update the ast when we are not already executing.
|
||||
/* if (!kclManager.isExecuting) {
|
||||
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.
|
||||
// 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 { deferExecution } from 'lib/utils'
|
||||
import { Themes } from 'lib/theme'
|
||||
import { makeDefaultPlanes, parse } from 'lang/wasm'
|
||||
import { makeDefaultPlanes } from 'lang/wasm'
|
||||
|
||||
export function useSetupEngineManager(
|
||||
streamRef: React.RefObject<HTMLDivElement>,
|
||||
@ -40,9 +40,10 @@ export function useSetupEngineManager(
|
||||
setIsStreamReady,
|
||||
width: quadWidth,
|
||||
height: quadHeight,
|
||||
executeCode: (code?: string) => {
|
||||
const _ast = parse(code || kclManager.code)
|
||||
return kclManager.executeAst(_ast, true)
|
||||
executeCode: () => {
|
||||
// We only want to execute the code here that we already have set.
|
||||
// Nothing else.
|
||||
return kclManager.executeCode(true)
|
||||
},
|
||||
token,
|
||||
theme,
|
||||
|
@ -3,10 +3,11 @@ import { createContext, useContext, useEffect, useState } from 'react'
|
||||
import { type IndexLoaderData } from 'lib/types'
|
||||
import { useLoaderData } from 'react-router-dom'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { codeManager, kclManager } from 'lib/singletons'
|
||||
|
||||
const KclContext = createContext({
|
||||
code: kclManager?.code || '',
|
||||
code: codeManager?.code || '',
|
||||
editorCode: codeManager?.code || '',
|
||||
programMemory: kclManager?.programMemory,
|
||||
ast: kclManager?.ast,
|
||||
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
|
||||
// Because useLoaderData assumes we are on within it's context.
|
||||
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 [ast, setAst] = useState(kclManager.ast)
|
||||
const [isExecuting, setIsExecuting] = useState(false)
|
||||
@ -36,8 +40,11 @@ export function KclContextProvider({
|
||||
const [wasmInitFailed, setWasmInitFailed] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
kclManager.registerCallBacks({
|
||||
codeManager.registerCallBacks({
|
||||
setCode,
|
||||
setEditorCode,
|
||||
})
|
||||
kclManager.registerCallBacks({
|
||||
setProgramMemory,
|
||||
setAst,
|
||||
setLogs,
|
||||
@ -49,12 +56,13 @@ export function KclContextProvider({
|
||||
|
||||
const params = useParams()
|
||||
useEffect(() => {
|
||||
kclManager.setParams(params)
|
||||
codeManager.setParams(params)
|
||||
}, [params])
|
||||
return (
|
||||
<KclContext.Provider
|
||||
value={{
|
||||
code,
|
||||
editorCode,
|
||||
programMemory,
|
||||
ast,
|
||||
isExecuting,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { executeAst, executeCode } from 'useStore'
|
||||
import { executeAst } from 'useStore'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { KCLError } from './errors'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
@ -16,17 +16,10 @@ import {
|
||||
SketchGroup,
|
||||
ExtrudeGroup,
|
||||
} from 'lang/wasm'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
import { getNodeFromPath } from './queryAst'
|
||||
import { Params } from 'react-router-dom'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { writeTextFile } from '@tauri-apps/plugin-fs'
|
||||
import { toast } from 'react-hot-toast'
|
||||
|
||||
const PERSIST_CODE_TOKEN = 'persistCode'
|
||||
import { codeManager } from 'lib/singletons'
|
||||
|
||||
export class KclManager {
|
||||
private _code = bracket
|
||||
private _ast: Program = {
|
||||
body: [],
|
||||
start: 0,
|
||||
@ -44,7 +37,6 @@ export class KclManager {
|
||||
private _kclErrors: KCLError[] = []
|
||||
private _isExecuting = false
|
||||
private _wasmInitFailed = true
|
||||
private _params: Params<string> = {}
|
||||
|
||||
engineCommandManager: EngineCommandManager
|
||||
private _defferer = deferExecution((code: string) => {
|
||||
@ -62,7 +54,6 @@ export class KclManager {
|
||||
}, 600)
|
||||
|
||||
private _isExecutingCallback: (arg: boolean) => void = () => {}
|
||||
private _codeCallBack: (arg: string) => void = () => {}
|
||||
private _astCallBack: (arg: Program) => void = () => {}
|
||||
private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {}
|
||||
private _logsCallBack: (arg: string[]) => void = () => {}
|
||||
@ -78,28 +69,6 @@ export class KclManager {
|
||||
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() {
|
||||
return this._programMemory
|
||||
}
|
||||
@ -140,38 +109,15 @@ export class KclManager {
|
||||
this._wasmInitFailedCallback(wasmInitFailed)
|
||||
}
|
||||
|
||||
setParams(params: Params<string>) {
|
||||
this._params = params
|
||||
}
|
||||
|
||||
constructor(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.ast = this.safeParse(this.code) || this.ast
|
||||
this.ast = this.safeParse(codeManager.code) || this.ast
|
||||
})
|
||||
}
|
||||
|
||||
registerCallBacks({
|
||||
setCode,
|
||||
setProgramMemory,
|
||||
setAst,
|
||||
setLogs,
|
||||
@ -179,7 +125,6 @@ export class KclManager {
|
||||
setIsExecuting,
|
||||
setWasmInitFailed,
|
||||
}: {
|
||||
setCode: (arg: string) => void
|
||||
setProgramMemory: (arg: ProgramMemory) => void
|
||||
setAst: (arg: Program) => void
|
||||
setLogs: (arg: string[]) => void
|
||||
@ -187,7 +132,6 @@ export class KclManager {
|
||||
setIsExecuting: (arg: boolean) => void
|
||||
setWasmInitFailed: (arg: boolean) => void
|
||||
}) {
|
||||
this._codeCallBack = setCode
|
||||
this._programMemoryCallBack = setProgramMemory
|
||||
this._astCallBack = setAst
|
||||
this._logsCallBack = setLogs
|
||||
@ -227,12 +171,11 @@ export class KclManager {
|
||||
|
||||
private _cancelTokens: Map<number, boolean> = new Map()
|
||||
|
||||
async executeAst(
|
||||
ast: Program = this._ast,
|
||||
updateCode = false,
|
||||
executionId?: number
|
||||
) {
|
||||
if (!this.engineCommandManager.engineConnection?.isReady()) return
|
||||
// This NEVER updates the code, if you want to update the code DO NOT add to
|
||||
// this function, too many other things that don't want it exist.
|
||||
// just call to codeManager from wherever you want in other files.
|
||||
async executeAst(ast: Program = this._ast, executionId?: number) {
|
||||
await this?.engineCommandManager?.waitForReady
|
||||
const currentExecutionId = executionId || Date.now()
|
||||
this._cancelTokens.set(currentExecutionId, false)
|
||||
|
||||
@ -253,9 +196,6 @@ export class KclManager {
|
||||
this.kclErrors = errors
|
||||
this.programMemory = programMemory
|
||||
this.ast = { ...ast }
|
||||
if (updateCode) {
|
||||
this.code = recast(ast)
|
||||
}
|
||||
this._executeCallback()
|
||||
this.engineCommandManager.addCommandLog({
|
||||
type: 'execution-done',
|
||||
@ -263,22 +203,24 @@ export class KclManager {
|
||||
})
|
||||
this._cancelTokens.delete(currentExecutionId)
|
||||
}
|
||||
// NOTE: this always updates the code state and editor.
|
||||
// DO NOT CALL THIS from codemirror ever.
|
||||
async executeAstMock(
|
||||
ast: Program = this._ast,
|
||||
{
|
||||
updates,
|
||||
}: {
|
||||
updates: 'none' | 'code' | 'codeAndArtifactRanges'
|
||||
updates: 'none' | 'artifactRanges'
|
||||
} = { updates: 'none' }
|
||||
) {
|
||||
await this.ensureWasmInit()
|
||||
const newCode = recast(ast)
|
||||
const newAst = this.safeParse(newCode)
|
||||
if (!newAst) return
|
||||
codeManager.updateCodeStateEditor(newCode)
|
||||
// Write the file to disk.
|
||||
await codeManager.writeToFile()
|
||||
await this?.engineCommandManager?.waitForReady
|
||||
if (updates !== 'none') {
|
||||
this.setCode(recast(ast))
|
||||
}
|
||||
this._ast = { ...newAst }
|
||||
|
||||
const { logs, errors, programMemory } = await executeAst({
|
||||
@ -289,7 +231,7 @@ export class KclManager {
|
||||
this._logs = logs
|
||||
this._kclErrors = errors
|
||||
this._programMemory = programMemory
|
||||
if (updates !== 'codeAndArtifactRanges') return
|
||||
if (updates !== 'artifactRanges') return
|
||||
Object.entries(this.engineCommandManager.artifactMap).forEach(
|
||||
([commandId, artifact]) => {
|
||||
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() {
|
||||
this._cancelTokens.forEach((_, key) => {
|
||||
this._cancelTokens.set(key, true)
|
||||
})
|
||||
}
|
||||
setCode(code: string, shouldWriteFile = true) {
|
||||
if (shouldWriteFile) {
|
||||
// use the normal code setter
|
||||
this.code = code
|
||||
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()
|
||||
executeCode(force?: boolean) {
|
||||
// If we want to force it we don't want to defer it.
|
||||
if (!force) return this._defferer(codeManager.code)
|
||||
return this.executeAst()
|
||||
}
|
||||
format() {
|
||||
const ast = this.safeParse(this.code)
|
||||
const originalCode = codeManager.code
|
||||
const ast = this.safeParse(originalCode)
|
||||
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.
|
||||
// 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
|
||||
// This always updates the code state and editor and writes to the file system.
|
||||
async updateAst(
|
||||
ast: Program,
|
||||
execute: boolean,
|
||||
@ -414,12 +309,17 @@ export class KclManager {
|
||||
|
||||
if (execute) {
|
||||
// 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 {
|
||||
// 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
|
||||
// instead.
|
||||
await this.executeAstMock(astWithUpdatedSource, { updates: 'code' })
|
||||
// instead..
|
||||
// Execute ast mock will update the code state and editor.
|
||||
await this.executeAstMock(astWithUpdatedSource)
|
||||
}
|
||||
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(
|
||||
programMemory: ProgramMemory,
|
||||
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
|
||||
width: number
|
||||
height: number
|
||||
executeCode: (code?: string, force?: boolean) => void
|
||||
executeCode: () => void
|
||||
token?: string
|
||||
makeDefaultPlanes: () => Promise<DefaultPlanes>
|
||||
theme?: Themes
|
||||
@ -1007,7 +1007,7 @@ export class EngineCommandManager {
|
||||
this.initPlanes().then(() => {
|
||||
this.resolveReady()
|
||||
setIsStreamReady(true)
|
||||
executeCode(undefined, true)
|
||||
executeCode()
|
||||
})
|
||||
},
|
||||
onClose: () => {
|
||||
|
@ -22,7 +22,7 @@ import {
|
||||
import makeUrlPathRelative from './makeUrlPathRelative'
|
||||
import { join, sep } from '@tauri-apps/api/path'
|
||||
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 { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
@ -100,7 +100,9 @@ export const fileLoader: LoaderFunction = async ({
|
||||
const children = await invoke<FileEntry[]>('read_dir_recursive', {
|
||||
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
|
||||
// So that WASM gets an updated path for operations
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Models } from '@kittycad/lib'
|
||||
import {
|
||||
codeManager,
|
||||
engineCommandManager,
|
||||
kclManager,
|
||||
sceneEntitiesManager,
|
||||
@ -142,7 +143,7 @@ export function getEventForSegmentSelection(
|
||||
// previous drags don't update ast for efficiency reasons
|
||||
// So we want to make sure we have and updated ast with
|
||||
// accurate source ranges
|
||||
const updatedAst = parse(kclManager.code)
|
||||
const updatedAst = parse(codeManager.code)
|
||||
const node = getNodeFromPath<CallExpression>(
|
||||
updatedAst,
|
||||
pathToNode,
|
||||
@ -192,7 +193,7 @@ export function handleSelectionBatch({
|
||||
|
||||
return {
|
||||
codeMirrorSelection: EditorSelection.create(
|
||||
[EditorSelection.cursor(kclManager.code.length)],
|
||||
[EditorSelection.cursor(codeManager.code.length)],
|
||||
0
|
||||
),
|
||||
engineEvents,
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { SceneEntities } from 'clientSideScene/sceneEntities'
|
||||
import { SceneInfra } from 'clientSideScene/sceneInfra'
|
||||
import { KclManager } from 'lang/KclSingleton'
|
||||
import CodeManager from 'lang/codeManager'
|
||||
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||
|
||||
export const codeManager = new CodeManager()
|
||||
|
||||
export const engineCommandManager = new EngineCommandManager()
|
||||
|
||||
export const kclManager = new KclManager(engineCommandManager)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { OnboardingButtons, useDismiss } from '.'
|
||||
import { useEffect } from 'react'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { codeManager, kclManager } from 'lib/singletons'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { APP_NAME } from 'lib/constants'
|
||||
import { onboardingPaths } from './paths'
|
||||
@ -11,12 +11,11 @@ export default function FutureWork() {
|
||||
const dismiss = useDismiss()
|
||||
|
||||
useEffect(() => {
|
||||
// We do want to update both the state and editor here.
|
||||
codeManager.updateCodeStateEditor(bracket)
|
||||
if (kclManager.engineCommandManager.engineConnection?.isReady()) {
|
||||
// If the engine is ready, promptly execute the loaded code
|
||||
kclManager.setCodeAndExecute(bracket)
|
||||
} else {
|
||||
// Otherwise, just set the code and wait for the connection to complete
|
||||
kclManager.setCode(bracket)
|
||||
kclManager.executeCode(true)
|
||||
}
|
||||
|
||||
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 { paths } from 'lib/paths'
|
||||
import { useEffect } from 'react'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { codeManager, kclManager } from 'lib/singletons'
|
||||
import { join } from '@tauri-apps/api/path'
|
||||
import { APP_NAME, PROJECT_ENTRYPOINT } from 'lib/constants'
|
||||
|
||||
@ -70,7 +70,9 @@ function OnboardingWithNewFile() {
|
||||
className="mt-6"
|
||||
dismiss={dismiss}
|
||||
next={() => {
|
||||
kclManager.setCodeAndExecute(bracket)
|
||||
// We do want to update both the state and editor here.
|
||||
codeManager.updateCodeStateEditor(bracket)
|
||||
kclManager.executeCode(true)
|
||||
next()
|
||||
}}
|
||||
nextText="Overwrite code and continue"
|
||||
@ -93,7 +95,7 @@ function OnboardingWithNewFile() {
|
||||
dismiss={dismiss}
|
||||
next={() => {
|
||||
void createAndOpenNewProject()
|
||||
kclManager.setCode(bracket, false)
|
||||
codeManager.updateCodeStateEditor(bracket)
|
||||
dismiss()
|
||||
}}
|
||||
nextText="Make a new project"
|
||||
@ -122,10 +124,11 @@ export default function Introduction() {
|
||||
: ''
|
||||
const dismiss = useDismiss()
|
||||
const next = useNextClick(onboardingPaths.CAMERA)
|
||||
const isStarterCode = kclManager.code === '' || kclManager.code === bracket
|
||||
const currentCode = codeManager.code
|
||||
const isStarterCode = currentCode === '' || currentCode === bracket
|
||||
|
||||
useEffect(() => {
|
||||
if (kclManager.code === '') kclManager.setCode(bracket)
|
||||
if (codeManager.code === '') codeManager.updateCodeStateEditor(bracket)
|
||||
}, [])
|
||||
|
||||
return isStarterCode ? (
|
||||
|
@ -2,7 +2,7 @@ import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useStore } from 'useStore'
|
||||
import { useEffect } from 'react'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { codeManager, kclManager } from 'lib/singletons'
|
||||
|
||||
export default function Sketching() {
|
||||
const buttonDownInStream = useStore((s) => s.buttonDownInStream)
|
||||
@ -10,12 +10,11 @@ export default function Sketching() {
|
||||
const next = useNextClick(onboardingPaths.FUTURE_WORK)
|
||||
|
||||
useEffect(() => {
|
||||
// We do want to update both the state and editor here.
|
||||
codeManager.updateCodeStateEditor('')
|
||||
if (kclManager.engineCommandManager.engineConnection?.isReady()) {
|
||||
// If the engine is ready, promptly execute the loaded code
|
||||
kclManager.setCodeAndExecute('')
|
||||
} else {
|
||||
// Otherwise, just set the code and wait for the connection to complete
|
||||
kclManager.setCode('')
|
||||
kclManager.executeCode(true)
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
@ -2,7 +2,6 @@ import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
import { addLineHighlight, EditorView } from './editor/highlightextension'
|
||||
import {
|
||||
parse,
|
||||
Program,
|
||||
_executor,
|
||||
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({
|
||||
ast,
|
||||
engineCommandManager,
|
||||
@ -268,10 +199,6 @@ export async function executeAst({
|
||||
: _executor(ast, programMemoryInit(), engineCommandManager, false))
|
||||
|
||||
await engineCommandManager.waitForAllCommands()
|
||||
engineCommandManager.addCommandLog({
|
||||
type: 'execution-done',
|
||||
data: null,
|
||||
})
|
||||
return {
|
||||
logs: [],
|
||||
errors: [],
|
||||
|