Skip to content

Commit 9ec50c6

Browse files
authored
Merge pull request microsoft#152749 from microsoft/3wm
2 parents ab9679d + 3268ed5 commit 9ec50c6

File tree

15 files changed

+635
-364
lines changed

15 files changed

+635
-364
lines changed

src/vs/workbench/contrib/audioCues/browser/observable.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ export class LazyDerived<T> extends ConvenientObservable<T, void> {
369369

370370
constructor(computeFn: (reader: IReader) => T, name: string) {
371371
super();
372-
this.observer = new LazyDerivedObserver(computeFn, name);
372+
this.observer = new LazyDerivedObserver(computeFn, name, this);
373373
}
374374

375375
public subscribe(observer: IObserver): void {
@@ -412,7 +412,8 @@ class LazyDerivedObserver<T>
412412

413413
constructor(
414414
private readonly computeFn: (reader: IReader) => T,
415-
public readonly name: string
415+
public readonly name: string,
416+
private readonly actualObservable: LazyDerived<T>,
416417
) {
417418
super();
418419
}
@@ -514,7 +515,7 @@ class LazyDerivedObserver<T>
514515
this.hasValue = true;
515516
if (this.hadValue && oldValue !== this.value) {
516517
for (const r of this.observers) {
517-
r.handleChange(this, undefined);
518+
r.handleChange(this.actualObservable, undefined);
518519
}
519520
}
520521
}
@@ -671,3 +672,19 @@ export function derivedObservableWithCache<T>(name: string, computeFn: (reader:
671672
});
672673
return observable;
673674
}
675+
676+
export function derivedObservableWithWritableCache<T>(name: string, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable<T> & { clearCache(transaction: ITransaction): void } {
677+
let lastValue: T | undefined = undefined;
678+
const counter = new ObservableValue(0, 'counter');
679+
const observable = derivedObservable(name, reader => {
680+
counter.read(reader);
681+
lastValue = computeFn(reader, lastValue);
682+
return lastValue;
683+
});
684+
return Object.assign(observable, {
685+
clearCache: (transaction: ITransaction) => {
686+
lastValue = undefined;
687+
counter.set(counter.get() + 1, transaction);
688+
},
689+
});
690+
}

src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@ export class MergeEditorOpenContents extends Action2 {
127127
async function setLanguageId(uri: URI, languageId: string): Promise<void> {
128128
const ref = await textModelService.createModelReference(uri);
129129
ref.object.textEditorModel.setMode(languageId);
130-
ref.dispose();
131130
}
132131

133132
await Promise.all([

src/vs/workbench/contrib/mergeEditor/browser/model/diffComputer.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { BugIndicatingError } from 'vs/base/common/errors';
6+
import { isDefined } from 'vs/base/common/types';
77
import { Range } from 'vs/editor/common/core/range';
88
import { ICharChange, IDiffComputationResult, ILineChange } from 'vs/editor/common/diff/diffComputer';
99
import { ITextModel } from 'vs/editor/common/model';
1010
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
11-
import { DetailedLineRangeMapping, RangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping';
1211
import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';
12+
import { DetailedLineRangeMapping, RangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping';
1313

1414
export interface IDiffComputer {
1515
computeDiff(textModel1: ITextModel, textModel2: ITextModel): Promise<IDiffComputerResult>;
@@ -52,7 +52,7 @@ function fromLineChange(lineChange: ILineChange, originalTextModel: ITextModel,
5252
modifiedRange = new LineRange(lineChange.modifiedStartLineNumber, lineChange.modifiedEndLineNumber - lineChange.modifiedStartLineNumber + 1);
5353
}
5454

55-
let innerDiffs = lineChange.charChanges?.map(c => rangeMappingFromCharChange(c, originalTextModel, modifiedTextModel));
55+
let innerDiffs = lineChange.charChanges?.map(c => rangeMappingFromCharChange(c, originalTextModel, modifiedTextModel)).filter(isDefined);
5656
if (!innerDiffs) {
5757
innerDiffs = [rangeMappingFromLineRanges(originalRange, modifiedRange)];
5858
}
@@ -83,19 +83,19 @@ function rangeMappingFromLineRanges(originalRange: LineRange, modifiedRange: Lin
8383
);
8484
}
8585

86-
function rangeMappingFromCharChange(charChange: ICharChange, inputTextModel: ITextModel, modifiedTextModel: ITextModel): RangeMapping {
86+
function rangeMappingFromCharChange(charChange: ICharChange, inputTextModel: ITextModel, modifiedTextModel: ITextModel): RangeMapping | undefined {
8787
return normalizeRangeMapping(new RangeMapping(
8888
new Range(charChange.originalStartLineNumber, charChange.originalStartColumn, charChange.originalEndLineNumber, charChange.originalEndColumn),
8989
new Range(charChange.modifiedStartLineNumber, charChange.modifiedStartColumn, charChange.modifiedEndLineNumber, charChange.modifiedEndColumn)
9090
), inputTextModel, modifiedTextModel);
9191
}
9292

93-
function normalizeRangeMapping(rangeMapping: RangeMapping, inputTextModel: ITextModel, outputTextModel: ITextModel): RangeMapping {
93+
function normalizeRangeMapping(rangeMapping: RangeMapping, inputTextModel: ITextModel, outputTextModel: ITextModel): RangeMapping | undefined {
9494
const inputRangeEmpty = rangeMapping.inputRange.isEmpty();
9595
const outputRangeEmpty = rangeMapping.outputRange.isEmpty();
9696

9797
if (inputRangeEmpty && outputRangeEmpty) {
98-
throw new BugIndicatingError(); // This case makes no sense, but it is an edge case we need to rule out
98+
return undefined;
9999
}
100100

101101
const originalStartsAtEndOfLine = isAtEndOfLine(rangeMapping.inputRange.startLineNumber, rangeMapping.inputRange.startColumn, inputTextModel);

src/vs/workbench/contrib/mergeEditor/browser/model/lineRange.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import { Comparator, compareBy, numberComparator } from 'vs/base/common/arrays';
77
import { BugIndicatingError } from 'vs/base/common/errors';
8+
import { Constants } from 'vs/base/common/uint';
89
import { Range } from 'vs/editor/common/core/range';
910
import { ITextModel } from 'vs/editor/common/model';
1011

@@ -103,4 +104,11 @@ export class LineRange {
103104
public toRange(): Range {
104105
return new Range(this.startLineNumber, 1, this.endLineNumberExclusive, 1);
105106
}
107+
108+
public toInclusiveRange(): Range | undefined {
109+
if (this.isEmpty) {
110+
return undefined;
111+
}
112+
return new Range(this.startLineNumber, 1, this.endLineNumberExclusive - 1, Constants.MAX_SAFE_SMALL_INTEGER);
113+
}
106114
}

src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts

Lines changed: 68 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { Range } from 'vs/editor/common/core/range';
1212
import { ITextModel } from 'vs/editor/common/model';
1313
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
1414
import { EditorModel } from 'vs/workbench/common/editor/editorModel';
15-
import { autorunHandleChanges, derivedObservable, derivedObservableWithCache, IObservable, ITransaction, keepAlive, ObservableValue, transaction, waitForState } from 'vs/workbench/contrib/audioCues/browser/observable';
15+
import { autorunHandleChanges, derivedObservable, IObservable, IReader, ITransaction, keepAlive, ObservableValue, transaction, waitForState } from 'vs/workbench/contrib/audioCues/browser/observable';
1616
import { EditorWorkerServiceDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer';
1717
import { DetailedLineRangeMapping, DocumentMapping, LineRangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping';
1818
import { LineRangeEdit, RangeEdit } from 'vs/workbench/contrib/mergeEditor/browser/model/editing';
@@ -53,11 +53,7 @@ export class MergeEditorModel extends EditorModel {
5353

5454
public readonly onInitialized = waitForState(this.state, state => state === MergeEditorModelState.upToDate);
5555

56-
public readonly modifiedBaseRanges = derivedObservableWithCache<ModifiedBaseRange[]>('modifiedBaseRanges', (reader, lastValue) => {
57-
if (this.state.read(reader) !== MergeEditorModelState.upToDate) {
58-
return lastValue || [];
59-
}
60-
56+
public readonly modifiedBaseRanges = derivedObservable<ModifiedBaseRange[]>('modifiedBaseRanges', (reader) => {
6157
const input1Diffs = this.input1TextModelDiffs.diffs.read(reader);
6258
const input2Diffs = this.input2TextModelDiffs.diffs.read(reader);
6359

@@ -76,6 +72,23 @@ export class MergeEditorModel extends EditorModel {
7672
return map;
7773
});
7874

75+
private readonly modifiedBaseRangeHandlingStateStores =
76+
derivedObservable('modifiedBaseRangeHandlingStateStores', reader => {
77+
const map = new Map(
78+
this.modifiedBaseRanges.read(reader).map(s => ([s, new ObservableValue(false, 'State')]))
79+
);
80+
return map;
81+
});
82+
83+
public readonly unhandledConflictsCount = derivedObservable('unhandledConflictsCount', reader => {
84+
const map = this.modifiedBaseRangeHandlingStateStores.read(reader);
85+
let handledCount = 0;
86+
for (const [_key, value] of map) {
87+
handledCount += value.read(reader) ? 1 : 0;
88+
}
89+
return map.size - handledCount;
90+
});
91+
7992
public readonly input1ResultMapping = derivedObservable('input1ResultMapping', reader => {
8093
const resultDiffs = this.resultDiffs.read(reader);
8194
const modifiedBaseRanges = DocumentMapping.betweenOutputs(this.input1LinesDiffs.read(reader), resultDiffs, this.input1.getLineCount());
@@ -124,26 +137,43 @@ export class MergeEditorModel extends EditorModel {
124137
super();
125138

126139
this._register(keepAlive(this.modifiedBaseRangeStateStores));
140+
this._register(keepAlive(this.modifiedBaseRangeHandlingStateStores));
127141
this._register(keepAlive(this.input1ResultMapping));
128142
this._register(keepAlive(this.input2ResultMapping));
129143

144+
let shouldResetHandlingState = true;
130145
this._register(
131146
autorunHandleChanges(
132147
'Recompute State',
133148
{
134-
handleChange: (ctx) =>
135-
ctx.didChange(this.resultTextModelDiffs.diffs)
149+
handleChange: (ctx) => {
150+
if (ctx.didChange(this.modifiedBaseRangeHandlingStateStores)) {
151+
shouldResetHandlingState = true;
152+
}
153+
return ctx.didChange(this.resultTextModelDiffs.diffs)
136154
// Ignore non-text changes as we update the state directly
137155
? ctx.change === TextModelDiffChangeReason.textChange
138-
: true,
156+
: true;
157+
},
139158
},
140159
(reader) => {
160+
const modifiedBaseRangeHandlingStateStores = this.modifiedBaseRangeHandlingStateStores.read(reader);
141161
if (!this.isUpToDate.read(reader)) {
142162
return;
143163
}
144164
const resultDiffs = this.resultTextModelDiffs.diffs.read(reader);
145165
const stores = this.modifiedBaseRangeStateStores.read(reader);
146-
this.recomputeState(resultDiffs, stores);
166+
transaction(tx => {
167+
this.recomputeState(resultDiffs, stores, tx);
168+
if (shouldResetHandlingState) {
169+
shouldResetHandlingState = false;
170+
for (const [range, store] of stores) {
171+
const state = store.get();
172+
modifiedBaseRangeHandlingStateStores.get(range)
173+
?.set(!(state.isEmpty || state.conflicting), tx);
174+
}
175+
}
176+
});
147177
}
148178
)
149179
);
@@ -153,24 +183,27 @@ export class MergeEditorModel extends EditorModel {
153183
});
154184
}
155185

156-
private recomputeState(resultDiffs: DetailedLineRangeMapping[], stores: Map<ModifiedBaseRange, ObservableValue<ModifiedBaseRangeState>>): void {
157-
transaction(tx => {
158-
const baseRangeWithStoreAndTouchingDiffs = leftJoin(
159-
stores,
160-
resultDiffs,
161-
(baseRange, diff) =>
162-
baseRange[0].baseRange.touches(diff.inputRange)
163-
? CompareResult.neitherLessOrGreaterThan
164-
: LineRange.compareByStart(
165-
baseRange[0].baseRange,
166-
diff.inputRange
167-
)
168-
);
186+
public getRangeInResult(baseRange: LineRange, reader?: IReader): LineRange {
187+
return this.resultTextModelDiffs.getResultRange(baseRange, reader);
188+
}
169189

170-
for (const row of baseRangeWithStoreAndTouchingDiffs) {
171-
row.left[1].set(this.computeState(row.left[0], row.rights), tx);
172-
}
173-
});
190+
private recomputeState(resultDiffs: DetailedLineRangeMapping[], stores: Map<ModifiedBaseRange, ObservableValue<ModifiedBaseRangeState>>, tx: ITransaction): void {
191+
const baseRangeWithStoreAndTouchingDiffs = leftJoin(
192+
stores,
193+
resultDiffs,
194+
(baseRange, diff) =>
195+
baseRange[0].baseRange.touches(diff.inputRange)
196+
? CompareResult.neitherLessOrGreaterThan
197+
: LineRange.compareByStart(
198+
baseRange[0].baseRange,
199+
diff.inputRange
200+
)
201+
);
202+
203+
for (const row of baseRangeWithStoreAndTouchingDiffs) {
204+
row.left[1].set(this.computeState(row.left[0], row.rights), tx);
205+
206+
}
174207
}
175208

176209
public resetUnknown(): void {
@@ -236,6 +269,11 @@ export class MergeEditorModel extends EditorModel {
236269
if (edit) {
237270
this.resultTextModelDiffs.applyEditRelativeToOriginal(edit, transaction);
238271
}
272+
273+
this.modifiedBaseRangeHandlingStateStores
274+
.get()
275+
.get(baseRange)!
276+
.set(true, transaction);
239277
}
240278

241279
private computeState(baseRange: ModifiedBaseRange, conflictingDiffs: DetailedLineRangeMapping[]): ModifiedBaseRangeState {
@@ -279,6 +317,9 @@ export class MergeEditorModel extends EditorModel {
279317
return ModifiedBaseRangeState.conflicting;
280318
}
281319

320+
public isHandled(baseRange: ModifiedBaseRange): IObservable<boolean> {
321+
return this.modifiedBaseRangeHandlingStateStores.get().get(baseRange)!;
322+
}
282323
}
283324

284325
function getEditForBase(baseRange: ModifiedBaseRange, state: ModifiedBaseRangeState): { edit: LineRangeEdit | undefined; effectiveState: ModifiedBaseRangeState } {

src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRa
1616
* Immutable.
1717
*/
1818
export class ModifiedBaseRange {
19-
/**
20-
* diffs1 and diffs2 together with the conflict relation form a bipartite graph.
21-
* This method computes strongly connected components of that graph while maintaining the side of each diff.
22-
*/
2319
public static fromDiffs(
2420
diffs1: readonly DetailedLineRangeMapping[],
2521
diffs2: readonly DetailedLineRangeMapping[],
@@ -65,6 +61,10 @@ export class ModifiedBaseRange {
6561
return inputNumber === 1 ? this.input1Range : this.input2Range;
6662
}
6763

64+
public getInputCombinedDiff(inputNumber: 1 | 2): DetailedLineRangeMapping | undefined {
65+
return inputNumber === 1 ? this.input1CombinedDiff : this.input2CombinedDiff;
66+
}
67+
6868
public getInputDiffs(inputNumber: 1 | 2): readonly DetailedLineRangeMapping[] {
6969
return inputNumber === 1 ? this.input1Diffs : this.input2Diffs;
7070
}

src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { compareBy, numberComparator } from 'vs/base/common/arrays';
77
import { BugIndicatingError } from 'vs/base/common/errors';
88
import { Disposable } from 'vs/base/common/lifecycle';
99
import { ITextModel } from 'vs/editor/common/model';
10-
import { IObservable, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable';
10+
import { IObservable, IReader, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable';
1111
import { DetailedLineRangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping';
1212
import { LineRangeEdit } from 'vs/workbench/contrib/mergeEditor/browser/model/editing';
1313
import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';
@@ -160,9 +160,10 @@ export class TextModelDiffs extends Disposable {
160160
return this.diffs.get().filter(d => d.inputRange.touches(baseRange));
161161
}
162162

163-
private getResultLine(lineNumber: number): number | DetailedLineRangeMapping {
163+
private getResultLine(lineNumber: number, reader?: IReader): number | DetailedLineRangeMapping {
164164
let offset = 0;
165-
for (const diff of this.diffs.get()) {
165+
const diffs = reader ? this.diffs.read(reader) : this.diffs.get();
166+
for (const diff of diffs) {
166167
if (diff.inputRange.contains(lineNumber) || diff.inputRange.endLineNumberExclusive === lineNumber) {
167168
return diff;
168169
} else if (diff.inputRange.endLineNumberExclusive < lineNumber) {
@@ -174,12 +175,12 @@ export class TextModelDiffs extends Disposable {
174175
return lineNumber + offset;
175176
}
176177

177-
public getResultRange(baseRange: LineRange): LineRange {
178-
let start = this.getResultLine(baseRange.startLineNumber);
178+
public getResultRange(baseRange: LineRange, reader?: IReader): LineRange {
179+
let start = this.getResultLine(baseRange.startLineNumber, reader);
179180
if (typeof start !== 'number') {
180181
start = start.outputRange.startLineNumber;
181182
}
182-
let endExclusive = this.getResultLine(baseRange.endLineNumberExclusive);
183+
let endExclusive = this.getResultLine(baseRange.endLineNumberExclusive, reader);
183184
if (typeof endExclusive !== 'number') {
184185
endExclusive = endExclusive.outputRange.endLineNumberExclusive;
185186
}

0 commit comments

Comments
 (0)