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,9 +1,17 @@
|
||||
import type { Component, Terminal, TUI } from "@earendil-works/pi-tui";
|
||||
import type { Component, Terminal, TUI, WheelScrollLines } from "@earendil-works/pi-tui";
|
||||
import { Container, getKeybindings, isViewportTUI, ScrollView, setKeybindings, Text } from "@earendil-works/pi-tui";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { VirtualTerminal } from "../../tui/test/virtual-terminal.ts";
|
||||
import { KeybindingsManager } from "../src/core/keybindings.ts";
|
||||
import type { FullscreenExitOutput, TuiMode } from "../src/core/settings-manager.ts";
|
||||
import {
|
||||
BranchSummaryStatusIndicator,
|
||||
CompactionStatusIndicator,
|
||||
RetryStatusIndicator,
|
||||
type StatusIndicator,
|
||||
type StatusIndicatorKind,
|
||||
WorkingStatusIndicator,
|
||||
} from "../src/modes/interactive/components/status-indicator.ts";
|
||||
import {
|
||||
createInteractiveTui,
|
||||
createInteractiveTuiReference,
|
||||
@@ -13,6 +21,7 @@ import { initTheme } from "../src/modes/interactive/theme/theme.ts";
|
||||
|
||||
const clipboardMocks = vi.hoisted(() => ({
|
||||
copyToClipboard: vi.fn<(text: string) => Promise<void>>(),
|
||||
readClipboardFilePaths: vi.fn<() => Promise<string[] | null>>(),
|
||||
readClipboardText: vi.fn<() => Promise<string | null>>(),
|
||||
}));
|
||||
|
||||
@@ -118,7 +127,14 @@ describe("createInteractiveTui", () => {
|
||||
renderer.setFocus(component);
|
||||
|
||||
type SwitchContext = {
|
||||
runtimeHost: { session: { settingsManager: { getFullscreenCopyOnSelect: () => boolean } } };
|
||||
runtimeHost: {
|
||||
session: {
|
||||
settingsManager: {
|
||||
getFullscreenCopyOnSelect: () => boolean;
|
||||
getFullscreenWheelScrollLines: () => WheelScrollLines;
|
||||
};
|
||||
};
|
||||
};
|
||||
renderer: ReturnType<typeof createInteractiveTui>;
|
||||
ui: TUI;
|
||||
fullscreenLayoutRoot: Component;
|
||||
@@ -127,7 +143,11 @@ describe("createInteractiveTui", () => {
|
||||
extensionTerminalInputSubscriptions: Set<never>;
|
||||
};
|
||||
const context = Object.assign(Object.create(InteractiveMode.prototype), {
|
||||
runtimeHost: { session: { settingsManager: { getFullscreenCopyOnSelect: () => true } } },
|
||||
runtimeHost: {
|
||||
session: {
|
||||
settingsManager: { getFullscreenCopyOnSelect: () => true, getFullscreenWheelScrollLines: () => "auto" },
|
||||
},
|
||||
},
|
||||
renderer,
|
||||
ui: undefined as unknown as TUI,
|
||||
fullscreenLayoutRoot: component,
|
||||
@@ -343,13 +363,53 @@ describe("InteractiveMode copy confirmation", () => {
|
||||
});
|
||||
});
|
||||
|
||||
type RenderSessionEntriesContext = {
|
||||
renderer: ReturnType<typeof createInteractiveTui>;
|
||||
renderSessionItems: (items: unknown[]) => void;
|
||||
};
|
||||
|
||||
describe("InteractiveMode transcript rebuild", () => {
|
||||
it("drops the fullscreen selection when session entries are re-rendered", async () => {
|
||||
// Regression test for #9311: selection coordinates survived session switches.
|
||||
const terminal = new RecordingTerminal(40, 4);
|
||||
const ui = createInteractiveTui({
|
||||
tuiMode: "fullscreen",
|
||||
showHardwareCursor: false,
|
||||
logDirectory: "/tmp",
|
||||
terminal,
|
||||
fullscreenCopyOnSelect: false,
|
||||
});
|
||||
ui.addChild(new Text("alpha\nbeta\ngamma\ndelta", 0, 0));
|
||||
const context: RenderSessionEntriesContext = { renderer: ui, renderSessionItems: vi.fn() };
|
||||
const { renderSessionEntries } = InteractiveMode.prototype as unknown as {
|
||||
renderSessionEntries(this: RenderSessionEntriesContext, entries: unknown[]): void;
|
||||
};
|
||||
|
||||
ui.start();
|
||||
try {
|
||||
await terminal.waitForRender();
|
||||
terminal.sendInput("\x1b[<0;1;1M");
|
||||
terminal.sendInput("\x1b[<32;4;2M");
|
||||
terminal.sendInput("\x1b[<0;4;2m");
|
||||
await terminal.waitForRender();
|
||||
expect(ui.hasActiveSelection()).toBe(true);
|
||||
|
||||
renderSessionEntries.call(context, []);
|
||||
|
||||
expect(ui.hasActiveSelection()).toBe(false);
|
||||
} finally {
|
||||
ui.stop();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
type StatusEditor = {
|
||||
embedWorkingStatus: boolean;
|
||||
setWorkingStatusIndicator: (indicator: undefined) => void;
|
||||
setWorkingStatusIndicator: (indicator: StatusIndicator | undefined) => void;
|
||||
};
|
||||
|
||||
type ClearStatusContext = {
|
||||
activeStatusIndicator: { kind: "working" | "retry"; dispose: () => void } | undefined;
|
||||
activeStatusIndicator: { kind: StatusIndicatorKind; dispose: () => void } | undefined;
|
||||
activeWorkingIndicatorEmbedded: boolean;
|
||||
statusContainer: Container;
|
||||
defaultEditor: StatusEditor;
|
||||
@@ -357,39 +417,83 @@ type ClearStatusContext = {
|
||||
options: { tuiMode?: TuiMode };
|
||||
ui: { getClearOnShrink: () => boolean };
|
||||
idleStatus: Component;
|
||||
setEditorWorkingStatusIndicator(indicator: undefined): boolean;
|
||||
setEditorWorkingStatusIndicator(indicator: StatusIndicator | undefined): boolean;
|
||||
};
|
||||
|
||||
type InteractiveModePrototype = {
|
||||
clearStatusIndicator(this: ClearStatusContext, kind?: "working" | "retry"): void;
|
||||
setEditorWorkingStatusIndicator(this: ClearStatusContext, indicator: undefined): boolean;
|
||||
showStatusIndicator(this: ClearStatusContext, indicator: StatusIndicator): void;
|
||||
clearStatusIndicator(this: ClearStatusContext, kind?: StatusIndicatorKind): void;
|
||||
setEditorWorkingStatusIndicator(this: ClearStatusContext, indicator: StatusIndicator | undefined): boolean;
|
||||
};
|
||||
|
||||
const interactiveModePrototype = InteractiveMode.prototype as unknown as InteractiveModePrototype;
|
||||
|
||||
describe("clear-on-shrink status spacing", () => {
|
||||
it("does not reserve separate status height for the editor-border working indicator", () => {
|
||||
const dispose = vi.fn();
|
||||
const editor: StatusEditor = { embedWorkingStatus: true, setWorkingStatusIndicator: vi.fn() };
|
||||
it.each([true, false])("routes every status through the editor opt-in (%s)", (embedWorkingStatus) => {
|
||||
initTheme("dark");
|
||||
const tui = { requestRender: vi.fn() } as unknown as TUI;
|
||||
const editor: StatusEditor = { embedWorkingStatus, setWorkingStatusIndicator: vi.fn() };
|
||||
const context: ClearStatusContext = {
|
||||
activeStatusIndicator: { kind: "working", dispose },
|
||||
activeWorkingIndicatorEmbedded: true,
|
||||
activeStatusIndicator: undefined,
|
||||
activeWorkingIndicatorEmbedded: false,
|
||||
statusContainer: new Container(),
|
||||
defaultEditor: editor,
|
||||
defaultEditor: { embedWorkingStatus: true, setWorkingStatusIndicator: vi.fn() },
|
||||
editor,
|
||||
options: { tuiMode: "regular" },
|
||||
ui: { getClearOnShrink: () => true },
|
||||
idleStatus: new Text("", 0, 0),
|
||||
setEditorWorkingStatusIndicator: interactiveModePrototype.setEditorWorkingStatusIndicator,
|
||||
};
|
||||
|
||||
interactiveModePrototype.clearStatusIndicator.call(context);
|
||||
|
||||
expect(dispose).toHaveBeenCalledOnce();
|
||||
expect(editor.setWorkingStatusIndicator).toHaveBeenCalledWith(undefined);
|
||||
expect(context.statusContainer.children).toHaveLength(0);
|
||||
const indicators = [
|
||||
new WorkingStatusIndicator(tui, "Working"),
|
||||
new CompactionStatusIndicator(tui, "manual"),
|
||||
new CompactionStatusIndicator(tui, "threshold"),
|
||||
new CompactionStatusIndicator(tui, "overflow"),
|
||||
new BranchSummaryStatusIndicator(tui),
|
||||
new RetryStatusIndicator(tui, 1, 3, 1000),
|
||||
];
|
||||
try {
|
||||
for (const indicator of indicators) {
|
||||
interactiveModePrototype.showStatusIndicator.call(context, indicator);
|
||||
expect(context.activeStatusIndicator).toBe(indicator);
|
||||
expect(context.activeWorkingIndicatorEmbedded).toBe(embedWorkingStatus);
|
||||
if (embedWorkingStatus) {
|
||||
expect(editor.setWorkingStatusIndicator).toHaveBeenLastCalledWith(indicator);
|
||||
expect(context.statusContainer.children).toHaveLength(0);
|
||||
} else {
|
||||
expect(context.statusContainer.children).toEqual([indicator]);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
for (const indicator of indicators) indicator.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
it.each<StatusIndicatorKind>(["working", "compaction", "branchSummary", "retry"])(
|
||||
"does not reserve separate status height for an embedded %s indicator",
|
||||
(kind) => {
|
||||
const dispose = vi.fn();
|
||||
const editor: StatusEditor = { embedWorkingStatus: true, setWorkingStatusIndicator: vi.fn() };
|
||||
const context: ClearStatusContext = {
|
||||
activeStatusIndicator: { kind, dispose },
|
||||
activeWorkingIndicatorEmbedded: true,
|
||||
statusContainer: new Container(),
|
||||
defaultEditor: editor,
|
||||
editor,
|
||||
options: { tuiMode: "regular" },
|
||||
ui: { getClearOnShrink: () => true },
|
||||
idleStatus: new Text("", 0, 0),
|
||||
setEditorWorkingStatusIndicator: interactiveModePrototype.setEditorWorkingStatusIndicator,
|
||||
};
|
||||
|
||||
interactiveModePrototype.clearStatusIndicator.call(context);
|
||||
|
||||
expect(dispose).toHaveBeenCalledOnce();
|
||||
expect(editor.setWorkingStatusIndicator).toHaveBeenCalledWith(undefined);
|
||||
expect(context.statusContainer.children).toHaveLength(0);
|
||||
},
|
||||
);
|
||||
|
||||
it("uses the standalone row for a custom editor that has not opted in", () => {
|
||||
for (const [tuiMode, expectedChildren] of [
|
||||
["regular", 1],
|
||||
|
||||
Reference in New Issue
Block a user