@marimo-team/frontend 0.23.12-dev19 → 0.23.12-dev20

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.
Files changed (72) hide show
  1. package/dist/assets/{CellStatus-BGSEw-sx.js → CellStatus-xkT4Dvj4.js} +1 -1
  2. package/dist/assets/{JsonOutput-BOGU8eqb.js → JsonOutput-B1X71OMV.js} +1 -1
  3. package/dist/assets/{MarimoErrorOutput-G8Q8ZYoi.js → MarimoErrorOutput-BQt_FXJL.js} +1 -1
  4. package/dist/assets/{RenderHTML-DXJCvdz4.js → RenderHTML-Cq4mwWYz.js} +1 -1
  5. package/dist/assets/{RunButton-B0ruC_FE.js → RunButton-DdQ4s_KP.js} +1 -1
  6. package/dist/assets/{add-cell-with-ai-BiqhfNGT.js → add-cell-with-ai-C-PLUgpy.js} +1 -1
  7. package/dist/assets/{add-connection-dialog-Dw-5iYFw.js → add-connection-dialog-ZpfrNSP5.js} +1 -1
  8. package/dist/assets/{agent-panel-CU-4QwJ9.js → agent-panel-DvRo8bi8.js} +1 -1
  9. package/dist/assets/{ai-model-dropdown-DGxHCaIB.js → ai-model-dropdown-bEbPXoSM.js} +1 -1
  10. package/dist/assets/{app-config-button-DPeNpqDG.js → app-config-button-C_o1pRld.js} +1 -1
  11. package/dist/assets/{cell-editor-C-U2giX2.js → cell-editor-BWw39or5.js} +1 -1
  12. package/dist/assets/{cell-link-BWqpbaiv.js → cell-link-DgWTiCtG.js} +1 -1
  13. package/dist/assets/{cells-BidbgSuB.js → cells-06TDmJ8V.js} +49 -49
  14. package/dist/assets/{chat-display-DP2qHVOG.js → chat-display-DbDJHscS.js} +1 -1
  15. package/dist/assets/{chat-panel-B2jv2agJ.js → chat-panel-8tlYTTQ-.js} +1 -1
  16. package/dist/assets/{chat-ui-DQt1g2fB.js → chat-ui-BvFUJMPP.js} +1 -1
  17. package/dist/assets/{column-preview-rFcgfgNL.js → column-preview-CchF5iQz.js} +1 -1
  18. package/dist/assets/{command-palette-Beut-0QI.js → command-palette-ByJVoDSa.js} +1 -1
  19. package/dist/assets/{common-B94MReIe.js → common-cKvfH0fD.js} +1 -1
  20. package/dist/assets/{components-DoRr7Hf1.js → components-BhmX9dcm.js} +1 -1
  21. package/dist/assets/{datasource-DI-YUWev.js → datasource-mVfTJz3V.js} +1 -1
  22. package/dist/assets/{dependency-graph-panel-B7_LMfP4.js → dependency-graph-panel-D9C_hBQw.js} +1 -1
  23. package/dist/assets/{documentation-panel-BGXFZYSy.js → documentation-panel-C0uXzYri.js} +1 -1
  24. package/dist/assets/{download-DgEarSps.js → download-VxqV5OSK.js} +1 -1
  25. package/dist/assets/{edit-page-C5sl-KY4.js → edit-page-DkG-GrgE.js} +4 -4
  26. package/dist/assets/{error-panel-Bd98g_JW.js → error-panel-D2WmJDnT.js} +1 -1
  27. package/dist/assets/{file-explorer-panel-DgVrcqyP.js → file-explorer-panel-Bq51Bmbd.js} +1 -1
  28. package/dist/assets/{file-icons-BwFX01F6.js → file-icons-BeJWUbBH.js} +1 -1
  29. package/dist/assets/{file-name-input-C_iFa52z.js → file-name-input-Slq_KVFS.js} +1 -1
  30. package/dist/assets/{floating-outline-BDx00oB1.js → floating-outline-3UBj62S1.js} +1 -1
  31. package/dist/assets/{focus-CocfMydm.js → focus-DqhVePQT.js} +1 -1
  32. package/dist/assets/{form-CgvTS8au.js → form-BAB18E37.js} +1 -1
  33. package/dist/assets/{home-page-KU0KRFti.js → home-page-Bhs2dkdU.js} +1 -1
  34. package/dist/assets/{hooks-D-42xkyU.js → hooks-BsAxp2GU.js} +1 -1
  35. package/dist/assets/{html-to-image-TCtIotr9.js → html-to-image-CbQeOjEJ.js} +1 -1
  36. package/dist/assets/{index-BUZYcPA6.js → index-D5xY00XH.js} +3 -3
  37. package/dist/assets/{kiosk-mode-sfaqwl5N.js → kiosk-mode-CyYnV3_y.js} +1 -1
  38. package/dist/assets/{layout-ymtRlhgJ.js → layout-wXClIsC4.js} +3 -3
  39. package/dist/assets/{logs-panel-Bs7u1Wfd.js → logs-panel-CiP_30S4.js} +1 -1
  40. package/dist/assets/{markdown-renderer-CGZTB2Dx.js → markdown-renderer-C1yypOYF.js} +1 -1
  41. package/dist/assets/{name-cell-input-D4k4IMJD.js → name-cell-input-CIkFEB6-.js} +1 -1
  42. package/dist/assets/{outline-panel-5sRz-QR2.js → outline-panel-BfysapqU.js} +1 -1
  43. package/dist/assets/{packages-panel-jBZjAS5M.js → packages-panel-Cu-rx9mM.js} +1 -1
  44. package/dist/assets/{panels-BYTI6wmu.js → panels-CHtiP6qk.js} +1 -1
  45. package/dist/assets/{process-output-CRIsE74Q.js → process-output-BIlXY0iE.js} +1 -1
  46. package/dist/assets/{radio-group-Czn6pjhm.js → radio-group-BaOG2bSA.js} +1 -1
  47. package/dist/assets/{readonly-python-code-DtUWOxnZ.js → readonly-python-code-CNyK8G1T.js} +1 -1
  48. package/dist/assets/{reveal-component-BxjPJT8L.js → reveal-component-DeMaX7RH.js} +1 -1
  49. package/dist/assets/{run-page-CBS-EWhB.js → run-page-CE_UtXUq.js} +1 -1
  50. package/dist/assets/{scratchpad-panel-ClomOYd7.js → scratchpad-panel-D5KM35-P.js} +1 -1
  51. package/dist/assets/{session-panel-BqPBoPls.js → session-panel-D64OCrhT.js} +1 -1
  52. package/dist/assets/{snippets-panel-94zH2JCj.js → snippets-panel-DV2ir7q8.js} +1 -1
  53. package/dist/assets/{state-ClDZE-YW.js → state-B3ypjIx-.js} +1 -1
  54. package/dist/assets/{state-BZgmxb7a.js → state-DD6m9X2h.js} +1 -1
  55. package/dist/assets/{textarea-Dz09niIv.js → textarea-BM7hAwR3.js} +1 -1
  56. package/dist/assets/{tracing-C_GaBGFx.js → tracing-DwjgsPkU.js} +1 -1
  57. package/dist/assets/{tracing-panel-BkzzwM_5.js → tracing-panel-BS9eUNK_.js} +2 -2
  58. package/dist/assets/{tree-actions-CesD4Rcl.js → tree-actions-lVyJAYy7.js} +1 -1
  59. package/dist/assets/{useCellActionButton-DNYd1sgZ.js → useCellActionButton-B88iHApv.js} +1 -1
  60. package/dist/assets/{useDeleteCell-CCCMHD2a.js → useDeleteCell-CXc93le9.js} +1 -1
  61. package/dist/assets/{useDependencyPanelTab-P57LIkal.js → useDependencyPanelTab-9Vnr8-OR.js} +1 -1
  62. package/dist/assets/{useNotebookActions-DFMW4uy3.js → useNotebookActions-Ct85HJ1G.js} +1 -1
  63. package/dist/assets/{useRunCells-DFXV3Rm5.js → useRunCells-DNaOKKqW.js} +1 -1
  64. package/dist/assets/{useSplitCell-R-0wFhnd.js → useSplitCell-IsXuVcLF.js} +1 -1
  65. package/dist/index.html +22 -22
  66. package/package.json +1 -1
  67. package/src/core/codemirror/go-to-definition/__tests__/commands.test.ts +67 -0
  68. package/src/core/codemirror/go-to-definition/__tests__/utils.test.ts +47 -0
  69. package/src/core/codemirror/go-to-definition/commands.ts +47 -30
  70. package/src/core/codemirror/go-to-definition/utils.ts +0 -1
  71. package/src/core/codemirror/reactive-references/__tests__/analyzer.test.ts +54 -0
  72. package/src/core/codemirror/reactive-references/analyzer.ts +44 -35
package/dist/index.html CHANGED
@@ -66,7 +66,7 @@
66
66
  <marimo-server-token data-token="{{ server_token }}" hidden></marimo-server-token>
67
67
  <!-- /TODO -->
68
68
  <title>{{ title }}</title>
69
- <script type="module" crossorigin src="./assets/index-BUZYcPA6.js"></script>
69
+ <script type="module" crossorigin src="./assets/index-D5xY00XH.js"></script>
70
70
  <link rel="modulepreload" crossorigin href="./assets/preload-helper-y72bE5iF.js">
71
71
  <link rel="modulepreload" crossorigin href="./assets/chunk-LvLJmgfZ.js">
72
72
  <link rel="modulepreload" crossorigin href="./assets/react-Bj1aDYRI.js">
@@ -133,7 +133,7 @@
133
133
  <link rel="modulepreload" crossorigin href="./assets/debounce-OiXznToA.js">
134
134
  <link rel="modulepreload" crossorigin href="./assets/database-zap-DIlSqcdp.js">
135
135
  <link rel="modulepreload" crossorigin href="./assets/main-DmhaVp6Y.js">
136
- <link rel="modulepreload" crossorigin href="./assets/cells-BidbgSuB.js">
136
+ <link rel="modulepreload" crossorigin href="./assets/cells-06TDmJ8V.js">
137
137
  <link rel="modulepreload" crossorigin href="./assets/ErrorBoundary-Bo313Z9X.js">
138
138
  <link rel="modulepreload" crossorigin href="./assets/kbd-BCXa3Ocm.js">
139
139
  <link rel="modulepreload" crossorigin href="./assets/useInstallPackage-DEHK-C20.js">
@@ -147,38 +147,38 @@
147
147
  <link rel="modulepreload" crossorigin href="./assets/usePress-mjvipKvz.js">
148
148
  <link rel="modulepreload" crossorigin href="./assets/input-b2KTOk0X.js">
149
149
  <link rel="modulepreload" crossorigin href="./assets/ImperativeModal-MQnbMIO2.js">
150
- <link rel="modulepreload" crossorigin href="./assets/cell-link-BWqpbaiv.js">
150
+ <link rel="modulepreload" crossorigin href="./assets/cell-link-DgWTiCtG.js">
151
151
  <link rel="modulepreload" crossorigin href="./assets/multi-map-9ogswH46.js">
152
152
  <link rel="modulepreload" crossorigin href="./assets/alert-Bjh7v4lb.js">
153
153
  <link rel="modulepreload" crossorigin href="./assets/chevron-right-C7RH0Nc7.js">
154
154
  <link rel="modulepreload" crossorigin href="./assets/dropdown-menu-DfGLyCbP.js">
155
155
  <link rel="modulepreload" crossorigin href="./assets/links-DK7te6sJ.js">
156
- <link rel="modulepreload" crossorigin href="./assets/useRunCells-DFXV3Rm5.js">
156
+ <link rel="modulepreload" crossorigin href="./assets/useRunCells-DNaOKKqW.js">
157
157
  <link rel="modulepreload" crossorigin href="./assets/copy-CyasABTK.js">
158
158
  <link rel="modulepreload" crossorigin href="./assets/copy-IBNGDypD.js">
159
159
  <link rel="modulepreload" crossorigin href="./assets/copy-icon-BCIGtBo0.js">
160
- <link rel="modulepreload" crossorigin href="./assets/RenderHTML-DXJCvdz4.js">
161
- <link rel="modulepreload" crossorigin href="./assets/datasource-DI-YUWev.js">
162
- <link rel="modulepreload" crossorigin href="./assets/state-ClDZE-YW.js">
160
+ <link rel="modulepreload" crossorigin href="./assets/RenderHTML-Cq4mwWYz.js">
161
+ <link rel="modulepreload" crossorigin href="./assets/datasource-mVfTJz3V.js">
162
+ <link rel="modulepreload" crossorigin href="./assets/state-B3ypjIx-.js">
163
163
  <link rel="modulepreload" crossorigin href="./assets/package-BoMcg6sy.js">
164
164
  <link rel="modulepreload" crossorigin href="./assets/sparkles-DctOjjEz.js">
165
- <link rel="modulepreload" crossorigin href="./assets/MarimoErrorOutput-G8Q8ZYoi.js">
165
+ <link rel="modulepreload" crossorigin href="./assets/MarimoErrorOutput-BQt_FXJL.js">
166
166
  <link rel="modulepreload" crossorigin href="./assets/eye-off-DI0EQXw0.js">
167
167
  <link rel="modulepreload" crossorigin href="./assets/refresh-cw-DcEasnos.js">
168
- <link rel="modulepreload" crossorigin href="./assets/tree-actions-CesD4Rcl.js">
168
+ <link rel="modulepreload" crossorigin href="./assets/tree-actions-lVyJAYy7.js">
169
169
  <link rel="modulepreload" crossorigin href="./assets/spinner-DTBVbtl-.js">
170
- <link rel="modulepreload" crossorigin href="./assets/html-to-image-TCtIotr9.js">
171
- <link rel="modulepreload" crossorigin href="./assets/focus-CocfMydm.js">
170
+ <link rel="modulepreload" crossorigin href="./assets/html-to-image-CbQeOjEJ.js">
171
+ <link rel="modulepreload" crossorigin href="./assets/focus-DqhVePQT.js">
172
172
  <link rel="modulepreload" crossorigin href="./assets/useAsyncData-CT1LpOBD.js">
173
173
  <link rel="modulepreload" crossorigin href="./assets/LazyAnyLanguageCodeMirror-CUQODgGJ.js">
174
174
  <link rel="modulepreload" crossorigin href="./assets/micromark-factory-space-P--XWZhg.js">
175
175
  <link rel="modulepreload" crossorigin href="./assets/chunk-5FQGJX7Z-C6VN0dQj.js">
176
- <link rel="modulepreload" crossorigin href="./assets/markdown-renderer-CGZTB2Dx.js">
176
+ <link rel="modulepreload" crossorigin href="./assets/markdown-renderer-C1yypOYF.js">
177
177
  <link rel="modulepreload" crossorigin href="./assets/command-CsbsBLBw.js">
178
178
  <link rel="modulepreload" crossorigin href="./assets/field-CA9HCE2Y.js">
179
179
  <link rel="modulepreload" crossorigin href="./assets/popover-DMNO6Poc.js">
180
180
  <link rel="modulepreload" crossorigin href="./assets/errors-48KFPvCp.js">
181
- <link rel="modulepreload" crossorigin href="./assets/download-DgEarSps.js">
181
+ <link rel="modulepreload" crossorigin href="./assets/download-VxqV5OSK.js">
182
182
  <link rel="modulepreload" crossorigin href="./assets/table-Fk_msRcD.js">
183
183
  <link rel="modulepreload" crossorigin href="./assets/useIframeCapabilities-DuGyzPVb.js">
184
184
  <link rel="modulepreload" crossorigin href="./assets/error-banner-C4DZksWV.js">
@@ -198,21 +198,21 @@
198
198
  <link rel="modulepreload" crossorigin href="./assets/plus-CExKUszm.js">
199
199
  <link rel="modulepreload" crossorigin href="./assets/trash-2-CjPoUZCP.js">
200
200
  <link rel="modulepreload" crossorigin href="./assets/react-resizable-panels.browser.esm-CgWOEYeG.js">
201
- <link rel="modulepreload" crossorigin href="./assets/JsonOutput-BOGU8eqb.js">
201
+ <link rel="modulepreload" crossorigin href="./assets/JsonOutput-B1X71OMV.js">
202
202
  <link rel="modulepreload" crossorigin href="./assets/chart-no-axes-column-DVEV2j1H.js">
203
203
  <link rel="modulepreload" crossorigin href="./assets/square-function-CPbVtjKW.js">
204
204
  <link rel="modulepreload" crossorigin href="./assets/spec-BzxoPflZ.js">
205
- <link rel="modulepreload" crossorigin href="./assets/column-preview-rFcgfgNL.js">
205
+ <link rel="modulepreload" crossorigin href="./assets/column-preview-CchF5iQz.js">
206
206
  <link rel="modulepreload" crossorigin href="./assets/icons-D3YFXNmD.js">
207
- <link rel="modulepreload" crossorigin href="./assets/radio-group-Czn6pjhm.js">
208
- <link rel="modulepreload" crossorigin href="./assets/floating-outline-BDx00oB1.js">
207
+ <link rel="modulepreload" crossorigin href="./assets/radio-group-BaOG2bSA.js">
208
+ <link rel="modulepreload" crossorigin href="./assets/floating-outline-3UBj62S1.js">
209
209
  <link rel="modulepreload" crossorigin href="./assets/objectWithoutPropertiesLoose-Msf1n32r.js">
210
210
  <link rel="modulepreload" crossorigin href="./assets/esm-DSv-aO_T.js">
211
- <link rel="modulepreload" crossorigin href="./assets/readonly-python-code-DtUWOxnZ.js">
211
+ <link rel="modulepreload" crossorigin href="./assets/readonly-python-code-CNyK8G1T.js">
212
212
  <link rel="modulepreload" crossorigin href="./assets/file-headphone-CUN2DC78.js">
213
213
  <link rel="modulepreload" crossorigin href="./assets/file-BvoBtS_V.js">
214
214
  <link rel="modulepreload" crossorigin href="./assets/image-jhXeQEgs.js">
215
- <link rel="modulepreload" crossorigin href="./assets/file-icons-BwFX01F6.js">
215
+ <link rel="modulepreload" crossorigin href="./assets/file-icons-BeJWUbBH.js">
216
216
  <link rel="modulepreload" crossorigin href="./assets/switch-DHhZkGEv.js">
217
217
  <link rel="modulepreload" crossorigin href="./assets/events-BRbNg7Li.js">
218
218
  <link rel="modulepreload" crossorigin href="./assets/globals-BDWM3c_z.js">
@@ -221,11 +221,11 @@
221
221
  <link rel="modulepreload" crossorigin href="./assets/memoize-Tp7rARFe.js">
222
222
  <link rel="modulepreload" crossorigin href="./assets/get-C-qh_et5.js">
223
223
  <link rel="modulepreload" crossorigin href="./assets/_baseSet-CsW0tABf.js">
224
- <link rel="modulepreload" crossorigin href="./assets/state-BZgmxb7a.js">
224
+ <link rel="modulepreload" crossorigin href="./assets/state-DD6m9X2h.js">
225
225
  <link rel="modulepreload" crossorigin href="./assets/label-CFvCMYuj.js">
226
- <link rel="modulepreload" crossorigin href="./assets/textarea-Dz09niIv.js">
226
+ <link rel="modulepreload" crossorigin href="./assets/textarea-BM7hAwR3.js">
227
227
  <link rel="modulepreload" crossorigin href="./assets/refresh-ccw-CUkO1Vlo.js">
228
- <link rel="modulepreload" crossorigin href="./assets/form-CgvTS8au.js">
228
+ <link rel="modulepreload" crossorigin href="./assets/form-BAB18E37.js">
229
229
  <link rel="modulepreload" crossorigin href="./assets/renderShortcut-CKbqJyj4.js">
230
230
  <link rel="modulepreload" crossorigin href="./assets/useBoolean-48bgGoSa.js">
231
231
  <link rel="modulepreload" crossorigin href="./assets/useDeepCompareMemoize-zUHU--0D.js">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/frontend",
3
- "version": "0.23.12-dev19",
3
+ "version": "0.23.12-dev20",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -253,6 +253,73 @@ a = 10`;
253
253
  `);
254
254
  });
255
255
 
256
+ test("from-import alias is the binding, not the imported name", async () => {
257
+ const code = `\
258
+ from math import sin as my_sin
259
+ print(my_sin)`;
260
+ view = createEditor(code);
261
+ const usagePosition = code.lastIndexOf("my_sin");
262
+ const result = goToVariableDefinition(view, "my_sin", usagePosition);
263
+
264
+ expect(result).toBe(true);
265
+ await tick();
266
+ // The alias `my_sin` (after `as`) is the real binding.
267
+ expect(renderEditorView(view)).toMatchInlineSnapshot(`
268
+ "
269
+ from math import sin as my_sin
270
+ ^
271
+ print(my_sin)
272
+ "
273
+ `);
274
+ });
275
+
276
+ test("module path in from-import is not a local definition", async () => {
277
+ const code = `\
278
+ from math import sin
279
+ print(math)`;
280
+ view = createEditor(code);
281
+ const usagePosition = code.lastIndexOf("math");
282
+ // `math` is a module reference in the from-clause, not a binding in this
283
+ // cell, so the scoped resolver should return false and let the caller fall
284
+ // through to cross-cell resolution.
285
+ const result = goToVariableDefinition(view, "math", usagePosition);
286
+
287
+ expect(result).toBe(false);
288
+ expect(view.state.selection.main.head).toBe(0);
289
+ });
290
+
291
+ test("imported name without `as` is a local definition", async () => {
292
+ const code = `\
293
+ from math import sin
294
+ print(sin)`;
295
+ view = createEditor(code);
296
+ const usagePosition = code.lastIndexOf("sin");
297
+ const result = goToVariableDefinition(view, "sin", usagePosition);
298
+
299
+ expect(result).toBe(true);
300
+ await tick();
301
+ expect(renderEditorView(view)).toMatchInlineSnapshot(`
302
+ "
303
+ from math import sin
304
+ ^
305
+ print(sin)
306
+ "
307
+ `);
308
+ });
309
+
310
+ test("imported name shadowed by `as` is not a binding", async () => {
311
+ const code = `\
312
+ from math import sin as my_sin
313
+ print(sin)`;
314
+ view = createEditor(code);
315
+ const usagePosition = code.lastIndexOf("sin");
316
+ // `sin` here refers to nothing in this cell (it was renamed to `my_sin`),
317
+ // so the scoped resolver should return false.
318
+ const result = goToVariableDefinition(view, "sin", usagePosition);
319
+
320
+ expect(result).toBe(false);
321
+ });
322
+
256
323
  test("selects outer-scope function declaration", async () => {
257
324
  view = createEditor(`\
258
325
  def x():
@@ -133,4 +133,51 @@ output = _x + 10`;
133
133
  await tick();
134
134
  expect(view.state.selection.main.head).toBe(code.indexOf("_x = 10"));
135
135
  });
136
+
137
+ test("falls through to cross-cell when in-cell occurrence is only a module path in a from-import", async () => {
138
+ // Regression: ImportStatement used to register every VariableName child
139
+ // (the module path and pre-`as` names) as in-cell declarations, so the
140
+ // local-first short-circuit would steal F12 from cross-cell resolution.
141
+ const moduleCell = cellId("module-cell");
142
+ const usageCell = cellId("usage-cell");
143
+ const moduleCode = `mymodule = 100`;
144
+ const usageCode = `\
145
+ from mymodule import something
146
+ print(mymodule)`;
147
+
148
+ const moduleView = createEditor(moduleCode, moduleCode.length);
149
+ const usageView = createEditor(
150
+ usageCode,
151
+ usageCode.lastIndexOf("mymodule"),
152
+ );
153
+ views.push(moduleView, usageView);
154
+
155
+ const notebook = initialNotebookState();
156
+ notebook.cellHandles[moduleCell] = {
157
+ current: { editorView: moduleView, editorViewOrNull: moduleView },
158
+ } as never;
159
+ notebook.cellHandles[usageCell] = {
160
+ current: { editorView: usageView, editorViewOrNull: usageView },
161
+ } as never;
162
+
163
+ store.set(notebookAtom, notebook);
164
+ store.set(variablesAtom, {
165
+ [variableName("mymodule")]: {
166
+ dataType: "int",
167
+ declaredBy: [moduleCell],
168
+ name: variableName("mymodule"),
169
+ usedBy: [usageCell],
170
+ value: "100",
171
+ },
172
+ });
173
+
174
+ const result = goToDefinitionAtCursorPosition(usageView);
175
+
176
+ expect(result).toBe(true);
177
+ await tick();
178
+ // Cross-cell jump: moduleView's cursor should land on `mymodule = 100`.
179
+ expect(moduleView.state.selection.main.head).toBe(
180
+ moduleCode.indexOf("mymodule"),
181
+ );
182
+ });
136
183
  });
@@ -307,33 +307,52 @@ function collectMatchingDeclarations(
307
307
  break;
308
308
  }
309
309
  case "ImportStatement": {
310
+ // The grammar emits one ImportStatement for both `import x [as y]` and
311
+ // `from m import x [as y], ...`. Direct children include the keywords
312
+ // (`from`/`import`/`as`), commas, dots, and every VariableName from the
313
+ // module path AND the import list. We only want the names that actually
314
+ // bind in the current scope: the post-`as` alias if present, otherwise
315
+ // the imported name itself. Names before `import` (the from-path) and
316
+ // the original name when an alias follows it are NOT bindings.
310
317
  const subCursor = node.cursor();
311
318
  subCursor.firstChild();
312
- do {
313
- if (
314
- subCursor.name === "VariableName" &&
315
- state.doc.sliceString(subCursor.from, subCursor.to) === variableName
316
- ) {
317
- addDeclaration(declarations, currentScope, subCursor.from);
319
+ let pastImport = false;
320
+ // Buffer the most recent post-`import` VariableName so we can defer
321
+ // committing it until we know whether `as` follows.
322
+ let pending: { from: number; matches: boolean } | null = null;
323
+ const commit = () => {
324
+ if (pending?.matches) {
325
+ addDeclaration(declarations, currentScope, pending.from);
318
326
  }
319
- } while (subCursor.nextSibling());
320
- break;
321
- }
322
- case "ImportFromStatement": {
323
- const subCursor = node.cursor();
324
- subCursor.firstChild();
325
- let foundImport = false;
327
+ pending = null;
328
+ };
326
329
  do {
327
330
  if (subCursor.name === "import") {
328
- foundImport = true;
329
- } else if (
330
- foundImport &&
331
- subCursor.name === "VariableName" &&
332
- state.doc.sliceString(subCursor.from, subCursor.to) === variableName
333
- ) {
334
- addDeclaration(declarations, currentScope, subCursor.from);
331
+ pastImport = true;
332
+ continue;
333
+ }
334
+ if (!pastImport) {
335
+ continue;
336
+ }
337
+ if (subCursor.name === "as") {
338
+ // Next VariableName is the alias and replaces `pending`.
339
+ pending = null;
340
+ continue;
341
+ }
342
+ if (subCursor.name === "VariableName") {
343
+ // Flush any previous pending name (no `as` followed it).
344
+ commit();
345
+ pending = {
346
+ from: subCursor.from,
347
+ matches:
348
+ state.doc.sliceString(subCursor.from, subCursor.to) ===
349
+ variableName,
350
+ };
351
+ } else if (subCursor.name === ",") {
352
+ commit();
335
353
  }
336
354
  } while (subCursor.nextSibling());
355
+ commit();
337
356
  break;
338
357
  }
339
358
  case "TryStatement":
@@ -410,23 +429,21 @@ function findScopedDefinitionPosition(
410
429
  * @param view The editor view which contains the variable name.
411
430
  * @param variableName The name of the variable to select, if found in the editor.
412
431
  * @param usagePosition The position of the variable usage, if available.
413
- * @param fallbackToFirstMatch Whether to fall back to the first matching
414
- * variable name when no scoped definition is found. Defaults to true.
415
432
  */
416
433
  export function goToVariableDefinition(
417
434
  view: EditorView,
418
435
  variableName: string,
419
436
  usagePosition?: number,
420
- fallbackToFirstMatch = true,
421
437
  ): boolean {
422
438
  const { state } = view;
423
- let from: number | null = null;
424
- if (usagePosition !== undefined) {
425
- from = findScopedDefinitionPosition(state, variableName, usagePosition);
426
- }
427
- if (from === null && fallbackToFirstMatch) {
428
- from = findFirstMatchingVariable(state, variableName);
429
- }
439
+ // When the caller knows the usage position, trust the scoped lookup. Falling
440
+ // back to first-match would defeat the local-vs-cross-cell decision in
441
+ // goToDefinition: if the symbol only appears as a module path in an import,
442
+ // scoped resolution returns null and we want the caller to try other cells.
443
+ const from =
444
+ usagePosition !== undefined
445
+ ? findScopedDefinitionPosition(state, variableName, usagePosition)
446
+ : findFirstMatchingVariable(state, variableName);
430
447
 
431
448
  if (from === null) {
432
449
  return false;
@@ -82,7 +82,6 @@ export function goToDefinition(
82
82
  view,
83
83
  variableName,
84
84
  usagePosition,
85
- false,
86
85
  );
87
86
  if (foundLocally) {
88
87
  return true;
@@ -640,6 +640,60 @@ def run(polars):
640
640
  `);
641
641
  });
642
642
 
643
+ test("set comprehension target shadows outer global", () => {
644
+ // Regression: SCOPE_CREATING_NODES used "SetComprehension" instead of the
645
+ // grammar's "SetComprehensionExpression", so set comprehensions never
646
+ // created a scope and their for-target was treated as reactive.
647
+ expect(runHighlight(["x"], "result = {x for x in range(5)}"))
648
+ .toMatchInlineSnapshot(`
649
+ "
650
+ result = {x for x in range(5)}
651
+ "
652
+ `);
653
+ });
654
+
655
+ test("from-import module path stays reactive", () => {
656
+ // Regression: ImportStatement collected every VariableName child, so the
657
+ // module name in `from m import y` was wrongly treated as a local binding.
658
+ expect(
659
+ runHighlight(
660
+ ["math"],
661
+ `
662
+ def f():
663
+ from math import sin as my_sin
664
+ return math + my_sin(1)`,
665
+ ),
666
+ ).toMatchInlineSnapshot(`
667
+ "
668
+ def f():
669
+ from math import sin as my_sin
670
+ return math + my_sin(1)
671
+ ^^^^
672
+ "
673
+ `);
674
+ });
675
+
676
+ test("from-import: aliased imported name is not a binding", () => {
677
+ // Regression: `sin` in `from math import sin as my_sin` was incorrectly
678
+ // registered as a local binding, hiding genuine reactive uses of `sin`.
679
+ expect(
680
+ runHighlight(
681
+ ["sin"],
682
+ `
683
+ def f():
684
+ from math import sin as my_sin
685
+ return sin + my_sin(1)`,
686
+ ),
687
+ ).toMatchInlineSnapshot(`
688
+ "
689
+ def f():
690
+ from math import sin as my_sin
691
+ return sin + my_sin(1)
692
+ ^^^
693
+ "
694
+ `);
695
+ });
696
+
643
697
  test("lambda inside function with outer global", () => {
644
698
  expect(
645
699
  runHighlight(
@@ -16,7 +16,7 @@ const SCOPE_CREATING_NODES = new Set([
16
16
  "FunctionDefinition",
17
17
  "LambdaExpression",
18
18
  "ArrayComprehensionExpression",
19
- "SetComprehension",
19
+ "SetComprehensionExpression",
20
20
  "DictionaryComprehensionExpression",
21
21
  "ComprehensionExpression",
22
22
  "ClassDefinition",
@@ -152,7 +152,7 @@ export function findReactiveVariables(options: {
152
152
  }
153
153
  case "ArrayComprehensionExpression":
154
154
  case "DictionaryComprehensionExpression":
155
- case "SetComprehension":
155
+ case "SetComprehensionExpression":
156
156
  case "ComprehensionExpression": {
157
157
  // Domprehension variables - look for VariableName or TupleExpression after 'for'
158
158
  const subCursor = node.cursor();
@@ -276,49 +276,52 @@ export function findReactiveVariables(options: {
276
276
  break;
277
277
  }
278
278
  case "ImportStatement": {
279
- // Handle import x
279
+ // The grammar emits a single ImportStatement for both `import x [as y]`
280
+ // and `from m import x [as y], ...`. Direct children mix keywords,
281
+ // module-path names (before `import`), imported names, and aliases.
282
+ // Only post-`import` names that aren't shadowed by a following `as`
283
+ // (and the alias itself when `as` is present) bind in the current
284
+ // scope.
280
285
  const subCursor = node.cursor();
281
286
  subCursor.firstChild();
282
- do {
283
- if (subCursor.name === "VariableName") {
284
- const varName = options.state.doc.sliceString(
285
- subCursor.from,
286
- subCursor.to,
287
- );
288
-
289
- const currentScope =
290
- currentScopeStack[currentScopeStack.length - 1] ?? -1;
291
- if (!allDeclarations.has(currentScope)) {
292
- allDeclarations.set(currentScope, new Set());
293
- }
294
- allDeclarations.get(currentScope)?.add(varName);
287
+ const currentScope =
288
+ currentScopeStack[currentScopeStack.length - 1] ?? -1;
289
+ if (!allDeclarations.has(currentScope)) {
290
+ allDeclarations.set(currentScope, new Set());
291
+ }
292
+ const scope = allDeclarations.get(currentScope);
293
+ let pastImport = false;
294
+ let pending: string | null = null;
295
+ const commit = () => {
296
+ if (pending !== null) {
297
+ scope?.add(pending);
295
298
  }
296
- } while (subCursor.nextSibling());
297
-
298
- break;
299
- }
300
- case "ImportFromStatement": {
301
- // Handle from x import y as z
302
- const subCursor = node.cursor();
303
- subCursor.firstChild();
304
- let foundImport = false;
299
+ pending = null;
300
+ };
305
301
  do {
306
302
  if (subCursor.name === "import") {
307
- foundImport = true;
308
- } else if (foundImport && subCursor.name === "VariableName") {
309
- const varName = options.state.doc.sliceString(
303
+ pastImport = true;
304
+ continue;
305
+ }
306
+ if (!pastImport) {
307
+ continue;
308
+ }
309
+ if (subCursor.name === "as") {
310
+ // Drop the imported name; the next VariableName is the alias.
311
+ pending = null;
312
+ continue;
313
+ }
314
+ if (subCursor.name === "VariableName") {
315
+ commit();
316
+ pending = options.state.doc.sliceString(
310
317
  subCursor.from,
311
318
  subCursor.to,
312
319
  );
313
- // Add to the current innermost scope
314
- const currentScope =
315
- currentScopeStack[currentScopeStack.length - 1] ?? -1;
316
- if (!allDeclarations.has(currentScope)) {
317
- allDeclarations.set(currentScope, new Set());
318
- }
319
- allDeclarations.get(currentScope)?.add(varName);
320
+ } else if (subCursor.name === ",") {
321
+ commit();
320
322
  }
321
323
  } while (subCursor.nextSibling());
324
+ commit();
322
325
 
323
326
  break;
324
327
  }
@@ -424,6 +427,12 @@ export function findReactiveVariables(options: {
424
427
  const nodeName = cursor.name;
425
428
  const nodeStart = cursor.from;
426
429
 
430
+ // Names inside an import statement are module paths, imported names, or
431
+ // aliases — none of them are reactive *uses* of an outer-cell global.
432
+ if (nodeName === "ImportStatement") {
433
+ return;
434
+ }
435
+
427
436
  const isNewScope = SCOPE_CREATING_NODES.has(nodeName);
428
437
 
429
438
  let currentScopeStack = scopeStack;