From 537d86c8ffd2b117b72cd0b3ec3d860c73af503d Mon Sep 17 00:00:00 2001 From: Jess Frazelle Date: Fri, 19 Apr 2024 14:24:40 -0700 Subject: [PATCH] Editor singleton to prevent re-renders (#2163) * move editor data into a singleton Signed-off-by: Jess Frazelle * debounce on update Signed-off-by: Jess Frazelle * updates Signed-off-by: Jess Frazelle * make select on extrude work Signed-off-by: Jess Frazelle * highlight range Signed-off-by: Jess Frazelle * highlight range Signed-off-by: Jess Frazelle * updates Signed-off-by: Jess Frazelle * fix errors Signed-off-by: Jess Frazelle * updates Signed-off-by: Jess Frazelle * almost forgot the error pane Signed-off-by: Jess Frazelle * loint Signed-off-by: Jess Frazelle * call out to codemirror Signed-off-by: Jess Frazelle * updates Signed-off-by: Jess Frazelle * fix tauri; Signed-off-by: Jess Frazelle * updates Signed-off-by: Jess Frazelle * more efficient Signed-off-by: Jess Frazelle * create the modals in the hook Signed-off-by: Jess Frazelle * Revert "create the modals in the hook" This reverts commit bbeba85030763cf7235a09fa24247dbf120f2a64. * change todo Signed-off-by: Jess Frazelle --------- Signed-off-by: Jess Frazelle --- .nvmrc | 2 +- babel.config.js | 2 +- e2e/playwright/flow-tests.spec.ts | 39 +++ package.json | 5 +- playwright.config.ts | 4 +- src/clientSideScene/ClientSideSceneComp.tsx | 6 - src/clientSideScene/sceneEntities.ts | 7 +- src/clientSideScene/sceneInfra.ts | 5 - src/components/AstExplorer.tsx | 11 +- .../CommandBar/CommandBarProvider.tsx | 7 +- src/components/ModelingMachineProvider.tsx | 46 ++-- .../ModelingPanes/KclEditorPane.tsx | 152 +++-------- .../ModelingPanes/MemoryPane.test.tsx | 4 +- src/editor/manager.ts | 238 ++++++++++++++++++ src/editor/plugins/lsp/plugin.ts | 28 +-- src/hooks/useEngineConnectionSubscriptions.ts | 23 +- src/hooks/useHotKeyListener.ts | 11 +- src/hooks/useToolbarGuards.ts | 9 +- src/lang/KclProvider.tsx | 9 - src/lang/KclSingleton.ts | 6 +- src/lang/abstractSyntaxTree.test.ts | 4 +- src/lang/artifact.test.ts | 4 +- src/lang/codeManager.ts | 32 ++- src/lang/executor.test.ts | 4 +- src/lang/getNodePathFromSourceRange.test.ts | 4 +- src/lang/modifyAst.test.ts | 4 +- src/lang/queryAst.test.ts | 4 +- src/lang/recast.test.ts | 4 +- src/lang/std/sketch.test.ts | 4 +- src/lang/std/sketchConstraints.test.ts | 4 +- src/lang/std/sketchcombos.test.ts | 4 +- src/lang/std/std.test.ts | 4 +- src/lang/tokeniser.test.ts | 4 +- src/lang/wasm.ts | 28 ++- src/lib/routeLoaders.ts | 1 + src/lib/singletons.ts | 5 + src/lib/utils2d.test.ts | 4 +- src/machines/commandBarMachine.ts | 96 +++---- src/machines/modelingMachine.ts | 9 +- src/useStore.ts | 33 --- tsconfig.json | 19 +- tsconfig.node.json | 6 +- vite.config.ts | 42 ++-- yarn.lock | 62 +++-- 44 files changed, 584 insertions(+), 415 deletions(-) create mode 100644 src/editor/manager.ts diff --git a/.nvmrc b/.nvmrc index 1df6fd41c..94ef0a9a3 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v20.5.0 +v21.7.1 diff --git a/babel.config.js b/babel.config.js index 1c6028342..e6196ef3d 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,3 +1,3 @@ module.exports = { - presets: ["@babel/preset-env"], + presets: ['@babel/preset-env'], } diff --git a/e2e/playwright/flow-tests.spec.ts b/e2e/playwright/flow-tests.spec.ts index b0b112fcc..4e91d0ace 100644 --- a/e2e/playwright/flow-tests.spec.ts +++ b/e2e/playwright/flow-tests.spec.ts @@ -1673,3 +1673,42 @@ test('Can code mod a line length', async ({ page }) => { `const length001 = 20const part001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> line([0, 20], %) |> xLine(-length001, %)` ) }) + +test('Extrude from command bar selects extrude line after', async ({ + page, +}) => { + await page.addInitScript(async () => { + localStorage.setItem( + 'persistCode', + `const part001 = startSketchOn('XY') + |> startProfileAt([-10, -10], %) + |> line([20, 0], %) + |> line([0, 20], %) + |> xLine(-20, %) + |> close(%) + ` + ) + }) + + const u = getUtils(page) + await page.setViewportSize({ width: 1200, height: 500 }) + await page.goto('/') + await u.waitForAuthSkipAppStart() + await u.openDebugPanel() + await u.expectCmdLog('[data-message-type="execution-done"]') + await u.closeDebugPanel() + + // Click the line of code for xLine. + await page.getByText(`close(%)`).click() // TODO remove this and reinstate // await topHorzSegmentClick() + await page.waitForTimeout(100) + + await page.getByRole('button', { name: 'Extrude' }).click() + await page.waitForTimeout(100) + await page.keyboard.press('Enter') + await page.waitForTimeout(100) + await page.keyboard.press('Enter') + await page.waitForTimeout(100) + await expect(page.locator('.cm-activeLine')).toHaveText( + ` |> extrude(5 + 7, %)` + ) +}) diff --git a/package.json b/package.json index 1beb8afb0..f0733bc2c 100644 --- a/package.json +++ b/package.json @@ -84,8 +84,8 @@ "test:e2e:tauri": "E2E_TAURI_ENABLED=true TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' wdio run wdio.conf.ts", "simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &", "simpleserver": "yarn pretest && http-server ./public --cors -p 3000", - "fmt": "prettier --write ./src && prettier --write ./e2e", - "fmt-check": "prettier --check ./src && prettier --check ./e2e", + "fmt": "prettier --write ./src *.ts *.json *.js ./e2e", + "fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e", "build:wasm-dev": "(cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt", "build:wasm": "(cd src/wasm-lib && wasm-pack build --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt", "build:wasm-clean": "yarn wasm-prep && yarn build:wasm", @@ -132,6 +132,7 @@ "@types/wicg-file-system-access": "^2023.10.5", "@types/ws": "^8.5.10", "@vitejs/plugin-react": "^4.2.1", + "@vitest/web-worker": "^1.5.0", "@wdio/cli": "^8.24.3", "@wdio/globals": "^8.36.0", "@wdio/local-runner": "^8.36.0", diff --git a/playwright.config.ts b/playwright.config.ts index ad16f97aa..bc39620cd 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -49,8 +49,6 @@ export default defineConfig({ // use: { ...devices['Desktop Chrome'] }, // }, - - /* Test against mobile viewports. */ // { // name: 'Mobile Chrome', @@ -78,4 +76,4 @@ export default defineConfig({ // url: 'http://127.0.0.1:3000', reuseExistingServer: !process.env.CI, }, -}) \ No newline at end of file +}) diff --git a/src/clientSideScene/ClientSideSceneComp.tsx b/src/clientSideScene/ClientSideSceneComp.tsx index dc874153a..9b03a4e46 100644 --- a/src/clientSideScene/ClientSideSceneComp.tsx +++ b/src/clientSideScene/ClientSideSceneComp.tsx @@ -3,7 +3,6 @@ import { useModelingContext } from 'hooks/useModelingContext' import { cameraMouseDragGuards } from 'lib/cameraControls' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' -import { useStore } from 'useStore' import { ARROWHEAD, DEBUG_SHOW_BOTH_SCENES } from './sceneInfra' import { ReactCameraProperties } from './CameraControls' import { throttle } from 'lib/utils' @@ -47,10 +46,6 @@ export const ClientSideScene = ({ const canvasRef = useRef(null) const { state, send, context } = useModelingContext() const { hideClient, hideServer } = useShouldHideScene() - const { setHighlightRange } = useStore((s) => ({ - setHighlightRange: s.setHighlightRange, - highlightRange: s.highlightRange, - })) // Listen for changes to the camera controls setting // and update the client-side scene's controls accordingly. @@ -69,7 +64,6 @@ export const ClientSideScene = ({ const canvas = canvasRef.current canvas.appendChild(sceneInfra.renderer.domElement) sceneInfra.animate() - sceneInfra.setHighlightCallback(setHighlightRange) canvas.addEventListener('mousemove', sceneInfra.onMouseMove, false) canvas.addEventListener('mousedown', sceneInfra.onMouseDown, false) canvas.addEventListener('mouseup', sceneInfra.onMouseUp, false) diff --git a/src/clientSideScene/sceneEntities.ts b/src/clientSideScene/sceneEntities.ts index 1d73cedb4..ea5ed7f1b 100644 --- a/src/clientSideScene/sceneEntities.ts +++ b/src/clientSideScene/sceneEntities.ts @@ -57,6 +57,7 @@ import { kclManager, sceneInfra, codeManager, + editorManager, } from 'lib/singletons' import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst' import { executeAst, useStore } from 'useStore' @@ -1423,7 +1424,7 @@ export class SceneEntities { parent.userData.pathToNode, 'CallExpression' ).node - sceneInfra.highlightCallback([node.start, node.end]) + editorManager.setHighlightRange([node.start, node.end]) const yellow = 0xffff00 colorSegment(selected, yellow) const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE) @@ -1459,10 +1460,10 @@ export class SceneEntities { } return } - sceneInfra.highlightCallback([0, 0]) + editorManager.setHighlightRange([0, 0]) }, onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => { - sceneInfra.highlightCallback([0, 0]) + editorManager.setHighlightRange([0, 0]) const parent = getParentGroup(selected, [ STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT, diff --git a/src/clientSideScene/sceneInfra.ts b/src/clientSideScene/sceneInfra.ts index 0a42686dc..a20452ebe 100644 --- a/src/clientSideScene/sceneInfra.ts +++ b/src/clientSideScene/sceneInfra.ts @@ -24,7 +24,6 @@ import { import { compareVec2Epsilon2 } from 'lang/std/sketch' import { useModelingContext } from 'hooks/useModelingContext' import * as TWEEN from '@tweenjs/tween.js' -import { SourceRange } from 'lang/wasm' import { Axis } from 'lib/selections' import { type BaseUnit } from 'lib/settings/settingsTypes' import { CameraControls } from './CameraControls' @@ -149,10 +148,6 @@ export class SceneInfra { onMouseLeave: () => {}, }) } - highlightCallback: (a: SourceRange) => void = () => {} - setHighlightCallback(cb: (a: SourceRange) => void) { - this.highlightCallback = cb - } modelingSend: SendType = (() => {}) as any setSend(send: SendType) { diff --git a/src/components/AstExplorer.tsx b/src/components/AstExplorer.tsx index 7e7d6cf22..6a448dc47 100644 --- a/src/components/AstExplorer.tsx +++ b/src/components/AstExplorer.tsx @@ -1,11 +1,9 @@ import { useModelingContext } from 'hooks/useModelingContext' -import { kclManager } from 'lib/singletons' +import { editorManager, kclManager } from 'lib/singletons' import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst' import { useEffect, useRef, useState } from 'react' -import { useStore } from 'useStore' export function AstExplorer() { - const setHighlightRange = useStore((s) => s.setHighlightRange) const { context } = useModelingContext() const pathToNode = getNodePathFromSourceRange( // TODO maybe need to have callback to make sure it stays in sync @@ -42,7 +40,7 @@ export function AstExplorer() {
{ - setHighlightRange([0, 0]) + editorManager.setHighlightRange([0, 0]) }} >
@@ -88,7 +86,6 @@ function DisplayObj({
   filterKeys: string[]
   node: any
 }) {
-  const setHighlightRange = useStore((s) => s.setHighlightRange)
   const { send } = useModelingContext()
   const ref = useRef(null)
   const [hasCursor, setHasCursor] = useState(false)
@@ -112,12 +109,12 @@ function DisplayObj({
         hasCursor ? 'bg-violet-100/80 dark:bg-violet-100/25' : ''
       }`}
       onMouseEnter={(e) => {
-        setHighlightRange([obj?.start || 0, obj.end])
+        editorManager.setHighlightRange([obj?.start || 0, obj.end])
         e.stopPropagation()
       }}
       onMouseMove={(e) => {
         e.stopPropagation()
-        setHighlightRange([obj?.start || 0, obj.end])
+        editorManager.setHighlightRange([obj?.start || 0, obj.end])
       }}
       onClick={(e) => {
         send({
diff --git a/src/components/CommandBar/CommandBarProvider.tsx b/src/components/CommandBar/CommandBarProvider.tsx
index 3baf439fd..9a080ba97 100644
--- a/src/components/CommandBar/CommandBarProvider.tsx
+++ b/src/components/CommandBar/CommandBarProvider.tsx
@@ -1,6 +1,7 @@
 import { useMachine } from '@xstate/react'
+import { editorManager } from 'lib/singletons'
 import { commandBarMachine } from 'machines/commandBarMachine'
-import { createContext } from 'react'
+import { createContext, useEffect } from 'react'
 import { EventFrom, StateFrom } from 'xstate'
 
 type CommandsContextType = {
@@ -30,6 +31,10 @@ export const CommandBarProvider = ({
     },
   })
 
+  useEffect(() => {
+    editorManager.setCommandBarSend(commandBarSend)
+  })
+
   return (
      coreDump(coreDumpManager, true))
 
-  const {
-    isShiftDown,
-    editorView,
-    setLastCodeMirrorSelectionUpdatedFromScene,
-  } = useStore((s) => ({
-    isShiftDown: s.isShiftDown,
-    editorView: s.editorView,
-    setLastCodeMirrorSelectionUpdatedFromScene:
-      s.setLastCodeMirrorSelectionUpdatedFromScene,
-  }))
-
   // Settings machine setup
   // const retrievedSettings = useRef(
   // localStorage?.getItem(MODELING_PERSIST_KEY) || '{}'
@@ -135,29 +125,33 @@ export const ModelingMachineProvider = ({
         'Set selection': assign(({ selectionRanges }, event) => {
           if (event.type !== 'Set selection') return {} // this was needed for ts after adding 'Set selection' action to on done modal events
           const setSelections = event.data
-          if (!editorView) return {}
+          if (!editorManager.editorView) return {}
           const dispatchSelection = (selection?: EditorSelection) => {
             if (!selection) return // TODO less of hack for the below please
-            setLastCodeMirrorSelectionUpdatedFromScene(Date.now())
-            setTimeout(() => editorView.dispatch({ selection }))
+            editorManager.lastSelectionEvent = Date.now()
+            setTimeout(() => {
+              if (editorManager.editorView) {
+                editorManager.editorView.dispatch({ selection })
+              }
+            })
           }
           let selections: Selections = {
             codeBasedSelections: [],
             otherSelections: [],
           }
           if (setSelections.selectionType === 'singleCodeCursor') {
-            if (!setSelections.selection && isShiftDown) {
-            } else if (!setSelections.selection && !isShiftDown) {
+            if (!setSelections.selection && editorManager.isShiftDown) {
+            } else if (!setSelections.selection && !editorManager.isShiftDown) {
               selections = {
                 codeBasedSelections: [],
                 otherSelections: [],
               }
-            } else if (setSelections.selection && !isShiftDown) {
+            } else if (setSelections.selection && !editorManager.isShiftDown) {
               selections = {
                 codeBasedSelections: [setSelections.selection],
                 otherSelections: [],
               }
-            } else if (setSelections.selection && isShiftDown) {
+            } else if (setSelections.selection && editorManager.isShiftDown) {
               selections = {
                 codeBasedSelections: [
                   ...selectionRanges.codeBasedSelections,
@@ -180,6 +174,7 @@ export const ModelingMachineProvider = ({
                 engineCommandManager.sendSceneCommand(event)
               )
             updateSceneObjectColors()
+
             return {
               selectionRanges: selections,
             }
@@ -192,7 +187,7 @@ export const ModelingMachineProvider = ({
           }
 
           if (setSelections.selectionType === 'otherSelection') {
-            if (isShiftDown) {
+            if (editorManager.isShiftDown) {
               selections = {
                 codeBasedSelections: selectionRanges.codeBasedSelections,
                 otherSelections: [setSelections.selection],
@@ -516,6 +511,19 @@ export const ModelingMachineProvider = ({
     })
   }, [modelingSend])
 
+  // Give the state back to the editorManager.
+  useEffect(() => {
+    editorManager.modelingSend = modelingSend
+  }, [modelingSend])
+
+  useEffect(() => {
+    editorManager.modelingEvent = modelingState.event
+  }, [modelingState.event])
+
+  useEffect(() => {
+    editorManager.selectionRanges = modelingState.context.selectionRanges
+  }, [modelingState.context.selectionRanges])
+
   useStateMachineCommands({
     machineId: 'modeling',
     state: modelingState,
diff --git a/src/components/ModelingSidebar/ModelingPanes/KclEditorPane.tsx b/src/components/ModelingSidebar/ModelingPanes/KclEditorPane.tsx
index 5180dde96..bb5ef33db 100644
--- a/src/components/ModelingSidebar/ModelingPanes/KclEditorPane.tsx
+++ b/src/components/ModelingSidebar/ModelingPanes/KclEditorPane.tsx
@@ -1,13 +1,8 @@
-import { undo, redo } from '@codemirror/commands'
 import ReactCodeMirror from '@uiw/react-codemirror'
 import { TEST } from 'env'
-import { useCommandsContext } from 'hooks/useCommandsContext'
 import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
-import { useConvertToVariable } from 'hooks/useToolbarGuards'
 import { Themes, getSystemTheme } from 'lib/theme'
-import { useEffect, useMemo, useRef } from 'react'
-import { useStore } from 'useStore'
-import { processCodeMirrorRanges } from 'lib/selections'
+import { useEffect, useMemo } from 'react'
 import { highlightSelectionMatches, searchKeymap } from '@codemirror/search'
 import { lineHighlightField } from 'editor/highlightextension'
 import { roundOff } from 'lib/utils'
@@ -29,7 +24,7 @@ import {
   historyKeymap,
   history,
 } from '@codemirror/commands'
-import { lintGutter, lintKeymap, linter } from '@codemirror/lint'
+import { lintGutter, lintKeymap } from '@codemirror/lint'
 import {
   foldGutter,
   foldKeymap,
@@ -39,25 +34,20 @@ import {
   syntaxHighlighting,
   defaultHighlightStyle,
 } from '@codemirror/language'
-import { useModelingContext } from 'hooks/useModelingContext'
 import interact from '@replit/codemirror-interact'
-import { engineCommandManager, sceneInfra, kclManager } from 'lib/singletons'
-import { useKclContext } from 'lang/KclProvider'
-import { ModelingMachineEvent } from 'machines/modelingMachine'
+import { kclManager, editorManager, codeManager } from 'lib/singletons'
 import { useHotkeys } from 'react-hotkeys-hook'
 import { isTauri } from 'lib/isTauri'
 import { useNavigate } from 'react-router-dom'
 import { paths } from 'lib/paths'
 import makeUrlPathRelative from 'lib/makeUrlPathRelative'
 import { useLspContext } from 'components/LspProvider'
-import { Prec, EditorState, Extension, SelectionRange } from '@codemirror/state'
+import { Prec, EditorState, Extension } from '@codemirror/state'
 import {
   closeBrackets,
   closeBracketsKeymap,
   completionKeymap,
-  hasNextSnippetField,
 } from '@codemirror/autocomplete'
-import { kclErrorsToDiagnostics } from 'lang/errors'
 
 export const editorShortcutMeta = {
   formatCode: {
@@ -77,13 +67,6 @@ export const KclEditorPane = () => {
     context.app.theme.current === Themes.System
       ? getSystemTheme()
       : context.app.theme.current
-  const { editorView, setEditorView, isShiftDown } = useStore((s) => ({
-    editorView: s.editorView,
-    setEditorView: s.setEditorView,
-    isShiftDown: s.isShiftDown,
-  }))
-  const { editorCode, errors } = useKclContext()
-  const lastEvent = useRef({ event: '', time: Date.now() })
   const { copilotLSP, kclLSP } = useLspContext()
   const navigate = useNavigate()
 
@@ -96,90 +79,15 @@ export const KclEditorPane = () => {
 
   useHotkeys('mod+z', (e) => {
     e.preventDefault()
-    if (editorView) {
-      undo(editorView)
-    }
+    editorManager.undo()
   })
   useHotkeys('mod+shift+z', (e) => {
     e.preventDefault()
-    if (editorView) {
-      redo(editorView)
-    }
+    editorManager.redo()
   })
 
-  const {
-    context: { selectionRanges },
-    send,
-    state,
-  } = useModelingContext()
-
-  const { settings } = useSettingsAuthContext()
-  const textWrapping = settings.context.textEditor.textWrapping
-  const cursorBlinking = settings.context.textEditor.blinkingCursor
-  const { commandBarSend } = useCommandsContext()
-  const { enable: convertEnabled, handleClick: convertCallback } =
-    useConvertToVariable()
-
-  const lastSelection = useRef('')
-  const onUpdate = (viewUpdate: ViewUpdate) => {
-    // 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 (hasNextSnippetField(viewUpdate.view.state)) {
-      return
-    }
-    if (!editorView) {
-      setEditorView(viewUpdate.view)
-    }
-    const selString = stringifyRanges(
-      viewUpdate?.state?.selection?.ranges || []
-    )
-    if (selString === lastSelection.current) {
-      // onUpdate is noisy and is fired a lot by extensions
-      // since we're only interested in selections changes we can ignore most of these.
-      return
-    }
-    lastSelection.current = selString
-
-    if (
-      // TODO find a less lazy way of getting the last
-      Date.now() - useStore.getState().lastCodeMirrorSelectionUpdatedFromScene <
-      150
-    )
-      return // update triggered by scene selection
-    if (sceneInfra.selected) return // mid drag
-    const ignoreEvents: ModelingMachineEvent['type'][] = [
-      'Equip Line tool',
-      'Equip tangential arc to',
-    ]
-    if (ignoreEvents.includes(state.event.type)) return
-    const eventInfo = processCodeMirrorRanges({
-      codeMirrorRanges: viewUpdate.state.selection.ranges,
-      selectionRanges,
-      isShiftDown,
-    })
-    if (!eventInfo) return
-    const deterministicEventInfo = {
-      ...eventInfo,
-      engineEvents: eventInfo.engineEvents.map((e) => ({
-        ...e,
-        cmd_id: 'static',
-      })),
-    }
-    const stringEvent = JSON.stringify(deterministicEventInfo)
-    if (
-      stringEvent === lastEvent.current.event &&
-      Date.now() - lastEvent.current.time < 500
-    )
-      return // don't repeat events
-    lastEvent.current = { event: stringEvent, time: Date.now() }
-    send(eventInfo.modelingEvent)
-    eventInfo.engineEvents.forEach((event) =>
-      engineCommandManager.sendSceneCommand(event)
-    )
-  }
+  const textWrapping = context.textEditor.textWrapping
+  const cursorBlinking = context.textEditor.blinkingCursor
 
   const editorExtensions = useMemo(() => {
     const extensions = [
@@ -202,7 +110,7 @@ export const KclEditorPane = () => {
         {
           key: 'Meta-k',
           run: () => {
-            commandBarSend({ type: 'Open' })
+            editorManager.commandBarSend({ type: 'Open' })
             return false
           },
         },
@@ -216,11 +124,7 @@ export const KclEditorPane = () => {
         {
           key: editorShortcutMeta.convertToVariable.codeMirror,
           run: () => {
-            if (convertEnabled) {
-              convertCallback()
-              return true
-            }
-            return false
+            return editorManager.convertToVariable()
           },
         },
       ]),
@@ -233,9 +137,6 @@ export const KclEditorPane = () => {
     if (!TEST) {
       extensions.push(
         lintGutter(),
-        linter((_view: EditorView) => {
-          return kclErrorsToDiagnostics(errors)
-        }),
         lineNumbers(),
         highlightActiveLineGutter(),
         highlightSpecialChars(),
@@ -288,13 +189,10 @@ export const KclEditorPane = () => {
     }
 
     return extensions
-  }, [
-    kclLSP,
-    copilotLSP,
-    textWrapping.current,
-    cursorBlinking.current,
-    convertCallback,
-  ])
+  }, [kclLSP, copilotLSP, textWrapping.current, cursorBlinking.current])
+
+  let debounceTimer: ReturnType | null = null
+  const updateDelay = 100
 
   return (
     
{ className={'absolute inset-0 ' + (cursorBlinking.current ? 'blink' : '')} > setEditorView(_editorView)} + onCreateEditor={(_editorView) => + editorManager.setEditorView(_editorView) + } + onUpdate={(view: ViewUpdate) => { + // debounce the view update. + // otherwise it is laggy for typing. + if (debounceTimer) { + clearTimeout(debounceTimer) + } + + debounceTimer = setTimeout(() => { + editorManager.handleOnViewUpdate(view) + }, updateDelay) + }} indentWithTab={false} basicSetup={false} />
) } - -function stringifyRanges(ranges: readonly SelectionRange[]): string { - return ranges.map(({ to, from }) => `${to}->${from}`).join('&') -} diff --git a/src/components/ModelingSidebar/ModelingPanes/MemoryPane.test.tsx b/src/components/ModelingSidebar/ModelingPanes/MemoryPane.test.tsx index 82babdf2b..db2c578a8 100644 --- a/src/components/ModelingSidebar/ModelingPanes/MemoryPane.test.tsx +++ b/src/components/ModelingSidebar/ModelingPanes/MemoryPane.test.tsx @@ -2,7 +2,9 @@ import { processMemory } from './MemoryPane' import { enginelessExecutor } from '../../../lib/testHelpers' import { initPromise, parse } from '../../../lang/wasm' -beforeAll(() => initPromise) +beforeAll(async () => { + await initPromise +}) describe('processMemory', () => { it('should grab the values and remove and geo data', async () => { diff --git a/src/editor/manager.ts b/src/editor/manager.ts new file mode 100644 index 000000000..81a81cb1b --- /dev/null +++ b/src/editor/manager.ts @@ -0,0 +1,238 @@ +import { hasNextSnippetField } from '@codemirror/autocomplete' +import { EditorView, ViewUpdate } from '@codemirror/view' +import { EditorSelection, SelectionRange } from '@codemirror/state' +import { engineCommandManager, sceneInfra } from 'lib/singletons' +import { ModelingMachineEvent } from 'machines/modelingMachine' +import { Selections, processCodeMirrorRanges, Selection } from 'lib/selections' +import { undo, redo } from '@codemirror/commands' +import { CommandBarMachineEvent } from 'machines/commandBarMachine' +import { addLineHighlight } from './highlightextension' +import { setDiagnostics, Diagnostic } from '@codemirror/lint' + +export default class EditorManager { + private _editorView: EditorView | null = null + + private _isShiftDown: boolean = false + private _selectionRanges: Selections = { + otherSelections: [], + codeBasedSelections: [], + } + + private _lastSelectionEvent: number | null = null + private _lastSelection: string = '' + private _lastEvent: { event: string; time: number } | null = null + + private _modelingSend: (eventInfo: ModelingMachineEvent) => void = () => {} + private _modelingEvent: ModelingMachineEvent | null = null + + private _commandBarSend: (eventInfo: CommandBarMachineEvent) => void = + () => {} + + private _convertToVariableEnabled: boolean = false + private _convertToVariableCallback: () => void = () => {} + + private _highlightRange: [number, number] = [0, 0] + + setEditorView(editorView: EditorView) { + this._editorView = editorView + } + + get editorView(): EditorView | null { + return this._editorView + } + + get isShiftDown(): boolean { + return this._isShiftDown + } + + setIsShiftDown(isShiftDown: boolean) { + this._isShiftDown = isShiftDown + } + + set selectionRanges(selectionRanges: Selections) { + this._selectionRanges = selectionRanges + } + + set lastSelectionEvent(time: number) { + this._lastSelectionEvent = time + } + + set modelingSend(send: (eventInfo: ModelingMachineEvent) => void) { + this._modelingSend = send + } + + set modelingEvent(event: ModelingMachineEvent) { + this._modelingEvent = event + } + + setCommandBarSend(send: (eventInfo: CommandBarMachineEvent) => void) { + this._commandBarSend = send + } + + commandBarSend(eventInfo: CommandBarMachineEvent): void { + return this._commandBarSend(eventInfo) + } + + get highlightRange(): [number, number] { + return this._highlightRange + } + + setHighlightRange(selection: Selection['range']): void { + this._highlightRange = selection + const editorView = this.editorView + const safeEnd = Math.min( + selection[1], + editorView?.state.doc.length || selection[1] + ) + if (editorView) { + editorView.dispatch({ + effects: addLineHighlight.of([selection[0], safeEnd]), + }) + } + } + + setDiagnostics(diagnostics: Diagnostic[]): void { + if (!this.editorView) return + this.editorView.dispatch(setDiagnostics(this.editorView.state, diagnostics)) + } + + undo() { + if (this._editorView) { + undo(this._editorView) + } + } + + redo() { + if (this._editorView) { + redo(this._editorView) + } + } + + set convertToVariableEnabled(enabled: boolean) { + this._convertToVariableEnabled = enabled + } + + set convertToVariableCallback(callback: () => void) { + this._convertToVariableCallback = callback + } + + convertToVariable() { + if (this._convertToVariableEnabled) { + this._convertToVariableCallback() + + return true + } + return false + } + + selectRange(selections: Selections) { + if (selections.codeBasedSelections.length === 0) { + return + } + if (!this.editorView) { + return + } + let codeBasedSelections = [] + for (const selection of selections.codeBasedSelections) { + codeBasedSelections.push( + EditorSelection.range(selection.range[0], selection.range[1]) + ) + } + + codeBasedSelections.push( + EditorSelection.cursor( + selections.codeBasedSelections[ + selections.codeBasedSelections.length - 1 + ].range[1] + ) + ) + this.editorView.dispatch({ + selection: EditorSelection.create(codeBasedSelections, 1), + }) + } + + handleOnViewUpdate(viewUpdate: ViewUpdate): void { + // 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 (hasNextSnippetField(viewUpdate.view.state)) { + return + } + + if (this.editorView === null) { + this.setEditorView(viewUpdate.view) + } + const selString = stringifyRanges( + viewUpdate?.state?.selection?.ranges || [] + ) + + if (selString === this._lastSelection) { + // onUpdate is noisy and is fired a lot by extensions + // since we're only interested in selections changes we can ignore most of these. + return + } + this._lastSelection = selString + + if ( + this._lastSelectionEvent && + Date.now() - this._lastSelectionEvent < 150 + ) { + return // update triggered by scene selection + } + + if (sceneInfra.selected) { + return // mid drag + } + + const ignoreEvents: ModelingMachineEvent['type'][] = [ + 'Equip Line tool', + 'Equip tangential arc to', + ] + + if (!this._modelingEvent) { + return + } + + if (ignoreEvents.includes(this._modelingEvent.type)) { + return + } + + const eventInfo = processCodeMirrorRanges({ + codeMirrorRanges: viewUpdate.state.selection.ranges, + selectionRanges: this._selectionRanges, + isShiftDown: this._isShiftDown, + }) + + if (!eventInfo) { + return + } + + const deterministicEventInfo = { + ...eventInfo, + engineEvents: eventInfo.engineEvents.map((e) => ({ + ...e, + cmd_id: 'static', + })), + } + const stringEvent = JSON.stringify(deterministicEventInfo) + if ( + this._lastEvent && + stringEvent === this._lastEvent.event && + Date.now() - this._lastEvent.time < 500 + ) { + return // don't repeat events + } + + this._lastEvent = { event: stringEvent, time: Date.now() } + this._modelingSend(eventInfo.modelingEvent) + eventInfo.engineEvents.forEach((event) => + engineCommandManager.sendSceneCommand(event) + ) + } +} + +function stringifyRanges(ranges: readonly SelectionRange[]): string { + return ranges.map(({ to, from }) => `${to}->${from}`).join('&') +} diff --git a/src/editor/plugins/lsp/plugin.ts b/src/editor/plugins/lsp/plugin.ts index 7edb2c525..cf1a199d0 100644 --- a/src/editor/plugins/lsp/plugin.ts +++ b/src/editor/plugins/lsp/plugin.ts @@ -21,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 { codeManager, kclManager } from 'lib/singletons' +import { codeManager, editorManager, 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' @@ -57,6 +57,9 @@ export class LanguageServerPlugin implements PluginValue { }, contentChanges: [{ text: code }], }) + if (editorManager.editorView) { + //editorManager.handleOnViewUpdate(editorManager.editorView) + } } catch (e) { console.error(e) } @@ -357,15 +360,9 @@ export class LanguageServerPlugin implements PluginValue { try { switch (notification.method) { case 'textDocument/publishDiagnostics': - const params = notification.params as PublishDiagnosticsParams - this.processDiagnostics(params) - // Update the kcl errors pane. - /*if (!kclManager.isExecuting) { - kclManager.kclErrors = lspDiagnosticsToKclErrors( - this.view.state.doc, - params.diagnostics - ) - }*/ + //const params = notification.params as PublishDiagnosticsParams + // this is sometimes slower than our actual typing. + //this.processDiagnostics(params) break case 'window/logMessage': console.log( @@ -385,17 +382,6 @@ 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) - // 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. diff --git a/src/hooks/useEngineConnectionSubscriptions.ts b/src/hooks/useEngineConnectionSubscriptions.ts index 7565e49dc..813f6db75 100644 --- a/src/hooks/useEngineConnectionSubscriptions.ts +++ b/src/hooks/useEngineConnectionSubscriptions.ts @@ -1,14 +1,9 @@ import { useEffect } from 'react' -import { useStore } from 'useStore' -import { engineCommandManager } from 'lib/singletons' +import { editorManager, engineCommandManager } from 'lib/singletons' import { useModelingContext } from './useModelingContext' import { getEventForSelectWithPoint } from 'lib/selections' export function useEngineConnectionSubscriptions() { - const { setHighlightRange, highlightRange } = useStore((s) => ({ - setHighlightRange: s.setHighlightRange, - highlightRange: s.highlightRange, - })) const { send, context } = useModelingContext() useEffect(() => { @@ -21,12 +16,13 @@ export function useEngineConnectionSubscriptions() { if (data?.entity_id) { const sourceRange = engineCommandManager.artifactMap?.[data.entity_id]?.range - setHighlightRange(sourceRange) + editorManager.setHighlightRange(sourceRange) } else if ( - !highlightRange || - (highlightRange[0] !== 0 && highlightRange[1] !== 0) + !editorManager.highlightRange || + (editorManager.highlightRange[0] !== 0 && + editorManager.highlightRange[1] !== 0) ) { - setHighlightRange([0, 0]) + editorManager.setHighlightRange([0, 0]) } }, }) @@ -43,10 +39,5 @@ export function useEngineConnectionSubscriptions() { unSubHover() unSubClick() } - }, [ - engineCommandManager, - setHighlightRange, - highlightRange, - context?.sketchEnginePathId, - ]) + }, [engineCommandManager, context?.sketchEnginePathId]) } diff --git a/src/hooks/useHotKeyListener.ts b/src/hooks/useHotKeyListener.ts index aac682cbe..10d6a3eed 100644 --- a/src/hooks/useHotKeyListener.ts +++ b/src/hooks/useHotKeyListener.ts @@ -1,4 +1,4 @@ -import { useStore } from '../useStore' +import { editorManager } from 'lib/singletons' import { useEffect } from 'react' // Kurt's note: codeMirror styling overrides were needed to make this work @@ -6,20 +6,17 @@ import { useEffect } from 'react' // search for code-mirror-override in the repo to find the relevant styles export function useHotKeyListener() { - const { setIsShiftDown } = useStore((s) => ({ - setIsShiftDown: s.setIsShiftDown, - })) const keyName = 'Shift' useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => - event.key === keyName && setIsShiftDown(true) + event.key === keyName && editorManager.setIsShiftDown(true) const handleKeyUp = (event: KeyboardEvent) => - event.key === keyName && setIsShiftDown(false) + event.key === keyName && editorManager.setIsShiftDown(false) window.addEventListener('keydown', handleKeyDown) window.addEventListener('keyup', handleKeyUp) return () => { window.removeEventListener('keydown', handleKeyDown) window.removeEventListener('keyup', handleKeyUp) } - }, [setIsShiftDown]) + }) } diff --git a/src/hooks/useToolbarGuards.ts b/src/hooks/useToolbarGuards.ts index a9670c2a2..48d925e48 100644 --- a/src/hooks/useToolbarGuards.ts +++ b/src/hooks/useToolbarGuards.ts @@ -2,7 +2,7 @@ import { SetVarNameModal, createSetVarNameModal, } from 'components/SetVarNameModal' -import { kclManager } from 'lib/singletons' +import { editorManager, kclManager } from 'lib/singletons' import { moveValueIntoNewVariable } from 'lang/modifyAst' import { isNodeSafeToReplace } from 'lang/queryAst' import { useEffect, useState } from 'react' @@ -13,6 +13,11 @@ const getModalInfo = createSetVarNameModal(SetVarNameModal) export function useConvertToVariable() { const { context } = useModelingContext() const [enable, setEnabled] = useState(false) + + useEffect(() => { + editorManager.convertToVariableEnabled = enable + }, [enable]) + useEffect(() => { const { isSafe, value } = isNodeSafeToReplace( kclManager.ast, @@ -45,5 +50,7 @@ export function useConvertToVariable() { } } + editorManager.convertToVariableCallback = handleClick + return { enable, handleClick } } diff --git a/src/lang/KclProvider.tsx b/src/lang/KclProvider.tsx index 2a0866a42..c4d47c956 100644 --- a/src/lang/KclProvider.tsx +++ b/src/lang/KclProvider.tsx @@ -2,12 +2,10 @@ import { KCLError } from './errors' 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 { codeManager, kclManager } from 'lib/singletons' const KclContext = createContext({ code: codeManager?.code || '', - editorCode: codeManager?.code || '', programMemory: kclManager?.programMemory, ast: kclManager?.ast, isExecuting: kclManager?.isExecuting, @@ -30,7 +28,6 @@ export function KclContextProvider({ const { code: loadedCode } = useLoaderData() as IndexLoaderData // 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) @@ -42,7 +39,6 @@ export function KclContextProvider({ useEffect(() => { codeManager.registerCallBacks({ setCode, - setEditorCode, }) kclManager.registerCallBacks({ setProgramMemory, @@ -54,15 +50,10 @@ export function KclContextProvider({ }) }, []) - const params = useParams() - useEffect(() => { - codeManager.setParams(params) - }, [params]) return ( initPromise) +beforeAll(async () => { + await initPromise +}) describe('testing AST', () => { test('5 + 6', () => { diff --git a/src/lang/artifact.test.ts b/src/lang/artifact.test.ts index f4fbe2937..573c73252 100644 --- a/src/lang/artifact.test.ts +++ b/src/lang/artifact.test.ts @@ -1,7 +1,9 @@ import { parse, initPromise } from './wasm' import { enginelessExecutor } from '../lib/testHelpers' -beforeAll(() => initPromise) +beforeAll(async () => { + await initPromise +}) describe('testing artifacts', () => { // Enable rotations #152 diff --git a/src/lang/codeManager.ts b/src/lang/codeManager.ts index d04d2f94f..0446d129c 100644 --- a/src/lang/codeManager.ts +++ b/src/lang/codeManager.ts @@ -5,7 +5,7 @@ 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' +import { editorManager } from 'lib/singletons' const PERSIST_CODE_TOKEN = 'persistCode' @@ -13,7 +13,7 @@ export default class CodeManager { private _code: string = bracket private _updateState: (arg: string) => void = () => {} private _updateEditor: (arg: string) => void = () => {} - private _params: Params = {} + private _currentFilePath: string | null = null constructor() { if (isTauri()) { @@ -45,19 +45,12 @@ export default class CodeManager { return this._code } - registerCallBacks({ - setCode, - setEditorCode, - }: { - setCode: (arg: string) => void - setEditorCode: (arg: string) => void - }) { + registerCallBacks({ setCode }: { setCode: (arg: string) => void }) { this._updateState = setCode - this._updateEditor = setEditorCode } - setParams(params: Params) { - this._params = params + updateCurrentFilePath(path: string) { + this._currentFilePath = path } // This updates the code state and calls the updateState function. @@ -70,11 +63,14 @@ export default class CodeManager { // Update the code in the editor. updateCodeEditor(code: string): void { - if (this._code !== code) { - this.code = code - this._updateEditor(code) - } + const lastCode = this._code + this.code = code this._updateEditor(code) + if (editorManager.editorView) { + editorManager.editorView.dispatch({ + changes: { from: 0, to: lastCode.length, insert: code }, + }) + } } // Update the code, state, and the code the code mirror editor sees. @@ -91,8 +87,8 @@ export default class CodeManager { 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) => { + this._currentFilePath && + writeTextFile(this._currentFilePath, 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') diff --git a/src/lang/executor.test.ts b/src/lang/executor.test.ts index 3da86f5d2..016c9524f 100644 --- a/src/lang/executor.test.ts +++ b/src/lang/executor.test.ts @@ -4,7 +4,9 @@ import { parse, ProgramMemory, SketchGroup, initPromise } from './wasm' import { enginelessExecutor } from '../lib/testHelpers' import { KCLError } from './errors' -beforeAll(() => initPromise) +beforeAll(async () => { + await initPromise +}) describe('test executor', () => { it('test assigning two variables, the second summing with the first', async () => { diff --git a/src/lang/getNodePathFromSourceRange.test.ts b/src/lang/getNodePathFromSourceRange.test.ts index 4c0ac56cf..44308078c 100644 --- a/src/lang/getNodePathFromSourceRange.test.ts +++ b/src/lang/getNodePathFromSourceRange.test.ts @@ -1,7 +1,9 @@ import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst' import { Identifier, parse, initPromise, Parameter } from './wasm' -beforeAll(() => initPromise) +beforeAll(async () => { + await initPromise +}) describe('testing getNodePathFromSourceRange', () => { it('test it gets the right path for a `lineTo` CallExpression within a SketchExpression', () => { diff --git a/src/lang/modifyAst.test.ts b/src/lang/modifyAst.test.ts index 59e42a64f..8e5b64917 100644 --- a/src/lang/modifyAst.test.ts +++ b/src/lang/modifyAst.test.ts @@ -17,7 +17,9 @@ import { import { enginelessExecutor } from '../lib/testHelpers' import { getNodePathFromSourceRange } from './queryAst' -beforeAll(() => initPromise) +beforeAll(async () => { + await initPromise +}) describe('Testing createLiteral', () => { it('should create a literal', () => { diff --git a/src/lang/queryAst.test.ts b/src/lang/queryAst.test.ts index 110c24c8a..b55047622 100644 --- a/src/lang/queryAst.test.ts +++ b/src/lang/queryAst.test.ts @@ -15,7 +15,9 @@ import { createPipeSubstitution, } from './modifyAst' -beforeAll(() => initPromise) +beforeAll(async () => { + await initPromise +}) describe('findAllPreviousVariables', () => { it('should find all previous variables', async () => { diff --git a/src/lang/recast.test.ts b/src/lang/recast.test.ts index 7985d41b2..0a0a5c948 100644 --- a/src/lang/recast.test.ts +++ b/src/lang/recast.test.ts @@ -1,7 +1,9 @@ import { parse, Program, recast, initPromise } from './wasm' import fs from 'node:fs' -beforeAll(() => initPromise) +beforeAll(async () => { + await initPromise +}) describe('recast', () => { it('recasts a simple program', () => { diff --git a/src/lang/std/sketch.test.ts b/src/lang/std/sketch.test.ts index dbee9375d..b247cf921 100644 --- a/src/lang/std/sketch.test.ts +++ b/src/lang/std/sketch.test.ts @@ -25,7 +25,9 @@ const eachQuad: [number, [number, number]][] = [ [675, [1, -1]], ] -beforeAll(() => initPromise) +beforeAll(async () => { + await initPromise +}) describe('testing getYComponent', () => { it('should return the vertical component of a vector correctly when given angles in each quadrant (and with angles < 0, or > 360)', () => { diff --git a/src/lang/std/sketchConstraints.test.ts b/src/lang/std/sketchConstraints.test.ts index b5eb8a09b..99dd29a27 100644 --- a/src/lang/std/sketchConstraints.test.ts +++ b/src/lang/std/sketchConstraints.test.ts @@ -8,7 +8,9 @@ import { getSketchSegmentFromSourceRange } from './sketchConstraints' import { Selection } from 'lib/selections' import { enginelessExecutor } from '../../lib/testHelpers' -beforeAll(() => initPromise) +beforeAll(async () => { + await initPromise +}) // testing helper function async function testingSwapSketchFnCall({ diff --git a/src/lang/std/sketchcombos.test.ts b/src/lang/std/sketchcombos.test.ts index 71d9acee8..d0f7c0a74 100644 --- a/src/lang/std/sketchcombos.test.ts +++ b/src/lang/std/sketchcombos.test.ts @@ -11,7 +11,9 @@ import { ToolTip } from '../../useStore' import { Selections } from 'lib/selections' import { enginelessExecutor } from '../../lib/testHelpers' -beforeAll(() => initPromise) +beforeAll(async () => { + await initPromise +}) describe('testing getConstraintType', () => { const helper = getConstraintTypeFromSourceHelper diff --git a/src/lang/std/std.test.ts b/src/lang/std/std.test.ts index fcc337d83..1d93894ad 100644 --- a/src/lang/std/std.test.ts +++ b/src/lang/std/std.test.ts @@ -1,7 +1,9 @@ import { parse, initPromise } from '../wasm' import { enginelessExecutor } from '../../lib/testHelpers' -beforeAll(() => initPromise) +beforeAll(async () => { + await initPromise +}) describe('testing angledLineThatIntersects', () => { it('angledLineThatIntersects should intersect with another line', async () => { diff --git a/src/lang/tokeniser.test.ts b/src/lang/tokeniser.test.ts index 99a84bb21..9f5a9a4d8 100644 --- a/src/lang/tokeniser.test.ts +++ b/src/lang/tokeniser.test.ts @@ -1,6 +1,8 @@ import { lexer, initPromise } from './wasm' -beforeAll(() => initPromise) +beforeAll(async () => { + await initPromise +}) describe('testing lexer', () => { it('async lexer works too', async () => { diff --git a/src/lang/wasm.ts b/src/lang/wasm.ts index 32653b2b0..8bffbf3ae 100644 --- a/src/lang/wasm.ts +++ b/src/lang/wasm.ts @@ -25,6 +25,7 @@ import { AppInfo } from 'wasm-lib/kcl/bindings/AppInfo' import { CoreDumpManager } from 'lib/coredump' import openWindow from 'lib/openWindow' import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes' +import { TEST } from 'env' export type { Program } from '../wasm-lib/kcl/bindings/Program' export type { Value } from '../wasm-lib/kcl/bindings/Value' @@ -95,10 +96,15 @@ export const wasmUrl = () => { // Initialise the wasm module. const initialise = async () => { - const fullUrl = wasmUrl() - const input = await fetch(fullUrl) - const buffer = await input.arrayBuffer() - return init(buffer) + try { + const fullUrl = wasmUrl() + const input = await fetch(fullUrl) + const buffer = await input.arrayBuffer() + return await init(buffer) + } catch (e) { + console.log('Error initialising WASM', e) + throw e + } } export const initPromise = initialise() @@ -153,10 +159,6 @@ export const executor = async ( return _programMemory } -const getSettingsState = import('components/SettingsAuthProvider').then( - (module) => module.getSettingsState -) - export const _executor = async ( node: Program, programMemory: ProgramMemory = { root: {}, return: null }, @@ -164,8 +166,14 @@ export const _executor = async ( isMock: boolean ): Promise => { try { - const baseUnit = - (await getSettingsState)()?.modeling.defaultUnit.current || 'mm' + let baseUnit = 'mm' + if (!TEST) { + const getSettingsState = import('components/SettingsAuthProvider').then( + (module) => module.getSettingsState + ) + baseUnit = + (await getSettingsState)()?.modeling.defaultUnit.current || 'mm' + } const memory: ProgramMemory = await execute_wasm( JSON.stringify(node), JSON.stringify(programMemory), diff --git a/src/lib/routeLoaders.ts b/src/lib/routeLoaders.ts index 3fbf1cbc2..75ead8ce8 100644 --- a/src/lib/routeLoaders.ts +++ b/src/lib/routeLoaders.ts @@ -103,6 +103,7 @@ export const fileLoader: LoaderFunction = async ({ // Update both the state and the editor's code. // We explicitly do not write to the file here since we are loading from // the file system and not the editor. + codeManager.updateCurrentFilePath(currentFilePath) codeManager.updateCodeStateEditor(code) kclManager.executeCode(true) diff --git a/src/lib/singletons.ts b/src/lib/singletons.ts index 24a4ec085..445df5053 100644 --- a/src/lib/singletons.ts +++ b/src/lib/singletons.ts @@ -1,5 +1,6 @@ import { SceneEntities } from 'clientSideScene/sceneEntities' import { SceneInfra } from 'clientSideScene/sceneInfra' +import EditorManager from 'editor/manager' import { KclManager } from 'lang/KclSingleton' import CodeManager from 'lang/codeManager' import { EngineCommandManager } from 'lang/std/engineConnection' @@ -8,6 +9,7 @@ export const codeManager = new CodeManager() export const engineCommandManager = new EngineCommandManager() +// This needs to be after codeManager is created. export const kclManager = new KclManager(engineCommandManager) engineCommandManager.getAstCb = () => kclManager.ast @@ -15,3 +17,6 @@ export const sceneInfra = new SceneInfra(engineCommandManager) engineCommandManager.camControlsCameraChange = sceneInfra.onCameraChange export const sceneEntitiesManager = new SceneEntities(engineCommandManager) + +// This needs to be after sceneInfra and engineCommandManager are is created. +export const editorManager = new EditorManager() diff --git a/src/lib/utils2d.test.ts b/src/lib/utils2d.test.ts index b6b550327..d64b0ca92 100644 --- a/src/lib/utils2d.test.ts +++ b/src/lib/utils2d.test.ts @@ -1,7 +1,9 @@ import { Coords2d } from 'lang/std/sketch' import { isPointsCCW, initPromise } from 'lang/wasm' -beforeAll(() => initPromise) +beforeAll(async () => { + await initPromise +}) describe('test isPointsCW', () => { test('basic test', () => { diff --git a/src/machines/commandBarMachine.ts b/src/machines/commandBarMachine.ts index da843e791..20e64d441 100644 --- a/src/machines/commandBarMachine.ts +++ b/src/machines/commandBarMachine.ts @@ -16,6 +16,54 @@ export type CommandBarContext = { argumentsToSubmit: { [x: string]: unknown } } +export type CommandBarMachineEvent = + | { type: 'Open' } + | { type: 'Close' } + | { type: 'Clear' } + | { + type: 'Select command' + data: { command: Command } + } + | { type: 'Deselect command' } + | { type: 'Submit command'; data: { [x: string]: unknown } } + | { + type: 'Add argument' + data: { argument: CommandArgumentWithName } + } + | { + type: 'Remove argument' + data: { [x: string]: CommandArgumentWithName } + } + | { + type: 'Edit argument' + data: { arg: CommandArgumentWithName } + } + | { + type: 'Add commands' + data: { commands: Command[] } + } + | { + type: 'Remove commands' + data: { commands: Command[] } + } + | { type: 'Submit argument'; data: { [x: string]: unknown } } + | { + type: 'done.invoke.validateArguments' + data: { [x: string]: unknown } + } + | { + type: 'error.platform.validateArguments' + data: { message: string; arg: CommandArgumentWithName } + } + | { + type: 'Find and select command' + data: { name: string; ownerMachine: string } + } + | { + type: 'Change current argument' + data: { [x: string]: CommandArgumentWithName } + } + export const commandBarMachine = createMachine( { /** @xstate-layout N4IgpgJg5mDOIC5QGED2BbdBDAdhABAEJYBOAxMgDaqxgDaADALqKgAONAlgC6eo6sQAD0QBaAJwA6AGwAmAKwBmBoukAWafIAcDcSoA0IAJ6JZDaZIDs8hgzV6AjA61a1DWQF8PhtJlwFiciowUkYWJBAOWB4+AQiRBFF5CwdpcVkHS1lpVyU5QxNEh1lFGTUsrUUtOQd5SwZLLx8MbDwiUkkqGkgyAHk2MBwwwSiY-kEEswZJbPltM0U3eXLZAsRFeQcrerUHRbTFvfkmkF9WgI6u2ggyADFONv98WkowAGNufDeW-2GI0d443iiHESkktUUilkskqdiUWjWCAcDC0kjUqnElkc6lkoK0JzOT0CnWo1zIAEEIARvn48LA-uwuIC4qAEqIHJjJPJcYtxFoYdJFFjVsZEG5pqCquJxJoGvJxOUCT82sSrj0AEpgdCoABuYC+yog9OYIyZsQmYmhWzMmTqLnU0kyikRGVRbj5lg2SmUam5StpFxIkgAymBXh8HlADQGyKHw58aecGZEzUDWYgchY7CisWoSlpQQ5EVDLJJxKkdIpyypUop-ed2kHCW0Xu9uD1kwDzcCEJCtlUNgWhZZ1OlnaKEBpZJItHVzDZ5xlpPWiZdDc8w22Ow5wozosyLUiVNMSg5ytVKmfrIipzO564z2otPVpI1vKd18SAOJYbgACzAEhI3wUgoAAV3QQZuFgCg-1wGAvjAkgSCgkCSHAyCcG4TtUxZYRED2TQZGSOw80qaQ7ARCc9mmZYSksextGkNJBRXFUOh-f9AOA0CIKgmCABE4E3D5oyTE1-lww8clKBjZF2Bh5SFZwXUUyRVDzJwNE2LQzzYwNJE4gCgJwKNeMw6DJHJAB3LAYlM-AHjYMDuFjMCACN0B4NCMKgnD927dMkWSUs8yY8RISfWQshvcsrDlapshULJPHfZsDKM7iHPM-jJAANSwShOAgX9IzICB+DASQHh1VAAGsqp1Qrit-MByXQvisP8sY8ISZwbCsDllBLSwz1qRFBQsRZ5WRIVkui-TG0M39jJ4jqLNgfLmpK3hTLIQCSFQIM2EoX8ADMjvQSQmqKna2vWvyJL3HrD1SEoyzSdJUkUm9dnUtQn10aK6hkxbiU1HVODAay3M87zE1+J6UwCtN8IQUauR0OZ0ksFwUUqRFPWmUdouPXR3TBjoIahmHKQIHKuqRrtUYSaVShhLF+TkeTzBFQosVKDRM2RVInEdSmg2p6GyE1bU9R8zrsKZqSexFqQHWlHNXHzCbFhnX1+UydFkVxiXJClmGAFEIG8hmld3ZGXp7TR5C5RSCxdjlQV1tR9bqaVdhsSFxDN5AALeOrgPa3ysJgiqcCqmr6sa7bWujxXjQd5nesQZYthsblIQYJx6hUic9AsdEFT2HQzByVLmgDJaw-eSOHPTjbysq6qcFqhrrtT9sO-4ugd1NFGc4QXEPo5eVLGsFxlnKREXGnZI9HcT1mOikO0s-S5w7bqNh9j-bkKOyQTvOy6B9utOHtj7qD1V8pSyrHJZ3STY4QmjQ0R2Ww0oSjuF3u+HAqAIBwEEOlRs48nZBVENkKQNprC42qBoJ0LothaXMJ9aElRXDLj3k3VUpJIBwOfgg4oMx8y41SPUOY8p5DFk2GiKoxsoRVmhGoM2cY2zAQRngChgU0a7HZtFH0ilRp1D5usVh6JXCKD2CUdI8lQ7rlbB8chkkJ6HkxFsBeulbQZAUCwrYCiqzIhyE+fqZtMomTMg-aCwiWYEV2NOBUCghQ6EFGzYsvpwQUT5MxawFECx2JWllRxMdLI2TsrtKMTkXIuMnmeCiZYwrePMFWCKv1XZKVGroWwmxNARK4g4hWG0tp3wSSk16lQpC41ULjV8uxUjSBvFCNEDTdCOkWCY44xCGzgzAJDaGdTnaZHBADYcqg5DpCovzeRno5izA0BeUOh8o5OPgDo+BaNvqV0xNFaE7gdYTnnvkmUChdKEMyF4LwQA */ @@ -227,53 +275,7 @@ export const commandBarMachine = createMachine( }, }, schema: { - events: {} as - | { type: 'Open' } - | { type: 'Close' } - | { type: 'Clear' } - | { - type: 'Select command' - data: { command: Command } - } - | { type: 'Deselect command' } - | { type: 'Submit command'; data: { [x: string]: unknown } } - | { - type: 'Add argument' - data: { argument: CommandArgumentWithName } - } - | { - type: 'Remove argument' - data: { [x: string]: CommandArgumentWithName } - } - | { - type: 'Edit argument' - data: { arg: CommandArgumentWithName } - } - | { - type: 'Add commands' - data: { commands: Command[] } - } - | { - type: 'Remove commands' - data: { commands: Command[] } - } - | { type: 'Submit argument'; data: { [x: string]: unknown } } - | { - type: 'done.invoke.validateArguments' - data: { [x: string]: unknown } - } - | { - type: 'error.platform.validateArguments' - data: { message: string; arg: CommandArgumentWithName } - } - | { - type: 'Find and select command' - data: { name: string; ownerMachine: string } - } - | { - type: 'Change current argument' - data: { [x: string]: CommandArgumentWithName } - }, + events: {} as CommandBarMachineEvent, }, preserveActionOrder: true, }, diff --git a/src/machines/modelingMachine.ts b/src/machines/modelingMachine.ts index 4574e4827..882ed4dd2 100644 --- a/src/machines/modelingMachine.ts +++ b/src/machines/modelingMachine.ts @@ -7,6 +7,7 @@ import { sceneInfra, sceneEntitiesManager, engineCommandManager, + editorManager, } from 'lib/singletons' import { horzVertInfo, @@ -800,7 +801,7 @@ export const modelingMachine = createMachine( sketchDetails.origin ) }, - 'AST extrude': (_, event) => { + 'AST extrude': async (_, event) => { if (!event.data) return const { selection, distance } = event.data let ast = kclManager.ast @@ -829,10 +830,12 @@ export const modelingMachine = createMachine( ? distance.variableIdentifierAst : distance.valueAst ) - // TODO not handling focusPath correctly I think - kclManager.updateAst(modifiedAst, true, { + const selections = await kclManager.updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg, }) + if (selections) { + editorManager.selectRange(selections) + } }, 'conditionally equip line tool': (_, { type }) => { if (type === 'done.invoke.animate-to-face') { diff --git a/src/useStore.ts b/src/useStore.ts index cafae767d..6fb0206e2 100644 --- a/src/useStore.ts +++ b/src/useStore.ts @@ -1,13 +1,11 @@ import { create } from 'zustand' import { persist } from 'zustand/middleware' -import { addLineHighlight, EditorView } from './editor/highlightextension' import { Program, _executor, ProgramMemory, programMemoryInit, } from './lang/wasm' -import { Selection } from 'lib/selections' import { enginelessExecutor } from './lib/testHelpers' import { EngineCommandManager } from './lang/std/engineConnection' import { KCLError } from './lang/errors' @@ -55,12 +53,6 @@ export type PaneType = | 'lspMessages' export interface StoreState { - editorView: EditorView | null - setEditorView: (editorView: EditorView) => void - highlightRange: [number, number] - setHighlightRange: (range: Selection['range']) => void - isShiftDown: boolean - setIsShiftDown: (isShiftDown: boolean) => void mediaStream?: MediaStream setMediaStream: (mediaStream: MediaStream) => void isStreamReady: boolean @@ -92,34 +84,12 @@ export interface StoreState { path: string }[] setHomeMenuItems: (items: { name: string; path: string }[]) => void - lastCodeMirrorSelectionUpdatedFromScene: number - setLastCodeMirrorSelectionUpdatedFromScene: (time: number) => void } export const useStore = create()( persist( (set, get) => { return { - editorView: null, - setEditorView: (editorView) => { - set({ editorView }) - }, - highlightRange: [0, 0], - setHighlightRange: (selection) => { - set({ highlightRange: selection }) - const editorView = get().editorView - const safeEnd = Math.min( - selection[1], - editorView?.state.doc.length || selection[1] - ) - if (editorView) { - editorView.dispatch({ - effects: addLineHighlight.of([selection[0], safeEnd]), - }) - } - }, - isShiftDown: false, - setIsShiftDown: (isShiftDown) => set({ isShiftDown }), setMediaStream: (mediaStream) => set({ mediaStream }), isStreamReady: false, setIsStreamReady: (isStreamReady) => set({ isStreamReady }), @@ -159,9 +129,6 @@ export const useStore = create()( setHomeShowMenu: (showHomeMenu) => set({ showHomeMenu }), homeMenuItems: [], setHomeMenuItems: (homeMenuItems) => set({ homeMenuItems }), - lastCodeMirrorSelectionUpdatedFromScene: Date.now(), - setLastCodeMirrorSelectionUpdatedFromScene: (time) => - set({ lastCodeMirrorSelectionUpdatedFromScene: time }), } }, { diff --git a/tsconfig.json b/tsconfig.json index 56b80659c..a96e737dc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,13 +4,14 @@ "paths": { "/*": ["src/*"] }, - "types": ["vite/client", "@types/wicg-file-system-access", "node", "@wdio/globals/types"], - "target": "esnext", - "lib": [ - "dom", - "dom.iterable", - "esnext" + "types": [ + "vite/client", + "@types/wicg-file-system-access", + "node", + "@wdio/globals/types" ], + "target": "esnext", + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, @@ -25,10 +26,6 @@ "noEmit": true, "jsx": "react-jsx" }, - "include": [ - "src", - "e2e", - "./*.ts" - ], + "include": ["src", "e2e", "./*.ts"], "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/tsconfig.node.json b/tsconfig.node.json index dcfd13701..9d31e2aed 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -5,7 +5,5 @@ "moduleResolution": "Node", "allowSyntheticDefaultImports": true }, - "include": [ - "vite.config.ts" - ] -} \ No newline at end of file + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts index 61927d378..0469f6047 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,15 +1,9 @@ import react from '@vitejs/plugin-react' import viteTsconfigPaths from 'vite-tsconfig-paths' import eslint from 'vite-plugin-eslint' -import dns from 'dns' import { defineConfig, configDefaults } from 'vitest/config' import version from 'vite-plugin-package-version' -// Only needed because we run Node < 17 -// and we want to open `localhost` not `127.0.0.1` on server start -// reference: https://vitejs.dev/config/server-options.html#server-host -dns.setDefaultResultOrder('verbatim') - const config = defineConfig({ server: { open: true, @@ -25,32 +19,38 @@ const config = defineConfig({ forks: { maxForks: 2, minForks: 1, - } + }, }, - setupFiles: 'src/setupTests.ts', + setupFiles: ['src/setupTests.ts', '@vitest/web-worker'], environment: 'happy-dom', coverage: { - provider: 'istanbul' // or 'v8' + provider: 'istanbul', // or 'v8' }, exclude: [...configDefaults.exclude, '**/e2e/playwright/**/*'], deps: { - inline: ['vitest-canvas-mock'] - } + optimizer: { + web: { + include: ['vitest-canvas-mock'], + }, + }, + }, + clearMocks: true, + restoreMocks: true, + mockReset: true, + reporters: process.env.GITHUB_ACTIONS + ? ['dot', 'github-actions'] + : ['verbose', 'hanging-process'], + testTimeout: 1000, + hookTimeout: 1000, + teardownTimeout: 1000, }, build: { outDir: 'build', }, - plugins: [ - react(), - viteTsconfigPaths(), - eslint(), - version(), - ], + plugins: [react(), viteTsconfigPaths(), eslint(), version()], worker: { - plugins: () => [ - viteTsconfigPaths(), - ], - } + plugins: () => [viteTsconfigPaths()], + }, }) export default config diff --git a/yarn.lock b/yarn.lock index fdc62311b..8f57b121d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1703,9 +1703,9 @@ integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== "@humanwhocodes/object-schema@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" - integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== "@iarna/toml@^2.2.5": version "2.2.5" @@ -2360,19 +2360,14 @@ integrity sha512-awNxydYSU+E2vL7EiOAMtiSOfL5gUM5X4YSE2A92qpxDJQ/rXz6oMPYBFDcDywlUmvIDI6zsqgq17cGm5CITQw== "@types/eslint@^8.4.5": - version "8.44.1" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.1.tgz#d1811559bb6bcd1a76009e3f7883034b78a0415e" - integrity sha512-XpNDc4Z5Tb4x+SW1MriMVeIsMoONHCkWFMkR/aPJbzEsxqHy+4Glu/BqTdPrApfDeMaXbtNh6bseNgl5KaWrSg== + version "8.56.10" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.10.tgz#eb2370a73bf04a901eeba8f22595c7ee0f7eb58d" + integrity sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ== dependencies: "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" - integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== - -"@types/estree@1.0.5", "@types/estree@^1.0.0": +"@types/estree@*", "@types/estree@1.0.5", "@types/estree@^1.0.0": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== @@ -2417,7 +2412,12 @@ expect "^29.0.0" pretty-format "^29.0.0" -"@types/json-schema@*", "@types/json-schema@^7.0.9": +"@types/json-schema@*": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/json-schema@^7.0.9": version "7.0.12" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== @@ -2764,6 +2764,13 @@ loupe "^2.3.7" pretty-format "^29.7.0" +"@vitest/web-worker@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@vitest/web-worker/-/web-worker-1.5.0.tgz#9de0e20b4a9cfed2a4958d757bd0594a76a2d4e9" + integrity sha512-WxX5VAgp8knJGTZknTDUICAVtNiDsBDQN1z/ldsDFqhRUFp09WLhRxWnKqCQc+CXk1W+kpUZmWDjxaD3i8QBmA== + dependencies: + debug "^4.3.4" + "@wdio/cli@^8.24.3": version "8.24.3" resolved "https://registry.yarnpkg.com/@wdio/cli/-/cli-8.24.3.tgz#305adc66cd2264ed4ef4549266bbb327826e10ef" @@ -5080,11 +5087,12 @@ find-up@^6.3.0: path-exists "^5.0.0" flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== dependencies: - flatted "^3.1.0" + flatted "^3.2.9" + keyv "^4.5.3" rimraf "^3.0.2" flat@^5.0.2: @@ -5092,10 +5100,10 @@ flat@^5.0.2: resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== -flatted@^3.1.0: - version "3.2.7" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" - integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== +flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== flux@^4.0.1: version "4.0.4" @@ -5436,9 +5444,9 @@ globals@^11.1.0: integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.19.0: - version "13.20.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" - integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== dependencies: type-fest "^0.20.2" @@ -8482,9 +8490,9 @@ tinybench@^2.5.1: integrity sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA== tinypool@^0.8.3: - version "0.8.3" - resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.8.3.tgz#e17d0a5315a7d425f875b05f7af653c225492d39" - integrity sha512-Ud7uepAklqRH1bvwy22ynrliC7Dljz7Tm8M/0RBUW+YRa4YHhZ6e4PpgE+fu1zr/WqB1kbeuVrdfeuyIBpy4tw== + version "0.8.4" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.8.4.tgz#e217fe1270d941b39e98c625dcecebb1408c9aa8" + integrity sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ== tinyspy@^2.2.0: version "2.2.1"