chore(vendor): import pi 1.3.0
Imported as a linear vendor commit so forge rebases do not replay the upstream root tree. git-subtree-dir: third_party/pi git-subtree-split: 71023aec1546d6d677a65669c5665c0927ce0dd0
This commit is contained in:
1 parent
719370c8f7
commit
6081e3e325
1743 files changed
+205459
-131704
No files matched your search
+206
-14
@@ -20,7 +20,7 @@ import {
|
||||
resetCapabilitiesCache,
|
||||
setCapabilities,
|
||||
} from "../src/terminal-image.ts";
|
||||
import type { TuiMouseEvent } from "../src/tui.ts";
|
||||
import { renderFakeCursor, type TuiMouseEvent } from "../src/tui.ts";
|
||||
import { TuiAltScreen } from "../src/tui-alt-screen.ts";
|
||||
import { stripTerminalSequences, visibleWidth } from "../src/utils.ts";
|
||||
import { VirtualTerminal } from "./virtual-terminal.ts";
|
||||
@@ -138,7 +138,51 @@ describe("TuiAltScreen", () => {
|
||||
tui.stop();
|
||||
});
|
||||
|
||||
it("leaves the scrollbar clickable when the jump-to-end indicator spans the transcript", async () => {
|
||||
it("keeps the jump-to-end indicator centered as the auto scrollbar hides and reappears", async () => {
|
||||
// Regression test for #9136: auto scrollbar visibility must not move the indicator.
|
||||
const terminal = new VirtualTerminal(80, 6);
|
||||
const label = " ↓ Jump to latest message · End ";
|
||||
const tui = new TuiAltScreen(terminal, undefined, undefined, {
|
||||
scrollToEndIndicator: () => label,
|
||||
});
|
||||
const transcript = new ScrollView(
|
||||
new Text(Array.from({ length: 20 }, (_, index) => `line ${index + 1}`).join("\n"), 0, 0),
|
||||
{ follow: "end", primary: true, scrollbar: "auto", scrollbarHideDelayMs: 0 },
|
||||
);
|
||||
tui.setLayoutRoot(transcript);
|
||||
tui.start();
|
||||
try {
|
||||
await terminal.waitForRender();
|
||||
|
||||
// Scrolling over the track keeps the scrollbar visible until the pointer leaves.
|
||||
terminal.sendInput("\x1b[<64;80;1M");
|
||||
await terminal.waitForRender();
|
||||
assert.strictEqual(transcript.isScrollbarVisible, true);
|
||||
assert.strictEqual(transcript.isFollowingEnd, false);
|
||||
const scrollTop = transcript.scrollTop;
|
||||
const visibleColumn = terminal.getViewport()[5].indexOf(label);
|
||||
|
||||
// Leaving the track lets the auto-hide timer expire without changing the content.
|
||||
terminal.sendInput("\x1b[<35;79;1M");
|
||||
await terminal.waitForRender();
|
||||
assert.strictEqual(transcript.isScrollbarVisible, false);
|
||||
assert.strictEqual(transcript.scrollTop, scrollTop);
|
||||
const hiddenColumn = terminal.getViewport()[5].indexOf(label);
|
||||
|
||||
terminal.sendInput("\x1b[<35;80;1M");
|
||||
await terminal.waitForRender();
|
||||
assert.strictEqual(transcript.isScrollbarVisible, true);
|
||||
assert.strictEqual(transcript.scrollTop, scrollTop);
|
||||
const revealedColumn = terminal.getViewport()[5].indexOf(label);
|
||||
|
||||
assert.deepStrictEqual([visibleColumn, hiddenColumn, revealedColumn], [24, 24, 24]);
|
||||
} finally {
|
||||
tui.stop();
|
||||
}
|
||||
});
|
||||
|
||||
it("leaves the scrollbar visible and clickable when the jump-to-end indicator spans the transcript", async () => {
|
||||
// Regression coverage for #9136: centering must not paint or capture clicks over the scrollbar.
|
||||
const terminal = new VirtualTerminal(30, 6);
|
||||
const tui = new TuiAltScreen(terminal, undefined, undefined, {
|
||||
scrollToEndIndicator: () => "↓".repeat(30),
|
||||
@@ -161,6 +205,7 @@ describe("TuiAltScreen", () => {
|
||||
assert.strictEqual(transcript.isFollowingEnd, false);
|
||||
|
||||
// The indicator must not intercept a press on the scrollbar's last column.
|
||||
assert.strictEqual(terminal.getViewport()[3], `${"↓".repeat(29)}┃`);
|
||||
terminal.sendInput("\x1b[<0;30;4M");
|
||||
terminal.sendInput("\x1b[<0;30;4m");
|
||||
await terminal.waitForRender();
|
||||
@@ -459,6 +504,31 @@ describe("TuiAltScreen", () => {
|
||||
tui.stop();
|
||||
});
|
||||
|
||||
// #9758: wheel line counts can change at runtime; Alt keeps its multiplier.
|
||||
it("applies runtime wheel line count updates", async () => {
|
||||
const terminal = new VirtualTerminal(20, 4);
|
||||
const tui = new TuiAltScreen(terminal, undefined, undefined, { wheelScrollLines: 3 });
|
||||
const deltas: Array<number | undefined> = [];
|
||||
tui.addChild(
|
||||
new MouseRegion(new Text("wheel target", 0, 0), (event) => {
|
||||
if (event.type !== "wheel") return undefined;
|
||||
deltas.push(event.wheelDelta);
|
||||
return { handled: true };
|
||||
}),
|
||||
);
|
||||
tui.start();
|
||||
try {
|
||||
await terminal.waitForRender();
|
||||
terminal.sendInput("\x1b[<64;1;1M");
|
||||
tui.setWheelScrollLines(2);
|
||||
terminal.sendInput("\x1b[<65;1;1M");
|
||||
terminal.sendInput("\x1b[<72;1;1M");
|
||||
assert.deepStrictEqual(deltas, [-3, 2, -10]);
|
||||
} finally {
|
||||
tui.stop();
|
||||
}
|
||||
});
|
||||
|
||||
it("chains unused wheel delta to an outer scroll view", async () => {
|
||||
const terminal = new VirtualTerminal(20, 4);
|
||||
const tui = new TuiAltScreen(terminal, undefined, undefined, { wheelScrollLines: 3 });
|
||||
@@ -506,14 +576,14 @@ describe("TuiAltScreen", () => {
|
||||
["line 5", "line 6", "line 7", "line 8", "line 9", "line 10", "line 11", "line 12"],
|
||||
);
|
||||
|
||||
terminal.sendInput("\x1bOH");
|
||||
terminal.sendInput("\x1b[7^");
|
||||
await terminal.waitForRender();
|
||||
assert.deepStrictEqual(
|
||||
terminal.getViewport().map((line) => line.trimEnd()),
|
||||
["line 1", "line 2", "line 3", "line 4", "line 5", "line 6", "line 7", "line 8"],
|
||||
);
|
||||
|
||||
terminal.sendInput("\x1bOF");
|
||||
terminal.sendInput("\x1b[8^");
|
||||
await terminal.waitForRender();
|
||||
assert.deepStrictEqual(
|
||||
terminal.getViewport().map((line) => line.trimEnd()),
|
||||
@@ -828,7 +898,7 @@ describe("TuiAltScreen", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("routes Ctrl-modified viewport navigation to the focused component", async () => {
|
||||
it("routes Home and End to the focused component and Ctrl+Home/End to the transcript", async () => {
|
||||
const terminal = new VirtualTerminal(20, 6);
|
||||
const tui = new TuiAltScreen(terminal);
|
||||
const transcript = new ScrollView(
|
||||
@@ -852,22 +922,34 @@ describe("TuiAltScreen", () => {
|
||||
tui.start();
|
||||
await terminal.waitForRender();
|
||||
|
||||
terminal.sendInput("\x1bOH");
|
||||
const bottom = transcript.scrollTop;
|
||||
assert.ok(bottom > 0);
|
||||
|
||||
// #10314: unmodified Home/End belong to the editor in every UI mode.
|
||||
const editorKeys = ["\x1bOH", "\x1b[F", "\x1b[57423u", "\x1b[5;5~", "\x1b[6;5~"];
|
||||
for (const input of editorKeys) terminal.sendInput(input);
|
||||
await terminal.waitForRender();
|
||||
assert.strictEqual(transcript.scrollTop, bottom);
|
||||
assert.deepStrictEqual(editorInputs, editorKeys);
|
||||
|
||||
terminal.sendInput("\x1b[1;5H");
|
||||
await terminal.waitForRender();
|
||||
assert.strictEqual(transcript.scrollTop, 0);
|
||||
assert.deepStrictEqual(editorInputs, []);
|
||||
|
||||
const modifiedInputs = ["\x1b[1;5H", "\x1b[1;5F", "\x1b[5;5~", "\x1b[6;5~", "\x1b[57423;5u"];
|
||||
for (const input of modifiedInputs) terminal.sendInput(input);
|
||||
terminal.sendInput("\x1b[1;5F");
|
||||
await terminal.waitForRender();
|
||||
assert.strictEqual(transcript.scrollTop, bottom);
|
||||
assert.strictEqual(transcript.isFollowingEnd, true);
|
||||
|
||||
terminal.sendInput("\x1b[57423;5u");
|
||||
terminal.sendInput("\x1b[57423;5:3u");
|
||||
await terminal.waitForRender();
|
||||
assert.strictEqual(transcript.scrollTop, 0);
|
||||
assert.deepStrictEqual(editorInputs, modifiedInputs);
|
||||
|
||||
terminal.sendInput("\x1b[6~");
|
||||
await terminal.waitForRender();
|
||||
assert.strictEqual(transcript.scrollTop, 1);
|
||||
assert.deepStrictEqual(editorInputs, modifiedInputs);
|
||||
assert.deepStrictEqual(editorInputs, editorKeys);
|
||||
|
||||
tui.stop();
|
||||
});
|
||||
@@ -992,6 +1074,47 @@ describe("TuiAltScreen", () => {
|
||||
tui.stop();
|
||||
});
|
||||
|
||||
it("redraws WezTerm Kitty images after writes to covered rows", async () => {
|
||||
// Regression test for #10319: a scrollbar update below an unchanged image anchor erased its cells.
|
||||
const weztermPane = process.env.WEZTERM_PANE;
|
||||
let tui: TuiAltScreen | undefined;
|
||||
process.env.WEZTERM_PANE = "1";
|
||||
setCapabilities({ images: "kitty", trueColor: true, hyperlinks: true });
|
||||
try {
|
||||
const terminal = new RecordingTerminal(20, 4);
|
||||
const imageId = 10319;
|
||||
const imageLine = encodeKitty("AAAA", { columns: 2, rows: 3, imageId, moveCursor: false });
|
||||
registerKittyImageMetadata({ imageId, columns: 2, rows: 3, widthPx: 100, heightPx: 100 });
|
||||
let coveredLine = "";
|
||||
tui = new TuiAltScreen(terminal);
|
||||
tui.setLayoutRoot({
|
||||
render: () => [imageLine, coveredLine, "", "after"],
|
||||
invalidate: () => {},
|
||||
});
|
||||
tui.start();
|
||||
await terminal.waitForRender();
|
||||
const eventCount = terminal.events.length;
|
||||
|
||||
coveredLine = "changed";
|
||||
tui.requestRender();
|
||||
await terminal.waitForRender();
|
||||
const redrawWrites = terminal.events
|
||||
.slice(eventCount)
|
||||
.filter((event): event is { type: "write"; data: string } => event.type === "write")
|
||||
.map((event) => event.data)
|
||||
.join("");
|
||||
const placementIndex = redrawWrites.indexOf("\x1b_Ga=p,q=2");
|
||||
assert.ok(redrawWrites.includes("\x1b_Ga=d,d=a,q=2\x1b\\"));
|
||||
assert.ok(placementIndex > redrawWrites.indexOf("changed"));
|
||||
assert.ok(!redrawWrites.includes("\x1b_Ga=T"));
|
||||
} finally {
|
||||
tui?.stop();
|
||||
resetCapabilitiesCache();
|
||||
if (weztermPane === undefined) delete process.env.WEZTERM_PANE;
|
||||
else process.env.WEZTERM_PANE = weztermPane;
|
||||
}
|
||||
});
|
||||
|
||||
it("reuses moved Kitty images without dropping HStack siblings", async () => {
|
||||
setCapabilities({ images: "kitty", trueColor: true, hyperlinks: true });
|
||||
try {
|
||||
@@ -1239,6 +1362,26 @@ describe("TuiAltScreen", () => {
|
||||
tui.stop();
|
||||
});
|
||||
|
||||
it("keeps the selection highlight after a fake cursor cell", async () => {
|
||||
const terminal = new RecordingTerminal(20, 4);
|
||||
const tui = new TuiAltScreen(terminal);
|
||||
tui.addChild(new Text(`> ${renderFakeCursor("a")}bcd`, 0, 0));
|
||||
tui.start();
|
||||
await terminal.waitForRender();
|
||||
|
||||
terminal.sendInput("\x1b[<0;1;1M");
|
||||
terminal.sendInput("\x1b[<32;6;1M");
|
||||
terminal.sendInput("\x1b[<3;6;1m");
|
||||
await terminal.waitForRender();
|
||||
|
||||
assert.ok(
|
||||
terminal.events.some((event) => event.type === "write" && event.data.includes("\x1b[27m\x1b[7mbcd")),
|
||||
"selection inverse must be reapplied after the fake cursor ends",
|
||||
);
|
||||
|
||||
tui.stop();
|
||||
});
|
||||
|
||||
it("uses an injected copySelection handler instead of OSC 52 and reports success", async () => {
|
||||
const terminal = new RecordingTerminal(20, 4);
|
||||
const copied: string[] = [];
|
||||
@@ -1351,6 +1494,36 @@ describe("TuiAltScreen", () => {
|
||||
tui.stop();
|
||||
});
|
||||
|
||||
it("flashes a specific error returned by the injected copySelection handler", async () => {
|
||||
// Regression test for #9618.
|
||||
const terminal = new RecordingTerminal(80, 4);
|
||||
const tui = new TuiAltScreen(terminal, undefined, undefined, {
|
||||
copyOnSelect: false,
|
||||
copySelection: async () => "Clipboard unavailable: install wl-clipboard",
|
||||
});
|
||||
let flashDuration: number | undefined;
|
||||
const flash = tui.flash.bind(tui);
|
||||
tui.flash = (message, durationMs) => {
|
||||
flashDuration = durationMs;
|
||||
flash(message, durationMs);
|
||||
};
|
||||
tui.addChild(new Text("alpha\nbeta\ngamma\ndelta", 0, 0));
|
||||
tui.start();
|
||||
await terminal.waitForRender();
|
||||
|
||||
terminal.sendInput("\x1b[<0;1;1M");
|
||||
terminal.sendInput("\x1b[<32;4;2M");
|
||||
terminal.sendInput("\x1b[<0;4;2m");
|
||||
await terminal.waitForRender();
|
||||
assert.strictEqual(await tui.copyActiveSelectionToClipboard(), false);
|
||||
await terminal.waitForRender();
|
||||
|
||||
assert.ok(terminal.getViewport().some((line) => line.includes("Clipboard unavailable: install wl-clipboard")));
|
||||
assert.ok(terminal.getViewport().every((line) => !line.includes("Copy failed")));
|
||||
assert.strictEqual(flashDuration, 5000);
|
||||
tui.stop();
|
||||
});
|
||||
|
||||
it("does not append whitespace to double-click word highlighting", async () => {
|
||||
const terminal = new RecordingTerminal(20, 1);
|
||||
const tui = new TuiAltScreen(terminal);
|
||||
@@ -1689,6 +1862,26 @@ describe("TuiAltScreen", () => {
|
||||
tui.stop();
|
||||
});
|
||||
|
||||
// #10769
|
||||
it("scrolls the transcript on wheel input a focused overlay does not handle", async () => {
|
||||
const terminal = new VirtualTerminal(20, 6);
|
||||
const tui = new TuiAltScreen(terminal);
|
||||
tui.addChild(new Text(Array.from({ length: 12 }, (_, index) => `line ${index + 1}`).join("\n"), 0, 0));
|
||||
const overlay = new InputOverlay();
|
||||
tui.start();
|
||||
await terminal.waitForRender();
|
||||
const topBefore = tui.viewportTop;
|
||||
tui.showOverlay(overlay, { anchor: "top-left", width: 10 });
|
||||
await terminal.waitForRender();
|
||||
assert.strictEqual(overlay.focused, true);
|
||||
|
||||
terminal.sendInput("\x1b[<64;3;1M");
|
||||
await terminal.waitForRender();
|
||||
assert.ok(tui.viewportTop < topBefore);
|
||||
assert.deepStrictEqual(overlay.inputs, []);
|
||||
tui.stop();
|
||||
});
|
||||
|
||||
it("focuses and captures drag gestures for mouse-aware components", async () => {
|
||||
const terminal = new VirtualTerminal(20, 2);
|
||||
const tui = new TuiAltScreen(terminal);
|
||||
@@ -1828,7 +2021,7 @@ describe("TuiAltScreen", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("gives wheel and viewport keys to a focused overlay", async () => {
|
||||
it("gives viewport keys to a focused overlay", async () => {
|
||||
const terminal = new VirtualTerminal(20, 6);
|
||||
const tui = new TuiAltScreen(terminal);
|
||||
tui.addChild(new Text(Array.from({ length: 12 }, (_, index) => `line ${index + 1}`).join("\n"), 0, 0));
|
||||
@@ -1840,8 +2033,7 @@ describe("TuiAltScreen", () => {
|
||||
await terminal.waitForRender();
|
||||
assert.strictEqual(overlay.focused, true);
|
||||
|
||||
const wheel = "\x1b[<64;10;3M";
|
||||
const keys = ["\x1b[5~", "\x1b[6~", "\x1bOH", "\x1bOF", wheel];
|
||||
const keys = ["\x1b[5~", "\x1b[6~", "\x1bOH", "\x1bOF"];
|
||||
for (const key of keys) terminal.sendInput(key);
|
||||
await terminal.waitForRender();
|
||||
|
||||
|
||||
Reference in new issue
Block a user