diff --git a/apps/review-desktop/code-oss/src/vs/review/common/reviewLens.test.ts b/apps/review-desktop/code-oss/src/vs/review/common/reviewLens.test.ts index 858a7a608..7799b376f 100644 --- a/apps/review-desktop/code-oss/src/vs/review/common/reviewLens.test.ts +++ b/apps/review-desktop/code-oss/src/vs/review/common/reviewLens.test.ts @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import test from 'node:test'; import { lensContextGaps } from './reviewLens.js'; -import type { IDocumentDiff } from '../../editor/common/diff/documentDiffProvider.js'; +import type { IDocumentContextGap, IDocumentDiff } from '../../editor/common/diff/documentDiffProvider.js'; const plain: IDocumentDiff = { changes: [], moves: [], identical: false, quitEarly: false }; test('a lens keeps disjoint attachments and hides the intervening code', () => { @@ -34,6 +34,49 @@ test('a clipped outer fold leaves its nested fold usable in a code peek', () => assert.deepEqual(whole.filter(gap => gap.foldStateId).map(gap => gap.foldStateId), [1]); }); +// The head lines left on screen, as [from, to] runs. +const shownLines = (gaps: readonly IDocumentContextGap[], lineCount: number) => { + const runs: [number, number][] = []; + for (let line = 1; line <= lineCount; line++) { + if (gaps.some(gap => gap.collapsed !== false && line >= gap.modifiedStart && line < gap.modifiedStart + gap.modifiedCount)) continue; + const last = runs.at(-1); + if (last && last[1] === line - 1) last[1] = line; + else runs.push([line, line]); + } + return runs; +}; +const head = (fromLine: number, toLine: number) => [{ side: 'head' as const, file: 'a.ts', fromLine, toLine }]; + +test('in an added file a lens shows the first and last lines of its enclosing scopes, not the whole file', () => { + // A 30-line added file: a class on lines 3-25 with a method on lines 10-18. Nothing is collapsed. + const added: IDocumentDiff = { + ...plain, + sourceLineAlignment: Array.from({ length: 30 }, (_, i) => [null, i] as const), + contextScopes: { original: [], modified: [[2, 25], [9, 18]] }, + }; + assert.deepEqual(shownLines(lensContextGaps(added, 0, 30, head(13, 14)), 30), [[3, 3], [10, 18], [25, 25]]); + // The same, with a function on lines 27-29 folded. + const folded: IDocumentDiff = { + ...added, + contextScopes: { original: [], modified: [[2, 25], [9, 18], [26, 29]] }, + contextGaps: [{ originalStart: 1, originalCount: 0, modifiedStart: 27, modifiedCount: 3, collapsed: true }], + }; + assert.deepEqual(shownLines(lensContextGaps(folded, 0, 30, head(13, 14)), 30), [[3, 3], [10, 18], [25, 25]]); +}); + +test('in a modified file a lens shows the whole run between collapsed bands', () => { + // A function on lines 5-20, with the code before and after it collapsed. + const modified: IDocumentDiff = { + ...plain, + contextScopes: { original: [[4, 20]], modified: [[4, 20]] }, + contextGaps: [ + { originalStart: 1, originalCount: 4, modifiedStart: 1, modifiedCount: 4, collapsed: true }, + { originalStart: 21, originalCount: 10, modifiedStart: 21, modifiedCount: 10, collapsed: true }, + ], + }; + assert.deepEqual(shownLines(lensContextGaps(modified, 30, 30, head(15, 15)), 30), [[5, 20]]); +}); + test('viewed folds never hide an unread counterpart, and do not overlap structural folds', async () => { const { viewedContextGaps } = await import('./reviewLens.js'); const range = (side: 'base' | 'head', fromLine: number, toLine: number) => ({ side, file: 'a.ts', fromLine, toLine }); diff --git a/apps/review-desktop/code-oss/src/vs/review/common/reviewLens.ts b/apps/review-desktop/code-oss/src/vs/review/common/reviewLens.ts index 1289ec812..11592fdb1 100644 --- a/apps/review-desktop/code-oss/src/vs/review/common/reviewLens.ts +++ b/apps/review-desktop/code-oss/src/vs/review/common/reviewLens.ts @@ -35,7 +35,10 @@ export function lensContextGaps(diff: IDocumentDiff, originalCount: number, modi if (!open[start]) { start++; continue; } let end = start + 1; while (end < rows.length && open[end]) end++; - if (seeds.slice(start, end).some(Boolean)) context.fill(true, start, end); + // diffr never collapses inside added code, so no band bounds a run of + // only added lines (as in an added file). Keep only its seeds. + if (rows.slice(start, end).every(row => row[0] === null)) for (let index = start; index < end; index++) context[index] ||= seeds[index]; + else if (seeds.slice(start, end).some(Boolean)) context.fill(true, start, end); start = end; } }