@marimo-team/islands 0.16.1 → 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/{ConnectedDataExplorerComponent-DyqLQGPc.js → ConnectedDataExplorerComponent-KvtsEmcw.js} +2 -2
- package/dist/{ImageComparisonComponent-CQDGJfUA.js → ImageComparisonComponent-R2tjqLSx.js} +1 -1
- package/dist/{_baseUniq-B2Nna6Kt.js → _baseUniq-BykZEXIq.js} +1 -1
- package/dist/{any-language-editor-D-wq0tOG.js → any-language-editor-CMt4Y6oz.js} +1 -1
- package/dist/{architectureDiagram-W76B3OCA-C6tdnMBf.js → architectureDiagram-W76B3OCA-mQ3sJdEW.js} +4 -4
- package/dist/{blockDiagram-QIGZ2CNN-IagL8LCN.js → blockDiagram-QIGZ2CNN-BxLRv5EM.js} +5 -5
- package/dist/{c4Diagram-FPNF74CW-D3_lIWUP.js → c4Diagram-FPNF74CW-Cfz16aWq.js} +2 -2
- package/dist/{channel-DCJI_DKk.js → channel-zr1uJJ5g.js} +1 -1
- package/dist/{chunk-4BX2VUAB-B2DrODwN.js → chunk-4BX2VUAB-B8iHvpDe.js} +1 -1
- package/dist/{chunk-55IACEB6-BUWDsQ-t.js → chunk-55IACEB6-CJs4dL1H.js} +1 -1
- package/dist/{chunk-FMBD7UC4-BExPNFv1.js → chunk-FMBD7UC4-C5irHg20.js} +1 -1
- package/dist/{chunk-K7UQS3LO-Cixi-Yko.js → chunk-K7UQS3LO-B2XW75WS.js} +4 -4
- package/dist/{chunk-QN33PNHL-B83MtvER.js → chunk-QN33PNHL-BtwctqGa.js} +1 -1
- package/dist/{chunk-QZHKN3VN-CXvbu85X.js → chunk-QZHKN3VN-Sb8ZD0FY.js} +1 -1
- package/dist/{chunk-TVAH2DTR-CpiumCHg.js → chunk-TVAH2DTR-CEJ5zkLX.js} +3 -3
- package/dist/{chunk-TZMSLE5B-DIzaZjcI.js → chunk-TZMSLE5B-Ccm4T92V.js} +1 -1
- package/dist/{classDiagram-v2-RKCZMP56-DyN5HPdk.js → classDiagram-KNZD7YFC-DWTbT0ww.js} +2 -2
- package/dist/{classDiagram-KNZD7YFC-DyN5HPdk.js → classDiagram-v2-RKCZMP56-DWTbT0ww.js} +2 -2
- package/dist/{clone-DrJYap2i.js → clone-CDDiMerE.js} +1 -1
- package/dist/{cose-bilkent-S5V4N54A-D39b4WrQ.js → cose-bilkent-S5V4N54A-B74aLjZ_.js} +2 -2
- package/dist/{dagre-5GWH7T2D-BLjRxDpS.js → dagre-5GWH7T2D-Cie_wUzI.js} +6 -6
- package/dist/{data-grid-overlay-editor-DTALqerV.js → data-grid-overlay-editor-OsCMRzfP.js} +2 -2
- package/dist/{diagram-N5W7TBWH-MM8AIKGR.js → diagram-N5W7TBWH-CCeFeV2B.js} +5 -5
- package/dist/{diagram-QEK2KX5R-BZGarWuJ.js → diagram-QEK2KX5R-BPROiVV7.js} +3 -3
- package/dist/{diagram-S2PKOQOG-CnPinN9Q.js → diagram-S2PKOQOG-BkLFbUa_.js} +3 -3
- package/dist/{dockerfile-U8DnCJ4X.js → dockerfile-CuJXUZQ_.js} +1 -1
- package/dist/{erDiagram-AWTI2OKA-CvDVbxOO.js → erDiagram-AWTI2OKA-CpBWOTMK.js} +4 -4
- package/dist/{flowDiagram-PVAE7QVJ-C2uuBTZS.js → flowDiagram-PVAE7QVJ-C_X4bmq3.js} +5 -5
- package/dist/{ganttDiagram-OWAHRB6G-BEff10RF.js → ganttDiagram-OWAHRB6G-CruldwEp.js} +4 -4
- package/dist/{gitGraphDiagram-NY62KEGX-wggu0kb2.js → gitGraphDiagram-NY62KEGX-ZJnVxaQP.js} +4 -4
- package/dist/{glide-data-editor-Bqh5_dzJ.js → glide-data-editor-CnOBht4I.js} +3 -3
- package/dist/{graph-DKpp_wzf.js → graph-DjTtWtcG.js} +3 -3
- package/dist/{index-DzJ_YPCG.js → index-CZ9vIBEc.js} +3 -3
- package/dist/{index-DdfF_cLK.js → index-DSpjUDnr.js} +1 -1
- package/dist/{index-DW0BCGJE.js → index-DeOkA9fC.js} +1 -1
- package/dist/{index-4XruEJkp.js → index-saLjL5eo.js} +1 -1
- package/dist/{infoDiagram-STP46IZ2-DF7KW-Op.js → infoDiagram-STP46IZ2-RZgl96nR.js} +2 -2
- package/dist/{journeyDiagram-BIP6EPQ6-B_jmhmqd.js → journeyDiagram-BIP6EPQ6-9ytZy-zY.js} +3 -3
- package/dist/{kanban-definition-6OIFK2YF-B-M9FTyw.js → kanban-definition-6OIFK2YF-CpjPWkgX.js} +2 -2
- package/dist/{layout-C4oVYZZD.js → layout-BAvhX25D.js} +4 -4
- package/dist/{linear-C-HCGr0T.js → linear-BKZuvJrK.js} +1 -1
- package/dist/{main-B9x2-9f2.js → main-pE28kbH0.js} +37316 -36749
- package/dist/main.js +1 -1
- package/dist/{mermaid-BE4cM3Qs.js → mermaid-BGd7kEsf.js} +30 -30
- package/dist/{min-DTpHJ698.js → min-DmsBJf5S.js} +2 -2
- package/dist/{mindmap-definition-Q6HEUPPD-Cpd-hO1E.js → mindmap-definition-Q6HEUPPD-BerypnVD.js} +3 -3
- package/dist/{number-overlay-editor-CvURA2Ud.js → number-overlay-editor-A7ZQ6sSs.js} +2 -2
- package/dist/{pieDiagram-ADFJNKIX-D9f_f6fn.js → pieDiagram-ADFJNKIX-ChcQLZM2.js} +3 -3
- package/dist/{quadrantDiagram-LMRXKWRM-DgllE7xw.js → quadrantDiagram-LMRXKWRM-BOMxo-5G.js} +2 -2
- package/dist/{react-plotly-BU-JRJSi.js → react-plotly-8f0vAPdB.js} +1 -1
- package/dist/{requirementDiagram-4UW4RH46-Dk_G8eUb.js → requirementDiagram-4UW4RH46-rql0vkIr.js} +3 -3
- package/dist/{sankeyDiagram-GR3RE2ED-BhLIhDc1.js → sankeyDiagram-GR3RE2ED-BMz3hqw4.js} +1 -1
- package/dist/{sequenceDiagram-C3RYC4MD-DHoZdMFJ.js → sequenceDiagram-C3RYC4MD-DFK39LUK.js} +3 -3
- package/dist/{slides-component-DXAgdf7K.js → slides-component-BNsZuUgg.js} +1 -1
- package/dist/{stateDiagram-KXAO66HF-C1Ie-7Xf.js → stateDiagram-KXAO66HF-B_3SWOCl.js} +4 -4
- package/dist/{stateDiagram-v2-UMBNRL4Z--CRuIHtM.js → stateDiagram-v2-UMBNRL4Z-BWAYN6aU.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/{time-yQjlGPwa.js → time-DCvYzQ5t.js} +2 -2
- package/dist/{timeline-definition-XQNQX7LJ-D_PjxB1B.js → timeline-definition-XQNQX7LJ-CSbxJ5mV.js} +1 -1
- package/dist/{treemap-75Q7IDZK--NYqQjUZ.js → treemap-75Q7IDZK-LMXGKln_.js} +5 -5
- package/dist/{vega-component-CCUOMM5K.js → vega-component-CRmon7pH.js} +2 -2
- package/dist/{xychartDiagram-6GGTOJPD-WLKsEnzs.js → xychartDiagram-6GGTOJPD-DaUD4dq3.js} +2 -2
- package/package.json +3 -3
- package/src/__mocks__/requests.ts +1 -0
- package/src/components/data-table/__tests__/columns.test.tsx +38 -0
- package/src/components/data-table/cell-hover-template/feature.ts +1 -1
- package/src/components/data-table/cell-hover-template/types.ts +1 -1
- package/src/components/data-table/columns.tsx +21 -2
- package/src/components/data-table/renderers.tsx +16 -8
- package/src/components/data-table/schemas.ts +16 -0
- package/src/components/editor/Cell.tsx +2 -0
- package/src/components/editor/errors/sql-validation-errors.tsx +34 -0
- package/src/components/editor/output/ConsoleOutput.tsx +13 -1
- package/src/components/editor/output/MarimoErrorOutput.tsx +60 -1
- package/src/core/ai/context/providers/cell-output.ts +1 -18
- 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/config/feature-flag.tsx +3 -1
- package/src/core/datasets/request-registry.ts +17 -1
- package/src/core/islands/bridge.ts +1 -0
- package/src/core/islands/main.ts +1 -0
- package/src/core/kernel/messages.ts +1 -0
- 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/wasm/bridge.ts +5 -0
- package/src/core/websocket/useMarimoWebSocket.tsx +4 -0
- package/src/plugins/core/registerReactComponent.tsx +23 -19
- package/src/plugins/impl/DataTablePlugin.tsx +11 -4
- package/src/plugins/impl/data-frames/DataFramePlugin.tsx +17 -5
- package/src/stories/dataframe.stories.tsx +2 -0
- package/src/utils/__tests__/dom.test.ts +167 -0
- package/src/utils/dom.ts +55 -0
|
@@ -36,6 +36,10 @@ import {
|
|
|
36
36
|
import { usePanelOwnership } from "@/components/data-table/hooks/use-panel-ownership";
|
|
37
37
|
import { LoadingTable } from "@/components/data-table/loading-table";
|
|
38
38
|
import { RowViewerPanel } from "@/components/data-table/row-viewer-panel/row-viewer";
|
|
39
|
+
import {
|
|
40
|
+
type DownloadAsArgs,
|
|
41
|
+
DownloadAsSchema,
|
|
42
|
+
} from "@/components/data-table/schemas";
|
|
39
43
|
import {
|
|
40
44
|
type BinValues,
|
|
41
45
|
type ColumnHeaderStats,
|
|
@@ -182,6 +186,7 @@ interface Data<T> {
|
|
|
182
186
|
freezeColumnsRight?: string[];
|
|
183
187
|
textJustifyColumns?: Record<string, "left" | "center" | "right">;
|
|
184
188
|
wrappedColumns?: string[];
|
|
189
|
+
headerTooltip?: Record<string, string>;
|
|
185
190
|
totalColumns: number;
|
|
186
191
|
maxColumns: number | "all";
|
|
187
192
|
hasStableRowId: boolean;
|
|
@@ -190,7 +195,7 @@ interface Data<T> {
|
|
|
190
195
|
|
|
191
196
|
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
192
197
|
type DataTableFunctions = {
|
|
193
|
-
download_as:
|
|
198
|
+
download_as: DownloadAsArgs;
|
|
194
199
|
get_column_summaries: <T>(
|
|
195
200
|
opts: ColumnSummariesArgs,
|
|
196
201
|
) => Promise<ColumnSummaries<T>>;
|
|
@@ -249,6 +254,7 @@ export const DataTablePlugin = createPlugin<S>("marimo-table")
|
|
|
249
254
|
.record(z.enum(["left", "center", "right"]))
|
|
250
255
|
.optional(),
|
|
251
256
|
wrappedColumns: z.array(z.string()).optional(),
|
|
257
|
+
headerTooltip: z.record(z.string()).optional(),
|
|
252
258
|
fieldTypes: columnToFieldTypesSchema.nullish(),
|
|
253
259
|
totalColumns: z.number(),
|
|
254
260
|
maxColumns: z.union([z.number(), z.literal("all")]).default("all"),
|
|
@@ -263,9 +269,7 @@ export const DataTablePlugin = createPlugin<S>("marimo-table")
|
|
|
263
269
|
}),
|
|
264
270
|
)
|
|
265
271
|
.withFunctions<DataTableFunctions>({
|
|
266
|
-
download_as:
|
|
267
|
-
.input(z.object({ format: z.enum(["csv", "json", "parquet"]) }))
|
|
268
|
-
.output(z.string()),
|
|
272
|
+
download_as: DownloadAsSchema,
|
|
269
273
|
get_column_summaries: rpc
|
|
270
274
|
.input(z.object({ precompute: z.boolean() }))
|
|
271
275
|
.output(
|
|
@@ -706,6 +710,7 @@ const DataTableComponent = ({
|
|
|
706
710
|
freezeColumnsRight,
|
|
707
711
|
textJustifyColumns,
|
|
708
712
|
wrappedColumns,
|
|
713
|
+
headerTooltip,
|
|
709
714
|
totalColumns,
|
|
710
715
|
get_row_ids,
|
|
711
716
|
cellStyles,
|
|
@@ -779,6 +784,7 @@ const DataTableComponent = ({
|
|
|
779
784
|
fieldTypes: memoizedClampedFieldTypes,
|
|
780
785
|
textJustifyColumns: memoizedTextJustifyColumns,
|
|
781
786
|
wrappedColumns: memoizedWrappedColumns,
|
|
787
|
+
headerTooltip: headerTooltip,
|
|
782
788
|
// Only show data types if they are explicitly set
|
|
783
789
|
showDataTypes: showDataTypes,
|
|
784
790
|
calculateTopKRows: calculate_top_k_rows,
|
|
@@ -791,6 +797,7 @@ const DataTableComponent = ({
|
|
|
791
797
|
memoizedClampedFieldTypes,
|
|
792
798
|
memoizedTextJustifyColumns,
|
|
793
799
|
memoizedWrappedColumns,
|
|
800
|
+
headerTooltip,
|
|
794
801
|
calculate_top_k_rows,
|
|
795
802
|
],
|
|
796
803
|
);
|
|
@@ -4,6 +4,10 @@ import { isEqual } from "lodash-es";
|
|
|
4
4
|
import { Code2Icon, DatabaseIcon, FunctionSquareIcon } from "lucide-react";
|
|
5
5
|
import { type JSX, memo, useEffect, useRef, useState } from "react";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
+
import {
|
|
8
|
+
type DownloadAsArgs,
|
|
9
|
+
DownloadAsSchema,
|
|
10
|
+
} from "@/components/data-table/schemas";
|
|
7
11
|
import type { FieldTypesWithExternalType } from "@/components/data-table/types";
|
|
8
12
|
import { ReadonlyCode } from "@/components/editor/code/readonly-python-code";
|
|
9
13
|
import { Spinner } from "@/components/icons/spinner";
|
|
@@ -38,6 +42,7 @@ interface Data {
|
|
|
38
42
|
label?: string | null;
|
|
39
43
|
columns: ColumnDataTypes;
|
|
40
44
|
pageSize: number;
|
|
45
|
+
showDownload: boolean;
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
@@ -67,6 +72,7 @@ type PluginFunctions = {
|
|
|
67
72
|
data: TableData<T>;
|
|
68
73
|
total_rows: number;
|
|
69
74
|
}>;
|
|
75
|
+
download_as: DownloadAsArgs;
|
|
70
76
|
};
|
|
71
77
|
|
|
72
78
|
// Value is selection, but it is not currently exposed to the user
|
|
@@ -77,13 +83,14 @@ export const DataFramePlugin = createPlugin<S>("marimo-dataframe")
|
|
|
77
83
|
z.object({
|
|
78
84
|
label: z.string().nullish(),
|
|
79
85
|
pageSize: z.number().default(5),
|
|
86
|
+
showDownload: z.boolean().default(true),
|
|
80
87
|
columns: z
|
|
81
88
|
.array(z.tuple([z.string().or(z.number()), z.string(), z.string()]))
|
|
82
89
|
.transform((value) => {
|
|
83
90
|
const map = new Map<ColumnId, string>();
|
|
84
|
-
value.forEach(([key, dataType]) =>
|
|
85
|
-
map.set(key as ColumnId, dataType as DataType)
|
|
86
|
-
);
|
|
91
|
+
value.forEach(([key, dataType]) => {
|
|
92
|
+
map.set(key as ColumnId, dataType as DataType);
|
|
93
|
+
});
|
|
87
94
|
return map;
|
|
88
95
|
}),
|
|
89
96
|
}),
|
|
@@ -124,6 +131,7 @@ export const DataFramePlugin = createPlugin<S>("marimo-dataframe")
|
|
|
124
131
|
total_rows: z.number(),
|
|
125
132
|
}),
|
|
126
133
|
),
|
|
134
|
+
download_as: DownloadAsSchema,
|
|
127
135
|
})
|
|
128
136
|
.renderer((props) => (
|
|
129
137
|
<TableProviders>
|
|
@@ -141,6 +149,8 @@ interface DataTableProps extends Data, PluginFunctions {
|
|
|
141
149
|
value: S;
|
|
142
150
|
setValue: (value: S) => void;
|
|
143
151
|
host: HTMLElement;
|
|
152
|
+
showDownload: boolean;
|
|
153
|
+
download_as: DownloadAsArgs;
|
|
144
154
|
}
|
|
145
155
|
|
|
146
156
|
const EMPTY: Transformations = {
|
|
@@ -151,11 +161,13 @@ export const DataFrameComponent = memo(
|
|
|
151
161
|
({
|
|
152
162
|
columns,
|
|
153
163
|
pageSize,
|
|
164
|
+
showDownload,
|
|
154
165
|
value,
|
|
155
166
|
setValue,
|
|
156
167
|
get_dataframe,
|
|
157
168
|
get_column_values,
|
|
158
169
|
search,
|
|
170
|
+
download_as,
|
|
159
171
|
host,
|
|
160
172
|
}: DataTableProps): JSX.Element => {
|
|
161
173
|
const { data, error, isPending } = useAsyncData(
|
|
@@ -270,8 +282,8 @@ export const DataFrameComponent = memo(
|
|
|
270
282
|
pagination={true}
|
|
271
283
|
fieldTypes={field_types}
|
|
272
284
|
rowHeaders={row_headers || Arrays.EMPTY}
|
|
273
|
-
showDownload={
|
|
274
|
-
download_as={
|
|
285
|
+
showDownload={showDownload}
|
|
286
|
+
download_as={download_as}
|
|
275
287
|
enableSearch={false}
|
|
276
288
|
showFilters={false}
|
|
277
289
|
search={search}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, test } from "vitest";
|
|
4
|
+
import { ansiToPlainText, parseHtmlContent } from "../dom";
|
|
5
|
+
|
|
6
|
+
describe("parseHtmlContent", () => {
|
|
7
|
+
test("strips HTML tags and returns plain text", () => {
|
|
8
|
+
const htmlString =
|
|
9
|
+
'<span style="color: red;">Error: Something went wrong</span>';
|
|
10
|
+
const result = parseHtmlContent(htmlString);
|
|
11
|
+
expect(result).toMatchInlineSnapshot(`"Error: Something went wrong"`);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("handles ANSI color span tags", () => {
|
|
15
|
+
const htmlString =
|
|
16
|
+
'<span style="color:#d03050;">ERROR</span>: <span style="color:#8ad03a;">File not found</span>';
|
|
17
|
+
const result = parseHtmlContent(htmlString);
|
|
18
|
+
expect(result).toMatchInlineSnapshot(`"ERROR: File not found"`);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("normalizes whitespace", () => {
|
|
22
|
+
const htmlString = "<span> Multiple \n\n spaces and \t tabs </span>";
|
|
23
|
+
const result = parseHtmlContent(htmlString);
|
|
24
|
+
expect(result).toMatchInlineSnapshot(`
|
|
25
|
+
" Multiple
|
|
26
|
+
|
|
27
|
+
spaces and tabs"
|
|
28
|
+
`);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("handles empty HTML", () => {
|
|
32
|
+
const htmlString = "";
|
|
33
|
+
const result = parseHtmlContent(htmlString);
|
|
34
|
+
expect(result).toMatchInlineSnapshot(`""`);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("handles plain text without HTML", () => {
|
|
38
|
+
const htmlString = "Simple error message";
|
|
39
|
+
const result = parseHtmlContent(htmlString);
|
|
40
|
+
expect(result).toMatchInlineSnapshot(`"Simple error message"`);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("handles nested HTML elements", () => {
|
|
44
|
+
const htmlString =
|
|
45
|
+
'<div><span>Traceback:</span><pre><code> File "test.py", line 1\n print("hello"</code></pre></div>';
|
|
46
|
+
const result = parseHtmlContent(htmlString);
|
|
47
|
+
expect(result).toMatchInlineSnapshot(`
|
|
48
|
+
"Traceback: File "test.py", line 1
|
|
49
|
+
print("hello""
|
|
50
|
+
`);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("handles complex ANSI-converted HTML with styles", () => {
|
|
54
|
+
const htmlString =
|
|
55
|
+
'<span style="background:#fff;color:#000"> File "</span><span style="background:#fff;color:#0000ff">test.py</span><span style="background:#fff;color:#000">", line </span><span style="background:#fff;color:#008000">1</span>';
|
|
56
|
+
const result = parseHtmlContent(htmlString);
|
|
57
|
+
expect(result).toMatchInlineSnapshot(`" File "test.py", line 1"`);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("ansiToPlainText", () => {
|
|
62
|
+
test("converts ANSI color codes to plain text", () => {
|
|
63
|
+
const ansiString = "\x1b[31mError:\x1b[0m Something went wrong";
|
|
64
|
+
const result = ansiToPlainText(ansiString);
|
|
65
|
+
expect(result).toMatchInlineSnapshot(`"Error: Something went wrong"`);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("handles multiple ANSI color codes", () => {
|
|
69
|
+
const ansiString =
|
|
70
|
+
"\x1b[32mSUCCESS:\x1b[0m \x1b[34mOperation completed\x1b[0m successfully";
|
|
71
|
+
const result = ansiToPlainText(ansiString);
|
|
72
|
+
expect(result).toMatchInlineSnapshot(
|
|
73
|
+
`"SUCCESS: Operation completed successfully"`,
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("handles ANSI bold and color combinations", () => {
|
|
78
|
+
const ansiString =
|
|
79
|
+
"\x1b[1;31mBOLD RED ERROR:\x1b[0m \x1b[33mWarning message\x1b[0m";
|
|
80
|
+
const result = ansiToPlainText(ansiString);
|
|
81
|
+
expect(result).toMatchInlineSnapshot(`"BOLD RED ERROR: Warning message"`);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("handles Python traceback with ANSI codes", () => {
|
|
85
|
+
const ansiString =
|
|
86
|
+
"\x1b[0;36m File \"\x1b[0m\x1b[0;32mtest.py\x1b[0m\x1b[0;36m\", line \x1b[0m\x1b[0;32m1\x1b[0m\x1b[0;36m, in \x1b[0m\x1b[0;35m<module>\x1b[0m\n\x1b[0;31mNameError\x1b[0m: name 'undefined_var' is not defined";
|
|
87
|
+
const result = ansiToPlainText(ansiString);
|
|
88
|
+
expect(result).toMatchInlineSnapshot(`
|
|
89
|
+
" File "test.py", line 1, in <module>
|
|
90
|
+
NameError: name 'undefined_var' is not defined"
|
|
91
|
+
`);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("handles error messages with background colors", () => {
|
|
95
|
+
const ansiString =
|
|
96
|
+
"\x1b[41;37m CRITICAL ERROR \x1b[0m \x1b[31mSystem failure detected\x1b[0m";
|
|
97
|
+
const result = ansiToPlainText(ansiString);
|
|
98
|
+
expect(result).toMatchInlineSnapshot(
|
|
99
|
+
`" CRITICAL ERROR System failure detected"`,
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("handles complex stack trace with mixed formatting", () => {
|
|
104
|
+
const ansiString =
|
|
105
|
+
'Traceback (most recent call last):\n \x1b[36mFile "\x1b[32m/path/to/file.py\x1b[36m", line \x1b[32m42\x1b[36m, in \x1b[35mfunction_name\x1b[0m\n \x1b[31mraise ValueError("Something went wrong")\x1b[0m\n\x1b[31mValueError\x1b[0m: Something went wrong';
|
|
106
|
+
const result = ansiToPlainText(ansiString);
|
|
107
|
+
expect(result).toMatchInlineSnapshot(`
|
|
108
|
+
"Traceback (most recent call last):
|
|
109
|
+
File "/path/to/file.py", line 42, in function_name
|
|
110
|
+
raise ValueError("Something went wrong")
|
|
111
|
+
ValueError: Something went wrong"
|
|
112
|
+
`);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("handles empty string", () => {
|
|
116
|
+
const ansiString = "";
|
|
117
|
+
const result = ansiToPlainText(ansiString);
|
|
118
|
+
expect(result).toMatchInlineSnapshot(`""`);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("handles plain text without ANSI codes", () => {
|
|
122
|
+
const ansiString = "Plain error message without colors";
|
|
123
|
+
const result = ansiToPlainText(ansiString);
|
|
124
|
+
expect(result).toMatchInlineSnapshot(
|
|
125
|
+
`"Plain error message without colors"`,
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("handles whitespace and newlines correctly", () => {
|
|
130
|
+
const ansiString =
|
|
131
|
+
"\x1b[31m Error: \x1b[0m\n\n \x1b[33m Warning \x1b[0m ";
|
|
132
|
+
const result = ansiToPlainText(ansiString);
|
|
133
|
+
expect(result).toMatchInlineSnapshot(`
|
|
134
|
+
" Error:
|
|
135
|
+
|
|
136
|
+
Warning"
|
|
137
|
+
`);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("handles JavaScript error stack trace", () => {
|
|
141
|
+
const ansiString =
|
|
142
|
+
"\x1b[31mReferenceError\x1b[0m: \x1b[33mvariable\x1b[0m is not defined\n at \x1b[36mObject.<anonymous>\x1b[0m (\x1b[32m/path/to/script.js\x1b[0m:\x1b[33m5\x1b[0m:\x1b[33m1\x1b[0m)";
|
|
143
|
+
const result = ansiToPlainText(ansiString);
|
|
144
|
+
expect(result).toMatchInlineSnapshot(`
|
|
145
|
+
"ReferenceError: variable is not defined
|
|
146
|
+
at Object.<anonymous> (/path/to/script.js:5:1)"
|
|
147
|
+
`);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("handles Rust panic with ANSI formatting", () => {
|
|
151
|
+
const ansiString =
|
|
152
|
+
"thread '\x1b[32mmain\x1b[0m' panicked at '\x1b[31massertion failed: `(left == right)`\x1b[0m'\n \x1b[36mleft\x1b[0m: `\x1b[33m5\x1b[0m`\n \x1b[36mright\x1b[0m: `\x1b[33m10\x1b[0m`";
|
|
153
|
+
const result = ansiToPlainText(ansiString);
|
|
154
|
+
expect(result).toMatchInlineSnapshot(`
|
|
155
|
+
"thread 'main' panicked at 'assertion failed: \`(left == right)\`'
|
|
156
|
+
left: \`5\`
|
|
157
|
+
right: \`10\`"
|
|
158
|
+
`);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("handles mix of 8-bit and 256-color ANSI codes", () => {
|
|
162
|
+
const ansiString =
|
|
163
|
+
"\x1b[38;5;196mBright Red\x1b[0m and \x1b[38;5;46mBright Green\x1b[0m text";
|
|
164
|
+
const result = ansiToPlainText(ansiString);
|
|
165
|
+
expect(result).toMatchInlineSnapshot(`"Bright Red and Bright Green text"`);
|
|
166
|
+
});
|
|
167
|
+
});
|
package/src/utils/dom.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { AnsiUp } from "ansi_up";
|
|
4
|
+
import { Logger } from "./Logger";
|
|
5
|
+
|
|
6
|
+
// Create a shared AnsiUp instance
|
|
7
|
+
const ansiUp = new AnsiUp();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Extracts plain text content from HTML by removing all HTML tags and normalizing whitespace.
|
|
11
|
+
*
|
|
12
|
+
* @param htmlString The HTML string to parse
|
|
13
|
+
* @returns Plain text content with HTML tags removed and whitespace normalized
|
|
14
|
+
*/
|
|
15
|
+
export function parseHtmlContent(htmlString: string): string {
|
|
16
|
+
try {
|
|
17
|
+
// Create a temporary DOM element to parse HTML
|
|
18
|
+
const tempDiv = document.createElement("div");
|
|
19
|
+
tempDiv.innerHTML = htmlString;
|
|
20
|
+
|
|
21
|
+
// Extract text content, removing HTML tags
|
|
22
|
+
const textContent = tempDiv.textContent || tempDiv.innerText || "";
|
|
23
|
+
const lines = textContent.split("\n");
|
|
24
|
+
return lines.map((line) => line.trimEnd()).join("\n");
|
|
25
|
+
} catch (error) {
|
|
26
|
+
Logger.error("Error parsing HTML content:", error);
|
|
27
|
+
// If parsing fails, return the original string
|
|
28
|
+
return htmlString;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Converts ANSI escape sequences to plain text by first converting to HTML and then stripping HTML tags.
|
|
34
|
+
* This is useful for console output that may contain ANSI color codes or formatting.
|
|
35
|
+
*
|
|
36
|
+
* @param ansiString String that may contain ANSI escape sequences
|
|
37
|
+
* @returns Plain text with ANSI codes removed and HTML stripped
|
|
38
|
+
*/
|
|
39
|
+
export function ansiToPlainText(ansiString: string): string {
|
|
40
|
+
if (!ansiString) {
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
// Convert ANSI escape sequences to HTML
|
|
46
|
+
const htmlString = ansiUp.ansi_to_html(ansiString);
|
|
47
|
+
|
|
48
|
+
// Strip HTML tags and return clean text
|
|
49
|
+
return parseHtmlContent(htmlString);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
Logger.error("Error converting ANSI to plain text:", error);
|
|
52
|
+
// If conversion fails, return the original string
|
|
53
|
+
return ansiString;
|
|
54
|
+
}
|
|
55
|
+
}
|