From dc5b3da6e726c5bcb92db2c4b236b9e93a59d06b Mon Sep 17 00:00:00 2001 From: Sidharth Menon Date: Tue, 22 Sep 2026 23:20:47 -0400 Subject: [PATCH] Preserve diffr context when applying review lenses --- .../common/diff/documentDiffProvider.ts | 5 ++++ .../vs/whiteboard/common/whiteboardLens.ts | 23 +++++++++++++++++++ .../common/whiteboardStructuralDiff.ts | 18 +++++++++++++++ .../services/whiteboardStructuralDiff.ts | 2 ++ 4 files changed, 48 insertions(+) diff --git a/apps/whiteboard-desktop/code-oss/src/vs/editor/common/diff/documentDiffProvider.ts b/apps/whiteboard-desktop/code-oss/src/vs/editor/common/diff/documentDiffProvider.ts index 6f59444b6..6bf2d744a 100644 --- a/apps/whiteboard-desktop/code-oss/src/vs/editor/common/diff/documentDiffProvider.ts +++ b/apps/whiteboard-desktop/code-oss/src/vs/editor/common/diff/documentDiffProvider.ts @@ -99,6 +99,11 @@ export interface IDocumentDiff { }; /** Optional authoritative zero-based source row correspondence. Null denotes padding. */ readonly contextGaps?: readonly IDocumentContextGap[]; + /** Syntax scopes supplied by the context plugin, as zero-based half-open line ranges. */ + readonly contextScopes?: { + readonly original: readonly (readonly [number, number])[]; + readonly modified: readonly (readonly [number, number])[]; + }; readonly sourceLineAlignment?: readonly (readonly [number | null, number | null])[]; /** * If true, both text models are identical (byte-wise). diff --git a/apps/whiteboard-desktop/code-oss/src/vs/whiteboard/common/whiteboardLens.ts b/apps/whiteboard-desktop/code-oss/src/vs/whiteboard/common/whiteboardLens.ts index 359caf00c..f1439901f 100644 --- a/apps/whiteboard-desktop/code-oss/src/vs/whiteboard/common/whiteboardLens.ts +++ b/apps/whiteboard-desktop/code-oss/src/vs/whiteboard/common/whiteboardLens.ts @@ -14,6 +14,29 @@ export function lensContextGaps(diff: IDocumentDiff, originalCount: number, modi })); // Context follows alignment rows, keeping both sides synchronized around insertions. const context = visible.map((_, index) => visible.slice(Math.max(0, index - 3), index + 4).some(Boolean)); + if (diff.contextScopes) { + // A lens must not clip context that diffr kept around its selected code. + // Seed enclosing scope boundaries, then retain each visible context run + // touching a seed. Never expand through a collapsed provider band. + const scopes = [diff.contextScopes.original, diff.contextScopes.modified]; + const enclosing = scopes.map((sideScopes, side) => sideScopes.filter(([start, end]) => + rows.some((row, index) => visible[index] && row[side] !== null && row[side]! >= start && row[side]! < end))); + const open = rows.map(row => row.every((line, side) => line === null || !(diff.contextGaps ?? []).some(gap => { + if (gap.collapsed === false) return false; + const start = side === 0 ? gap.originalStart : gap.modifiedStart; + const count = side === 0 ? gap.originalCount : gap.modifiedCount; + return line + 1 >= start && line + 1 < start + count; + }))); + const seeds = rows.map((row, index) => visible[index] || row.some((line, side) => + line !== null && enclosing[side].some(([start, end]) => line === start || line === end - 1))); + for (let start = 0; start < rows.length;) { + 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); + start = end; + } + } const gaps: IDocumentContextGap[] = []; let left = 1, right = 1; for (let index = 0; index < rows.length;) { diff --git a/apps/whiteboard-desktop/code-oss/src/vs/whiteboard/common/whiteboardStructuralDiff.ts b/apps/whiteboard-desktop/code-oss/src/vs/whiteboard/common/whiteboardStructuralDiff.ts index 6f261d1fc..6df181148 100644 --- a/apps/whiteboard-desktop/code-oss/src/vs/whiteboard/common/whiteboardStructuralDiff.ts +++ b/apps/whiteboard-desktop/code-oss/src/vs/whiteboard/common/whiteboardStructuralDiff.ts @@ -33,6 +33,24 @@ export function regionLines(region: StructuralRegion): { start: number; end: num return { start: region.start.line, end: region.end.column === 0 ? region.end.line : region.end.line + 1 }; } +/** Preserve the context plugin's scope boundaries for lens projection. */ +export function structuralContextScopes(diff: StructuralTextDiff) { + const scopes = (source: StructuralSource | undefined) => { + const result: [number, number][] = []; + const visit = (region: StructuralRegion) => { + if (region.kind !== "fold") return; + if (region.tags?.includes("context:scope")) { + const { start, end } = regionLines(region); + result.push([start, end]); + } + region.children.forEach(visit); + }; + source?.regions?.forEach(visit); + return result; + }; + return { original: scopes(diff.lhs), modified: scopes(diff.rhs) }; +} + export function structuralLeaves(regions: readonly StructuralRegion[] | undefined): StructuralLeaf[] { const leaves: StructuralLeaf[] = []; const walk = (region: StructuralRegion) => { diff --git a/apps/whiteboard-desktop/code-oss/src/vs/whiteboard/services/whiteboardStructuralDiff.ts b/apps/whiteboard-desktop/code-oss/src/vs/whiteboard/services/whiteboardStructuralDiff.ts index e199ae10a..90b409b9d 100644 --- a/apps/whiteboard-desktop/code-oss/src/vs/whiteboard/services/whiteboardStructuralDiff.ts +++ b/apps/whiteboard-desktop/code-oss/src/vs/whiteboard/services/whiteboardStructuralDiff.ts @@ -22,6 +22,7 @@ import { autorun, derived, type IObservable } from "../../base/common/observable import type { UnchangedRegion } from "../../editor/browser/widget/diffEditor/diffEditorViewModel.js"; import { structuralContextGaps, + structuralContextScopes, structuralRows, structuralHighlights, } from "../common/whiteboardStructuralDiff.js"; @@ -122,6 +123,7 @@ export class StructuralDiffProvider implements IDocumentDiffProvider { identical: left === right, quitEarly: false, sourceLineAlignment: rows, + contextScopes: structuralContextScopes(diff), // Every collapsed region is a hidden-region band, labelled by the wire. contextGaps: structuralContextGaps( diff,