@marimo-team/islands 0.20.3-dev88 → 0.20.3-dev90
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/main.js +93 -84
- package/package.json +1 -1
- package/src/components/chat/chat-display.tsx +13 -0
- package/src/components/editor/output/console/ConsoleOutput.tsx +28 -12
- package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +150 -8
- package/src/hooks/useInputHistory.ts +1 -1
package/dist/main.js
CHANGED
|
@@ -68637,7 +68637,11 @@ Image URL: ${r.imageUrl}`)), contextToXml({
|
|
|
68637
68637
|
if (isDataPart(d2)) return Logger.debug("Found data part", d2), null;
|
|
68638
68638
|
switch (d2.type) {
|
|
68639
68639
|
case "text":
|
|
68640
|
-
return (0, import_jsx_runtime.jsx)(
|
|
68640
|
+
return d2.text.includes("<marimo-") ? (0, import_jsx_runtime.jsx)(import_react.Fragment, {
|
|
68641
|
+
children: renderHTML({
|
|
68642
|
+
html: d2.text
|
|
68643
|
+
})
|
|
68644
|
+
}, f) : (0, import_jsx_runtime.jsx)(MarkdownRenderer, {
|
|
68641
68645
|
content: d2.text
|
|
68642
68646
|
}, f);
|
|
68643
68647
|
case "reasoning":
|
|
@@ -70347,7 +70351,7 @@ Image URL: ${r.imageUrl}`)), contextToXml({
|
|
|
70347
70351
|
return Logger.warn("Failed to get version from mount config"), null;
|
|
70348
70352
|
}
|
|
70349
70353
|
}
|
|
70350
|
-
const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.20.3-
|
|
70354
|
+
const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.20.3-dev90"), showCodeInRunModeAtom = atom(true);
|
|
70351
70355
|
atom(null);
|
|
70352
70356
|
var import_compiler_runtime$88 = require_compiler_runtime();
|
|
70353
70357
|
function useKeydownOnElement(e, r) {
|
|
@@ -99662,26 +99666,31 @@ ${c}
|
|
|
99662
99666
|
}), r[0] = e, r[1] = c), c;
|
|
99663
99667
|
};
|
|
99664
99668
|
var ConsoleOutputInternal = (e) => {
|
|
99665
|
-
let r = (0, import_compiler_runtime$4.c)(
|
|
99666
|
-
|
|
99669
|
+
let r = (0, import_compiler_runtime$4.c)(62), c = import_react.useRef(null), { wrapText: d, setWrapText: f } = useWrapText(), [_, v] = useExpandedConsoleOutput(e.cellId), [y, S] = import_react.useState(""), w;
|
|
99670
|
+
r[0] === y ? w = r[1] : (w = {
|
|
99671
|
+
value: y,
|
|
99672
|
+
setValue: S
|
|
99673
|
+
}, r[0] = y, r[1] = w);
|
|
99674
|
+
let E = useInputHistory(w), { consoleOutputs: O, stale: M, cellName: I, cellId: z, onSubmitDebugger: G, onClear: q, onRefactorWithAI: IY, className: LY } = e, RY = O.length > 0, zY = useSelectAllContent(RY), BY = useOverflowDetection(c, RY), VY;
|
|
99675
|
+
if (r[2] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (VY = () => {
|
|
99667
99676
|
let e2 = c.current;
|
|
99668
99677
|
if (!e2) return;
|
|
99669
99678
|
let r2 = e2.scrollHeight - e2.clientHeight;
|
|
99670
99679
|
r2 - e2.scrollTop < 120 && (e2.scrollTop = r2);
|
|
99671
|
-
}, r[
|
|
99672
|
-
let
|
|
99673
|
-
if (r[
|
|
99680
|
+
}, r[2] = VY) : VY = r[2], (0, import_react.useLayoutEffect)(VY), !RY && isInternalCellName(I)) return null;
|
|
99681
|
+
let HY, UY, WY, GY, KY, qY, JY, YY, XY, ZY;
|
|
99682
|
+
if (r[3] !== z || r[4] !== LY || r[5] !== O || r[6] !== RY || r[7] !== E || r[8] !== _ || r[9] !== BY || r[10] !== q || r[11] !== IY || r[12] !== G || r[13] !== zY || r[14] !== v || r[15] !== f || r[16] !== M || r[17] !== y || r[18] !== d) {
|
|
99674
99683
|
let e2 = [
|
|
99675
|
-
...
|
|
99676
|
-
].reverse(), w2 = e2.some(_temp$2),
|
|
99677
|
-
r[
|
|
99678
|
-
let
|
|
99679
|
-
|
|
99684
|
+
...O
|
|
99685
|
+
].reverse(), w2 = e2.some(_temp$2), I2 = e2.findIndex(_temp2$2), VY2;
|
|
99686
|
+
r[29] === O ? VY2 = r[30] : (VY2 = () => O.filter(_temp3$1).map(_temp4$1).join("\n"), r[29] = O, r[30] = VY2);
|
|
99687
|
+
let QY2 = VY2;
|
|
99688
|
+
HY = "relative group", r[31] !== QY2 || r[32] !== RY || r[33] !== _ || r[34] !== BY || r[35] !== v || r[36] !== f || r[37] !== d ? (UY = RY && (0, import_jsx_runtime.jsxs)("div", {
|
|
99680
99689
|
className: "absolute top-1 right-4 z-10 opacity-0 group-hover:opacity-100 flex items-center gap-1 print:hidden",
|
|
99681
99690
|
children: [
|
|
99682
99691
|
(0, import_jsx_runtime.jsx)(CopyClipboardIcon, {
|
|
99683
99692
|
tooltip: "Copy console output",
|
|
99684
|
-
value:
|
|
99693
|
+
value: QY2,
|
|
99685
99694
|
className: "h-4 w-4"
|
|
99686
99695
|
}),
|
|
99687
99696
|
(0, import_jsx_runtime.jsx)(Tooltip, {
|
|
@@ -99698,7 +99707,7 @@ ${c}
|
|
|
99698
99707
|
})
|
|
99699
99708
|
})
|
|
99700
99709
|
}),
|
|
99701
|
-
(
|
|
99710
|
+
(BY || _) && (0, import_jsx_runtime.jsx)(Button, {
|
|
99702
99711
|
"aria-label": _ ? "Collapse output" : "Expand output",
|
|
99703
99712
|
className: "p-0 mb-px",
|
|
99704
99713
|
onClick: () => v(!_),
|
|
@@ -99717,21 +99726,24 @@ ${c}
|
|
|
99717
99726
|
})
|
|
99718
99727
|
})
|
|
99719
99728
|
]
|
|
99720
|
-
}), r[
|
|
99721
|
-
let
|
|
99722
|
-
r[
|
|
99729
|
+
}), r[31] = QY2, r[32] = RY, r[33] = _, r[34] = BY, r[35] = v, r[36] = f, r[37] = d, r[38] = UY) : UY = r[38], WY = M ? "This console output is stale" : void 0, GY = "console-output-area", KY = c, qY = zY, JY = 0;
|
|
99730
|
+
let $Y2 = M && "marimo-output-stale", eX2 = RY ? "p-5" : "p-3";
|
|
99731
|
+
r[39] !== LY || r[40] !== $Y2 || r[41] !== eX2 ? (YY = cn("console-output-area overflow-hidden rounded-b-lg flex flex-col-reverse w-full gap-1 focus:outline-hidden", $Y2, eX2, LY), r[39] = LY, r[40] = $Y2, r[41] = eX2, r[42] = YY) : YY = r[42], r[43] === _ ? XY = r[44] : (XY = _ ? {
|
|
99723
99732
|
maxHeight: "none"
|
|
99724
|
-
} : void 0, r[
|
|
99733
|
+
} : void 0, r[43] = _, r[44] = XY), ZY = e2.map((e3, r2) => {
|
|
99725
99734
|
if (e3.channel === "pdb") return null;
|
|
99726
99735
|
if (e3.channel === "stdin") {
|
|
99727
99736
|
invariant(typeof e3.data == "string", "Expected data to be a string");
|
|
99728
|
-
let c2 =
|
|
99729
|
-
return e3.response == null &&
|
|
99737
|
+
let c2 = O.length - r2 - 1, d2 = e3.mimetype === "text/password";
|
|
99738
|
+
return e3.response == null && I2 === r2 ? (0, import_jsx_runtime.jsx)(StdInput, {
|
|
99730
99739
|
output: e3.data,
|
|
99731
99740
|
isPdb: w2,
|
|
99732
99741
|
isPassword: d2,
|
|
99733
|
-
onSubmit: (e4) =>
|
|
99734
|
-
onClear:
|
|
99742
|
+
onSubmit: (e4) => G(e4, c2),
|
|
99743
|
+
onClear: q,
|
|
99744
|
+
value: y,
|
|
99745
|
+
setValue: S,
|
|
99746
|
+
inputHistory: E
|
|
99735
99747
|
}, r2) : (0, import_jsx_runtime.jsx)(StdInputWithResponse, {
|
|
99736
99748
|
output: e3.data,
|
|
99737
99749
|
response: e3.response,
|
|
@@ -99740,94 +99752,91 @@ ${c}
|
|
|
99740
99752
|
}
|
|
99741
99753
|
return (0, import_jsx_runtime.jsx)(import_react.Fragment, {
|
|
99742
99754
|
children: (0, import_jsx_runtime.jsx)(OutputRenderer, {
|
|
99743
|
-
cellId:
|
|
99744
|
-
onRefactorWithAI:
|
|
99755
|
+
cellId: z,
|
|
99756
|
+
onRefactorWithAI: IY,
|
|
99745
99757
|
message: e3,
|
|
99746
99758
|
wrapText: d
|
|
99747
99759
|
})
|
|
99748
99760
|
}, r2);
|
|
99749
|
-
}), r[
|
|
99750
|
-
} else
|
|
99751
|
-
let
|
|
99752
|
-
r[
|
|
99753
|
-
value:
|
|
99754
|
-
cellId:
|
|
99761
|
+
}), r[3] = z, r[4] = LY, r[5] = O, r[6] = RY, r[7] = E, r[8] = _, r[9] = BY, r[10] = q, r[11] = IY, r[12] = G, r[13] = zY, r[14] = v, r[15] = f, r[16] = M, r[17] = y, r[18] = d, r[19] = HY, r[20] = UY, r[21] = WY, r[22] = GY, r[23] = KY, r[24] = qY, r[25] = JY, r[26] = YY, r[27] = XY, r[28] = ZY;
|
|
99762
|
+
} else HY = r[19], UY = r[20], WY = r[21], GY = r[22], KY = r[23], qY = r[24], JY = r[25], YY = r[26], XY = r[27], ZY = r[28];
|
|
99763
|
+
let QY;
|
|
99764
|
+
r[45] !== z || r[46] !== I ? (QY = (0, import_jsx_runtime.jsx)(NameCellContentEditable, {
|
|
99765
|
+
value: I,
|
|
99766
|
+
cellId: z,
|
|
99755
99767
|
className: "bg-(--slate-4) border-(--slate-4) hover:bg-(--slate-5) dark:border-(--sky-5) dark:bg-(--sky-6) dark:text-(--sky-12) text-(--slate-12) rounded-l rounded-br-lg absolute right-0 bottom-0 text-xs px-1.5 py-0.5 font-mono max-w-[75%] whitespace-nowrap overflow-hidden"
|
|
99756
|
-
}), r[
|
|
99757
|
-
let
|
|
99758
|
-
r[
|
|
99759
|
-
title:
|
|
99760
|
-
"data-testid":
|
|
99761
|
-
ref:
|
|
99762
|
-
...
|
|
99763
|
-
tabIndex:
|
|
99764
|
-
className:
|
|
99765
|
-
style:
|
|
99768
|
+
}), r[45] = z, r[46] = I, r[47] = QY) : QY = r[47];
|
|
99769
|
+
let $Y;
|
|
99770
|
+
r[48] !== QY || r[49] !== WY || r[50] !== GY || r[51] !== KY || r[52] !== qY || r[53] !== JY || r[54] !== YY || r[55] !== XY || r[56] !== ZY ? ($Y = (0, import_jsx_runtime.jsxs)("div", {
|
|
99771
|
+
title: WY,
|
|
99772
|
+
"data-testid": GY,
|
|
99773
|
+
ref: KY,
|
|
99774
|
+
...qY,
|
|
99775
|
+
tabIndex: JY,
|
|
99776
|
+
className: YY,
|
|
99777
|
+
style: XY,
|
|
99766
99778
|
children: [
|
|
99767
|
-
|
|
99768
|
-
|
|
99779
|
+
ZY,
|
|
99780
|
+
QY
|
|
99769
99781
|
]
|
|
99770
|
-
}), r[
|
|
99771
|
-
let
|
|
99772
|
-
return r[
|
|
99773
|
-
className:
|
|
99782
|
+
}), r[48] = QY, r[49] = WY, r[50] = GY, r[51] = KY, r[52] = qY, r[53] = JY, r[54] = YY, r[55] = XY, r[56] = ZY, r[57] = $Y) : $Y = r[57];
|
|
99783
|
+
let eX;
|
|
99784
|
+
return r[58] !== HY || r[59] !== UY || r[60] !== $Y ? (eX = (0, import_jsx_runtime.jsxs)("div", {
|
|
99785
|
+
className: HY,
|
|
99774
99786
|
children: [
|
|
99775
|
-
|
|
99776
|
-
|
|
99787
|
+
UY,
|
|
99788
|
+
$Y
|
|
99777
99789
|
]
|
|
99778
|
-
}), r[
|
|
99790
|
+
}), r[58] = HY, r[59] = UY, r[60] = $Y, r[61] = eX) : eX = r[61], eX;
|
|
99779
99791
|
}, StdInput = (e) => {
|
|
99780
|
-
let r = (0, import_compiler_runtime$4.c)(
|
|
99781
|
-
r[0] ===
|
|
99782
|
-
|
|
99783
|
-
|
|
99784
|
-
|
|
99785
|
-
|
|
99786
|
-
r[2] === e.output ? S = r[3] : (S = renderText(e.output), r[2] = e.output, r[3] = S);
|
|
99787
|
-
let w = e.isPassword ? "password" : "text", E, O;
|
|
99788
|
-
r[4] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (E = (e2) => d(e2.target.value), O = (0, import_jsx_runtime.jsx)(ChevronRight, {
|
|
99792
|
+
let r = (0, import_compiler_runtime$4.c)(25), { value: c, setValue: d, inputHistory: f, output: _, isPassword: v, isPdb: y, onSubmit: S, onClear: w } = e, { navigateUp: E, navigateDown: O, addToHistory: M } = f, I;
|
|
99793
|
+
r[0] === _ ? I = r[1] : (I = renderText(_), r[0] = _, r[1] = I);
|
|
99794
|
+
let z = v ? "password" : "text", G;
|
|
99795
|
+
r[2] === d ? G = r[3] : (G = (e2) => d(e2.target.value), r[2] = d, r[3] = G);
|
|
99796
|
+
let q;
|
|
99797
|
+
r[4] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (q = (0, import_jsx_runtime.jsx)(ChevronRight, {
|
|
99789
99798
|
className: "w-5 h-5"
|
|
99790
|
-
}), r[4] =
|
|
99791
|
-
let
|
|
99792
|
-
r[6] !==
|
|
99793
|
-
if (
|
|
99794
|
-
|
|
99799
|
+
}), r[4] = q) : q = r[4];
|
|
99800
|
+
let IY;
|
|
99801
|
+
r[5] !== M || r[6] !== O || r[7] !== E || r[8] !== S || r[9] !== d || r[10] !== c ? (IY = (e2) => {
|
|
99802
|
+
if (e2.key === "ArrowUp") {
|
|
99803
|
+
E(), e2.preventDefault();
|
|
99795
99804
|
return;
|
|
99796
99805
|
}
|
|
99797
|
-
if (
|
|
99798
|
-
|
|
99806
|
+
if (e2.key === "ArrowDown") {
|
|
99807
|
+
O(), e2.preventDefault();
|
|
99799
99808
|
return;
|
|
99800
99809
|
}
|
|
99801
|
-
|
|
99802
|
-
}, r[6] =
|
|
99803
|
-
let
|
|
99804
|
-
r[12] !==
|
|
99810
|
+
e2.key === "Enter" && !e2.shiftKey && (c && (M(c), S(c), d("")), e2.preventDefault(), e2.stopPropagation()), e2.key === "Enter" && e2.metaKey && (e2.preventDefault(), e2.stopPropagation());
|
|
99811
|
+
}, r[5] = M, r[6] = O, r[7] = E, r[8] = S, r[9] = d, r[10] = c, r[11] = IY) : IY = r[11];
|
|
99812
|
+
let LY;
|
|
99813
|
+
r[12] !== z || r[13] !== G || r[14] !== IY || r[15] !== c ? (LY = (0, import_jsx_runtime.jsx)(Input, {
|
|
99805
99814
|
"data-testid": "console-input",
|
|
99806
99815
|
"data-stdin-blocking": true,
|
|
99807
|
-
type:
|
|
99816
|
+
type: z,
|
|
99808
99817
|
autoComplete: "off",
|
|
99809
99818
|
autoFocus: true,
|
|
99810
99819
|
value: c,
|
|
99811
|
-
onChange:
|
|
99812
|
-
icon:
|
|
99820
|
+
onChange: G,
|
|
99821
|
+
icon: q,
|
|
99813
99822
|
className: "m-0 h-8 focus-visible:shadow-xs-solid",
|
|
99814
99823
|
placeholder: "stdin",
|
|
99815
|
-
onKeyDownCapture:
|
|
99816
|
-
}), r[12] =
|
|
99817
|
-
let
|
|
99818
|
-
r[
|
|
99819
|
-
onSubmit:
|
|
99820
|
-
onClear:
|
|
99821
|
-
}), r[
|
|
99822
|
-
let
|
|
99823
|
-
return r[
|
|
99824
|
+
onKeyDownCapture: IY
|
|
99825
|
+
}), r[12] = z, r[13] = G, r[14] = IY, r[15] = c, r[16] = LY) : LY = r[16];
|
|
99826
|
+
let RY;
|
|
99827
|
+
r[17] !== y || r[18] !== w || r[19] !== S ? (RY = y && (0, import_jsx_runtime.jsx)(DebuggerControls, {
|
|
99828
|
+
onSubmit: S,
|
|
99829
|
+
onClear: w
|
|
99830
|
+
}), r[17] = y, r[18] = w, r[19] = S, r[20] = RY) : RY = r[20];
|
|
99831
|
+
let zY;
|
|
99832
|
+
return r[21] !== I || r[22] !== LY || r[23] !== RY ? (zY = (0, import_jsx_runtime.jsxs)("div", {
|
|
99824
99833
|
className: "flex gap-2 items-center pt-2",
|
|
99825
99834
|
children: [
|
|
99826
|
-
S,
|
|
99827
99835
|
I,
|
|
99828
|
-
|
|
99836
|
+
LY,
|
|
99837
|
+
RY
|
|
99829
99838
|
]
|
|
99830
|
-
}), r[
|
|
99839
|
+
}), r[21] = I, r[22] = LY, r[23] = RY, r[24] = zY) : zY = r[24], zY;
|
|
99831
99840
|
}, StdInputWithResponse = (e) => {
|
|
99832
99841
|
let r = (0, import_compiler_runtime$4.c)(8), c;
|
|
99833
99842
|
r[0] === e.output ? c = r[1] : (c = renderText(e.output), r[0] = e.output, r[1] = c);
|
package/package.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import type { DataUIPart, ToolUIPart, UIMessage } from "ai";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { renderHTML } from "@/plugins/core/RenderHTML";
|
|
4
6
|
import { logNever } from "@/utils/assertNever";
|
|
5
7
|
import { Logger } from "@/utils/Logger";
|
|
6
8
|
import { MarkdownRenderer } from "../markdown/markdown-renderer";
|
|
@@ -46,6 +48,17 @@ export const renderUIMessage = ({
|
|
|
46
48
|
|
|
47
49
|
switch (part.type) {
|
|
48
50
|
case "text":
|
|
51
|
+
// Streamdown sanitizes the HTML which strips out marimo elements
|
|
52
|
+
// So instead, we render the HTML with our custom renderer.
|
|
53
|
+
if (part.text.includes("<marimo-")) {
|
|
54
|
+
return (
|
|
55
|
+
<React.Fragment key={index}>
|
|
56
|
+
{renderHTML({
|
|
57
|
+
html: part.text,
|
|
58
|
+
})}
|
|
59
|
+
</React.Fragment>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
49
62
|
return <MarkdownRenderer key={index} content={part.text} />;
|
|
50
63
|
case "reasoning":
|
|
51
64
|
return (
|
|
@@ -18,7 +18,10 @@ import { isInternalCellName } from "@/core/cells/names";
|
|
|
18
18
|
import { useExpandedConsoleOutput } from "@/core/cells/outputs";
|
|
19
19
|
import type { WithResponse } from "@/core/cells/types";
|
|
20
20
|
import type { OutputMessage } from "@/core/kernel/messages";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
type UseInputHistoryReturn,
|
|
23
|
+
useInputHistory,
|
|
24
|
+
} from "@/hooks/useInputHistory";
|
|
22
25
|
import { useOverflowDetection } from "@/hooks/useOverflowDetection";
|
|
23
26
|
import { useSelectAllContent } from "@/hooks/useSelectAllContent";
|
|
24
27
|
import { cn } from "@/utils/cn";
|
|
@@ -54,6 +57,11 @@ const ConsoleOutputInternal = (props: Props): React.ReactNode => {
|
|
|
54
57
|
const ref = React.useRef<HTMLDivElement>(null);
|
|
55
58
|
const { wrapText, setWrapText } = useWrapText();
|
|
56
59
|
const [isExpanded, setIsExpanded] = useExpandedConsoleOutput(props.cellId);
|
|
60
|
+
const [stdinValue, setStdinValue] = React.useState("");
|
|
61
|
+
const inputHistory = useInputHistory({
|
|
62
|
+
value: stdinValue,
|
|
63
|
+
setValue: setStdinValue,
|
|
64
|
+
});
|
|
57
65
|
const {
|
|
58
66
|
consoleOutputs,
|
|
59
67
|
stale,
|
|
@@ -210,6 +218,9 @@ const ConsoleOutputInternal = (props: Props): React.ReactNode => {
|
|
|
210
218
|
isPassword={isPassword}
|
|
211
219
|
onSubmit={(text) => onSubmitDebugger(text, originalIdx)}
|
|
212
220
|
onClear={onClear}
|
|
221
|
+
value={stdinValue}
|
|
222
|
+
setValue={setStdinValue}
|
|
223
|
+
inputHistory={inputHistory}
|
|
213
224
|
/>
|
|
214
225
|
);
|
|
215
226
|
}
|
|
@@ -249,25 +260,32 @@ const StdInput = (props: {
|
|
|
249
260
|
onSubmit: (text: string) => void;
|
|
250
261
|
onClear?: () => void;
|
|
251
262
|
output: string;
|
|
252
|
-
response?: string;
|
|
253
263
|
isPdb: boolean;
|
|
254
264
|
isPassword?: boolean;
|
|
265
|
+
value: string;
|
|
266
|
+
setValue: (value: string) => void;
|
|
267
|
+
inputHistory: UseInputHistoryReturn;
|
|
255
268
|
}) => {
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
const { navigateUp, navigateDown, addToHistory } = useInputHistory({
|
|
269
|
+
const {
|
|
259
270
|
value,
|
|
260
271
|
setValue,
|
|
261
|
-
|
|
272
|
+
inputHistory,
|
|
273
|
+
output,
|
|
274
|
+
isPassword,
|
|
275
|
+
isPdb,
|
|
276
|
+
onSubmit,
|
|
277
|
+
onClear,
|
|
278
|
+
} = props;
|
|
279
|
+
const { navigateUp, navigateDown, addToHistory } = inputHistory;
|
|
262
280
|
|
|
263
281
|
return (
|
|
264
282
|
<div className="flex gap-2 items-center pt-2">
|
|
265
|
-
{renderText(
|
|
283
|
+
{renderText(output)}
|
|
266
284
|
<Input
|
|
267
285
|
data-testid="console-input"
|
|
268
286
|
// This is used in <StdinBlockingAlert> to find the input
|
|
269
287
|
data-stdin-blocking={true}
|
|
270
|
-
type={
|
|
288
|
+
type={isPassword ? "password" : "text"}
|
|
271
289
|
autoComplete="off"
|
|
272
290
|
autoFocus={true}
|
|
273
291
|
value={value}
|
|
@@ -292,7 +310,7 @@ const StdInput = (props: {
|
|
|
292
310
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
293
311
|
if (value) {
|
|
294
312
|
addToHistory(value);
|
|
295
|
-
|
|
313
|
+
onSubmit(value);
|
|
296
314
|
setValue("");
|
|
297
315
|
}
|
|
298
316
|
e.preventDefault();
|
|
@@ -306,9 +324,7 @@ const StdInput = (props: {
|
|
|
306
324
|
}
|
|
307
325
|
}}
|
|
308
326
|
/>
|
|
309
|
-
{
|
|
310
|
-
<DebuggerControls onSubmit={props.onSubmit} onClear={props.onClear} />
|
|
311
|
-
)}
|
|
327
|
+
{isPdb && <DebuggerControls onSubmit={onSubmit} onClear={onClear} />}
|
|
312
328
|
</div>
|
|
313
329
|
);
|
|
314
330
|
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import { render, screen } from "@testing-library/react";
|
|
4
|
-
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import { describe, expect, it, vi } from "vitest";
|
|
5
5
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
6
6
|
import type { CellId } from "@/core/cells/ids";
|
|
7
|
+
import type { WithResponse } from "@/core/cells/types";
|
|
7
8
|
import type { OutputMessage } from "@/core/kernel/messages";
|
|
8
9
|
import { ConsoleOutput } from "../ConsoleOutput";
|
|
9
10
|
|
|
@@ -20,6 +21,10 @@ global.ResizeObserver = class ResizeObserver {
|
|
|
20
21
|
}
|
|
21
22
|
};
|
|
22
23
|
|
|
24
|
+
const renderWithProvider = (ui: React.ReactElement) => {
|
|
25
|
+
return render(<TooltipProvider>{ui}</TooltipProvider>);
|
|
26
|
+
};
|
|
27
|
+
|
|
23
28
|
describe("ConsoleOutput integration", () => {
|
|
24
29
|
const createOutput = (data: string, channel = "stdout"): OutputMessage => ({
|
|
25
30
|
channel: channel as "stdout" | "stderr",
|
|
@@ -31,7 +36,7 @@ describe("ConsoleOutput integration", () => {
|
|
|
31
36
|
const defaultProps = {
|
|
32
37
|
cellId: "cell-1" as CellId,
|
|
33
38
|
cellName: "test_cell",
|
|
34
|
-
consoleOutputs: [],
|
|
39
|
+
consoleOutputs: [] as WithResponse<OutputMessage>[],
|
|
35
40
|
stale: false,
|
|
36
41
|
debuggerActive: false,
|
|
37
42
|
onSubmitDebugger: () => {
|
|
@@ -50,14 +55,151 @@ describe("ConsoleOutput integration", () => {
|
|
|
50
55
|
],
|
|
51
56
|
};
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
<TooltipProvider>
|
|
55
|
-
<ConsoleOutput {...props} />
|
|
56
|
-
</TooltipProvider>,
|
|
57
|
-
);
|
|
58
|
+
renderWithProvider(<ConsoleOutput {...props} />);
|
|
58
59
|
|
|
59
60
|
const link = screen.getByRole("link", { name: "https://marimo.io" });
|
|
60
61
|
expect(link).toBeInTheDocument();
|
|
61
62
|
expect(link).toHaveAttribute("href", "https://marimo.io");
|
|
62
63
|
});
|
|
63
64
|
});
|
|
65
|
+
|
|
66
|
+
describe("ConsoleOutput pdb history", () => {
|
|
67
|
+
const defaultProps = {
|
|
68
|
+
cellId: "cell-1" as CellId,
|
|
69
|
+
cellName: "test_cell",
|
|
70
|
+
consoleOutputs: [] as WithResponse<OutputMessage>[],
|
|
71
|
+
stale: false,
|
|
72
|
+
debuggerActive: false,
|
|
73
|
+
onSubmitDebugger: vi.fn(),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const stdinPrompt = (
|
|
77
|
+
data: string,
|
|
78
|
+
response?: string,
|
|
79
|
+
): WithResponse<OutputMessage> => ({
|
|
80
|
+
channel: "stdin" as const,
|
|
81
|
+
mimetype: "text/plain",
|
|
82
|
+
data,
|
|
83
|
+
timestamp: 0,
|
|
84
|
+
response,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("should persist command history across StdInput remounts", () => {
|
|
88
|
+
// Initial state: pdb prompt waiting for input
|
|
89
|
+
const outputs1: WithResponse<OutputMessage>[] = [stdinPrompt("(Pdb) ")];
|
|
90
|
+
|
|
91
|
+
const onSubmitDebugger = vi.fn();
|
|
92
|
+
const { rerender } = renderWithProvider(
|
|
93
|
+
<ConsoleOutput
|
|
94
|
+
{...defaultProps}
|
|
95
|
+
consoleOutputs={outputs1}
|
|
96
|
+
onSubmitDebugger={onSubmitDebugger}
|
|
97
|
+
/>,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const input = screen.getByTestId("console-input");
|
|
101
|
+
|
|
102
|
+
// Type "next" and submit
|
|
103
|
+
fireEvent.change(input, { target: { value: "next" } });
|
|
104
|
+
fireEvent.keyDown(input, { key: "Enter" });
|
|
105
|
+
|
|
106
|
+
expect(onSubmitDebugger).toHaveBeenCalledWith("next", 0);
|
|
107
|
+
|
|
108
|
+
// Simulate server response: old stdin gets a response, new stdin prompt appears
|
|
109
|
+
const outputs2: WithResponse<OutputMessage>[] = [
|
|
110
|
+
stdinPrompt("(Pdb) ", "next"),
|
|
111
|
+
stdinPrompt("(Pdb) "),
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
rerender(
|
|
115
|
+
<TooltipProvider>
|
|
116
|
+
<ConsoleOutput
|
|
117
|
+
{...defaultProps}
|
|
118
|
+
consoleOutputs={outputs2}
|
|
119
|
+
onSubmitDebugger={onSubmitDebugger}
|
|
120
|
+
/>
|
|
121
|
+
</TooltipProvider>,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// New StdInput mounted — press ArrowUp to recall previous command
|
|
125
|
+
const newInput = screen.getByTestId("console-input");
|
|
126
|
+
fireEvent.keyDown(newInput, { key: "ArrowUp" });
|
|
127
|
+
|
|
128
|
+
expect(newInput).toHaveValue("next");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("should navigate through multiple history entries across remounts", () => {
|
|
132
|
+
const onSubmitDebugger = vi.fn();
|
|
133
|
+
|
|
134
|
+
// First prompt
|
|
135
|
+
const outputs1: WithResponse<OutputMessage>[] = [stdinPrompt("(Pdb) ")];
|
|
136
|
+
|
|
137
|
+
const { rerender } = renderWithProvider(
|
|
138
|
+
<ConsoleOutput
|
|
139
|
+
{...defaultProps}
|
|
140
|
+
consoleOutputs={outputs1}
|
|
141
|
+
onSubmitDebugger={onSubmitDebugger}
|
|
142
|
+
/>,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Submit "step"
|
|
146
|
+
let input = screen.getByTestId("console-input");
|
|
147
|
+
fireEvent.change(input, { target: { value: "step" } });
|
|
148
|
+
fireEvent.keyDown(input, { key: "Enter" });
|
|
149
|
+
|
|
150
|
+
// Second prompt
|
|
151
|
+
const outputs2: WithResponse<OutputMessage>[] = [
|
|
152
|
+
stdinPrompt("(Pdb) ", "step"),
|
|
153
|
+
stdinPrompt("(Pdb) "),
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
rerender(
|
|
157
|
+
<TooltipProvider>
|
|
158
|
+
<ConsoleOutput
|
|
159
|
+
{...defaultProps}
|
|
160
|
+
consoleOutputs={outputs2}
|
|
161
|
+
onSubmitDebugger={onSubmitDebugger}
|
|
162
|
+
/>
|
|
163
|
+
</TooltipProvider>,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// Submit "print(x)"
|
|
167
|
+
input = screen.getByTestId("console-input");
|
|
168
|
+
fireEvent.change(input, { target: { value: "print(x)" } });
|
|
169
|
+
fireEvent.keyDown(input, { key: "Enter" });
|
|
170
|
+
|
|
171
|
+
// Third prompt
|
|
172
|
+
const outputs3: WithResponse<OutputMessage>[] = [
|
|
173
|
+
stdinPrompt("(Pdb) ", "step"),
|
|
174
|
+
stdinPrompt("(Pdb) ", "print(x)"),
|
|
175
|
+
stdinPrompt("(Pdb) "),
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
rerender(
|
|
179
|
+
<TooltipProvider>
|
|
180
|
+
<ConsoleOutput
|
|
181
|
+
{...defaultProps}
|
|
182
|
+
consoleOutputs={outputs3}
|
|
183
|
+
onSubmitDebugger={onSubmitDebugger}
|
|
184
|
+
/>
|
|
185
|
+
</TooltipProvider>,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// ArrowUp should show most recent command first
|
|
189
|
+
input = screen.getByTestId("console-input");
|
|
190
|
+
fireEvent.keyDown(input, { key: "ArrowUp" });
|
|
191
|
+
expect(input).toHaveValue("print(x)");
|
|
192
|
+
|
|
193
|
+
// ArrowUp again should show older command
|
|
194
|
+
fireEvent.keyDown(input, { key: "ArrowUp" });
|
|
195
|
+
expect(input).toHaveValue("step");
|
|
196
|
+
|
|
197
|
+
// ArrowDown should go back to "print(x)"
|
|
198
|
+
fireEvent.keyDown(input, { key: "ArrowDown" });
|
|
199
|
+
expect(input).toHaveValue("print(x)");
|
|
200
|
+
|
|
201
|
+
// ArrowDown again should return to empty input
|
|
202
|
+
fireEvent.keyDown(input, { key: "ArrowDown" });
|
|
203
|
+
expect(input).toHaveValue("");
|
|
204
|
+
});
|
|
205
|
+
});
|