diff --git a/e2e/playwright/testing-settings.spec.ts b/e2e/playwright/testing-settings.spec.ts index 02c99943ee..a3ab72944b 100644 --- a/e2e/playwright/testing-settings.spec.ts +++ b/e2e/playwright/testing-settings.spec.ts @@ -344,14 +344,13 @@ test.describe('Testing settings', () => { await test.step('Refresh the application and see project setting applied', async () => { // Make sure we're done navigating before we reload await expect(settingsCloseButton).not.toBeVisible() - await page.reload({ waitUntil: 'domcontentloaded' }) + await page.reload({ waitUntil: 'domcontentloaded' }) await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor) }) await test.step(`Navigate back to the home view and see user setting applied`, async () => { await logoLink.click() - await page.screenshot({ path: 'out.png' }) await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor) }) diff --git a/src/clientSideScene/ClientSideSceneComp.tsx b/src/clientSideScene/ClientSideSceneComp.tsx index a4870decb5..0d12e3fcce 100644 --- a/src/clientSideScene/ClientSideSceneComp.tsx +++ b/src/clientSideScene/ClientSideSceneComp.tsx @@ -202,12 +202,20 @@ const Overlay = ({ let xAlignment = overlay.angle < 0 ? '0%' : '-100%' let yAlignment = overlay.angle < -90 || overlay.angle >= 90 ? '0%' : '-100%' + // It's possible for the pathToNode to request a newer AST node + // than what's available in the AST at the moment of query. + // It eventually settles on being updated. const _node1 = getNodeFromPath>( kclManager.ast, overlay.pathToNode, 'CallExpression' ) - if (err(_node1)) return + + // For that reason, to prevent console noise, we do not use err here. + if (_node1 instanceof Error) { + console.warn('ast older than pathToNode, not fatal, eventually settles', '') + return + } const callExpression = _node1.node const constraints = getConstraintInfo( @@ -637,10 +645,16 @@ const ConstraintSymbol = ({ kclManager.ast, kclManager.programMemory ) + if (!transform) return const { modifiedAst } = transform - // eslint-disable-next-line @typescript-eslint/no-floating-promises - kclManager.updateAst(modifiedAst, true) + + await kclManager.updateAst(modifiedAst, true) + + // Code editor will be updated in the modelingMachine. + const newCode = recast(modifiedAst) + if (err(newCode)) return + await codeManager.updateCodeEditor(newCode) } catch (e) { console.log('error', e) } diff --git a/src/clientSideScene/sceneEntities.ts b/src/clientSideScene/sceneEntities.ts index 77d4f9e9de..c0263b6f5a 100644 --- a/src/clientSideScene/sceneEntities.ts +++ b/src/clientSideScene/sceneEntities.ts @@ -453,6 +453,7 @@ export class SceneEntities { const { modifiedAst } = addStartProfileAtRes await kclManager.updateAst(modifiedAst, false) + this.removeIntersectionPlane() this.scene.remove(draftPointGroup) @@ -685,7 +686,7 @@ export class SceneEntities { }) return nextAst } - setUpDraftSegment = async ( + setupDraftSegment = async ( sketchPathToNode: PathToNode, forward: [number, number, number], up: [number, number, number], @@ -856,10 +857,11 @@ export class SceneEntities { } await kclManager.executeAstMock(modifiedAst) + if (intersectsProfileStart) { sceneInfra.modelingSend({ type: 'CancelSketch' }) } else { - await this.setUpDraftSegment( + await this.setupDraftSegment( sketchPathToNode, forward, up, @@ -867,6 +869,8 @@ export class SceneEntities { segmentName ) } + + await codeManager.updateEditorWithAstAndWriteToFile(modifiedAst) }, onMove: (args) => { this.onDragSegment({ @@ -991,43 +995,51 @@ export class SceneEntities { if (trap(_node)) return const sketchInit = _node.node?.declarations?.[0]?.init - if (sketchInit.type === 'PipeExpression') { - updateRectangleSketch(sketchInit, x, y, tags[0]) + if (sketchInit.type !== 'PipeExpression') { + return + } - let _recastAst = parse(recast(_ast)) - if (trap(_recastAst)) return - _ast = _recastAst + updateRectangleSketch(sketchInit, x, y, tags[0]) - // Update the primary AST and unequip the rectangle tool - await kclManager.executeAstMock(_ast) - sceneInfra.modelingSend({ type: 'Finish rectangle' }) + const newCode = recast(_ast) + let _recastAst = parse(newCode) + if (trap(_recastAst)) return + _ast = _recastAst - const { execState } = await executeAst({ - ast: _ast, - useFakeExecutor: true, - engineCommandManager: this.engineCommandManager, - programMemoryOverride, - idGenerator: kclManager.execState.idGenerator, - }) - const programMemory = execState.memory + // Update the primary AST and unequip the rectangle tool + await kclManager.executeAstMock(_ast) + sceneInfra.modelingSend({ type: 'Finish rectangle' }) - // Prepare to update the THREEjs scene - this.sceneProgramMemory = programMemory - const sketch = sketchFromKclValue( - programMemory.get(variableDeclarationName), - variableDeclarationName - ) - if (err(sketch)) return - const sgPaths = sketch.paths - const orthoFactor = orthoScale(sceneInfra.camControls.camera) + // lee: I had this at the bottom of the function, but it's + // possible sketchFromKclValue "fails" when sketching on a face, + // and this couldn't wouldn't run. + await codeManager.updateEditorWithAstAndWriteToFile(_ast) - // Update the starting segment of the THREEjs scene - this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch) - // Update the rest of the segments of the THREEjs scene - sgPaths.forEach((seg, index) => - this.updateSegment(seg, index, 0, _ast, orthoFactor, sketch) - ) - } + const { execState } = await executeAst({ + ast: _ast, + useFakeExecutor: true, + engineCommandManager: this.engineCommandManager, + programMemoryOverride, + idGenerator: kclManager.execState.idGenerator, + }) + const programMemory = execState.memory + + // Prepare to update the THREEjs scene + this.sceneProgramMemory = programMemory + const sketch = sketchFromKclValue( + programMemory.get(variableDeclarationName), + variableDeclarationName + ) + if (err(sketch)) return + const sgPaths = sketch.paths + const orthoFactor = orthoScale(sceneInfra.camControls.camera) + + // Update the starting segment of the THREEjs scene + this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch) + // Update the rest of the segments of the THREEjs scene + sgPaths.forEach((seg, index) => + this.updateSegment(seg, index, 0, _ast, orthoFactor, sketch) + ) }, }) } @@ -1187,13 +1199,17 @@ export class SceneEntities { if (err(moddedResult)) return modded = moddedResult.modifiedAst - let _recastAst = parse(recast(modded)) + const newCode = recast(modded) + if (err(newCode)) return + let _recastAst = parse(newCode) if (trap(_recastAst)) return Promise.reject(_recastAst) _ast = _recastAst // Update the primary AST and unequip the rectangle tool await kclManager.executeAstMock(_ast) sceneInfra.modelingSend({ type: 'Finish circle' }) + + await codeManager.updateEditorWithAstAndWriteToFile(_ast) } }, }) @@ -1229,6 +1245,7 @@ export class SceneEntities { forward, position, }) + await codeManager.writeToFile() } }, onDrag: async ({ diff --git a/src/components/FileTree.tsx b/src/components/FileTree.tsx index 9c98b224d4..de7a3f77aa 100644 --- a/src/components/FileTree.tsx +++ b/src/components/FileTree.tsx @@ -22,6 +22,7 @@ import usePlatform from 'hooks/usePlatform' import { FileEntry } from 'lib/project' import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher' import { normalizeLineEndings } from 'lib/codeEditor' +import { reportRejection } from 'lib/trap' function getIndentationCSS(level: number) { return `calc(1rem * ${level + 1})` @@ -189,15 +190,14 @@ const FileTreeItem = ({ // the ReactNodes are destroyed, so is this listener :) useFileSystemWatcher( async (eventType, path) => { - // Don't try to read a file that was removed. - if (isCurrentFile && eventType !== 'unlink') { - // Prevents a cyclic read / write causing editor problems such as - // misplaced cursor positions. - if (codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher) { - codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher = false - return - } + // Prevents a cyclic read / write causing editor problems such as + // misplaced cursor positions. + if (codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher) { + codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher = false + return + } + if (isCurrentFile && eventType === 'change') { let code = await window.electron.readFile(path, { encoding: 'utf-8' }) code = normalizeLineEndings(code) codeManager.updateCodeStateEditor(code) @@ -242,11 +242,11 @@ const FileTreeItem = ({ // Show the renaming form addCurrentItemToRenaming() } else if (e.code === 'Space') { - handleClick() + void handleClick().catch(reportRejection) } } - function handleClick() { + async function handleClick() { setTreeSelection(fileOrDir) if (fileOrDir.children !== null) return // Don't open directories @@ -258,12 +258,10 @@ const FileTreeItem = ({ `import("${fileOrDir.path.replace(project.path, '.')}")\n` + codeManager.code ) - // eslint-disable-next-line @typescript-eslint/no-floating-promises - codeManager.writeToFile() + await codeManager.writeToFile() // Prevent seeing the model built one piece at a time when changing files - // eslint-disable-next-line @typescript-eslint/no-floating-promises - kclManager.executeCode(true) + await kclManager.executeCode(true) } else { // Let the lsp servers know we closed a file. onFileClose(currentFile?.path || null, project?.path || null) @@ -295,7 +293,7 @@ const FileTreeItem = ({ style={{ paddingInlineStart: getIndentationCSS(level) }} onClick={(e) => { e.currentTarget.focus() - handleClick() + void handleClick().catch(reportRejection) }} onKeyUp={handleKeyUp} > @@ -655,6 +653,13 @@ export const FileTreeInner = ({ const isCurrentFile = loaderData.file?.path === path const hasChanged = eventType === 'change' if (isCurrentFile && hasChanged) return + + // If it's a settings file we wrote to already from the app ignore it. + if (codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher) { + codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher = false + return + } + fileSend({ type: 'Refresh' }) }, [loaderData?.project?.path, fileContext.selectedDirectory.path].filter( diff --git a/src/components/ModelingMachineProvider.tsx b/src/components/ModelingMachineProvider.tsx index 90d68e1344..e450e76ee8 100644 --- a/src/components/ModelingMachineProvider.tsx +++ b/src/components/ModelingMachineProvider.tsx @@ -304,6 +304,7 @@ export const ModelingMachineProvider = ({ const dispatchSelection = (selection?: EditorSelection) => { if (!selection) return // TODO less of hack for the below please if (!editorManager.editorView) return + setTimeout(() => { if (!editorManager.editorView) return editorManager.editorView.dispatch({ @@ -732,6 +733,11 @@ export const ModelingMachineProvider = ({ sketchDetails.origin ) if (err(updatedAst)) return Promise.reject(updatedAst) + + await codeManager.updateEditorWithAstAndWriteToFile( + updatedAst.newAst + ) + const selection = updateSelections( pathToNodeMap, selectionRanges, @@ -768,6 +774,11 @@ export const ModelingMachineProvider = ({ sketchDetails.origin ) if (err(updatedAst)) return Promise.reject(updatedAst) + + await codeManager.updateEditorWithAstAndWriteToFile( + updatedAst.newAst + ) + const selection = updateSelections( pathToNodeMap, selectionRanges, @@ -813,6 +824,11 @@ export const ModelingMachineProvider = ({ sketchDetails.origin ) if (err(updatedAst)) return Promise.reject(updatedAst) + + await codeManager.updateEditorWithAstAndWriteToFile( + updatedAst.newAst + ) + const selection = updateSelections( pathToNodeMap, selectionRanges, @@ -846,6 +862,11 @@ export const ModelingMachineProvider = ({ sketchDetails.origin ) if (err(updatedAst)) return Promise.reject(updatedAst) + + await codeManager.updateEditorWithAstAndWriteToFile( + updatedAst.newAst + ) + const selection = updateSelections( pathToNodeMap, selectionRanges, @@ -881,6 +902,11 @@ export const ModelingMachineProvider = ({ sketchDetails.origin ) if (err(updatedAst)) return Promise.reject(updatedAst) + + await codeManager.updateEditorWithAstAndWriteToFile( + updatedAst.newAst + ) + const selection = updateSelections( pathToNodeMap, selectionRanges, @@ -917,6 +943,11 @@ export const ModelingMachineProvider = ({ sketchDetails.origin ) if (err(updatedAst)) return Promise.reject(updatedAst) + + await codeManager.updateEditorWithAstAndWriteToFile( + updatedAst.newAst + ) + const selection = updateSelections( pathToNodeMap, selectionRanges, @@ -953,6 +984,11 @@ export const ModelingMachineProvider = ({ sketchDetails.origin ) if (err(updatedAst)) return Promise.reject(updatedAst) + + await codeManager.updateEditorWithAstAndWriteToFile( + updatedAst.newAst + ) + const selection = updateSelections( pathToNodeMap, selectionRanges, @@ -999,6 +1035,11 @@ export const ModelingMachineProvider = ({ sketchDetails.origin ) if (err(updatedAst)) return Promise.reject(updatedAst) + + await codeManager.updateEditorWithAstAndWriteToFile( + updatedAst.newAst + ) + const selection = updateSelections( { 0: pathToReplacedNode }, selectionRanges, diff --git a/src/components/SettingsAuthProvider.tsx b/src/components/SettingsAuthProvider.tsx index c3203ef271..d04f2b930f 100644 --- a/src/components/SettingsAuthProvider.tsx +++ b/src/components/SettingsAuthProvider.tsx @@ -41,6 +41,7 @@ import { reportRejection } from 'lib/trap' import { getAppSettingsFilePath } from 'lib/desktop' import { isDesktop } from 'lib/isDesktop' import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher' +import { codeManager } from 'lib/singletons' import { createRouteCommands } from 'lib/commandBarConfigs/routeCommandConfig' type MachineContext = { @@ -201,13 +202,13 @@ export const SettingsAuthProviderBase = ({ console.error('Error executing AST after settings change', e) } }, - persistSettings: ({ context, event }) => { + async persistSettings({ context, event }) { // Without this, when a user changes the file, it'd // create a detection loop with the file-system watcher. if (event.doNotPersist) return - // eslint-disable-next-line @typescript-eslint/no-floating-promises - saveSettings(context, loadedProject?.project?.path) + codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher = true + return saveSettings(context, loadedProject?.project?.path) }, }, }), @@ -221,7 +222,7 @@ export const SettingsAuthProviderBase = ({ }, []) useFileSystemWatcher( - async () => { + async (eventType: string) => { // If there is a projectPath but it no longer exists it means // it was exterally removed. If we let the code past this condition // execute it will recreate the directory due to code in @@ -235,6 +236,9 @@ export const SettingsAuthProviderBase = ({ } } + // Only reload if there are changes. Ignore everything else. + if (eventType !== 'change') return + const data = await loadAndValidateSettings(loadedProject?.project?.path) settingsSend({ type: 'Set all settings', diff --git a/src/editor/plugins/lsp/kcl/index.ts b/src/editor/plugins/lsp/kcl/index.ts index 42de1a5b26..205397b799 100644 --- a/src/editor/plugins/lsp/kcl/index.ts +++ b/src/editor/plugins/lsp/kcl/index.ts @@ -96,10 +96,10 @@ export class KclPlugin implements PluginValue { const newCode = viewUpdate.state.doc.toString() codeManager.code = newCode - // eslint-disable-next-line @typescript-eslint/no-floating-promises - codeManager.writeToFile() - this.scheduleUpdateDoc() + void codeManager.writeToFile().then(() => { + this.scheduleUpdateDoc() + }) } scheduleUpdateDoc() { diff --git a/src/hooks/useRefreshSettings.ts b/src/hooks/useRefreshSettings.ts index 6c1447b7b4..da7c440d26 100644 --- a/src/hooks/useRefreshSettings.ts +++ b/src/hooks/useRefreshSettings.ts @@ -26,6 +26,7 @@ export function useRefreshSettings(routeId: string = PATHS.INDEX) { ctx.settings.send({ type: 'Set all settings', settings: routeData, + doNotPersist: true, }) }, []) } diff --git a/src/hooks/useToolbarGuards.ts b/src/hooks/useToolbarGuards.ts index bfcb5eb80b..8d8ae96784 100644 --- a/src/hooks/useToolbarGuards.ts +++ b/src/hooks/useToolbarGuards.ts @@ -2,13 +2,13 @@ import { SetVarNameModal, createSetVarNameModal, } from 'components/SetVarNameModal' -import { editorManager, kclManager } from 'lib/singletons' -import { reportRejection, trap } from 'lib/trap' +import { editorManager, kclManager, codeManager } from 'lib/singletons' +import { reportRejection, trap, err } from 'lib/trap' import { moveValueIntoNewVariable } from 'lang/modifyAst' import { isNodeSafeToReplace } from 'lang/queryAst' import { useEffect, useState } from 'react' import { useModelingContext } from './useModelingContext' -import { PathToNode, SourceRange } from 'lang/wasm' +import { PathToNode, SourceRange, recast } from 'lang/wasm' import { useKclContext } from 'lang/KclProvider' import { toSync } from 'lib/utils' @@ -57,6 +57,11 @@ export function useConvertToVariable(range?: SourceRange) { ) await kclManager.updateAst(_modifiedAst, true) + + const newCode = recast(_modifiedAst) + if (err(newCode)) return + codeManager.updateCodeEditor(newCode) + return pathToReplacedNode } catch (e) { console.log('error', e) diff --git a/src/lang/KclSingleton.ts b/src/lang/KclSingleton.ts index c30aec1dc5..8f3363eff5 100644 --- a/src/lang/KclSingleton.ts +++ b/src/lang/KclSingleton.ts @@ -357,9 +357,6 @@ export class KclManager { this.clearAst() return } - codeManager.updateCodeEditor(newCode) - // Write the file to disk. - await codeManager.writeToFile() this._ast = { ...newAst } const { logs, errors, execState } = await executeAst({ @@ -434,13 +431,9 @@ export class KclManager { // Update the code state and the editor. codeManager.updateCodeStateEditor(code) - // Write back to the file system. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - codeManager.writeToFile() - // execute the code. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.executeCode() + // Write back to the file system. + void codeManager.writeToFile().then(() => this.executeCode()) } // 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. @@ -501,11 +494,6 @@ export class KclManager { } if (execute) { - // Call execute on the set ast. - // Update the code state and editor. - codeManager.updateCodeEditor(newCode) - // Write the file to disk. - await codeManager.writeToFile() await this.executeAst({ ast: astWithUpdatedSource, zoomToFit: optionalParams?.zoomToFit, diff --git a/src/lang/codeManager.ts b/src/lang/codeManager.ts index 5fdb72b323..39a21e360c 100644 --- a/src/lang/codeManager.ts +++ b/src/lang/codeManager.ts @@ -7,6 +7,8 @@ import toast from 'react-hot-toast' import { editorManager } from 'lib/singletons' import { Annotation, Transaction } from '@codemirror/state' import { KeyBinding } from '@codemirror/view' +import { recast, Program } from 'lang/wasm' +import { err } from 'lib/trap' const PERSIST_CODE_KEY = 'persistCode' @@ -121,24 +123,39 @@ export default class CodeManager { // Only write our buffer contents to file once per second. Any faster // and file-system watchers which read, will receive empty data during // writes. + clearTimeout(this.timeoutWriter) this.writeCausedByAppCheckedInFileTreeFileSystemWatcher = true - this.timeoutWriter = setTimeout(() => { - // Wait one event loop to give a chance for params to be set - // Save the file to disk - this._currentFilePath && + + return new Promise((resolve, reject) => { + this.timeoutWriter = setTimeout(() => { + if (!this._currentFilePath) + return reject(new Error('currentFilePath not set')) + + // Wait one event loop to give a chance for params to be set + // Save the file to disk window.electron .writeFile(this._currentFilePath, this.code ?? '') + .then(resolve) .catch((err: Error) => { // 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') + reject(err) }) - }, 1000) + }, 1000) + }) } else { safeLSSetItem(PERSIST_CODE_KEY, this.code) } } + + async updateEditorWithAstAndWriteToFile(ast: Program) { + const newCode = recast(ast) + if (err(newCode)) return + this.updateCodeStateEditor(newCode) + await this.writeToFile() + } } function safeLSGetItem(key: string) { diff --git a/src/lang/modifyAst/addFillet.ts b/src/lang/modifyAst/addFillet.ts index e8a5ec0de7..faf7a41d14 100644 --- a/src/lang/modifyAst/addFillet.ts +++ b/src/lang/modifyAst/addFillet.ts @@ -35,7 +35,12 @@ import { ArtifactGraph, getSweepFromSuspectedPath, } from 'lang/std/artifactGraph' -import { kclManager, engineCommandManager, editorManager } from 'lib/singletons' +import { + kclManager, + engineCommandManager, + editorManager, + codeManager, +} from 'lib/singletons' import { Node } from 'wasm-lib/kcl/bindings/Node' // Apply Fillet To Selection @@ -253,6 +258,9 @@ async function updateAstAndFocus( const updatedAst = await kclManager.updateAst(modifiedAst, true, { focusPath: pathToFilletNode, }) + + await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst) + if (updatedAst?.selections) { editorManager.selectRange(updatedAst?.selections) } diff --git a/src/lib/settings/settingsUtils.ts b/src/lib/settings/settingsUtils.ts index 283301ace4..f9ab5ad32e 100644 --- a/src/lib/settings/settingsUtils.ts +++ b/src/lib/settings/settingsUtils.ts @@ -178,6 +178,7 @@ export async function loadAndValidateSettings( if (err(appSettingsPayload)) return Promise.reject(appSettingsPayload) let settingsNext = createSettings() + // Because getting the default directory is async, we need to set it after if (onDesktop) { settings.app.projectDirectory.default = await getInitialDefaultDir() diff --git a/src/lib/useCalculateKclExpression.ts b/src/lib/useCalculateKclExpression.ts index 6d44721991..bc3b74bc57 100644 --- a/src/lib/useCalculateKclExpression.ts +++ b/src/lib/useCalculateKclExpression.ts @@ -115,6 +115,7 @@ export function useCalculateKclExpression({ setCalcResult(typeof result === 'number' ? String(result) : 'NAN') init && setValueNode(init) } + if (!value) return execAstAndSetResult().catch(() => { setCalcResult('NAN') setValueNode(null) diff --git a/src/machines/modelingMachine.ts b/src/machines/modelingMachine.ts index 156bec8b6b..ad2fd9eac8 100644 --- a/src/machines/modelingMachine.ts +++ b/src/machines/modelingMachine.ts @@ -18,6 +18,7 @@ import { sceneEntitiesManager, engineCommandManager, editorManager, + codeManager, } from 'lib/singletons' import { horzVertInfo, @@ -531,8 +532,10 @@ export const modelingMachine = setup({ } } ), - // eslint-disable-next-line @typescript-eslint/no-misused-promises - 'hide default planes': () => kclManager.hidePlanes(), + 'hide default planes': () => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + kclManager.hidePlanes() + }, 'reset sketch metadata': assign({ sketchDetails: null, sketchEnginePathId: '', @@ -595,7 +598,6 @@ export const modelingMachine = setup({ if (trap(extrudeSketchRes)) return const { modifiedAst, pathToExtrudeArg } = extrudeSketchRes - store.videoElement?.pause() const updatedAst = await kclManager.updateAst(modifiedAst, true, { focusPath: [pathToExtrudeArg], zoomToFit: true, @@ -604,11 +606,9 @@ export const modelingMachine = setup({ type: 'path', }, }) - if (!engineCommandManager.engineConnection?.idleMode) { - store.videoElement?.play().catch((e) => { - console.warn('Video playing was prevented', e) - }) - } + + await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst) + if (updatedAst?.selections) { editorManager.selectRange(updatedAst?.selections) } @@ -642,7 +642,6 @@ export const modelingMachine = setup({ if (trap(revolveSketchRes)) return const { modifiedAst, pathToRevolveArg } = revolveSketchRes - store.videoElement?.pause() const updatedAst = await kclManager.updateAst(modifiedAst, true, { focusPath: [pathToRevolveArg], zoomToFit: true, @@ -651,11 +650,9 @@ export const modelingMachine = setup({ type: 'path', }, }) - if (!engineCommandManager.engineConnection?.idleMode) { - store.videoElement?.play().catch((e) => { - console.warn('Video playing was prevented', e) - }) - } + + await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst) + if (updatedAst?.selections) { editorManager.selectRange(updatedAst?.selections) } @@ -685,6 +682,7 @@ export const modelingMachine = setup({ } await kclManager.updateAst(modifiedAst, true) + await codeManager.updateEditorWithAstAndWriteToFile(modifiedAst) })().catch(reportRejection) }, 'AST fillet': ({ event }) => { @@ -702,6 +700,9 @@ export const modelingMachine = setup({ radius ) if (err(applyFilletToSelectionResult)) return applyFilletToSelectionResult + + // eslint-disable-next-line @typescript-eslint/no-floating-promises + codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast) }, 'set selection filter to curves only': () => { ;(async () => { @@ -758,25 +759,35 @@ export const modelingMachine = setup({ 'remove sketch grid': () => sceneEntitiesManager.removeSketchGrid(), 'set up draft line': ({ context: { sketchDetails } }) => { if (!sketchDetails) return + // eslint-disable-next-line @typescript-eslint/no-floating-promises - sceneEntitiesManager.setUpDraftSegment( - sketchDetails.sketchPathToNode, - sketchDetails.zAxis, - sketchDetails.yAxis, - sketchDetails.origin, - 'line' - ) + sceneEntitiesManager + .setupDraftSegment( + sketchDetails.sketchPathToNode, + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin, + 'line' + ) + .then(() => { + return codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast) + }) }, 'set up draft arc': ({ context: { sketchDetails } }) => { if (!sketchDetails) return + // eslint-disable-next-line @typescript-eslint/no-floating-promises - sceneEntitiesManager.setUpDraftSegment( - sketchDetails.sketchPathToNode, - sketchDetails.zAxis, - sketchDetails.yAxis, - sketchDetails.origin, - 'tangentialArcTo' - ) + sceneEntitiesManager + .setupDraftSegment( + sketchDetails.sketchPathToNode, + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin, + 'tangentialArcTo' + ) + .then(() => { + return codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast) + }) }, 'listen for rectangle origin': ({ context: { sketchDetails } }) => { if (!sketchDetails) return @@ -834,38 +845,53 @@ export const modelingMachine = setup({ 'set up draft rectangle': ({ context: { sketchDetails }, event }) => { if (event.type !== 'Add rectangle origin') return if (!sketchDetails || !event.data) return + // eslint-disable-next-line @typescript-eslint/no-floating-promises - sceneEntitiesManager.setupDraftRectangle( - sketchDetails.sketchPathToNode, - sketchDetails.zAxis, - sketchDetails.yAxis, - sketchDetails.origin, - event.data - ) + sceneEntitiesManager + .setupDraftRectangle( + sketchDetails.sketchPathToNode, + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin, + event.data + ) + .then(() => { + return codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast) + }) }, 'set up draft circle': ({ context: { sketchDetails }, event }) => { if (event.type !== 'Add circle origin') return if (!sketchDetails || !event.data) return + // eslint-disable-next-line @typescript-eslint/no-floating-promises - sceneEntitiesManager.setupDraftCircle( - sketchDetails.sketchPathToNode, - sketchDetails.zAxis, - sketchDetails.yAxis, - sketchDetails.origin, - event.data - ) + sceneEntitiesManager + .setupDraftCircle( + sketchDetails.sketchPathToNode, + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin, + event.data + ) + .then(() => { + return codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast) + }) }, 'set up draft line without teardown': ({ context: { sketchDetails } }) => { if (!sketchDetails) return + // eslint-disable-next-line @typescript-eslint/no-floating-promises - sceneEntitiesManager.setUpDraftSegment( - sketchDetails.sketchPathToNode, - sketchDetails.zAxis, - sketchDetails.yAxis, - sketchDetails.origin, - 'line', - false - ) + sceneEntitiesManager + .setupDraftSegment( + sketchDetails.sketchPathToNode, + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin, + 'line', + false + ) + .then(() => { + return codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast) + }) }, 'show default planes': () => { // eslint-disable-next-line @typescript-eslint/no-floating-promises @@ -882,12 +908,17 @@ export const modelingMachine = setup({ 'add axis n grid': ({ context: { sketchDetails } }) => { if (!sketchDetails) return if (localStorage.getItem('disableAxis')) return + + // eslint-disable-next-line @typescript-eslint/no-floating-promises sceneEntitiesManager.createSketchAxis( sketchDetails.sketchPathToNode || [], sketchDetails.zAxis, sketchDetails.yAxis, sketchDetails.origin ) + + // eslint-disable-next-line @typescript-eslint/no-floating-promises + codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast) }, 'reset client scene mouse handlers': () => { // when not in sketch mode we don't need any mouse listeners @@ -916,10 +947,13 @@ export const modelingMachine = setup({ 'Delete segment': ({ context: { sketchDetails }, event }) => { if (event.type !== 'Delete segment') return if (!sketchDetails || !event.data) return + // eslint-disable-next-line @typescript-eslint/no-floating-promises deleteSegment({ pathToNode: event.data, sketchDetails, + }).then(() => { + return codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast) }) }, 'Reset Segment Overlays': () => sceneEntitiesManager.resetOverlays(), @@ -984,6 +1018,9 @@ export const modelingMachine = setup({ ) if (trap(updatedAst, { suppress: true })) return if (!updatedAst) return + + await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst) + return { selectionType: 'completeSelection', selection: updateSelections( @@ -1018,6 +1055,7 @@ export const modelingMachine = setup({ ) if (trap(updatedAst, { suppress: true })) return if (!updatedAst) return + await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst) return { selectionType: 'completeSelection', selection: updateSelections( @@ -1052,6 +1090,7 @@ export const modelingMachine = setup({ ) if (trap(updatedAst, { suppress: true })) return if (!updatedAst) return + await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst) return { selectionType: 'completeSelection', selection: updateSelections( @@ -1084,6 +1123,7 @@ export const modelingMachine = setup({ ) if (trap(updatedAst, { suppress: true })) return if (!updatedAst) return + await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst) const updatedSelectionRanges = updateSelections( pathToNodeMap, selectionRanges, @@ -1117,6 +1157,7 @@ export const modelingMachine = setup({ ) if (trap(updatedAst, { suppress: true })) return if (!updatedAst) return + await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst) const updatedSelectionRanges = updateSelections( pathToNodeMap, selectionRanges, @@ -1150,6 +1191,7 @@ export const modelingMachine = setup({ ) if (trap(updatedAst, { suppress: true })) return if (!updatedAst) return + await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst) const updatedSelectionRanges = updateSelections( pathToNodeMap, selectionRanges, @@ -1183,6 +1225,7 @@ export const modelingMachine = setup({ ) if (trap(updatedAst, { suppress: true })) return if (!updatedAst) return + await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst) const updatedSelectionRanges = updateSelections( pathToNodeMap, selectionRanges, @@ -1220,6 +1263,8 @@ export const modelingMachine = setup({ ) if (trap(updatedAst, { suppress: true })) return if (!updatedAst) return + await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst) + const updatedSelectionRanges = updateSelections( pathToNodeMap, selectionRanges, @@ -1252,6 +1297,7 @@ export const modelingMachine = setup({ ) if (trap(updatedAst, { suppress: true })) return if (!updatedAst) return + await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst) const updatedSelectionRanges = updateSelections( pathToNodeMap, selectionRanges, @@ -1556,7 +1602,7 @@ export const modelingMachine = setup({ }, }, - entry: 'setup client side sketch segments', + entry: ['setup client side sketch segments'], }, 'Await horizontal distance info': { @@ -1801,7 +1847,7 @@ export const modelingMachine = setup({ onError: 'SketchIdle', onDone: { target: 'SketchIdle', - actions: ['Set selection'], + actions: 'Set selection', }, }, },