@marimo-team/islands 0.25.3-dev196 → 0.25.3-dev198

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 (135) hide show
  1. package/dist/{ConnectedDataExplorerComponent-BRuwLOIx.js → ConnectedDataExplorerComponent-BsWiIPFk.js} +4 -4
  2. package/dist/{ErrorBoundary-C3Xv3yzV.js → ErrorBoundary-D1Z3KAf_.js} +2 -2
  3. package/dist/{abnfDiagram-VCTEODGH-LlJTOOU_.js → abnfDiagram-VCTEODGH-I2x4Yu25.js} +2 -2
  4. package/dist/{architectureDiagram-5GKGNRK7-9PQtGLLZ.js → architectureDiagram-5GKGNRK7-1bSQMKzK.js} +1 -1
  5. package/dist/{arrays-DA6KkLfq.js → arrays-DoQ-AILx.js} +7 -21
  6. package/dist/{capabilities-CvWU-ey_.js → capabilities-C2QUNRXi.js} +1 -1
  7. package/dist/{chat-ui-Bl2euMBD.js → chat-ui-BAyHM_GD.js} +524 -524
  8. package/dist/{chunk-IMKFNOWR-DcXv-BD5.js → chunk-IMKFNOWR-fsCGAJ9G.js} +1 -1
  9. package/dist/{chunk-SHT3W25Y-VwnELN23.js → chunk-SHT3W25Y-D2STW4hj.js} +1 -1
  10. package/dist/{chunk-SVP7TREG-DgQpI5Zi.js → chunk-SVP7TREG-CiY7cQG-.js} +1 -1
  11. package/dist/{chunk-TICWLB2K-CTg6VTJT.js → chunk-TICWLB2K-C_t5RBp9.js} +1 -1
  12. package/dist/{classDiagram-ZZMXUADV-UzmcFrnW.js → classDiagram-ZZMXUADV-B2JvZAP4.js} +2 -2
  13. package/dist/{classDiagram-v2-VYDZK3BY-DhjiI6IQ.js → classDiagram-v2-VYDZK3BY-L8VsZG5A.js} +2 -2
  14. package/dist/{code-visibility-BCcqmP58.js → code-visibility-CInEQd1e.js} +19 -19
  15. package/dist/{common-DrNB2bY2.js → common-W5HfVJPN.js} +4362 -4362
  16. package/dist/{cynefinDiagram-5FMLGOSQ-O4ShIbJX.js → cynefinDiagram-5FMLGOSQ-DdllC264.js} +1 -1
  17. package/dist/{dagre-GXQ25YYZ-dtHhhcPu.js → dagre-GXQ25YYZ-BaWHw4_f.js} +1 -1
  18. package/dist/{data-grid-overlay-editor-TGXV_Hf4.js → data-grid-overlay-editor-BQCaV0fI.js} +1 -1
  19. package/dist/{diagram-S7CK7UJ4-ppPYVZ6W.js → diagram-S7CK7UJ4-pFt-tUDe.js} +1 -1
  20. package/dist/{diagram-UQ7AKVKN-CMZDf--k.js → diagram-UQ7AKVKN-DqGJiBp4.js} +1 -1
  21. package/dist/{diagram-VX7I27RA-CFvJdx-C.js → diagram-VX7I27RA-Dr-yzMjf.js} +1 -1
  22. package/dist/{diagram-Z3DM3KII-Dt6_uFbv.js → diagram-Z3DM3KII-8xzJzv-w.js} +1 -1
  23. package/dist/{dropdown-menu-CeaOsjWZ.js → dropdown-menu-NlnsmNA6.js} +2 -2
  24. package/dist/{ebnfDiagram-PWID7BFC-DpRFrKK-.js → ebnfDiagram-PWID7BFC-Br04Wy-E.js} +2 -2
  25. package/dist/{erDiagram-RLTQ6QDP-CqAU4b0u.js → erDiagram-RLTQ6QDP-D5-XVgvv.js} +1 -1
  26. package/dist/{error-entries-CYGwr5-O.js → error-entries-BacCWMWj.js} +4678 -5028
  27. package/dist/{feedback-button-PTiPn2GP.js → feedback-button-DLXUcQ7l.js} +113 -113
  28. package/dist/{flowDiagram-HODETNUW-i8-Gw8HO.js → flowDiagram-HODETNUW-CVl2kREF.js} +2 -2
  29. package/dist/{formats-BtoHHy42.js → formats-BCYthwyc.js} +1 -1
  30. package/dist/{glide-data-editor-BSyexjkB.js → glide-data-editor-D5YaHPKx.js} +5 -5
  31. package/dist/{globals-B7gkJAxm.js → globals-DjX1uxzy.js} +2 -2
  32. package/dist/{html-to-image-Cy5UDpQJ.js → html-to-image-DAmgSo5P.js} +624 -573
  33. package/dist/{infoDiagram-27XIBGKW-DpfOKdLL.js → infoDiagram-27XIBGKW-BJ4vNcZE.js} +1 -1
  34. package/dist/{ishikawaDiagram-5VMMS53U-Dg7mrMG7.js → ishikawaDiagram-5VMMS53U-B_Jy1Dsm.js} +1 -1
  35. package/dist/{kanban-definition-UXKFOSKX-QB6e3Jrj.js → kanban-definition-UXKFOSKX-C6MkS9vw.js} +1 -1
  36. package/dist/main.js +1117 -1117
  37. package/dist/{mermaid-CeWG4Egr.js → mermaid-Bh918qrq.js} +34 -34
  38. package/dist/{mindmap-definition-YA3MSWOX-BTz3uTGe.js → mindmap-definition-YA3MSWOX-BP3ZTuir.js} +1 -1
  39. package/dist/{number-overlay-editor-Bb91yQ46.js → number-overlay-editor-3C_K6w_0.js} +1 -1
  40. package/dist/{once-DWGO2mQ2.js → once-NnpX_HnX.js} +1 -1
  41. package/dist/{pegDiagram-XKGWAZYB-3ZIEa3mB.js → pegDiagram-XKGWAZYB-nNGOQtnt.js} +2 -2
  42. package/dist/{pieDiagram-E7YTZNPT-BJAlk046.js → pieDiagram-E7YTZNPT-CpRz5BZz.js} +1 -1
  43. package/dist/{process-output-q9MTAIMX.js → process-output-DweUmAmV.js} +3 -3
  44. package/dist/{railroadDiagram-O6MQD6OU-Dn3bsSRa.js → railroadDiagram-O6MQD6OU-BqOEN4I0.js} +2 -2
  45. package/dist/{requirementDiagram-BXWQKSXE-wcOmQQCL.js → requirementDiagram-BXWQKSXE-CEfg6vu9.js} +1 -1
  46. package/dist/{reveal-component-vTdjiyDv.js → reveal-component-JjJRTT9d.js} +17 -17
  47. package/dist/{sequenceDiagram-WJ2MYXX4-DSh_qxoF.js → sequenceDiagram-WJ2MYXX4-IOmpQcOi.js} +1 -1
  48. package/dist/{spec-C7Fos-If.js → spec-Dkp64vVQ.js} +1 -1
  49. package/dist/{stateDiagram-D77RDMKH-DH_H8NMH.js → stateDiagram-D77RDMKH-tgU-EY9_.js} +2 -2
  50. package/dist/{stateDiagram-v2-MP3YSRHH-D_JM7MEK.js → stateDiagram-v2-MP3YSRHH-BofhG6q1.js} +2 -2
  51. package/dist/{swimlanes-42K2YHIH-CfoTJO-9.js → swimlanes-42K2YHIH-DW4vWDBd.js} +1 -1
  52. package/dist/{swimlanesDiagram-VR7AAH4N-Z_QhvK0G.js → swimlanesDiagram-VR7AAH4N-BJcrqUJr.js} +2 -2
  53. package/dist/{swiper-component-D2geL1Ku.js → swiper-component-BVfnhbnt.js} +1 -1
  54. package/dist/{timeline-definition-24CTP7MA-BFtdScaL.js → timeline-definition-24CTP7MA-D_QU3NGs.js} +1 -1
  55. package/dist/{toDate-CQAFSwos.js → toDate-BNDEXbfV.js} +30 -30
  56. package/dist/{useAsyncData-BeyZyjso.js → useAsyncData-ASDni9vH.js} +1 -1
  57. package/dist/{useDeepCompareMemoize-DOQgae0W.js → useDeepCompareMemoize-B1Ogyf53.js} +1 -1
  58. package/dist/{useIframeCapabilities-DNvaj9wM.js → useIframeCapabilities-6a_yDNXp.js} +1 -1
  59. package/dist/{useLifecycle-n2lnCFS8.js → useLifecycle-Bfpa2m7k.js} +1 -1
  60. package/dist/{useTheme-CY777LXN.js → useTheme-5TwyOh3B.js} +66 -66
  61. package/dist/{vega-component-0nQzA-I8.js → vega-component-BHS9UOtC.js} +19 -19
  62. package/dist/{vennDiagram-4TSXK5OY-BXY8IGCA.js → vennDiagram-4TSXK5OY-YFYsNIAh.js} +1 -1
  63. package/dist/{wardleyDiagram-VM6X3IG4-D0tFe6eC.js → wardleyDiagram-VM6X3IG4-i_n8mAWC.js} +1 -1
  64. package/dist/{xychartDiagram-S5SC5T6Z-Dx8wFM-y.js → xychartDiagram-S5SC5T6Z-BYGN95wW.js} +1 -1
  65. package/package.json +1 -1
  66. package/src/__mocks__/document.tsx +13 -4
  67. package/src/__mocks__/notebook.ts +1 -2
  68. package/src/components/datasources/datasources.tsx +1 -1
  69. package/src/components/dependency-graph/custom-node.tsx +1 -1
  70. package/src/components/dependency-graph/minimap-content.tsx +12 -14
  71. package/src/components/editor/actions/useSetCodeVisibility.ts +1 -1
  72. package/src/components/editor/actions/useSetOutputsExpanded.ts +1 -1
  73. package/src/components/editor/alerts/stdin-blocking-alert.tsx +1 -1
  74. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  75. package/src/components/editor/cell/useSplitCell.tsx +8 -5
  76. package/src/components/editor/chrome/panels/dependency-graph-panel.tsx +1 -1
  77. package/src/components/editor/chrome/panels/session-panel.tsx +1 -1
  78. package/src/components/editor/chrome/panels/variable-panel.tsx +1 -5
  79. package/src/components/editor/chrome/wrapper/minimap-state.ts +1 -1
  80. package/src/components/editor/columns/__tests__/cell-column.test.tsx +1 -1
  81. package/src/components/editor/columns/cell-column.tsx +1 -1
  82. package/src/components/editor/columns/sortable-column.tsx +1 -1
  83. package/src/components/editor/links/__tests__/cell-link.test.tsx +1 -1
  84. package/src/components/editor/links/cell-link-list.tsx +1 -1
  85. package/src/components/editor/links/cell-link.tsx +1 -1
  86. package/src/components/editor/navigation/__tests__/navigation.test.ts +20 -6
  87. package/src/components/editor/navigation/__tests__/selection.test.ts +1 -2
  88. package/src/components/editor/navigation/clipboard.ts +5 -3
  89. package/src/components/editor/renderers/cell-array.tsx +7 -12
  90. package/src/components/editor/renderers/slides-layout/slides-layout.tsx +1 -1
  91. package/src/components/editor/renderers/vertical-layout/__tests__/useFocusFirstEditor.test.ts +4 -4
  92. package/src/components/editor/renderers/vertical-layout/useFocusFirstEditor.ts +1 -1
  93. package/src/components/editor/stdin-blocking-alert.tsx +1 -1
  94. package/src/components/slides/__tests__/minimap-actions.test.tsx +13 -4
  95. package/src/components/slides/__tests__/minimap.test.ts +38 -19
  96. package/src/components/slides/minimap.tsx +11 -10
  97. package/src/components/sort/SortableCellsProvider.tsx +2 -21
  98. package/src/components/tracing/tracing.tsx +1 -1
  99. package/src/core/ai/__tests__/staged-cells.test.ts +1 -1
  100. package/src/core/ai/context/providers/__tests__/cell-output.test.ts +8 -12
  101. package/src/core/ai/context/providers/cell-output.ts +2 -2
  102. package/src/core/ai/tools/__tests__/edit-notebook-tool.test.ts +34 -66
  103. package/src/core/ai/tools/edit-notebook-tool.ts +12 -10
  104. package/src/core/cells/__tests__/actions.test.ts +64 -25
  105. package/src/core/cells/__tests__/arrange.test.ts +156 -4
  106. package/src/core/cells/__tests__/cells.test.ts +12 -290
  107. package/src/core/cells/__tests__/document.test.ts +2 -46
  108. package/src/core/cells/__tests__/harness.ts +28 -4
  109. package/src/core/cells/__tests__/layout.test.ts +42 -54
  110. package/src/core/cells/__tests__/session.test.ts +0 -2
  111. package/src/core/cells/__tests__/utils.test.ts +9 -107
  112. package/src/core/cells/actions.ts +112 -26
  113. package/src/core/cells/add-missing-import.ts +1 -1
  114. package/src/core/cells/arrange.ts +206 -13
  115. package/src/core/cells/cells.ts +28 -256
  116. package/src/core/cells/document.ts +18 -122
  117. package/src/core/cells/layout.ts +79 -10
  118. package/src/core/cells/pending-delete-service.ts +1 -1
  119. package/src/core/cells/utils.ts +4 -4
  120. package/src/core/codemirror/copilot/__tests__/getCodes.test.ts +6 -7
  121. package/src/core/codemirror/copilot/getCodes.ts +2 -2
  122. package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +1 -2
  123. package/src/core/codemirror/lsp/utils.ts +1 -1
  124. package/src/core/documentation/doc-lookup.ts +1 -1
  125. package/src/core/edit-app.tsx +2 -5
  126. package/src/core/errors/error-entries.ts +1 -1
  127. package/src/core/islands/__tests__/bootstrap.test.ts +2 -6
  128. package/src/core/islands/components/web-components.tsx +1 -1
  129. package/src/core/notebook/document/__tests__/codemirror.test.ts +8 -0
  130. package/src/core/session/__tests__/useNotebookSession.hook.test.tsx +1 -1
  131. package/src/stories/cell.stories.tsx +1 -2
  132. package/src/stories/layout/vertical/one-column.stories.tsx +1 -2
  133. package/src/stories/runtime-story.tsx +2 -3
  134. package/src/utils/__tests__/id-tree.test.ts +0 -617
  135. package/src/utils/id-tree.tsx +0 -609
@@ -3,15 +3,25 @@
3
3
  import type * as Protocol from "@marimo-team/client/protocol";
4
4
  import { afterEach, describe, expect, it, vi } from "vitest";
5
5
  import { cellId } from "@/__tests__/branded";
6
- import { cell, CONFIG } from "@/core/notebook/document/__tests__/fixtures";
7
- import type { CellIndex } from "@/utils/id-tree";
6
+ import {
7
+ cell,
8
+ CONFIG,
9
+ document as documentOf,
10
+ withCells,
11
+ } from "@/core/notebook/document/__tests__/fixtures";
8
12
  import { collapseCellAtom, undoDeleteCellAtom } from "../actions";
9
13
  import {
14
+ addColumnAtom,
15
+ addColumnBreakpointAtom,
16
+ compactColumnsAtom,
17
+ deleteColumnAtom,
10
18
  dropCellOverCellAtom,
11
19
  dropCellOverColumnAtom,
20
+ mergeAllColumnsAtom,
12
21
  moveCellAtom,
13
22
  moveCellsRelativeToAtom,
14
23
  moveCellToIndexAtom,
24
+ moveColumnAtom,
15
25
  sendToBottomAtom,
16
26
  sendToTopAtom,
17
27
  } from "../arrange";
@@ -220,8 +230,8 @@ describe("moveCellsRelativeToAtom", () => {
220
230
  targetCellId: c3,
221
231
  position: "after",
222
232
  previousPlacements: [
223
- { columnId: column, index: 0 as CellIndex },
224
- { columnId: column, index: 1 as CellIndex },
233
+ { columnId: column, index: 0 },
234
+ { columnId: column, index: 1 },
225
235
  ],
226
236
  });
227
237
  expect(order()).toEqual([c3, c1, c2]);
@@ -261,3 +271,145 @@ describe("moveCellsRelativeToAtom", () => {
261
271
  expect(order()).toEqual([c2, c3, c1]);
262
272
  });
263
273
  });
274
+
275
+ describe("columns", () => {
276
+ it("adds a column after another with a new cell, and names it", () => {
277
+ const { store, columns, columnIds, notebook, cellIn } = setup();
278
+ const [first] = columnIds();
279
+
280
+ store.set(addColumnAtom, { columnId: first });
281
+
282
+ const [left, right] = store.get(layoutAtom).columns;
283
+ expect(left.id).toBe(first);
284
+ expect(right.id).not.toBe(first);
285
+ expect(right.ids).toHaveLength(1);
286
+ const id = right.ids[0];
287
+ expect(columns()).toEqual([[c1, c2], [id]]);
288
+ expect(cellIn(id)?.config.column).toBe(1);
289
+ expect(notebook().scrollKey).toBe(id);
290
+ });
291
+
292
+ it("starts a column at a cell, but not at the first cell", () => {
293
+ const { store, model, columns, cellIn } = setup({
294
+ c3: cell("c3", "z = 3"),
295
+ });
296
+ const before = store.get(model.documentAtom);
297
+
298
+ store.set(addColumnBreakpointAtom, { cellId: c1 });
299
+ expect(store.get(model.documentAtom)).toBe(before);
300
+
301
+ store.set(addColumnBreakpointAtom, { cellId: c2 });
302
+ expect(columns()).toEqual([[c1], [c2, c3]]);
303
+ expect(cellIn(c2)?.config.column).toBe(1);
304
+ });
305
+
306
+ it("starts a column at a column's first cell, keeping the id with the cells", () => {
307
+ const { store, columns, columnIds } = twoColumns();
308
+ const [first, second] = columnIds();
309
+
310
+ store.set(addColumnBreakpointAtom, { cellId: c3 });
311
+
312
+ expect(columns()).toEqual([[c1, c2], [], [c3, c4]]);
313
+ const [left, , right] = columnIds();
314
+ expect(left).toBe(first);
315
+ expect(right).toBe(second);
316
+ });
317
+
318
+ it("deletes a column into the one before it, or after it for the first", () => {
319
+ const { store, columns, columnIds, cellIn } = twoColumns();
320
+ const [first, second] = columnIds();
321
+
322
+ store.set(deleteColumnAtom, { columnId: first });
323
+ expect(columns()).toEqual([[c1, c2, c3, c4]]);
324
+ expect(cellIn(c3)?.config.column).toBeNull();
325
+ expect(columnIds()).toEqual([second]);
326
+
327
+ store.set(deleteColumnAtom, { columnId: second });
328
+ expect(columns()).toEqual([[c1, c2, c3, c4]]);
329
+ });
330
+
331
+ it("merges every column into the first", () => {
332
+ const { store, columns, columnIds, cellIn } = twoColumns();
333
+ const [first] = columnIds();
334
+
335
+ store.set(mergeAllColumnsAtom);
336
+
337
+ expect(columns()).toEqual([[c1, c2, c3, c4]]);
338
+ expect(cellIn(c3)?.config.column).toBeNull();
339
+ expect(columnIds()).toEqual([first]);
340
+ });
341
+
342
+ it("keeps the column a drag emptied until a drop compacts it", () => {
343
+ const { store, columns, columnIds, cellIn } = twoColumns();
344
+ const [first, second] = columnIds();
345
+ store.set(dropCellOverColumnAtom, { cellId: c3, columnId: first });
346
+ store.set(dropCellOverColumnAtom, { cellId: c4, columnId: first });
347
+ expect(columns()).toEqual([[c4, c3, c1, c2], []]);
348
+ expect(columnIds()).toEqual([first, second]);
349
+ expect(cellIn(c4)?.config.column).toBeNull();
350
+
351
+ store.set(dropCellOverColumnAtom, { cellId: c3, columnId: second });
352
+ expect(columns()).toEqual([[c4, c1, c2], [c3]]);
353
+ expect(cellIn(c3)?.config.column).toBe(1);
354
+
355
+ store.set(dropCellOverColumnAtom, { cellId: c3, columnId: first });
356
+ store.set(compactColumnsAtom);
357
+
358
+ expect(columns()).toEqual([[c3, c4, c1, c2]]);
359
+ expect(columnIds()).toEqual([first]);
360
+ });
361
+
362
+ it("moves a column, keeping its id with it", () => {
363
+ const { store, columns, columnIds, cellIn } = twoColumns();
364
+ const [first, second] = columnIds();
365
+
366
+ store.set(moveColumnAtom, { column: first, overColumn: second });
367
+ expect(columns()).toEqual([
368
+ [c3, c4],
369
+ [c1, c2],
370
+ ]);
371
+ expect(columnIds()).toEqual([second, first]);
372
+ expect(cellIn(c3)?.config.column).toBe(0);
373
+ expect(cellIn(c1)?.config.column).toBe(1);
374
+
375
+ store.set(moveColumnAtom, { column: first, overColumn: "_left_" });
376
+ expect(columns()).toEqual([
377
+ [c1, c2],
378
+ [c3, c4],
379
+ ]);
380
+ expect(columnIds()).toEqual([first, second]);
381
+ });
382
+ });
383
+
384
+ describe("the host's columns", () => {
385
+ it("names a column the host adds, leaving the others as they are", () => {
386
+ const { host, columns, columnIds } = setup();
387
+ const [first] = columnIds();
388
+
389
+ host(
390
+ withCells(documentOf(), {
391
+ c3: cell("c3", "z = 3", { config: { ...CONFIG, column: 1 } }),
392
+ }),
393
+ );
394
+
395
+ expect(columns()).toEqual([[c1, c2], [c3]]);
396
+ const [left, right] = columnIds();
397
+ expect(left).toBe(first);
398
+ expect(right).not.toBe(first);
399
+ });
400
+
401
+ it("drops a column the host removed", () => {
402
+ const { host, columns, columnIds } = twoColumns();
403
+ const [first] = columnIds();
404
+
405
+ host(
406
+ withCells(documentOf(), {
407
+ c3: cell("c3", "z = 3"),
408
+ c4: cell("c4", "w = 4"),
409
+ }),
410
+ );
411
+
412
+ expect(columns()).toEqual([[c1, c2, c3, c4]]);
413
+ expect(columnIds()).toEqual([first]);
414
+ });
415
+ });
@@ -24,11 +24,6 @@ import { adaptiveLanguageConfiguration } from "@/core/codemirror/language/extens
24
24
  import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys";
25
25
  import type { OutputMessage } from "@/core/kernel/messages";
26
26
  import type { CellConfig } from "@/core/network/types";
27
- import {
28
- type CollapsibleTree,
29
- MultiColumn,
30
- type CellColumnId,
31
- } from "@/utils/id-tree";
32
27
  import type { Seconds } from "@/utils/time";
33
28
  import {
34
29
  exportedForTesting,
@@ -61,8 +56,6 @@ vi.mock("../scrollCellIntoView", async (importOriginal) => {
61
56
  };
62
57
  });
63
58
 
64
- const FIRST_COLUMN = 0;
65
-
66
59
  const { initialNotebookState, reducer, createActions } = exportedForTesting;
67
60
 
68
61
  function formatCells(notebook: NotebookState) {
@@ -78,14 +71,7 @@ function formatCells(notebook: NotebookState) {
78
71
  const cells = cellsId.map((cellId) => cellData[cellId]);
79
72
  return cells.map((cell) => printCell(cell)).join("\n\n");
80
73
  };
81
- const printColumn = (column: CollapsibleTree<CellId>, columnIdx: number) => {
82
- return `> col ${columnIdx}\n${printCells(column.inOrderIds)}`;
83
- };
84
- const columns = cellIds.getColumns();
85
- if (columns.length > 1) {
86
- return wrap(columns.map(printColumn).join("\n\n"));
87
- }
88
- return wrap(printCells(cellIds.inOrderIds));
74
+ return wrap(printCells(cellIds));
89
75
  }
90
76
 
91
77
  function createEditor(content: string) {
@@ -152,7 +138,7 @@ describe("cell reducer", () => {
152
138
  hideCode,
153
139
  autoFocus = true,
154
140
  }: {
155
- cellId: CellId | "__end__" | { type: "__end__"; columnId: CellColumnId };
141
+ cellId: CellId | "__end__";
156
142
  before: boolean;
157
143
  code?: string;
158
144
  name?: string;
@@ -161,26 +147,16 @@ describe("cell reducer", () => {
161
147
  hideCode?: boolean;
162
148
  autoFocus?: boolean;
163
149
  }): CellId {
164
- let column: CollapsibleTree<CellId>;
165
- let cellIndex: number;
166
- if (cellId === "__end__") {
167
- column = state.cellIds.atOrThrow(0);
168
- cellIndex = column.length;
169
- } else if (typeof cellId === "string") {
170
- column = state.cellIds.findWithId(cellId);
171
- cellIndex = column.topLevelIds.indexOf(cellId);
172
- } else {
173
- column = state.cellIds.get(cellId.columnId) ?? state.cellIds.atOrThrow(0);
174
- cellIndex = column.length;
175
- }
150
+ const cellIndex =
151
+ cellId === "__end__"
152
+ ? state.cellIds.length
153
+ : state.cellIds.indexOf(cellId);
176
154
  const id = newCellId ?? CellId.create();
155
+ const cellIds = [...state.cellIds];
156
+ cellIds.splice(before ? cellIndex : cellIndex + 1, 0, id);
177
157
  state = {
178
158
  ...state,
179
- cellIds: state.cellIds.insertId(
180
- id,
181
- column.id,
182
- before ? cellIndex : cellIndex + 1,
183
- ),
159
+ cellIds,
184
160
  cellData: {
185
161
  ...state.cellData,
186
162
  [id]: createCell({
@@ -215,9 +191,8 @@ describe("cell reducer", () => {
215
191
  i = 0;
216
192
 
217
193
  state = initialNotebookState();
218
- state.cellIds = MultiColumn.from([]);
219
194
  seedCell({ cellId: "__end__", before: false });
220
- firstCellId = state.cellIds.inOrderIds[0];
195
+ firstCellId = state.cellIds[0];
221
196
  });
222
197
 
223
198
  afterAll(() => {
@@ -1156,37 +1131,6 @@ describe("cell reducer", () => {
1156
1131
  expect(state.cellLogs).toEqual([]);
1157
1132
  });
1158
1133
 
1159
- it("can split and undo split cells", () => {
1160
- seedCell({
1161
- cellId: firstCellId,
1162
- before: false,
1163
- code: "line1\nline2",
1164
- });
1165
- const nextCellId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
1166
-
1167
- const originalCellCount = state.cellIds.atOrThrow(FIRST_COLUMN).length;
1168
- // Move cursor to the second line
1169
- const editor = state.cellHandles[nextCellId].current?.editorView;
1170
- if (!editor) {
1171
- throw new Error("Editor not found");
1172
- }
1173
- editor.dispatch({ selection: { anchor: 5, head: 5 } });
1174
- actions.splitCell({ cellId: nextCellId });
1175
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).length).toBe(
1176
- originalCellCount + 1,
1177
- );
1178
- expect(state.cellData[nextCellId].code).toBe("line1");
1179
- expect(
1180
- state.cellData[state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(2)].code,
1181
- ).toBe("line2");
1182
-
1183
- actions.undoSplitCell({ cellId: nextCellId, snapshot: "line1\nline2" });
1184
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).length).toBe(
1185
- originalCellCount,
1186
- );
1187
- expect(state.cellData[nextCellId].code).toBe("line1\nline2");
1188
- });
1189
-
1190
1134
  it("can handle multiple console outputs", () => {
1191
1135
  const STDOUT1: OutputMessage = {
1192
1136
  channel: "stdout",
@@ -1223,228 +1167,6 @@ describe("cell reducer", () => {
1223
1167
  `);
1224
1168
  });
1225
1169
 
1226
- it("can add a column breakpoint", () => {
1227
- seedCell({ cellId: firstCellId, before: false });
1228
- seedCell({ cellId: cellId("1"), before: false });
1229
- seedCell({ cellId: cellId("2"), before: false });
1230
-
1231
- expect(state.cellIds.getColumns().length).toBe(1);
1232
- expect(formatCells(state)).toMatchInlineSnapshot(`
1233
- "
1234
- [0] ''
1235
-
1236
- [1] ''
1237
-
1238
- [2] ''
1239
-
1240
- [3] ''
1241
- "
1242
- `);
1243
-
1244
- actions.addColumnBreakpoint({ cellId: cellId("2") });
1245
-
1246
- expect(state.cellIds.getColumns().length).toBe(2);
1247
- expect(formatCells(state)).toMatchInlineSnapshot(`
1248
- "
1249
- > col 0
1250
- [0] ''
1251
-
1252
- [1] ''
1253
-
1254
- > col 1
1255
- [2] ''
1256
-
1257
- [3] ''
1258
- "
1259
- `);
1260
-
1261
- // Check that the cells are in the correct columns
1262
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
1263
- cellId("0"),
1264
- cellId("1"),
1265
- ]);
1266
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([
1267
- cellId("2"),
1268
- cellId("3"),
1269
- ]);
1270
- });
1271
-
1272
- it("cannot add a column breakpoint before the first cell", () => {
1273
- expect(state.cellIds.getColumns().length).toBe(1);
1274
- seedCell({ cellId: firstCellId, before: false });
1275
- seedCell({ cellId: cellId("1"), before: false });
1276
- actions.addColumnBreakpoint({ cellId: firstCellId });
1277
- expect(state.cellIds.getColumns().length).toBe(1);
1278
- });
1279
-
1280
- it("can delete a column", () => {
1281
- seedCell({ cellId: firstCellId, before: false });
1282
- seedCell({ cellId: cellId("1"), before: false });
1283
- seedCell({ cellId: cellId("2"), before: false });
1284
- actions.addColumnBreakpoint({ cellId: cellId("2") });
1285
-
1286
- expect(state.cellIds.getColumns().length).toBe(2);
1287
- expect(formatCells(state)).toMatchInlineSnapshot(`
1288
- "
1289
- > col 0
1290
- [0] ''
1291
-
1292
- [1] ''
1293
-
1294
- > col 1
1295
- [2] ''
1296
-
1297
- [3] ''
1298
- "
1299
- `);
1300
-
1301
- const columnId = state.cellIds.atOrThrow(0).id;
1302
- actions.deleteColumn({ columnId: columnId });
1303
-
1304
- expect(state.cellIds.getColumns().length).toBe(1);
1305
- expect(formatCells(state)).toMatchInlineSnapshot(`
1306
- "
1307
- [2] ''
1308
-
1309
- [3] ''
1310
-
1311
- [0] ''
1312
-
1313
- [1] ''
1314
- "
1315
- `);
1316
-
1317
- // Check that all cells are now in the remaining column
1318
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
1319
- "2",
1320
- "3",
1321
- "0",
1322
- "1",
1323
- ]);
1324
- });
1325
-
1326
- it("deleting the last column does nothing", () => {
1327
- seedCell({ cellId: firstCellId, before: false });
1328
- seedCell({ cellId: cellId("1"), before: false });
1329
-
1330
- const initialState = { ...state };
1331
-
1332
- actions.deleteColumn({ columnId: initialState.cellIds.atOrThrow(0).id });
1333
-
1334
- // State should not change
1335
- expect(state).toEqual(initialState);
1336
- });
1337
-
1338
- it("can drop a column over another column", () => {
1339
- seedCell({ cellId: firstCellId, before: false });
1340
- seedCell({ cellId: cellId("1"), before: false });
1341
- seedCell({ cellId: cellId("2"), before: false });
1342
- actions.addColumnBreakpoint({ cellId: cellId("2") });
1343
-
1344
- expect(state.cellIds.getColumns().length).toBe(2);
1345
- expect(formatCells(state)).toMatchInlineSnapshot(`
1346
- "
1347
- > col 0
1348
- [0] ''
1349
-
1350
- [1] ''
1351
-
1352
- > col 1
1353
- [2] ''
1354
-
1355
- [3] ''
1356
- "
1357
- `);
1358
-
1359
- const columnId0 = state.cellIds.atOrThrow(0).id;
1360
- const columnId1 = state.cellIds.atOrThrow(1).id;
1361
- expect(columnId0).not.toBe(columnId1);
1362
- actions.moveColumn({ column: columnId1, overColumn: columnId0 });
1363
-
1364
- expect(state.cellIds.getColumns().length).toBe(2);
1365
- expect(formatCells(state)).toMatchInlineSnapshot(`
1366
- "
1367
- > col 0
1368
- [2] ''
1369
-
1370
- [3] ''
1371
-
1372
- > col 1
1373
- [0] ''
1374
-
1375
- [1] ''
1376
- "
1377
- `);
1378
-
1379
- // Check that the columns have swapped positions
1380
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
1381
- cellId("2"),
1382
- cellId("3"),
1383
- ]);
1384
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([
1385
- cellId("0"),
1386
- cellId("1"),
1387
- ]);
1388
- });
1389
-
1390
- it("can compact columns", () => {
1391
- // Create initial state with 3 columns, including an empty one
1392
- seedCell({ cellId: firstCellId, before: false });
1393
- seedCell({ cellId: cellId("1"), before: false });
1394
- actions.addColumnBreakpoint({ cellId: cellId("1") });
1395
- actions.addColumnBreakpoint({ cellId: cellId("2") });
1396
- // A column emptied by a drag stays until the drop compacts it.
1397
- state = {
1398
- ...state,
1399
- cellIds: state.cellIds.addColumn(state.cellIds.getColumnIds()[1]),
1400
- };
1401
-
1402
- expect(state.cellIds.getColumns().length).toBe(4);
1403
- expect(formatCells(state)).toMatchInlineSnapshot(`
1404
- "
1405
- > col 0
1406
- [0] ''
1407
-
1408
- > col 1
1409
- [1] ''
1410
-
1411
- > col 2
1412
-
1413
-
1414
- > col 3
1415
- [2] ''
1416
- "
1417
- `);
1418
-
1419
- // Check initial column structure
1420
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([cellId("0")]);
1421
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([cellId("1")]);
1422
- expect(state.cellIds.getColumns()[2].topLevelIds).toEqual([]);
1423
- expect(state.cellIds.getColumns()[3].topLevelIds).toEqual([cellId("2")]);
1424
-
1425
- // Compact columns
1426
- actions.compactColumns();
1427
-
1428
- expect(state.cellIds.getColumns().length).toBe(3);
1429
- expect(formatCells(state)).toMatchInlineSnapshot(`
1430
- "
1431
- > col 0
1432
- [0] ''
1433
-
1434
- > col 1
1435
- [1] ''
1436
-
1437
- > col 2
1438
- [2] ''
1439
- "
1440
- `);
1441
-
1442
- // Check compacted column structure
1443
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([cellId("0")]);
1444
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([cellId("1")]);
1445
- expect(state.cellIds.getColumns()[2].topLevelIds).toEqual([cellId("2")]);
1446
- });
1447
-
1448
1170
  it("can clear output of a single cell", () => {
1449
1171
  // Set up initial state with output
1450
1172
  actions.handleCellMessage({
@@ -1534,7 +1256,7 @@ describe("cell reducer", () => {
1534
1256
  it("can clear output of all cells", () => {
1535
1257
  // Create multiple cells with output
1536
1258
  seedCell({ cellId: firstCellId, before: false });
1537
- const secondCellId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
1259
+ const secondCellId = state.cellIds[1];
1538
1260
 
1539
1261
  // Add output to both cells
1540
1262
  const outputMessage = {
@@ -1616,7 +1338,7 @@ describe("cell reducer", () => {
1616
1338
  exportedForTesting;
1617
1339
 
1618
1340
  seedCell({ cellId: firstCellId, before: false });
1619
- const newCellId = state.cellIds.inOrderIds[1];
1341
+ const newCellId = state.cellIds[1];
1620
1342
 
1621
1343
  const dataAtom1 = cellDataAtom(newCellId);
1622
1344
  const runtimeAtom1 = cellRuntimeAtom(newCellId);
@@ -46,7 +46,6 @@ const { initialNotebookState } = exportedForTesting;
46
46
  const a = cellId("a");
47
47
  const b = cellId("b");
48
48
  const c = cellId("c");
49
- const d = cellId("d");
50
49
 
51
50
  function cell(id: CellId, patch: Partial<EditableCell> = {}): EditableCell {
52
51
  return {
@@ -102,7 +101,7 @@ describe("applyDocument", () => {
102
101
  it("adds the document's cells to an empty notebook", () => {
103
102
  const state = notebookOf([cell(a), cell(b, { code: "" })]);
104
103
 
105
- expect(state.cellIds.inOrderIds).toEqual([a, b]);
104
+ expect(state.cellIds).toEqual([a, b]);
106
105
  expect(state.cellData[a]).toMatchObject({
107
106
  code: "a = 1",
108
107
  name: "_",
@@ -199,42 +198,11 @@ describe("applyDocument", () => {
199
198
 
200
199
  const next = applyDocument(state, documentOf([cell(a), cell(c)]));
201
200
 
202
- expect(next.cellIds.inOrderIds).toEqual([a, c]);
201
+ expect(next.cellIds).toEqual([a, c]);
203
202
  expect(next.cellData[b]).toBeUndefined();
204
203
  expect(next.cellRuntime[b]).toBeUndefined();
205
204
  expect(next.cellHandles[b]).toBeUndefined();
206
205
  });
207
-
208
- it("regroups columns from the cells' configs, keeping column ids", () => {
209
- const column1 = { ...cell(d).config, column: 1 };
210
- const state = notebookOf([
211
- cell(a),
212
- cell(b),
213
- cell(c),
214
- cell(d, { config: column1 }),
215
- ]);
216
- const next = applyDocument(
217
- state,
218
- documentOf([cell(c), cell(a), cell(b), cell(d, { config: column1 })]),
219
- );
220
-
221
- expect(next.cellIds.inOrderIds).toEqual([c, a, b, d]);
222
- expect(next.cellIds.getColumnIds()).toEqual(state.cellIds.getColumnIds());
223
- expect(next.cellIds.atOrThrow(1).inOrderIds).toEqual([d]);
224
- });
225
-
226
- it("keeps a trailing empty column", () => {
227
- const state = notebookOf([cell(a)]);
228
- const withEmpty = {
229
- ...state,
230
- cellIds: state.cellIds.addColumn(state.cellIds.atOrThrow(0).id),
231
- };
232
-
233
- const next = applyDocument(withEmpty, documentOf([cell(a, { code: "" })]));
234
-
235
- expect(next.cellIds.colLength).toBe(2);
236
- expect(next.cellData[a].code).toBe("");
237
- });
238
206
  });
239
207
 
240
208
  describe("documentEditMiddleware", () => {
@@ -283,18 +251,6 @@ describe("documentEditMiddleware", () => {
283
251
  expect(calls).toEqual([["setCode", a, "a = 2"]]);
284
252
  });
285
253
 
286
- it("clears columns merged into one", () => {
287
- state = notebookOf([
288
- cell(a),
289
- cell(b),
290
- cell(c, { config: { ...cell(c).config, column: 1 } }),
291
- ]);
292
-
293
- actions.mergeAllColumns();
294
-
295
- expect(calls).toEqual([["updateConfig", c, { column: null }]]);
296
- });
297
-
298
254
  it("ignores the document and the kernel's run state", () => {
299
255
  actions.prepareForRun({ cellId: a });
300
256
  actions.setLastRun({
@@ -3,15 +3,20 @@
3
3
  /** A store whose notebook follows a document model, as the session wires it. */
4
4
 
5
5
  import { history } from "@codemirror/commands";
6
+ import { python } from "@codemirror/lang-python";
6
7
  import { EditorState } from "@codemirror/state";
7
8
  import { EditorView } from "@codemirror/view";
8
9
  import type * as Protocol from "@marimo-team/client/protocol";
9
10
  import { createStore } from "jotai";
10
11
  import { mockDocumentModel } from "@/__mocks__/document";
12
+ import type { CellId } from "@/core/cells/ids";
13
+ import { adaptiveLanguageConfiguration } from "@/core/codemirror/language/extension";
14
+ import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys";
11
15
  import {
12
16
  document as documentOf,
13
17
  withCells,
14
18
  } from "@/core/notebook/document/__tests__/fixtures";
19
+ import type { Document } from "@/core/notebook/replica";
15
20
  import { exportedForTesting, notebookAtom, notebookReducer } from "../cells";
16
21
  import { followDocument } from "../document";
17
22
  import { layoutAtom } from "../layout";
@@ -22,7 +27,7 @@ export function setupNotebook(
22
27
  ) {
23
28
  const store = createStore();
24
29
  store.set(notebookAtom, exportedForTesting.initialNotebookState());
25
- const { model, stop } = mockDocumentModel(
30
+ const { model, documentAtom, stop } = mockDocumentModel(
26
31
  store,
27
32
  cells ? withCells(documentOf(), cells) : documentOf(),
28
33
  );
@@ -43,17 +48,36 @@ export function setupNotebook(
43
48
  return {
44
49
  store,
45
50
  model,
46
- order: () => store.get(notebookAtom).cellIds.inOrderIds,
51
+ /** Deliver a document from the host. Edits pending here stay on top. */
52
+ host: (document: Document) => store.set(documentAtom, document),
53
+ order: () => store.get(notebookAtom).cellIds,
47
54
  /** The cells of each column, as the page lays them out. */
48
55
  columns: () => store.get(layoutAtom).columns.map((column) => column.ids),
56
+ /** The columns' ids, as the page keys them. */
57
+ columnIds: () => store.get(layoutAtom).columns.map((column) => column.id),
49
58
  notebook: () => store.get(notebookAtom),
50
59
  cellIn: (id: string) => store.get(model.documentAtom)?.cells.get(id),
51
- /** Give a cell an editor, as mounting it would. */
60
+ /** Give a cell an editor, as mounting it would, with a cell's language setup. */
52
61
  mountEditor: (id: string) => {
53
62
  const view = new EditorView({
54
63
  state: EditorState.create({
55
64
  doc: store.get(model.documentAtom)?.cells.get(id)?.code ?? "",
56
- extensions: [history()],
65
+ extensions: [
66
+ history(),
67
+ python(),
68
+ adaptiveLanguageConfiguration({
69
+ cellId: id as CellId,
70
+ completionConfig: {
71
+ activate_on_typing: true,
72
+ signature_hint_on_typing: false,
73
+ copilot: false,
74
+ codeium_api_key: null,
75
+ },
76
+ hotkeys: new OverridingHotkeyProvider({}),
77
+ placeholderType: "marimo-import",
78
+ lspConfig: {},
79
+ }),
80
+ ],
57
81
  }),
58
82
  });
59
83
  store.get(notebookAtom).cellHandles[id as never].current = {