diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 11098023c..ebb7b6182 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,11 @@ ## [Unreleased] +### Changed + +- Memoize copy-selector preview highlight so keyboard navigation does not re-highlight the whole preview every render ([#4238](https://github.com/can1357/oh-my-pi/issues/4238)) + + ## [16.3.0] - 2026-07-02 ### Added diff --git a/packages/coding-agent/src/modes/components/copy-selector.ts b/packages/coding-agent/src/modes/components/copy-selector.ts index 02fe40e0b..70eab4e1d 100644 --- a/packages/coding-agent/src/modes/components/copy-selector.ts +++ b/packages/coding-agent/src/modes/components/copy-selector.ts @@ -53,6 +53,8 @@ function gutterCells(hasNext: boolean): string { export class CopySelectorComponent implements Component { #roots: CopyTarget[]; #cursorId: string; + #lastSourceTarget?: CopyTarget; + #lastSource?: string; #treeRows = MIN_TREE_ROWS; // Reused across renders to wrap preview content to the pane width. #previewText = new Text("", 0, 0); @@ -65,7 +67,10 @@ export class CopySelectorComponent implements Component { this.#cursorId = roots[0]?.id ?? ""; } - invalidate(): void {} + invalidate(): void { + this.#lastSourceTarget = undefined; + this.#lastSource = undefined; + } #flatten(): FlatNode[] { const out: FlatNode[] = []; @@ -153,9 +158,16 @@ export class CopySelectorComponent implements Component { // Code/command previews are syntax-highlighted; everything else is shown // as plain text. Both are wrapped (not hard-truncated) to the pane width. const isCode = target.language !== undefined; - const source = isCode - ? highlightCode(replaceTabs(target.preview), target.language).join("\n") - : replaceTabs(target.preview); + let source: string; + if (target === this.#lastSourceTarget && this.#lastSource !== undefined) { + source = this.#lastSource; + } else { + source = isCode + ? highlightCode(replaceTabs(target.preview), target.language).join("\n") + : replaceTabs(target.preview); + this.#lastSourceTarget = target; + this.#lastSource = source; + } this.#previewText.setText(source); const wrapped = this.#previewText.render(Math.max(1, width - 4));