diff --git a/e2e/playwright/testing-selections.spec.ts b/e2e/playwright/testing-selections.spec.ts
index 840715bd2a..9a9d8783eb 100644
--- a/e2e/playwright/testing-selections.spec.ts
+++ b/e2e/playwright/testing-selections.spec.ts
@@ -862,4 +862,40 @@ part001 = startSketchOn(XZ)
)
previousCodeContent = await page.locator('.cm-content').innerText()
})
+
+ test('"View KCL source code" right click menu in scene', async ({
+ page,
+ homePage,
+ scene,
+ cmdBar,
+ }) => {
+ await page.setBodyDimensions({ width: 1200, height: 500 })
+ const middleX = 1200 / 2
+ const middleY = 500 / 2
+
+ await homePage.goToModelingScene()
+ await scene.settled(cmdBar)
+
+ await test.step('Empty scene should have disabled "View KCL source code"', async () => {
+ // Right-click in empty scene
+ await page.mouse.click(middleX, middleY, { button: 'right' })
+
+ // Verify context menu appears
+ await expect(page.getByTestId('view-controls-menu')).toBeVisible()
+
+ // "View KCL source code" should be disabled in empty scene
+ const menuItems = page.locator(
+ '[data-testid="view-controls-menu"] button'
+ )
+ const viewKclSourceCodeOption = menuItems.filter({
+ hasText: 'View KCL source code',
+ })
+ await expect(viewKclSourceCodeOption).toBeVisible()
+ await expect(viewKclSourceCodeOption).toBeDisabled()
+ })
+
+ // TODO: add this step for happy path, the bracket code is long enough for this
+ // await test.step('Right click on bracket sample leads to the right place in code', async () => {
+ // })
+ })
})
diff --git a/src/components/ViewControlMenu.tsx b/src/components/ViewControlMenu.tsx
index 8f7b213951..a3b8239818 100644
--- a/src/components/ViewControlMenu.tsx
+++ b/src/components/ViewControlMenu.tsx
@@ -19,6 +19,7 @@ import {
selectOffsetSketchPlane,
} from '@src/lib/selections'
import { getSelectedPlaneId } from '@src/lang/queryAst'
+import toast from 'react-hot-toast'
export function useViewControlMenuItems() {
const { state: modelingState, send: modelingSend } = useModelingContext()
@@ -30,6 +31,20 @@ export function useViewControlMenuItems() {
const shouldLockView =
modelingState.matches('Sketch') &&
!settings.app.allowOrbitInSketchMode.current
+
+ // Check if there's a valid selection with source range for "View KCL source code"
+ const firstValidSelection = useMemo(() => {
+ return modelingState.context.selectionRanges.graphSelections.find(
+ (selection) => {
+ return (
+ selection.codeRef?.range &&
+ selection.codeRef.range[0] !== undefined &&
+ selection.codeRef.range[1] !== undefined
+ )
+ }
+ )
+ }, [modelingState.context.selectionRanges.graphSelections])
+
const menuItems = useMemo(
() => [
...Object.entries(VIEW_NAMES_SEMANTIC).map(([axisName, axisSemantic]) => (
@@ -64,6 +79,41 @@ export function useViewControlMenuItems() {
>
Center view on selection
,
+ {
+ if (firstValidSelection?.codeRef?.range) {
+ // First, open the code pane if it's not already open
+ if (!modelingState.context.store.openPanes.includes('code')) {
+ modelingSend({
+ type: 'Set context',
+ data: {
+ openPanes: [...modelingState.context.store.openPanes, 'code'],
+ },
+ })
+ }
+
+ // Navigate to the source code location
+ modelingSend({
+ type: 'Set selection',
+ data: {
+ selectionType: 'singleCodeCursor',
+ selection: {
+ artifact: firstValidSelection.artifact,
+ codeRef: firstValidSelection.codeRef,
+ },
+ scrollIntoView: true,
+ },
+ })
+ } else {
+ toast.error(
+ 'No valid selection with source range found. Please select a valid element.'
+ )
+ }
+ }}
+ disabled={!firstValidSelection}
+ >
+ View KCL source code
+ ,
,
{
@@ -94,7 +144,13 @@ export function useViewControlMenuItems() {
,
],
// eslint-disable-next-line react-hooks/exhaustive-deps -- TODO: blanket-ignored fix me!
- [VIEW_NAMES_SEMANTIC, shouldLockView, selectedPlaneId]
+ [
+ VIEW_NAMES_SEMANTIC,
+ shouldLockView,
+ selectedPlaneId,
+ firstValidSelection,
+ modelingState.context.store.openPanes,
+ ]
)
return menuItems
}