Skip to content

Commit ba71128

Browse files
committed
🐛 Preserve readable command controls in Sidekick work output
1 parent 26ee453 commit ba71128

2 files changed

Lines changed: 51 additions & 1 deletion

File tree

‎packages/cli/src/repl/application.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ import type { ReplHistoryNavigation } from "./navigation.ts";
111111
import { blankBand, EMPTY_BAND } from "./history-rail.ts";
112112
import type { ReplPreparedRail } from "./history-rail.ts";
113113
import { entryReading, NO_READING } from "./source-reading.ts";
114+
import { inertCommandText } from "./command-output.ts";
114115
import type { ReplCommandOutput } from "./command-output.ts";
115116
import type { ReplReading } from "./source-reading.ts";
116117
import { readingRuns, fitPlain } from "./fitting.ts";
@@ -10978,7 +10979,7 @@ function workComposed(staging: ReplChatStaging, key: string): readonly ReplChatC
1097810979
}),
1097910980
);
1098010981
// The record's own final line break ends its last line rather than adding one.
10981-
const recorded = entry.terminal?.output ?? "";
10982+
const recorded = inertCommandText(entry.terminal?.output ?? "");
1098210983
const output = recorded.endsWith("\n") ? recorded.slice(0, -1) : recorded;
1098310984
if (output !== "") {
1098410985
found.push(

‎packages/cli/tests/repl-exec-output.test.ts‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,55 @@ import type { ReplPage } from "./support/repl-page.ts";
1717
import { awaitedFact, mountedReading } from "./support/mounted-repl.ts";
1818
import { intoComposer, pasteDraft, sidekickIdle } from "./support/screen-journeys.ts";
1919

20+
describe("E3 associated command Output", () => {
21+
it("shows captured controls as readable escapes in both entry readings", function* () {
22+
const received = "PROBE\x1b[2JTAIL\rEND";
23+
const displayed = "PROBE\\x1b[2JTAIL\\x0dEND";
24+
yield* API.Process.around({
25+
*exec() {
26+
yield* Stdio.operations.stdout(new TextEncoder().encode(received));
27+
return { exitCode: 0, stdout: undefined, stderr: undefined };
28+
},
29+
});
30+
const page = yield* useReplPage({ size: { columns: 160, rows: 44 } });
31+
const program = '```sh exec as="result"\ncontrolled-bound\n```\n{result.stdout}';
32+
page.repl.fake.script({
33+
reply:
34+
'<Let as="code" select="code">\n````xmd\n' +
35+
program +
36+
"\n````\n</Let>\n<REPL.EntryInput>{code}</REPL.EntryInput>\n<REPL.Submit />",
37+
});
38+
page.repl.fake.script({ reply: "<Output>Probe completed.</Output>" });
39+
yield* intoComposer(page);
40+
yield* page.type("Run the probe");
41+
yield* page.key("Enter");
42+
const shown = yield* page.until(
43+
"the completed associated entry",
44+
(one) => one.shows("1 ✓") && sidekickIdle(one) && one.shows("Probe completed."),
45+
);
46+
const centerColumn = shown.locate("Entries").column;
47+
const sidekickColumn = shown.locate("BINDINGS").column;
48+
const center = shown.rows.map((row) => row.slice(centerColumn, sidekickColumn)).join("\n");
49+
const associated = shown.rows.map((row) => row.slice(sidekickColumn)).join("\n");
50+
expect(center).toContain(displayed);
51+
expect(associated).toContain(displayed);
52+
expect(center).not.toContain("�");
53+
expect(associated).not.toContain("�");
54+
expect(shown.occurrences(displayed)).toBe(2);
55+
const saved = yield* mountedReading(page.repl);
56+
const entry = saved.model.entries[0];
57+
expect(entry.terminal?.output).toContain(received);
58+
expect(entry.bindings.find((one) => one.name === "result")?.value).toEqual({
59+
stdout: received,
60+
stderr: "",
61+
exitCode: 0,
62+
});
63+
expect(entry.records.flatMap((one) => (one.exec === undefined ? [] : [one.exec]))).toEqual([
64+
{ stdout: received, stderr: "", exitCode: 0 },
65+
]);
66+
});
67+
});
68+
2069
function* submit(page: ReplPage, source: string, order = 1): Operation<void> {
2170
yield* pasteDraft(page, source);
2271
yield* page.press("[send]", { onRow: " DRAFT" });

0 commit comments

Comments
 (0)