From 40f4aeff5539e97ca9124fa33dd0deb70c75f90a Mon Sep 17 00:00:00 2001 From: Quentin Chateau Date: Sat, 19 Jun 2021 15:24:45 +0200 Subject: [PATCH 1/2] Merge formatting ranges that result in conflicting edits --- src/vs/editor/contrib/format/format.ts | 57 +++++++++++++++++++++----- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/src/vs/editor/contrib/format/format.ts b/src/vs/editor/contrib/format/format.ts index 7056eec2e5ada..7399898a18550 100644 --- a/src/vs/editor/contrib/format/format.ts +++ b/src/vs/editor/contrib/format/format.ts @@ -169,27 +169,62 @@ export async function formatDocumentRangesWithProvider( } } - const allEdits: TextEdit[] = []; + const computeEdits = async (range: Range) => { + const rawEdits = await provider.provideDocumentRangeFormattingEdits( + model, + range, + model.getFormattingOptions(), + cts.token + ); + return (await workerService.computeMoreMinimalEdits(model.uri, rawEdits)) || []; + }; + + const hasIntersectingEdit = (a: TextEdit[], b: TextEdit[]) => { + for (let edit of a) { + for (let otherEdit of b) { + if (Range.intersectRanges(edit.range, otherEdit.range)) { + return true; + } + } + } + return false; + }; + + const rawEditsList: TextEdit[][] = []; try { for (let range of ranges) { - const rawEdits = await provider.provideDocumentRangeFormattingEdits( - model, - range, - model.getFormattingOptions(), - cts.token - ); - const minEdits = await workerService.computeMoreMinimalEdits(model.uri, rawEdits); - if (minEdits) { - allEdits.push(...minEdits); - } + rawEditsList.push(await computeEdits(range)); if (cts.token.isCancellationRequested) { return true; } } + + for (let i = 0; i < ranges.length; ++i) { + for (let j = i + 1; j < ranges.length; ++j) { + if (cts.token.isCancellationRequested) { + return true; + } + if (hasIntersectingEdit(rawEditsList[i], rawEditsList[j])) { + // Merge ranges i and j into a single range, recompute the associated edits + const mergedRange = Range.plusRange(ranges[i], ranges[j]); + const edits = await computeEdits(mergedRange); + ranges.splice(j, 1); + ranges.splice(i, 1); + ranges.push(mergedRange); + rawEditsList.splice(j, 1); + rawEditsList.splice(i, 1); + rawEditsList.push(edits); + // Restart scanning + i = 0; + j = 0; + } + } + } } finally { cts.dispose(); } + const allEdits = rawEditsList.reduce((acc, val) => acc.concat(val), []); if (allEdits.length === 0) { return false; } From 974fce69e322054fa31830ee14d8d6904fcb6d6d Mon Sep 17 00:00:00 2001 From: Quentin Chateau Date: Mon, 21 Jun 2021 23:48:41 +0200 Subject: [PATCH 2/2] Optimize format range merging --- src/vs/editor/contrib/format/format.ts | 28 +++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/contrib/format/format.ts b/src/vs/editor/contrib/format/format.ts index 7399898a18550..72f7aa9bdb6e7 100644 --- a/src/vs/editor/contrib/format/format.ts +++ b/src/vs/editor/contrib/format/format.ts @@ -170,16 +170,24 @@ export async function formatDocumentRangesWithProvider( } const computeEdits = async (range: Range) => { - const rawEdits = await provider.provideDocumentRangeFormattingEdits( + return (await provider.provideDocumentRangeFormattingEdits( model, range, model.getFormattingOptions(), cts.token - ); - return (await workerService.computeMoreMinimalEdits(model.uri, rawEdits)) || []; + )) || []; }; const hasIntersectingEdit = (a: TextEdit[], b: TextEdit[]) => { + if (!a.length || !b.length) { + return false; + } + // quick exit if the list of ranges are completely unrelated [O(n)] + const mergedA = a.reduce((acc, val) => { return Range.plusRange(acc, val.range); }, a[0].range); + if (!b.some(x => { return Range.intersectRanges(mergedA, x.range); })) { + return false; + } + // fallback to a complete check [O(n^2)] for (let edit of a) { for (let otherEdit of b) { if (Range.intersectRanges(edit.range, otherEdit.range)) { @@ -190,13 +198,14 @@ export async function formatDocumentRangesWithProvider( return false; }; + const allEdits: TextEdit[] = []; const rawEditsList: TextEdit[][] = []; try { for (let range of ranges) { - rawEditsList.push(await computeEdits(range)); if (cts.token.isCancellationRequested) { return true; } + rawEditsList.push(await computeEdits(range)); } for (let i = 0; i < ranges.length; ++i) { @@ -220,11 +229,20 @@ export async function formatDocumentRangesWithProvider( } } } + + for (let rawEdits of rawEditsList) { + if (cts.token.isCancellationRequested) { + return true; + } + const minimalEdits = await workerService.computeMoreMinimalEdits(model.uri, rawEdits); + if (minimalEdits) { + allEdits.push(...minimalEdits); + } + } } finally { cts.dispose(); } - const allEdits = rawEditsList.reduce((acc, val) => acc.concat(val), []); if (allEdits.length === 0) { return false; }