@marimo-team/islands 0.23.10-dev9 → 0.23.11-dev1
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-CyV83R2m.js → ConnectedDataExplorerComponent-Z3RP7Vmm.js} +31 -31
- package/dist/{ErrorBoundary-rULOrC_p.js → ErrorBoundary-DpbaKVv7.js} +1 -1
- package/dist/{any-language-editor-CiES2a2h.js → any-language-editor-CAgFD4Kd.js} +20 -20
- package/dist/assets/__vite-browser-external-eshhtsgZ.js +1 -0
- package/dist/assets/worker-CC0Oul9k.js +73 -0
- package/dist/{chat-ui-K84W5ecY.js → chat-ui-C4-GMW9y.js} +77 -77
- package/dist/{check-DTbrK0zt.js → check-BCaJeT-J.js} +1 -1
- package/dist/{chunk-5FQGJX7Z-BNjes6Yx.js → chunk-5FQGJX7Z-BbqSm5gU.js} +1227 -1222
- package/dist/{code-block-37QAKDTI-m92Yc8pv.js → code-block-37QAKDTI-O6IH6wAi.js} +1 -1
- package/dist/{code-visibility-v6ucEVUg.js → code-visibility-BDDI_K4x.js} +1943 -1223
- package/dist/{copy-5jQ_kGE1.js → copy-UqRYxiOg.js} +1 -1
- package/dist/dist-7QfXoMdB.js +5 -0
- package/dist/{dist-DgnE8F-r.js → dist-A2846XWO.js} +1 -1
- package/dist/dist-BEXXyZig.js +5 -0
- package/dist/{dist-B3pZ0Ab6.js → dist-BR_gyG9L.js} +3 -3
- package/dist/{dist-CcXxepx6.js → dist-BSAt6RhH.js} +27 -27
- package/dist/{dist-Bde4a2kU.js → dist-BY018Paw.js} +8 -8
- package/dist/dist-BYj57OV4.js +5 -0
- package/dist/{dist-CUCNs1ja.js → dist-BaoDKvdy.js} +2 -2
- package/dist/{dist-Cy1WxgBD.js → dist-Bf7SHuNp.js} +5 -5
- package/dist/{dist-Bz_sYWbr.js → dist-Bk75fBZA.js} +2 -2
- package/dist/dist-BlSvQzNr.js +5 -0
- package/dist/{dist-C5VC_yzu.js → dist-BzEzfugY.js} +1 -1
- package/dist/dist-CCBlxAgS.js +8 -0
- package/dist/dist-CIDTVIUf.js +5 -0
- package/dist/{dist-CLUtPrdy.js → dist-CIYBwstr.js} +1 -1
- package/dist/{dist-BotSqB48.js → dist-C_Y3oV3C.js} +12 -12
- package/dist/{dist-BTfv03uy.js → dist-CcWX6tmx.js} +2 -2
- package/dist/{dist-BhM8gdSO.js → dist-CoXAujgg.js} +4 -4
- package/dist/{dist-4j4c7bjm.js → dist-CpxNdDkw.js} +3 -3
- package/dist/dist-CqQyhAM8.js +8 -0
- package/dist/dist-CwRu2Xzh.js +5 -0
- package/dist/{dist-BcuoonNH.js → dist-CxJDU6Bh.js} +9 -9
- package/dist/{dist-DxvORzUR.js → dist-D-W5ny5a.js} +8 -8
- package/dist/dist-D8CDTVgf.js +6 -0
- package/dist/dist-D8DNB0nO.js +8 -0
- package/dist/dist-DL6N_q-A.js +5 -0
- package/dist/{dist-BbbIBDiQ.js → dist-DMjWuVs8.js} +1 -1
- package/dist/dist-DOFbNV_b.js +8 -0
- package/dist/dist-DPrYzMY0.js +6 -0
- package/dist/{dist-h2c8sZvT.js → dist-DZORgqKY.js} +1 -1
- package/dist/{dist-B3P2fFpz.js → dist-DZo4nSS0.js} +14 -14
- package/dist/{dist-D4CewLk6.js → dist-Dax--nl9.js} +1 -1
- package/dist/{dist-DRfcqpxJ.js → dist-DgGbNavJ.js} +2 -2
- package/dist/{dist-C1BYNeCR.js → dist-Dk6PV_d3.js} +10 -10
- package/dist/{dist-fQ0ViXGs.js → dist-Dv_Y15yk.js} +107 -107
- package/dist/{dist-Bfwsv11D.js → dist-DyyjKEYf.js} +2 -2
- package/dist/{dist-p2qyWijU.js → dist-GZXUmt0b.js} +2 -2
- package/dist/{dist-CLJWPTX2.js → dist-LTU8Hdvn.js} +3 -3
- package/dist/{dist-DqAWR3CS.js → dist-M9Vag9Y0.js} +20 -20
- package/dist/{dist-DNdhYsgW.js → dist-U4F-tbMs.js} +79 -62
- package/dist/{dist-RqXTaiir.js → dist-abid3KgM.js} +11 -11
- package/dist/dist-cdmMjgsn.js +5 -0
- package/dist/dist-hT4QzYX-.js +1247 -0
- package/dist/{dist-luvabDEB.js → dist-t9Kf7xqC.js} +2 -2
- package/dist/{error-banner-5bz0L9hS.js → error-banner-CJXYJ6Sb.js} +11 -6
- package/dist/esm-BaH2eg5-.js +1171 -0
- package/dist/{esm-Duie8iU-.js → esm-ga2Bf3O2.js} +43 -43
- package/dist/{extends-CkydH1Q5.js → extends-D_hDsj6R.js} +4 -4
- package/dist/files/wasm-intro.py +11 -8
- package/dist/{formats-DHxc-FdY.js → formats-C4wO47tk.js} +1 -1
- package/dist/{glide-data-editor-CgIxTzAP.js → glide-data-editor-BPkCPs7L.js} +255 -255
- package/dist/{html-to-image-B93KtAVY.js → html-to-image-BwfcJJxA.js} +2412 -2282
- package/dist/{input-_2sjvfne.js → input-OdWHkobi.js} +263 -245
- package/dist/{label-LWtdw5i8.js → label-CC4ytI1X.js} +1 -1
- package/dist/main.js +7630 -7558
- package/dist/{mermaid-4DMBBIKO-DIdL224_.js → mermaid-4DMBBIKO-Eg3D4FVz.js} +1 -1
- package/dist/{mermaid-lXOw5Py9.js → mermaid-BotvIKg9.js} +4 -4
- package/dist/{process-output-BOXi9fnS.js → process-output-DsloEDna.js} +3 -3
- package/dist/{reveal-component-gRcjQbNL.js → reveal-component-BOEk3vwx.js} +698 -620
- package/dist/{spec-B96zNUEA.js → spec-X7FwLJni.js} +4 -4
- package/dist/{strings-Bu3vlb6W.js → strings-J57tzLr3.js} +47 -46
- package/dist/style.css +1 -1
- package/dist/{toDate-x-WRDCH7.js → toDate-d8RCRrRd.js} +2 -2
- package/dist/{tooltip-C5FYOpQc.js → tooltip-DpcyNkQ2.js} +2 -2
- package/dist/{types-CVvp1fKr.js → types-ChtMFmZ2.js} +1 -1
- package/dist/{useAsyncData-iRgKDT5s.js → useAsyncData-PonK__yh.js} +1 -1
- package/dist/{useDateFormatter-BA4FCquG.js → useDateFormatter-QB-3MpYr.js} +2 -2
- package/dist/{useDeepCompareMemoize-CkQ57VS2.js → useDeepCompareMemoize-D3NGWke6.js} +1 -1
- package/dist/{useLifecycle-BBO9PIph.js → useLifecycle-00mO3OSS.js} +2 -2
- package/dist/{useTheme-DHIrRQOe.js → useTheme-DEhDzATN.js} +1 -1
- package/dist/{vega-component-Dq-SH463.js → vega-component-DGPUhbDs.js} +8 -8
- package/dist/{zod-CoBiJ5v4.js → zod-aLSua2NL.js} +24 -23
- package/package.json +4 -3
- package/src/__tests__/setup.ts +16 -0
- package/src/components/chat/chat-display.tsx +2 -2
- package/src/components/data-table/TableBottomBar.tsx +1 -15
- package/src/components/data-table/TableTopBar.tsx +8 -13
- package/src/components/data-table/__tests__/TableBottomBar.test.tsx +6 -12
- package/src/components/data-table/__tests__/column-visibility-dropdown.test.tsx +227 -0
- package/src/components/data-table/column-visibility-dropdown.tsx +204 -0
- package/src/components/data-table/data-table.tsx +1 -1
- package/src/components/data-table/filter-by-values-picker.tsx +39 -17
- package/src/components/data-table/filter-pills.tsx +1 -1
- package/src/components/datasources/__tests__/filter-empty.test.ts +183 -0
- package/src/components/datasources/datasources.tsx +92 -3
- package/src/components/editor/ai/ai-completion-editor.tsx +6 -5
- package/src/components/editor/cell/code/cell-editor.tsx +70 -32
- package/src/components/editor/cell/code/code-placeholder.css +18 -0
- package/src/components/editor/cell/code/code-placeholder.tsx +47 -0
- package/src/components/editor/cell/code/language-toggle.tsx +7 -1
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +97 -52
- package/src/components/editor/chrome/wrapper/lazy-panels.ts +91 -0
- package/src/components/editor/chrome/wrapper/sidebar.tsx +2 -0
- package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +9 -2
- package/src/components/editor/connections/storage/__tests__/as-code.test.ts +62 -27
- package/src/components/editor/connections/storage/add-storage-form.tsx +5 -1
- package/src/components/editor/connections/storage/as-code.ts +20 -6
- package/src/components/editor/documentation.css +35 -0
- package/src/components/editor/file-tree/file-explorer.tsx +8 -18
- package/src/components/editor/file-tree/tree-actions.tsx +46 -1
- package/src/components/editor/notebook-cell.tsx +3 -3
- package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +20 -0
- package/src/components/editor/renderers/slides-layout/types.ts +1 -0
- package/src/components/markdown/__tests__/markdown-renderer.test.tsx +43 -0
- package/src/components/markdown/markdown-renderer.tsx +24 -1
- package/src/components/slides/__tests__/minimap-actions.test.tsx +166 -0
- package/src/components/slides/__tests__/reveal-component.test.ts +425 -0
- package/src/components/slides/minimap.tsx +127 -10
- package/src/components/slides/reveal-component.tsx +287 -61
- package/src/components/slides/slide-cell-view.tsx +26 -2
- package/src/components/slides/slide-form.tsx +26 -4
- package/src/components/storage/__tests__/storage-inspector.test.ts +180 -0
- package/src/components/storage/storage-inspector.tsx +535 -81
- package/src/components/ui/combobox.tsx +51 -32
- package/src/components/ui/number-field.tsx +5 -1
- package/src/components/ui/reorderable-list.tsx +13 -0
- package/src/components/ui/select-core/__tests__/use-select-list.test.ts +294 -0
- package/src/components/ui/select-core/__tests__/utils.test.ts +222 -0
- package/src/components/ui/select-core/index.ts +16 -0
- package/src/components/ui/select-core/option-row.tsx +33 -0
- package/src/components/ui/select-core/render-slot.ts +20 -0
- package/src/components/ui/select-core/select-list.tsx +248 -0
- package/src/components/ui/select-core/types.ts +44 -0
- package/src/components/ui/select-core/use-select-list.ts +347 -0
- package/src/components/ui/select-core/utils.ts +121 -0
- package/src/components/ui/toast.tsx +1 -0
- package/src/core/ai/__tests__/strip-wrapping-backticks.test.ts +133 -0
- package/src/core/ai/stream-completion-text.ts +48 -0
- package/src/core/ai/strip-wrapping-backticks.ts +88 -0
- package/src/core/cells/__tests__/document-changes.test.ts +2 -2
- package/src/core/cells/__tests__/focus.test.ts +45 -2
- package/src/core/cells/focus.ts +1 -1
- package/src/core/cells/scrollCellIntoView.ts +4 -0
- package/src/core/cells/utils.ts +1 -1
- package/src/core/codemirror/__tests__/editor-mount-scheduler.test.ts +143 -0
- package/src/core/codemirror/ai/request.ts +2 -14
- package/src/core/codemirror/copilot/getCodes.ts +3 -1
- package/src/core/codemirror/editor-mount-scheduler.ts +101 -0
- package/src/core/codemirror/language/languages/python.ts +2 -0
- package/src/core/codemirror/language/languages/sql/utils.ts +3 -1
- package/src/core/codemirror/lsp/__tests__/markdown-renderer.test.ts +41 -0
- package/src/core/codemirror/lsp/markdown-renderer.ts +59 -0
- package/src/core/datasets/data-source-connections.ts +2 -0
- package/src/core/errors/errors.ts +1 -1
- package/src/core/storage/__tests__/state.test.ts +49 -0
- package/src/core/storage/__tests__/useStorageEntries.test.tsx +199 -3
- package/src/core/storage/state.ts +124 -13
- package/src/core/storage/types.ts +6 -0
- package/src/core/wasm/worker/bootstrap.ts +12 -4
- package/src/plugins/core/__test__/registerReactComponent.test.ts +96 -0
- package/src/plugins/core/registerReactComponent.tsx +42 -10
- package/src/plugins/impl/MultiselectPlugin.tsx +19 -142
- package/src/plugins/impl/SearchableSelect.tsx +16 -97
- package/src/plugins/impl/SliderPlugin.tsx +132 -18
- package/src/plugins/impl/__tests__/DropdownPlugin.test.tsx +5 -2
- package/src/plugins/impl/__tests__/MultiSelectPlugin.test.ts +1 -1
- package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +278 -8
- package/src/utils/__tests__/numbers.test.ts +20 -0
- package/src/utils/errors.ts +9 -0
- package/src/utils/lazy.ts +6 -1
- package/src/utils/numbers.ts +27 -0
- package/src/utils/schedule-task.ts +71 -0
- package/dist/assets/__vite-browser-external-Ci2ZQfXU.js +0 -1
- package/dist/assets/worker-ip3AI_sN.js +0 -73
- package/dist/dist-0Fif7jnk.js +0 -5
- package/dist/dist-B5h_9sHB.js +0 -6
- package/dist/dist-B9M6R5ye.js +0 -5
- package/dist/dist-BCt3tnck.js +0 -8
- package/dist/dist-BUIJwMwn.js +0 -8
- package/dist/dist-BpquMd3k.js +0 -5
- package/dist/dist-BzJsqYfz.js +0 -5
- package/dist/dist-CA5ELXAf.js +0 -6
- package/dist/dist-CLBRs6Uv.js +0 -5
- package/dist/dist-CStVCMbq.js +0 -5
- package/dist/dist-CZRIEY3Y.js +0 -8
- package/dist/dist-CuUHbFD0.js +0 -5
- package/dist/dist-DV7Iabxb.js +0 -8
- package/dist/dist-DhHh0jLg.js +0 -1247
- package/dist/dist-DuEeHMvL.js +0 -5
- package/dist/esm-CCuYCd3R.js +0 -1171
- package/src/plugins/impl/multiselectFilterFn.tsx +0 -22
- /package/src/components/{data-table → ui}/value-chips.tsx +0 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
const LANGS = ["python", "sql", "markdown"] as const;
|
|
4
|
+
|
|
5
|
+
interface StripOptions {
|
|
6
|
+
/**
|
|
7
|
+
* When true, the text is treated as a partial stream:
|
|
8
|
+
* - the opening fence is only stripped once we can be sure of its language
|
|
9
|
+
* identifier (e.g. "```py" is left untouched since it may still become
|
|
10
|
+
* "```python"), and
|
|
11
|
+
* - the closing fence is left in place, since it may not have arrived yet (or
|
|
12
|
+
* a trailing "```" may be part of the content).
|
|
13
|
+
*/
|
|
14
|
+
streaming?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Removes wrapping markdown code fences from a completion string.
|
|
19
|
+
*/
|
|
20
|
+
export function stripWrappingBackticks(
|
|
21
|
+
text: string,
|
|
22
|
+
opts: StripOptions = {},
|
|
23
|
+
): string {
|
|
24
|
+
const { streaming = false } = opts;
|
|
25
|
+
const leadingWhitespace = text.match(/^\s*/)?.[0] ?? "";
|
|
26
|
+
const rest = text.slice(leadingWhitespace.length);
|
|
27
|
+
|
|
28
|
+
let body: string | null = null;
|
|
29
|
+
let strippedOpening = false;
|
|
30
|
+
|
|
31
|
+
for (const lang of LANGS) {
|
|
32
|
+
if (rest.startsWith(`\`\`\`${lang}`)) {
|
|
33
|
+
strippedOpening = true;
|
|
34
|
+
body = rest.slice(3 + lang.length);
|
|
35
|
+
if (body.startsWith("\n")) {
|
|
36
|
+
body = body.slice(1);
|
|
37
|
+
}
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!strippedOpening && rest.startsWith("```")) {
|
|
43
|
+
const afterFence = rest.slice(3);
|
|
44
|
+
if (streaming && isPartialLanguageFence(afterFence)) {
|
|
45
|
+
return text;
|
|
46
|
+
}
|
|
47
|
+
strippedOpening = true;
|
|
48
|
+
body = afterFence;
|
|
49
|
+
if (body.startsWith("\n")) {
|
|
50
|
+
body = body.slice(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!strippedOpening || body === null) {
|
|
55
|
+
return text;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// While streaming, the closing fence may not have arrived yet, so leave the
|
|
59
|
+
// body as-is once the opening fence is removed.
|
|
60
|
+
if (streaming) {
|
|
61
|
+
return body;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const strippedEnd = body.trimEnd();
|
|
65
|
+
const trailingSpace = body.slice(strippedEnd.length);
|
|
66
|
+
|
|
67
|
+
if (strippedEnd.endsWith("\n```")) {
|
|
68
|
+
return strippedEnd.slice(0, -4) + trailingSpace;
|
|
69
|
+
}
|
|
70
|
+
if (strippedEnd.endsWith("```")) {
|
|
71
|
+
return strippedEnd.slice(0, -3) + trailingSpace;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return body;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Returns true if the text after an opening "```" has no terminating newline
|
|
79
|
+
* yet and could still become a known language identifier.
|
|
80
|
+
*/
|
|
81
|
+
function isPartialLanguageFence(afterFence: string): boolean {
|
|
82
|
+
// Once the first line is terminated, we already know it is not a known
|
|
83
|
+
// language fence (those are handled above), so treat it as a plain fence.
|
|
84
|
+
if (afterFence.includes("\n")) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return LANGS.some((lang) => lang.startsWith(afterFence));
|
|
88
|
+
}
|
|
@@ -430,7 +430,7 @@ describe("toDocumentChanges", () => {
|
|
|
430
430
|
const [a] = state.cellIds.inOrderIds;
|
|
431
431
|
|
|
432
432
|
// Position cursor at end of "line1" (position 5)
|
|
433
|
-
const view = state.cellHandles[a].current!.editorView
|
|
433
|
+
const view = state.cellHandles[a].current!.editorView!;
|
|
434
434
|
view.dispatch({ selection: { anchor: 5 } });
|
|
435
435
|
|
|
436
436
|
const { changes } = resolve(state, {
|
|
@@ -466,7 +466,7 @@ describe("toDocumentChanges", () => {
|
|
|
466
466
|
const [a] = state.cellIds.inOrderIds;
|
|
467
467
|
|
|
468
468
|
// Split the cell first
|
|
469
|
-
const view = state.cellHandles[a].current!.editorView
|
|
469
|
+
const view = state.cellHandles[a].current!.editorView!;
|
|
470
470
|
view.dispatch({ selection: { anchor: 5 } });
|
|
471
471
|
const snapshot = view.state.doc.toString();
|
|
472
472
|
state = dispatch(state, {
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
+
import type { EditorView } from "@codemirror/view";
|
|
4
|
+
import { createStore } from "jotai";
|
|
3
5
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
6
|
+
import { MockNotebook } from "@/__mocks__/notebook";
|
|
4
7
|
import { cellId } from "@/__tests__/branded";
|
|
8
|
+
import type { CellHandle } from "@/components/editor/notebook-cell";
|
|
9
|
+
import { notebookAtom } from "../cells";
|
|
5
10
|
import type { CellFocusState } from "../focus";
|
|
6
|
-
import { exportedForTesting } from "../focus";
|
|
11
|
+
import { exportedForTesting, lastFocusedCellAtom } from "../focus";
|
|
7
12
|
|
|
8
|
-
const { initialState, reducer, createActions } =
|
|
13
|
+
const { initialState, reducer, createActions, cellFocusAtom } =
|
|
14
|
+
exportedForTesting;
|
|
9
15
|
|
|
10
16
|
const CellIds = {
|
|
11
17
|
a: cellId("a"),
|
|
@@ -270,3 +276,40 @@ describe("cell focus reducer", () => {
|
|
|
270
276
|
});
|
|
271
277
|
});
|
|
272
278
|
});
|
|
279
|
+
|
|
280
|
+
describe("lastFocusedCellAtom", () => {
|
|
281
|
+
it("returns null getEditorView when CodeMirror is not mounted", () => {
|
|
282
|
+
const store = createStore();
|
|
283
|
+
const markdownCellId = cellId("markdown");
|
|
284
|
+
|
|
285
|
+
const unmountedEditorHandle: CellHandle = {
|
|
286
|
+
get editorView(): EditorView {
|
|
287
|
+
throw new ReferenceError("Attempting to dereference null object.");
|
|
288
|
+
},
|
|
289
|
+
editorViewOrNull: null,
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const notebook = MockNotebook.notebookState({
|
|
293
|
+
cellData: {
|
|
294
|
+
[markdownCellId]: {
|
|
295
|
+
config: { hide_code: true },
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
notebook.cellHandles[markdownCellId] = {
|
|
300
|
+
current: unmountedEditorHandle,
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
store.set(notebookAtom, notebook);
|
|
304
|
+
store.set(cellFocusAtom, {
|
|
305
|
+
focusedCellId: markdownCellId,
|
|
306
|
+
lastFocusedCellId: markdownCellId,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const lastFocusedCell = store.get(lastFocusedCellAtom);
|
|
310
|
+
|
|
311
|
+
expect(lastFocusedCell).not.toBeNull();
|
|
312
|
+
expect(lastFocusedCell?.cellId).toBe(markdownCellId);
|
|
313
|
+
expect(lastFocusedCell?.getEditorView()).toBeNull();
|
|
314
|
+
});
|
|
315
|
+
});
|
package/src/core/cells/focus.ts
CHANGED
|
@@ -106,7 +106,7 @@ function cellFocusDetails(cellId: CellId, notebookState: NotebookState) {
|
|
|
106
106
|
if (!data || !handle) {
|
|
107
107
|
return null;
|
|
108
108
|
}
|
|
109
|
-
const getEditorView = () => handle.
|
|
109
|
+
const getEditorView = () => handle.editorViewOrNull;
|
|
110
110
|
|
|
111
111
|
return {
|
|
112
112
|
cellId,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import type { CellHandle } from "@/components/editor/notebook-cell";
|
|
8
8
|
import { retryWithTimeout } from "@/utils/timeout";
|
|
9
9
|
import { Logger } from "../../utils/Logger";
|
|
10
|
+
import { editorMountScheduler } from "../codemirror/editor-mount-scheduler";
|
|
10
11
|
import { scrollActiveLineIntoView } from "../codemirror/extensions";
|
|
11
12
|
import { goToVariableDefinition } from "../codemirror/go-to-definition/commands";
|
|
12
13
|
import { getCellEditorView } from "./cells";
|
|
@@ -48,6 +49,9 @@ export function focusAndScrollCellIntoView({
|
|
|
48
49
|
// https://github.com/marimo-team/marimo/issues/2940
|
|
49
50
|
tryFocus(element);
|
|
50
51
|
} else {
|
|
52
|
+
// Build this cell's editor now if it is still queued, so focus can land in
|
|
53
|
+
// it instead of the placeholder.
|
|
54
|
+
editorMountScheduler.promote(cellId);
|
|
51
55
|
const editor = cell.current?.editorView;
|
|
52
56
|
if (!editor) {
|
|
53
57
|
Logger.warn("scrollCellIntoView: editor not found", cellId);
|
package/src/core/cells/utils.ts
CHANGED
|
@@ -29,7 +29,7 @@ export function notebookCells(state: NotebookState) {
|
|
|
29
29
|
export function notebookCellEditorViews({ cellHandles }: NotebookState) {
|
|
30
30
|
const views: Record<CellId, EditorView> = {};
|
|
31
31
|
for (const [cell, ref] of Objects.entries(cellHandles)) {
|
|
32
|
-
if (!ref.current) {
|
|
32
|
+
if (!ref.current?.editorView) {
|
|
33
33
|
continue;
|
|
34
34
|
}
|
|
35
35
|
views[cell] = ref.current.editorView;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { createEditorMountScheduler } from "../editor-mount-scheduler";
|
|
4
|
+
|
|
5
|
+
function makeManualSchedule() {
|
|
6
|
+
let pending: (() => void) | null = null;
|
|
7
|
+
const schedule = (cb: () => void) => {
|
|
8
|
+
pending = cb;
|
|
9
|
+
};
|
|
10
|
+
const flush = () => {
|
|
11
|
+
const cb = pending;
|
|
12
|
+
pending = null;
|
|
13
|
+
cb?.();
|
|
14
|
+
};
|
|
15
|
+
return { schedule, flush, hasPending: () => pending !== null };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe("createEditorMountScheduler", () => {
|
|
19
|
+
it("builds one queued editor per tick in FIFO order, rescheduling between", () => {
|
|
20
|
+
const { schedule, flush, hasPending } = makeManualSchedule();
|
|
21
|
+
const built: string[] = [];
|
|
22
|
+
const s = createEditorMountScheduler(schedule);
|
|
23
|
+
s.request("a", () => built.push("a"));
|
|
24
|
+
s.request("b", () => built.push("b"));
|
|
25
|
+
flush();
|
|
26
|
+
expect(built).toEqual(["a"]);
|
|
27
|
+
expect(hasPending()).toBe(true);
|
|
28
|
+
flush();
|
|
29
|
+
expect(built).toEqual(["a", "b"]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("stops rescheduling once the queue is empty", () => {
|
|
33
|
+
const { schedule, flush, hasPending } = makeManualSchedule();
|
|
34
|
+
const built: string[] = [];
|
|
35
|
+
const s = createEditorMountScheduler(schedule);
|
|
36
|
+
s.request("a", () => built.push("a"));
|
|
37
|
+
flush();
|
|
38
|
+
expect(built).toEqual(["a"]);
|
|
39
|
+
expect(hasPending()).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("promote builds immediately and removes from the queue", () => {
|
|
43
|
+
const { schedule, flush } = makeManualSchedule();
|
|
44
|
+
const built: string[] = [];
|
|
45
|
+
const s = createEditorMountScheduler(schedule);
|
|
46
|
+
s.request("a", () => built.push("a"));
|
|
47
|
+
s.request("b", () => built.push("b"));
|
|
48
|
+
s.promote("b");
|
|
49
|
+
expect(built).toEqual(["b"]);
|
|
50
|
+
flush();
|
|
51
|
+
expect(built).toEqual(["b", "a"]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("cancel prevents a queued build", () => {
|
|
55
|
+
const { schedule, flush } = makeManualSchedule();
|
|
56
|
+
const built: string[] = [];
|
|
57
|
+
const s = createEditorMountScheduler(schedule);
|
|
58
|
+
s.request("a", () => built.push("a"));
|
|
59
|
+
s.cancel("a");
|
|
60
|
+
flush();
|
|
61
|
+
expect(built).toEqual([]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("builds prioritized cells before earlier-queued ones", () => {
|
|
65
|
+
const { schedule, flush } = makeManualSchedule();
|
|
66
|
+
const built: string[] = [];
|
|
67
|
+
const s = createEditorMountScheduler(schedule);
|
|
68
|
+
s.request("a", () => built.push("a"));
|
|
69
|
+
s.request("b", () => built.push("b"));
|
|
70
|
+
s.request("c", () => built.push("c"));
|
|
71
|
+
s.prioritize("c");
|
|
72
|
+
flush();
|
|
73
|
+
flush();
|
|
74
|
+
flush();
|
|
75
|
+
expect(built).toEqual(["c", "a", "b"]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("builds multiple prioritized cells in prioritization order, then FIFO", () => {
|
|
79
|
+
const { schedule, flush } = makeManualSchedule();
|
|
80
|
+
const built: string[] = [];
|
|
81
|
+
const s = createEditorMountScheduler(schedule);
|
|
82
|
+
s.request("a", () => built.push("a"));
|
|
83
|
+
s.request("b", () => built.push("b"));
|
|
84
|
+
s.request("c", () => built.push("c"));
|
|
85
|
+
s.request("d", () => built.push("d"));
|
|
86
|
+
s.prioritize("d");
|
|
87
|
+
s.prioritize("b");
|
|
88
|
+
flush();
|
|
89
|
+
flush();
|
|
90
|
+
flush();
|
|
91
|
+
flush();
|
|
92
|
+
expect(built).toEqual(["d", "b", "a", "c"]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("ignores prioritize for a cell that is not queued", () => {
|
|
96
|
+
const { schedule, flush } = makeManualSchedule();
|
|
97
|
+
const built: string[] = [];
|
|
98
|
+
const s = createEditorMountScheduler(schedule);
|
|
99
|
+
s.request("a", () => built.push("a"));
|
|
100
|
+
s.prioritize("not-queued");
|
|
101
|
+
flush();
|
|
102
|
+
expect(built).toEqual(["a"]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("deprioritize reverts a cell to FIFO order without dropping it", () => {
|
|
106
|
+
const { schedule, flush } = makeManualSchedule();
|
|
107
|
+
const built: string[] = [];
|
|
108
|
+
const s = createEditorMountScheduler(schedule);
|
|
109
|
+
s.request("a", () => built.push("a"));
|
|
110
|
+
s.request("b", () => built.push("b"));
|
|
111
|
+
s.prioritize("b");
|
|
112
|
+
s.deprioritize("b");
|
|
113
|
+
flush();
|
|
114
|
+
flush();
|
|
115
|
+
expect(built).toEqual(["a", "b"]);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("keeps draining the queue after a build throws", () => {
|
|
119
|
+
const { schedule, flush } = makeManualSchedule();
|
|
120
|
+
const built: string[] = [];
|
|
121
|
+
const s = createEditorMountScheduler(schedule);
|
|
122
|
+
s.request("a", () => {
|
|
123
|
+
throw new Error("boom");
|
|
124
|
+
});
|
|
125
|
+
s.request("b", () => built.push("b"));
|
|
126
|
+
expect(() => flush()).toThrow("boom");
|
|
127
|
+
flush();
|
|
128
|
+
expect(built).toEqual(["b"]);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("cancel removes a prioritized cell", () => {
|
|
132
|
+
const { schedule, flush } = makeManualSchedule();
|
|
133
|
+
const built: string[] = [];
|
|
134
|
+
const s = createEditorMountScheduler(schedule);
|
|
135
|
+
s.request("a", () => built.push("a"));
|
|
136
|
+
s.request("b", () => built.push("b"));
|
|
137
|
+
s.prioritize("b");
|
|
138
|
+
s.cancel("b");
|
|
139
|
+
flush();
|
|
140
|
+
flush();
|
|
141
|
+
expect(built).toEqual(["a"]);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { waitForConnectionOpen } from "@/core/network/connection";
|
|
4
4
|
import type { AiCompletionRequest } from "@/core/network/types";
|
|
5
|
+
import { streamCompletionText } from "@/core/ai/stream-completion-text";
|
|
5
6
|
import { getRuntimeManager } from "@/core/runtime/config";
|
|
6
7
|
import type { LanguageAdapterType } from "../language/types";
|
|
7
8
|
|
|
@@ -47,20 +48,7 @@ ${opts.codeAfter}
|
|
|
47
48
|
|
|
48
49
|
const firstLineIndent = opts.selection.match(/^\s*/)?.[0] || "";
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
if (!reader) {
|
|
52
|
-
throw new Error("Failed to get response reader");
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let result = "";
|
|
56
|
-
// oxlint-disable-next-line no-constant-condition
|
|
57
|
-
while (true) {
|
|
58
|
-
const { done, value } = await reader.read();
|
|
59
|
-
if (done) {
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
result += new TextDecoder().decode(value);
|
|
63
|
-
}
|
|
51
|
+
let result = await streamCompletionText(response);
|
|
64
52
|
|
|
65
53
|
// Add back the indent if it was stripped, which can happen with
|
|
66
54
|
// LLM responses
|
|
@@ -47,7 +47,9 @@ const notebookCellCodes = atom((get) => {
|
|
|
47
47
|
const handle = notebook.cellHandles[id];
|
|
48
48
|
return [
|
|
49
49
|
id,
|
|
50
|
-
handle?.current
|
|
50
|
+
handle?.current?.editorView
|
|
51
|
+
? getEditorCodeAsPython(handle.current.editorView)
|
|
52
|
+
: "",
|
|
51
53
|
];
|
|
52
54
|
}),
|
|
53
55
|
);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import {
|
|
3
|
+
scheduleTask as defaultScheduleTask,
|
|
4
|
+
type ScheduleTaskFn,
|
|
5
|
+
} from "@/utils/schedule-task";
|
|
6
|
+
|
|
7
|
+
type BuildFn = () => void;
|
|
8
|
+
|
|
9
|
+
export interface EditorMountScheduler {
|
|
10
|
+
/** Queue a cell's editor build to run on a future task. */
|
|
11
|
+
request(cellId: string, build: BuildFn): void;
|
|
12
|
+
/** Build a queued cell's editor synchronously, right now (e.g. on focus). */
|
|
13
|
+
promote(cellId: string): void;
|
|
14
|
+
/**
|
|
15
|
+
* Mark a queued cell to build ahead of un-prioritized ones, keeping the
|
|
16
|
+
* one-build-per-task pacing (e.g. when the cell scrolls into view).
|
|
17
|
+
*/
|
|
18
|
+
prioritize(cellId: string): void;
|
|
19
|
+
/** Drop a cell's prioritized mark, reverting it to plain FIFO order. */
|
|
20
|
+
deprioritize(cellId: string): void;
|
|
21
|
+
/** Remove a cell's queued build entirely. */
|
|
22
|
+
cancel(cellId: string): void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Drains a FIFO queue of editor builds, one per macrotask, so constructing many
|
|
27
|
+
* CodeMirror instances never blocks the main thread in a single long task. The
|
|
28
|
+
* scheduler yields between every build to keep the page responsive. Cells can
|
|
29
|
+
* be prioritized (e.g. when they scroll into view) to jump ahead of the queue
|
|
30
|
+
* without breaking the one-build-per-macrotask pacing.
|
|
31
|
+
*/
|
|
32
|
+
export function createEditorMountScheduler(
|
|
33
|
+
scheduleTask: ScheduleTaskFn = defaultScheduleTask,
|
|
34
|
+
): EditorMountScheduler {
|
|
35
|
+
const queue = new Map<string, BuildFn>();
|
|
36
|
+
const priority = new Set<string>();
|
|
37
|
+
let isProcessingScheduled = false;
|
|
38
|
+
|
|
39
|
+
function scheduleProcessing(): void {
|
|
40
|
+
if (isProcessingScheduled || queue.size === 0) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
isProcessingScheduled = true;
|
|
44
|
+
scheduleTask(processNext);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Prioritized cells first, in the order they were prioritized; then FIFO.
|
|
48
|
+
function nextCellId(): string | undefined {
|
|
49
|
+
for (const cellId of priority) {
|
|
50
|
+
if (queue.has(cellId)) {
|
|
51
|
+
return cellId;
|
|
52
|
+
}
|
|
53
|
+
priority.delete(cellId);
|
|
54
|
+
}
|
|
55
|
+
return queue.keys().next().value;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function processNext(): void {
|
|
59
|
+
isProcessingScheduled = false;
|
|
60
|
+
try {
|
|
61
|
+
const cellId = nextCellId();
|
|
62
|
+
if (cellId !== undefined) {
|
|
63
|
+
const build = queue.get(cellId);
|
|
64
|
+
queue.delete(cellId);
|
|
65
|
+
priority.delete(cellId);
|
|
66
|
+
build?.();
|
|
67
|
+
}
|
|
68
|
+
} finally {
|
|
69
|
+
scheduleProcessing();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
request(cellId, build) {
|
|
75
|
+
queue.set(cellId, build);
|
|
76
|
+
scheduleProcessing();
|
|
77
|
+
},
|
|
78
|
+
promote(cellId) {
|
|
79
|
+
const build = queue.get(cellId);
|
|
80
|
+
if (build) {
|
|
81
|
+
queue.delete(cellId);
|
|
82
|
+
priority.delete(cellId);
|
|
83
|
+
build();
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
prioritize(cellId) {
|
|
87
|
+
if (queue.has(cellId)) {
|
|
88
|
+
priority.add(cellId);
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
deprioritize(cellId) {
|
|
92
|
+
priority.delete(cellId);
|
|
93
|
+
},
|
|
94
|
+
cancel(cellId) {
|
|
95
|
+
queue.delete(cellId);
|
|
96
|
+
priority.delete(cellId);
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const editorMountScheduler = createEditorMountScheduler();
|
|
@@ -33,6 +33,7 @@ import { cellActionsState } from "../../cells/state";
|
|
|
33
33
|
import { pythonCompletionSource } from "../../completion/completer";
|
|
34
34
|
import type { PlaceholderType } from "../../config/types";
|
|
35
35
|
import { FederatedLanguageServerClient } from "../../lsp/federated-lsp";
|
|
36
|
+
import { createLspMarkdownRenderer } from "../../lsp/markdown-renderer";
|
|
36
37
|
import { NotebookLanguageServerClient } from "../../lsp/notebook-lsp";
|
|
37
38
|
import { createTransport } from "../../lsp/transports";
|
|
38
39
|
import { CellDocumentUri, type ILanguageServerClient } from "../../lsp/types";
|
|
@@ -342,6 +343,7 @@ export class PythonLanguageAdapter implements LanguageAdapter<{}> {
|
|
|
342
343
|
client: client as unknown as LanguageServerClient,
|
|
343
344
|
languageId: "python",
|
|
344
345
|
allowHTMLContent: true,
|
|
346
|
+
markdownRenderer: createLspMarkdownRenderer(),
|
|
345
347
|
useSnippetOnCompletion: true,
|
|
346
348
|
hoverConfig: hoverOptions,
|
|
347
349
|
completionConfig: autocompleteOptions,
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from "@codemirror/lang-sql";
|
|
15
15
|
import {
|
|
16
16
|
BigQueryDialect,
|
|
17
|
+
DremioDialect,
|
|
17
18
|
DuckDBDialect,
|
|
18
19
|
} from "@marimo-team/codemirror-sql/dialects";
|
|
19
20
|
import type { DataSourceConnection } from "@/core/kernel/messages";
|
|
@@ -97,6 +98,8 @@ export function guessDialect(
|
|
|
97
98
|
return PLSQL;
|
|
98
99
|
case "bigquery":
|
|
99
100
|
return BigQueryDialect;
|
|
101
|
+
case "dremio":
|
|
102
|
+
return DremioDialect;
|
|
100
103
|
case "timescaledb":
|
|
101
104
|
return PostgreSQL; // TimescaleDB is a PostgreSQL dialect
|
|
102
105
|
case "awsathena":
|
|
@@ -116,7 +119,6 @@ export function guessDialect(
|
|
|
116
119
|
case "spark":
|
|
117
120
|
case "databricks":
|
|
118
121
|
case "datafusion":
|
|
119
|
-
case "dremio":
|
|
120
122
|
Logger.debug("Unsupported dialect", { dialect });
|
|
121
123
|
return ModifiedStandardSQL;
|
|
122
124
|
default:
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { createLspMarkdownRenderer } from "../markdown-renderer";
|
|
4
|
+
|
|
5
|
+
describe("createLspMarkdownRenderer", () => {
|
|
6
|
+
it("syntax-highlights python code blocks with tok-* span classes", () => {
|
|
7
|
+
const render = createLspMarkdownRenderer();
|
|
8
|
+
const result = render("```python\ndef foo():\n pass\n```");
|
|
9
|
+
expect(result).toContain('class="language-python"');
|
|
10
|
+
expect(result).toContain("tok-keyword");
|
|
11
|
+
expect(result).toContain("<pre><code");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("does not highlight non-python code blocks", () => {
|
|
15
|
+
const render = createLspMarkdownRenderer();
|
|
16
|
+
const result = render("```bash\necho hello\n```");
|
|
17
|
+
expect(result).not.toContain("tok-keyword");
|
|
18
|
+
expect(result).toContain("<code");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("renders empty python code blocks as empty string", () => {
|
|
22
|
+
const render = createLspMarkdownRenderer();
|
|
23
|
+
const result = render("```python\n \n```");
|
|
24
|
+
expect(result).toBe("");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("renders markdown prose unchanged", () => {
|
|
28
|
+
const render = createLspMarkdownRenderer();
|
|
29
|
+
const result = render("## Examples\n\nSome text.");
|
|
30
|
+
expect(result).toContain("<h2");
|
|
31
|
+
expect(result).toContain("Examples");
|
|
32
|
+
expect(result).toContain("Some text.");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("escapes HTML in highlighted code", () => {
|
|
36
|
+
const render = createLspMarkdownRenderer();
|
|
37
|
+
const result = render('```python\nx = "<div>"\n```');
|
|
38
|
+
expect(result).not.toContain("<div>");
|
|
39
|
+
expect(result).toContain("<div>");
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import { classHighlighter, highlightCode } from "@lezer/highlight";
|
|
3
|
+
import { parser as pythonParser } from "@lezer/python";
|
|
4
|
+
import { marked } from "marked";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Syntax-highlight a Python code string using the lezer Python parser.
|
|
8
|
+
* Returns an HTML string with tok-* span classes for styling.
|
|
9
|
+
*/
|
|
10
|
+
function highlightPython(code: string): string {
|
|
11
|
+
const tree = pythonParser.parse(code);
|
|
12
|
+
let html = "";
|
|
13
|
+
highlightCode(
|
|
14
|
+
code,
|
|
15
|
+
tree,
|
|
16
|
+
classHighlighter,
|
|
17
|
+
(text, classes) => {
|
|
18
|
+
const escaped = text
|
|
19
|
+
.replace(/&/g, "&")
|
|
20
|
+
.replace(/</g, "<")
|
|
21
|
+
.replace(/>/g, ">");
|
|
22
|
+
html += classes ? `<span class="${classes}">${escaped}</span>` : escaped;
|
|
23
|
+
},
|
|
24
|
+
() => {
|
|
25
|
+
html += "\n";
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
return html;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A markdown renderer for LSP hover tooltips that adds syntax highlighting
|
|
33
|
+
* to Python code blocks using the lezer Python parser and classHighlighter.
|
|
34
|
+
* The tok-* CSS classes are defined in documentation.css.
|
|
35
|
+
*/
|
|
36
|
+
export function createLspMarkdownRenderer(): (markdown: string) => string {
|
|
37
|
+
const renderer = new marked.Renderer();
|
|
38
|
+
const prevCode = renderer.code.bind(renderer);
|
|
39
|
+
|
|
40
|
+
renderer.code = (token) => {
|
|
41
|
+
const { text, lang } = token;
|
|
42
|
+
if (!text.trim()) {
|
|
43
|
+
return "";
|
|
44
|
+
}
|
|
45
|
+
if (lang === "python" || lang === "py") {
|
|
46
|
+
const highlighted = highlightPython(text);
|
|
47
|
+
return `<pre><code class="language-python">${highlighted}</code></pre>\n`;
|
|
48
|
+
}
|
|
49
|
+
return prevCode(token);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return (markdown: string): string =>
|
|
53
|
+
marked(markdown, {
|
|
54
|
+
async: false,
|
|
55
|
+
gfm: true,
|
|
56
|
+
breaks: true,
|
|
57
|
+
renderer,
|
|
58
|
+
}) as string;
|
|
59
|
+
}
|
|
@@ -169,6 +169,7 @@ const {
|
|
|
169
169
|
return {
|
|
170
170
|
...db,
|
|
171
171
|
schemas: schemas,
|
|
172
|
+
schemas_resolved: true,
|
|
172
173
|
};
|
|
173
174
|
}),
|
|
174
175
|
};
|
|
@@ -213,6 +214,7 @@ const {
|
|
|
213
214
|
return {
|
|
214
215
|
...schema,
|
|
215
216
|
tables: tables,
|
|
217
|
+
tables_resolved: true,
|
|
216
218
|
};
|
|
217
219
|
}),
|
|
218
220
|
};
|