From 1c817df1e522f438d4392568b64fc039dc867031 Mon Sep 17 00:00:00 2001 From: liruifengv Date: Mon, 6 Jul 2026 13:50:23 +0800 Subject: [PATCH] fix(tui): include surrounding context in edit approval preview (#1410) --- .changeset/fix-edit-preview-context.md | 5 +++++ .../components/dialogs/approval-preview.ts | 20 ++++++++++--------- .../src/tui/components/media/diff-preview.ts | 10 +++++++++- .../dialogs/approval-preview.test.ts | 20 +++++++++++++++++++ .../tui/components/media/diff-preview.test.ts | 16 +++++++++++++++ 5 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 .changeset/fix-edit-preview-context.md diff --git a/.changeset/fix-edit-preview-context.md b/.changeset/fix-edit-preview-context.md new file mode 100644 index 000000000..ca472a800 --- /dev/null +++ b/.changeset/fix-edit-preview-context.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Fix the edit approval preview shown by ctrl+e to include surrounding context lines, matching the summary panel. diff --git a/apps/kimi-code/src/tui/components/dialogs/approval-preview.ts b/apps/kimi-code/src/tui/components/dialogs/approval-preview.ts index b5be012e7..044884792 100644 --- a/apps/kimi-code/src/tui/components/dialogs/approval-preview.ts +++ b/apps/kimi-code/src/tui/components/dialogs/approval-preview.ts @@ -27,7 +27,7 @@ import { } from '@moonshot-ai/pi-tui'; import { highlightLines, langFromPath } from '#/tui/components/media/code-highlight'; -import { renderDiffLines } from '#/tui/components/media/diff-preview'; +import { renderDiffLinesClustered } from '#/tui/components/media/diff-preview'; import type { DiffDisplayBlock, FileContentDisplayBlock } from '#/tui/reverse-rpc/types'; import { currentTheme } from '#/tui/theme'; import { printableChar } from '#/tui/utils/printable-key'; @@ -218,17 +218,19 @@ function buildBody(block: ApprovalPreviewBlock): BuiltBody { } function buildDiffBody(block: DiffDisplayBlock): BuiltBody { - // renderDiffLines emits a `+N -M path` header on its first line followed - // by every changed line. We pull the header out into the viewer chrome so - // the body is purely scrollable diff content; this also means we don't - // double-render the path. - const rendered = renderDiffLines( + // renderDiffLinesClustered emits a `+N -M path` header on its first line + // followed by every changed line plus surrounding context. We pull the + // header out into the viewer chrome so the body is purely scrollable diff + // content; this also means we don't double-render the path. + const rendered = renderDiffLinesClustered( block.old_text, block.new_text, block.path, - false, - block.old_start ?? 1, - block.new_start ?? 1, + { + contextLines: 3, + oldStart: block.old_start ?? 1, + newStart: block.new_start ?? 1, + }, ); const [header = '', ...rest] = rendered; return { lines: rest, title: stripLeadingSpace(header) }; diff --git a/apps/kimi-code/src/tui/components/media/diff-preview.ts b/apps/kimi-code/src/tui/components/media/diff-preview.ts index 00ede4a26..1fec48b26 100644 --- a/apps/kimi-code/src/tui/components/media/diff-preview.ts +++ b/apps/kimi-code/src/tui/components/media/diff-preview.ts @@ -156,6 +156,8 @@ export interface ClusteredDiffOptions { readonly maxLines?: number; readonly isIncomplete?: boolean; readonly expandKeyHint?: string; + readonly oldStart?: number; + readonly newStart?: number; } interface Cluster { @@ -239,7 +241,13 @@ export function renderDiffLinesClustered( const s = makeDiffStyles(); const contextLines = opts.contextLines ?? 3; const maxLines = opts.maxLines; - const diffLines = computeDiffLines(oldText, newText, 1, 1, opts.isIncomplete ?? false); + const diffLines = computeDiffLines( + oldText, + newText, + opts.oldStart ?? 1, + opts.newStart ?? 1, + opts.isIncomplete ?? false, + ); const { clusters, changedCount, addedCount, removedCount } = buildClusters( diffLines, contextLines, diff --git a/apps/kimi-code/test/tui/components/dialogs/approval-preview.test.ts b/apps/kimi-code/test/tui/components/dialogs/approval-preview.test.ts index a1b8b563e..316b5f89d 100644 --- a/apps/kimi-code/test/tui/components/dialogs/approval-preview.test.ts +++ b/apps/kimi-code/test/tui/components/dialogs/approval-preview.test.ts @@ -138,6 +138,26 @@ describe('ApprovalPreviewViewer', () => { expect(text).toContain('BETA'); }); + it('shows surrounding context lines for a diff block', () => { + const viewer = makeViewer({ + block: { + type: 'diff', + path: 'src/foo.ts', + old_text: ['before1', 'before2', 'old', 'after1', 'after2'].join('\n'), + new_text: ['before1', 'before2', 'new', 'after1', 'after2'].join('\n'), + }, + rows: 24, + }); + + const text = strip(viewer.render(100).join('\n')); + expect(text).toContain('before1'); + expect(text).toContain('before2'); + expect(text).toContain('old'); + expect(text).toContain('new'); + expect(text).toContain('after1'); + expect(text).toContain('after2'); + }); + // Sanity: rendering is a pure slice — repeated render() calls without // input changes produce the same output, no incremental state drift. it('renders deterministically across repeated calls', () => { diff --git a/apps/kimi-code/test/tui/components/media/diff-preview.test.ts b/apps/kimi-code/test/tui/components/media/diff-preview.test.ts index 8e7214e11..d355bb7ed 100644 --- a/apps/kimi-code/test/tui/components/media/diff-preview.test.ts +++ b/apps/kimi-code/test/tui/components/media/diff-preview.test.ts @@ -155,6 +155,22 @@ describe('renderDiffLinesClustered', () => { expect(text).toContain('ctrl+o to expand'); }); + it('respects oldStart and newStart for line numbers', () => { + const text = stripAnsi( + renderDiffLinesClustered('A\nB\nC', 'A\nX\nC', 'f.ts', { + contextLines: 1, + oldStart: 10, + newStart: 20, + }).join('\n'), + ); + // Context lines keep the new (post-edit) line numbers from newStart; + // deleted lines use oldStart; added lines use newStart. + expect(text).toContain(' 20 A'); + expect(text).toContain(' 11 - B'); + expect(text).toContain(' 21 + X'); + expect(text).toContain(' 22 C'); + }); + it('truncates at cluster boundary and appends the ctrl+o footer when maxLines is set', () => { const oldLines: string[] = []; for (let i = 1; i <= 50; i++) oldLines.push(`L${String(i)}`);