@marimo-team/islands 0.19.3-dev23 → 0.19.3-dev27

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.19.3-dev23",
3
+ "version": "0.19.3-dev27",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -146,8 +146,8 @@
146
146
  "plotly.js": "^2.35.3",
147
147
  "pyodide": "0.27.7",
148
148
  "react-arborist": "^3.4.3",
149
- "react-aria": "^3.44.0",
150
- "react-aria-components": "^1.13.0",
149
+ "react-aria": "^3.45.0",
150
+ "react-aria-components": "^1.14.0",
151
151
  "react-codemirror-merge": "4.25.4",
152
152
  "react-dnd": "^16.0.1",
153
153
  "react-dnd-html5-backend": "^16.0.1",
@@ -1,4 +1,4 @@
1
- /* Copyright 2024 Marimo. All rights reserved. */
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import { describe, expect, test } from "vitest";
4
4
  import { getDirtyValues } from "../user-config-form";
@@ -69,7 +69,7 @@ export function getDirtyValues<T extends FieldValues>(
69
69
  dirtyFields: Partial<Record<keyof T, unknown>>,
70
70
  ): Partial<T> {
71
71
  const result: Partial<T> = {};
72
- for (const key of Object.keys(dirtyFields) as Array<keyof T>) {
72
+ for (const key of Object.keys(dirtyFields) as (keyof T)[]) {
73
73
  const dirty = dirtyFields[key];
74
74
  if (dirty === true) {
75
75
  result[key] = values[key];
@@ -194,10 +194,8 @@ export function isPanelHidden(
194
194
  if (panel.hidden) {
195
195
  return true;
196
196
  }
197
- if (panel.requiredCapability) {
198
- if (!capabilities[panel.requiredCapability]) {
199
- return true;
200
- }
197
+ if (panel.requiredCapability && !capabilities[panel.requiredCapability]) {
198
+ return true;
201
199
  }
202
200
  return false;
203
201
  }
@@ -756,6 +756,129 @@ describe("NotebookLanguageServerClient", () => {
756
756
  // Verify that the import cell was not changed
757
757
  expect(mockView1.state.doc.toString()).toBe("import marimo as mo");
758
758
  });
759
+
760
+ it("should only rename private variables in the current cell (issue #7810)", async () => {
761
+ const props = {
762
+ workspaceFolders: null,
763
+ capabilities: {
764
+ textDocument: {
765
+ rename: {
766
+ prepareSupport: true,
767
+ },
768
+ },
769
+ },
770
+ languageId: "python",
771
+ transport: {
772
+ sendData: vi.fn(),
773
+ subscribe: vi.fn(),
774
+ connect: vi.fn(),
775
+ transportRequestManager: {
776
+ send: vi.fn(),
777
+ },
778
+ } as any,
779
+ };
780
+
781
+ // Setup editor views - both cells have a private variable _x
782
+ const cell1Code = "_x = 1\nprint(_x)";
783
+ const cell2Code = "_x = 2\nprint(_x)";
784
+
785
+ const mockView1 = new EditorView({
786
+ doc: cell1Code,
787
+ extensions: [
788
+ languageAdapterState.init(() => new PythonLanguageAdapter()),
789
+ languageMetadataField.init(() => ({})),
790
+ languageServerWithClient({
791
+ client: mockClient as unknown as LanguageServerClient,
792
+ documentUri: CellDocumentUri.of(Cells.cell1),
793
+ ...props,
794
+ }),
795
+ ],
796
+ });
797
+
798
+ const mockView2 = new EditorView({
799
+ doc: cell2Code,
800
+ extensions: [
801
+ languageAdapterState.init(() => new PythonLanguageAdapter()),
802
+ languageMetadataField.init(() => ({})),
803
+ languageServerWithClient({
804
+ client: mockClient as unknown as LanguageServerClient,
805
+ documentUri: CellDocumentUri.of(Cells.cell2),
806
+ ...props,
807
+ }),
808
+ ],
809
+ });
810
+
811
+ (notebookClient as any).getNotebookEditors = () => ({
812
+ [Cells.cell1]: mockView1,
813
+ [Cells.cell2]: mockView2,
814
+ });
815
+
816
+ // Update the mock to return the correct codes
817
+ vi.spyOn(store, "get").mockImplementation((atom) => {
818
+ if (atom === topologicalCodesAtom) {
819
+ return {
820
+ cellIds: [Cells.cell1, Cells.cell2],
821
+ codes: {
822
+ [Cells.cell1]: cell1Code,
823
+ [Cells.cell2]: cell2Code,
824
+ },
825
+ };
826
+ }
827
+ return undefined;
828
+ });
829
+
830
+ // Setup rename params - renaming _x in cell1
831
+ const renameParams: LSP.RenameParams = {
832
+ textDocument: { uri: CellDocumentUri.of(Cells.cell1) },
833
+ position: { line: 0, character: 0 }, // position of '_x'
834
+ newName: "_y",
835
+ };
836
+
837
+ // Open a document first to set up the lens
838
+ await notebookClient.textDocumentDidOpen({
839
+ textDocument: {
840
+ uri: CellDocumentUri.of(Cells.cell1),
841
+ languageId: "python",
842
+ version: 1,
843
+ text: cell1Code,
844
+ },
845
+ });
846
+
847
+ // Mock the response from the language server
848
+ // The LSP server would rename _x in BOTH cells (since it sees the merged doc)
849
+ const mockRenameResponse: LSP.WorkspaceEdit = {
850
+ documentChanges: [
851
+ {
852
+ textDocument: {
853
+ uri: "file:///__marimo_notebook__.py",
854
+ version: 1,
855
+ },
856
+ edits: [
857
+ {
858
+ range: {
859
+ start: { line: 0, character: 0 },
860
+ end: { line: 3, character: 10 },
861
+ },
862
+ // LSP renames _x to _y in both cells
863
+ newText: "_y = 1\nprint(_y)\n_y = 2\nprint(_y)",
864
+ },
865
+ ],
866
+ },
867
+ ],
868
+ };
869
+
870
+ mockClient.textDocumentRename = vi
871
+ .fn()
872
+ .mockResolvedValue(mockRenameResponse);
873
+
874
+ // Call rename
875
+ await notebookClient.textDocumentRename(renameParams);
876
+
877
+ // The fix: only cell1 should be renamed, cell2 should remain unchanged
878
+ // because private variables are cell-local in marimo
879
+ expect(mockView1.state.doc.toString()).toBe("_y = 1\nprint(_y)");
880
+ expect(mockView2.state.doc.toString()).toBe("_x = 2\nprint(_x)");
881
+ });
759
882
  });
760
883
 
761
884
  describe("diagnostics handling", () => {
@@ -9,6 +9,7 @@ import { invariant } from "@/utils/invariant";
9
9
  import { Logger } from "@/utils/Logger";
10
10
  import { LRUCache } from "@/utils/lru";
11
11
  import { Objects } from "@/utils/objects";
12
+ import { getPositionAtWordBounds } from "../completion/hints";
12
13
  import { topologicalCodesAtom } from "../copilot/getCodes";
13
14
  import {
14
15
  getEditorCodeAsPython,
@@ -22,6 +23,14 @@ import {
22
23
  } from "./types";
23
24
  import { getLSPDocument } from "./utils";
24
25
 
26
+ /**
27
+ * Check if a variable name is private (starts with underscore but not dunder).
28
+ * Private variables in marimo are cell-local and should not be renamed across cells.
29
+ */
30
+ function isPrivateVariable(name: string): boolean {
31
+ return name.startsWith("_") && !name.startsWith("__");
32
+ }
33
+
25
34
  class Snapshotter {
26
35
  private documentVersion = 0;
27
36
  private readonly getNotebookCode: () => {
@@ -433,15 +442,46 @@ export class NotebookLanguageServerClient implements ILanguageServerClient {
433
442
 
434
443
  // Update the code in the plugins manually
435
444
  const editors = this.getNotebookEditors();
436
- for (const [cellId, ev] of Objects.entries(editors)) {
437
- const newCode = editsToNewCode.get(cellId);
445
+
446
+ // Check if this is a private variable rename (should only affect current cell)
447
+ // Private variables in marimo are cell-local and should not be renamed across cells
448
+ const originEditor = editors[cellId];
449
+ let isPrivateRename = false;
450
+ if (originEditor) {
451
+ // Convert LSP position (line, character) to CodeMirror position
452
+ const line = originEditor.state.doc.line(params.position.line + 1);
453
+ const cmPosition = line.from + params.position.character;
454
+ const { startToken, endToken } = getPositionAtWordBounds(
455
+ originEditor.state.doc,
456
+ cmPosition,
457
+ );
458
+ const originalName = originEditor.state.doc.sliceString(
459
+ startToken,
460
+ endToken,
461
+ );
462
+ isPrivateRename = isPrivateVariable(originalName);
463
+ if (isPrivateRename) {
464
+ Logger.debug(
465
+ "[lsp] Private variable rename detected, limiting to current cell",
466
+ originalName,
467
+ );
468
+ }
469
+ }
470
+
471
+ for (const [currentCellId, ev] of Objects.entries(editors)) {
472
+ // For private variable renames, only update the originating cell
473
+ if (isPrivateRename && currentCellId !== cellId) {
474
+ continue;
475
+ }
476
+
477
+ const newCode = editsToNewCode.get(currentCellId);
438
478
  if (newCode == null) {
439
- Logger.warn("No new code for cell", cellId);
479
+ Logger.warn("No new code for cell", currentCellId);
440
480
  continue;
441
481
  }
442
482
 
443
483
  if (!ev) {
444
- Logger.warn("No view for plugin", cellId);
484
+ Logger.warn("No view for plugin", currentCellId);
445
485
  continue;
446
486
  }
447
487
 
@@ -142,8 +142,8 @@ describe("buildCellData", () => {
142
142
  cell2: "y = 2",
143
143
  },
144
144
  last_execution_time: {
145
- cell1: 1234567890,
146
- cell2: 1234567891,
145
+ cell1: 1_234_567_890,
146
+ cell2: 1_234_567_891,
147
147
  },
148
148
  app_config: {
149
149
  width: "normal",
@@ -1,3 +1,4 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
1
2
  import { atom } from "jotai";
2
3
  import { waitFor } from "../state/jotai";
3
4
 
@@ -1,4 +1,4 @@
1
- /* Copyright 2024 Marimo. All rights reserved. */
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import { beforeEach, describe, expect, it, vi } from "vitest";
4
4
  import type { RuntimeManager } from "../../runtime/runtime";
@@ -1,4 +1,4 @@
1
- /* Copyright 2024 Marimo. All rights reserved. */
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import { NoKernelConnectedError } from "@/utils/errors";
4
4
  import { Logger } from "@/utils/Logger";
@@ -152,7 +152,7 @@ export function createLazyRequests(
152
152
  `Dropping request: ${key}, since not connected to a kernel.`,
153
153
  );
154
154
  // Silently drop the request
155
- return Promise.resolve();
155
+ return;
156
156
 
157
157
  case "throwError":
158
158
  throw new NoKernelConnectedError();
@@ -184,8 +184,10 @@
184
184
  --shadow-2xl-solid: 10px 12px 0px 0px var(--base-shadow-darker), 0 0px 8px 0px hsl(0deg 0% 90% / 50%);
185
185
 
186
186
  /* Solid shadows with lighter shade color */
187
+
187
188
  /* biome-ignore format: definition needs to be oneline or breaks variants */
188
189
  --shadow-sm-solid-shade: 2px 2px 0px 0px var(--base-shadow), 0px 0px 2px 0px hsl(0deg 0% 50% / 20%);
190
+
189
191
  /* biome-ignore format: definition needs to be oneline or breaks variants */
190
192
  --shadow-md-solid-shade: 4px 4px 0px 0px var(--base-shadow), 0 0px 2px 0px hsl(0deg 0% 60% / 50%);
191
193
  }