@marimo-team/islands 0.23.7-dev9 → 0.23.7-dev90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ConnectedDataExplorerComponent-DnRhpPMJ.js → ConnectedDataExplorerComponent-2lBNiUv6.js} +13 -13
- package/dist/{ErrorBoundary-Da4UeYxT.js → ErrorBoundary-D3wrPNma.js} +1 -1
- package/dist/{any-language-editor-DDubl8YH.js → any-language-editor-VWs_7v27.js} +5 -5
- package/dist/assets/__vite-browser-external-CAdMKBac.js +1 -0
- package/dist/assets/worker-CpBbwbQo.js +73 -0
- package/dist/{button-CA5pI2YF.js → button-Dj4BTre0.js} +5 -0
- package/dist/{capabilities-6laDasij.js → capabilities-C9rrYCzf.js} +1 -1
- package/dist/{chat-ui-BmWZZ3mE.js → chat-ui-D3XBept8.js} +625 -233
- package/dist/{check-CFM2mVDr.js → check-BcUIXnUT.js} +1 -1
- package/dist/{code-visibility-CRHzv49w.js → code-visibility-C5NrPsUC.js} +11480 -1992
- package/dist/{copy-TGGAUEWp.js → copy-DLf4aN7I.js} +2 -2
- package/dist/{dist-ESg7xyoD.js → dist-D3ZI9nhS.js} +2 -2
- package/dist/{error-banner-DnBPzEWg.js → error-banner-CVkfBUT3.js} +2 -2
- package/dist/{esm-Dd1z1auZ.js → esm-CWp0KQeK.js} +1 -1
- package/dist/{extends-CzJgxo2J.js → extends-vAi97cpa.js} +4 -4
- package/dist/{formats-CgaK7Gmx.js → formats-Dsy9kkZu.js} +3 -3
- package/dist/{glide-data-editor-B-3A3G02.js → glide-data-editor-DucgdjRo.js} +9 -9
- package/dist/{html-to-image-BwZL1Pkk.js → html-to-image-CpggM7u1.js} +2667 -2408
- package/dist/{input-BAOe64zx.js → input-D4kjoQUB.js} +8 -6
- package/dist/{label-BCWi-Oqu.js → label-BLqV33b1.js} +2 -2
- package/dist/{loader-BvW0-YWZ.js → loader-Dr8Qem8p.js} +1 -1
- package/dist/main.js +1697 -10282
- package/dist/{mermaid-cXSZ1pfD.js → mermaid-DO-Daq7u.js} +5 -5
- package/dist/{process-output-lpVrk7d5.js → process-output-X8TR20AK.js} +3 -3
- package/dist/reveal-component-kMIwe09M.js +7447 -0
- package/dist/{spec-DSIuqd3f.js → spec-hVaaZsY5.js} +4 -4
- package/dist/{strings-B_FOH6eV.js → strings-BiIhGaI8.js} +4 -4
- package/dist/style.css +1 -1
- package/dist/{swiper-component-BHs0PWwp.js → swiper-component-DlD2GU2g.js} +2 -2
- package/dist/{toDate-CHtl9vts.js → toDate-CIpC_34u.js} +33 -20
- package/dist/{tooltip-B0mtKTXm.js → tooltip-DRaMBu06.js} +3 -3
- package/dist/{types-DBtDeUKD.js → types-Dzuoc3LN.js} +1 -1
- package/dist/{useAsyncData-B6hCGywC.js → useAsyncData-C56Khv_R.js} +1 -1
- package/dist/{useDateFormatter-B3mCQMP3.js → useDateFormatter-B_9k85Ex.js} +2 -2
- package/dist/{useDeepCompareMemoize-CmwDuYUH.js → useDeepCompareMemoize-Dt98v2ua.js} +1 -1
- package/dist/{useIframeCapabilities-DbdLoEDm.js → useIframeCapabilities-BkYHTrss.js} +1 -1
- package/dist/{useLifecycle-CjMjllqy.js → useLifecycle-BF6-z62y.js} +3 -3
- package/dist/{useTheme-CByZUW0p.js → useTheme-DykuNHR2.js} +2 -2
- package/dist/{vega-component-C2BYPkfd.js → vega-component-cSdqoAxe.js} +10 -10
- package/dist/{zod-BxdsqRPd.js → zod-BWkcDORu.js} +1 -1
- package/package.json +3 -3
- package/src/components/chat/chat-components.tsx +47 -0
- package/src/components/chat/chat-display.tsx +41 -7
- package/src/components/chat/chat-panel.tsx +37 -10
- package/src/components/chat/chat-utils.ts +42 -20
- package/src/components/chat/reasoning-accordion.tsx +14 -3
- package/src/components/chat/tool-call/shared.ts +13 -0
- package/src/components/chat/tool-call/tool-approval-card.tsx +62 -0
- package/src/components/chat/tool-call/tool-args.tsx +26 -0
- package/src/components/chat/tool-call/tool-call-view.tsx +99 -0
- package/src/components/chat/tool-call/tool-error-card.tsx +81 -0
- package/src/components/chat/tool-call/tool-history-row.tsx +153 -0
- package/src/components/chat/tool-call/tool-result.tsx +101 -0
- package/src/components/data-table/__tests__/column-header.test.ts +3 -1
- package/src/components/data-table/__tests__/column-header.test.tsx +308 -0
- package/src/components/data-table/__tests__/filter-by-values-picker.test.tsx +112 -0
- package/src/components/data-table/__tests__/filter-pill-editor.test.tsx +261 -0
- package/src/components/data-table/__tests__/filters.test.ts +196 -49
- package/src/components/data-table/charts/components/form-fields.tsx +1 -0
- package/src/components/data-table/column-header.tsx +349 -170
- package/src/components/data-table/date-filter-inputs.tsx +325 -0
- package/src/components/data-table/filter-by-values-picker.tsx +70 -9
- package/src/components/data-table/filter-pill-editor.tsx +410 -156
- package/src/components/data-table/filter-pills.tsx +69 -54
- package/src/components/data-table/filters.ts +218 -101
- package/src/components/data-table/header-items.tsx +8 -1
- package/src/components/data-table/operator-labels.ts +25 -0
- package/src/components/data-table/regex-input.tsx +61 -0
- package/src/components/dependency-graph/minimap-content.tsx +14 -3
- package/src/components/editor/actions/pair-with-agent-modal.tsx +140 -49
- package/src/components/editor/actions/useNotebookActions.tsx +3 -1
- package/src/components/editor/app-container.tsx +7 -1
- package/src/components/editor/chrome/panels/context-aware-panel/context-aware-panel.tsx +10 -2
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +1 -0
- package/src/components/editor/chrome/wrapper/footer-items/backend-status.tsx +1 -1
- package/src/components/editor/chrome/wrapper/footer.tsx +4 -1
- package/src/components/editor/chrome/wrapper/panels.tsx +4 -1
- package/src/components/editor/chrome/wrapper/sidebar.tsx +4 -1
- package/src/components/editor/controls/Controls.tsx +11 -3
- package/src/components/editor/file-tree/file-explorer.tsx +12 -2
- package/src/components/editor/header/__tests__/status.test.tsx +108 -0
- package/src/components/editor/header/status.tsx +44 -10
- package/src/components/editor/navigation/__tests__/clipboard.test.ts +106 -0
- package/src/components/editor/navigation/__tests__/navigation.test.ts +70 -0
- package/src/components/editor/navigation/clipboard.ts +99 -25
- package/src/components/editor/navigation/navigation.ts +15 -1
- package/src/components/editor/notebook-cell.tsx +5 -0
- package/src/components/editor/output/console/ConsoleOutput.tsx +23 -5
- package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +114 -0
- package/src/components/editor/renderers/slides-layout/__tests__/compute-slide-cells.test.ts +5 -4
- package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +55 -15
- package/src/components/editor/renderers/slides-layout/plugin.tsx +8 -25
- package/src/components/editor/renderers/slides-layout/slides-layout.tsx +19 -6
- package/src/components/editor/renderers/slides-layout/types.ts +40 -31
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +1 -0
- package/src/components/home/components.tsx +6 -0
- package/src/components/pages/run-page.tsx +4 -1
- package/src/components/scratchpad/scratchpad.tsx +1 -0
- package/src/components/slides/__tests__/slide-notes.test.ts +131 -0
- package/src/components/slides/reveal-component.tsx +252 -147
- package/src/components/slides/slide-notes-editor.tsx +127 -0
- package/src/components/slides/slide-notes.ts +64 -0
- package/src/components/slides/slides.css +14 -0
- package/src/components/ui/combobox.tsx +24 -5
- package/src/components/ui/number-field.tsx +2 -0
- package/src/core/ai/tools/__tests__/registry.test.ts +10 -12
- package/src/core/ai/tools/registry.ts +9 -5
- package/src/core/cells/__tests__/cells.test.ts +187 -0
- package/src/core/cells/__tests__/pending-cut-service.test.tsx +123 -0
- package/src/core/cells/cells.ts +102 -17
- package/src/core/cells/document-changes.ts +6 -1
- package/src/core/cells/pending-cut-service.ts +55 -0
- package/src/core/cells/utils.ts +11 -0
- package/src/core/codemirror/cells/extensions.ts +10 -0
- package/src/core/codemirror/go-to-definition/__tests__/commands.test.ts +152 -0
- package/src/core/codemirror/go-to-definition/__tests__/utils.test.ts +99 -0
- package/src/core/codemirror/go-to-definition/commands.ts +382 -22
- package/src/core/codemirror/go-to-definition/utils.ts +23 -5
- package/src/core/edit-app.tsx +3 -2
- package/src/core/hotkeys/hotkeys.ts +5 -0
- package/src/core/islands/worker/worker.tsx +3 -2
- package/src/core/run-app.tsx +2 -1
- package/src/core/runtime/__tests__/runtime.test.ts +38 -17
- package/src/core/runtime/runtime.ts +57 -34
- package/src/core/wasm/__tests__/utils.test.ts +34 -0
- package/src/core/wasm/utils.ts +14 -0
- package/src/core/wasm/worker/bootstrap.ts +3 -2
- package/src/core/wasm/worker/worker.ts +3 -2
- package/src/core/websocket/__tests__/useMarimoKernelConnection.hook.test.tsx +156 -0
- package/src/core/websocket/__tests__/useMarimoKernelConnection.test.ts +101 -0
- package/src/core/websocket/transports/__tests__/ws.test.ts +125 -0
- package/src/core/websocket/transports/basic.ts +1 -1
- package/src/core/websocket/transports/ws.ts +96 -0
- package/src/core/websocket/useMarimoKernelConnection.tsx +133 -54
- package/src/core/websocket/useWebSocket.tsx +3 -15
- package/src/css/app/Cell.css +10 -0
- package/src/plugins/core/__test__/sanitize.test.ts +30 -0
- package/src/plugins/impl/DropdownPlugin.tsx +12 -1
- package/src/plugins/impl/MultiselectPlugin.tsx +4 -0
- package/src/plugins/impl/SearchableSelect.tsx +11 -1
- package/src/plugins/impl/TabsPlugin.tsx +35 -7
- package/src/plugins/impl/__tests__/DropdownPlugin.test.tsx +56 -0
- package/src/plugins/impl/__tests__/TabsPlugin.test.tsx +154 -0
- package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +48 -36
- package/src/plugins/impl/data-frames/schema.ts +4 -1
- package/src/plugins/layout/DownloadPlugin.tsx +9 -7
- package/src/utils/__tests__/id-tree.test.ts +71 -0
- package/src/utils/download.ts +4 -2
- package/src/utils/id-tree.tsx +89 -0
- package/dist/assets/__vite-browser-external-rrUYDKRl.js +0 -1
- package/dist/assets/worker-Bfy15ViQ.js +0 -73
- package/dist/reveal-component-C97Ceb7e.js +0 -4863
- package/src/components/chat/tool-call-accordion.tsx +0 -247
|
@@ -218,13 +218,14 @@ export function toDocumentChanges(
|
|
|
218
218
|
];
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
// dropCellOverCell/dropCellOverColumn/moveCellToIndex → set-config + reorder-cells
|
|
221
|
+
// dropCellOverCell/dropCellOverColumn/moveCellToIndex/moveCellsRelativeTo → set-config + reorder-cells
|
|
222
222
|
// Drag-and-drop reorders can move cells within or across columns.
|
|
223
223
|
// We emit config changes for cells whose column changed, then
|
|
224
224
|
// the full ordering.
|
|
225
225
|
case "dropCellOverCell":
|
|
226
226
|
case "dropCellOverColumn":
|
|
227
227
|
case "moveCellToIndex":
|
|
228
|
+
case "moveCellsRelativeTo":
|
|
228
229
|
return columnChanges(prevState, newState);
|
|
229
230
|
|
|
230
231
|
// updateCellCode → set-code
|
|
@@ -302,6 +303,10 @@ export function toDocumentChanges(
|
|
|
302
303
|
case "undoDeleteCell": {
|
|
303
304
|
const changes = newCellChanges(prevState, newState);
|
|
304
305
|
const colChanges = columnChanges(prevState, newState);
|
|
306
|
+
// Undo-cut has no new cells — always emit reorder to sync the move.
|
|
307
|
+
if (changes.length === 0) {
|
|
308
|
+
return colChanges;
|
|
309
|
+
}
|
|
305
310
|
// Only include column changes if layout actually changed
|
|
306
311
|
// (colChanges always has at least a reorder-cells change)
|
|
307
312
|
return colChanges.length > 1 ? [...changes, ...colChanges] : changes;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { atom, useAtomValue } from "jotai";
|
|
4
|
+
import type { CellId } from "@/core/cells/ids";
|
|
5
|
+
import { createReducerAndAtoms } from "@/utils/createReducer";
|
|
6
|
+
|
|
7
|
+
interface PendingCutState {
|
|
8
|
+
cellIds: Set<CellId>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const initialState = (): PendingCutState => ({
|
|
12
|
+
cellIds: new Set(),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
valueAtom: pendingCutStateAtom,
|
|
17
|
+
useActions: usePendingCutActionsInternal,
|
|
18
|
+
} = createReducerAndAtoms(initialState, {
|
|
19
|
+
markForCut: (_state, action: { cellIds: CellId[] }) => {
|
|
20
|
+
return {
|
|
21
|
+
cellIds: new Set(action.cellIds),
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
clear: () => {
|
|
25
|
+
return initialState();
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export { pendingCutStateAtom };
|
|
30
|
+
|
|
31
|
+
export const pendingCutCellIdsAtom = atom(
|
|
32
|
+
(get) => get(pendingCutStateAtom).cellIds,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export const clearPendingCutAtom = atom(null, (_get, set) => {
|
|
36
|
+
set(pendingCutStateAtom, initialState());
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export function usePendingCutActions() {
|
|
40
|
+
return usePendingCutActionsInternal();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function useIsPendingCut(cellId: CellId): boolean {
|
|
44
|
+
const cellIds = useAtomValue(pendingCutCellIdsAtom);
|
|
45
|
+
return cellIds.has(cellId);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function usePendingCutState() {
|
|
49
|
+
return useAtomValue(pendingCutStateAtom);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function useHasPendingCut(): boolean {
|
|
53
|
+
const state = useAtomValue(pendingCutStateAtom);
|
|
54
|
+
return state.cellIds.size > 0;
|
|
55
|
+
}
|
package/src/core/cells/utils.ts
CHANGED
|
@@ -65,6 +65,17 @@ export function canUndoDeletes(state: NotebookState) {
|
|
|
65
65
|
return state.history.length > 0;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Label for the undo action based on the last history entry type.
|
|
70
|
+
*/
|
|
71
|
+
export function getUndoLabel(state: NotebookState): string {
|
|
72
|
+
const last = state.history[state.history.length - 1];
|
|
73
|
+
if (!last) {
|
|
74
|
+
return "Undo cell deletion";
|
|
75
|
+
}
|
|
76
|
+
return last.type === "move" ? "Undo move" : "Undo cell deletion";
|
|
77
|
+
}
|
|
78
|
+
|
|
68
79
|
/**
|
|
69
80
|
* Get the status of the descendants of the given cell.
|
|
70
81
|
*/
|
|
@@ -10,6 +10,10 @@ import {
|
|
|
10
10
|
} from "@codemirror/view";
|
|
11
11
|
import { createTracebackInfoAtom } from "@/core/cells/cells";
|
|
12
12
|
import { type CellId, HTMLCellId, SCRATCH_CELL_ID } from "@/core/cells/ids";
|
|
13
|
+
import {
|
|
14
|
+
clearPendingCutAtom,
|
|
15
|
+
pendingCutCellIdsAtom,
|
|
16
|
+
} from "@/core/cells/pending-cut-service";
|
|
13
17
|
import { loroSyncAnnotation } from "@/core/codemirror/rtc/loro/sync";
|
|
14
18
|
import type { KeymapConfig } from "@/core/config/config-schema";
|
|
15
19
|
import type { HotkeyProvider } from "@/core/hotkeys/hotkeys";
|
|
@@ -326,6 +330,12 @@ function cellCodeEditing(hotkeys: HotkeyProvider): Extension[] {
|
|
|
326
330
|
code: nextCode,
|
|
327
331
|
formattingChange: isFormattingChange,
|
|
328
332
|
});
|
|
333
|
+
|
|
334
|
+
// Clear pending cut state if this cell was marked for cut
|
|
335
|
+
const pendingCutCellIds = store.get(pendingCutCellIdsAtom);
|
|
336
|
+
if (pendingCutCellIds.has(cellId)) {
|
|
337
|
+
store.set(clearPendingCutAtom);
|
|
338
|
+
}
|
|
329
339
|
}
|
|
330
340
|
});
|
|
331
341
|
|
|
@@ -101,6 +101,158 @@ print(x)`);
|
|
|
101
101
|
`);
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
+
test("selects the nearest in-scope local definition", async () => {
|
|
105
|
+
const code = `\
|
|
106
|
+
a = 10
|
|
107
|
+
|
|
108
|
+
def my_func():
|
|
109
|
+
a = 20
|
|
110
|
+
print(a)`;
|
|
111
|
+
view = createEditor(code);
|
|
112
|
+
const result = goToVariableDefinition(view, "a", code.lastIndexOf("a"));
|
|
113
|
+
|
|
114
|
+
expect(result).toBe(true);
|
|
115
|
+
await tick();
|
|
116
|
+
expect(renderEditorView(view)).toMatchInlineSnapshot(`
|
|
117
|
+
"
|
|
118
|
+
a = 10
|
|
119
|
+
|
|
120
|
+
def my_func():
|
|
121
|
+
a = 20
|
|
122
|
+
^
|
|
123
|
+
print(a)
|
|
124
|
+
"
|
|
125
|
+
`);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("selects the nearest in-scope parameter definition", async () => {
|
|
129
|
+
const code = `\
|
|
130
|
+
a = 10
|
|
131
|
+
|
|
132
|
+
def my_func(a):
|
|
133
|
+
print(a)`;
|
|
134
|
+
view = createEditor(code);
|
|
135
|
+
const result = goToVariableDefinition(view, "a", code.lastIndexOf("a"));
|
|
136
|
+
|
|
137
|
+
expect(result).toBe(true);
|
|
138
|
+
await tick();
|
|
139
|
+
expect(renderEditorView(view)).toMatchInlineSnapshot(`
|
|
140
|
+
"
|
|
141
|
+
a = 10
|
|
142
|
+
|
|
143
|
+
def my_func(a):
|
|
144
|
+
^
|
|
145
|
+
print(a)
|
|
146
|
+
"
|
|
147
|
+
`);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("selects the comprehension target inside a set comprehension", async () => {
|
|
151
|
+
const code = `\
|
|
152
|
+
x = 100
|
|
153
|
+
s = {x for x in range(10)}`;
|
|
154
|
+
view = createEditor(code);
|
|
155
|
+
// Go-to-definition on the `x` before `for` (the expression part of the
|
|
156
|
+
// comprehension).
|
|
157
|
+
const usagePosition = code.indexOf("{x") + 1;
|
|
158
|
+
const result = goToVariableDefinition(view, "x", usagePosition);
|
|
159
|
+
|
|
160
|
+
expect(result).toBe(true);
|
|
161
|
+
await tick();
|
|
162
|
+
// Should jump to the comprehension target `x` (after `for`), not the
|
|
163
|
+
// outer `x = 100`. The Lezer Python grammar emits
|
|
164
|
+
// `SetComprehensionExpression`, and we now correctly match it in
|
|
165
|
+
// SCOPE_CREATING_NODES, so the comprehension creates a scope and the
|
|
166
|
+
// for-target is collected correctly.
|
|
167
|
+
expect(renderEditorView(view)).toMatchInlineSnapshot(`
|
|
168
|
+
"
|
|
169
|
+
x = 100
|
|
170
|
+
s = {x for x in range(10)}
|
|
171
|
+
^
|
|
172
|
+
"
|
|
173
|
+
`);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("selects the comprehension target inside a dict comprehension", async () => {
|
|
177
|
+
const code = `\
|
|
178
|
+
x = 100
|
|
179
|
+
d = {x: x for x in range(10)}`;
|
|
180
|
+
view = createEditor(code);
|
|
181
|
+
const usagePosition = code.indexOf("{x") + 1;
|
|
182
|
+
const result = goToVariableDefinition(view, "x", usagePosition);
|
|
183
|
+
|
|
184
|
+
expect(result).toBe(true);
|
|
185
|
+
await tick();
|
|
186
|
+
// Positive control: `DictionaryComprehensionExpression` matches the grammar
|
|
187
|
+
// and is in SCOPE_CREATING_NODES, so this should jump to the comprehension
|
|
188
|
+
// target `x` (after `for`).
|
|
189
|
+
expect(renderEditorView(view)).toMatchInlineSnapshot(`
|
|
190
|
+
"
|
|
191
|
+
x = 100
|
|
192
|
+
d = {x: x for x in range(10)}
|
|
193
|
+
^
|
|
194
|
+
"
|
|
195
|
+
`);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("skips enclosing class scope when resolving from inside a method", async () => {
|
|
199
|
+
const code = `\
|
|
200
|
+
x = 100
|
|
201
|
+
class Foo:
|
|
202
|
+
x = 10
|
|
203
|
+
def method(self):
|
|
204
|
+
return x`;
|
|
205
|
+
view = createEditor(code);
|
|
206
|
+
// Go-to-definition on the `x` inside `return x`.
|
|
207
|
+
const usagePosition = code.lastIndexOf("x");
|
|
208
|
+
const result = goToVariableDefinition(view, "x", usagePosition);
|
|
209
|
+
|
|
210
|
+
expect(result).toBe(true);
|
|
211
|
+
await tick();
|
|
212
|
+
// Should jump to `x = 100` at module scope. In Python, methods do NOT see
|
|
213
|
+
// their enclosing class body's names — class scopes are skipped in LEGB
|
|
214
|
+
// lookup once a function boundary has been crossed. We now correctly skip
|
|
215
|
+
// ClassDefinition in getScopeChain once a function boundary is crossed.
|
|
216
|
+
expect(renderEditorView(view)).toMatchInlineSnapshot(`
|
|
217
|
+
"
|
|
218
|
+
x = 100
|
|
219
|
+
^
|
|
220
|
+
class Foo:
|
|
221
|
+
x = 10
|
|
222
|
+
def method(self):
|
|
223
|
+
return x
|
|
224
|
+
"
|
|
225
|
+
`);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("resolves a global forward-reference from inside a function", async () => {
|
|
229
|
+
const code = `\
|
|
230
|
+
def foo():
|
|
231
|
+
return a
|
|
232
|
+
|
|
233
|
+
a = 10`;
|
|
234
|
+
view = createEditor(code);
|
|
235
|
+
// Go-to-definition on the `a` inside `return a`.
|
|
236
|
+
const usagePosition = code.indexOf("return a") + "return ".length;
|
|
237
|
+
const result = goToVariableDefinition(view, "a", usagePosition);
|
|
238
|
+
|
|
239
|
+
expect(result).toBe(true);
|
|
240
|
+
await tick();
|
|
241
|
+
// Should jump to `a = 10` at the bottom. Python allows forward references
|
|
242
|
+
// from within nested functions to module-level names. We now correctly omit
|
|
243
|
+
// "global" from POSITION_SENSITIVE_SCOPES, allowing forward references to
|
|
244
|
+
// global-level definitions declared after the usage position.
|
|
245
|
+
expect(renderEditorView(view)).toMatchInlineSnapshot(`
|
|
246
|
+
"
|
|
247
|
+
def foo():
|
|
248
|
+
return a
|
|
249
|
+
|
|
250
|
+
a = 10
|
|
251
|
+
^
|
|
252
|
+
"
|
|
253
|
+
`);
|
|
254
|
+
});
|
|
255
|
+
|
|
104
256
|
test("selects outer-scope function declaration", async () => {
|
|
105
257
|
view = createEditor(`\
|
|
106
258
|
def x():
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { python } from "@codemirror/lang-python";
|
|
4
|
+
import { EditorState } from "@codemirror/state";
|
|
5
|
+
import { EditorView } from "@codemirror/view";
|
|
6
|
+
import { afterEach, describe, expect, test } from "vitest";
|
|
7
|
+
import { cellId, variableName } from "@/__tests__/branded";
|
|
8
|
+
import { initialNotebookState, notebookAtom } from "@/core/cells/cells";
|
|
9
|
+
import { store } from "@/core/state/jotai";
|
|
10
|
+
import { variablesAtom } from "@/core/variables/state";
|
|
11
|
+
import { goToDefinitionAtCursorPosition } from "../utils";
|
|
12
|
+
|
|
13
|
+
async function tick(): Promise<void> {
|
|
14
|
+
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function createEditor(content: string, selection: number) {
|
|
18
|
+
const state = EditorState.create({
|
|
19
|
+
doc: content,
|
|
20
|
+
selection: { anchor: selection },
|
|
21
|
+
extensions: [python()],
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return new EditorView({
|
|
25
|
+
state,
|
|
26
|
+
parent: document.body,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const views: EditorView[] = [];
|
|
31
|
+
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
for (const view of views.splice(0)) {
|
|
34
|
+
view.destroy();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
store.set(notebookAtom, initialNotebookState());
|
|
38
|
+
store.set(variablesAtom, {});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("goToDefinitionAtCursorPosition", () => {
|
|
42
|
+
test("prefers the current-cell local definition over a reactive global", async () => {
|
|
43
|
+
const globalCell = cellId("global-cell");
|
|
44
|
+
const localCell = cellId("local-cell");
|
|
45
|
+
const globalCode = `\
|
|
46
|
+
a = 10
|
|
47
|
+
print(a)`;
|
|
48
|
+
const localCode = `\
|
|
49
|
+
def test():
|
|
50
|
+
a = 20
|
|
51
|
+
print(a)`;
|
|
52
|
+
|
|
53
|
+
const globalView = createEditor(globalCode, globalCode.length);
|
|
54
|
+
const localView = createEditor(localCode, localCode.lastIndexOf("a"));
|
|
55
|
+
views.push(globalView, localView);
|
|
56
|
+
|
|
57
|
+
const notebook = initialNotebookState();
|
|
58
|
+
notebook.cellHandles[globalCell] = {
|
|
59
|
+
current: { editorView: globalView, editorViewOrNull: globalView },
|
|
60
|
+
};
|
|
61
|
+
notebook.cellHandles[localCell] = {
|
|
62
|
+
current: { editorView: localView, editorViewOrNull: localView },
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
store.set(notebookAtom, notebook);
|
|
66
|
+
store.set(variablesAtom, {
|
|
67
|
+
[variableName("a")]: {
|
|
68
|
+
dataType: "int",
|
|
69
|
+
declaredBy: [globalCell],
|
|
70
|
+
name: variableName("a"),
|
|
71
|
+
usedBy: [localCell],
|
|
72
|
+
value: "10",
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const result = goToDefinitionAtCursorPosition(localView);
|
|
77
|
+
|
|
78
|
+
expect(result).toBe(true);
|
|
79
|
+
await tick();
|
|
80
|
+
expect(localView.state.selection.main.head).toBe(
|
|
81
|
+
localCode.indexOf("a = 20"),
|
|
82
|
+
);
|
|
83
|
+
expect(globalView.state.selection.main.head).toBe(globalCode.length);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("keeps private variables within the current cell", async () => {
|
|
87
|
+
const code = `\
|
|
88
|
+
_x = 10
|
|
89
|
+
output = _x + 10`;
|
|
90
|
+
const view = createEditor(code, code.lastIndexOf("_x"));
|
|
91
|
+
views.push(view);
|
|
92
|
+
|
|
93
|
+
const result = goToDefinitionAtCursorPosition(view);
|
|
94
|
+
|
|
95
|
+
expect(result).toBe(true);
|
|
96
|
+
await tick();
|
|
97
|
+
expect(view.state.selection.main.head).toBe(code.indexOf("_x = 10"));
|
|
98
|
+
});
|
|
99
|
+
});
|