Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/coding-agent/src/cli/config-selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export async function selectConfig(options: ConfigSelectorOptions): Promise<void
initTheme(options.settingsManager.getTheme(), true);

return new Promise((resolve) => {
const ui = new TUI(new ProcessTerminal());
const ui = new TUI(new ProcessTerminal(), undefined, options.agentDir);
let resolved = false;

const selector = new ConfigSelectorComponent(
Expand Down
2 changes: 1 addition & 1 deletion packages/coding-agent/src/cli/startup-ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export async function createStartupTui(settingsManager: SettingsManager): Promis
const terminalTheme = detectTerminalBackgroundFromEnv().theme;
initTheme(resolveThemeSetting(settingsManager.getThemeSetting(), terminalTheme) ?? terminalTheme);
setKeybindings(KeybindingsManager.create());
const ui = new TUI(new ProcessTerminal(), settingsManager.getShowHardwareCursor());
const ui = new TUI(new ProcessTerminal(), settingsManager.getShowHardwareCursor(), getAgentDir());
ui.setClearOnShrink(settingsManager.getClearOnShrink());
return ui;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ export class InteractiveMode {
await this.rebindCurrentSession({ renderBeforeBind: true });
});
this.version = VERSION;
this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());
this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor(), getAgentDir());
this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
this.headerContainer = new Container();
this.loadedResourcesContainer = new Container();
Expand Down
9 changes: 6 additions & 3 deletions packages/tui/src/tui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,15 +319,17 @@ export class TUI extends Container {
private pendingOsc11BackgroundQueries: PendingOsc11BackgroundQuery[] = [];
private terminalColorSchemeListeners = new Set<(scheme: TerminalColorScheme) => void>();
private terminalColorSchemeNotificationsEnabled = false;
private readonly logDirectory: string;

// Overlay stack for modal components rendered on top of base content
private focusOrderCounter = 0;
private overlayStack: OverlayStackEntry[] = [];
private overlayFocusRestore: OverlayFocusRestoreState = { status: "inactive" };

constructor(terminal: Terminal, showHardwareCursor?: boolean) {
constructor(terminal: Terminal, showHardwareCursor?: boolean, logDirectory?: string) {
super();
this.terminal = terminal;
this.logDirectory = logDirectory ?? process.env.PI_CODING_AGENT_DIR ?? path.join(os.homedir(), ".pi", "agent");
if (showHardwareCursor !== undefined) {
this.showHardwareCursor = showHardwareCursor;
}
Expand Down Expand Up @@ -1329,8 +1331,9 @@ export class TUI extends Container {
const debugRedraw = process.env.PI_DEBUG_REDRAW === "1";
const logRedraw = (reason: string): void => {
if (!debugRedraw) return;
const logPath = path.join(os.homedir(), ".pi", "agent", "pi-debug.log");
const logPath = path.join(this.logDirectory, "pi-debug.log");
const msg = `[${new Date().toISOString()}] fullRender: ${reason} (prev=${this.previousLines.length}, new=${newLines.length}, height=${height})\n`;
fs.mkdirSync(path.dirname(logPath), { recursive: true });
fs.appendFileSync(logPath, msg);
};

Expand Down Expand Up @@ -1521,7 +1524,7 @@ export class TUI extends Container {
buffer += "\x1b[2K"; // Clear current line
if (!isImage && visibleWidth(line) > width) {
// Log all lines to crash file for debugging
const crashLogPath = path.join(os.homedir(), ".pi", "agent", "pi-crash.log");
const crashLogPath = path.join(this.logDirectory, "pi-crash.log");
const crashData = [
`Crash at ${new Date().toISOString()}`,
`Terminal width: ${width}`,
Expand Down
25 changes: 25 additions & 0 deletions packages/tui/test/tui-render.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import assert from "node:assert";
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, it } from "node:test";
import type { Terminal as XtermTerminalType } from "@xterm/headless";
import { Image } from "../src/components/image.ts";
Expand Down Expand Up @@ -71,6 +74,28 @@ function getCellItalic(terminal: VirtualTerminal, row: number, col: number): num
return cell.isItalic();
}

describe("TUI debug logging", () => {
it("writes redraw logs to the provided directory", async () => {
const logDir = mkdtempSync(join(tmpdir(), "pi-tui-log-"));
try {
await withEnv({ PI_DEBUG_REDRAW: "1" }, async () => {
const terminal = new VirtualTerminal(40, 10);
const tui = new TUI(terminal, undefined, logDir);
const component = new TestComponent();
tui.addChild(component);
component.lines = ["test"];
tui.start();
await terminal.waitForRender();

assert.match(readFileSync(join(logDir, "pi-debug.log"), "utf-8"), /fullRender: first render/);
tui.stop();
});
} finally {
rmSync(logDir, { recursive: true, force: true });
}
});
});

describe("TUI Kitty image cleanup", () => {
it("clears reserved Kitty image rows before drawing appended image placements", async () => {
setCapabilities({ images: "kitty", trueColor: true, hyperlinks: true });
Expand Down