Merge branch 'main' into coredump-enginecommandmanager
17
README.md
@ -59,7 +59,9 @@ followed by:
|
||||
```
|
||||
yarn build:wasm-dev
|
||||
```
|
||||
|
||||
or if you have the gh cli installed
|
||||
|
||||
```
|
||||
./get-latest-wasm-bundle.sh # this will download the latest main wasm bundle
|
||||
```
|
||||
@ -100,6 +102,7 @@ yarn test
|
||||
Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testing Library E2E](https://testing-library.com/docs/react-testing-library/intro/) tests, in interactive mode by default.
|
||||
|
||||
For running the rust (not tauri rust though) only, you can
|
||||
|
||||
```bash
|
||||
cd src/wasm-lib
|
||||
cargo test
|
||||
@ -162,6 +165,7 @@ console.log(
|
||||
- `)
|
||||
)
|
||||
```
|
||||
|
||||
grab the md list and delete any that are older than the last bump
|
||||
|
||||
2. Merge the PR
|
||||
@ -191,23 +195,26 @@ $ cargo +nightly fuzz run parser
|
||||
For more information on fuzzing you can check out
|
||||
[this guide](https://rust-fuzz.github.io/book/cargo-fuzz.html).
|
||||
|
||||
|
||||
### Playwright
|
||||
|
||||
First time running plawright locally, you'll need to add the secrets file
|
||||
|
||||
```bash
|
||||
touch ./e2e/playwright/playwright-secrets.env
|
||||
printf 'token="your-token"\nsnapshottoken="your-snapshot-token"' > ./e2e/playwright/playwright-secrets.env
|
||||
```
|
||||
|
||||
then replace "your-token" with a dev token from dev.zoo.dev/account/api-tokens
|
||||
|
||||
then:
|
||||
run playwright
|
||||
|
||||
```
|
||||
yarn playwright test
|
||||
```
|
||||
|
||||
run a specific test suite
|
||||
|
||||
```
|
||||
yarn playwright test src/e2e-tests/example.spec.ts
|
||||
```
|
||||
@ -216,14 +223,17 @@ run a specific test change the test from `test('...` to `test.only('...`
|
||||
(note if you commit this, the tests will instantly fail without running any of the tests)
|
||||
|
||||
run headed
|
||||
|
||||
```
|
||||
yarn playwright test --headed
|
||||
```
|
||||
|
||||
run with step through debugger
|
||||
|
||||
```
|
||||
PWDEBUG=1 yarn playwright test
|
||||
```
|
||||
|
||||
However, if you want a debugger I recommend using VSCode and the `playwright` extension, as the above command is a cruder debugger that steps into every function call which is annoying.
|
||||
With the extension you can set a breakpoint after `waitForDefaultPlanesVisibilityChange` in order to skip app loading, then the vscode debugger's "step over" is much better for being able to stay at the right level of abstraction as you debug the code.
|
||||
|
||||
@ -268,7 +278,6 @@ Where `./store` should look like this
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
However because much of our tests involve clicking in the stream at specific locations, it's code-gen looks `await page.locator('video').click();` when really we need to use a pixel coord, so I think it's of limited use.
|
||||
|
||||
#### Some notes on CI
|
||||
@ -299,3 +308,7 @@ PS: for the debug panel, the following JSON is useful for snapping the camera
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## KCL
|
||||
|
||||
For how to contribute to KCL, [see our KCL README](https://github.com/KittyCAD/modeling-app/tree/main/src/wasm-lib/kcl).
|
||||
|
@ -56,6 +56,9 @@ layout: manual
|
||||
* [`patternLinear3d`](kcl/patternLinear3d)
|
||||
* [`pi`](kcl/pi)
|
||||
* [`pow`](kcl/pow)
|
||||
* [`profileStart`](kcl/profileStart)
|
||||
* [`profileStartX`](kcl/profileStartX)
|
||||
* [`profileStartY`](kcl/profileStartY)
|
||||
* [`revolve`](kcl/revolve)
|
||||
* [`segAng`](kcl/segAng)
|
||||
* [`segEndX`](kcl/segEndX)
|
||||
|
206
docs/kcl/profileStart.md
Normal file
201
docs/kcl/profileStartX.md
Normal file
200
docs/kcl/profileStartY.md
Normal file
2972
docs/kcl/std.json
@ -424,6 +424,80 @@ test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('error with 2 source ranges gets 2 diagnostics', async ({ page }) => {
|
||||
const u = getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const length = .750
|
||||
const width = 0.500
|
||||
const height = 0.500
|
||||
const dia = 4
|
||||
|
||||
fn squareHole = (l, w) => {
|
||||
const squareHoleSketch = startSketchOn('XY')
|
||||
|> startProfileAt([-width / 2, -length / 2], %)
|
||||
|> lineTo([width / 2, -length / 2], %)
|
||||
|> lineTo([width / 2, length / 2], %)
|
||||
|> lineTo([-width / 2, length / 2], %)
|
||||
|> close(%)
|
||||
return squareHoleSketch
|
||||
}
|
||||
`
|
||||
)
|
||||
})
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await page.goto('/')
|
||||
const lspStartPromise = page.waitForEvent('console', async (message) => {
|
||||
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
|
||||
// but that doesn't seem to make it to the console for macos/safari :(
|
||||
if (message.text().includes('start kcl lsp')) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await lspStartPromise
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
// Click on the bottom of the code editor to add a new line
|
||||
await page.click('.cm-content')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type(`const extrusion = startSketchOn('XY')
|
||||
|> circle([0, 0], dia/2, %)
|
||||
|> hole(squareHole(length, width, height), %)
|
||||
|> extrude(height, %)`)
|
||||
|
||||
// error in gutter
|
||||
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
||||
await page.hover('.cm-lint-marker-error:first-child')
|
||||
await expect(page.getByText('Expected 2 arguments, got 3')).toBeVisible()
|
||||
|
||||
// Make sure there are two diagnostics
|
||||
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(2)
|
||||
})
|
||||
|
||||
test('if your kcl gets an error from the engine it is inlined', async ({
|
||||
page,
|
||||
}) => {
|
||||
@ -646,8 +720,8 @@ test('Auto complete works', async ({ page }) => {
|
||||
await page.click('.cm-content')
|
||||
await page.keyboard.type('const part001 = start')
|
||||
|
||||
// expect there to be three auto complete options
|
||||
await expect(page.locator('.cm-completionLabel')).toHaveCount(3)
|
||||
// expect there to be six auto complete options
|
||||
await expect(page.locator('.cm-completionLabel')).toHaveCount(6)
|
||||
await page.getByText('startSketchOn').click()
|
||||
await page.keyboard.type("'XZ'")
|
||||
await page.keyboard.press('Tab')
|
||||
@ -775,6 +849,57 @@ test('Project settings can be set and override user settings', async ({
|
||||
await expect(page.locator('select[name="app-theme"]')).toHaveValue('light')
|
||||
})
|
||||
|
||||
test('Project settings can be opened with keybinding from the editor', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/', { waitUntil: 'domcontentloaded' })
|
||||
await page
|
||||
.getByRole('button', { name: 'Start Sketch' })
|
||||
.waitFor({ state: 'visible' })
|
||||
|
||||
// Put the cursor in the editor
|
||||
await page.click('.cm-content')
|
||||
|
||||
// Open the settings modal with the browser keyboard shortcut
|
||||
await page.keyboard.press('Meta+Shift+,')
|
||||
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Settings', exact: true })
|
||||
).toBeVisible()
|
||||
await page
|
||||
.locator('select[name="app-theme"]')
|
||||
.selectOption({ value: 'light' })
|
||||
|
||||
// Verify the toast appeared
|
||||
await expect(
|
||||
page.getByText(`Set theme to "light" for this project`)
|
||||
).toBeVisible()
|
||||
// Check that the theme changed
|
||||
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||
|
||||
// Check that the user setting was not changed
|
||||
await page.getByRole('radio', { name: 'User' }).click()
|
||||
await expect(page.locator('select[name="app-theme"]')).toHaveValue('dark')
|
||||
|
||||
// Roll back to default "system" theme
|
||||
await page
|
||||
.getByText(
|
||||
'themeRoll back themeRoll back to match defaultThe overall appearance of the appl'
|
||||
)
|
||||
.hover()
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Roll back theme ; Has tooltip: Roll back to match default',
|
||||
})
|
||||
.click()
|
||||
await expect(page.locator('select[name="app-theme"]')).toHaveValue('system')
|
||||
|
||||
// Check that the project setting did not change
|
||||
await page.getByRole('radio', { name: 'Project' }).click()
|
||||
await expect(page.locator('select[name="app-theme"]')).toHaveValue('light')
|
||||
})
|
||||
|
||||
test('Click through each onboarding step', async ({ page }) => {
|
||||
const u = getUtils(page)
|
||||
|
||||
@ -1093,6 +1218,51 @@ test.describe('Command bar tests', () => {
|
||||
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||
})
|
||||
|
||||
test('Command bar keybinding works from code editor and can change a setting', async ({
|
||||
page,
|
||||
}) => {
|
||||
// Brief boilerplate
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/', { waitUntil: 'domcontentloaded' })
|
||||
|
||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
|
||||
// Put the cursor in the code editor
|
||||
await page.click('.cm-content')
|
||||
|
||||
// Now try the same, but with the keyboard shortcut, check focus
|
||||
await page.keyboard.press('Meta+K')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
await expect(cmdSearchBar).toBeFocused()
|
||||
|
||||
// Try typing in the command bar
|
||||
await page.keyboard.type('theme')
|
||||
const themeOption = page.getByRole('option', {
|
||||
name: 'Settings · app · theme',
|
||||
})
|
||||
await expect(themeOption).toBeVisible()
|
||||
await themeOption.click()
|
||||
const themeInput = page.getByPlaceholder('Select an option')
|
||||
await expect(themeInput).toBeVisible()
|
||||
await expect(themeInput).toBeFocused()
|
||||
// Select dark theme
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await expect(page.getByRole('option', { name: 'system' })).toHaveAttribute(
|
||||
'data-headlessui-state',
|
||||
'active'
|
||||
)
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Check the toast appeared
|
||||
await expect(
|
||||
page.getByText(`Set theme to "system" for this project`)
|
||||
).toBeVisible()
|
||||
// Check that the theme changed
|
||||
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||
})
|
||||
|
||||
test('Can extrude from the command bar', async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
|
10
package.json
@ -17,12 +17,12 @@
|
||||
"@replit/codemirror-interact": "^6.3.1",
|
||||
"@tauri-apps/api": "2.0.0-beta.8",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-fs": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-fs": "^2.0.0-beta.3",
|
||||
"@tauri-apps/plugin-http": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-os": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-process": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-shell": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-updater": "^2.0.0-beta.2",
|
||||
"@tauri-apps/plugin-updater": "^2.0.0-beta.3",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^15.0.2",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
@ -52,15 +52,15 @@
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-modal": "^3.16.1",
|
||||
"react-modal-promise": "^1.0.2",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"sketch-helpers": "^0.0.4",
|
||||
"swr": "^2.2.5",
|
||||
"three": "^0.163.0",
|
||||
"three": "^0.164.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.5",
|
||||
"ua-parser-js": "^1.0.37",
|
||||
"uuid": "^9.0.1",
|
||||
"vitest": "^1.5.0",
|
||||
"vitest": "^1.6.0",
|
||||
"vscode-jsonrpc": "^8.1.0",
|
||||
"vscode-languageserver-protocol": "^3.17.5",
|
||||
"wasm-pack": "^0.12.1",
|
||||
|
@ -23,6 +23,7 @@ import { useRefreshSettings } from 'hooks/useRefreshSettings'
|
||||
import { ModelingSidebar } from 'components/ModelingSidebar/ModelingSidebar'
|
||||
import { LowerRightControls } from 'components/LowerRightControls'
|
||||
import ModalContainer from 'react-modal-promise'
|
||||
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
||||
|
||||
export function App() {
|
||||
useRefreshSettings(paths.FILE + 'SETTINGS')
|
||||
@ -63,8 +64,8 @@ export function App() {
|
||||
useHotkeys('backspace', (e) => {
|
||||
e.preventDefault()
|
||||
})
|
||||
useHotkeys(
|
||||
isTauri() ? 'mod + ,' : 'shift + mod + ,',
|
||||
useHotkeyWrapper(
|
||||
[isTauri() ? 'mod + ,' : 'shift + mod + ,'],
|
||||
() => navigate(filePath + paths.SETTINGS),
|
||||
{
|
||||
splitKey: '|',
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { Dialog, Popover, Transition } from '@headlessui/react'
|
||||
import { Fragment, useEffect } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import CommandBarArgument from './CommandBarArgument'
|
||||
import CommandComboBox from '../CommandComboBox'
|
||||
import CommandBarReview from './CommandBarReview'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
||||
|
||||
export const CommandBar = () => {
|
||||
const { pathname } = useLocation()
|
||||
@ -22,7 +22,7 @@ export const CommandBar = () => {
|
||||
}, [pathname])
|
||||
|
||||
// Hook up keyboard shortcuts
|
||||
useHotkeys(['mod+k', 'mod+/'], () => {
|
||||
useHotkeyWrapper(['mod+k', 'mod+/'], () => {
|
||||
if (commandBarState.context.commands.length === 0) return
|
||||
if (commandBarState.matches('Closed')) {
|
||||
commandBarSend({ type: 'Open' })
|
||||
|
@ -8,7 +8,6 @@ import { Dialog, Disclosure } from '@headlessui/react'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faChevronRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { useFileContext } from 'hooks/useFileContext'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import styles from './FileTree.module.css'
|
||||
import { sortProject } from 'lib/tauriFS'
|
||||
import { FILE_EXT } from 'lib/constants'
|
||||
@ -16,6 +15,7 @@ import { CustomIcon } from './CustomIcon'
|
||||
import { codeManager, kclManager } from 'lib/singletons'
|
||||
import { useDocumentHasFocus } from 'hooks/useDocumentHasFocus'
|
||||
import { useLspContext } from './LspProvider'
|
||||
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
||||
|
||||
function getIndentationCSS(level: number) {
|
||||
return `calc(1rem * ${level + 1})`
|
||||
@ -333,8 +333,8 @@ export const FileTreeMenu = () => {
|
||||
send({ type: 'Create file', data: { name: '', makeDir: true } })
|
||||
}
|
||||
|
||||
useHotkeys('meta + n', createFile)
|
||||
useHotkeys('meta + shift + n', createFolder)
|
||||
useHotkeyWrapper(['meta + n'], createFile)
|
||||
useHotkeyWrapper(['meta + shift + n'], createFolder)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -55,9 +55,9 @@ import { Models } from '@kittycad/lib/dist/types/src'
|
||||
import toast from 'react-hot-toast'
|
||||
import { EditorSelection } from '@uiw/react-codemirror'
|
||||
import { CoreDumpManager } from 'lib/coredump'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
||||
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -103,7 +103,7 @@ export const ModelingMachineProvider = ({
|
||||
htmlRef,
|
||||
token
|
||||
)
|
||||
useHotkeys('meta + shift + .', () => coreDump(coreDumpManager, true))
|
||||
useHotkeyWrapper(['meta + shift + .'], () => coreDump(coreDumpManager, true))
|
||||
|
||||
// Settings machine setup
|
||||
// const retrievedSettings = useRef(
|
||||
|
@ -36,10 +36,6 @@ import {
|
||||
import interact from '@replit/codemirror-interact'
|
||||
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 } from '@codemirror/state'
|
||||
import {
|
||||
@ -67,7 +63,6 @@ export const KclEditorPane = () => {
|
||||
? getSystemTheme()
|
||||
: context.app.theme.current
|
||||
const { copilotLSP, kclLSP } = useLspContext()
|
||||
const navigate = useNavigate()
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return
|
||||
@ -76,6 +71,8 @@ export const KclEditorPane = () => {
|
||||
return () => window.removeEventListener('online', onlineCallback)
|
||||
}, [])
|
||||
|
||||
// Since these already exist in the editor, we don't need to define them
|
||||
// with the wrapper.
|
||||
useHotkeys('mod+z', (e) => {
|
||||
e.preventDefault()
|
||||
editorManager.undo()
|
||||
@ -87,6 +84,7 @@ export const KclEditorPane = () => {
|
||||
|
||||
const textWrapping = context.textEditor.textWrapping
|
||||
const cursorBlinking = context.textEditor.blinkingCursor
|
||||
const codeMirrorHotkeys = codeManager.getCodemirrorHotkeys()
|
||||
|
||||
const editorExtensions = useMemo(() => {
|
||||
const extensions = [
|
||||
@ -106,20 +104,7 @@ export const KclEditorPane = () => {
|
||||
...completionKeymap,
|
||||
...lintKeymap,
|
||||
indentWithTab,
|
||||
{
|
||||
key: 'Meta-k',
|
||||
run: () => {
|
||||
editorManager.commandBarSend({ type: 'Open' })
|
||||
return false
|
||||
},
|
||||
},
|
||||
{
|
||||
key: isTauri() ? 'Meta-,' : 'Meta-Shift-,',
|
||||
run: () => {
|
||||
navigate(makeUrlPathRelative(paths.SETTINGS))
|
||||
return false
|
||||
},
|
||||
},
|
||||
...codeMirrorHotkeys,
|
||||
{
|
||||
key: editorShortcutMeta.convertToVariable.codeMirror,
|
||||
run: () => {
|
||||
@ -188,7 +173,13 @@ export const KclEditorPane = () => {
|
||||
}
|
||||
|
||||
return extensions
|
||||
}, [kclLSP, copilotLSP, textWrapping.current, cursorBlinking.current])
|
||||
}, [
|
||||
kclLSP,
|
||||
copilotLSP,
|
||||
textWrapping.current,
|
||||
cursorBlinking.current,
|
||||
codeMirrorHotkeys,
|
||||
])
|
||||
|
||||
const initialCode = useRef(codeManager.code)
|
||||
|
||||
|
@ -6,6 +6,7 @@ import { isTauri } from 'lib/isTauri'
|
||||
import { writeTextFile } from '@tauri-apps/plugin-fs'
|
||||
import toast from 'react-hot-toast'
|
||||
import { editorManager } from 'lib/singletons'
|
||||
import { KeyBinding } from '@uiw/react-codemirror'
|
||||
|
||||
const PERSIST_CODE_TOKEN = 'persistCode'
|
||||
|
||||
@ -13,6 +14,7 @@ export default class CodeManager {
|
||||
private _code: string = bracket
|
||||
#updateState: (arg: string) => void = () => {}
|
||||
private _currentFilePath: string | null = null
|
||||
private _hotkeys: { [key: string]: () => void } = {}
|
||||
|
||||
constructor() {
|
||||
if (isTauri()) {
|
||||
@ -48,6 +50,20 @@ export default class CodeManager {
|
||||
this.#updateState = setCode
|
||||
}
|
||||
|
||||
registerHotkey(hotkey: string, callback: () => void) {
|
||||
this._hotkeys[hotkey] = callback
|
||||
}
|
||||
|
||||
getCodemirrorHotkeys(): KeyBinding[] {
|
||||
return Object.keys(this._hotkeys).map((key) => ({
|
||||
key,
|
||||
run: () => {
|
||||
this._hotkeys[key]()
|
||||
return false
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
updateCurrentFilePath(path: string) {
|
||||
this._currentFilePath = path
|
||||
}
|
||||
|
@ -1,20 +1,21 @@
|
||||
import { ToolTip } from '../useStore'
|
||||
import { Selection, Selections } from 'lib/selections'
|
||||
import {
|
||||
ArrayExpression,
|
||||
BinaryExpression,
|
||||
Program,
|
||||
SyntaxType,
|
||||
Value,
|
||||
CallExpression,
|
||||
ExpressionStatement,
|
||||
VariableDeclaration,
|
||||
ReturnStatement,
|
||||
ArrayExpression,
|
||||
PathToNode,
|
||||
PipeExpression,
|
||||
Program,
|
||||
ProgramMemory,
|
||||
ReturnStatement,
|
||||
SketchGroup,
|
||||
SourceRange,
|
||||
PipeExpression,
|
||||
SyntaxType,
|
||||
Value,
|
||||
VariableDeclaration,
|
||||
VariableDeclarator,
|
||||
} from './wasm'
|
||||
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
|
||||
import { getSketchSegmentFromSourceRange } from './std/sketchConstraints'
|
||||
@ -295,6 +296,58 @@ export function getNodePathFromSourceRange(
|
||||
return path
|
||||
}
|
||||
|
||||
type KCLNode =
|
||||
| Value
|
||||
| ExpressionStatement
|
||||
| VariableDeclaration
|
||||
| VariableDeclarator
|
||||
| ReturnStatement
|
||||
|
||||
export function traverse(
|
||||
node: KCLNode,
|
||||
option: {
|
||||
enter?: (node: KCLNode) => void
|
||||
leave?: (node: KCLNode) => void
|
||||
}
|
||||
) {
|
||||
option?.enter?.(node)
|
||||
const _traverse = (node: KCLNode) => traverse(node, option)
|
||||
|
||||
if (node.type === 'VariableDeclaration') {
|
||||
node.declarations.forEach(_traverse)
|
||||
} else if (node.type === 'VariableDeclarator') {
|
||||
_traverse(node.init)
|
||||
} else if (node.type === 'PipeExpression') {
|
||||
node.body.forEach(_traverse)
|
||||
} else if (node.type === 'CallExpression') {
|
||||
_traverse(node.callee)
|
||||
node.arguments.forEach(_traverse)
|
||||
} else if (node.type === 'BinaryExpression') {
|
||||
_traverse(node.left)
|
||||
_traverse(node.right)
|
||||
} else if (node.type === 'Identifier') {
|
||||
// do nothing
|
||||
} else if (node.type === 'Literal') {
|
||||
// do nothing
|
||||
} else if (node.type === 'ArrayExpression') {
|
||||
node.elements.forEach(_traverse)
|
||||
} else if (node.type === 'ObjectExpression') {
|
||||
node.properties.forEach(({ key, value }) => {
|
||||
_traverse(key)
|
||||
_traverse(value)
|
||||
})
|
||||
} else if (node.type === 'UnaryExpression') {
|
||||
_traverse(node.argument)
|
||||
} else if (node.type === 'MemberExpression') {
|
||||
// hmm this smell
|
||||
_traverse(node.object)
|
||||
_traverse(node.property)
|
||||
} else if ('body' in node && Array.isArray(node.body)) {
|
||||
node.body.forEach(_traverse)
|
||||
}
|
||||
option?.leave?.(node)
|
||||
}
|
||||
|
||||
export interface PrevVariable<T> {
|
||||
key: string
|
||||
value: T
|
||||
|
@ -551,6 +551,7 @@ class EngineConnection {
|
||||
// Everything is now connected.
|
||||
this.state = { type: EngineConnectionStateType.ConnectionEstablished }
|
||||
|
||||
this.engineCommandManager.inSequence = 1
|
||||
this.onEngineConnectionOpen(this)
|
||||
})
|
||||
|
||||
|
37
src/lib/hotkeyWrapper.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { Options, useHotkeys } from 'react-hotkeys-hook'
|
||||
import { useEffect } from 'react'
|
||||
import { codeManager } from './singletons'
|
||||
|
||||
// Hotkey wrapper wraps hotkeys for the app (outside of the editor)
|
||||
// With hotkeys inside the editor.
|
||||
// This way we can have hotkeys defined in one place and not have to worry about
|
||||
// conflicting hotkeys, or them only being implemented for the app but not
|
||||
// inside the editor.
|
||||
// TODO: would be nice if this didn't have to be a react hook. It's not needed
|
||||
// for the code mirror stuff but but it is needed for the useHotkeys hook.
|
||||
export default function useHotkeyWrapper(
|
||||
hotkey: string[],
|
||||
callback: () => void,
|
||||
additionalOptions?: Options
|
||||
) {
|
||||
useHotkeys(hotkey, callback, additionalOptions)
|
||||
useEffect(() => {
|
||||
for (const key of hotkey) {
|
||||
const keybinding = mapHotkeyToCodeMirrorHotkey(key)
|
||||
codeManager.registerHotkey(keybinding, callback)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Convert hotkey to code mirror hotkey
|
||||
// See: https://codemirror.net/docs/ref/#view.KeyBinding
|
||||
function mapHotkeyToCodeMirrorHotkey(hotkey: string): string {
|
||||
return hotkey
|
||||
.replaceAll('+', '-')
|
||||
.replaceAll(' ', '')
|
||||
.replaceAll('mod', 'Meta')
|
||||
.replaceAll('meta', 'Meta')
|
||||
.replaceAll('ctrl', 'Ctrl')
|
||||
.replaceAll('shift', 'Shift')
|
||||
.replaceAll('alt', 'Alt')
|
||||
}
|
40
src/wasm-lib/Cargo.lock
generated
@ -177,6 +177,15 @@ dependencies = [
|
||||
"num-traits 0.2.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.7.0"
|
||||
@ -979,6 +988,17 @@ dependencies = [
|
||||
"syn 2.0.65",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.65",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diesel_derives"
|
||||
version = "2.1.3"
|
||||
@ -1851,15 +1871,6 @@ dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.12.1"
|
||||
@ -2674,7 +2685,7 @@ dependencies = [
|
||||
"bincode",
|
||||
"either",
|
||||
"fnv",
|
||||
"itertools 0.11.0",
|
||||
"itertools 0.12.1",
|
||||
"lazy_static",
|
||||
"nom",
|
||||
"quick-xml",
|
||||
@ -5303,13 +5314,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "0.6.6"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
|
||||
checksum = "f1f4a27345eb6f7aa7bd015ba7eb4175fa4e1b462a29874b779e0bbcf96c6ac7"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"arbitrary",
|
||||
"crc32fast",
|
||||
"crossbeam-utils",
|
||||
"displaydoc",
|
||||
"indexmap 2.2.5",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -43,7 +43,7 @@ url = { version = "2.5.0", features = ["serde"] }
|
||||
uuid = { version = "1.8.0", features = ["v4", "js", "serde"] }
|
||||
validator = { version = "0.18.1", features = ["derive"] }
|
||||
winnow = "0.5.40"
|
||||
zip = { version = "0.6.6", default-features = false }
|
||||
zip = { version = "1.3.0", default-features = false }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
js-sys = { version = "0.3.69" }
|
||||
|
20
src/wasm-lib/kcl/README.md
Normal file
@ -0,0 +1,20 @@
|
||||
# KCL
|
||||
|
||||
Our language for defining geometry and working with our Geometry Engine efficiently. Short for KittyCAD Language, named after our Design API.
|
||||
|
||||
## Contributing a standard library function
|
||||
|
||||
We've built a lot of tooling to make contributing to KCL easier. If you are interested in contributing a new standard library function to KCL, here is the rough process:
|
||||
|
||||
1. Open just the wasm-lib folder in your editor of choice. VS Code, for example, struggles to run rust-analyzer on the entire modeling-app directory because it's such a turducken of TS and Rust code.
|
||||
2. Find the definition for similar standard library functions in `./kcl/src/std` and place your new one near it or in the same category file.
|
||||
3. Add your new code. A new standard library function consists of:
|
||||
4. A `pub async` of the actual standard library function in Rust
|
||||
5. A doc comment block containing at least one example using your new standard library function (the Rust compiler will error if you don't provide an example our teammates are dope)
|
||||
6. A `stdlib` macro providing the name that will need to be written by KCL users to use the function (this is usually a camelCase version of your Rust implementation, which is named with snake_case)
|
||||
7. An inner function that is published only to the crate
|
||||
8. Add your new standard library function to [the long list of CORE_FNS in mod.rs](https://github.com/KittyCAD/modeling-app/blob/main/src/wasm-lib/kcl/src/std/mod.rs#L42)
|
||||
9. Get a production Zoo dev token and run `export KITTYCAD_API_TOKEN=your-token-here` in a terminal
|
||||
10. Run `TWENTY_TWENTY=overwrite cargo nextest run --workspace --no-fail-fast` to take snapshot tests of your example code running in the engine
|
||||
11. Run `EXPECTORATE=overwrite cargo test --all generate_stdlib -- --nocapture` to generate new Markdown documentation for your function that will be used [to generate docs on our website](https://zoo.dev/docs/kcl).
|
||||
12. Create a PR in GitHub.
|
@ -1214,7 +1214,11 @@ impl CallExpression {
|
||||
let func = memory.get(&fn_name, self.into())?;
|
||||
let result = func
|
||||
.call_fn(fn_args, memory.clone(), ctx.clone())
|
||||
.await?
|
||||
.await
|
||||
.map_err(|e| {
|
||||
// Add the call expression to the source ranges.
|
||||
e.add_source_ranges(vec![self.into()])
|
||||
})?
|
||||
.ok_or_else(|| {
|
||||
KclError::UndefinedValue(KclErrorDetails {
|
||||
message: format!("Result of user-defined function {} is undefined", fn_name),
|
||||
|
@ -142,6 +142,25 @@ impl KclError {
|
||||
|
||||
new
|
||||
}
|
||||
|
||||
pub fn add_source_ranges(&self, source_ranges: Vec<SourceRange>) -> Self {
|
||||
let mut new = self.clone();
|
||||
match &mut new {
|
||||
KclError::Lexical(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::Syntax(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::Semantic(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::Type(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::Unimplemented(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::Unexpected(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::ValueAlreadyDefined(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::UndefinedValue(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::InvalidExpression(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::Engine(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::Internal(e) => e.source_ranges.extend(source_ranges),
|
||||
}
|
||||
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
/// This is different than to_string() in that it will serialize the Error
|
||||
|
@ -498,14 +498,13 @@ impl Backend {
|
||||
for (entry, value) in self.code_map.inner().await.iter() {
|
||||
let file_name = entry.replace("file://", "").to_string();
|
||||
|
||||
let options = zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored);
|
||||
let options = zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
|
||||
zip.start_file(file_name, options)?;
|
||||
zip.write_all(value)?;
|
||||
}
|
||||
// Apply the changes you've made.
|
||||
// Dropping the `ZipWriter` will have the same effect, but may silently fail
|
||||
zip.finish()?;
|
||||
drop(zip);
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
|
@ -533,3 +533,38 @@ pub async fn to_degrees(args: Args) -> Result<MemoryItem, KclError> {
|
||||
fn inner_to_degrees(num: f64) -> Result<f64, KclError> {
|
||||
Ok(num.to_degrees())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_inner_max() {
|
||||
let nums = vec![4.0, 5.0, 6.0];
|
||||
let result = inner_max(nums);
|
||||
assert_eq!(result, 6.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inner_max_with_neg() {
|
||||
let nums = vec![4.0, -5.0];
|
||||
let result = inner_max(nums);
|
||||
assert_eq!(result, 4.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inner_min() {
|
||||
let nums = vec![4.0, 5.0, 6.0];
|
||||
let result = inner_min(nums);
|
||||
assert_eq!(result, 4.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inner_min_with_neg() {
|
||||
let nums = vec![4.0, -5.0];
|
||||
let result = inner_min(nums);
|
||||
assert_eq!(result, -5.0);
|
||||
}
|
||||
}
|
||||
|
@ -68,6 +68,9 @@ lazy_static! {
|
||||
Box::new(crate::std::sketch::StartSketchAt),
|
||||
Box::new(crate::std::sketch::StartSketchOn),
|
||||
Box::new(crate::std::sketch::StartProfileAt),
|
||||
Box::new(crate::std::sketch::ProfileStartX),
|
||||
Box::new(crate::std::sketch::ProfileStartY),
|
||||
Box::new(crate::std::sketch::ProfileStart),
|
||||
Box::new(crate::std::sketch::Close),
|
||||
Box::new(crate::std::sketch::Arc),
|
||||
Box::new(crate::std::sketch::TangentialArc),
|
||||
|
@ -12,7 +12,7 @@ use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{
|
||||
BasePath, ExtrudeGroup, ExtrudeSurface, Face, GeoMeta, MemoryItem, Path, Plane, PlaneType, Point2d, Point3d,
|
||||
Position, Rotation, SketchGroup, SketchGroupSet, SketchSurface, SourceRange,
|
||||
Position, Rotation, SketchGroup, SketchGroupSet, SketchSurface, SourceRange, UserVal,
|
||||
},
|
||||
std::{
|
||||
utils::{
|
||||
@ -995,7 +995,14 @@ async fn start_sketch_on_face(
|
||||
args: Args,
|
||||
) -> Result<Box<Face>, KclError> {
|
||||
let extrude_plane_id = match tag {
|
||||
SketchOnFaceTag::String(ref s) => extrude_group
|
||||
SketchOnFaceTag::String(ref s) => {
|
||||
if s.is_empty() {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message: "Expected a non-empty tag for the face to sketch on".to_string(),
|
||||
source_ranges: vec![args.source_range],
|
||||
}));
|
||||
}
|
||||
extrude_group
|
||||
.value
|
||||
.iter()
|
||||
.find_map(|extrude_surface| match extrude_surface {
|
||||
@ -1015,7 +1022,8 @@ async fn start_sketch_on_face(
|
||||
message: format!("Expected a face with the tag `{}`", tag),
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})??,
|
||||
})??
|
||||
}
|
||||
SketchOnFaceTag::StartOrEnd(StartOrEnd::Start) => extrude_group.start_cap_id.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: "Expected a start face to sketch on".to_string(),
|
||||
@ -1206,6 +1214,78 @@ pub(crate) async fn inner_start_profile_at(
|
||||
Ok(Box::new(sketch_group))
|
||||
}
|
||||
|
||||
/// Returns the X component of the sketch profile start point.
|
||||
pub async fn profile_start_x(args: Args) -> Result<MemoryItem, KclError> {
|
||||
let sketch_group: Box<SketchGroup> = args.get_sketch_group()?;
|
||||
let x = inner_profile_start_x(sketch_group)?;
|
||||
args.make_user_val_from_f64(x)
|
||||
}
|
||||
|
||||
/// ```no_run
|
||||
/// const sketch001 = startSketchOn('XY')
|
||||
/// |> startProfileAt([5, 2], %)
|
||||
/// |> angledLine([-26.6, 50], %)
|
||||
/// |> angledLine([90, 50], %)
|
||||
/// |> angledLineToX({ angle: 30, to: profileStartX(%) }, %)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "profileStartX"
|
||||
}]
|
||||
pub(crate) fn inner_profile_start_x(sketch_group: Box<SketchGroup>) -> Result<f64, KclError> {
|
||||
Ok(sketch_group.start.to[0])
|
||||
}
|
||||
|
||||
/// Returns the Y component of the sketch profile start point.
|
||||
pub async fn profile_start_y(args: Args) -> Result<MemoryItem, KclError> {
|
||||
let sketch_group: Box<SketchGroup> = args.get_sketch_group()?;
|
||||
let x = inner_profile_start_y(sketch_group)?;
|
||||
args.make_user_val_from_f64(x)
|
||||
}
|
||||
|
||||
/// ```no_run
|
||||
/// const sketch001 = startSketchOn('XY')
|
||||
/// |> startProfileAt([5, 2], %)
|
||||
/// |> angledLine({ angle: -60, length: 14 }, %)
|
||||
/// |> angledLineToY({ angle: 30, to: profileStartY(%) }, %)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "profileStartY"
|
||||
}]
|
||||
pub(crate) fn inner_profile_start_y(sketch_group: Box<SketchGroup>) -> Result<f64, KclError> {
|
||||
Ok(sketch_group.start.to[1])
|
||||
}
|
||||
|
||||
/// Returns the sketch profile start point.
|
||||
pub async fn profile_start(args: Args) -> Result<MemoryItem, KclError> {
|
||||
let sketch_group: Box<SketchGroup> = args.get_sketch_group()?;
|
||||
let point = inner_profile_start(sketch_group)?;
|
||||
Ok(MemoryItem::UserVal(UserVal {
|
||||
value: serde_json::to_value(point).map_err(|e| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!("Failed to convert point to json: {}", e),
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})?,
|
||||
meta: Default::default(),
|
||||
}))
|
||||
}
|
||||
|
||||
/// ```no_run
|
||||
/// const sketch001 = startSketchOn('XY')
|
||||
/// |> startProfileAt([5, 2], %)
|
||||
/// |> angledLine({ angle: 120, length: 50 }, %, 'seg01')
|
||||
/// |> angledLine({ angle: segAng('seg01', %) + 120, length: 50 }, %)
|
||||
/// |> lineTo(profileStart(%), %)
|
||||
/// |> close(%)
|
||||
/// |> extrude(20, %)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "profileStart"
|
||||
}]
|
||||
pub(crate) fn inner_profile_start(sketch_group: Box<SketchGroup>) -> Result<[f64; 2], KclError> {
|
||||
Ok(sketch_group.start.to)
|
||||
}
|
||||
|
||||
/// Close the current sketch.
|
||||
pub async fn close(args: Args) -> Result<MemoryItem, KclError> {
|
||||
let (sketch_group, tag): (Box<SketchGroup>, Option<String>) = args.get_sketch_group_and_optional_tag()?;
|
||||
|
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 214 KiB |
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
After Width: | Height: | Size: 106 KiB |
After Width: | Height: | Size: 109 KiB |
After Width: | Height: | Size: 95 KiB |
@ -2057,3 +2057,58 @@ const bracket = startSketchOn('XY')
|
||||
r#"engine: KclErrorDetails { source_ranges: [SourceRange([1443, 1443])], message: "Modeling command failed: Some([ApiError { error_code: BadRequest, message: \"Fillet failed\" }])" }"#
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_error_empty_start_sketch_on_string() {
|
||||
let code = r#"const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([75.75, 184.25], %)
|
||||
|> line([190.03, -118.13], %)
|
||||
|> line([-33.38, -202.86], %)
|
||||
|> line([-315.86, -64.2], %)
|
||||
|> tangentialArcTo([-147.66, 121.34], %)
|
||||
|> close(%)
|
||||
|> extrude(100, %)
|
||||
|
||||
const secondSketch = startSketchOn(part001, '')
|
||||
|> circle([-20, 50], 40, %)
|
||||
|> extrude(20, %)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, kcl_lib::settings::types::UnitLength::Mm).await;
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
result.err().unwrap().to_string(),
|
||||
r#"type: KclErrorDetails { source_ranges: [SourceRange([272, 298])], message: "Expected a non-empty tag for the face to sketch on" }"#
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_error_user_function_wrong_args() {
|
||||
let code = r#"const length = .750
|
||||
const width = 0.500
|
||||
const height = 0.500
|
||||
const dia = 4
|
||||
|
||||
fn squareHole = (l, w) => {
|
||||
const squareHoleSketch = startSketchOn('XY')
|
||||
|> startProfileAt([-width / 2, -length / 2], %)
|
||||
|> lineTo([width / 2, -length / 2], %)
|
||||
|> lineTo([width / 2, length / 2], %)
|
||||
|> lineTo([-width / 2, length / 2], %)
|
||||
|> close(%)
|
||||
return squareHoleSketch
|
||||
}
|
||||
|
||||
const extrusion = startSketchOn('XY')
|
||||
|> circle([0, 0], dia/2, %)
|
||||
|> hole(squareHole(length, width, height), %)
|
||||
|> extrude(height, %)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, kcl_lib::settings::types::UnitLength::Mm).await;
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
result.err().unwrap().to_string(),
|
||||
r#"semantic: KclErrorDetails { source_ranges: [SourceRange([92, 364]), SourceRange([444, 477])], message: "Expected 2 arguments, got 3" }"#
|
||||
);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 145 KiB |
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 136 KiB |
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 140 KiB |
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 123 KiB |
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 134 KiB |
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 214 KiB |
Before Width: | Height: | Size: 211 KiB After Width: | Height: | Size: 212 KiB |
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 173 KiB |
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 147 KiB |
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
137
yarn.lock
@ -1970,10 +1970,10 @@
|
||||
"@react-hook/latest" "^1.0.2"
|
||||
"@react-hook/passive-layout-effect" "^1.2.0"
|
||||
|
||||
"@remix-run/router@1.15.3":
|
||||
version "1.15.3"
|
||||
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.15.3.tgz#d2509048d69dbb72d5389a14945339f1430b2d3c"
|
||||
integrity sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==
|
||||
"@remix-run/router@1.16.1":
|
||||
version "1.16.1"
|
||||
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.16.1.tgz#73db3c48b975eeb06d0006481bde4f5f2d17d1cd"
|
||||
integrity sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==
|
||||
|
||||
"@replit/codemirror-interact@^6.3.1":
|
||||
version "6.3.1"
|
||||
@ -2106,6 +2106,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.2.0.tgz#874d36135e4badce2719e7bdc556ce240cbaff14"
|
||||
integrity sha512-P5XgYoAw/vfW65byBbJQCw+cagdXDT/qH6wmABiLt4v4YBT2q2vqCOhihe+D1Nt325F/S/0Tkv6C5z0Lv+VBQQ==
|
||||
|
||||
"@tauri-apps/api@2.0.0-beta.11":
|
||||
version "2.0.0-beta.11"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-beta.11.tgz#7ec6f6f007da4e55315270619448ce39fd11b4e3"
|
||||
integrity sha512-wJRY+fBUm3KpqZDHMIz5HRv+1vlnvRJ/dFxiyY3NlINTx2qXqDou5qWYcP1CuZXsd39InWVPV3FAZvno/kGCkA==
|
||||
|
||||
"@tauri-apps/api@2.0.0-beta.4":
|
||||
version "2.0.0-beta.4"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-beta.4.tgz#7688950f6e03f38b3bac73585f8f4cdd61be6aa6"
|
||||
@ -2189,12 +2194,12 @@
|
||||
dependencies:
|
||||
"@tauri-apps/api" "2.0.0-beta.4"
|
||||
|
||||
"@tauri-apps/plugin-fs@^2.0.0-beta.2":
|
||||
version "2.0.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-fs/-/plugin-fs-2.0.0-beta.2.tgz#b2dfcd72422f778e4c32edcfad24c1e96299c760"
|
||||
integrity sha512-jqeRBrm0h9QUoep5OzHx5R0vgFCYVAmZIy45jJpR7hHvnEgUwDU8JLUUVPvWniq6tUtxjwr1V/a0Hm9pE9V+NQ==
|
||||
"@tauri-apps/plugin-fs@^2.0.0-beta.3":
|
||||
version "2.0.0-beta.3"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-fs/-/plugin-fs-2.0.0-beta.3.tgz#461628704a10ce5177c49f3f65153aa9ecd8df3b"
|
||||
integrity sha512-LBgA7S10NwcitHaugIfmCSkewz45vSz1VOpMHhzvE38i1r1KpuTSHlr3MZ0LLq93tH/lvhYZ+3LAml4Sriwthw==
|
||||
dependencies:
|
||||
"@tauri-apps/api" "2.0.0-beta.4"
|
||||
"@tauri-apps/api" "2.0.0-beta.11"
|
||||
|
||||
"@tauri-apps/plugin-http@^2.0.0-beta.2":
|
||||
version "2.0.0-beta.2"
|
||||
@ -2224,12 +2229,12 @@
|
||||
dependencies:
|
||||
"@tauri-apps/api" "2.0.0-beta.4"
|
||||
|
||||
"@tauri-apps/plugin-updater@^2.0.0-beta.2":
|
||||
version "2.0.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-updater/-/plugin-updater-2.0.0-beta.2.tgz#60002e54ad647a56db5e1b0b54e792f399d425a4"
|
||||
integrity sha512-T8EkAXawbyV/6/Lcf1VVIWhtGuals63zKn+udYNqlC8CRM5iYQ+8bM8Nmy2E+pIzkkx93d1t6/8geFitLZPmKw==
|
||||
"@tauri-apps/plugin-updater@^2.0.0-beta.3":
|
||||
version "2.0.0-beta.3"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-updater/-/plugin-updater-2.0.0-beta.3.tgz#d6403363e540005e4bd38ff8d0cd689f40db96d0"
|
||||
integrity sha512-bD1ikPz80uK9YJKNYpYlA6StSp9lr0Ob1kGLG2XdmOgspv7SZLLNVzMORtKeqgepxwG99qdYGDDegT3Ll6+UlA==
|
||||
dependencies:
|
||||
"@tauri-apps/api" "2.0.0-beta.4"
|
||||
"@tauri-apps/api" "2.0.0-beta.11"
|
||||
|
||||
"@testing-library/dom@^10.0.0":
|
||||
version "10.0.0"
|
||||
@ -2720,44 +2725,44 @@
|
||||
"@types/babel__core" "^7.20.5"
|
||||
react-refresh "^0.14.0"
|
||||
|
||||
"@vitest/expect@1.5.0":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-1.5.0.tgz#961190510a2723bd4abf5540bcec0a4dfd59ef14"
|
||||
integrity sha512-0pzuCI6KYi2SIC3LQezmxujU9RK/vwC1U9R0rLuGlNGcOuDWxqWKu6nUdFsX9tH1WU0SXtAxToOsEjeUn1s3hA==
|
||||
"@vitest/expect@1.6.0":
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-1.6.0.tgz#0b3ba0914f738508464983f4d811bc122b51fb30"
|
||||
integrity sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==
|
||||
dependencies:
|
||||
"@vitest/spy" "1.5.0"
|
||||
"@vitest/utils" "1.5.0"
|
||||
"@vitest/spy" "1.6.0"
|
||||
"@vitest/utils" "1.6.0"
|
||||
chai "^4.3.10"
|
||||
|
||||
"@vitest/runner@1.5.0":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-1.5.0.tgz#1f7cb78ee4064e73e53d503a19c1b211c03dfe0c"
|
||||
integrity sha512-7HWwdxXP5yDoe7DTpbif9l6ZmDwCzcSIK38kTSIt6CFEpMjX4EpCgT6wUmS0xTXqMI6E/ONmfgRKmaujpabjZQ==
|
||||
"@vitest/runner@1.6.0":
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-1.6.0.tgz#a6de49a96cb33b0e3ba0d9064a3e8d6ce2f08825"
|
||||
integrity sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==
|
||||
dependencies:
|
||||
"@vitest/utils" "1.5.0"
|
||||
"@vitest/utils" "1.6.0"
|
||||
p-limit "^5.0.0"
|
||||
pathe "^1.1.1"
|
||||
|
||||
"@vitest/snapshot@1.5.0", "@vitest/snapshot@^1.2.2":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-1.5.0.tgz#cd2d611fd556968ce8fb6b356a09b4593c525947"
|
||||
integrity sha512-qpv3fSEuNrhAO3FpH6YYRdaECnnRjg9VxbhdtPwPRnzSfHVXnNzzrpX4cJxqiwgRMo7uRMWDFBlsBq4Cr+rO3A==
|
||||
"@vitest/snapshot@1.6.0", "@vitest/snapshot@^1.2.2":
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-1.6.0.tgz#deb7e4498a5299c1198136f56e6e0f692e6af470"
|
||||
integrity sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==
|
||||
dependencies:
|
||||
magic-string "^0.30.5"
|
||||
pathe "^1.1.1"
|
||||
pretty-format "^29.7.0"
|
||||
|
||||
"@vitest/spy@1.5.0":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-1.5.0.tgz#1369a1bec47f46f18eccfa45f1e8fbb9b5e15e77"
|
||||
integrity sha512-vu6vi6ew5N5MMHJjD5PoakMRKYdmIrNJmyfkhRpQt5d9Ewhw9nZ5Aqynbi3N61bvk9UvZ5UysMT6ayIrZ8GA9w==
|
||||
"@vitest/spy@1.6.0":
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-1.6.0.tgz#362cbd42ccdb03f1613798fde99799649516906d"
|
||||
integrity sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==
|
||||
dependencies:
|
||||
tinyspy "^2.2.0"
|
||||
|
||||
"@vitest/utils@1.5.0":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-1.5.0.tgz#90c9951f4516f6d595da24876b58e615f6c99863"
|
||||
integrity sha512-BDU0GNL8MWkRkSRdNFvCUCAVOeHaUlVJ9Tx0TYBZyXaaOTmGtUFObzchCivIBrIwKzvZA7A9sCejVhXM2aY98A==
|
||||
"@vitest/utils@1.6.0":
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-1.6.0.tgz#5c5675ca7d6f546a7b4337de9ae882e6c57896a1"
|
||||
integrity sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==
|
||||
dependencies:
|
||||
diff-sequences "^29.6.3"
|
||||
estree-walker "^3.0.3"
|
||||
@ -7614,20 +7619,20 @@ react-refresh@^0.14.0:
|
||||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e"
|
||||
integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==
|
||||
|
||||
react-router-dom@^6.22.3:
|
||||
version "6.22.3"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.22.3.tgz#9781415667fd1361a475146c5826d9f16752a691"
|
||||
integrity sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==
|
||||
react-router-dom@^6.23.1:
|
||||
version "6.23.1"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.23.1.tgz#30cbf266669693e9492aa4fc0dde2541ab02322f"
|
||||
integrity sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==
|
||||
dependencies:
|
||||
"@remix-run/router" "1.15.3"
|
||||
react-router "6.22.3"
|
||||
"@remix-run/router" "1.16.1"
|
||||
react-router "6.23.1"
|
||||
|
||||
react-router@6.22.3:
|
||||
version "6.22.3"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.22.3.tgz#9d9142f35e08be08c736a2082db5f0c9540a885e"
|
||||
integrity sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==
|
||||
react-router@6.23.1:
|
||||
version "6.23.1"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.23.1.tgz#d08cbdbd9d6aedc13eea6e94bc6d9b29cb1c4be9"
|
||||
integrity sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==
|
||||
dependencies:
|
||||
"@remix-run/router" "1.15.3"
|
||||
"@remix-run/router" "1.16.1"
|
||||
|
||||
react-textarea-autosize@^8.3.2:
|
||||
version "8.5.2"
|
||||
@ -8469,10 +8474,10 @@ thenify-all@^1.0.0:
|
||||
dependencies:
|
||||
any-promise "^1.0.0"
|
||||
|
||||
three@^0.163.0:
|
||||
version "0.163.0"
|
||||
resolved "https://registry.yarnpkg.com/three/-/three-0.163.0.tgz#cbfefbfd64a1353ab7cc8bf0fc396ddca1875a49"
|
||||
integrity sha512-HlMgCb2TF/dTLRtknBnjUTsR8FsDqBY43itYop2+Zg822I+Kd0Ua2vs8CvfBVefXkBdNDrLMoRTGCIIpfCuDew==
|
||||
three@^0.164.1:
|
||||
version "0.164.1"
|
||||
resolved "https://registry.yarnpkg.com/three/-/three-0.164.1.tgz#b742f76bd8dfd3736ba0d86a12dfddb73c5cdcc0"
|
||||
integrity sha512-iC/hUBbl1vzFny7f5GtqzVXYjMJKaTPxiCxXfrvVdBi1Sf+jhd1CAkitiFwC7mIBFCo3MrDLJG97yisoaWig0w==
|
||||
|
||||
through@^2.3.8:
|
||||
version "2.3.8"
|
||||
@ -8857,10 +8862,10 @@ validate-npm-package-license@^3.0.4:
|
||||
spdx-correct "^3.0.0"
|
||||
spdx-expression-parse "^3.0.0"
|
||||
|
||||
vite-node@1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-1.5.0.tgz#7f74dadfecb15bca016c5ce5ef85e5cc4b82abf2"
|
||||
integrity sha512-tV8h6gMj6vPzVCa7l+VGq9lwoJjW8Y79vst8QZZGiuRAfijU+EEWuc0kFpmndQrWhMMhet1jdSF+40KSZUqIIw==
|
||||
vite-node@1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-1.6.0.tgz#2c7e61129bfecc759478fa592754fd9704aaba7f"
|
||||
integrity sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==
|
||||
dependencies:
|
||||
cac "^6.7.14"
|
||||
debug "^4.3.4"
|
||||
@ -8910,16 +8915,16 @@ vitest-webgl-canvas-mock@^1.1.0:
|
||||
cssfontparser "^1.2.1"
|
||||
parse-color "^1.0.0"
|
||||
|
||||
vitest@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/vitest/-/vitest-1.5.0.tgz#6ebb396bd358650011a9c96c18fa614b668365c1"
|
||||
integrity sha512-d8UKgR0m2kjdxDWX6911uwxout6GHS0XaGH1cksSIVVG8kRlE7G7aBw7myKQCvDI5dT4j7ZMa+l706BIORMDLw==
|
||||
vitest@^1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/vitest/-/vitest-1.6.0.tgz#9d5ad4752a3c451be919e412c597126cffb9892f"
|
||||
integrity sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==
|
||||
dependencies:
|
||||
"@vitest/expect" "1.5.0"
|
||||
"@vitest/runner" "1.5.0"
|
||||
"@vitest/snapshot" "1.5.0"
|
||||
"@vitest/spy" "1.5.0"
|
||||
"@vitest/utils" "1.5.0"
|
||||
"@vitest/expect" "1.6.0"
|
||||
"@vitest/runner" "1.6.0"
|
||||
"@vitest/snapshot" "1.6.0"
|
||||
"@vitest/spy" "1.6.0"
|
||||
"@vitest/utils" "1.6.0"
|
||||
acorn-walk "^8.3.2"
|
||||
chai "^4.3.10"
|
||||
debug "^4.3.4"
|
||||
@ -8933,7 +8938,7 @@ vitest@^1.5.0:
|
||||
tinybench "^2.5.1"
|
||||
tinypool "^0.8.3"
|
||||
vite "^5.0.0"
|
||||
vite-node "1.5.0"
|
||||
vite-node "1.6.0"
|
||||
why-is-node-running "^2.2.2"
|
||||
|
||||
vscode-jsonrpc@8.2.0, vscode-jsonrpc@^8.1.0:
|
||||
|