@marimo-team/islands 0.25.3-dev170 → 0.25.3-dev175
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-BbSXV-Xa.js → ConnectedDataExplorerComponent-Le_OBfmt.js} +9 -9
- package/dist/{ErrorBoundary-NE86qEWZ.js → ErrorBoundary-DNvcAIsP.js} +8 -8
- package/dist/{abnfDiagram-VCTEODGH-D7n07nXf.js → abnfDiagram-VCTEODGH-Tf1ghyQQ.js} +2 -2
- package/dist/{any-language-editor-3E24SnCV.js → any-language-editor-BVFHDDQ3.js} +4 -4
- package/dist/{architectureDiagram-5GKGNRK7-D6CjRib5.js → architectureDiagram-5GKGNRK7-xlnbTG0T.js} +1 -1
- package/dist/{chat-ui-CC7GekpG.js → chat-ui-X-cWrL9u.js} +1218 -1218
- package/dist/{check-SpOMoCLP.js → check-C9Mp9eCK.js} +1 -1
- package/dist/{chunk-IMKFNOWR-6dtPz7uS.js → chunk-IMKFNOWR-Cmd4a5jy.js} +1 -1
- package/dist/{chunk-SHT3W25Y-BwnFNKDA.js → chunk-SHT3W25Y-mSNp-WpO.js} +1 -1
- package/dist/{chunk-SVP7TREG-D2eV4vBJ.js → chunk-SVP7TREG-CWC49D8X.js} +1 -1
- package/dist/{chunk-TICWLB2K-BDs4-_-r.js → chunk-TICWLB2K-De9FINtG.js} +1 -1
- package/dist/{classDiagram-ZZMXUADV-DCJnf_8v.js → classDiagram-ZZMXUADV-DweTkYdp.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-BOmSzPBD.js → classDiagram-v2-VYDZK3BY-A6MR2TiE.js} +2 -2
- package/dist/{code-visibility-Bzvc6ajl.js → code-visibility-CH1lmM1h.js} +5 -5
- package/dist/{common-Ciw4zkh2.js → common-DIrS1UR9.js} +1081 -1085
- package/dist/{copy-icon-C1trdvVt.js → copy-icon-De584pZ4.js} +2 -2
- package/dist/{cynefinDiagram-5FMLGOSQ-Bln_Soky.js → cynefinDiagram-5FMLGOSQ-BSvo5le-.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-D-nf7WzW.js → dagre-GXQ25YYZ-tXRQslO6.js} +1 -1
- package/dist/{data-grid-overlay-editor-AQREmxI7.js → data-grid-overlay-editor-DFLZvFK0.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-PsgSMkY2.js → diagram-S7CK7UJ4-r3t5VtwI.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-DfwfUuSw.js → diagram-UQ7AKVKN-qgbC8YnT.js} +1 -1
- package/dist/{diagram-VX7I27RA-C9_e-oMc.js → diagram-VX7I27RA-CiHxEwtp.js} +1 -1
- package/dist/{diagram-Z3DM3KII-CWLa5tuY.js → diagram-Z3DM3KII-D95u15Ev.js} +1 -1
- package/dist/{dialog-DgeqyrEZ.js → dialog-DVRfQuhD.js} +24 -24
- package/dist/{dist-F8Ep36rk.js → dist-B1M0bGO3.js} +1 -1
- package/dist/{dist-D6d86eNJ.js → dist-BA13RJj5.js} +1 -1
- package/dist/{dist-Bvs24R_E.js → dist-BJtmSgcE.js} +48 -48
- package/dist/{dist-CN_BeG2I.js → dist-Ba2VIn1K.js} +53 -53
- package/dist/{dist-PXYI_yuP.js → dist-Bj4bmwUm.js} +12 -12
- package/dist/{dist-DoE4XJBC.js → dist-BmS-ZGjl.js} +8626 -8626
- package/dist/{dist-BorMD-uX.js → dist-Bzogh7CW.js} +87 -87
- package/dist/{dist-CN8zI-XC.js → dist-CThPVKaP.js} +1385 -1382
- package/dist/{dist-DiDibOYr.js → dist-CTi5FTiV.js} +87 -87
- package/dist/{dist-diIx5qJW.js → dist-Ck_C32ye.js} +2 -2
- package/dist/{dist-BLfKmR-l.js → dist-D1fnFp1J.js} +1 -1
- package/dist/{dist-OR1OnN-y.js → dist-D3x9__Fu.js} +46 -46
- package/dist/{dist-BMQf5zIS.js → dist-D7mrh5sO.js} +36 -36
- package/dist/{dist-CSJ0MDRw.js → dist-D9Q3xjiP.js} +1 -1
- package/dist/{dist-BuaRHxOc.js → dist-DP60Ubh8.js} +2 -2
- package/dist/{dist-CSbh1PNP.js → dist-DTv9KkpW.js} +12 -12
- package/dist/{dist-D-wwqlzJ.js → dist-DVVwOfrD.js} +109 -109
- package/dist/{dist-C8fAKSH1.js → dist-DYXM73-k.js} +36 -36
- package/dist/{dist-DBtSl3sf.js → dist-DjeybPOb.js} +1 -1
- package/dist/{dist-CrSP_H1M.js → dist-DtGn9N4h.js} +197 -197
- package/dist/{dist-CLdpGQVr.js → dist-KN80qWCP.js} +2 -2
- package/dist/{dist-Cg7gzw-a.js → dist-O0qFpEJh.js} +56 -56
- package/dist/{dist-CkTTIdBe.js → dist-OUle2RT7.js} +207 -207
- package/dist/{dist-BK4Oj99o.js → dist-T5rh6JvL.js} +1 -1
- package/dist/{dropdown-menu-B6ftyzOg.js → dropdown-menu-DCMbbQ35.js} +691 -691
- package/dist/{ebnfDiagram-PWID7BFC-B1QRM58v.js → ebnfDiagram-PWID7BFC-P7e4bryK.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-DJK_FqvW.js → erDiagram-RLTQ6QDP-CUHZcmRj.js} +1 -1
- package/dist/{error-banner-D40pmfQQ.js → error-banner-DxJ3s8YO.js} +57 -57
- package/dist/{error-entries-DgK3wUJR.js → error-entries-mpoD5t79.js} +4985 -5006
- package/dist/{esm-D2gTsXEV.js → esm-Bbw_Njjx.js} +575 -575
- package/dist/{esm-AMFssoNp.js → esm-mDQNEFe_.js} +354 -354
- package/dist/{feedback-button-8a6SETmT.js → feedback-button-DL0DJ-LB.js} +30 -30
- package/dist/{flowDiagram-HODETNUW-Uxc25XSJ.js → flowDiagram-HODETNUW-BGZ95AzI.js} +2 -2
- package/dist/{formats-DQCyPd5Z.js → formats-DI7HEVv7.js} +1 -1
- package/dist/{glide-data-editor-B70oke_v.js → glide-data-editor-n4wnx19S.js} +10 -10
- package/dist/{glide-utils-Dz_F4pAY.js → glide-utils-D98laidb.js} +2 -2
- package/dist/{globals-Cxd5hNlI.js → globals-NCsYyjpB.js} +3 -3
- package/dist/{html-to-image-D4yG5Ru0.js → html-to-image-BoZthB4P.js} +467 -458
- package/dist/{infoDiagram-27XIBGKW-DmjxqwL0.js → infoDiagram-27XIBGKW-BF0YX70D.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-NgCLHqQi.js → ishikawaDiagram-5VMMS53U-_itsAAn-.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-CZaG-iam.js → kanban-definition-UXKFOSKX-VNW2Beqh.js} +1 -1
- package/dist/{label-D9_-zSg-.js → label-Qvw1E97W.js} +1 -1
- package/dist/main.js +8257 -6606
- package/dist/{mermaid-CSvw7c71.js → mermaid-CZ6FrClS.js} +35 -35
- package/dist/{mindmap-definition-YA3MSWOX-DJYQBDH_.js → mindmap-definition-YA3MSWOX-DR4ripvd.js} +1 -1
- package/dist/{number-overlay-editor-ChK-SKUk.js → number-overlay-editor-rlvo4mo3.js} +1 -1
- package/dist/{pegDiagram-XKGWAZYB-CHKO_dAe.js → pegDiagram-XKGWAZYB-CFr3XlGl.js} +2 -2
- package/dist/{pieDiagram-E7YTZNPT-DVevbMAo.js → pieDiagram-E7YTZNPT-j91XYSLY.js} +1 -1
- package/dist/{process-output-DxvBAeL3.js → process-output-DLS4Mqg_.js} +4 -4
- package/dist/{railroadDiagram-O6MQD6OU-Detj1kcB.js → railroadDiagram-O6MQD6OU-Cd47ZV9E.js} +2 -2
- package/dist/{requirementDiagram-BXWQKSXE-BLFD0T_Z.js → requirementDiagram-BXWQKSXE-eJH6gLtX.js} +1 -1
- package/dist/{reveal-component-CXMXeDZD.js → reveal-component-BJfmRUzb.js} +9 -9
- package/dist/{select-CkF_GBxO.js → select-2KOiReNY.js} +324 -324
- package/dist/{sequenceDiagram-WJ2MYXX4-BZBgatQV.js → sequenceDiagram-WJ2MYXX4-DdfFEIrm.js} +1 -1
- package/dist/{spec-Mnkn3XEr.js → spec-h69nC3SI.js} +4 -4
- package/dist/{stateDiagram-D77RDMKH-4BZW9-6_.js → stateDiagram-D77RDMKH-DkjDvip8.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-C4sKC8Iq.js → stateDiagram-v2-MP3YSRHH-DBHACnOo.js} +2 -2
- package/dist/{swimlanes-42K2YHIH-SL5yeGe2.js → swimlanes-42K2YHIH-ogImlOmp.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-DqPmAYUv.js → swimlanesDiagram-VR7AAH4N-D6x-zsci.js} +2 -2
- package/dist/{timeline-definition-24CTP7MA-BfXUMWkq.js → timeline-definition-24CTP7MA-DQpLtsHD.js} +1 -1
- package/dist/{toDate-TMubKIVL.js → toDate-CDzoofJo.js} +2 -2
- package/dist/{toast-Btbve3Lm.js → toast-B5t9o6s_.js} +47 -47
- package/dist/{tooltip-CJ4Rqasp.js → tooltip-s4zZxwKe.js} +67 -67
- package/dist/{use-restore-focus-CmGwRNId.js → use-restore-focus-BgDTd2tc.js} +1 -1
- package/dist/{useAsyncData-DjMGFzFy.js → useAsyncData-Bn8SxUqM.js} +1 -1
- package/dist/{useDateFormatter-eCL_SdeG.js → useDateFormatter-D3Tkfzej.js} +2 -2
- package/dist/{useDeepCompareMemoize-BQR5TJot.js → useDeepCompareMemoize-CC2tnPCk.js} +1 -1
- package/dist/{useLifecycle-C_GvKWbQ.js → useLifecycle-BJlxHqWa.js} +2 -2
- package/dist/{useTheme-Bns2gJZ6.js → useTheme-DMfQYmlG.js} +85 -85
- package/dist/{vega-component-BsVptuhz.js → vega-component-CovWHi60.js} +7 -7
- package/dist/{vennDiagram-4TSXK5OY-CH65znGa.js → vennDiagram-4TSXK5OY-BD-SL_vq.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-z9pWKb2_.js → wardleyDiagram-VM6X3IG4-BDgJV4ah.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-MxyaWjjq.js → xychartDiagram-S5SC5T6Z-1hIHN6Rl.js} +1 -1
- package/package.json +1 -1
- package/src/__mocks__/document.tsx +54 -0
- package/src/__mocks__/notebook.ts +2 -1
- package/src/__mocks__/requests.ts +0 -2
- package/src/components/chat/chat-panel.tsx +3 -3
- package/src/components/datasources/__tests__/column-preview.test.tsx +6 -8
- package/src/components/datasources/column-preview.tsx +4 -8
- package/src/components/datasources/datasources.tsx +4 -3
- package/src/components/dependency-graph/minimap-content.tsx +2 -6
- package/src/components/dependency-graph/panels.tsx +2 -1
- package/src/components/editor/__tests__/Output.test.tsx +50 -32
- package/src/components/editor/__tests__/data-attributes.test.tsx +22 -11
- package/src/components/editor/__tests__/output-persistence.test.tsx +6 -1
- package/src/components/editor/actions/__tests__/name-cell-input.test.tsx +69 -0
- package/src/components/editor/actions/name-cell-input.tsx +4 -3
- package/src/components/editor/actions/useCellActionButton.tsx +20 -26
- package/src/components/editor/actions/useNotebookActions.tsx +13 -18
- package/src/components/editor/actions/useSetCodeVisibility.ts +10 -11
- package/src/components/editor/actions/useSetOutputsExpanded.ts +13 -18
- package/src/components/editor/cell/CreateCellButton.tsx +5 -9
- package/src/components/editor/cell/__tests__/StagedAICell.test.tsx +3 -1
- package/src/components/editor/cell/code/cell-editor.tsx +13 -20
- package/src/components/editor/cell/useAddCell.ts +4 -8
- package/src/components/editor/cell/useDeleteCell.tsx +10 -36
- package/src/components/editor/cell/useRunCells.ts +2 -5
- package/src/components/editor/cell/useSplitCell.tsx +2 -1
- package/src/components/editor/chrome/panels/logs-panel.tsx +2 -1
- package/src/components/editor/chrome/panels/snippets-panel.tsx +5 -11
- package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +3 -1
- package/src/components/editor/columns/sortable-column.tsx +1 -1
- package/src/components/editor/connections/components.tsx +4 -9
- package/src/components/editor/controls/Controls.tsx +1 -1
- package/src/components/editor/controls/__tests__/notebook-menu-dropdown.test.tsx +9 -3
- package/src/components/editor/documentation.css +34 -11
- package/src/components/editor/errors/auto-fix.tsx +4 -8
- package/src/components/editor/file-tree/file-explorer.tsx +3 -7
- package/src/components/editor/links/__tests__/cell-link.test.tsx +3 -1
- package/src/components/editor/links/cell-link.tsx +2 -1
- package/src/components/editor/navigation/__tests__/clipboard.test.ts +60 -38
- package/src/components/editor/navigation/__tests__/navigation.test.ts +73 -64
- package/src/components/editor/navigation/clipboard.ts +12 -11
- package/src/components/editor/navigation/multi-cell-action-toolbar.tsx +11 -20
- package/src/components/editor/navigation/navigation.ts +20 -28
- package/src/components/editor/notebook-cell.tsx +7 -10
- package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +125 -69
- package/src/components/editor/renderers/cell-array.tsx +10 -17
- package/src/components/markdown/markdown-renderer.tsx +4 -8
- package/src/components/pages/edit-page.tsx +32 -17
- package/src/components/scratchpad/scratchpad.test.tsx +3 -1
- package/src/components/scratchpad/scratchpad.tsx +4 -7
- package/src/components/slides/__tests__/minimap-actions.test.tsx +9 -25
- package/src/components/slides/minimap.tsx +7 -3
- package/src/components/slides/slide-cell-view.tsx +3 -3
- package/src/components/sort/SortableCellsProvider.tsx +2 -1
- package/src/core/ai/__tests__/staged-cells.test.ts +18 -20
- package/src/core/ai/staged-cells.ts +8 -16
- package/src/core/ai/tools/__tests__/edit-notebook-tool.test.ts +2 -2
- package/src/core/ai/tools/__tests__/run-cells-tool.test.ts +2 -2
- package/src/core/ai/tools/base.ts +2 -2
- package/src/core/ai/tools/edit-notebook-tool.ts +13 -21
- package/src/core/cells/__tests__/actions.test.ts +474 -34
- package/src/core/cells/__tests__/add-missing-import.test.ts +7 -8
- package/src/core/cells/__tests__/cells.test.ts +251 -861
- package/src/core/cells/__tests__/document.test.ts +42 -38
- package/src/core/cells/__tests__/notebook-outline.test.ts +7 -1
- package/src/core/cells/__tests__/outputs.test.tsx +71 -53
- package/src/core/cells/actions.ts +391 -3
- package/src/core/cells/add-missing-import.ts +46 -42
- package/src/core/cells/cells.ts +73 -444
- package/src/core/cells/document.ts +38 -6
- package/src/core/cells/outputs.ts +12 -28
- package/src/core/codemirror/cells/extensions.ts +6 -6
- package/src/core/codemirror/cells/state.ts +1 -1
- package/src/core/codemirror/keymaps/vim.ts +2 -2
- package/src/core/edit-app.tsx +20 -25
- package/src/core/islands/__tests__/bootstrap.test.ts +56 -0
- package/src/core/islands/__tests__/bridge.test.ts +42 -0
- package/src/core/islands/bootstrap.ts +24 -1
- package/src/core/islands/bridge.ts +46 -5
- package/src/core/kernel/__tests__/handlers.test.ts +7 -153
- package/src/core/kernel/handlers.ts +18 -59
- package/src/core/network/__tests__/requests-lazy.test.ts +1 -3
- package/src/core/network/requests-lazy.ts +0 -2
- package/src/core/network/requests-network.ts +0 -16
- package/src/core/network/requests-static.ts +0 -2
- package/src/core/network/requests-toasting.tsx +0 -2
- package/src/core/network/types.ts +0 -7
- package/src/core/notebook/document/__tests__/codemirror.test.ts +5 -0
- package/src/core/notebook/document/__tests__/draft.test.ts +41 -0
- package/src/core/notebook/document/__tests__/state.test.ts +21 -0
- package/src/core/notebook/document/draft.ts +35 -5
- package/src/core/notebook/document/index.ts +1 -0
- package/src/core/notebook/document/state.ts +6 -2
- package/src/core/run-app.tsx +1 -7
- package/src/core/session/__tests__/useNotebookSession.hook.test.tsx +0 -6
- package/src/core/session/useNotebookSession.tsx +28 -35
- package/src/core/wasm/bridge.ts +0 -16
- package/src/css/globals.css +56 -22
- package/src/stories/runtime-story.tsx +22 -15
|
@@ -1,14 +1,402 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
+
/** Notebook operations that edit the document and then adjust the editor. */
|
|
4
|
+
|
|
5
|
+
import { historyField } from "@codemirror/commands";
|
|
6
|
+
import { atom, type Getter, type Setter, useSetAtom } from "jotai";
|
|
7
|
+
import { useMemo } from "react";
|
|
3
8
|
import { scrollAndHighlightCell } from "@/components/editor/links/cell-link";
|
|
9
|
+
import type { DocumentModel, EditableDocument } from "@/core/notebook/document";
|
|
10
|
+
import type { CellColumnId } from "@/utils/id-tree";
|
|
11
|
+
import { Logger } from "@/utils/Logger";
|
|
12
|
+
import type { SerializedEditorState } from "../codemirror/types";
|
|
13
|
+
import type { CellConfig } from "../network/types";
|
|
4
14
|
import { store } from "../state/jotai";
|
|
5
|
-
import {
|
|
15
|
+
import { activeDocumentModelAtom } from "../session/active-notebook";
|
|
16
|
+
import {
|
|
17
|
+
type DeletedCell,
|
|
18
|
+
notebookAtom,
|
|
19
|
+
notebookReducer,
|
|
20
|
+
releaseCellAtoms,
|
|
21
|
+
useReducerActions,
|
|
22
|
+
} from "./cells";
|
|
23
|
+
import { resolveColumns } from "./columns";
|
|
24
|
+
import { CellId, SCRATCH_CELL_ID, SETUP_CELL_ID } from "./ids";
|
|
25
|
+
import { focusAndScrollCellIntoView } from "./scrollCellIntoView";
|
|
26
|
+
|
|
27
|
+
export interface CreateCellOptions {
|
|
28
|
+
/** Place the cell after this one. */
|
|
29
|
+
after?: CellId | null;
|
|
30
|
+
/** Place the cell before this one. */
|
|
31
|
+
before?: CellId | null;
|
|
32
|
+
/** Without an anchor, the column to append to. The first by default. */
|
|
33
|
+
endOf?: CellColumnId;
|
|
34
|
+
/** The id to create with, for callers that refer to the cell before it exists. */
|
|
35
|
+
id?: CellId;
|
|
36
|
+
code?: string;
|
|
37
|
+
name?: string;
|
|
38
|
+
/** The cell's config. Its column follows from the placement. */
|
|
39
|
+
config?: Partial<Omit<CellConfig, "column">>;
|
|
40
|
+
/** Scroll to and focus the cell once it mounts. On by default. */
|
|
41
|
+
focus?: boolean;
|
|
42
|
+
/** Create nothing when a cell already has this code. */
|
|
43
|
+
skipIfCodeExists?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type CreateCell = (options?: CreateCellOptions) => CellId | null;
|
|
47
|
+
|
|
48
|
+
function modelOf(get: Getter): DocumentModel {
|
|
49
|
+
const model = get(activeDocumentModelAtom);
|
|
50
|
+
if (!model) {
|
|
51
|
+
throw new Error("The notebook has no document to edit");
|
|
52
|
+
}
|
|
53
|
+
return model;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function documentOf(get: Getter): EditableDocument {
|
|
57
|
+
const document = get(modelOf(get).documentAtom);
|
|
58
|
+
if (!document) {
|
|
59
|
+
throw new Error("The notebook has no document to edit");
|
|
60
|
+
}
|
|
61
|
+
return document;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function hasCellWithCode(document: EditableDocument, code: string): boolean {
|
|
65
|
+
for (const cell of document.cells.values()) {
|
|
66
|
+
if (cell.code === code) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Scroll to and focus the cell once it mounts. */
|
|
74
|
+
function focusWhenMounted(set: Setter, cellId: CellId): void {
|
|
75
|
+
set(notebookAtom, (state) => ({ ...state, scrollKey: cellId }));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Create a cell. Returns its id, or null when it was skipped. */
|
|
79
|
+
export const createCellAtom = atom(
|
|
80
|
+
null,
|
|
81
|
+
(get, set, options: CreateCellOptions = {}): CellId | null => {
|
|
82
|
+
const { code = "", focus = true } = options;
|
|
83
|
+
const document = documentOf(get);
|
|
84
|
+
if (options.skipIfCodeExists && hasCellWithCode(document, code)) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const { cellIds } = get(notebookAtom);
|
|
88
|
+
|
|
89
|
+
// The document knows cells and order. Columns come from each cell's
|
|
90
|
+
// `column` config, which the cell before it provides unless it starts a
|
|
91
|
+
// column, so a cell placed first in a later column names its column.
|
|
92
|
+
let placement: { after?: CellId; before?: CellId } = {};
|
|
93
|
+
let column: number | null = null;
|
|
94
|
+
if (options.after) {
|
|
95
|
+
placement = { after: options.after };
|
|
96
|
+
} else if (options.before) {
|
|
97
|
+
const tree = cellIds.findWithId(options.before);
|
|
98
|
+
const index = cellIds.indexOf(tree);
|
|
99
|
+
placement = { before: options.before };
|
|
100
|
+
if (index > 0 && tree.indexOfOrThrow(options.before) === 0) {
|
|
101
|
+
column = index;
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
const tree =
|
|
105
|
+
(options.endOf && cellIds.get(options.endOf)) ?? cellIds.atOrThrow(0);
|
|
106
|
+
if (tree.length > 0) {
|
|
107
|
+
placement = { after: tree.last() };
|
|
108
|
+
} else if (cellIds.colLength > 1) {
|
|
109
|
+
column = cellIds.indexOf(tree);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const id = options.id ?? CellId.create();
|
|
114
|
+
const config = { ...options.config, column };
|
|
115
|
+
set(modelOf(get).editAtom, (tx) => {
|
|
116
|
+
tx.createCell({ id, code, name: options.name, config }, placement);
|
|
117
|
+
// Only a column's first cell carries its index, as the file is written.
|
|
118
|
+
if (column !== null && options.before) {
|
|
119
|
+
tx.updateConfig(options.before, { column: null });
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
if (focus) {
|
|
123
|
+
focusWhenMounted(set, id);
|
|
124
|
+
// Hidden code stays visible until the cell is blurred for the first time.
|
|
125
|
+
if (config.hide_code) {
|
|
126
|
+
set(notebookAtom, (state) => ({
|
|
127
|
+
...state,
|
|
128
|
+
untouchedNewCells: new Set([...state.untouchedNewCells, id]),
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return id;
|
|
133
|
+
},
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Focus the cell before or after `cellId`. Past the edge of the column,
|
|
138
|
+
* create one there, unless `create` is false.
|
|
139
|
+
*/
|
|
140
|
+
export const moveToNextCellAtom = atom(
|
|
141
|
+
null,
|
|
142
|
+
(
|
|
143
|
+
get,
|
|
144
|
+
set,
|
|
145
|
+
{
|
|
146
|
+
cellId,
|
|
147
|
+
before,
|
|
148
|
+
create = true,
|
|
149
|
+
}: { cellId: CellId; before: boolean; create?: boolean },
|
|
150
|
+
): void => {
|
|
151
|
+
if (cellId === SCRATCH_CELL_ID) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const notebook = get(notebookAtom);
|
|
155
|
+
const column = notebook.cellIds.findWithId(cellId);
|
|
156
|
+
const index = column.indexOfOrThrow(cellId);
|
|
157
|
+
const nextIndex = before ? index - 1 : index + 1;
|
|
158
|
+
if (nextIndex < 0 || nextIndex >= column.length) {
|
|
159
|
+
if (create) {
|
|
160
|
+
set(createCellAtom, before ? { before: cellId } : { after: cellId });
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const nextCellId = column.atOrThrow(nextIndex);
|
|
165
|
+
const cell = notebook.cellData[nextCellId];
|
|
166
|
+
focusAndScrollCellIntoView({
|
|
167
|
+
cellId: nextCellId,
|
|
168
|
+
cell: notebook.cellHandles[nextCellId],
|
|
169
|
+
isCodeHidden:
|
|
170
|
+
Boolean(cell.config.hide_code) &&
|
|
171
|
+
!notebook.untouchedNewCells.has(nextCellId),
|
|
172
|
+
codeFocus: before ? "bottom" : "top",
|
|
173
|
+
variableName: undefined,
|
|
174
|
+
});
|
|
175
|
+
},
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
/** Focus the setup cell, creating it at the top with `code` if it is missing. */
|
|
179
|
+
export const addSetupCellAtom = atom(
|
|
180
|
+
null,
|
|
181
|
+
(get, set, { code }: { code?: string } = {}): void => {
|
|
182
|
+
const { cellIds } = get(notebookAtom);
|
|
183
|
+
if (cellIds.setupCellExists()) {
|
|
184
|
+
focusWhenMounted(set, SETUP_CELL_ID);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
// The setup cell leads the first column, which may be empty.
|
|
188
|
+
const first = cellIds.atOrThrow(0);
|
|
189
|
+
set(createCellAtom, {
|
|
190
|
+
id: SETUP_CELL_ID,
|
|
191
|
+
name: SETUP_CELL_ID,
|
|
192
|
+
code: code ?? "",
|
|
193
|
+
...(first.length > 0
|
|
194
|
+
? { before: first.atOrThrow(0) }
|
|
195
|
+
: { endOf: first.id }),
|
|
196
|
+
});
|
|
197
|
+
},
|
|
198
|
+
);
|
|
6
199
|
|
|
7
200
|
/**
|
|
8
|
-
*
|
|
201
|
+
* Delete cells, keeping them in the history for undo. The notebook keeps at
|
|
202
|
+
* least one cell. Returns false when nothing was deleted.
|
|
9
203
|
*/
|
|
204
|
+
export const deleteCellsAtom = atom(
|
|
205
|
+
null,
|
|
206
|
+
(get, set, { cellIds }: { cellIds: CellId[] }): boolean => {
|
|
207
|
+
const document = documentOf(get);
|
|
208
|
+
const notebook = get(notebookAtom);
|
|
209
|
+
const requested = new Set(cellIds);
|
|
210
|
+
for (const id of cellIds) {
|
|
211
|
+
if (!document.cells.has(id)) {
|
|
212
|
+
// Expected for kernel-only cells or a cell another client removed.
|
|
213
|
+
Logger.warn("Skipping delete for missing cellId", { cellId: id });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const order = document.order as CellId[];
|
|
217
|
+
const deleting = order.flatMap((id) => {
|
|
218
|
+
const cell = document.cells.get(id);
|
|
219
|
+
return cell !== undefined && requested.has(id) ? [cell] : [];
|
|
220
|
+
});
|
|
221
|
+
if (deleting.length === order.length) {
|
|
222
|
+
deleting.pop();
|
|
223
|
+
}
|
|
224
|
+
if (deleting.length === 0) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
const gone = new Set(deleting.map((cell) => cell.id as CellId));
|
|
228
|
+
const firstSurvivor = order.find((id) => !gone.has(id));
|
|
229
|
+
|
|
230
|
+
const cells: DeletedCell[] = deleting.map((cell) => {
|
|
231
|
+
const id = cell.id as CellId;
|
|
232
|
+
const index = order.indexOf(id);
|
|
233
|
+
const code = cell.code ?? notebook.cellData[id]?.code ?? "";
|
|
234
|
+
const editorView = notebook.cellHandles[id]?.current?.editorView;
|
|
235
|
+
const serializedEditorState: SerializedEditorState | null =
|
|
236
|
+
editorView?.state.toJSON({ history: historyField }) ?? null;
|
|
237
|
+
if (serializedEditorState) {
|
|
238
|
+
serializedEditorState.doc = code;
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
cell: { id, code, name: cell.name, config: cell.config },
|
|
242
|
+
// Anchor to the cell before it, which undo restores first if it is
|
|
243
|
+
// also being deleted. The first cell anchors to the next survivor.
|
|
244
|
+
placement:
|
|
245
|
+
index > 0 ? { after: order[index - 1] } : { before: firstSurvivor },
|
|
246
|
+
serializedEditorState,
|
|
247
|
+
};
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Focus the nearest survivor in the column, looking up before down.
|
|
251
|
+
const first = deleting[0].id as CellId;
|
|
252
|
+
const ids = notebook.cellIds.findWithId(first).inOrderIds;
|
|
253
|
+
const at = ids.indexOf(first);
|
|
254
|
+
const scrollKey =
|
|
255
|
+
ids.slice(0, at).findLast((id) => !gone.has(id)) ??
|
|
256
|
+
ids.slice(at + 1).find((id) => !gone.has(id)) ??
|
|
257
|
+
null;
|
|
258
|
+
|
|
259
|
+
const columns = resolveColumns(document);
|
|
260
|
+
set(modelOf(get).editAtom, (tx) => {
|
|
261
|
+
// A cell without a `column` config takes the column of the cell before
|
|
262
|
+
// it, so a survivor that followed a deleted cell names its own.
|
|
263
|
+
let previous: CellId | null = null;
|
|
264
|
+
let followsDeleted = false;
|
|
265
|
+
for (const id of order) {
|
|
266
|
+
if (gone.has(id)) {
|
|
267
|
+
followsDeleted = true;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
const inherited = previous === null ? 0 : columns.get(previous);
|
|
271
|
+
if (
|
|
272
|
+
followsDeleted &&
|
|
273
|
+
document.cells.get(id)?.config.column == null &&
|
|
274
|
+
inherited !== columns.get(id)
|
|
275
|
+
) {
|
|
276
|
+
tx.updateConfig(id, { column: columns.get(id) });
|
|
277
|
+
}
|
|
278
|
+
previous = id;
|
|
279
|
+
followsDeleted = false;
|
|
280
|
+
}
|
|
281
|
+
for (const id of gone) {
|
|
282
|
+
tx.deleteCell(id);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
for (const id of gone) {
|
|
286
|
+
releaseCellAtoms(id);
|
|
287
|
+
}
|
|
288
|
+
set(notebookAtom, (state) => ({
|
|
289
|
+
...state,
|
|
290
|
+
history: [...state.history, { type: "delete", cells }],
|
|
291
|
+
untouchedNewCells: new Set(
|
|
292
|
+
[...state.untouchedNewCells].filter((id) => !gone.has(id)),
|
|
293
|
+
),
|
|
294
|
+
scrollKey,
|
|
295
|
+
}));
|
|
296
|
+
return true;
|
|
297
|
+
},
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
/** Undo the last deletion or move. */
|
|
301
|
+
export const undoDeleteCellAtom = atom(null, (get, set): void => {
|
|
302
|
+
const last = get(notebookAtom).history.at(-1);
|
|
303
|
+
if (last === undefined) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (last.type === "move") {
|
|
307
|
+
set(notebookAtom, (state) =>
|
|
308
|
+
notebookReducer(
|
|
309
|
+
{ ...state, history: state.history.slice(0, -1) },
|
|
310
|
+
{ type: "undoMoveCells", payload: last },
|
|
311
|
+
),
|
|
312
|
+
);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const model = modelOf(get);
|
|
316
|
+
const before = documentOf(get);
|
|
317
|
+
set(model.editAtom, (tx) => {
|
|
318
|
+
// Each cell anchors to the one before it, restored just before it. An
|
|
319
|
+
// anchor another client has removed since falls back to the end.
|
|
320
|
+
const restored = new Set<string>();
|
|
321
|
+
for (const { cell, placement } of last.cells) {
|
|
322
|
+
const anchor = placement.after ?? placement.before;
|
|
323
|
+
const anchored =
|
|
324
|
+
anchor === undefined ||
|
|
325
|
+
before.cells.has(anchor) ||
|
|
326
|
+
restored.has(anchor);
|
|
327
|
+
tx.createCell(cell, anchored ? placement : {});
|
|
328
|
+
restored.add(cell.id);
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
// Subscribers hear of the edit after the outermost set, so the notebook
|
|
332
|
+
// has not followed the document yet. Catch it up to reach the new cells.
|
|
333
|
+
const document = get(model.documentAtom);
|
|
334
|
+
if (document !== null) {
|
|
335
|
+
set(notebookAtom, (state) =>
|
|
336
|
+
notebookReducer(state, { type: "reconcileDocument", payload: document }),
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
set(notebookAtom, (state) => {
|
|
340
|
+
let { cellData } = state;
|
|
341
|
+
for (const { cell, serializedEditorState } of last.cells) {
|
|
342
|
+
const id = cell.id as CellId;
|
|
343
|
+
if (serializedEditorState !== null && cellData[id] !== undefined) {
|
|
344
|
+
cellData = {
|
|
345
|
+
...cellData,
|
|
346
|
+
[id]: { ...cellData[id], serializedEditorState },
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
...state,
|
|
352
|
+
cellData,
|
|
353
|
+
history: state.history.slice(0, -1),
|
|
354
|
+
scrollKey: last.cells[0].cell.id as CellId,
|
|
355
|
+
};
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Every notebook operation, for components. Stable for the store's lifetime.
|
|
361
|
+
*
|
|
362
|
+
* The reducer's actions are included while the reducer still has cases.
|
|
363
|
+
* Outside React, set the atoms on the store directly.
|
|
364
|
+
*/
|
|
365
|
+
export function useCellActions() {
|
|
366
|
+
// The session feeds the kernel's messages and the document into the
|
|
367
|
+
// notebook through `useReducerActions`; components get the operations.
|
|
368
|
+
const {
|
|
369
|
+
handleCellMessage: _handleCellMessage,
|
|
370
|
+
addLogs: _addLogs,
|
|
371
|
+
reconcileDocument: _reconcileDocument,
|
|
372
|
+
setLastRun: _setLastRun,
|
|
373
|
+
resetCellRuntimes: _resetCellRuntimes,
|
|
374
|
+
...operations
|
|
375
|
+
} = useReducerActions();
|
|
376
|
+
const createCell = useSetAtom(createCellAtom);
|
|
377
|
+
const moveToNextCell = useSetAtom(moveToNextCellAtom);
|
|
378
|
+
const addSetupCell = useSetAtom(addSetupCellAtom);
|
|
379
|
+
const deleteCells = useSetAtom(deleteCellsAtom);
|
|
380
|
+
const undoDeleteCell = useSetAtom(undoDeleteCellAtom);
|
|
381
|
+
return useMemo(
|
|
382
|
+
() => ({
|
|
383
|
+
...operations,
|
|
384
|
+
createCell,
|
|
385
|
+
moveToNextCell,
|
|
386
|
+
addSetupCell,
|
|
387
|
+
deleteCells,
|
|
388
|
+
undoDeleteCell,
|
|
389
|
+
}),
|
|
390
|
+
// The reducer's actions are stable for the store's lifetime.
|
|
391
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
392
|
+
[createCell, moveToNextCell, addSetupCell, deleteCells, undoDeleteCell],
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export type CellActions = ReturnType<typeof useCellActions>;
|
|
397
|
+
|
|
398
|
+
/** Scroll to the first cell that is currently running. */
|
|
10
399
|
export function notebookScrollToRunning() {
|
|
11
|
-
// find cell that is currently in "running" state
|
|
12
400
|
const { cellIds, cellRuntime } = store.get(notebookAtom);
|
|
13
401
|
const cellId = cellIds.inOrderIds.find(
|
|
14
402
|
(id) => cellRuntime[id]?.status === "running",
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
import { getRequestClient } from "../network/requests";
|
|
4
4
|
import { store } from "../state/jotai";
|
|
5
5
|
import { variablesAtom } from "../variables/state";
|
|
6
|
-
import {
|
|
6
|
+
import { notebookAtom } from "./cells";
|
|
7
|
+
import type { CreateCell, CreateCellOptions } from "./actions";
|
|
7
8
|
import { CellId } from "./ids";
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -50,82 +51,85 @@ export function maybeAddMissingImport({
|
|
|
50
51
|
/**
|
|
51
52
|
* Adds a marimo import to the notebook if not already present.
|
|
52
53
|
* @param autoInstantiate Whether to automatically run the cell.
|
|
53
|
-
* @param
|
|
54
|
-
* @param fromCellId The cell to add the import to.
|
|
54
|
+
* @param createCell The command that creates a cell.
|
|
55
|
+
* @param fromCellId The cell to add the import next to.
|
|
55
56
|
* @param before Whether to add the import before or after the cell.
|
|
56
57
|
*
|
|
57
58
|
* Returns the ID of the new cell if added, otherwise null.
|
|
58
59
|
*/
|
|
59
60
|
export function maybeAddMarimoImport({
|
|
60
61
|
autoInstantiate,
|
|
61
|
-
|
|
62
|
+
createCell,
|
|
62
63
|
fromCellId,
|
|
63
64
|
before,
|
|
64
65
|
}: {
|
|
65
66
|
autoInstantiate: boolean;
|
|
66
|
-
|
|
67
|
+
createCell: CreateCell;
|
|
67
68
|
fromCellId?: CellId | null;
|
|
68
69
|
before?: boolean;
|
|
69
70
|
}): CellId | null {
|
|
70
|
-
|
|
71
|
-
let newCellId: CellId | null = null;
|
|
72
|
-
const added = maybeAddMissingImport({
|
|
71
|
+
return addImport({
|
|
73
72
|
moduleName: "marimo",
|
|
74
73
|
variableName: "mo",
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
cellId: fromCellId ?? "__end__",
|
|
79
|
-
before: before ?? false,
|
|
80
|
-
code: importStatement,
|
|
81
|
-
lastCodeRun: autoInstantiate ? importStatement : undefined,
|
|
82
|
-
newCellId: newCellId,
|
|
83
|
-
skipIfCodeExists: true,
|
|
84
|
-
autoFocus: false,
|
|
85
|
-
});
|
|
86
|
-
if (autoInstantiate) {
|
|
87
|
-
void client.sendRun({
|
|
88
|
-
cellIds: [newCellId],
|
|
89
|
-
codes: [importStatement],
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
},
|
|
74
|
+
autoInstantiate,
|
|
75
|
+
createCell,
|
|
76
|
+
placement: before ? { before: fromCellId } : { after: fromCellId },
|
|
93
77
|
});
|
|
94
|
-
return added ? newCellId : null;
|
|
95
78
|
}
|
|
96
79
|
|
|
97
80
|
export function maybeAddAltairImport({
|
|
98
81
|
autoInstantiate,
|
|
99
|
-
|
|
82
|
+
createCell,
|
|
100
83
|
fromCellId,
|
|
101
84
|
}: {
|
|
102
85
|
autoInstantiate: boolean;
|
|
103
|
-
|
|
86
|
+
createCell: CreateCell;
|
|
104
87
|
fromCellId?: CellId | null;
|
|
105
88
|
}): CellId | null {
|
|
106
|
-
|
|
107
|
-
let newCellId: CellId | null = null;
|
|
108
|
-
const added = maybeAddMissingImport({
|
|
89
|
+
return addImport({
|
|
109
90
|
moduleName: "altair",
|
|
110
91
|
variableName: "alt",
|
|
92
|
+
autoInstantiate,
|
|
93
|
+
createCell,
|
|
94
|
+
placement: { after: fromCellId },
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Create the import cell, and run it when the notebook runs on its own. */
|
|
99
|
+
function addImport({
|
|
100
|
+
moduleName,
|
|
101
|
+
variableName,
|
|
102
|
+
autoInstantiate,
|
|
103
|
+
createCell,
|
|
104
|
+
placement,
|
|
105
|
+
}: {
|
|
106
|
+
moduleName: string;
|
|
107
|
+
variableName: string;
|
|
108
|
+
autoInstantiate: boolean;
|
|
109
|
+
createCell: CreateCell;
|
|
110
|
+
placement: Pick<CreateCellOptions, "after" | "before">;
|
|
111
|
+
}): CellId | null {
|
|
112
|
+
let newCellId: CellId | null = null;
|
|
113
|
+
maybeAddMissingImport({
|
|
114
|
+
moduleName,
|
|
115
|
+
variableName,
|
|
111
116
|
onAddImport: (importStatement) => {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
117
|
+
const id = CellId.create();
|
|
118
|
+
const created = createCell({
|
|
119
|
+
...placement,
|
|
120
|
+
id,
|
|
116
121
|
code: importStatement,
|
|
117
|
-
lastCodeRun: autoInstantiate ? importStatement : undefined,
|
|
118
|
-
newCellId: newCellId,
|
|
119
122
|
skipIfCodeExists: true,
|
|
120
|
-
|
|
123
|
+
focus: false,
|
|
121
124
|
});
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
newCellId = created === null ? null : id;
|
|
126
|
+
if (autoInstantiate && newCellId !== null) {
|
|
127
|
+
void getRequestClient().sendRun({
|
|
124
128
|
cellIds: [newCellId],
|
|
125
129
|
codes: [importStatement],
|
|
126
130
|
});
|
|
127
131
|
}
|
|
128
132
|
},
|
|
129
133
|
});
|
|
130
|
-
return
|
|
134
|
+
return newCellId;
|
|
131
135
|
}
|