fix(tui): used cursor save/restore for direct image placement
- Added cursor save/restore escape handling around direct image emission. - Replaced the previous up-and-down cursor movement sequence with restore logic to keep renderer output positioned correctly. - Ensured direct image replay path no longer depends on moving back down after placement to align the cursor.
This commit is contained in:
@@ -19,10 +19,10 @@ import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config
|
||||
import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
|
||||
import type { InteractiveModeContext } from "@oh-my-pi/pi-coding-agent/modes/types";
|
||||
import { UiHelpers } from "@oh-my-pi/pi-coding-agent/modes/utils/ui-helpers";
|
||||
import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager";
|
||||
import type { SessionContext } from "@oh-my-pi/pi-coding-agent/session/session-context";
|
||||
import { TempDir } from "@oh-my-pi/pi-utils";
|
||||
import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager";
|
||||
import { type Component, Container, Image, ImageProtocol, setTerminalImageProtocol, TERMINAL } from "@oh-my-pi/pi-tui";
|
||||
import { TempDir } from "@oh-my-pi/pi-utils";
|
||||
|
||||
beforeAll(() => {
|
||||
initTheme();
|
||||
@@ -171,8 +171,10 @@ function makeRenderCtx(transcript: SessionContext): { ctx: InteractiveModeContex
|
||||
},
|
||||
addMessageToChat: (message: AgentMessage, options?: { populateHistory?: boolean }) =>
|
||||
helpers.addMessageToChat(message, options),
|
||||
renderSessionContext: (context: SessionContext, options?: { updateFooter?: boolean; populateHistory?: boolean }) =>
|
||||
helpers.renderSessionContext(context, options),
|
||||
renderSessionContext: (
|
||||
context: SessionContext,
|
||||
options?: { updateFooter?: boolean; populateHistory?: boolean },
|
||||
) => helpers.renderSessionContext(context, options),
|
||||
showStatus: vi.fn(),
|
||||
} as unknown as InteractiveModeContext;
|
||||
helpers = new UiHelpers(ctx);
|
||||
@@ -277,7 +279,9 @@ describe("UiHelpers.renderInitialMessages — image replay", () => {
|
||||
isError: false,
|
||||
timestamp: 2,
|
||||
});
|
||||
session.appendMessage(assistantToolCall("eval-reopened", "eval", { cells: [{ language: "py", code: "display(image)" }] }));
|
||||
session.appendMessage(
|
||||
assistantToolCall("eval-reopened", "eval", { cells: [{ language: "py", code: "display(image)" }] }),
|
||||
);
|
||||
session.appendMessage({
|
||||
role: "toolResult",
|
||||
toolCallId: "eval-reopened",
|
||||
|
||||
@@ -29,6 +29,8 @@ export interface ImageOptions {
|
||||
|
||||
const EMPTY_IDS: readonly number[] = [];
|
||||
const EMPTY_TRANSMITS: readonly string[] = [];
|
||||
const SAVE_CURSOR = "\x1b7";
|
||||
const RESTORE_CURSOR = "\x1b8";
|
||||
// Direct placements reserve height with leading zero-width rows. Keep them
|
||||
// non-plain so transcript blank-edge trimming does not collapse image-only blocks.
|
||||
const RESERVED_IMAGE_ROW = "\x1b[0m";
|
||||
@@ -366,16 +368,18 @@ export class Image implements Component {
|
||||
} else if (result) {
|
||||
// Direct placement: return `rows` lines so TUI accounts for image
|
||||
// height. First (rows-1) lines are empty (TUI clears them); the last
|
||||
// moves the cursor back up, emits the image sequence, then restores the
|
||||
// cursor so the renderer's next CRLF starts below the reserved block.
|
||||
// saves the final-row cursor, moves up to the image origin, emits the
|
||||
// image sequence, then restores the final-row cursor. Save/restore is
|
||||
// required because CUU clamps at the viewport top when leading rows are
|
||||
// clipped away.
|
||||
lines = [];
|
||||
for (let i = 0; i < result.rows - 1; i++) {
|
||||
lines.push(RESERVED_IMAGE_ROW);
|
||||
}
|
||||
const cursorRows = result.rows - 1;
|
||||
const moveUp = cursorRows > 0 ? `\x1b[${cursorRows}A` : "";
|
||||
const moveDown = cursorRows > 0 ? `\x1b[${cursorRows}B` : "";
|
||||
lines.push(moveUp + (result.sequence ?? "") + moveDown);
|
||||
const placement = moveUp + (result.sequence ?? "");
|
||||
lines.push(cursorRows > 0 ? SAVE_CURSOR + placement + RESTORE_CURSOR : placement);
|
||||
} else {
|
||||
lines = this.#fallbackLines();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user