@marimo-team/frontend 0.16.0 → 0.16.2
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/assets/ConnectedDataExplorerComponent-B5cPvWoQ.js +19 -0
- package/dist/assets/{ImageComparisonComponent-fTHv1Ih0.js → ImageComparisonComponent-CqR26LSv.js} +1 -1
- package/dist/assets/{VegaLite-Bdi-TyfY.js → VegaLite-DvQDATwI.js} +1 -1
- package/dist/assets/_baseEach--KDTwKbG.js +1 -0
- package/dist/assets/_baseMap-Cu3o-eyO.js +1 -0
- package/dist/assets/{_baseUniq-CCgDNtZb.js → _baseUniq-y7ZXnMo1.js} +1 -1
- package/dist/assets/{_createAggregator-DcD0kTA5.js → _createAggregator-ZcHkHPNJ.js} +1 -1
- package/dist/assets/{agent-panel-Crv430aI.js → agent-panel-B91RoLct.js} +76 -57
- package/dist/assets/{any-language-editor-CQh552Wu.js → any-language-editor-CxfHcm5h.js} +1 -1
- package/dist/assets/{architectureDiagram-W76B3OCA-BAJeBxzt.js → architectureDiagram-W76B3OCA-BQsvK8uR.js} +1 -1
- package/dist/assets/{between-horizontal-start-Boxgxbt_.js → between-horizontal-start-BmYToIaM.js} +1 -1
- package/dist/assets/{blockDiagram-QIGZ2CNN-CL-1svEK.js → blockDiagram-QIGZ2CNN-r3HgCj4w.js} +1 -1
- package/dist/assets/{c4Diagram-FPNF74CW-BbEqbCTl.js → c4Diagram-FPNF74CW-BJbPNt41.js} +1 -1
- package/dist/assets/channel-DFaEx1fu.js +1 -0
- package/dist/assets/chat-panel-IoPMv8e2.js +3 -0
- package/dist/assets/{chunk-4BX2VUAB-C--8TXeE.js → chunk-4BX2VUAB-Dv4MZ9Hj.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-Bj00HDqq.js → chunk-55IACEB6-CM4AHquB.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-C-lhB6hN.js → chunk-FMBD7UC4-C_Zz0ENB.js} +1 -1
- package/dist/assets/{chunk-K7UQS3LO-B-pGTXPt.js → chunk-K7UQS3LO-DYSmiXYq.js} +1 -1
- package/dist/assets/{chunk-QN33PNHL-DqUzGhvm.js → chunk-QN33PNHL-QM4OPuQP.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-TntJHfSk.js → chunk-QZHKN3VN-CfAsGyeB.js} +1 -1
- package/dist/assets/{chunk-TVAH2DTR-HUJb1psV.js → chunk-TVAH2DTR-6j_Cpjsi.js} +1 -1
- package/dist/assets/{chunk-TZMSLE5B-BK3C__t3.js → chunk-TZMSLE5B-BHslFJQE.js} +1 -1
- package/dist/assets/{circle-play-DBLOv1Yu.js → circle-play-CK3UZRYQ.js} +1 -1
- package/dist/assets/classDiagram-KNZD7YFC-BsZtvV5O.js +1 -0
- package/dist/assets/classDiagram-v2-RKCZMP56-BsZtvV5O.js +1 -0
- package/dist/assets/{clear-button-BeoFbEKH.js → clear-button-C4fDVSv8.js} +1 -1
- package/dist/assets/clone-YBEvPE-s.js +1 -0
- package/dist/assets/command-palette-D7hOfvf6.js +1 -0
- package/dist/assets/{common-C7oJcmCT.js → common-D-lbuUwz.js} +1 -1
- package/dist/assets/{compile-7L0MwhyI.js → compile-DVQe1Mzk.js} +1 -1
- package/dist/assets/{cose-bilkent-S5V4N54A-BMkGLcVC.js → cose-bilkent-S5V4N54A-D-IS7WC8.js} +1 -1
- package/dist/assets/{dagre-5GWH7T2D-BJtRienS.js → dagre-5GWH7T2D-lYu-tEWT.js} +1 -1
- package/dist/assets/{data-grid-overlay-editor-DBkmGtNs.js → data-grid-overlay-editor-C5peOCit.js} +1 -1
- package/dist/assets/datasources-panel-D3NA20uZ.js +1 -0
- package/dist/assets/{dependency-graph-panel-DEdOxp2X.js → dependency-graph-panel-BGVYOfkV.js} +1 -1
- package/dist/assets/{diagram-N5W7TBWH-CmECY3nb.js → diagram-N5W7TBWH-BnvIuYUp.js} +1 -1
- package/dist/assets/{diagram-QEK2KX5R-DMOVSNKD.js → diagram-QEK2KX5R-DemedRK3.js} +1 -1
- package/dist/assets/{diagram-S2PKOQOG-BiJ96PNQ.js → diagram-S2PKOQOG-iiY7AuyH.js} +1 -1
- package/dist/assets/{documentation-panel-xULhaEv3.js → documentation-panel-C3dSwOSQ.js} +1 -1
- package/dist/assets/edit-page-C5TsEeSo.js +129 -0
- package/dist/assets/{ellipsis-vertical-BBqXIlc2.js → ellipsis-vertical-CazJl8M7.js} +1 -1
- package/dist/assets/{empty-state-B3dA3G5P.js → empty-state-DW308mFO.js} +1 -1
- package/dist/assets/{erDiagram-AWTI2OKA-MP1DiFRo.js → erDiagram-AWTI2OKA-6wQ8Ugg0.js} +1 -1
- package/dist/assets/{error-panel-Cc1sv-Ag.js → error-panel-D1VnJ1yP.js} +1 -1
- package/dist/assets/file-explorer-panel-0oVd4t-D.js +1 -0
- package/dist/assets/{flowDiagram-PVAE7QVJ-BX7caPp7.js → flowDiagram-PVAE7QVJ-C55IUWjm.js} +1 -1
- package/dist/assets/{ganttDiagram-OWAHRB6G-B462g4Yf.js → ganttDiagram-OWAHRB6G-DmqCM6ME.js} +4 -4
- package/dist/assets/{gitGraphDiagram-NY62KEGX-CGgvZ9-9.js → gitGraphDiagram-NY62KEGX-DBvhAeM_.js} +1 -1
- package/dist/assets/{glide-data-editor-C0gUFZON.js → glide-data-editor-CHNuHidQ.js} +4 -4
- package/dist/assets/{graph-CHRVBzY5.js → graph-CG6BgUWQ.js} +1 -1
- package/dist/assets/home-page-dgivXuSR.js +9 -0
- package/dist/assets/{index-Clbi_Yaq.js → index-BTGpssVX.js} +1 -1
- package/dist/assets/{index-CQDrxQ0j.js → index-BYVZlBF8.js} +1 -1
- package/dist/assets/{index-lYa_leQE.js → index-BelfnXwL.js} +1 -1
- package/dist/assets/{index-DRMm6SNo.js → index-BneyUujp.js} +1 -1
- package/dist/assets/{index-BY93Ejhl.js → index-C02SqeRj.js} +1 -1
- package/dist/assets/{index-C-8WADat.js → index-C7dtgr9A.js} +1 -1
- package/dist/assets/{index-D9UKkrr2.js → index-CAQvMTzM.js} +1 -1
- package/dist/assets/index-CGDMlQfO.css +1 -0
- package/dist/assets/index-CelXfcd8.js +580 -0
- package/dist/assets/{index-vmICa5KN.js → index-Csd6QrCV.js} +1 -1
- package/dist/assets/{index-DoRmcrKM.js → index-CtPksxf0.js} +1 -1
- package/dist/assets/{index-C1v_Z9et.js → index-Cxyk7pt-.js} +1 -1
- package/dist/assets/{index-CpTPJo4k.js → index-DAZ-9ri2.js} +1 -1
- package/dist/assets/{index-DEQvTChO.js → index-DONRrmA2.js} +1 -1
- package/dist/assets/{index-C4Tn5NvJ.js → index-Db36XTG_.js} +1 -1
- package/dist/assets/{index-z9bohSQJ.js → index-DdIhdEVw.js} +1 -1
- package/dist/assets/{index-C-GhZ7ti.js → index-M_pBKDSe.js} +1 -1
- package/dist/assets/{index-C77h_TXN.js → index-_luCZMLM.js} +1 -1
- package/dist/assets/{index-D1vmG6DS.js → index-mkubqy9-.js} +1 -1
- package/dist/assets/{index-BVgAenPd.js → index-sbO9UaUU.js} +1 -1
- package/dist/assets/{index-CWMgowgL.js → index-z4krxQ4j.js} +1 -1
- package/dist/assets/infoDiagram-STP46IZ2-wTALjfPc.js +2 -0
- package/dist/assets/{isEmpty-DU_ogP_D.js → isEmpty-CqX_YTIf.js} +1 -1
- package/dist/assets/{journeyDiagram-BIP6EPQ6-C6EgLP_Q.js → journeyDiagram-BIP6EPQ6-Y5w_Tqe_.js} +1 -1
- package/dist/assets/{kanban-definition-6OIFK2YF-BXzYO1yj.js → kanban-definition-6OIFK2YF-DbXs5Rxi.js} +1 -1
- package/dist/assets/{layout-jihVw5-i.js → layout-BCNPDACj.js} +1 -1
- package/dist/assets/{linear-C4blANlC.js → linear-uO6UVhXt.js} +1 -1
- package/dist/assets/links-Drv7cJgN.js +7 -0
- package/dist/assets/{logs-panel-D401qzZh.js → logs-panel-BEQ1eRUp.js} +1 -1
- package/dist/assets/{markdown-renderer-Cd9eYyaL.js → markdown-renderer-Dmzbb00W.js} +20 -20
- package/dist/assets/{mermaid-BEVuRz_O.js → mermaid-qRc4MXIj.js} +1 -1
- package/dist/assets/{mermaid.core-CaSnaLH0.js → mermaid.core-CvvJtCRj.js} +4 -4
- package/dist/assets/min-DYUOb1RR.js +1 -0
- package/dist/assets/{mindmap-definition-Q6HEUPPD-BXUM5MT2.js → mindmap-definition-Q6HEUPPD-G5NognM-.js} +1 -1
- package/dist/assets/{number-overlay-editor-4uWXGlPG.js → number-overlay-editor-DPr5sHFu.js} +1 -1
- package/dist/assets/outline-panel-gxQXvVi4.js +1 -0
- package/dist/assets/{packages-panel-CJL0MVlj.js → packages-panel-B1T0VPlg.js} +1 -1
- package/dist/assets/{pieDiagram-ADFJNKIX-Dxt5PVNo.js → pieDiagram-ADFJNKIX-DK9SHkfc.js} +1 -1
- package/dist/assets/{quadrantDiagram-LMRXKWRM-D4pUaA31.js → quadrantDiagram-LMRXKWRM-D1DdWF8C.js} +1 -1
- package/dist/assets/{react-plotly-cJZ0VWBq.js → react-plotly-CTwajqCb.js} +1 -1
- package/dist/assets/{requirementDiagram-4UW4RH46-DVRTjgas.js → requirementDiagram-4UW4RH46-DnjDAypr.js} +1 -1
- package/dist/assets/{run-page-BUEnMC9w.js → run-page-CQY9im22.js} +1 -1
- package/dist/assets/{sankeyDiagram-GR3RE2ED-CVFnD9C-.js → sankeyDiagram-GR3RE2ED-B67Va-ER.js} +1 -1
- package/dist/assets/{scratchpad-panel-BIgRENkI.js → scratchpad-panel-DlDfcDtW.js} +1 -1
- package/dist/assets/{secrets-panel-xY5-V_BD.js → secrets-panel-BDGyuGZA.js} +1 -1
- package/dist/assets/{sequenceDiagram-C3RYC4MD-_lY4ZN_S.js → sequenceDiagram-C3RYC4MD-DiWgZPtN.js} +1 -1
- package/dist/assets/{slides-component-DMjQomc3.css → slides-component-C-LoGC1U.css} +1 -1
- package/dist/assets/{slides-component-Xjymwj7X.js → slides-component-DhpPRtQp.js} +1 -1
- package/dist/assets/snippets-panel-CLkBXhJ2.js +1 -0
- package/dist/assets/sortBy-D4OG7w4O.js +1 -0
- package/dist/assets/{state-C4NiC9tO.js → state-Dz_3JyED.js} +1 -1
- package/dist/assets/{stateDiagram-KXAO66HF-Da0JQWCn.js → stateDiagram-KXAO66HF-ByF2AULw.js} +1 -1
- package/dist/assets/stateDiagram-v2-UMBNRL4Z-CtBJqosP.js +1 -0
- package/dist/assets/storage-Dr0CC44z.js +26 -0
- package/dist/assets/{terminal-BPwTkXae.js → terminal-BtdissBf.js} +1 -1
- package/dist/assets/{time-Dv5_Ouz_.js → time-DKdOTnQg.js} +1 -1
- package/dist/assets/{timeline-definition-XQNQX7LJ-Dxh5Zu2e.js → timeline-definition-XQNQX7LJ-DzER9bf6.js} +1 -1
- package/dist/assets/tracing-Dpx5M-u3.js +2 -0
- package/dist/assets/{tracing-panel-DAzrzNmm.js → tracing-panel-hCjBkSER.js} +2 -2
- package/dist/assets/{trash-Dc6DSjz_.js → trash-C6Ko-g5q.js} +1 -1
- package/dist/assets/{tree-jheoerAX.js → tree-BHN2gcCF.js} +6 -6
- package/dist/assets/{treemap-75Q7IDZK-IgpxeGaf.js → treemap-75Q7IDZK-DR79Mhzt.js} +27 -27
- package/dist/assets/variable-panel-PFBCFz36.js +1 -0
- package/dist/assets/{vega-component-BpfpiPKI.js → vega-component-Db6-uY4C.js} +1 -1
- package/dist/assets/worker-fHbtoWvT.js +1 -0
- package/dist/assets/{xychartDiagram-6GGTOJPD-CmNigJ31.js → xychartDiagram-6GGTOJPD-DWzBP3tZ.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +5 -4
- package/src/__mocks__/requests.ts +1 -0
- package/src/components/app-config/user-config-form.tsx +46 -1
- package/src/components/chat/acp/__tests__/__snapshots__/prompt.test.ts.snap +62 -43
- package/src/components/chat/acp/__tests__/atoms.test.ts +1 -1
- package/src/components/chat/acp/__tests__/state.test.ts +36 -36
- package/src/components/chat/acp/agent-panel.tsx +24 -27
- package/src/components/chat/acp/blocks.tsx +6 -6
- package/src/components/chat/acp/prompt.ts +62 -43
- package/src/components/chat/chat-panel.tsx +5 -1
- package/src/components/chat/markdown-renderer.tsx +6 -10
- package/src/components/chat/tool-call-accordion.tsx +52 -20
- package/src/components/data-table/SearchBar.tsx +8 -7
- package/src/components/data-table/__tests__/column_formatting.test.ts +50 -35
- package/src/components/data-table/__tests__/columns.test.tsx +38 -0
- package/src/components/data-table/__tests__/data-table.test.tsx +39 -1
- package/src/components/data-table/cell-hover-template/feature.ts +14 -0
- package/src/components/data-table/cell-hover-template/types.ts +11 -0
- package/src/components/data-table/charts/components/form-fields.tsx +41 -37
- package/src/components/data-table/charts/forms/common-chart.tsx +2 -2
- package/src/components/data-table/column-explorer-panel/column-explorer.tsx +5 -2
- package/src/components/data-table/column-formatting/feature.ts +62 -29
- package/src/components/data-table/column-formatting/types.ts +1 -0
- package/src/components/data-table/column-header.tsx +3 -1
- package/src/components/data-table/column-summary/chart-spec-model.tsx +24 -7
- package/src/components/data-table/column-summary/column-summary.tsx +18 -9
- package/src/components/data-table/columns.tsx +63 -20
- package/src/components/data-table/data-table.tsx +10 -2
- package/src/components/data-table/date-popover.tsx +85 -75
- package/src/components/data-table/filter-pills.tsx +14 -9
- package/src/components/data-table/header-items.tsx +5 -1
- package/src/components/data-table/pagination.tsx +20 -13
- package/src/components/data-table/renderers.tsx +36 -0
- package/src/components/data-table/row-viewer-panel/row-viewer.tsx +10 -8
- package/src/components/data-table/schemas.ts +16 -0
- package/src/components/datasources/column-preview.tsx +6 -2
- package/src/components/datasources/datasources.tsx +8 -12
- package/src/components/editor/Cell.tsx +2 -0
- package/src/components/editor/ai/transport/chat-transport.tsx +4 -1
- package/src/components/editor/cell/CellStatus.tsx +23 -20
- package/src/components/editor/cell/CreateCellButton.tsx +3 -4
- package/src/components/editor/cell/code/language-toggle.tsx +3 -4
- package/src/components/editor/chrome/wrapper/footer-items/machine-stats.tsx +39 -28
- package/src/components/editor/controls/notebook-menu-dropdown.tsx +4 -2
- package/src/components/editor/errors/sql-validation-errors.tsx +34 -0
- package/src/components/editor/file-tree/requesting-tree.tsx +14 -8
- package/src/components/editor/output/ConsoleOutput.tsx +13 -1
- package/src/components/editor/output/MarimoErrorOutput.tsx +60 -1
- package/src/components/editor/renderers/CellArray.tsx +3 -4
- package/src/components/editor/renderers/slides-layout/slides-layout.tsx +3 -3
- package/src/components/editor/renderers/slides-layout/types.ts +1 -0
- package/src/components/pages/home-page.tsx +4 -1
- package/src/components/slides/slides-component.tsx +1 -1
- package/src/components/slides/slides.css +6 -0
- package/src/components/terminal/theme.tsx +1 -0
- package/src/components/tracing/tracing-spec.ts +5 -4
- package/src/components/ui/range-slider.tsx +4 -2
- package/src/components/ui/slider.tsx +3 -1
- package/src/components/variables/variables-table.tsx +3 -0
- package/src/core/MarimoApp.tsx +9 -6
- package/src/core/ai/context/__tests__/registry.test.ts +6 -4
- package/src/core/ai/context/providers/cell-output.ts +4 -20
- package/src/core/ai/context/providers/error.ts +3 -1
- package/src/core/ai/context/providers/file.ts +7 -2
- package/src/core/ai/context/providers/tables.ts +3 -2
- package/src/core/ai/context/providers/variable.ts +6 -4
- package/src/core/ai/staged-cells.ts +34 -1
- package/src/core/cells/__tests__/add-missing-import.test.ts +67 -22
- package/src/core/cells/add-missing-import.ts +24 -7
- package/src/core/cells/cells.ts +26 -27
- package/src/core/cells/logs.ts +1 -1
- package/src/core/codemirror/find-replace/search-highlight.ts +3 -1
- package/src/core/codemirror/language/LanguageAdapters.ts +9 -3
- package/src/core/codemirror/language/__tests__/extension.test.ts +24 -0
- package/src/core/codemirror/language/__tests__/sql-validation.test.ts +133 -0
- package/src/core/codemirror/language/languages/sql/sql-mode.ts +20 -0
- package/src/core/codemirror/language/languages/sql/sql.ts +90 -3
- package/src/core/codemirror/language/languages/sql/validation-errors.ts +79 -0
- package/src/core/codemirror/language/panel/panel.tsx +8 -2
- package/src/core/codemirror/language/panel/sql.tsx +81 -4
- package/src/core/codemirror/lsp/notebook-lsp.ts +8 -2
- package/src/core/codemirror/readonly/__tests__/extension.test.ts +1 -1
- package/src/core/codemirror/rtc/loro/awareness.ts +52 -17
- package/src/core/codemirror/rtc/loro/sync.ts +12 -4
- package/src/core/config/config-schema.ts +1 -0
- package/src/core/config/config.ts +4 -0
- package/src/core/config/feature-flag.tsx +3 -1
- package/src/core/datasets/request-registry.ts +17 -1
- package/src/core/hotkeys/hotkeys.ts +8 -4
- package/src/core/i18n/__tests__/locale-provider.test.tsx +176 -0
- package/src/core/i18n/locale-provider.tsx +35 -0
- package/src/core/i18n/with-locale.tsx +12 -0
- package/src/core/islands/bridge.ts +1 -0
- package/src/core/islands/components/web-components.tsx +13 -10
- package/src/core/islands/main.ts +1 -0
- package/src/core/kernel/RuntimeState.ts +4 -1
- package/src/core/kernel/messages.ts +3 -2
- package/src/core/network/DeferredRequestRegistry.ts +16 -4
- package/src/core/network/requests-network.ts +7 -0
- package/src/core/network/requests-static.ts +1 -0
- package/src/core/network/requests-toasting.ts +1 -0
- package/src/core/network/types.ts +2 -0
- package/src/core/runtime/runtime.ts +5 -4
- package/src/core/wasm/bridge.ts +10 -1
- package/src/core/wasm/store.ts +4 -1
- package/src/core/wasm/worker/message-buffer.ts +3 -2
- package/src/core/websocket/types.ts +22 -16
- package/src/core/websocket/useMarimoWebSocket.tsx +4 -0
- package/src/hooks/useFormatting.ts +97 -0
- package/src/hooks/useTimer.ts +8 -5
- package/src/plugins/core/registerReactComponent.tsx +39 -29
- package/src/plugins/impl/DataTablePlugin.tsx +15 -4
- package/src/plugins/impl/RangeSliderPlugin.tsx +5 -3
- package/src/plugins/impl/SliderPlugin.tsx +3 -1
- package/src/plugins/impl/anywidget/model.ts +16 -5
- package/src/plugins/impl/data-editor/types.ts +7 -5
- package/src/plugins/impl/data-explorer/components/column-summary.tsx +20 -13
- package/src/plugins/impl/data-frames/DataFramePlugin.tsx +17 -5
- package/src/plugins/impl/panel/utils.ts +6 -4
- package/src/plugins/layout/OutlinePlugin.tsx +69 -0
- package/src/plugins/layout/StatPlugin.tsx +4 -1
- package/src/plugins/plugins.ts +2 -0
- package/src/stories/dataframe.stories.tsx +2 -0
- package/src/utils/__tests__/dates.test.ts +45 -24
- package/src/utils/__tests__/dom.test.ts +167 -0
- package/src/utils/__tests__/numbers.test.ts +42 -30
- package/src/utils/__tests__/once.test.ts +187 -0
- package/src/utils/dates.ts +15 -10
- package/src/utils/dom.ts +55 -0
- package/src/utils/edit-distance.ts +8 -6
- package/src/utils/errors.ts +1 -1
- package/src/utils/id-tree.tsx +21 -10
- package/src/utils/localStorage.ts +13 -4
- package/src/utils/numbers.ts +11 -11
- package/src/utils/once.ts +32 -0
- package/src/utils/paths.ts +4 -1
- package/src/utils/pluralize.ts +12 -5
- package/src/utils/python-poet/poet.ts +30 -15
- package/src/utils/time.ts +5 -1
- package/dist/assets/ConnectedDataExplorerComponent-BErMbWvG.js +0 -19
- package/dist/assets/_baseEach-CNBxBxvS.js +0 -1
- package/dist/assets/_baseMap-D1WHjKrd.js +0 -1
- package/dist/assets/channel-_2eNSz0n.js +0 -1
- package/dist/assets/chat-panel-CXh5Wl6C.js +0 -3
- package/dist/assets/classDiagram-KNZD7YFC-BGmh9POF.js +0 -1
- package/dist/assets/classDiagram-v2-RKCZMP56-BGmh9POF.js +0 -1
- package/dist/assets/clone-BFDSPAj3.js +0 -1
- package/dist/assets/command-palette-CXZiSv0I.js +0 -1
- package/dist/assets/datasources-panel-B7FbYLiy.js +0 -1
- package/dist/assets/edit-page-BrYda9VE.js +0 -129
- package/dist/assets/file-explorer-panel-Bw59Kva1.js +0 -1
- package/dist/assets/home-page-Fb2osjys.js +0 -9
- package/dist/assets/index-Cx0bsY1w.css +0 -1
- package/dist/assets/index-DKEudB02.js +0 -578
- package/dist/assets/infoDiagram-STP46IZ2-CVyrdLc8.js +0 -2
- package/dist/assets/links-D59GIweI.js +0 -7
- package/dist/assets/min-DUMu_zeK.js +0 -1
- package/dist/assets/outline-panel-DIzkvm2I.js +0 -1
- package/dist/assets/snippets-panel-CTPYW41n.js +0 -1
- package/dist/assets/sortBy-BNZKwiq_.js +0 -1
- package/dist/assets/stateDiagram-v2-UMBNRL4Z-D5lYZOOt.js +0 -1
- package/dist/assets/storage-CMdLzB_c.js +0 -26
- package/dist/assets/tracing-BCIurUfa.js +0 -2
- package/dist/assets/variable-panel-DYAiLBmF.js +0 -1
- package/dist/assets/worker-X5rxzQGQ.js +0 -1
|
@@ -1,18 +1,35 @@
|
|
|
1
1
|
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { createStore } from "jotai";
|
|
4
|
-
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { MockNotebook } from "@/__mocks__/notebook";
|
|
6
|
+
import { MockRequestClient } from "@/__mocks__/requests";
|
|
7
|
+
import { store } from "@/core/state/jotai";
|
|
5
8
|
import { variablesAtom } from "@/core/variables/state";
|
|
6
9
|
import type { Variables } from "@/core/variables/types";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
import {
|
|
11
|
+
maybeAddAltairImport,
|
|
12
|
+
maybeAddMarimoImport,
|
|
13
|
+
maybeAddMissingImport,
|
|
14
|
+
} from "../add-missing-import";
|
|
15
|
+
import { notebookAtom } from "../cells";
|
|
10
16
|
import type { CellId } from "../ids";
|
|
11
|
-
import type { CellData } from "../types";
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
// Mock the getRequestClient function
|
|
19
|
+
const mockRequestClient = MockRequestClient.create();
|
|
20
|
+
vi.mock("@/core/network/requests", () => ({
|
|
21
|
+
getRequestClient: () => mockRequestClient,
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
const Cell1 = "1" as CellId;
|
|
25
|
+
const Cell2 = "2" as CellId;
|
|
14
26
|
|
|
15
27
|
describe("maybeAddMissingImport", () => {
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
store.set(variablesAtom, {} as Variables);
|
|
30
|
+
store.set(notebookAtom, MockNotebook.notebookState({ cellData: {} }));
|
|
31
|
+
});
|
|
32
|
+
|
|
16
33
|
it("should not add an import if the variable is already in the variables state", () => {
|
|
17
34
|
const appStore = createStore();
|
|
18
35
|
appStore.set(variablesAtom, { mo: {} } as Variables);
|
|
@@ -36,14 +53,14 @@ describe("maybeAddMissingImport", () => {
|
|
|
36
53
|
(code) => {
|
|
37
54
|
const appStore = createStore();
|
|
38
55
|
appStore.set(variablesAtom, {} as Variables);
|
|
39
|
-
appStore.set(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
appStore.set(
|
|
57
|
+
notebookAtom,
|
|
58
|
+
MockNotebook.notebookState({
|
|
59
|
+
cellData: {
|
|
60
|
+
[Cell1]: { code: code },
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
47
64
|
const onAddImport = vi.fn();
|
|
48
65
|
maybeAddMissingImport({
|
|
49
66
|
moduleName: "marimo",
|
|
@@ -58,14 +75,14 @@ describe("maybeAddMissingImport", () => {
|
|
|
58
75
|
it("should add an import if the variable is not in the variables state and the import statement does not exist in the notebook", () => {
|
|
59
76
|
const appStore = createStore();
|
|
60
77
|
appStore.set(variablesAtom, {} as Variables);
|
|
61
|
-
appStore.set(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
78
|
+
appStore.set(
|
|
79
|
+
notebookAtom,
|
|
80
|
+
MockNotebook.notebookState({
|
|
81
|
+
cellData: {
|
|
82
|
+
[Cell1]: { code: "mo.md('hello')" },
|
|
83
|
+
},
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
69
86
|
const onAddImport = vi.fn();
|
|
70
87
|
maybeAddMissingImport({
|
|
71
88
|
moduleName: "marimo",
|
|
@@ -75,4 +92,32 @@ describe("maybeAddMissingImport", () => {
|
|
|
75
92
|
});
|
|
76
93
|
expect(onAddImport).toHaveBeenCalledWith("import marimo as mo");
|
|
77
94
|
});
|
|
95
|
+
|
|
96
|
+
it("should not create a new cell if import already exists due to skipIfCodeExists", () => {
|
|
97
|
+
const addImports = [maybeAddMarimoImport, maybeAddAltairImport];
|
|
98
|
+
for (const addImport of addImports) {
|
|
99
|
+
store.set(variablesAtom, {} as Variables);
|
|
100
|
+
store.set(
|
|
101
|
+
notebookAtom,
|
|
102
|
+
MockNotebook.notebookState({
|
|
103
|
+
cellData: {
|
|
104
|
+
[Cell1]: { code: "import marimo as mo" },
|
|
105
|
+
[Cell2]: { code: "import altair as alt" },
|
|
106
|
+
},
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const createNewCell = vi.fn();
|
|
111
|
+
const result = addImport({
|
|
112
|
+
autoInstantiate: false,
|
|
113
|
+
createNewCell,
|
|
114
|
+
fromCellId: Cell1,
|
|
115
|
+
before: false,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Should not create a new cell since the import already exists
|
|
119
|
+
expect(createNewCell).not.toHaveBeenCalled();
|
|
120
|
+
expect(result).toBeNull();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
78
123
|
});
|
|
@@ -47,27 +47,40 @@ export function maybeAddMissingImport({
|
|
|
47
47
|
return true;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Adds a marimo import to the notebook if not already present.
|
|
52
|
+
* @param autoInstantiate Whether to automatically run the cell.
|
|
53
|
+
* @param createNewCell The function to create a new cell.
|
|
54
|
+
* @param fromCellId The cell to add the import to.
|
|
55
|
+
* @param before Whether to add the import before or after the cell.
|
|
56
|
+
*
|
|
57
|
+
* Returns the ID of the new cell if added, otherwise null.
|
|
58
|
+
*/
|
|
50
59
|
export function maybeAddMarimoImport({
|
|
51
60
|
autoInstantiate,
|
|
52
61
|
createNewCell,
|
|
53
62
|
fromCellId,
|
|
63
|
+
before,
|
|
54
64
|
}: {
|
|
55
65
|
autoInstantiate: boolean;
|
|
56
66
|
createNewCell: CellActions["createNewCell"];
|
|
57
67
|
fromCellId?: CellId | null;
|
|
58
|
-
|
|
68
|
+
before?: boolean;
|
|
69
|
+
}): CellId | null {
|
|
59
70
|
const client = getRequestClient();
|
|
60
|
-
|
|
71
|
+
let newCellId: CellId | null = null;
|
|
72
|
+
const added = maybeAddMissingImport({
|
|
61
73
|
moduleName: "marimo",
|
|
62
74
|
variableName: "mo",
|
|
63
75
|
onAddImport: (importStatement) => {
|
|
64
|
-
|
|
76
|
+
newCellId = CellId.create();
|
|
65
77
|
createNewCell({
|
|
66
78
|
cellId: fromCellId ?? "__end__",
|
|
67
|
-
before: false,
|
|
79
|
+
before: before ?? false,
|
|
68
80
|
code: importStatement,
|
|
69
81
|
lastCodeRun: autoInstantiate ? importStatement : undefined,
|
|
70
82
|
newCellId: newCellId,
|
|
83
|
+
skipIfCodeExists: true,
|
|
71
84
|
autoFocus: false,
|
|
72
85
|
});
|
|
73
86
|
if (autoInstantiate) {
|
|
@@ -78,6 +91,7 @@ export function maybeAddMarimoImport({
|
|
|
78
91
|
}
|
|
79
92
|
},
|
|
80
93
|
});
|
|
94
|
+
return added ? newCellId : null;
|
|
81
95
|
}
|
|
82
96
|
|
|
83
97
|
export function maybeAddAltairImport({
|
|
@@ -88,19 +102,21 @@ export function maybeAddAltairImport({
|
|
|
88
102
|
autoInstantiate: boolean;
|
|
89
103
|
createNewCell: CellActions["createNewCell"];
|
|
90
104
|
fromCellId?: CellId | null;
|
|
91
|
-
}):
|
|
105
|
+
}): CellId | null {
|
|
92
106
|
const client = getRequestClient();
|
|
93
|
-
|
|
107
|
+
let newCellId: CellId | null = null;
|
|
108
|
+
const added = maybeAddMissingImport({
|
|
94
109
|
moduleName: "altair",
|
|
95
110
|
variableName: "alt",
|
|
96
111
|
onAddImport: (importStatement) => {
|
|
97
|
-
|
|
112
|
+
newCellId = CellId.create();
|
|
98
113
|
createNewCell({
|
|
99
114
|
cellId: fromCellId ?? "__end__",
|
|
100
115
|
before: false,
|
|
101
116
|
code: importStatement,
|
|
102
117
|
lastCodeRun: autoInstantiate ? importStatement : undefined,
|
|
103
118
|
newCellId: newCellId,
|
|
119
|
+
skipIfCodeExists: true,
|
|
104
120
|
autoFocus: false,
|
|
105
121
|
});
|
|
106
122
|
if (autoInstantiate) {
|
|
@@ -111,4 +127,5 @@ export function maybeAddAltairImport({
|
|
|
111
127
|
}
|
|
112
128
|
},
|
|
113
129
|
});
|
|
130
|
+
return added ? newCellId : null;
|
|
114
131
|
}
|
package/src/core/cells/cells.ts
CHANGED
|
@@ -145,6 +145,31 @@ export function initialNotebookState(): NotebookState {
|
|
|
145
145
|
});
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
export interface CreateNewCellAction {
|
|
149
|
+
/** The target cell ID to create a new cell relative to. Can be:
|
|
150
|
+
* - A CellId string for an existing cell
|
|
151
|
+
* - "__end__" to append at the end of the first column
|
|
152
|
+
* - {type: "__end__", columnId} to append at the end of a specific column
|
|
153
|
+
*/
|
|
154
|
+
cellId: CellId | "__end__" | { type: "__end__"; columnId: CellColumnId };
|
|
155
|
+
/** Whether to insert before (true) or after (false) the target cell */
|
|
156
|
+
before: boolean;
|
|
157
|
+
/** Initial code content for the new cell */
|
|
158
|
+
code?: string;
|
|
159
|
+
/** The last executed code for the new cell */
|
|
160
|
+
lastCodeRun?: string;
|
|
161
|
+
/** Timestamp of the last execution */
|
|
162
|
+
lastExecutionTime?: number;
|
|
163
|
+
/** Optional custom ID for the new cell. Auto-generated if not provided */
|
|
164
|
+
newCellId?: CellId;
|
|
165
|
+
/** Whether to focus the new cell after creation */
|
|
166
|
+
autoFocus?: boolean;
|
|
167
|
+
/** If true, skip creation if code already exists */
|
|
168
|
+
skipIfCodeExists?: boolean;
|
|
169
|
+
/** Hide the code in the new cell. This will be initially shown until the cell is blurred for the first time. */
|
|
170
|
+
hideCode?: boolean;
|
|
171
|
+
}
|
|
172
|
+
|
|
148
173
|
/**
|
|
149
174
|
* Actions and reducer for the notebook state.
|
|
150
175
|
*/
|
|
@@ -154,33 +179,7 @@ const {
|
|
|
154
179
|
useActions,
|
|
155
180
|
valueAtom: notebookAtom,
|
|
156
181
|
} = createReducerAndAtoms(initialNotebookState, {
|
|
157
|
-
createNewCell: (
|
|
158
|
-
state,
|
|
159
|
-
action: {
|
|
160
|
-
/** The target cell ID to create a new cell relative to. Can be:
|
|
161
|
-
* - A CellId string for an existing cell
|
|
162
|
-
* - "__end__" to append at the end of the first column
|
|
163
|
-
* - {type: "__end__", columnId} to append at the end of a specific column
|
|
164
|
-
*/
|
|
165
|
-
cellId: CellId | "__end__" | { type: "__end__"; columnId: CellColumnId };
|
|
166
|
-
/** Whether to insert before (true) or after (false) the target cell */
|
|
167
|
-
before: boolean;
|
|
168
|
-
/** Initial code content for the new cell */
|
|
169
|
-
code?: string;
|
|
170
|
-
/** The last executed code for the new cell */
|
|
171
|
-
lastCodeRun?: string;
|
|
172
|
-
/** Timestamp of the last execution */
|
|
173
|
-
lastExecutionTime?: number;
|
|
174
|
-
/** Optional custom ID for the new cell. Auto-generated if not provided */
|
|
175
|
-
newCellId?: CellId;
|
|
176
|
-
/** Whether to focus the new cell after creation */
|
|
177
|
-
autoFocus?: boolean;
|
|
178
|
-
/** If true, skip creation if code already exists */
|
|
179
|
-
skipIfCodeExists?: boolean;
|
|
180
|
-
/** Hide the code in the new cell. This will be initially shown until the cell is blurred for the first time. */
|
|
181
|
-
hideCode?: boolean;
|
|
182
|
-
},
|
|
183
|
-
) => {
|
|
182
|
+
createNewCell: (state, action: CreateNewCellAction) => {
|
|
184
183
|
const {
|
|
185
184
|
cellId,
|
|
186
185
|
before,
|
package/src/core/cells/logs.ts
CHANGED
|
@@ -102,7 +102,7 @@ export function formatLogTimestamp(timestamp: number): string {
|
|
|
102
102
|
try {
|
|
103
103
|
// parse from UTC
|
|
104
104
|
const date = fromUnixTime(timestamp);
|
|
105
|
-
return date.toLocaleTimeString(
|
|
105
|
+
return date.toLocaleTimeString(undefined, {
|
|
106
106
|
hour12: true,
|
|
107
107
|
hour: "numeric",
|
|
108
108
|
minute: "numeric",
|
|
@@ -91,8 +91,10 @@ const HighlightMargin = 250;
|
|
|
91
91
|
export const searchHighlighter = ViewPlugin.fromClass(
|
|
92
92
|
class {
|
|
93
93
|
decorations: DecorationSet;
|
|
94
|
+
readonly view: EditorView;
|
|
94
95
|
|
|
95
|
-
constructor(
|
|
96
|
+
constructor(view: EditorView) {
|
|
97
|
+
this.view = view;
|
|
96
98
|
this.decorations = this.highlight(view.state.field(searchState));
|
|
97
99
|
}
|
|
98
100
|
|
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
+
import { once } from "@/utils/once";
|
|
3
4
|
import { MarkdownLanguageAdapter } from "./languages/markdown";
|
|
4
5
|
import { PythonLanguageAdapter } from "./languages/python";
|
|
5
6
|
import { SQLLanguageAdapter } from "./languages/sql/sql";
|
|
6
7
|
import type { LanguageAdapter, LanguageAdapterType } from "./types";
|
|
7
8
|
|
|
9
|
+
// Create cached instances
|
|
10
|
+
const createPythonAdapter = once(() => new PythonLanguageAdapter());
|
|
11
|
+
const createMarkdownAdapter = once(() => new MarkdownLanguageAdapter());
|
|
12
|
+
const createSqlAdapter = once(() => new SQLLanguageAdapter());
|
|
13
|
+
|
|
8
14
|
export const LanguageAdapters: Record<LanguageAdapterType, LanguageAdapter> = {
|
|
9
15
|
// Getters to prevent circular dependencies
|
|
10
16
|
get python() {
|
|
11
|
-
return
|
|
17
|
+
return createPythonAdapter();
|
|
12
18
|
},
|
|
13
19
|
get markdown() {
|
|
14
|
-
return
|
|
20
|
+
return createMarkdownAdapter();
|
|
15
21
|
},
|
|
16
22
|
get sql() {
|
|
17
|
-
return
|
|
23
|
+
return createSqlAdapter();
|
|
18
24
|
},
|
|
19
25
|
};
|
|
20
26
|
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
languageAdapterState,
|
|
15
15
|
switchLanguage,
|
|
16
16
|
} from "../extension";
|
|
17
|
+
import { exportedForTesting as sqlValidationErrorsForTesting } from "../languages/sql/validation-errors";
|
|
17
18
|
import { languageMetadataField } from "../metadata";
|
|
18
19
|
|
|
19
20
|
let view: EditorView | null = null;
|
|
@@ -258,3 +259,26 @@ describe("switchLanguage", () => {
|
|
|
258
259
|
});
|
|
259
260
|
});
|
|
260
261
|
});
|
|
262
|
+
|
|
263
|
+
describe("sqlValidationErrors", () => {
|
|
264
|
+
const { splitErrorMessage } = sqlValidationErrorsForTesting;
|
|
265
|
+
|
|
266
|
+
describe("split error message", () => {
|
|
267
|
+
it("should split the error message into error type and error message", () => {
|
|
268
|
+
const error = "SyntaxError: SELECT * FROM df";
|
|
269
|
+
const { errorType, errorMessage } = splitErrorMessage(error);
|
|
270
|
+
expect(errorType).toBe("SyntaxError");
|
|
271
|
+
expect(errorMessage).toBe("SELECT * FROM df");
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("should handle multiple colons", () => {
|
|
275
|
+
const error =
|
|
276
|
+
"SyntaxError: SELECT * FROM df:SyntaxError: SELECT * FROM df";
|
|
277
|
+
const { errorType, errorMessage } = splitErrorMessage(error);
|
|
278
|
+
expect(errorType).toBe("SyntaxError");
|
|
279
|
+
expect(errorMessage).toBe(
|
|
280
|
+
"SELECT * FROM df:SyntaxError: SELECT * FROM df",
|
|
281
|
+
);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { exportedForTesting } from "../languages/sql/validation-errors";
|
|
5
|
+
|
|
6
|
+
describe("Error Message Splitting", () => {
|
|
7
|
+
it("should handle error message splitting correctly", () => {
|
|
8
|
+
const { splitErrorMessage } = exportedForTesting;
|
|
9
|
+
|
|
10
|
+
const result1 = splitErrorMessage("Syntax error: unexpected token");
|
|
11
|
+
expect(result1.errorType).toBe("Syntax error");
|
|
12
|
+
expect(result1.errorMessage).toBe("unexpected token");
|
|
13
|
+
|
|
14
|
+
const result2 = splitErrorMessage("Multiple: colons: in error");
|
|
15
|
+
expect(result2.errorType).toBe("Multiple");
|
|
16
|
+
expect(result2.errorMessage).toBe("colons: in error");
|
|
17
|
+
|
|
18
|
+
const result3 = splitErrorMessage("No colon error");
|
|
19
|
+
expect(result3.errorType).toBe("No colon error");
|
|
20
|
+
expect(result3.errorMessage).toBe("");
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("DuckDB Error Handling", () => {
|
|
25
|
+
it("should extract codeblock from error with LINE information", () => {
|
|
26
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
27
|
+
|
|
28
|
+
const error =
|
|
29
|
+
'Binder Error: Referenced column "attacks" not found in FROM clause! Candidate bindings: "Attack", "Total" LINE 1:... from pokemon WHERE \'type_2\' = 32 and attack = 32 and not attacks = \'hi\' ^';
|
|
30
|
+
|
|
31
|
+
const result = handleDuckdbError(error);
|
|
32
|
+
|
|
33
|
+
expect(result.errorType).toBe("Binder Error");
|
|
34
|
+
expect(result.errorMessage).toBe(
|
|
35
|
+
'Referenced column "attacks" not found in FROM clause! Candidate bindings: "Attack", "Total"',
|
|
36
|
+
);
|
|
37
|
+
expect(result.codeblock).toBe(
|
|
38
|
+
"LINE 1:... from pokemon WHERE 'type_2' = 32 and attack = 32 and not attacks = 'hi' ^",
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should handle error without LINE information", () => {
|
|
43
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
44
|
+
|
|
45
|
+
const error = "Syntax Error: Invalid syntax near WHERE";
|
|
46
|
+
|
|
47
|
+
const result = handleDuckdbError(error);
|
|
48
|
+
|
|
49
|
+
expect(result.errorType).toBe("Syntax Error");
|
|
50
|
+
expect(result.errorMessage).toBe("Invalid syntax near WHERE");
|
|
51
|
+
expect(result.codeblock).toBeUndefined();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should handle error with LINE at the beginning", () => {
|
|
55
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
56
|
+
|
|
57
|
+
const error = "LINE 1: SELECT * FROM table WHERE invalid_column = 1 ^";
|
|
58
|
+
|
|
59
|
+
const result = handleDuckdbError(error);
|
|
60
|
+
|
|
61
|
+
expect(result.errorType).toBe("LINE 1");
|
|
62
|
+
expect(result.errorMessage).toBe(
|
|
63
|
+
"SELECT * FROM table WHERE invalid_column = 1 ^",
|
|
64
|
+
);
|
|
65
|
+
expect(result.codeblock).toBeUndefined();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should handle error with multiple LINE occurrences", () => {
|
|
69
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
70
|
+
|
|
71
|
+
const error =
|
|
72
|
+
"Error: Something went wrong LINE 1: SELECT * FROM table WHERE invalid_column = 1 ^";
|
|
73
|
+
|
|
74
|
+
const result = handleDuckdbError(error);
|
|
75
|
+
|
|
76
|
+
expect(result.errorType).toBe("Error");
|
|
77
|
+
expect(result.errorMessage).toBe("Something went wrong");
|
|
78
|
+
expect(result.codeblock).toBe(
|
|
79
|
+
"LINE 1: SELECT * FROM table WHERE invalid_column = 1 ^",
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should handle complex error with nested quotes", () => {
|
|
84
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
85
|
+
|
|
86
|
+
const error =
|
|
87
|
+
"Binder Error: Column \"name\" not found! LINE 1: SELECT * FROM users WHERE name = 'John' AND age > 25 ^";
|
|
88
|
+
|
|
89
|
+
const result = handleDuckdbError(error);
|
|
90
|
+
|
|
91
|
+
expect(result.errorType).toBe("Binder Error");
|
|
92
|
+
expect(result.errorMessage).toBe('Column "name" not found!');
|
|
93
|
+
expect(result.codeblock).toBe(
|
|
94
|
+
"LINE 1: SELECT * FROM users WHERE name = 'John' AND age > 25 ^",
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should handle error with LINE but no caret", () => {
|
|
99
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
100
|
+
|
|
101
|
+
const error = "Error: Invalid query LINE 1: SELECT * FROM table";
|
|
102
|
+
|
|
103
|
+
const result = handleDuckdbError(error);
|
|
104
|
+
|
|
105
|
+
expect(result.errorType).toBe("Error");
|
|
106
|
+
expect(result.errorMessage).toBe("Invalid query");
|
|
107
|
+
expect(result.codeblock).toBe("LINE 1: SELECT * FROM table");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should trim whitespace from codeblock", () => {
|
|
111
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
112
|
+
|
|
113
|
+
const error = "Error: Something wrong LINE 1: SELECT * FROM table ^ ";
|
|
114
|
+
|
|
115
|
+
const result = handleDuckdbError(error);
|
|
116
|
+
|
|
117
|
+
expect(result.errorType).toBe("Error");
|
|
118
|
+
expect(result.errorMessage).toBe("Something wrong");
|
|
119
|
+
expect(result.codeblock).toBe("LINE 1: SELECT * FROM table ^");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should handle empty error message", () => {
|
|
123
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
124
|
+
|
|
125
|
+
const error = "";
|
|
126
|
+
|
|
127
|
+
const result = handleDuckdbError(error);
|
|
128
|
+
|
|
129
|
+
expect(result.errorType).toBe("");
|
|
130
|
+
expect(result.errorMessage).toBe("");
|
|
131
|
+
expect(result.codeblock).toBeUndefined();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { useAtom } from "jotai";
|
|
4
|
+
import { atomWithStorage } from "jotai/utils";
|
|
5
|
+
import { store } from "@/core/state/jotai";
|
|
6
|
+
|
|
7
|
+
const BASE_KEY = "marimo:notebook-sql-mode";
|
|
8
|
+
|
|
9
|
+
export type SQLMode = "validate" | "default";
|
|
10
|
+
|
|
11
|
+
const sqlModeAtom = atomWithStorage<SQLMode>(BASE_KEY, "default");
|
|
12
|
+
|
|
13
|
+
export function useSQLMode() {
|
|
14
|
+
const [sqlMode, setSQLMode] = useAtom(sqlModeAtom);
|
|
15
|
+
return { sqlMode, setSQLMode };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getSQLMode() {
|
|
19
|
+
return store.get(sqlModeAtom);
|
|
20
|
+
}
|
|
@@ -5,7 +5,7 @@ import { insertTab } from "@codemirror/commands";
|
|
|
5
5
|
import { type SQLDialect, type SQLNamespace, sql } from "@codemirror/lang-sql";
|
|
6
6
|
import type { EditorState, Extension } from "@codemirror/state";
|
|
7
7
|
import { Compartment } from "@codemirror/state";
|
|
8
|
-
import {
|
|
8
|
+
import { EditorView, keymap } from "@codemirror/view";
|
|
9
9
|
import type { SyntaxNode, TreeCursor } from "@lezer/common";
|
|
10
10
|
import { parser } from "@lezer/python";
|
|
11
11
|
import {
|
|
@@ -16,12 +16,19 @@ import {
|
|
|
16
16
|
} from "@marimo-team/codemirror-sql";
|
|
17
17
|
import { DuckDBDialect } from "@marimo-team/codemirror-sql/dialects";
|
|
18
18
|
import dedent from "string-dedent";
|
|
19
|
+
import { cellIdState } from "@/core/codemirror/cells/state";
|
|
19
20
|
import { getFeatureFlag } from "@/core/config/feature-flag";
|
|
20
21
|
import {
|
|
21
22
|
dataSourceConnectionsAtom,
|
|
22
23
|
setLatestEngineSelected,
|
|
23
24
|
} from "@/core/datasets/data-source-connections";
|
|
24
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
type ConnectionName,
|
|
27
|
+
DUCKDB_ENGINE,
|
|
28
|
+
INTERNAL_SQL_ENGINES,
|
|
29
|
+
} from "@/core/datasets/engines";
|
|
30
|
+
import { ValidateSQL } from "@/core/datasets/request-registry";
|
|
31
|
+
import type { ValidateSQLResult } from "@/core/kernel/messages";
|
|
25
32
|
import { store } from "@/core/state/jotai";
|
|
26
33
|
import { resolvedThemeAtom } from "@/theme/useTheme";
|
|
27
34
|
import { Logger } from "@/utils/Logger";
|
|
@@ -37,6 +44,11 @@ import {
|
|
|
37
44
|
tablesCompletionSource,
|
|
38
45
|
} from "./completion-sources";
|
|
39
46
|
import { SCHEMA_CACHE } from "./completion-store";
|
|
47
|
+
import { getSQLMode } from "./sql-mode";
|
|
48
|
+
import {
|
|
49
|
+
clearSqlValidationError,
|
|
50
|
+
setSqlValidationError,
|
|
51
|
+
} from "./validation-errors";
|
|
40
52
|
|
|
41
53
|
const DEFAULT_DIALECT = DuckDBDialect;
|
|
42
54
|
const DEFAULT_PARSER_DIALECT = "DuckDB";
|
|
@@ -64,12 +76,15 @@ export class SQLLanguageAdapter
|
|
|
64
76
|
{
|
|
65
77
|
readonly type = "sql";
|
|
66
78
|
sqlLinterEnabled: boolean;
|
|
79
|
+
sqlModeEnabled: boolean;
|
|
67
80
|
|
|
68
81
|
constructor() {
|
|
69
82
|
try {
|
|
70
83
|
this.sqlLinterEnabled = getFeatureFlag("sql_linter");
|
|
84
|
+
this.sqlModeEnabled = getFeatureFlag("sql_mode");
|
|
71
85
|
} catch {
|
|
72
86
|
this.sqlLinterEnabled = false;
|
|
87
|
+
this.sqlModeEnabled = false;
|
|
73
88
|
}
|
|
74
89
|
}
|
|
75
90
|
|
|
@@ -265,6 +280,10 @@ export class SQLLanguageAdapter
|
|
|
265
280
|
);
|
|
266
281
|
}
|
|
267
282
|
|
|
283
|
+
if (this.sqlModeEnabled) {
|
|
284
|
+
extensions.push(sqlValidationExtension());
|
|
285
|
+
}
|
|
286
|
+
|
|
268
287
|
return extensions;
|
|
269
288
|
}
|
|
270
289
|
}
|
|
@@ -315,9 +334,14 @@ function getSchema(view: EditorView): SQLNamespace {
|
|
|
315
334
|
function guessParserDialect(state: EditorState): ParserDialects | null {
|
|
316
335
|
const metadata = getSQLMetadata(state);
|
|
317
336
|
const connectionName = metadata.engine;
|
|
337
|
+
return connectionNameToParserDialect(connectionName);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function connectionNameToParserDialect(
|
|
341
|
+
connectionName: ConnectionName,
|
|
342
|
+
): ParserDialects | null {
|
|
318
343
|
const dialect =
|
|
319
344
|
SCHEMA_CACHE.getInternalDialect(connectionName)?.toLowerCase();
|
|
320
|
-
|
|
321
345
|
switch (dialect) {
|
|
322
346
|
case "postgresql":
|
|
323
347
|
case "postgres":
|
|
@@ -543,3 +567,66 @@ function safeDedent(code: string): string {
|
|
|
543
567
|
return code;
|
|
544
568
|
}
|
|
545
569
|
}
|
|
570
|
+
|
|
571
|
+
function sqlValidationExtension(): Extension {
|
|
572
|
+
let debounceTimeout: NodeJS.Timeout | null = null;
|
|
573
|
+
let lastValidationRequest: string | null = null;
|
|
574
|
+
|
|
575
|
+
return EditorView.updateListener.of((update) => {
|
|
576
|
+
const sqlMode = getSQLMode();
|
|
577
|
+
if (sqlMode !== "validate") {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const metadata = getSQLMetadata(update.state);
|
|
582
|
+
const connectionName = metadata.engine;
|
|
583
|
+
if (!INTERNAL_SQL_ENGINES.has(connectionName)) {
|
|
584
|
+
// Currently only internal engines are supported
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (!update.docChanged) {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const doc = update.state.doc;
|
|
593
|
+
const sqlContent = doc.toString();
|
|
594
|
+
|
|
595
|
+
// Clear existing timeout
|
|
596
|
+
if (debounceTimeout) {
|
|
597
|
+
clearTimeout(debounceTimeout);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Debounce the validation call
|
|
601
|
+
debounceTimeout = setTimeout(async () => {
|
|
602
|
+
// Skip if content hasn't changed since last validation
|
|
603
|
+
if (lastValidationRequest === sqlContent) {
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
lastValidationRequest = sqlContent;
|
|
608
|
+
const cellId = update.view.state.facet(cellIdState);
|
|
609
|
+
|
|
610
|
+
if (sqlContent === "") {
|
|
611
|
+
clearSqlValidationError(cellId);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
try {
|
|
616
|
+
const result: ValidateSQLResult = await ValidateSQL.request({
|
|
617
|
+
engine: connectionName,
|
|
618
|
+
query: sqlContent,
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
if (result.error) {
|
|
622
|
+
const dialect = connectionNameToParserDialect(connectionName);
|
|
623
|
+
setSqlValidationError({ cellId, error: result.error, dialect });
|
|
624
|
+
} else {
|
|
625
|
+
clearSqlValidationError(cellId);
|
|
626
|
+
}
|
|
627
|
+
} catch (error) {
|
|
628
|
+
Logger.warn("Failed to validate SQL", { error });
|
|
629
|
+
}
|
|
630
|
+
}, 300);
|
|
631
|
+
});
|
|
632
|
+
}
|