Merge pull request #587 from smileynet/fix/puppeteer-excessive-scrolling
fix(tui): cap inline image height and preserve multiplexer scrollback
This commit is contained in:
@@ -8,6 +8,9 @@
|
||||
- `--scope user|project` flag added to `/marketplace uninstall`, `/marketplace upgrade`, `/plugins enable`, and `/plugins disable` to disambiguate when a plugin is installed in both scopes
|
||||
- `omp plugin upgrade --scope project` with no plugin ID warns that `--scope` is ignored for bulk upgrades
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed inline image rendering to cap image height and preserve multiplexer scrollback during terminal resizes ([#587](https://github.com/can1357/oh-my-pi/pull/587) by [@smileynet](https://github.com/smileynet))
|
||||
## [13.17.1] - 2026-04-01
|
||||
### Removed
|
||||
|
||||
|
||||
@@ -372,6 +372,12 @@ export const SETTINGS_SCHEMA = {
|
||||
"Maximum width in terminal columns for inline images (default 100). Set to 0 for unlimited (bounded only by terminal width).",
|
||||
},
|
||||
|
||||
"tui.maxInlineImageRows": {
|
||||
type: "number",
|
||||
default: 20,
|
||||
description:
|
||||
"Maximum height in terminal rows for inline images (default 20). Set to 0 to use only the viewport-based limit (60% of terminal height).",
|
||||
},
|
||||
// Display rendering
|
||||
"display.tabWidth": {
|
||||
type: "number",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { formatNumber, logger } from "@oh-my-pi/pi-utils";
|
||||
import { settings } from "../../config/settings";
|
||||
import { hasPendingMermaid, prerenderMermaid } from "../../modes/theme/mermaid-cache";
|
||||
import { getMarkdownTheme, theme } from "../../modes/theme/theme";
|
||||
import { resolveImageOptions } from "../../tools/render-utils";
|
||||
|
||||
/**
|
||||
* Component that renders a complete assistant message
|
||||
@@ -76,7 +77,7 @@ export class AssistantMessageComponent extends Container {
|
||||
image.data,
|
||||
image.mimeType,
|
||||
{ fallbackColor: (text: string) => theme.fg("toolOutput", text) },
|
||||
{ maxWidthCells: settings.get("tui.maxInlineImageColumns") },
|
||||
resolveImageOptions(),
|
||||
),
|
||||
);
|
||||
continue;
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
type TUI,
|
||||
} from "@oh-my-pi/pi-tui";
|
||||
import { getProjectDir, logger } from "@oh-my-pi/pi-utils";
|
||||
import { settings } from "../../config/settings";
|
||||
import type { Theme } from "../../modes/theme/theme";
|
||||
import { theme } from "../../modes/theme/theme";
|
||||
import { computeEditDiff, computeHashlineDiff, computePatchDiff, type DiffError, type DiffResult } from "../../patch";
|
||||
@@ -31,7 +30,7 @@ import {
|
||||
stripInternalArgs,
|
||||
} from "../../tools/json-tree";
|
||||
import { PYTHON_DEFAULT_PREVIEW_LINES } from "../../tools/python";
|
||||
import { formatExpandHint, replaceTabs, truncateToWidth } from "../../tools/render-utils";
|
||||
import { formatExpandHint, replaceTabs, resolveImageOptions, truncateToWidth } from "../../tools/render-utils";
|
||||
import { toolRenderers } from "../../tools/renderers";
|
||||
import { renderStatusLine } from "../../tui";
|
||||
import { convertToPng } from "../../utils/image-convert";
|
||||
@@ -531,7 +530,7 @@ export class ToolExecutionComponent extends Container {
|
||||
imageData,
|
||||
imageMimeType,
|
||||
{ fallbackColor: (s: string) => theme.fg("toolOutput", s) },
|
||||
{ maxWidthCells: settings.get("tui.maxInlineImageColumns") },
|
||||
resolveImageOptions(),
|
||||
);
|
||||
this.#imageComponents.push(imageComponent);
|
||||
this.addChild(imageComponent);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import * as os from "node:os";
|
||||
import { type Ellipsis, truncateToWidth } from "@oh-my-pi/pi-tui";
|
||||
import { getIndentation, pluralize } from "@oh-my-pi/pi-utils";
|
||||
import { settings } from "../config/settings";
|
||||
import type { Theme } from "../modes/theme/theme";
|
||||
import { formatDimensionNote, type ResizedImage } from "../utils/image-resize";
|
||||
|
||||
@@ -19,6 +20,25 @@ export function replaceTabs(text: string, file?: string): string {
|
||||
// Standardized Display Constants
|
||||
// =============================================================================
|
||||
|
||||
/** Resolve inline image dimension caps from settings and viewport. */
|
||||
export function resolveImageOptions(): { maxWidthCells: number; maxHeightCells?: number } {
|
||||
const maxWidthCells = settings.get("tui.maxInlineImageColumns");
|
||||
const rowSetting = Math.max(0, settings.get("tui.maxInlineImageRows"));
|
||||
const viewportRows = process.stdout.rows;
|
||||
const viewportFraction = viewportRows ? Math.floor(viewportRows * 0.6) : 0;
|
||||
let maxHeightCells: number | undefined;
|
||||
if (rowSetting === 0) {
|
||||
// No explicit cap — use viewport fraction as safety bound
|
||||
maxHeightCells = viewportFraction || undefined;
|
||||
} else if (viewportFraction > 0) {
|
||||
maxHeightCells = Math.min(rowSetting, viewportFraction);
|
||||
} else {
|
||||
// Viewport size unknown (transitional state) — honor explicit setting
|
||||
maxHeightCells = rowSetting;
|
||||
}
|
||||
return { maxWidthCells, maxHeightCells };
|
||||
}
|
||||
|
||||
/** Preview limits for collapsed/expanded views */
|
||||
export const PREVIEW_LIMITS = {
|
||||
/** Lines shown in collapsed view */
|
||||
|
||||
@@ -112,6 +112,9 @@ function isTermuxSession(): boolean {
|
||||
return Boolean(process.env.TERMUX_VERSION);
|
||||
}
|
||||
|
||||
/** Detect terminal multiplexers where scrollback clearing and height-change redraws are hostile. */
|
||||
const isMultiplexer = Boolean(Bun.env.TMUX || Bun.env.STY || Bun.env.ZELLIJ);
|
||||
|
||||
/**
|
||||
* Options for overlay positioning and sizing.
|
||||
* Values can be absolute numbers or percentage strings (e.g., "50%").
|
||||
@@ -1007,7 +1010,8 @@ export class TUI extends Container {
|
||||
const fullRender = (clear: boolean): void => {
|
||||
this.#fullRedrawCount += 1;
|
||||
let buffer = "\x1b[?2026h"; // Begin synchronized output
|
||||
if (clear) buffer += "\x1b[2J\x1b[H\x1b[3J"; // Clear screen, home, then clear scrollback
|
||||
// Skip clearing scrollback (3J) in multiplexers — users actively navigate scrollback history
|
||||
if (clear) buffer += isMultiplexer ? "\x1b[2J\x1b[H" : "\x1b[2J\x1b[H\x1b[3J";
|
||||
const reset = SEGMENT_RESET;
|
||||
for (let i = 0; i < newLines.length; i++) {
|
||||
if (i > 0) buffer += "\r\n";
|
||||
@@ -1056,7 +1060,7 @@ export class TUI extends Container {
|
||||
// Height changes normally need a full re-render to keep the visible viewport aligned,
|
||||
// but Termux changes height when the software keyboard shows or hides.
|
||||
// In that environment, a full redraw causes the entire history to replay on every toggle.
|
||||
if (heightChanged && !isTermuxSession()) {
|
||||
if (heightChanged && !isTermuxSession() && !isMultiplexer) {
|
||||
logRedraw(`terminal height changed (${this.#previousHeight} -> ${height})`);
|
||||
fullRender(true);
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user