@marimo-team/islands 0.23.9-dev4 → 0.23.9-dev6
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/{code-visibility-VZebNmSs.js → code-visibility-DILs4Gug.js} +919 -766
- package/dist/main.js +842 -840
- package/dist/{reveal-component-DZtPMEoM.js → reveal-component-Pq7C0LSU.js} +1 -1
- package/package.json +1 -1
- package/src/components/data-table/TableBottomBar.tsx +30 -6
- package/src/components/data-table/__tests__/TableBottomBar.test.tsx +73 -0
- package/src/components/data-table/__tests__/data-table.test.tsx +52 -1
- package/src/components/data-table/__tests__/header-items.test.tsx +47 -1
- package/src/components/data-table/__tests__/useColumnVisibility.test.ts +42 -0
- package/src/components/data-table/column-explorer-panel/column-explorer.tsx +7 -1
- package/src/components/data-table/column-header.tsx +2 -0
- package/src/components/data-table/columns.tsx +3 -4
- package/src/components/data-table/data-table.tsx +30 -0
- package/src/components/data-table/header-items.tsx +18 -1
- package/src/components/data-table/hooks/use-column-visibility.ts +42 -0
- package/src/components/data-table/pagination.tsx +16 -3
- package/src/components/editor/errors/mangled-local-chip.tsx +50 -0
- package/src/components/editor/output/MarimoErrorOutput.tsx +7 -2
- package/src/components/editor/output/MarimoTracebackOutput.tsx +31 -2
- package/src/plugins/impl/DataTablePlugin.tsx +4 -0
- package/src/utils/__tests__/local-variables.test.ts +132 -0
- package/src/utils/local-variables.ts +67 -0
|
@@ -17,15 +17,17 @@ import { Button } from "@/components/ui/button";
|
|
|
17
17
|
import { Kbd } from "@/components/ui/kbd";
|
|
18
18
|
import { ExternalLink } from "@/components/ui/links";
|
|
19
19
|
import type { CellId } from "@/core/cells/ids";
|
|
20
|
+
import { renderHTML } from "@/plugins/core/RenderHTML";
|
|
21
|
+
import { splitMangledLocals } from "@/utils/local-variables";
|
|
20
22
|
import type { MarimoError } from "../../../core/kernel/messages";
|
|
21
23
|
import { cn } from "../../../utils/cn";
|
|
22
24
|
import { Alert, AlertTitle } from "../../ui/alert";
|
|
23
25
|
import { openPackageManager } from "../chrome/panels/packages-utils";
|
|
24
26
|
import { useChromeActions } from "../chrome/state";
|
|
25
27
|
import { AutoFixButton } from "../errors/auto-fix";
|
|
28
|
+
import { MangledSegments } from "../errors/mangled-local-chip";
|
|
26
29
|
import { CellLinkError } from "../links/cell-link";
|
|
27
30
|
import { processTextForUrls } from "./console/text-rendering";
|
|
28
|
-
import { renderHTML } from "@/plugins/core/RenderHTML";
|
|
29
31
|
|
|
30
32
|
const Tip = (props: {
|
|
31
33
|
title?: string;
|
|
@@ -454,10 +456,13 @@ export const MarimoErrorOutput = ({
|
|
|
454
456
|
error.exception_type === "NameError" &&
|
|
455
457
|
error.msg.startsWith("name '_")
|
|
456
458
|
) {
|
|
459
|
+
const segments = splitMangledLocals(error.msg);
|
|
457
460
|
return (
|
|
458
461
|
<li className="my-2" key={`exception-${idx}`}>
|
|
459
462
|
<div>
|
|
460
|
-
<p className="text-muted-foreground">
|
|
463
|
+
<p className="text-muted-foreground">
|
|
464
|
+
<MangledSegments segments={segments} />
|
|
465
|
+
</p>
|
|
461
466
|
<p className="text-muted-foreground mt-2">
|
|
462
467
|
Variables prefixed with an underscore are local to a cell{" "}
|
|
463
468
|
(
|
|
@@ -36,6 +36,10 @@ import { isWasm } from "@/core/wasm/utils";
|
|
|
36
36
|
import { renderHTML } from "@/plugins/core/RenderHTML";
|
|
37
37
|
import { sanitizeHtml } from "@/plugins/core/sanitize-html";
|
|
38
38
|
import { copyToClipboard } from "@/utils/copy";
|
|
39
|
+
import {
|
|
40
|
+
containsMangledLocal,
|
|
41
|
+
splitMangledLocals,
|
|
42
|
+
} from "@/utils/local-variables";
|
|
39
43
|
import {
|
|
40
44
|
elementContainsMarimoCellFile,
|
|
41
45
|
extractAllTracebackInfo,
|
|
@@ -43,6 +47,7 @@ import {
|
|
|
43
47
|
} from "@/utils/traceback";
|
|
44
48
|
import { cn } from "../../../utils/cn";
|
|
45
49
|
import { AIFixButton } from "../errors/auto-fix";
|
|
50
|
+
import { MangledSegments } from "../errors/mangled-local-chip";
|
|
46
51
|
import { CellLinkTraceback } from "../links/cell-link";
|
|
47
52
|
import type { OnRefactorWithAI } from "../Output";
|
|
48
53
|
|
|
@@ -64,7 +69,11 @@ export const MarimoTracebackOutput = ({
|
|
|
64
69
|
}: Props): JSX.Element => {
|
|
65
70
|
const htmlTraceback = renderHTML({
|
|
66
71
|
html: traceback,
|
|
67
|
-
additionalReplacements: [
|
|
72
|
+
additionalReplacements: [
|
|
73
|
+
replaceTracebackFilenames,
|
|
74
|
+
replaceTracebackPrefix,
|
|
75
|
+
replaceMangledLocal,
|
|
76
|
+
],
|
|
68
77
|
});
|
|
69
78
|
const [expanded, setExpanded] = useState(true);
|
|
70
79
|
|
|
@@ -94,6 +103,9 @@ export const MarimoTracebackOutput = ({
|
|
|
94
103
|
};
|
|
95
104
|
|
|
96
105
|
const [error, errorMessage] = lastTracebackLine.split(":", 2);
|
|
106
|
+
const errorMessageSegments = errorMessage
|
|
107
|
+
? splitMangledLocals(errorMessage)
|
|
108
|
+
: [];
|
|
97
109
|
|
|
98
110
|
return (
|
|
99
111
|
<div className="flex flex-col gap-2 min-w-full w-fit">
|
|
@@ -111,7 +123,7 @@ export const MarimoTracebackOutput = ({
|
|
|
111
123
|
/>
|
|
112
124
|
<div className="text-sm inline font-mono">
|
|
113
125
|
<span className="text-destructive">{error || "Error"}:</span>{" "}
|
|
114
|
-
{
|
|
126
|
+
<MangledSegments segments={errorMessageSegments} />
|
|
115
127
|
</div>
|
|
116
128
|
</div>
|
|
117
129
|
<AccordionContent className="text-muted-foreground px-4 pt-2 text-xs overflow-auto">
|
|
@@ -249,6 +261,23 @@ export const replaceTracebackFilenames = (domNode: DOMNode) => {
|
|
|
249
261
|
}
|
|
250
262
|
};
|
|
251
263
|
|
|
264
|
+
/**
|
|
265
|
+
* Replace any cell-local mangled name (`_cell_<id>_<name>`) inside a text
|
|
266
|
+
* node with a {@link MangledLocalChip}. The mangled name appears in both
|
|
267
|
+
* the final `NameError:` line and inside compiled-cell source lines because
|
|
268
|
+
* the compiler rewrites underscore-prefixed references at AST-visit time.
|
|
269
|
+
*/
|
|
270
|
+
export const replaceMangledLocal = (domNode: DOMNode) => {
|
|
271
|
+
if (!(domNode instanceof Text) || !domNode.nodeValue) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (!containsMangledLocal(domNode.nodeValue)) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const segments = splitMangledLocals(domNode.nodeValue);
|
|
278
|
+
return <MangledSegments segments={segments} />;
|
|
279
|
+
};
|
|
280
|
+
|
|
252
281
|
export const replaceTracebackPrefix = (domNode: DOMNode) => {
|
|
253
282
|
if (
|
|
254
283
|
domNode instanceof Text &&
|
|
@@ -190,6 +190,7 @@ interface Data<T> {
|
|
|
190
190
|
fieldTypes?: FieldTypesWithExternalType | null;
|
|
191
191
|
freezeColumnsLeft?: string[];
|
|
192
192
|
freezeColumnsRight?: string[];
|
|
193
|
+
hiddenColumns?: string[];
|
|
193
194
|
textJustifyColumns?: Record<string, "left" | "center" | "right">;
|
|
194
195
|
wrappedColumns?: string[];
|
|
195
196
|
headerTooltip?: Record<string, string>;
|
|
@@ -265,6 +266,7 @@ export const DataTablePlugin = createPlugin<S>("marimo-table")
|
|
|
265
266
|
rowHeaders: columnToFieldTypesSchema,
|
|
266
267
|
freezeColumnsLeft: z.array(z.string()).optional(),
|
|
267
268
|
freezeColumnsRight: z.array(z.string()).optional(),
|
|
269
|
+
hiddenColumns: z.array(z.string()).optional(),
|
|
268
270
|
textJustifyColumns: z
|
|
269
271
|
.record(z.string(), z.enum(["left", "center", "right"]))
|
|
270
272
|
.optional(),
|
|
@@ -814,6 +816,7 @@ const DataTableComponent = ({
|
|
|
814
816
|
reloading,
|
|
815
817
|
freezeColumnsLeft,
|
|
816
818
|
freezeColumnsRight,
|
|
819
|
+
hiddenColumns,
|
|
817
820
|
textJustifyColumns,
|
|
818
821
|
wrappedColumns,
|
|
819
822
|
headerTooltip,
|
|
@@ -1090,6 +1093,7 @@ const DataTableComponent = ({
|
|
|
1090
1093
|
onRowSelectionChange={handleRowSelectionChange}
|
|
1091
1094
|
freezeColumnsLeft={freezeColumnsLeft}
|
|
1092
1095
|
freezeColumnsRight={freezeColumnsRight}
|
|
1096
|
+
hiddenColumns={hiddenColumns}
|
|
1093
1097
|
onCellSelectionChange={handleCellSelectionChange}
|
|
1094
1098
|
getRowIds={get_row_ids}
|
|
1095
1099
|
toggleDisplayHeader={toggleDisplayHeader}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
containsMangledLocal,
|
|
5
|
+
splitMangledLocals,
|
|
6
|
+
unmangleLocal,
|
|
7
|
+
} from "../local-variables";
|
|
8
|
+
|
|
9
|
+
describe("unmangleLocal", () => {
|
|
10
|
+
it("extracts the cell id and original name", () => {
|
|
11
|
+
expect(unmangleLocal("_cell_Hbol_a")).toEqual({
|
|
12
|
+
cellId: "Hbol",
|
|
13
|
+
name: "_a",
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("handles names with multiple underscore segments", () => {
|
|
18
|
+
expect(unmangleLocal("_cell_Hbol_a_b")).toEqual({
|
|
19
|
+
cellId: "Hbol",
|
|
20
|
+
name: "_a_b",
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("returns null for non-mangled strings", () => {
|
|
25
|
+
expect(unmangleLocal("just_a_variable")).toBeNull();
|
|
26
|
+
expect(unmangleLocal("_private")).toBeNull();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("handles the single-underscore local", () => {
|
|
30
|
+
// `_` is a valid local name (variables.py:62-63), mangling to
|
|
31
|
+
// `_cell_<id>_` with no trailing suffix.
|
|
32
|
+
expect(unmangleLocal("_cell_Hbol_")).toEqual({
|
|
33
|
+
cellId: "Hbol",
|
|
34
|
+
name: "_",
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("handles UUID-style cell ids", () => {
|
|
39
|
+
// External / VSCode notebooks use `external_prefix()` which is a
|
|
40
|
+
// `uuid4()` (hyphenated).
|
|
41
|
+
expect(
|
|
42
|
+
unmangleLocal("_cell_c9bf9e57-1685-4c89-bafb-ff5af830be8a_a"),
|
|
43
|
+
).toEqual({
|
|
44
|
+
cellId: "c9bf9e57-1685-4c89-bafb-ff5af830be8a",
|
|
45
|
+
name: "_a",
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("does not match marimo cell file paths", () => {
|
|
50
|
+
// The compiled cell file is `__marimo__cell_<id>_.py` (two leading
|
|
51
|
+
// underscores, trailing `_` with no name); it must not be confused with a
|
|
52
|
+
// mangled local.
|
|
53
|
+
expect(unmangleLocal("__marimo__cell_Hbol_.py")).toBeNull();
|
|
54
|
+
expect(unmangleLocal("/tmp/marimo_42/__marimo__cell_Hbol_.py")).toBeNull();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("splitMangledLocals", () => {
|
|
59
|
+
it("returns a single text segment when nothing matches", () => {
|
|
60
|
+
expect(splitMangledLocals("plain text")).toEqual(["plain text"]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("splits a NameError message", () => {
|
|
64
|
+
expect(splitMangledLocals("name '_cell_Hbol_a' is not defined")).toEqual([
|
|
65
|
+
"name '",
|
|
66
|
+
{ cellId: "Hbol", name: "_a" },
|
|
67
|
+
"' is not defined",
|
|
68
|
+
]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("handles multiple mangled names in one string", () => {
|
|
72
|
+
expect(splitMangledLocals("_cell_AAAA_x and _cell_BBBB_y")).toEqual([
|
|
73
|
+
{ cellId: "AAAA", name: "_x" },
|
|
74
|
+
" and ",
|
|
75
|
+
{ cellId: "BBBB", name: "_y" },
|
|
76
|
+
]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("leaves the cell file path alone", () => {
|
|
80
|
+
const path = "/tmp/marimo_42/__marimo__cell_Hbol_.py";
|
|
81
|
+
expect(splitMangledLocals(path)).toEqual([path]);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("ignores `_cell_...` substrings preceded by `_`", () => {
|
|
85
|
+
// Mirrors `(?<!_)` in `variables.py`: a leading `_` (e.g. inside
|
|
86
|
+
// `__marimo__cell_<id>_<...>`) means this is not a mangle the compiler
|
|
87
|
+
// produced, so we must not demangle it.
|
|
88
|
+
const text = "see __marimo__cell_Hbol_a for details";
|
|
89
|
+
expect(splitMangledLocals(text)).toEqual([text]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("splits a UUID-style cell id", () => {
|
|
93
|
+
expect(
|
|
94
|
+
splitMangledLocals(
|
|
95
|
+
"name '_cell_c9bf9e57-1685-4c89-bafb-ff5af830be8a_a' is not defined",
|
|
96
|
+
),
|
|
97
|
+
).toEqual([
|
|
98
|
+
"name '",
|
|
99
|
+
{
|
|
100
|
+
cellId: "c9bf9e57-1685-4c89-bafb-ff5af830be8a",
|
|
101
|
+
name: "_a",
|
|
102
|
+
},
|
|
103
|
+
"' is not defined",
|
|
104
|
+
]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("splits the single-underscore local", () => {
|
|
108
|
+
expect(splitMangledLocals("name '_cell_Hbol_' is not defined")).toEqual([
|
|
109
|
+
"name '",
|
|
110
|
+
{ cellId: "Hbol", name: "_" },
|
|
111
|
+
"' is not defined",
|
|
112
|
+
]);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe("containsMangledLocal", () => {
|
|
117
|
+
it("detects mangled names", () => {
|
|
118
|
+
expect(containsMangledLocal("name '_cell_Hbol_a' is not defined")).toBe(
|
|
119
|
+
true,
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("ignores the cell file path", () => {
|
|
124
|
+
expect(containsMangledLocal("/tmp/marimo_42/__marimo__cell_Hbol_.py")).toBe(
|
|
125
|
+
false,
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("ignores `_cell_...` substrings preceded by `_`", () => {
|
|
130
|
+
expect(containsMangledLocal("see __marimo__cell_Hbol_a")).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import type { CellId } from "@/core/cells/ids";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The marimo compiler rewrites underscore-prefixed references inside a cell
|
|
6
|
+
* (which Python would treat as module-private) into `_cell_<cell_id>_<name>`
|
|
7
|
+
* so each cell gets its own private namespace. When such a reference fails at
|
|
8
|
+
* runtime (`NameError: name '_cell_Hbol_a' is not defined`), the mangled name
|
|
9
|
+
* leaks into the error UI. These helpers undo that mangling for display.
|
|
10
|
+
*
|
|
11
|
+
* Mirrors `marimo/_ast/variables.py`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// Matches `_cell_<cell_id><name>` for normal ids and UUIDs. The `[\w-]`
|
|
15
|
+
// id class admits hyphens; the `_\w*` name group admits the bare `_`
|
|
16
|
+
// local; the `(?<!_)` lookbehind skips `__marimo__cell_...` paths.
|
|
17
|
+
// Mirrors `_MANGLED_LOCAL_IN_TEXT_RE` in `variables.py`.
|
|
18
|
+
const MANGLED_LOCAL_BODY = String.raw`_cell_([^\W_][\w-]*?)(_\w*)`;
|
|
19
|
+
const MANGLED_LOCAL_PATTERN = String.raw`(?<!_)${MANGLED_LOCAL_BODY}`;
|
|
20
|
+
// Strict (whole-string) form for `unmangleLocal`; the leading `^` makes the
|
|
21
|
+
// lookbehind trivially satisfied, so use the bare body.
|
|
22
|
+
const ANCHORED_RE = new RegExp(`^${MANGLED_LOCAL_BODY}$`);
|
|
23
|
+
const UNANCHORED_RE = new RegExp(MANGLED_LOCAL_PATTERN);
|
|
24
|
+
const GLOBAL_RE = new RegExp(MANGLED_LOCAL_PATTERN, "g");
|
|
25
|
+
|
|
26
|
+
export interface UnmangledLocal {
|
|
27
|
+
cellId: CellId;
|
|
28
|
+
/** The original underscore-prefixed name, e.g. "_a". */
|
|
29
|
+
name: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function unmangleLocal(mangled: string): UnmangledLocal | null {
|
|
33
|
+
const match = ANCHORED_RE.exec(mangled);
|
|
34
|
+
if (!match) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return { cellId: match[1] as CellId, name: match[2] };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type MangledSegment = string | UnmangledLocal;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Split a plain text string into alternating literal text and unmangled-local
|
|
44
|
+
* segments, so callers can render mixed React content.
|
|
45
|
+
*/
|
|
46
|
+
export function splitMangledLocals(text: string): MangledSegment[] {
|
|
47
|
+
const segments: MangledSegment[] = [];
|
|
48
|
+
GLOBAL_RE.lastIndex = 0;
|
|
49
|
+
let lastIndex = 0;
|
|
50
|
+
let match: RegExpExecArray | null = GLOBAL_RE.exec(text);
|
|
51
|
+
while (match !== null) {
|
|
52
|
+
if (match.index > lastIndex) {
|
|
53
|
+
segments.push(text.slice(lastIndex, match.index));
|
|
54
|
+
}
|
|
55
|
+
segments.push({ cellId: match[1] as CellId, name: match[2] });
|
|
56
|
+
lastIndex = match.index + match[0].length;
|
|
57
|
+
match = GLOBAL_RE.exec(text);
|
|
58
|
+
}
|
|
59
|
+
if (lastIndex < text.length) {
|
|
60
|
+
segments.push(text.slice(lastIndex));
|
|
61
|
+
}
|
|
62
|
+
return segments;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function containsMangledLocal(text: string): boolean {
|
|
66
|
+
return UNANCHORED_RE.test(text);
|
|
67
|
+
}
|