@marimo-team/islands 0.25.3-dev209 → 0.25.3-dev210
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-ApTYh0V3.js → ConnectedDataExplorerComponent-D6RHJV8D.js} +114 -112
- package/dist/{ErrorBoundary-B1QlXN-y.js → ErrorBoundary-VruaQLnK.js} +10 -10
- package/dist/{abnfDiagram-VCTEODGH-uo9xhZY0.js → abnfDiagram-VCTEODGH-0q7Lbmii.js} +2 -2
- package/dist/{any-language-editor-BVFHDDQ3.js → any-language-editor-jGaR0EAo.js} +5 -5
- package/dist/{architectureDiagram-5GKGNRK7-Hz6eyeOD.js → architectureDiagram-5GKGNRK7-1PydKPmE.js} +1 -1
- package/dist/{button-BQRdqQi1.js → button-B6RjDKJx.js} +54 -51
- package/dist/{capabilities-C2QUNRXi.js → capabilities-CkVzWJEX.js} +1 -1
- package/dist/{chat-ui-BTA1I5i1.js → chat-ui-B5pH_aXk.js} +414 -413
- package/dist/{check-C9Mp9eCK.js → check-Z_vtI3OL.js} +1 -1
- package/dist/{chunk-IMKFNOWR-DBM2i23k.js → chunk-IMKFNOWR-DEzZZ1Zc.js} +1 -1
- package/dist/{chunk-SHT3W25Y-L8KQLLBy.js → chunk-SHT3W25Y-CuHj-hnn.js} +1 -1
- package/dist/{chunk-SVP7TREG-BOUJKJEP.js → chunk-SVP7TREG-BPiZK2bA.js} +1 -1
- package/dist/{chunk-TICWLB2K-DY3CzFjX.js → chunk-TICWLB2K-DF6XcIjM.js} +1 -1
- package/dist/{classDiagram-ZZMXUADV-s9iwW-q4.js → classDiagram-ZZMXUADV-CMdR-cP4.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-2GI5CjGB.js → classDiagram-v2-VYDZK3BY-DRvc3_7Y.js} +2 -2
- package/dist/{code-visibility-CCGlDSq4.js → code-visibility-BdFhHfIC.js} +28 -28
- package/dist/{common-C1PmVtVW.js → common-Ce1SgsX1.js} +8034 -8027
- package/dist/{toDate-BZZ64K_m.js → config-DbjzjwxX.js} +98 -164
- package/dist/{copy-icon-De584pZ4.js → copy-icon-CG57cxwb.js} +3 -3
- package/dist/createReducer-DePYJ0F3.js +63 -0
- package/dist/{cynefinDiagram-5FMLGOSQ-DOxnhBK2.js → cynefinDiagram-5FMLGOSQ-BkBjEY9l.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-D1AhEfpM.js → dagre-GXQ25YYZ-D-HEaW4I.js} +1 -1
- package/dist/{data-grid-overlay-editor-B3KXkZvo.js → data-grid-overlay-editor-Bps1p6jQ.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-DV_GebF_.js → diagram-S7CK7UJ4-Bn-oF-rB.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-6dNzGFfD.js → diagram-UQ7AKVKN-BpzzXWBj.js} +1 -1
- package/dist/{diagram-VX7I27RA-DUh9jsfi.js → diagram-VX7I27RA-COOtvfBP.js} +1 -1
- package/dist/{diagram-Z3DM3KII-Cug-qcQ8.js → diagram-Z3DM3KII-K5cXvbrl.js} +1 -1
- package/dist/{dialog-DVRfQuhD.js → dialog-DsZ49mmU.js} +26 -26
- package/dist/{dist-BJtmSgcE.js → dist-11T5wq3_.js} +41 -40
- package/dist/{dist-B1M0bGO3.js → dist-17ugwGZc.js} +59 -58
- package/dist/{dist-D7mrh5sO.js → dist-7cRjf--y.js} +135 -134
- package/dist/{dist-Ck_C32ye.js → dist-B8pkfkzc.js} +31 -30
- package/dist/{dist-Ba2VIn1K.js → dist-B9UoM_wq.js} +54 -53
- package/dist/{dist-KN80qWCP.js → dist-BGuMTs1i.js} +118 -117
- package/dist/{dist-T5rh6JvL.js → dist-BHW11fni.js} +18 -17
- package/dist/dist-BLY4m1Wi.js +850 -0
- package/dist/{dist-Bj4bmwUm.js → dist-BNlcVoxs.js} +31 -30
- package/dist/{dist-BA13RJj5.js → dist-BQYe5Bju.js} +47 -46
- package/dist/{dist-OUle2RT7.js → dist-BX1mn8na.js} +297 -297
- package/dist/{dist-CTi5FTiV.js → dist-Bh0PTKUW.js} +108 -107
- package/dist/{dist-D9Q3xjiP.js → dist-CKaXK92p.js} +56 -55
- package/dist/{dist-DYXM73-k.js → dist-CLhwfhpJ.js} +58 -57
- package/dist/{dist-BmS-ZGjl.js → dist-CYeqK4Xw.js} +1854 -2702
- package/dist/{dist-D1fnFp1J.js → dist-Cxlp3WH6.js} +16 -15
- package/dist/{dist-DVVwOfrD.js → dist-DSJPGj42.js} +64 -63
- package/dist/{dist-Bzogh7CW.js → dist-DU9ICsWZ.js} +93 -92
- package/dist/{dist-DjeybPOb.js → dist-DWv0ALBi.js} +47 -46
- package/dist/{dist-D3x9__Fu.js → dist-DiBpWEsB.js} +39 -38
- package/dist/{dist-DtGn9N4h.js → dist-DiLFTlnl.js} +165 -165
- package/dist/{dist-DTv9KkpW.js → dist-cVDV5IuI.js} +26 -25
- package/dist/{dist-DP60Ubh8.js → dist-fRfaOoq8.js} +44 -43
- package/dist/{dist-O0qFpEJh.js → dist-gTTI-9Px.js} +66 -65
- package/dist/{dropdown-menu-dlEm41Bt.js → dropdown-menu-CSNdPSnS.js} +2886 -1529
- package/dist/{ebnfDiagram-PWID7BFC-d5LKx7Vb.js → ebnfDiagram-PWID7BFC-BI1ZmjVm.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-BwvlkcID.js → erDiagram-RLTQ6QDP-BtWjj9xy.js} +1 -1
- package/dist/{error-banner-DxJ3s8YO.js → error-banner-IsonnvA0.js} +47 -46
- package/dist/error-entries-CRPfxMqm.js +2505 -0
- package/dist/{esm-Bbw_Njjx.js → esm-BgKjMvWw.js} +389 -388
- package/dist/{esm-mDQNEFe_.js → esm-CHjVSexr.js} +305 -306
- package/dist/{feedback-button-UsovT9sL.js → feedback-button-2ycmR9GG.js} +112 -112
- package/dist/{flowDiagram-HODETNUW-CJcSoFer.js → flowDiagram-HODETNUW-DQvZh96A.js} +2 -2
- package/dist/{formats-Ry7c-aIV.js → formats-BK4VefeF.js} +6 -6
- package/dist/{glide-data-editor-CRd8kBSp.js → glide-data-editor-Buj2OZjL.js} +99 -99
- package/dist/{glide-utils-D98laidb.js → glide-utils-CQddGpa-.js} +3 -3
- package/dist/{globals-BDWrOstF.js → globals-B-14fUeb.js} +3 -3
- package/dist/{error-entries-CVUHA5r_.js → html-to-image-CgAnxFi8.js} +26314 -25474
- package/dist/{infoDiagram-27XIBGKW-Cezgppw3.js → infoDiagram-27XIBGKW-BdAObnc_.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-BahZQwJz.js → ishikawaDiagram-5VMMS53U-DP-jE8dw.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-Cvfn_Obm.js → kanban-definition-UXKFOSKX-BkMgPjfm.js} +1 -1
- package/dist/{loader-DOVB0Syl.js → loader-BisjOLiW.js} +1 -1
- package/dist/main.js +2996 -2992
- package/dist/{mermaid-BrdwbArF.js → mermaid-B-OFqheA.js} +36 -36
- package/dist/{mindmap-definition-YA3MSWOX-uB4B2d-J.js → mindmap-definition-YA3MSWOX-Du7toAZX.js} +1 -1
- package/dist/{number-overlay-editor-Bp9a4z5Q.js → number-overlay-editor-ZVFcJ7_u.js} +1 -1
- package/dist/{dist-CThPVKaP.js → objectWithoutPropertiesLoose-ai5NUqGa.js} +1139 -1120
- package/dist/{pegDiagram-XKGWAZYB-CYaIwAmH.js → pegDiagram-XKGWAZYB-CX_729j0.js} +2 -2
- package/dist/{pieDiagram-E7YTZNPT-BJ1oUxVv.js → pieDiagram-E7YTZNPT-dtTFJcVI.js} +1 -1
- package/dist/{process-output-SjJxB1wh.js → process-output-Bmev5azU.js} +5 -5
- package/dist/{railroadDiagram-O6MQD6OU-BqKSaBux.js → railroadDiagram-O6MQD6OU-Dsc2Cf4c.js} +2 -2
- package/dist/{requirementDiagram-BXWQKSXE-DwFhgDhH.js → requirementDiagram-BXWQKSXE-D3g0c60I.js} +1 -1
- package/dist/{reveal-component-ov3pS-oO.js → reveal-component-BWz1KjWe.js} +725 -725
- package/dist/schemas-CdscWNy_.js +4747 -0
- package/dist/select-BSxlITBo.js +1200 -0
- package/dist/{sequenceDiagram-WJ2MYXX4-L7ENjfKF.js → sequenceDiagram-WJ2MYXX4-w1pWbIpk.js} +1 -1
- package/dist/{spec-BcJ9ncAY.js → spec-Dq8RNplp.js} +4 -4
- package/dist/{stateDiagram-D77RDMKH-CVf88u-p.js → stateDiagram-D77RDMKH-BCv7kP4H.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-DvpkQ1V6.js → stateDiagram-v2-MP3YSRHH-D1cTOt0x.js} +2 -2
- package/dist/static-state-BldGd99T.js +72 -0
- package/dist/strings-BGW-fXVJ.js +507 -0
- package/dist/{swimlanes-42K2YHIH-DIuFHbVE.js → swimlanes-42K2YHIH-DNO6jDnd.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-CAqHAziW.js → swimlanesDiagram-VR7AAH4N-CkSWsxYJ.js} +2 -2
- package/dist/{swiper-component-BVfnhbnt.js → swiper-component-Dir1o8ml.js} +2 -2
- package/dist/{timeline-definition-24CTP7MA-xO4EQqBm.js → timeline-definition-24CTP7MA-DQvqGFM9.js} +1 -1
- package/dist/toast-BWIPi3aF.js +491 -0
- package/dist/{tooltip-s4zZxwKe.js → tooltip-C3UnvsRe.js} +58 -58
- package/dist/use-restore-focus-BWO0z1lo.js +1672 -0
- package/dist/{useAsyncData-Dqw22wZQ.js → useAsyncData-DsZMvNXn.js} +1 -1
- package/dist/{useDateFormatter-D3Tkfzej.js → useDateFormatter-ChB4eyim.js} +2 -2
- package/dist/{useDeepCompareMemoize-D3gozcjJ.js → useDeepCompareMemoize-KMBBux8C.js} +1 -1
- package/dist/{useIframeCapabilities-6a_yDNXp.js → useIframeCapabilities-cMNQ80CK.js} +1 -1
- package/dist/useLifecycle-BquH9ET-.js +112 -0
- package/dist/useTheme-B9C1JIy9.js +307 -0
- package/dist/utils-DXkj82sK.js +813 -0
- package/dist/{vega-component-Blvspl7v.js → vega-component-9NOEITbg.js} +55 -54
- package/dist/{vennDiagram-4TSXK5OY-DzCi-lRh.js → vennDiagram-4TSXK5OY-BfzXp4SH.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-CW48xa0n.js → wardleyDiagram-VM6X3IG4-CiqEE8eq.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-UPxGGI2B.js → xychartDiagram-S5SC5T6Z-BTIHCVOI.js} +1 -1
- package/package.json +1 -1
- package/src/components/editor/cell/__tests__/StagedAICell.test.tsx +1 -1
- package/src/components/editor/cell/code/cell-editor.tsx +13 -36
- package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +1 -1
- package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +11 -5
- package/src/components/editor/notebook-cell.tsx +0 -1
- package/src/components/scratchpad/scratchpad.test.tsx +3 -11
- package/src/components/scratchpad/scratchpad.tsx +24 -22
- package/src/components/scratchpad/state.ts +7 -0
- package/src/core/ai/__tests__/staged-cells.test.ts +6 -8
- package/src/core/ai/staged-cells.ts +4 -2
- package/src/core/cells/__tests__/cells.test.ts +44 -60
- package/src/core/cells/__tests__/document.test.ts +1 -139
- package/src/core/cells/__tests__/notebook-outline.test.ts +8 -6
- package/src/core/cells/actions.ts +39 -0
- package/src/core/cells/cells.ts +15 -35
- package/src/core/cells/document.ts +9 -84
- package/src/core/codemirror/__tests__/format.test.ts +0 -34
- package/src/core/codemirror/cells/extensions.ts +20 -18
- package/src/core/codemirror/cells/state.ts +0 -5
- package/src/core/codemirror/format.ts +1 -19
- package/src/core/codemirror/misc/__tests__/paste.test.ts +3 -1
- package/src/core/codemirror/misc/paste.ts +1 -1
- package/src/core/notebook/document/__tests__/codemirror.test.ts +172 -0
- package/src/stories/layout/vertical/one-column.stories.tsx +26 -18
- package/dist/html-to-image--E3uFbJ9.js +0 -3348
- package/dist/label-Qvw1E97W.js +0 -281
- package/dist/objectWithoutPropertiesLoose-BRDcTsfB.js +0 -23
- package/dist/select-2KOiReNY.js +0 -1423
- package/dist/toast-B5t9o6s_.js +0 -1848
- package/dist/use-restore-focus-BgDTd2tc.js +0 -6418
- package/dist/useLifecycle-CaZi4wTw.js +0 -172
- package/dist/useTheme-TzPgWXwB.js +0 -1114
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Make the notebook state follow the host document while cells live in
|
|
5
|
+
* both. Edits go the other way through the document model directly: the
|
|
6
|
+
* cell operations write it, and each cell editor is bound to its cell.
|
|
6
7
|
*
|
|
7
8
|
* TODO(MO-8028): Delete this module once components read cells from the
|
|
8
9
|
* document model and `notebookAtom` holds only runtime and editor state.
|
|
@@ -10,20 +11,15 @@
|
|
|
10
11
|
|
|
11
12
|
import { dequal as isEqual } from "dequal";
|
|
12
13
|
import type { createStore } from "jotai";
|
|
13
|
-
import { createRef
|
|
14
|
-
import type { CellHandle } from "@/components/editor/notebook-cell";
|
|
14
|
+
import { createRef } from "react";
|
|
15
15
|
import type {
|
|
16
|
-
DocumentEdit,
|
|
17
|
-
DocumentEditor,
|
|
18
16
|
DocumentModel,
|
|
19
17
|
EditableCell,
|
|
20
18
|
EditableDocument,
|
|
21
19
|
} from "@/core/notebook/document";
|
|
22
|
-
import type { DispatchedActionOf } from "@/utils/createReducer";
|
|
23
|
-
import { prepareCellCodeUpdate } from "../codemirror/language/cell-code";
|
|
24
20
|
import { store } from "../state/jotai";
|
|
25
21
|
import { activeDocumentModelAtom } from "../session/active-notebook";
|
|
26
|
-
import type { NotebookState
|
|
22
|
+
import type { NotebookState } from "./cells";
|
|
27
23
|
import type { CellId } from "./ids";
|
|
28
24
|
import { followColumnsAtom } from "./layout";
|
|
29
25
|
import {
|
|
@@ -34,16 +30,10 @@ import {
|
|
|
34
30
|
isEdited,
|
|
35
31
|
} from "./types";
|
|
36
32
|
|
|
37
|
-
type CellAction = DispatchedActionOf<ReducerCellActions>;
|
|
38
|
-
|
|
39
33
|
// Following the document ----------------------------------------------------
|
|
40
34
|
|
|
41
|
-
/** The cell's data
|
|
42
|
-
function followCell(
|
|
43
|
-
current: CellData,
|
|
44
|
-
cell: EditableCell,
|
|
45
|
-
handle: RefObject<CellHandle | null> | undefined,
|
|
46
|
-
): CellData {
|
|
35
|
+
/** The cell's data after the document's cell, the same object when nothing differs. */
|
|
36
|
+
function followCell(current: CellData, cell: EditableCell): CellData {
|
|
47
37
|
// Withheld source keeps the code this client has.
|
|
48
38
|
const code = cell.code ?? current.code;
|
|
49
39
|
const config = isEqual(current.config, cell.config)
|
|
@@ -59,13 +49,6 @@ function followCell(
|
|
|
59
49
|
if (code === current.code) {
|
|
60
50
|
return { ...current, name: cell.name, config };
|
|
61
51
|
}
|
|
62
|
-
// The editor takes the host's code; one that shows it already, as the
|
|
63
|
-
// editor this client types in does, is left alone.
|
|
64
|
-
const view = handle?.current?.editorViewOrNull;
|
|
65
|
-
const update = view && prepareCellCodeUpdate(view.state, code);
|
|
66
|
-
if (update) {
|
|
67
|
-
view.dispatch(update);
|
|
68
|
-
}
|
|
69
52
|
return {
|
|
70
53
|
...current,
|
|
71
54
|
code,
|
|
@@ -110,7 +93,7 @@ export function applyDocument(
|
|
|
110
93
|
code,
|
|
111
94
|
name: cell.name,
|
|
112
95
|
config: createCellConfig(cell.config),
|
|
113
|
-
edited: code
|
|
96
|
+
edited: isEdited(null, code),
|
|
114
97
|
}),
|
|
115
98
|
};
|
|
116
99
|
cellRuntime = {
|
|
@@ -120,7 +103,7 @@ export function applyDocument(
|
|
|
120
103
|
cellHandles = { ...cellHandles, [id]: createRef() };
|
|
121
104
|
continue;
|
|
122
105
|
}
|
|
123
|
-
const next = followCell(current, cell
|
|
106
|
+
const next = followCell(current, cell);
|
|
124
107
|
if (next !== current) {
|
|
125
108
|
cellData = { ...cellData, [id]: next };
|
|
126
109
|
}
|
|
@@ -176,64 +159,6 @@ export function followDocument({
|
|
|
176
159
|
follow();
|
|
177
160
|
}
|
|
178
161
|
|
|
179
|
-
// Editing the document ------------------------------------------------------
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* The edit that writes the code a reducer action changed, or null when none
|
|
183
|
-
* did. Order, creation, deletion, names and configs are edited on the model
|
|
184
|
-
* directly by their atoms.
|
|
185
|
-
*/
|
|
186
|
-
function diffNotebook(
|
|
187
|
-
prev: NotebookState,
|
|
188
|
-
next: NotebookState,
|
|
189
|
-
): DocumentEdit | null {
|
|
190
|
-
const steps: Array<(tx: DocumentEditor) => void> = [];
|
|
191
|
-
for (const id of next.cellIds) {
|
|
192
|
-
const cell = next.cellData[id];
|
|
193
|
-
const before = prev.cellData[id];
|
|
194
|
-
if (before !== undefined && cell !== before && before.code !== cell.code) {
|
|
195
|
-
steps.push((tx) => tx.setCode(id, cell.code));
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
if (steps.length === 0) {
|
|
199
|
-
return null;
|
|
200
|
-
}
|
|
201
|
-
return (tx) => {
|
|
202
|
-
for (const step of steps) {
|
|
203
|
-
step(tx);
|
|
204
|
-
}
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Middleware for the notebook reducer. Writes what an action changed about
|
|
210
|
-
* the cells and their order into the document model as one edit.
|
|
211
|
-
*
|
|
212
|
-
* TODO(MO-8028): Only code is left in `diffNotebook`. The middleware goes
|
|
213
|
-
* with it, once the editors bind to the model.
|
|
214
|
-
*/
|
|
215
|
-
export function documentEditMiddleware(
|
|
216
|
-
prev: NotebookState,
|
|
217
|
-
next: NotebookState,
|
|
218
|
-
action: CellAction,
|
|
219
|
-
): void {
|
|
220
|
-
// The document replaces the notebook's cells, so following it is no edit.
|
|
221
|
-
if (action.type === "reconcileDocument") {
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
if (prev.cellIds === next.cellIds && prev.cellData === next.cellData) {
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
const model = store.get(activeDocumentModelAtom);
|
|
228
|
-
if (model === null) {
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
const edit = diffNotebook(prev, next);
|
|
232
|
-
if (edit !== null) {
|
|
233
|
-
store.set(model.editAtom, edit);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
162
|
// Commands that read the document on the host -------------------------------
|
|
238
163
|
|
|
239
164
|
/**
|
|
@@ -17,7 +17,6 @@ import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys";
|
|
|
17
17
|
import { requestClientAtom } from "@/core/network/requests";
|
|
18
18
|
import type { MarimoConfig } from "@/core/network/types";
|
|
19
19
|
import { store } from "@/core/state/jotai";
|
|
20
|
-
import { type CodemirrorCellActions, cellActionsState } from "../cells/state";
|
|
21
20
|
import { cellIdState } from "../config/extension";
|
|
22
21
|
import { formatAll, formatEditorViews, formatSQL } from "../format";
|
|
23
22
|
import {
|
|
@@ -44,7 +43,6 @@ vi.mock("@/core/config/config", () => ({
|
|
|
44
43
|
}),
|
|
45
44
|
}));
|
|
46
45
|
|
|
47
|
-
const updateCellCode = vi.fn();
|
|
48
46
|
const createdViews: EditorView[] = [];
|
|
49
47
|
|
|
50
48
|
function createEditor(content: string, cellId: CellId) {
|
|
@@ -65,9 +63,6 @@ function createEditor(content: string, cellId: CellId) {
|
|
|
65
63
|
lspConfig: {},
|
|
66
64
|
}),
|
|
67
65
|
cellIdState.of(cellId),
|
|
68
|
-
cellActionsState.of({
|
|
69
|
-
updateCellCode,
|
|
70
|
-
} as unknown as CodemirrorCellActions),
|
|
71
66
|
],
|
|
72
67
|
});
|
|
73
68
|
|
|
@@ -85,7 +80,6 @@ const mockConfig = {
|
|
|
85
80
|
} as MarimoConfig;
|
|
86
81
|
|
|
87
82
|
beforeEach(() => {
|
|
88
|
-
updateCellCode.mockClear();
|
|
89
83
|
vi.clearAllMocks();
|
|
90
84
|
// Set the mock request client in the atom
|
|
91
85
|
store.set(requestClientAtom, mockRequestClient);
|
|
@@ -131,16 +125,6 @@ describe("format", () => {
|
|
|
131
125
|
|
|
132
126
|
expect(views[cid1].state.doc.toString()).toBe(formattedCode1);
|
|
133
127
|
expect(views[cid2].state.doc.toString()).toBe(formattedCode2);
|
|
134
|
-
expect(updateCellCode).toHaveBeenCalledWith({
|
|
135
|
-
cellId: cid1,
|
|
136
|
-
code: formattedCode1,
|
|
137
|
-
formattingChange: true,
|
|
138
|
-
});
|
|
139
|
-
expect(updateCellCode).toHaveBeenCalledWith({
|
|
140
|
-
cellId: cid2,
|
|
141
|
-
code: formattedCode2,
|
|
142
|
-
formattingChange: true,
|
|
143
|
-
});
|
|
144
128
|
});
|
|
145
129
|
|
|
146
130
|
it("should not update editor if formatted code is same as original", async () => {
|
|
@@ -161,7 +145,6 @@ describe("format", () => {
|
|
|
161
145
|
await formatEditorViews(views);
|
|
162
146
|
|
|
163
147
|
expect(views[cid].state.doc.toString()).toBe(originalCode);
|
|
164
|
-
expect(updateCellCode).not.toHaveBeenCalled();
|
|
165
148
|
});
|
|
166
149
|
});
|
|
167
150
|
|
|
@@ -194,12 +177,6 @@ describe("format", () => {
|
|
|
194
177
|
},
|
|
195
178
|
lineLength: 88,
|
|
196
179
|
});
|
|
197
|
-
|
|
198
|
-
expect(updateCellCode).toHaveBeenCalledWith({
|
|
199
|
-
cellId: cid1,
|
|
200
|
-
code: "import numpy as np",
|
|
201
|
-
formattingChange: true,
|
|
202
|
-
});
|
|
203
180
|
});
|
|
204
181
|
});
|
|
205
182
|
|
|
@@ -220,11 +197,6 @@ describe("format", () => {
|
|
|
220
197
|
WHERE
|
|
221
198
|
id = 1"
|
|
222
199
|
`);
|
|
223
|
-
expect(updateCellCode).toHaveBeenCalledWith({
|
|
224
|
-
cellId: cid,
|
|
225
|
-
code: editor.state.doc.toString(),
|
|
226
|
-
formattingChange: true,
|
|
227
|
-
});
|
|
228
200
|
});
|
|
229
201
|
|
|
230
202
|
it("should not format if language adapter is not SQL", async () => {
|
|
@@ -238,7 +210,6 @@ describe("format", () => {
|
|
|
238
210
|
expect(editor.state.doc.toString()).toBe(
|
|
239
211
|
"SELECT * FROM table WHERE id = 1",
|
|
240
212
|
);
|
|
241
|
-
expect(updateCellCode).not.toHaveBeenCalled();
|
|
242
213
|
});
|
|
243
214
|
});
|
|
244
215
|
|
|
@@ -257,10 +228,5 @@ describe("format", () => {
|
|
|
257
228
|
WHERE
|
|
258
229
|
id = 1"
|
|
259
230
|
`);
|
|
260
|
-
expect(updateCellCode).toHaveBeenCalledWith({
|
|
261
|
-
cellId: cid,
|
|
262
|
-
code: editor.state.doc.toString(),
|
|
263
|
-
formattingChange: true,
|
|
264
|
-
});
|
|
265
231
|
});
|
|
266
232
|
});
|
|
@@ -23,7 +23,7 @@ import { createObservable } from "@/core/state/observable";
|
|
|
23
23
|
import { formatKeymapExtension } from "../extensions";
|
|
24
24
|
import { formattingChangeEffect } from "../format";
|
|
25
25
|
import { goToDefinitionAtCursorPosition } from "../go-to-definition/utils";
|
|
26
|
-
import {
|
|
26
|
+
import { getCellCode } from "../language/cell-code";
|
|
27
27
|
import { isAtEndOfEditor, isAtStartOfEditor } from "../utils";
|
|
28
28
|
import {
|
|
29
29
|
breakpointGutter,
|
|
@@ -323,26 +323,28 @@ function cellKeymaps({
|
|
|
323
323
|
*/
|
|
324
324
|
function cellCodeEditing(hotkeys: HotkeyProvider): Extension[] {
|
|
325
325
|
const onChangePlugin = EditorView.updateListener.of((update) => {
|
|
326
|
-
if (update.docChanged) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
326
|
+
if (!update.docChanged) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
const cellId = update.view.state.facet(cellIdState);
|
|
330
|
+
// The code itself reaches the document through the editor's binding.
|
|
331
|
+
// Formatting, including a language switch, rewrites the code without
|
|
332
|
+
// editing it, so the cell stays as run or as edited as it was.
|
|
333
|
+
const formatted = update.transactions.some((tr) =>
|
|
334
|
+
tr.effects.some((effect) => effect.is(formattingChangeEffect)),
|
|
335
|
+
);
|
|
336
|
+
if (formatted) {
|
|
337
|
+
update.view.state.facet(cellActionsState).codeFormatted({
|
|
336
338
|
cellId,
|
|
337
|
-
|
|
338
|
-
|
|
339
|
+
before: getCellCode(update.startState),
|
|
340
|
+
after: getCellCode(update.state),
|
|
339
341
|
});
|
|
342
|
+
}
|
|
340
343
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
}
|
|
344
|
+
// Clear pending cut state if this cell was marked for cut
|
|
345
|
+
const pendingCutCellIds = store.get(pendingCutCellIdsAtom);
|
|
346
|
+
if (pendingCutCellIds.has(cellId)) {
|
|
347
|
+
store.set(clearPendingCutAtom);
|
|
346
348
|
}
|
|
347
349
|
});
|
|
348
350
|
|
|
@@ -8,11 +8,6 @@ export interface CodemirrorCellActions extends CellActions {
|
|
|
8
8
|
toggleHideCode: () => boolean;
|
|
9
9
|
aiCellCompletion: () => boolean;
|
|
10
10
|
createManyBelow: (content: string[]) => void;
|
|
11
|
-
/**
|
|
12
|
-
* Create the setup cell with the given code, or append the code to the
|
|
13
|
-
* existing setup cell if one already exists.
|
|
14
|
-
*/
|
|
15
|
-
addOrAppendSetupCell: (code: string) => void;
|
|
16
11
|
onRun: () => void;
|
|
17
12
|
deleteCell: () => void;
|
|
18
13
|
afterToggleMarkdown: () => void;
|
|
@@ -13,8 +13,6 @@ import { notebookCellEditorViews } from "../cells/utils";
|
|
|
13
13
|
import { getResolvedMarimoConfig } from "../config/config";
|
|
14
14
|
import type { ConnectionName } from "../datasets/engines";
|
|
15
15
|
import { getRequestClient } from "../network/requests";
|
|
16
|
-
import { cellActionsState } from "./cells/state";
|
|
17
|
-
import { cellIdState } from "./config/extension";
|
|
18
16
|
import { languageAdapterState } from "./language/extension";
|
|
19
17
|
import { SCHEMA_CACHE } from "./language/languages/sql/completion-store";
|
|
20
18
|
import { isKnownDialect } from "./language/languages/sql/utils";
|
|
@@ -54,15 +52,9 @@ export async function formatEditorViews(views: Record<CellId, EditorView>) {
|
|
|
54
52
|
continue;
|
|
55
53
|
}
|
|
56
54
|
|
|
57
|
-
const actions = view.state.facet(cellActionsState);
|
|
58
|
-
actions.updateCellCode({
|
|
59
|
-
cellId,
|
|
60
|
-
code: formattedCode,
|
|
61
|
-
formattingChange: true,
|
|
62
|
-
});
|
|
63
55
|
const update = prepareCellCodeUpdate(view.state, formattedCode);
|
|
64
56
|
if (update) {
|
|
65
|
-
view.dispatch(update);
|
|
57
|
+
view.dispatch(update, { effects: formattingChangeEffect.of(true) });
|
|
66
58
|
}
|
|
67
59
|
}
|
|
68
60
|
}
|
|
@@ -111,16 +103,6 @@ export async function formatSQL(editor: EditorView, engine: ConnectionName) {
|
|
|
111
103
|
return;
|
|
112
104
|
}
|
|
113
105
|
|
|
114
|
-
// Update Python in the notebook state
|
|
115
|
-
const codeAsPython = languageAdapter.transformIn(formattedSQL)[0];
|
|
116
|
-
const actions = editor.state.facet(cellActionsState);
|
|
117
|
-
const cellId = editor.state.facet(cellIdState);
|
|
118
|
-
actions.updateCellCode({
|
|
119
|
-
cellId,
|
|
120
|
-
code: codeAsPython,
|
|
121
|
-
formattingChange: true,
|
|
122
|
-
});
|
|
123
|
-
|
|
124
106
|
// Update editor with formatted SQL
|
|
125
107
|
replaceEditorContent(editor, formattedSQL, {
|
|
126
108
|
effects: [formattingChangeEffect.of(true)],
|
|
@@ -443,7 +443,9 @@ def _():
|
|
|
443
443
|
view.dom.onpaste = (evt) => extension[0].domEventHandlers.paste(evt, view);
|
|
444
444
|
view.dom.dispatchEvent(event);
|
|
445
445
|
|
|
446
|
-
expect(addOrAppendSetupCell).toHaveBeenCalledWith(
|
|
446
|
+
expect(addOrAppendSetupCell).toHaveBeenCalledWith({
|
|
447
|
+
code: "import numpy as np",
|
|
448
|
+
});
|
|
447
449
|
expect(createManyBelow).toHaveBeenCalledWith(["x = 1"]);
|
|
448
450
|
});
|
|
449
451
|
});
|
|
@@ -23,7 +23,7 @@ export function pasteBundle(): Extension[] {
|
|
|
23
23
|
// The `with app.setup` block belongs in the special setup cell, not in
|
|
24
24
|
// an ordinary cell.
|
|
25
25
|
if (setup !== null) {
|
|
26
|
-
actions.addOrAppendSetupCell(setup);
|
|
26
|
+
actions.addOrAppendSetupCell({ code: setup });
|
|
27
27
|
}
|
|
28
28
|
if (cells.length > 0) {
|
|
29
29
|
actions.createManyBelow(cells);
|
|
@@ -22,6 +22,10 @@ import {
|
|
|
22
22
|
languageMetadataField,
|
|
23
23
|
setLanguageMetadata,
|
|
24
24
|
} from "../../../codemirror/language/metadata";
|
|
25
|
+
import {
|
|
26
|
+
editorShowsCellCode,
|
|
27
|
+
getCellCode,
|
|
28
|
+
} from "../../../codemirror/language/cell-code";
|
|
25
29
|
import { SCHEMA_CACHE } from "../../../codemirror/language/languages/sql/completion-store";
|
|
26
30
|
import type { Document } from "../../replica/resources/document";
|
|
27
31
|
import { cellCodeBinding } from "../codemirror";
|
|
@@ -62,6 +66,7 @@ function languages(id: CellId) {
|
|
|
62
66
|
aiCellCompletion: () => false,
|
|
63
67
|
createManyBelow: vi.fn(),
|
|
64
68
|
addOrAppendSetupCell: vi.fn(),
|
|
69
|
+
setCellCode: vi.fn(),
|
|
65
70
|
createCell: vi.fn(),
|
|
66
71
|
moveToNextCell: vi.fn(),
|
|
67
72
|
addSetupCell: vi.fn(),
|
|
@@ -584,3 +589,170 @@ describe("cellCodeBinding before the cell has source", () => {
|
|
|
584
589
|
expect(view.state.field(languageAdapterState).type).toBe("markdown");
|
|
585
590
|
});
|
|
586
591
|
});
|
|
592
|
+
|
|
593
|
+
describe("cellCodeBinding under random interleavings", () => {
|
|
594
|
+
// A small deterministic generator, so a failure names the seed to replay.
|
|
595
|
+
function generator(seed: number) {
|
|
596
|
+
let state = seed >>> 0;
|
|
597
|
+
return () => {
|
|
598
|
+
state = (Math.imul(state, 1664525) + 1013904223) >>> 0;
|
|
599
|
+
return state / 2 ** 32;
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const TOKENS = ["x", " ", "\n", "= 1", "mo.md(", ")", '"""', "#", "y + 2"];
|
|
604
|
+
const REMOTE = [
|
|
605
|
+
"x = 2",
|
|
606
|
+
'mo.md(r"""hi""")',
|
|
607
|
+
"y = x + 1\nz = y",
|
|
608
|
+
"",
|
|
609
|
+
"# other",
|
|
610
|
+
];
|
|
611
|
+
|
|
612
|
+
async function stress(seed: number, adapters: boolean) {
|
|
613
|
+
const random = generator(seed);
|
|
614
|
+
const pick = <T>(items: readonly T[]) =>
|
|
615
|
+
items[Math.floor(random() * items.length)];
|
|
616
|
+
const { store, source, model, view, editor, code } = setup({
|
|
617
|
+
adapters,
|
|
618
|
+
initial: withCells(document(), { c1: cell("c1", "x = 1") }),
|
|
619
|
+
});
|
|
620
|
+
const other = editor();
|
|
621
|
+
await settle();
|
|
622
|
+
let composing = false;
|
|
623
|
+
Object.defineProperty(view, "composing", { get: () => composing });
|
|
624
|
+
let version = 1;
|
|
625
|
+
let hostCode = "x = 1";
|
|
626
|
+
const host = (next: string | null) => {
|
|
627
|
+
version += 1;
|
|
628
|
+
store.set(
|
|
629
|
+
source,
|
|
630
|
+
withCells(document(), {
|
|
631
|
+
c1:
|
|
632
|
+
next === null
|
|
633
|
+
? null
|
|
634
|
+
: cell("c1", next, { version, etag: `c1-${version}` }),
|
|
635
|
+
}),
|
|
636
|
+
);
|
|
637
|
+
};
|
|
638
|
+
const agrees = (state = view.state) => {
|
|
639
|
+
const current = code();
|
|
640
|
+
return typeof current !== "string" || editorShowsCellCode(state, current);
|
|
641
|
+
};
|
|
642
|
+
// Every write to the model is one local or host step. A binding that
|
|
643
|
+
// echoed its own pulls back as pushes would write many more.
|
|
644
|
+
let writes = 0;
|
|
645
|
+
let steps = 0;
|
|
646
|
+
// Checks inside the loop collect here, so one assertion reports them all.
|
|
647
|
+
const failures: string[] = [];
|
|
648
|
+
const check = (label: string, ok: boolean) => {
|
|
649
|
+
if (!ok) {
|
|
650
|
+
failures.push(label);
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
const unsubscribe = store.sub(model.documentAtom, () => {
|
|
654
|
+
writes += 1;
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
for (let step = 0; step < 200; step++) {
|
|
658
|
+
const roll = random();
|
|
659
|
+
steps += 1;
|
|
660
|
+
if (roll < 0.35) {
|
|
661
|
+
const length = view.state.doc.length;
|
|
662
|
+
const from = Math.floor(random() * (length + 1));
|
|
663
|
+
view.dispatch({
|
|
664
|
+
changes: { from, insert: pick(TOKENS) },
|
|
665
|
+
userEvent: "input.type",
|
|
666
|
+
});
|
|
667
|
+
// A local change reaches the model before the update returns.
|
|
668
|
+
check(`step ${step} after typing`, agrees());
|
|
669
|
+
} else if (roll < 0.45) {
|
|
670
|
+
const length = view.state.doc.length;
|
|
671
|
+
if (length > 0) {
|
|
672
|
+
const from = Math.floor(random() * length);
|
|
673
|
+
const to = Math.min(length, from + 1 + Math.floor(random() * 5));
|
|
674
|
+
view.dispatch({
|
|
675
|
+
changes: { from, to },
|
|
676
|
+
userEvent: "delete.backward",
|
|
677
|
+
});
|
|
678
|
+
check(`step ${step} after deleting`, agrees());
|
|
679
|
+
}
|
|
680
|
+
} else if (roll < 0.6) {
|
|
681
|
+
// The host confirms what this client wrote.
|
|
682
|
+
const current = code();
|
|
683
|
+
if (typeof current === "string") {
|
|
684
|
+
hostCode = current;
|
|
685
|
+
host(hostCode);
|
|
686
|
+
}
|
|
687
|
+
} else if (roll < 0.75) {
|
|
688
|
+
// Another client changes the cell.
|
|
689
|
+
hostCode = pick(REMOTE);
|
|
690
|
+
host(hostCode);
|
|
691
|
+
} else if (roll < 0.8) {
|
|
692
|
+
// The cell leaves the document and returns.
|
|
693
|
+
host(null);
|
|
694
|
+
await settle();
|
|
695
|
+
host(hostCode);
|
|
696
|
+
} else if (roll < 0.88) {
|
|
697
|
+
composing = !composing;
|
|
698
|
+
if (!composing) {
|
|
699
|
+
view.contentDOM.dispatchEvent(new Event("compositionend"));
|
|
700
|
+
}
|
|
701
|
+
} else if (roll < 0.94) {
|
|
702
|
+
other.dispatch({
|
|
703
|
+
changes: { from: other.state.doc.length, insert: pick(TOKENS) },
|
|
704
|
+
userEvent: "input.type",
|
|
705
|
+
});
|
|
706
|
+
} else if (roll < 0.97) {
|
|
707
|
+
// The host withholds the cell's source for a while.
|
|
708
|
+
version += 1;
|
|
709
|
+
store.set(
|
|
710
|
+
source,
|
|
711
|
+
withCells(document(), {
|
|
712
|
+
c1: cell("c1", "", { code: null, version, etag: `c1-${version}` }),
|
|
713
|
+
}),
|
|
714
|
+
);
|
|
715
|
+
} else {
|
|
716
|
+
// The document goes away and comes back.
|
|
717
|
+
store.set(source, null);
|
|
718
|
+
await settle();
|
|
719
|
+
host(hostCode);
|
|
720
|
+
}
|
|
721
|
+
if (random() < 0.5) {
|
|
722
|
+
await settle();
|
|
723
|
+
check(`step ${step} after settling`, composing || agrees());
|
|
724
|
+
check(`step ${step} other editor`, agrees(other.state));
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
unsubscribe();
|
|
728
|
+
check(`${writes} writes for ${steps} steps`, writes <= 2 * steps);
|
|
729
|
+
|
|
730
|
+
composing = false;
|
|
731
|
+
view.contentDOM.dispatchEvent(new Event("compositionend"));
|
|
732
|
+
host(hostCode);
|
|
733
|
+
await settle();
|
|
734
|
+
await settle();
|
|
735
|
+
const current = code();
|
|
736
|
+
check("cell present at the end", typeof current === "string");
|
|
737
|
+
check("editor agrees at the end", agrees());
|
|
738
|
+
check("other editor agrees at the end", agrees(other.state));
|
|
739
|
+
// Without adapters the agreement is plain text equality.
|
|
740
|
+
check(
|
|
741
|
+
"text equal at the end",
|
|
742
|
+
adapters || getCellCode(view.state) === current,
|
|
743
|
+
);
|
|
744
|
+
return failures;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
const seeds = Array.from({ length: 16 }, (_, i) => i + 1);
|
|
748
|
+
for (const seed of seeds) {
|
|
749
|
+
it(`keeps editor and model in agreement, seed ${seed}`, async () => {
|
|
750
|
+
expect(await stress(seed, false)).toEqual([]);
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
for (const seed of seeds) {
|
|
754
|
+
it(`keeps editor and model in agreement with language adapters, seed ${seed}`, async () => {
|
|
755
|
+
expect(await stress(seed, true)).toEqual([]);
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
});
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
import type { Meta } from "@storybook/react-vite";
|
|
4
4
|
import { createStore, Provider } from "jotai";
|
|
5
5
|
import { createRef } from "react";
|
|
6
|
+
import { mockDocumentModel } from "@/__mocks__/document";
|
|
6
7
|
import { cellId } from "@/__tests__/branded";
|
|
7
8
|
import { type NotebookState, notebookAtom } from "@/core/cells/cells";
|
|
8
9
|
import { createCellRuntimeState } from "@/core/cells/types";
|
|
9
10
|
import { defaultUserConfig, parseAppConfig } from "@/core/config/config-schema";
|
|
10
11
|
import { showCodeInRunModeAtom } from "@/core/meta/state";
|
|
12
|
+
import { DocumentProvider } from "@/core/notebook/document";
|
|
11
13
|
import { connectionAtom } from "@/core/network/connection";
|
|
12
14
|
import { requestClientAtom } from "@/core/network/requests";
|
|
13
15
|
import { resolveRequestClient } from "@/core/network/resolve";
|
|
@@ -105,18 +107,21 @@ const EditModeCodeShown = () => {
|
|
|
105
107
|
store.set(connectionAtom, { state: ConnectionState.OPEN });
|
|
106
108
|
store.set(requestClientAtom, resolveRequestClient());
|
|
107
109
|
store.set(showCodeInRunModeAtom, true);
|
|
110
|
+
const { model } = mockDocumentModel(store, null);
|
|
108
111
|
|
|
109
112
|
return (
|
|
110
113
|
<Provider store={store}>
|
|
111
|
-
<
|
|
112
|
-
<
|
|
113
|
-
<
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
114
|
+
<DocumentProvider model={model}>
|
|
115
|
+
<TooltipProvider>
|
|
116
|
+
<CellsRenderer appConfig={parseAppConfig({})} mode="edit">
|
|
117
|
+
<CellArray
|
|
118
|
+
mode="edit"
|
|
119
|
+
userConfig={defaultUserConfig()}
|
|
120
|
+
appConfig={parseAppConfig({})}
|
|
121
|
+
/>
|
|
122
|
+
</CellsRenderer>
|
|
123
|
+
</TooltipProvider>
|
|
124
|
+
</DocumentProvider>
|
|
120
125
|
</Provider>
|
|
121
126
|
);
|
|
122
127
|
};
|
|
@@ -130,18 +135,21 @@ const EditModeCodeHidden = () => {
|
|
|
130
135
|
store.set(connectionAtom, { state: ConnectionState.OPEN });
|
|
131
136
|
store.set(requestClientAtom, resolveRequestClient());
|
|
132
137
|
store.set(showCodeInRunModeAtom, true);
|
|
138
|
+
const { model } = mockDocumentModel(store, null);
|
|
133
139
|
|
|
134
140
|
return (
|
|
135
141
|
<Provider store={store}>
|
|
136
|
-
<
|
|
137
|
-
<
|
|
138
|
-
<
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
<DocumentProvider model={model}>
|
|
143
|
+
<TooltipProvider>
|
|
144
|
+
<CellsRenderer appConfig={parseAppConfig({})} mode="edit">
|
|
145
|
+
<CellArray
|
|
146
|
+
mode="edit"
|
|
147
|
+
userConfig={defaultUserConfig()}
|
|
148
|
+
appConfig={parseAppConfig({})}
|
|
149
|
+
/>
|
|
150
|
+
</CellsRenderer>
|
|
151
|
+
</TooltipProvider>
|
|
152
|
+
</DocumentProvider>
|
|
145
153
|
</Provider>
|
|
146
154
|
);
|
|
147
155
|
};
|