Merge branch 'main' into pierremtb/adhoc/pnpm
This commit is contained in:
		@ -3,6 +3,7 @@ import { HomePageFixture } from './fixtures/homePageFixture'
 | 
			
		||||
import { getUtils } from './test-utils'
 | 
			
		||||
import { EngineCommand } from 'lang/std/artifactGraph'
 | 
			
		||||
import { uuidv4 } from 'lib/utils'
 | 
			
		||||
import { SceneFixture } from './fixtures/sceneFixture'
 | 
			
		||||
 | 
			
		||||
test.describe(
 | 
			
		||||
  'Can create sketches on all planes and their back sides',
 | 
			
		||||
@ -11,16 +12,17 @@ test.describe(
 | 
			
		||||
    const sketchOnPlaneAndBackSideTest = async (
 | 
			
		||||
      page: Page,
 | 
			
		||||
      homePage: HomePageFixture,
 | 
			
		||||
      scene: SceneFixture,
 | 
			
		||||
      plane: string,
 | 
			
		||||
      clickCoords: { x: number; y: number }
 | 
			
		||||
    ) => {
 | 
			
		||||
      const u = await getUtils(page)
 | 
			
		||||
      const PUR = 400 / 37.5 //pixeltoUnitRatio
 | 
			
		||||
      await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
 | 
			
		||||
      await homePage.goToModelingScene()
 | 
			
		||||
      // FIXME: Cannot use scene.waitForExecutionDone() since there is no KCL code
 | 
			
		||||
      await page.waitForTimeout(10000)
 | 
			
		||||
      const XYPlanRed: [number, number, number] = [98, 50, 51]
 | 
			
		||||
      await scene.expectPixelColor(XYPlanRed, { x: 700, y: 300 }, 15)
 | 
			
		||||
 | 
			
		||||
      await u.openDebugPanel()
 | 
			
		||||
 | 
			
		||||
      const coord =
 | 
			
		||||
@ -43,7 +45,7 @@ test.describe(
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const code = `sketch001 = startSketchOn('${plane}')profile001 = startProfileAt([0.9, -1.22], sketch001)`
 | 
			
		||||
      const code = `sketch001 = startSketchOn('${plane}')profile001 = startProfileAt([0.91, -1.22], sketch001)`
 | 
			
		||||
 | 
			
		||||
      await u.openDebugPanel()
 | 
			
		||||
 | 
			
		||||
@ -56,17 +58,14 @@ test.describe(
 | 
			
		||||
 | 
			
		||||
      await u.closeDebugPanel()
 | 
			
		||||
      await page.mouse.click(clickCoords.x, clickCoords.y)
 | 
			
		||||
      await page.waitForTimeout(300) // wait for animation
 | 
			
		||||
      await page.waitForTimeout(600) // wait for animation
 | 
			
		||||
 | 
			
		||||
      await expect(
 | 
			
		||||
        page.getByRole('button', { name: 'line Line', exact: true })
 | 
			
		||||
      ).toBeVisible()
 | 
			
		||||
 | 
			
		||||
      // draw a line
 | 
			
		||||
      const startXPx = 600
 | 
			
		||||
 | 
			
		||||
      await u.closeDebugPanel()
 | 
			
		||||
      await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
 | 
			
		||||
      await page.mouse.click(707, 393)
 | 
			
		||||
 | 
			
		||||
      await expect(page.locator('.cm-content')).toHaveText(code)
 | 
			
		||||
 | 
			
		||||
@ -81,49 +80,50 @@ test.describe(
 | 
			
		||||
      await u.clearCommandLogs()
 | 
			
		||||
      await u.removeCurrentCode()
 | 
			
		||||
    }
 | 
			
		||||
    test('XY', async ({ page, homePage }) => {
 | 
			
		||||
 | 
			
		||||
    const planeConfigs = [
 | 
			
		||||
      {
 | 
			
		||||
        plane: 'XY',
 | 
			
		||||
        coords: { x: 600, y: 388 },
 | 
			
		||||
        description: 'red plane',
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        plane: 'YZ',
 | 
			
		||||
        coords: { x: 700, y: 250 },
 | 
			
		||||
        description: 'green plane',
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        plane: 'XZ',
 | 
			
		||||
        coords: { x: 684, y: 427 },
 | 
			
		||||
        description: 'blue plane',
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        plane: '-XY',
 | 
			
		||||
        coords: { x: 600, y: 118 },
 | 
			
		||||
        description: 'back of red plane',
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        plane: '-YZ',
 | 
			
		||||
        coords: { x: 700, y: 219 },
 | 
			
		||||
        description: 'back of green plane',
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        plane: '-XZ',
 | 
			
		||||
        coords: { x: 700, y: 80 },
 | 
			
		||||
        description: 'back of blue plane',
 | 
			
		||||
      },
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    for (const config of planeConfigs) {
 | 
			
		||||
      test(config.plane, async ({ page, homePage, scene }) => {
 | 
			
		||||
        await sketchOnPlaneAndBackSideTest(
 | 
			
		||||
          page,
 | 
			
		||||
          homePage,
 | 
			
		||||
        'XY',
 | 
			
		||||
        { x: 600, y: 388 } // red plane
 | 
			
		||||
        // { x: 600, y: 400 }, // red plane // clicks grid helper and that causes problems, should fix so that these coords work too.
 | 
			
		||||
          scene,
 | 
			
		||||
          config.plane,
 | 
			
		||||
          config.coords
 | 
			
		||||
        )
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
    test('YZ', async ({ page, homePage }) => {
 | 
			
		||||
      await sketchOnPlaneAndBackSideTest(page, homePage, 'YZ', {
 | 
			
		||||
        x: 700,
 | 
			
		||||
        y: 250,
 | 
			
		||||
      }) // green plane
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    test('XZ', async ({ page, homePage }) => {
 | 
			
		||||
      await sketchOnPlaneAndBackSideTest(page, homePage, '-XZ', {
 | 
			
		||||
        x: 700,
 | 
			
		||||
        y: 80,
 | 
			
		||||
      }) // blue plane
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    test('-XY', async ({ page, homePage }) => {
 | 
			
		||||
      await sketchOnPlaneAndBackSideTest(page, homePage, '-XY', {
 | 
			
		||||
        x: 600,
 | 
			
		||||
        y: 118,
 | 
			
		||||
      }) // back of red plane
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    test('-YZ', async ({ page, homePage }) => {
 | 
			
		||||
      await sketchOnPlaneAndBackSideTest(page, homePage, '-YZ', {
 | 
			
		||||
        x: 700,
 | 
			
		||||
        y: 219,
 | 
			
		||||
      }) // back of green plan
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    test('-XZ', async ({ page, homePage }) => {
 | 
			
		||||
      await sketchOnPlaneAndBackSideTest(page, homePage, 'XZ', {
 | 
			
		||||
        x: 700,
 | 
			
		||||
        y: 427,
 | 
			
		||||
      }) // back of blue plane
 | 
			
		||||
    })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -629,6 +629,39 @@ extrude001 = extrude(sketch001, length = 50)
 | 
			
		||||
      )
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test(`Toolbar doesn't show modeling tools during sketch plane selection animation`, async ({
 | 
			
		||||
    page,
 | 
			
		||||
    homePage,
 | 
			
		||||
    toolbar,
 | 
			
		||||
  }) => {
 | 
			
		||||
    await test.step('Load an empty file', async () => {
 | 
			
		||||
      await page.addInitScript(async () => {
 | 
			
		||||
        localStorage.setItem('persistCode', '')
 | 
			
		||||
      })
 | 
			
		||||
      await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
      await homePage.goToModelingScene()
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    const toolBarMode = () =>
 | 
			
		||||
      page.locator('[data-currentMode]').getAttribute('data-currentMode')
 | 
			
		||||
 | 
			
		||||
    await test.step('Start sketch and select a plane', async () => {
 | 
			
		||||
      await expect.poll(toolBarMode).toEqual('modeling')
 | 
			
		||||
      // Click the start sketch button
 | 
			
		||||
      await toolbar.startSketchPlaneSelection()
 | 
			
		||||
 | 
			
		||||
      // Click on a default plane at position [700, 200]
 | 
			
		||||
      await page.mouse.click(700, 200)
 | 
			
		||||
 | 
			
		||||
      // Check that the modeling toolbar doesn't appear during the animation
 | 
			
		||||
      // The animation typically takes around 500ms, so we'll check for a second
 | 
			
		||||
      await expect.poll(toolBarMode, { timeout: 1000 }).not.toEqual('modeling')
 | 
			
		||||
 | 
			
		||||
      // After animation completes, we should see the sketching toolbar
 | 
			
		||||
      await expect.poll(toolBarMode).toEqual('sketching')
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
async function clickExportButton(page: Page) {
 | 
			
		||||
 | 
			
		||||
@ -29,5 +29,5 @@
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "kcl_version": "0.2.41"
 | 
			
		||||
  "kcl_version": "0.2.44"
 | 
			
		||||
}
 | 
			
		||||
@ -167,7 +167,10 @@ export function Toolbar({
 | 
			
		||||
  }, [currentMode, disableAllButtons, configCallbackProps])
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <menu className="max-w-full whitespace-nowrap rounded-b px-2 py-1 bg-chalkboard-10 dark:bg-chalkboard-90 relative border border-chalkboard-30 dark:border-chalkboard-80 border-t-0 shadow-sm">
 | 
			
		||||
    <menu
 | 
			
		||||
      data-currentMode={currentMode}
 | 
			
		||||
      className="max-w-full whitespace-nowrap rounded-b px-2 py-1 bg-chalkboard-10 dark:bg-chalkboard-90 relative border border-chalkboard-30 dark:border-chalkboard-80 border-t-0 shadow-sm"
 | 
			
		||||
    >
 | 
			
		||||
      <ul
 | 
			
		||||
        {...props}
 | 
			
		||||
        ref={toolbarButtonsRef}
 | 
			
		||||
 | 
			
		||||
@ -72,6 +72,7 @@ import {
 | 
			
		||||
  addCallExpressionsToPipe,
 | 
			
		||||
  addCloseToPipe,
 | 
			
		||||
  addNewSketchLn,
 | 
			
		||||
  ARG_END_ABSOLUTE,
 | 
			
		||||
  changeSketchArguments,
 | 
			
		||||
  updateStartProfileAtArgs,
 | 
			
		||||
} from 'lang/std/sketch'
 | 
			
		||||
@ -904,7 +905,7 @@ export class SceneEntities {
 | 
			
		||||
                    createPipeSubstitution(),
 | 
			
		||||
                  ])
 | 
			
		||||
                : createCallExpressionStdLibKw('line', null, [
 | 
			
		||||
                    createLabeledArg('endAbsolute', originCoords),
 | 
			
		||||
                    createLabeledArg(ARG_END_ABSOLUTE, originCoords),
 | 
			
		||||
                  ]),
 | 
			
		||||
            ],
 | 
			
		||||
          })
 | 
			
		||||
 | 
			
		||||
@ -159,7 +159,7 @@ export class KCLTypeError extends KCLError {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class KCLUnimplementedError extends KCLError {
 | 
			
		||||
export class KCLIoError extends KCLError {
 | 
			
		||||
  constructor(
 | 
			
		||||
    msg: string,
 | 
			
		||||
    sourceRange: SourceRange,
 | 
			
		||||
@ -169,7 +169,7 @@ export class KCLUnimplementedError extends KCLError {
 | 
			
		||||
    filenames: { [x: number]: ModulePath | undefined }
 | 
			
		||||
  ) {
 | 
			
		||||
    super(
 | 
			
		||||
      'unimplemented',
 | 
			
		||||
      'io',
 | 
			
		||||
      msg,
 | 
			
		||||
      sourceRange,
 | 
			
		||||
      operations,
 | 
			
		||||
@ -177,7 +177,7 @@ export class KCLUnimplementedError extends KCLError {
 | 
			
		||||
      artifactGraph,
 | 
			
		||||
      filenames
 | 
			
		||||
    )
 | 
			
		||||
    Object.setPrototypeOf(this, KCLUnimplementedError.prototype)
 | 
			
		||||
    Object.setPrototypeOf(this, KCLIoError.prototype)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -629,8 +629,8 @@ export const lineTo: SketchLineHelperKw = {
 | 
			
		||||
    const { node: callExpression } = nodeMeta
 | 
			
		||||
 | 
			
		||||
    const toArrExp = createArrayExpression([
 | 
			
		||||
      createLiteral(roundOff(to[0] - from[0], 2)),
 | 
			
		||||
      createLiteral(roundOff(to[1] - from[1], 2)),
 | 
			
		||||
      createLiteral(roundOff(to[0], 2)),
 | 
			
		||||
      createLiteral(roundOff(to[1], 2)),
 | 
			
		||||
    ])
 | 
			
		||||
 | 
			
		||||
    mutateKwArg(ARG_END_ABSOLUTE, callExpression, toArrExp)
 | 
			
		||||
@ -2348,7 +2348,7 @@ export function changeSketchArguments(
 | 
			
		||||
  if (fnName in sketchLineHelperMapKw) {
 | 
			
		||||
    const isAbsolute =
 | 
			
		||||
      callExpression.type === 'CallExpressionKw' &&
 | 
			
		||||
      findKwArg('endAbsolute', callExpression) !== undefined
 | 
			
		||||
      findKwArg(ARG_END_ABSOLUTE, callExpression) !== undefined
 | 
			
		||||
    const correctFnName = fnName === 'line' && isAbsolute ? 'lineTo' : fnName
 | 
			
		||||
    const { updateArgs } = sketchLineHelperMapKw[correctFnName]
 | 
			
		||||
    if (!updateArgs) {
 | 
			
		||||
@ -2391,7 +2391,7 @@ export function getConstraintInfoKw(
 | 
			
		||||
  const fnName = callExpression?.callee?.name || ''
 | 
			
		||||
  const isAbsolute =
 | 
			
		||||
    fnName === 'circleThreePoint' ||
 | 
			
		||||
    findKwArg('endAbsolute', callExpression) !== undefined
 | 
			
		||||
    findKwArg(ARG_END_ABSOLUTE, callExpression) !== undefined
 | 
			
		||||
  if (!(fnName in sketchLineHelperMapKw)) return []
 | 
			
		||||
  const correctFnName = fnName === 'line' && isAbsolute ? 'lineTo' : fnName
 | 
			
		||||
  return sketchLineHelperMapKw[correctFnName].getConstraintInfo(
 | 
			
		||||
 | 
			
		||||
@ -138,7 +138,7 @@ function createCallWrapper(
 | 
			
		||||
    }
 | 
			
		||||
    if (tooltip === 'lineTo') {
 | 
			
		||||
      const labeledArgs = [
 | 
			
		||||
        createLabeledArg('endAbsolute', createArrayExpression(val)),
 | 
			
		||||
        createLabeledArg(ARG_END_ABSOLUTE, createArrayExpression(val)),
 | 
			
		||||
      ]
 | 
			
		||||
      if (tag) {
 | 
			
		||||
        labeledArgs.push(createLabeledArg(ARG_TAG, tag))
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,6 @@ import {
 | 
			
		||||
  get_kcl_version,
 | 
			
		||||
  make_default_planes,
 | 
			
		||||
  coredump,
 | 
			
		||||
  toml_stringify,
 | 
			
		||||
  default_app_settings,
 | 
			
		||||
  parse_app_settings,
 | 
			
		||||
  parse_project_settings,
 | 
			
		||||
@ -21,6 +20,8 @@ import {
 | 
			
		||||
  clear_scene_and_bust_cache,
 | 
			
		||||
  kcl_settings,
 | 
			
		||||
  change_kcl_settings,
 | 
			
		||||
  serialize_project_configuration,
 | 
			
		||||
  serialize_configuration,
 | 
			
		||||
  reloadModule,
 | 
			
		||||
} from 'lib/wasm_lib_wrapper'
 | 
			
		||||
 | 
			
		||||
@ -636,10 +637,6 @@ export async function coreDump(
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function tomlStringify(toml: any): string | Error {
 | 
			
		||||
  return toml_stringify(JSON.stringify(toml))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function defaultAppSettings(): DeepPartial<Configuration> | Error {
 | 
			
		||||
  return default_app_settings()
 | 
			
		||||
}
 | 
			
		||||
@ -786,3 +783,27 @@ export function unitAngToUnitAngle(input: UnitAng): UnitAngle {
 | 
			
		||||
export function getKclVersion(): string {
 | 
			
		||||
  return get_kcl_version()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Serialize a project configuration to a TOML string.
 | 
			
		||||
 */
 | 
			
		||||
export function serializeConfiguration(configuration: any): string | Error {
 | 
			
		||||
  try {
 | 
			
		||||
    return serialize_configuration(configuration)
 | 
			
		||||
  } catch (e: any) {
 | 
			
		||||
    return new Error(`Error serializing configuration: ${e}`)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Serialize a project configuration to a TOML string.
 | 
			
		||||
 */
 | 
			
		||||
export function serializeProjectConfiguration(
 | 
			
		||||
  configuration: any
 | 
			
		||||
): string | Error {
 | 
			
		||||
  try {
 | 
			
		||||
    return serialize_project_configuration(configuration)
 | 
			
		||||
  } catch (e: any) {
 | 
			
		||||
    return new Error(`Error serializing project configuration: ${e}`)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -24,6 +24,7 @@ import {
 | 
			
		||||
  isBinaryExpression,
 | 
			
		||||
  isLiteralValueNumber,
 | 
			
		||||
} from 'lang/util'
 | 
			
		||||
import { ARG_END_ABSOLUTE } from 'lang/std/sketch'
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * It does not create the startSketchOn and it does not create the startProfileAt.
 | 
			
		||||
@ -70,7 +71,7 @@ export const getRectangleCallExpressions = (
 | 
			
		||||
  ]),
 | 
			
		||||
  createCallExpressionStdLibKw('line', null, [
 | 
			
		||||
    createLabeledArg(
 | 
			
		||||
      'endAbsolute',
 | 
			
		||||
      ARG_END_ABSOLUTE,
 | 
			
		||||
      createArrayExpression([
 | 
			
		||||
        createCallExpressionStdLib('profileStartX', [createPipeSubstitution()]),
 | 
			
		||||
        createCallExpressionStdLib('profileStartY', [createPipeSubstitution()]),
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,8 @@ import {
 | 
			
		||||
  initPromise,
 | 
			
		||||
  parseAppSettings,
 | 
			
		||||
  parseProjectSettings,
 | 
			
		||||
  tomlStringify,
 | 
			
		||||
  serializeConfiguration,
 | 
			
		||||
  serializeProjectConfiguration,
 | 
			
		||||
} from 'lang/wasm'
 | 
			
		||||
import { mouseControlsToCameraSystem } from 'lib/cameraControls'
 | 
			
		||||
import { BROWSER_PROJECT_NAME } from 'lib/constants'
 | 
			
		||||
@ -131,7 +132,7 @@ export function readLocalStorageAppSettingsFile():
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    const settings = defaultAppSettings()
 | 
			
		||||
    if (err(settings)) return settings
 | 
			
		||||
    const tomlStr = tomlStringify(settings)
 | 
			
		||||
    const tomlStr = serializeConfiguration(settings)
 | 
			
		||||
    if (err(tomlStr)) return tomlStr
 | 
			
		||||
 | 
			
		||||
    localStorage.setItem(localStorageAppSettingsPath(), tomlStr)
 | 
			
		||||
@ -152,7 +153,7 @@ function readLocalStorageProjectSettingsFile():
 | 
			
		||||
  const projectSettings = parseProjectSettings(stored)
 | 
			
		||||
  if (err(projectSettings)) {
 | 
			
		||||
    const settings = defaultProjectSettings()
 | 
			
		||||
    const tomlStr = tomlStringify(settings)
 | 
			
		||||
    const tomlStr = serializeProjectConfiguration(settings)
 | 
			
		||||
    if (err(tomlStr)) return tomlStr
 | 
			
		||||
 | 
			
		||||
    localStorage.setItem(localStorageProjectSettingsPath(), tomlStr)
 | 
			
		||||
@ -229,7 +230,7 @@ export async function saveSettings(
 | 
			
		||||
 | 
			
		||||
  // Get the user settings.
 | 
			
		||||
  const jsAppSettings = getChangedSettingsAtLevel(allSettings, 'user')
 | 
			
		||||
  const appTomlString = tomlStringify({ settings: jsAppSettings })
 | 
			
		||||
  const appTomlString = serializeConfiguration({ settings: jsAppSettings })
 | 
			
		||||
  if (err(appTomlString)) return
 | 
			
		||||
 | 
			
		||||
  // Write the app settings.
 | 
			
		||||
@ -246,7 +247,9 @@ export async function saveSettings(
 | 
			
		||||
 | 
			
		||||
  // Get the project settings.
 | 
			
		||||
  const jsProjectSettings = getChangedSettingsAtLevel(allSettings, 'project')
 | 
			
		||||
  const projectTomlString = tomlStringify({ settings: jsProjectSettings })
 | 
			
		||||
  const projectTomlString = serializeProjectConfiguration({
 | 
			
		||||
    settings: jsProjectSettings,
 | 
			
		||||
  })
 | 
			
		||||
  if (err(projectTomlString)) return
 | 
			
		||||
 | 
			
		||||
  // Write the project settings.
 | 
			
		||||
 | 
			
		||||
@ -56,7 +56,12 @@ export type ToolbarItemResolved = Omit<
 | 
			
		||||
export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
 | 
			
		||||
  modeling: {
 | 
			
		||||
    check: (state) =>
 | 
			
		||||
      !(state.matches('Sketch') || state.matches('Sketch no face')),
 | 
			
		||||
      !(
 | 
			
		||||
        state.matches('Sketch') ||
 | 
			
		||||
        state.matches('Sketch no face') ||
 | 
			
		||||
        state.matches('animating to existing sketch') ||
 | 
			
		||||
        state.matches('animating to plane')
 | 
			
		||||
      ),
 | 
			
		||||
    items: [
 | 
			
		||||
      {
 | 
			
		||||
        id: 'sketch',
 | 
			
		||||
@ -330,7 +335,10 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
 | 
			
		||||
  },
 | 
			
		||||
  sketching: {
 | 
			
		||||
    check: (state) =>
 | 
			
		||||
      state.matches('Sketch') || state.matches('Sketch no face'),
 | 
			
		||||
      state.matches('Sketch') ||
 | 
			
		||||
      state.matches('Sketch no face') ||
 | 
			
		||||
      state.matches('animating to existing sketch') ||
 | 
			
		||||
      state.matches('animating to plane'),
 | 
			
		||||
    items: [
 | 
			
		||||
      {
 | 
			
		||||
        id: 'sketch-exit',
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,6 @@ import {
 | 
			
		||||
  get_tangential_arc_to_info as GetTangentialArcToInfo,
 | 
			
		||||
  make_default_planes as MakeDefaultPlanes,
 | 
			
		||||
  coredump as CoreDump,
 | 
			
		||||
  toml_stringify as TomlStringify,
 | 
			
		||||
  default_app_settings as DefaultAppSettings,
 | 
			
		||||
  parse_app_settings as ParseAppSettings,
 | 
			
		||||
  parse_project_settings as ParseProjectSettings,
 | 
			
		||||
@ -29,6 +28,8 @@ import {
 | 
			
		||||
  kcl_settings as KclSettings,
 | 
			
		||||
  change_kcl_settings as ChangeKclSettings,
 | 
			
		||||
  get_kcl_version as GetKclVersion,
 | 
			
		||||
  serialize_configuration as SerializeConfiguration,
 | 
			
		||||
  serialize_project_configuration as SerializeProjectConfiguration,
 | 
			
		||||
} from '../wasm-lib/pkg/wasm_lib'
 | 
			
		||||
 | 
			
		||||
type ModuleType = typeof import('../wasm-lib/pkg/wasm_lib')
 | 
			
		||||
@ -86,9 +87,6 @@ export const make_default_planes: typeof MakeDefaultPlanes = (...args) => {
 | 
			
		||||
export const coredump: typeof CoreDump = (...args) => {
 | 
			
		||||
  return getModule().coredump(...args)
 | 
			
		||||
}
 | 
			
		||||
export const toml_stringify: typeof TomlStringify = (...args) => {
 | 
			
		||||
  return getModule().toml_stringify(...args)
 | 
			
		||||
}
 | 
			
		||||
export const default_app_settings: typeof DefaultAppSettings = (...args) => {
 | 
			
		||||
  return getModule().default_app_settings(...args)
 | 
			
		||||
}
 | 
			
		||||
@ -122,3 +120,12 @@ export const change_kcl_settings: typeof ChangeKclSettings = (...args) => {
 | 
			
		||||
export const get_kcl_version: typeof GetKclVersion = () => {
 | 
			
		||||
  return getModule().get_kcl_version()
 | 
			
		||||
}
 | 
			
		||||
export const serialize_configuration: typeof SerializeConfiguration = (
 | 
			
		||||
  ...args
 | 
			
		||||
) => {
 | 
			
		||||
  return getModule().serialize_configuration(...args)
 | 
			
		||||
}
 | 
			
		||||
export const serialize_project_configuration: typeof SerializeProjectConfiguration =
 | 
			
		||||
  (...args) => {
 | 
			
		||||
    return getModule().serialize_project_configuration(...args)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										6
									
								
								src/wasm-lib/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								src/wasm-lib/Cargo.lock
									
									
									
										generated
									
									
									
								
							@ -730,7 +730,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "derive-docs"
 | 
			
		||||
version = "0.1.40"
 | 
			
		||||
version = "0.1.44"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "Inflector",
 | 
			
		||||
 "anyhow",
 | 
			
		||||
@ -1724,7 +1724,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-lib"
 | 
			
		||||
version = "0.2.41"
 | 
			
		||||
version = "0.2.44"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "approx 0.5.1",
 | 
			
		||||
@ -1791,7 +1791,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-test-server"
 | 
			
		||||
version = "0.1.40"
 | 
			
		||||
version = "0.1.44"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "hyper 0.14.32",
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "derive-docs"
 | 
			
		||||
description = "A tool for generating documentation from Rust derive macros"
 | 
			
		||||
version = "0.1.40"
 | 
			
		||||
version = "0.1.44"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
license = "MIT"
 | 
			
		||||
repository = "https://github.com/KittyCAD/modeling-app"
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "kcl-test-server"
 | 
			
		||||
description = "A test server for KCL"
 | 
			
		||||
version = "0.1.40"
 | 
			
		||||
version = "0.1.44"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
license = "MIT"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "kcl-lib"
 | 
			
		||||
description = "KittyCAD Language implementation and tools"
 | 
			
		||||
version = "0.2.41"
 | 
			
		||||
version = "0.2.44"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
license = "MIT"
 | 
			
		||||
repository = "https://github.com/KittyCAD/modeling-app"
 | 
			
		||||
@ -22,7 +22,7 @@ clap = { version = "4.5.27", default-features = false, optional = true, features
 | 
			
		||||
] }
 | 
			
		||||
convert_case = "0.6.0"
 | 
			
		||||
dashmap = "6.1.0"
 | 
			
		||||
derive-docs = { version = "0.1.40", path = "../derive-docs" }
 | 
			
		||||
derive-docs = { version = "0.1.44", path = "../derive-docs" }
 | 
			
		||||
dhat = { version = "0.3", optional = true }
 | 
			
		||||
fnv = "1.0.7"
 | 
			
		||||
form_urlencoded = "1.2.1"
 | 
			
		||||
 | 
			
		||||
@ -87,8 +87,8 @@ pub enum KclError {
 | 
			
		||||
    ImportCycle(KclErrorDetails),
 | 
			
		||||
    #[error("type: {0:?}")]
 | 
			
		||||
    Type(KclErrorDetails),
 | 
			
		||||
    #[error("unimplemented: {0:?}")]
 | 
			
		||||
    Unimplemented(KclErrorDetails),
 | 
			
		||||
    #[error("i/o: {0:?}")]
 | 
			
		||||
    Io(KclErrorDetails),
 | 
			
		||||
    #[error("unexpected: {0:?}")]
 | 
			
		||||
    Unexpected(KclErrorDetails),
 | 
			
		||||
    #[error("value already defined: {0:?}")]
 | 
			
		||||
@ -150,7 +150,7 @@ impl KclErrorWithOutputs {
 | 
			
		||||
            source_files: Default::default(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    pub fn into_miette_report_with_outputs(self, code: &str) -> anyhow::Result<ReportWithOutputs> {
 | 
			
		||||
    pub fn into_miette_report_with_outputs(self) -> anyhow::Result<ReportWithOutputs> {
 | 
			
		||||
        let mut source_ranges = self.error.source_ranges();
 | 
			
		||||
 | 
			
		||||
        // Pop off the first source range to get the filename.
 | 
			
		||||
@ -162,33 +162,23 @@ impl KclErrorWithOutputs {
 | 
			
		||||
            .source_files
 | 
			
		||||
            .get(&first_source_range.module_id())
 | 
			
		||||
            .cloned()
 | 
			
		||||
            .unwrap_or(ModuleSource {
 | 
			
		||||
                source: code.to_string(),
 | 
			
		||||
                path: self
 | 
			
		||||
                    .filenames
 | 
			
		||||
                    .get(&first_source_range.module_id())
 | 
			
		||||
            .ok_or_else(|| {
 | 
			
		||||
                anyhow::anyhow!(
 | 
			
		||||
                            "Could not find filename for module id: {:?}",
 | 
			
		||||
                    "Could not find source file for module id: {:?}",
 | 
			
		||||
                    first_source_range.module_id()
 | 
			
		||||
                )
 | 
			
		||||
                    })?
 | 
			
		||||
                    .clone(),
 | 
			
		||||
            });
 | 
			
		||||
            })?;
 | 
			
		||||
        let filename = source.path.to_string();
 | 
			
		||||
        let kcl_source = source.source.to_string();
 | 
			
		||||
 | 
			
		||||
        let mut related = Vec::new();
 | 
			
		||||
        for source_range in source_ranges {
 | 
			
		||||
            let module_id = source_range.module_id();
 | 
			
		||||
            let source = self.source_files.get(&module_id).cloned().unwrap_or(ModuleSource {
 | 
			
		||||
                source: code.to_string(),
 | 
			
		||||
                path: self
 | 
			
		||||
                    .filenames
 | 
			
		||||
            let source = self
 | 
			
		||||
                .source_files
 | 
			
		||||
                .get(&module_id)
 | 
			
		||||
                    .ok_or_else(|| anyhow::anyhow!("Could not find filename for module id: {:?}", module_id))?
 | 
			
		||||
                    .clone(),
 | 
			
		||||
            });
 | 
			
		||||
                .cloned()
 | 
			
		||||
                .ok_or_else(|| anyhow::anyhow!("Could not find source file for module id: {:?}", module_id))?;
 | 
			
		||||
            let error = self.error.override_source_ranges(vec![source_range]);
 | 
			
		||||
            let report = Report {
 | 
			
		||||
                error,
 | 
			
		||||
@ -278,7 +268,7 @@ impl miette::Diagnostic for ReportWithOutputs {
 | 
			
		||||
            KclError::Semantic(_) => "Semantic",
 | 
			
		||||
            KclError::ImportCycle(_) => "ImportCycle",
 | 
			
		||||
            KclError::Type(_) => "Type",
 | 
			
		||||
            KclError::Unimplemented(_) => "Unimplemented",
 | 
			
		||||
            KclError::Io(_) => "I/O",
 | 
			
		||||
            KclError::Unexpected(_) => "Unexpected",
 | 
			
		||||
            KclError::ValueAlreadyDefined(_) => "ValueAlreadyDefined",
 | 
			
		||||
            KclError::UndefinedValue(_) => "UndefinedValue",
 | 
			
		||||
@ -328,7 +318,7 @@ impl miette::Diagnostic for Report {
 | 
			
		||||
            KclError::Semantic(_) => "Semantic",
 | 
			
		||||
            KclError::ImportCycle(_) => "ImportCycle",
 | 
			
		||||
            KclError::Type(_) => "Type",
 | 
			
		||||
            KclError::Unimplemented(_) => "Unimplemented",
 | 
			
		||||
            KclError::Io(_) => "I/O",
 | 
			
		||||
            KclError::Unexpected(_) => "Unexpected",
 | 
			
		||||
            KclError::ValueAlreadyDefined(_) => "ValueAlreadyDefined",
 | 
			
		||||
            KclError::UndefinedValue(_) => "UndefinedValue",
 | 
			
		||||
@ -387,7 +377,7 @@ impl KclError {
 | 
			
		||||
            KclError::Semantic(_) => "semantic",
 | 
			
		||||
            KclError::ImportCycle(_) => "import cycle",
 | 
			
		||||
            KclError::Type(_) => "type",
 | 
			
		||||
            KclError::Unimplemented(_) => "unimplemented",
 | 
			
		||||
            KclError::Io(_) => "i/o",
 | 
			
		||||
            KclError::Unexpected(_) => "unexpected",
 | 
			
		||||
            KclError::ValueAlreadyDefined(_) => "value already defined",
 | 
			
		||||
            KclError::UndefinedValue(_) => "undefined value",
 | 
			
		||||
@ -404,7 +394,7 @@ impl KclError {
 | 
			
		||||
            KclError::Semantic(e) => e.source_ranges.clone(),
 | 
			
		||||
            KclError::ImportCycle(e) => e.source_ranges.clone(),
 | 
			
		||||
            KclError::Type(e) => e.source_ranges.clone(),
 | 
			
		||||
            KclError::Unimplemented(e) => e.source_ranges.clone(),
 | 
			
		||||
            KclError::Io(e) => e.source_ranges.clone(),
 | 
			
		||||
            KclError::Unexpected(e) => e.source_ranges.clone(),
 | 
			
		||||
            KclError::ValueAlreadyDefined(e) => e.source_ranges.clone(),
 | 
			
		||||
            KclError::UndefinedValue(e) => e.source_ranges.clone(),
 | 
			
		||||
@ -422,7 +412,7 @@ impl KclError {
 | 
			
		||||
            KclError::Semantic(e) => &e.message,
 | 
			
		||||
            KclError::ImportCycle(e) => &e.message,
 | 
			
		||||
            KclError::Type(e) => &e.message,
 | 
			
		||||
            KclError::Unimplemented(e) => &e.message,
 | 
			
		||||
            KclError::Io(e) => &e.message,
 | 
			
		||||
            KclError::Unexpected(e) => &e.message,
 | 
			
		||||
            KclError::ValueAlreadyDefined(e) => &e.message,
 | 
			
		||||
            KclError::UndefinedValue(e) => &e.message,
 | 
			
		||||
@ -440,7 +430,7 @@ impl KclError {
 | 
			
		||||
            KclError::Semantic(e) => e.source_ranges = source_ranges,
 | 
			
		||||
            KclError::ImportCycle(e) => e.source_ranges = source_ranges,
 | 
			
		||||
            KclError::Type(e) => e.source_ranges = source_ranges,
 | 
			
		||||
            KclError::Unimplemented(e) => e.source_ranges = source_ranges,
 | 
			
		||||
            KclError::Io(e) => e.source_ranges = source_ranges,
 | 
			
		||||
            KclError::Unexpected(e) => e.source_ranges = source_ranges,
 | 
			
		||||
            KclError::ValueAlreadyDefined(e) => e.source_ranges = source_ranges,
 | 
			
		||||
            KclError::UndefinedValue(e) => e.source_ranges = source_ranges,
 | 
			
		||||
@ -460,7 +450,7 @@ impl KclError {
 | 
			
		||||
            KclError::Semantic(e) => e.source_ranges.extend(source_ranges),
 | 
			
		||||
            KclError::ImportCycle(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::Io(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),
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,7 @@ use crate::{
 | 
			
		||||
    },
 | 
			
		||||
    fs::FileManager,
 | 
			
		||||
    modules::{ModuleId, ModulePath},
 | 
			
		||||
    parsing::ast::types::{Expr, ImportPath, Node, NodeRef, Program},
 | 
			
		||||
    parsing::ast::types::{Expr, ImportPath, NodeRef},
 | 
			
		||||
    settings::types::UnitLength,
 | 
			
		||||
    source_range::SourceRange,
 | 
			
		||||
    std::StdLib,
 | 
			
		||||
@ -532,7 +532,7 @@ impl ExecutorContext {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let result = self.inner_run(&program.ast, &mut exec_state, true).await?;
 | 
			
		||||
        let result = self.inner_run(&program, &mut exec_state, true).await?;
 | 
			
		||||
 | 
			
		||||
        // Restore any temporary variables, then save any newly created variables back to
 | 
			
		||||
        // memory in case another run wants to use them. Note this is just saved to the preserved
 | 
			
		||||
@ -585,9 +585,15 @@ impl ExecutorContext {
 | 
			
		||||
                            .await
 | 
			
		||||
                            .is_err()
 | 
			
		||||
                    {
 | 
			
		||||
                        (true, program.ast)
 | 
			
		||||
                        (true, program)
 | 
			
		||||
                    } else {
 | 
			
		||||
                        (clear_scene, changed_program)
 | 
			
		||||
                        (
 | 
			
		||||
                            clear_scene,
 | 
			
		||||
                            crate::Program {
 | 
			
		||||
                                ast: changed_program,
 | 
			
		||||
                                original_file_contents: program.original_file_contents,
 | 
			
		||||
                            },
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                CacheResult::NoAction(true) => {
 | 
			
		||||
@ -608,7 +614,7 @@ impl ExecutorContext {
 | 
			
		||||
 | 
			
		||||
                        return Ok(old_state.to_wasm_outcome(result_env));
 | 
			
		||||
                    }
 | 
			
		||||
                    (true, program.ast)
 | 
			
		||||
                    (true, program)
 | 
			
		||||
                }
 | 
			
		||||
                CacheResult::NoAction(false) => return Ok(old_state.to_wasm_outcome(result_env)),
 | 
			
		||||
            };
 | 
			
		||||
@ -636,7 +642,7 @@ impl ExecutorContext {
 | 
			
		||||
            self.send_clear_scene(&mut exec_state, Default::default())
 | 
			
		||||
                .await
 | 
			
		||||
                .map_err(KclErrorWithOutputs::no_outputs)?;
 | 
			
		||||
            (program.ast, exec_state, false)
 | 
			
		||||
            (program, exec_state, false)
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let result = self.inner_run(&program, &mut exec_state, preserve_mem).await;
 | 
			
		||||
@ -650,7 +656,7 @@ impl ExecutorContext {
 | 
			
		||||
 | 
			
		||||
        // Save this as the last successful execution to the cache.
 | 
			
		||||
        cache::write_old_ast(OldAstState {
 | 
			
		||||
            ast: program,
 | 
			
		||||
            ast: program.ast,
 | 
			
		||||
            exec_state: exec_state.clone(),
 | 
			
		||||
            settings: self.settings.clone(),
 | 
			
		||||
            result_env: result.0,
 | 
			
		||||
@ -691,18 +697,19 @@ impl ExecutorContext {
 | 
			
		||||
        self.send_clear_scene(exec_state, Default::default())
 | 
			
		||||
            .await
 | 
			
		||||
            .map_err(KclErrorWithOutputs::no_outputs)?;
 | 
			
		||||
        self.inner_run(&program.ast, exec_state, false).await
 | 
			
		||||
        self.inner_run(program, exec_state, false).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Perform the execution of a program.  Accept all possible parameters and
 | 
			
		||||
    /// output everything.
 | 
			
		||||
    async fn inner_run(
 | 
			
		||||
        &self,
 | 
			
		||||
        program: &Node<Program>,
 | 
			
		||||
        program: &crate::Program,
 | 
			
		||||
        exec_state: &mut ExecState,
 | 
			
		||||
        preserve_mem: bool,
 | 
			
		||||
    ) -> Result<(EnvironmentRef, Option<ModelingSessionData>), KclErrorWithOutputs> {
 | 
			
		||||
        let _stats = crate::log::LogPerfStats::new("Interpretation");
 | 
			
		||||
        exec_state.add_root_module_contents(program);
 | 
			
		||||
 | 
			
		||||
        // Re-apply the settings, in case the cache was busted.
 | 
			
		||||
        self.engine
 | 
			
		||||
@ -711,7 +718,7 @@ impl ExecutorContext {
 | 
			
		||||
            .map_err(KclErrorWithOutputs::no_outputs)?;
 | 
			
		||||
 | 
			
		||||
        let env_ref = self
 | 
			
		||||
            .execute_and_build_graph(program, exec_state, preserve_mem)
 | 
			
		||||
            .execute_and_build_graph(&program.ast, exec_state, preserve_mem)
 | 
			
		||||
            .await
 | 
			
		||||
            .map_err(|e| {
 | 
			
		||||
                let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = exec_state
 | 
			
		||||
 | 
			
		||||
@ -182,8 +182,27 @@ impl ExecState {
 | 
			
		||||
        self.global.path_to_source_id.insert(path.clone(), id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn add_root_module_contents(&mut self, program: &crate::Program) {
 | 
			
		||||
        let root_id = ModuleId::default();
 | 
			
		||||
        // Get the path for the root module.
 | 
			
		||||
        let path = self
 | 
			
		||||
            .global
 | 
			
		||||
            .path_to_source_id
 | 
			
		||||
            .iter()
 | 
			
		||||
            .find(|(_, v)| **v == root_id)
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .0
 | 
			
		||||
            .clone();
 | 
			
		||||
        self.add_id_to_source(
 | 
			
		||||
            root_id,
 | 
			
		||||
            ModuleSource {
 | 
			
		||||
                path,
 | 
			
		||||
                source: program.original_file_contents.to_string(),
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn add_id_to_source(&mut self, id: ModuleId, source: ModuleSource) {
 | 
			
		||||
        debug_assert!(!self.global.id_to_source.contains_key(&id));
 | 
			
		||||
        self.global.id_to_source.insert(id, source.clone());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -251,8 +270,6 @@ impl GlobalState {
 | 
			
		||||
        global
 | 
			
		||||
            .path_to_source_id
 | 
			
		||||
            .insert(ModulePath::Local { value: root_path }, root_id);
 | 
			
		||||
        // Ideally we'd have a way to set the root module's source here, but
 | 
			
		||||
        // we don't have a way to get the source from the executor settings.
 | 
			
		||||
        global
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -31,7 +31,7 @@ impl FileSystem for FileManager {
 | 
			
		||||
        source_range: SourceRange,
 | 
			
		||||
    ) -> Result<Vec<u8>, KclError> {
 | 
			
		||||
        tokio::fs::read(&path).await.map_err(|e| {
 | 
			
		||||
            KclError::Engine(KclErrorDetails {
 | 
			
		||||
            KclError::Io(KclErrorDetails {
 | 
			
		||||
                message: format!("Failed to read file `{}`: {}", path.as_ref().display(), e),
 | 
			
		||||
                source_ranges: vec![source_range],
 | 
			
		||||
            })
 | 
			
		||||
@ -44,7 +44,7 @@ impl FileSystem for FileManager {
 | 
			
		||||
        source_range: SourceRange,
 | 
			
		||||
    ) -> Result<String, KclError> {
 | 
			
		||||
        tokio::fs::read_to_string(&path).await.map_err(|e| {
 | 
			
		||||
            KclError::Engine(KclErrorDetails {
 | 
			
		||||
            KclError::Io(KclErrorDetails {
 | 
			
		||||
                message: format!("Failed to read file `{}`: {}", path.as_ref().display(), e),
 | 
			
		||||
                source_ranges: vec![source_range],
 | 
			
		||||
            })
 | 
			
		||||
@ -60,7 +60,7 @@ impl FileSystem for FileManager {
 | 
			
		||||
            if e.kind() == std::io::ErrorKind::NotFound {
 | 
			
		||||
                Ok(false)
 | 
			
		||||
            } else {
 | 
			
		||||
                Err(KclError::Engine(KclErrorDetails {
 | 
			
		||||
                Err(KclError::Io(KclErrorDetails {
 | 
			
		||||
                    message: format!("Failed to check if file `{}` exists: {}", path.as_ref().display(), e),
 | 
			
		||||
                    source_ranges: vec![source_range],
 | 
			
		||||
                }))
 | 
			
		||||
@ -82,7 +82,7 @@ impl FileSystem for FileManager {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let mut read_dir = tokio::fs::read_dir(&path).await.map_err(|e| {
 | 
			
		||||
                KclError::Engine(KclErrorDetails {
 | 
			
		||||
                KclError::Io(KclErrorDetails {
 | 
			
		||||
                    message: format!("Failed to read directory `{}`: {}", path.display(), e),
 | 
			
		||||
                    source_ranges: vec![source_range],
 | 
			
		||||
                })
 | 
			
		||||
 | 
			
		||||
@ -134,6 +134,11 @@ use crate::log::{log, logln};
 | 
			
		||||
pub struct Program {
 | 
			
		||||
    #[serde(flatten)]
 | 
			
		||||
    pub ast: parsing::ast::types::Node<parsing::ast::types::Program>,
 | 
			
		||||
    // The ui doesn't need to know about this.
 | 
			
		||||
    // It's purely used for saving the contents of the original file, so we can use it for errors.
 | 
			
		||||
    // Because in the case of the root file, we don't want to read the file from disk again.
 | 
			
		||||
    #[serde(skip)]
 | 
			
		||||
    pub original_file_contents: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(any(test, feature = "lsp-test-util"))]
 | 
			
		||||
@ -147,7 +152,13 @@ impl Program {
 | 
			
		||||
        let tokens = parsing::token::lex(input, module_id)?;
 | 
			
		||||
        let (ast, errs) = parsing::parse_tokens(tokens).0?;
 | 
			
		||||
 | 
			
		||||
        Ok((ast.map(|ast| Program { ast }), errs))
 | 
			
		||||
        Ok((
 | 
			
		||||
            ast.map(|ast| Program {
 | 
			
		||||
                ast,
 | 
			
		||||
                original_file_contents: input.to_string(),
 | 
			
		||||
            }),
 | 
			
		||||
            errs,
 | 
			
		||||
        ))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn parse_no_errs(input: &str) -> Result<Program, KclError> {
 | 
			
		||||
@ -155,7 +166,10 @@ impl Program {
 | 
			
		||||
        let tokens = parsing::token::lex(input, module_id)?;
 | 
			
		||||
        let ast = parsing::parse_tokens(tokens).parse_errs_as_err()?;
 | 
			
		||||
 | 
			
		||||
        Ok(Program { ast })
 | 
			
		||||
        Ok(Program {
 | 
			
		||||
            ast,
 | 
			
		||||
            original_file_contents: input.to_string(),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn compute_digest(&mut self) -> parsing::ast::digest::Digest {
 | 
			
		||||
@ -168,9 +182,10 @@ impl Program {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Change the meta settings for the kcl file.
 | 
			
		||||
    pub fn change_meta_settings(&mut self, settings: crate::MetaSettings) -> Result<Self, KclError> {
 | 
			
		||||
    pub fn change_meta_settings(&self, settings: crate::MetaSettings) -> Result<Self, KclError> {
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            ast: self.ast.change_meta_settings(settings)?,
 | 
			
		||||
            original_file_contents: self.original_file_contents.clone(),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -192,12 +207,6 @@ impl Program {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<parsing::ast::types::Node<parsing::ast::types::Program>> for Program {
 | 
			
		||||
    fn from(ast: parsing::ast::types::Node<parsing::ast::types::Program>) -> Program {
 | 
			
		||||
        Self { ast }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[inline]
 | 
			
		||||
fn try_f64_to_usize(f: f64) -> Option<usize> {
 | 
			
		||||
    let i = f as usize;
 | 
			
		||||
 | 
			
		||||
@ -46,7 +46,7 @@ use crate::{
 | 
			
		||||
    errors::Suggestion,
 | 
			
		||||
    lsp::{backend::Backend as _, util::IntoDiagnostic},
 | 
			
		||||
    parsing::{
 | 
			
		||||
        ast::types::{Expr, Node, VariableKind},
 | 
			
		||||
        ast::types::{Expr, VariableKind},
 | 
			
		||||
        token::TokenStream,
 | 
			
		||||
        PIPE_OPERATOR,
 | 
			
		||||
    },
 | 
			
		||||
@ -102,7 +102,7 @@ pub struct Backend {
 | 
			
		||||
    /// Token maps.
 | 
			
		||||
    pub(super) token_map: DashMap<String, TokenStream>,
 | 
			
		||||
    /// AST maps.
 | 
			
		||||
    pub ast_map: DashMap<String, Node<crate::parsing::ast::types::Program>>,
 | 
			
		||||
    pub ast_map: DashMap<String, crate::Program>,
 | 
			
		||||
    /// Current code.
 | 
			
		||||
    pub code_map: DashMap<String, Vec<u8>>,
 | 
			
		||||
    /// Diagnostics.
 | 
			
		||||
@ -327,11 +327,17 @@ impl crate::lsp::backend::Backend for Backend {
 | 
			
		||||
        // this if it backfires and only hork the LSP.
 | 
			
		||||
        ast.compute_digest();
 | 
			
		||||
 | 
			
		||||
        // Save it as a program.
 | 
			
		||||
        let ast = crate::Program {
 | 
			
		||||
            ast,
 | 
			
		||||
            original_file_contents: params.text.clone(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Check if the ast changed.
 | 
			
		||||
        let ast_changed = match self.ast_map.get(&filename) {
 | 
			
		||||
            Some(old_ast) => {
 | 
			
		||||
                // Check if the ast changed.
 | 
			
		||||
                *old_ast != ast
 | 
			
		||||
                *old_ast.ast != *ast.ast
 | 
			
		||||
            }
 | 
			
		||||
            None => true,
 | 
			
		||||
        };
 | 
			
		||||
@ -346,7 +352,7 @@ impl crate::lsp::backend::Backend for Backend {
 | 
			
		||||
            // Update the symbols map.
 | 
			
		||||
            self.symbols_map.insert(
 | 
			
		||||
                params.uri.to_string(),
 | 
			
		||||
                ast.get_lsp_symbols(¶ms.text).unwrap_or_default(),
 | 
			
		||||
                ast.ast.get_lsp_symbols(¶ms.text).unwrap_or_default(),
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Update our semantic tokens.
 | 
			
		||||
@ -361,14 +367,14 @@ impl crate::lsp::backend::Backend for Backend {
 | 
			
		||||
            // Only send the notification if we can execute.
 | 
			
		||||
            // Otherwise it confuses the client.
 | 
			
		||||
            self.client
 | 
			
		||||
                .send_notification::<custom_notifications::AstUpdated>(ast.clone())
 | 
			
		||||
                .send_notification::<custom_notifications::AstUpdated>(ast.ast.clone())
 | 
			
		||||
                .await;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Execute the code if we have an executor context.
 | 
			
		||||
        // This function automatically executes if we should & updates the diagnostics if we got
 | 
			
		||||
        // errors.
 | 
			
		||||
        if self.execute(¶ms, &ast.into()).await.is_err() {
 | 
			
		||||
        if self.execute(¶ms, &ast).await.is_err() {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -421,7 +427,7 @@ impl Backend {
 | 
			
		||||
            let token_modifiers_bitset = if let Some(ast) = self.ast_map.get(params.uri.as_str()) {
 | 
			
		||||
                let token_index = Arc::new(Mutex::new(token_type_index));
 | 
			
		||||
                let modifier_index: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
 | 
			
		||||
                crate::walk::walk(&ast, |node: crate::walk::Node| {
 | 
			
		||||
                crate::walk::walk(&ast.ast, |node: crate::walk::Node| {
 | 
			
		||||
                    let Ok(node_range): Result<SourceRange, _> = (&node).try_into() else {
 | 
			
		||||
                        return Ok(true);
 | 
			
		||||
                    };
 | 
			
		||||
@ -1021,7 +1027,7 @@ impl LanguageServer for Backend {
 | 
			
		||||
            return Ok(None);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let Some(hover) = ast.get_hover_value_for_position(pos, current_code) else {
 | 
			
		||||
        let Some(hover) = ast.ast.get_hover_value_for_position(pos, current_code) else {
 | 
			
		||||
            return Ok(None);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
@ -1150,13 +1156,13 @@ impl LanguageServer for Backend {
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let position = position_to_char_index(params.text_document_position.position, current_code);
 | 
			
		||||
        if ast.get_non_code_meta_for_position(position).is_some() {
 | 
			
		||||
        if ast.ast.get_non_code_meta_for_position(position).is_some() {
 | 
			
		||||
            // If we are in a code comment we don't want to show completions.
 | 
			
		||||
            return Ok(None);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Get the completion items for the ast.
 | 
			
		||||
        let Ok(variables) = ast.completion_items() else {
 | 
			
		||||
        let Ok(variables) = ast.ast.completion_items() else {
 | 
			
		||||
            return Ok(Some(CompletionResponse::Array(completions)));
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
@ -1211,7 +1217,7 @@ impl LanguageServer for Backend {
 | 
			
		||||
            return Ok(None);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let Some(value) = ast.get_expr_for_position(pos) else {
 | 
			
		||||
        let Some(value) = ast.ast.get_expr_for_position(pos) else {
 | 
			
		||||
            return Ok(None);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
@ -1360,7 +1366,7 @@ impl LanguageServer for Backend {
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Get the folding ranges.
 | 
			
		||||
        let folding_ranges = ast.get_lsp_folding_ranges();
 | 
			
		||||
        let folding_ranges = ast.ast.get_lsp_folding_ranges();
 | 
			
		||||
 | 
			
		||||
        if folding_ranges.is_empty() {
 | 
			
		||||
            return Ok(None);
 | 
			
		||||
 | 
			
		||||
@ -1138,7 +1138,7 @@ fn myFn = (param1) => {
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != Node::<Program>::default());
 | 
			
		||||
    assert!(ast.ast != Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Send semantic tokens request.
 | 
			
		||||
    let semantic_tokens = server
 | 
			
		||||
@ -2251,7 +2251,7 @@ part001 = cube([0,0], 20)
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert_eq!(ast.body.len(), 2);
 | 
			
		||||
    assert_eq!(ast.ast.body.len(), 2);
 | 
			
		||||
 | 
			
		||||
    // Send change file.
 | 
			
		||||
    server
 | 
			
		||||
@ -2428,7 +2428,7 @@ async fn kcl_test_kcl_lsp_full_to_empty_file_updates_ast_and_memory() {
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != Node::<Program>::default());
 | 
			
		||||
    assert!(ast.ast != Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Send change file.
 | 
			
		||||
    server
 | 
			
		||||
@ -2450,7 +2450,7 @@ async fn kcl_test_kcl_lsp_full_to_empty_file_updates_ast_and_memory() {
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert_eq!(ast, default_hashed);
 | 
			
		||||
    assert_eq!(ast.ast, default_hashed);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[tokio::test(flavor = "multi_thread")]
 | 
			
		||||
@ -2479,7 +2479,7 @@ async fn kcl_test_kcl_lsp_code_unchanged_but_has_diagnostics_reexecute() {
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != Node::<Program>::default());
 | 
			
		||||
    assert!(ast.ast != Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Assure we have no diagnostics.
 | 
			
		||||
    assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
 | 
			
		||||
@ -2506,11 +2506,15 @@ async fn kcl_test_kcl_lsp_code_unchanged_but_has_diagnostics_reexecute() {
 | 
			
		||||
    assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 1);
 | 
			
		||||
 | 
			
		||||
    // Clear the ast and memory.
 | 
			
		||||
    server
 | 
			
		||||
        .ast_map
 | 
			
		||||
        .insert("file:///test.kcl".to_string(), Node::<Program>::default());
 | 
			
		||||
    server.ast_map.insert(
 | 
			
		||||
        "file:///test.kcl".to_string(),
 | 
			
		||||
        crate::Program {
 | 
			
		||||
            ast: Default::default(),
 | 
			
		||||
            original_file_contents: Default::default(),
 | 
			
		||||
        },
 | 
			
		||||
    );
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert_eq!(ast, Node::<Program>::default());
 | 
			
		||||
    assert_eq!(ast.ast, Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Send change file, but the code is the same.
 | 
			
		||||
    server
 | 
			
		||||
@ -2529,7 +2533,7 @@ async fn kcl_test_kcl_lsp_code_unchanged_but_has_diagnostics_reexecute() {
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != Node::<Program>::default());
 | 
			
		||||
    assert!(ast.ast != Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Assure we have no diagnostics.
 | 
			
		||||
    assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
 | 
			
		||||
@ -2561,7 +2565,7 @@ async fn kcl_test_kcl_lsp_code_and_ast_unchanged_but_has_diagnostics_reexecute()
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != Node::<Program>::default());
 | 
			
		||||
    assert!(ast.ast != Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Assure we have no diagnostics.
 | 
			
		||||
    assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
 | 
			
		||||
@ -2604,7 +2608,7 @@ async fn kcl_test_kcl_lsp_code_and_ast_unchanged_but_has_diagnostics_reexecute()
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != Node::<Program>::default());
 | 
			
		||||
    assert!(ast.ast != Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Assure we have no diagnostics.
 | 
			
		||||
    assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
 | 
			
		||||
@ -2636,7 +2640,7 @@ async fn kcl_test_kcl_lsp_code_and_ast_units_unchanged_but_has_diagnostics_reexe
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != Node::<Program>::default());
 | 
			
		||||
    assert!(ast.ast != Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Assure we have no diagnostics.
 | 
			
		||||
    assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
 | 
			
		||||
@ -2682,7 +2686,7 @@ async fn kcl_test_kcl_lsp_code_and_ast_units_unchanged_but_has_diagnostics_reexe
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != Node::<Program>::default());
 | 
			
		||||
    assert!(ast.ast != Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Assure we have no diagnostics.
 | 
			
		||||
    assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
 | 
			
		||||
@ -2714,7 +2718,7 @@ async fn kcl_test_kcl_lsp_code_and_ast_units_unchanged_but_has_memory_reexecute_
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != Node::<Program>::default());
 | 
			
		||||
    assert!(ast.ast != Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Assure we have no diagnostics.
 | 
			
		||||
    assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
 | 
			
		||||
@ -2739,7 +2743,7 @@ async fn kcl_test_kcl_lsp_code_and_ast_units_unchanged_but_has_memory_reexecute_
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != Node::<Program>::default());
 | 
			
		||||
    assert!(ast.ast != Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Assure we have no diagnostics.
 | 
			
		||||
    assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
 | 
			
		||||
@ -2771,7 +2775,7 @@ async fn kcl_test_kcl_lsp_cant_execute_set() {
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != Node::<Program>::default());
 | 
			
		||||
    assert!(ast.ast != Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Assure we have no diagnostics.
 | 
			
		||||
    assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
 | 
			
		||||
@ -2795,7 +2799,7 @@ async fn kcl_test_kcl_lsp_cant_execute_set() {
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != Node::<Program>::default());
 | 
			
		||||
    assert!(ast.ast != Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Assure we have no diagnostics.
 | 
			
		||||
    assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
 | 
			
		||||
@ -2831,7 +2835,7 @@ async fn kcl_test_kcl_lsp_cant_execute_set() {
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != default_hashed);
 | 
			
		||||
    assert!(ast.ast != default_hashed);
 | 
			
		||||
 | 
			
		||||
    // Assure we have no diagnostics.
 | 
			
		||||
    assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
 | 
			
		||||
@ -2862,7 +2866,7 @@ async fn kcl_test_kcl_lsp_cant_execute_set() {
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != Node::<Program>::default());
 | 
			
		||||
    assert!(ast.ast != Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Assure we have no diagnostics.
 | 
			
		||||
    assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
 | 
			
		||||
@ -2995,7 +2999,7 @@ part001 = startSketchOn('XY')
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != Node::<Program>::default());
 | 
			
		||||
    assert!(ast.ast != Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Assure we have one diagnostics.
 | 
			
		||||
    assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 1);
 | 
			
		||||
@ -3016,7 +3020,7 @@ part001 = startSketchOn('XY')
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != Node::<Program>::default());
 | 
			
		||||
    assert!(ast.ast != Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Assure we have one diagnostics.
 | 
			
		||||
    assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 1);
 | 
			
		||||
@ -3109,7 +3113,7 @@ part001 = startSketchOn('XY')
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != Node::<Program>::default());
 | 
			
		||||
    assert!(ast.ast != Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Send change file, but the code is the same.
 | 
			
		||||
    server
 | 
			
		||||
@ -3128,7 +3132,7 @@ part001 = startSketchOn('XY')
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != Node::<Program>::default());
 | 
			
		||||
    assert!(ast.ast != Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Assure we have diagnostics.
 | 
			
		||||
 | 
			
		||||
@ -3168,7 +3172,7 @@ part001 = startSketchOn('XY')
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != Node::<Program>::default());
 | 
			
		||||
    assert!(ast.ast != Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Send change file, but the code is the same.
 | 
			
		||||
    server
 | 
			
		||||
@ -3195,7 +3199,7 @@ NEW_LINT = 1"#
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != Node::<Program>::default());
 | 
			
		||||
    assert!(ast.ast != Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Assure we have diagnostics.
 | 
			
		||||
 | 
			
		||||
@ -3302,7 +3306,7 @@ part001 = startSketchOn('XY')
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != Node::<Program>::default());
 | 
			
		||||
    assert!(ast.ast != Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Get the symbols map.
 | 
			
		||||
    let symbols_map = server.symbols_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
@ -3389,7 +3393,7 @@ part001 = startSketchOn('XY')
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != Node::<Program>::default());
 | 
			
		||||
    assert!(ast.ast != Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Get the symbols map.
 | 
			
		||||
    let symbols_map = server.symbols_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
@ -3428,7 +3432,7 @@ part001 = startSketchOn('XY')
 | 
			
		||||
 | 
			
		||||
    // Get the ast.
 | 
			
		||||
    let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
    assert!(ast != Node::<Program>::default());
 | 
			
		||||
    assert!(ast.ast != Node::<Program>::default());
 | 
			
		||||
 | 
			
		||||
    // Get the symbols map.
 | 
			
		||||
    let symbols_map = server.symbols_map.get("file:///test.kcl").unwrap().clone();
 | 
			
		||||
 | 
			
		||||
@ -182,9 +182,7 @@ pub async fn modify_ast_for_sketch(
 | 
			
		||||
    let recasted = program.ast.recast(&FormatOptions::default(), 0);
 | 
			
		||||
 | 
			
		||||
    // Re-parse the ast so we get the correct source ranges.
 | 
			
		||||
    *program = crate::parsing::parse_str(&recasted, module_id)
 | 
			
		||||
        .parse_errs_as_err()?
 | 
			
		||||
        .into();
 | 
			
		||||
    program.ast = crate::parsing::parse_str(&recasted, module_id).parse_errs_as_err()?;
 | 
			
		||||
 | 
			
		||||
    Ok(recasted)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -275,7 +275,7 @@ impl Node<Program> {
 | 
			
		||||
        Ok(None)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn change_meta_settings(&mut self, settings: crate::execution::MetaSettings) -> Result<Self, KclError> {
 | 
			
		||||
    pub fn change_meta_settings(&self, settings: crate::execution::MetaSettings) -> Result<Self, KclError> {
 | 
			
		||||
        let mut new_program = self.clone();
 | 
			
		||||
        let mut found = false;
 | 
			
		||||
        for node in &mut new_program.inner_attrs {
 | 
			
		||||
@ -4035,7 +4035,7 @@ startSketchOn('XY')"#;
 | 
			
		||||
        let some_program_string = r#"@settings(defaultLengthUnit = inch)
 | 
			
		||||
 | 
			
		||||
startSketchOn('XY')"#;
 | 
			
		||||
        let mut program = crate::parsing::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
        let program = crate::parsing::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
        let result = program.meta_settings().unwrap();
 | 
			
		||||
        assert!(result.is_some());
 | 
			
		||||
        let meta_settings = result.unwrap();
 | 
			
		||||
@ -4077,7 +4077,7 @@ startSketchOn('XY')
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread")]
 | 
			
		||||
    async fn test_parse_get_meta_settings_nothing_to_mm() {
 | 
			
		||||
        let some_program_string = r#"startSketchOn('XY')"#;
 | 
			
		||||
        let mut program = crate::parsing::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
        let program = crate::parsing::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
        let result = program.meta_settings().unwrap();
 | 
			
		||||
        assert!(result.is_none());
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -84,10 +84,14 @@ async fn execute(test_name: &str, render_to_png: bool) {
 | 
			
		||||
    let Ok(ast) = ast_res else {
 | 
			
		||||
        return;
 | 
			
		||||
    };
 | 
			
		||||
    let ast = crate::Program {
 | 
			
		||||
        ast,
 | 
			
		||||
        original_file_contents: read("input.kcl", test_name),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Run the program.
 | 
			
		||||
    let exec_res = crate::test_server::execute_and_snapshot_ast(
 | 
			
		||||
        ast.into(),
 | 
			
		||||
        ast,
 | 
			
		||||
        crate::settings::types::UnitLength::Mm,
 | 
			
		||||
        Some(Path::new("tests").join(test_name).join("input.kcl").to_owned()),
 | 
			
		||||
    )
 | 
			
		||||
@ -132,10 +136,7 @@ async fn execute(test_name: &str, render_to_png: bool) {
 | 
			
		||||
                        Box::new(miette::MietteHandlerOpts::new().show_related_errors_as_nested().build())
 | 
			
		||||
                    }))
 | 
			
		||||
                    .unwrap();
 | 
			
		||||
                    let report = error
 | 
			
		||||
                        .clone()
 | 
			
		||||
                        .into_miette_report_with_outputs(&read("input.kcl", test_name))
 | 
			
		||||
                        .unwrap();
 | 
			
		||||
                    let report = error.clone().into_miette_report_with_outputs().unwrap();
 | 
			
		||||
                    let report = miette::Report::new(report);
 | 
			
		||||
                    if previously_passed {
 | 
			
		||||
                        eprintln!("This test case failed, but it previously passed. If this is intended, and the test should actually be failing now, please delete kcl/{ok_path_str} and other associated passing artifacts");
 | 
			
		||||
 | 
			
		||||
@ -2,9 +2,9 @@
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Error from executing import_file_not_exist_error.kcl
 | 
			
		||||
---
 | 
			
		||||
KCL Engine error
 | 
			
		||||
KCL I/O error
 | 
			
		||||
 | 
			
		||||
  × engine: Failed to read file `tests/import_file_not_exist_error/not-
 | 
			
		||||
  × i/o: Failed to read file `tests/import_file_not_exist_error/not-
 | 
			
		||||
  │ exist.kcl`: No such file or directory (os error 2)
 | 
			
		||||
   ╭────
 | 
			
		||||
 1 │ import hotdog from "not-exist.kcl"
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,7 @@
 | 
			
		||||
//! Wasm bindings for `kcl`.
 | 
			
		||||
 | 
			
		||||
#[cfg(target_arch = "wasm32")]
 | 
			
		||||
mod toml;
 | 
			
		||||
#[cfg(target_arch = "wasm32")]
 | 
			
		||||
mod wasm;
 | 
			
		||||
 | 
			
		||||
#[cfg(target_arch = "wasm32")]
 | 
			
		||||
pub use toml::*;
 | 
			
		||||
#[cfg(target_arch = "wasm32")]
 | 
			
		||||
pub use wasm::*;
 | 
			
		||||
 | 
			
		||||
@ -1,13 +0,0 @@
 | 
			
		||||
//! Functions for interacting with TOML files.
 | 
			
		||||
//! We do this in rust because the Javascript TOML libraries are actual trash.
 | 
			
		||||
 | 
			
		||||
use wasm_bindgen::prelude::wasm_bindgen;
 | 
			
		||||
 | 
			
		||||
#[wasm_bindgen]
 | 
			
		||||
pub fn toml_stringify(json: &str) -> Result<String, String> {
 | 
			
		||||
    console_error_panic_hook::set_once();
 | 
			
		||||
 | 
			
		||||
    let value: serde_json::Value = serde_json::from_str(json).map_err(|e| e.to_string())?;
 | 
			
		||||
 | 
			
		||||
    toml::to_string_pretty(&value).map_err(|e| e.to_string())
 | 
			
		||||
}
 | 
			
		||||
@ -483,9 +483,9 @@ pub fn parse_project_settings(toml_str: &str) -> Result<JsValue, String> {
 | 
			
		||||
    JsValue::from_serde(&settings).map_err(|e| e.to_string())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Serialize the project settings.
 | 
			
		||||
/// Serialize the configuration settings.
 | 
			
		||||
#[wasm_bindgen]
 | 
			
		||||
pub fn serialize_project_settings(val: JsValue) -> Result<JsValue, String> {
 | 
			
		||||
pub fn serialize_configuration(val: JsValue) -> Result<JsValue, String> {
 | 
			
		||||
    console_error_panic_hook::set_once();
 | 
			
		||||
 | 
			
		||||
    let config: kcl_lib::Configuration = val.into_serde().map_err(|e| e.to_string())?;
 | 
			
		||||
@ -497,6 +497,20 @@ pub fn serialize_project_settings(val: JsValue) -> Result<JsValue, String> {
 | 
			
		||||
    Ok(JsValue::from_str(&toml_str))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Serialize the project configuration settings.
 | 
			
		||||
#[wasm_bindgen]
 | 
			
		||||
pub fn serialize_project_configuration(val: JsValue) -> Result<JsValue, String> {
 | 
			
		||||
    console_error_panic_hook::set_once();
 | 
			
		||||
 | 
			
		||||
    let config: kcl_lib::ProjectConfiguration = val.into_serde().map_err(|e| e.to_string())?;
 | 
			
		||||
 | 
			
		||||
    let toml_str = toml::to_string_pretty(&config).map_err(|e| e.to_string())?;
 | 
			
		||||
 | 
			
		||||
    // The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the
 | 
			
		||||
    // gloo-serialize crate instead.
 | 
			
		||||
    Ok(JsValue::from_str(&toml_str))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static ALLOWED_DECODING_FORMATS: &[data_encoding::Encoding] = &[
 | 
			
		||||
    data_encoding::BASE64,
 | 
			
		||||
    data_encoding::BASE64URL,
 | 
			
		||||
@ -561,7 +575,7 @@ pub fn change_kcl_settings(code: &str, settings_str: &str) -> Result<String, Str
 | 
			
		||||
    console_error_panic_hook::set_once();
 | 
			
		||||
 | 
			
		||||
    let settings: kcl_lib::MetaSettings = serde_json::from_str(settings_str).map_err(|e| e.to_string())?;
 | 
			
		||||
    let mut program = Program::parse_no_errs(code).map_err(|e| e.to_string())?;
 | 
			
		||||
    let program = Program::parse_no_errs(code).map_err(|e| e.to_string())?;
 | 
			
		||||
 | 
			
		||||
    let new_program = program.change_meta_settings(settings).map_err(|e| e.to_string())?;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user