Skip to content

Commit d3810b7

Browse files
committed
Don't show "Accept both" when not both can be accepted.
Don't show "Swap" when it does not have an effect.
1 parent 82eb075 commit d3810b7

File tree

3 files changed

+156
-120
lines changed

3 files changed

+156
-120
lines changed

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

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

6-
import { compareBy, CompareResult, equals, numberComparator, tieBreakComparators } from 'vs/base/common/arrays';
6+
import { CompareResult, equals } from 'vs/base/common/arrays';
77
import { BugIndicatingError } from 'vs/base/common/errors';
8-
import { splitLines } from 'vs/base/common/strings';
9-
import { Constants } from 'vs/base/common/uint';
10-
import { Position } from 'vs/editor/common/core/position';
11-
import { Range } from 'vs/editor/common/core/range';
128
import { ILanguageService } from 'vs/editor/common/languages/language';
139
import { ITextModel } from 'vs/editor/common/model';
1410
import { IModelService } from 'vs/editor/common/services/model';
1511
import { EditorModel } from 'vs/workbench/common/editor/editorModel';
1612
import { autorunHandleChanges, derivedObservable, IObservable, IReader, ITransaction, keepAlive, ObservableValue, transaction, waitForState } from 'vs/workbench/contrib/audioCues/browser/observable';
1713
import { IDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer';
18-
import { LineRangeEdit, RangeEdit } from 'vs/workbench/contrib/mergeEditor/browser/model/editing';
1914
import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';
2015
import { DetailedLineRangeMapping, DocumentMapping, LineRangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping';
2116
import { TextModelDiffChangeReason, TextModelDiffs, TextModelDiffState } from 'vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs';
22-
import { concatArrays, elementAtOrUndefined, leftJoin } from 'vs/workbench/contrib/mergeEditor/browser/utils';
17+
import { leftJoin } from 'vs/workbench/contrib/mergeEditor/browser/utils';
2318
import { ModifiedBaseRange, ModifiedBaseRangeState } from './modifiedBaseRange';
2419

2520
export const enum MergeEditorModelState {
@@ -269,7 +264,7 @@ export class MergeEditorModel extends EditorModel {
269264
this.resultTextModelDiffs.removeDiffs(conflictingDiffs, transaction);
270265
}
271266

272-
const { edit, effectiveState } = getEditForBase(baseRange, state);
267+
const { edit, effectiveState } = baseRange.getEditForBase(state);
273268

274269
existingState.set(effectiveState, transaction);
275270

@@ -312,7 +307,7 @@ export class MergeEditorModel extends EditorModel {
312307
];
313308

314309
for (const s of states) {
315-
const { edit } = getEditForBase(baseRange, s);
310+
const { edit } = baseRange.getEditForBase(s);
316311
if (edit) {
317312
const resultRange = this.resultTextModelDiffs.getResultRange(baseRange.baseRange);
318313
const existingLines = resultRange.getLines(this.result);
@@ -342,108 +337,3 @@ export class MergeEditorModel extends EditorModel {
342337
this.modelService.setMode(this.result, language);
343338
}
344339
}
345-
346-
function getEditForBase(baseRange: ModifiedBaseRange, state: ModifiedBaseRangeState): { edit: LineRangeEdit | undefined; effectiveState: ModifiedBaseRangeState } {
347-
const diffs = concatArrays(
348-
state.input1 && baseRange.input1CombinedDiff ? [{ diff: baseRange.input1CombinedDiff, inputNumber: 1 as const }] : [],
349-
state.input2 && baseRange.input2CombinedDiff ? [{ diff: baseRange.input2CombinedDiff, inputNumber: 2 as const }] : [],
350-
);
351-
352-
if (state.input2First) {
353-
diffs.reverse();
354-
}
355-
356-
const firstDiff = elementAtOrUndefined(diffs, 0);
357-
const secondDiff = elementAtOrUndefined(diffs, 1);
358-
359-
if (!firstDiff) {
360-
return { edit: undefined, effectiveState: ModifiedBaseRangeState.default };
361-
}
362-
if (!secondDiff) {
363-
return { edit: firstDiff.diff.getLineEdit(), effectiveState: ModifiedBaseRangeState.default.withInputValue(firstDiff.inputNumber, true) };
364-
}
365-
366-
const result = combineInputs(baseRange, state.input2First ? 2 : 1);
367-
if (result) {
368-
return { edit: result, effectiveState: state };
369-
}
370-
371-
return {
372-
edit: secondDiff.diff.getLineEdit(),
373-
effectiveState: ModifiedBaseRangeState.default.withInputValue(
374-
secondDiff.inputNumber,
375-
true
376-
),
377-
};
378-
}
379-
380-
function combineInputs(baseRange: ModifiedBaseRange, firstInput: 1 | 2): LineRangeEdit | undefined {
381-
const combinedDiffs = concatArrays(
382-
baseRange.input1Diffs.flatMap((diffs) =>
383-
diffs.rangeMappings.map((diff) => ({ diff, input: 1 as const }))
384-
),
385-
baseRange.input2Diffs.flatMap((diffs) =>
386-
diffs.rangeMappings.map((diff) => ({ diff, input: 2 as const }))
387-
)
388-
).sort(
389-
tieBreakComparators(
390-
compareBy((d) => d.diff.inputRange, Range.compareRangesUsingStarts),
391-
compareBy((d) => (d.input === firstInput ? 1 : 2), numberComparator)
392-
)
393-
);
394-
395-
const sortedEdits = combinedDiffs.map(d => {
396-
const sourceTextModel = d.input === 1 ? baseRange.input1TextModel : baseRange.input2TextModel;
397-
return new RangeEdit(d.diff.inputRange, sourceTextModel.getValueInRange(d.diff.outputRange));
398-
});
399-
400-
return editsToLineRangeEdit(baseRange.baseRange, sortedEdits, baseRange.baseTextModel);
401-
}
402-
403-
function editsToLineRangeEdit(range: LineRange, sortedEdits: RangeEdit[], textModel: ITextModel): LineRangeEdit | undefined {
404-
let text = '';
405-
const startsLineBefore = range.startLineNumber > 1;
406-
let currentPosition = startsLineBefore
407-
? new Position(
408-
range.startLineNumber - 1,
409-
Constants.MAX_SAFE_SMALL_INTEGER
410-
)
411-
: new Position(range.startLineNumber, 1);
412-
413-
for (const edit of sortedEdits) {
414-
const diffStart = edit.range.getStartPosition();
415-
if (!currentPosition.isBeforeOrEqual(diffStart)) {
416-
return undefined;
417-
}
418-
let originalText = textModel.getValueInRange(Range.fromPositions(currentPosition, diffStart));
419-
if (diffStart.lineNumber > textModel.getLineCount()) {
420-
// assert diffStart.lineNumber === textModel.getLineCount() + 1
421-
// getValueInRange doesn't include this virtual line break, as the document ends the line before.
422-
// endsLineAfter will be false.
423-
originalText += '\n';
424-
}
425-
text += originalText;
426-
text += edit.newText;
427-
currentPosition = edit.range.getEndPosition();
428-
}
429-
430-
const endsLineAfter = range.endLineNumberExclusive <= textModel.getLineCount();
431-
const end = endsLineAfter ? new Position(
432-
range.endLineNumberExclusive,
433-
1
434-
) : new Position(range.endLineNumberExclusive - 1, Constants.MAX_SAFE_SMALL_INTEGER);
435-
436-
const originalText = textModel.getValueInRange(
437-
Range.fromPositions(currentPosition, end)
438-
);
439-
text += originalText;
440-
441-
const lines = splitLines(text);
442-
if (startsLineBefore) {
443-
lines.shift();
444-
}
445-
if (endsLineAfter) {
446-
lines.pop();
447-
}
448-
return new LineRangeEdit(range, lines);
449-
}

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

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ import { BugIndicatingError } from 'vs/base/common/errors';
77
import { ITextModel } from 'vs/editor/common/model';
88
import { DetailedLineRangeMapping, MappingAlignment } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping';
99
import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';
10+
import { tieBreakComparators, compareBy, numberComparator } from 'vs/base/common/arrays';
11+
import { splitLines } from 'vs/base/common/strings';
12+
import { Constants } from 'vs/base/common/uint';
13+
import { LineRangeEdit, RangeEdit } from 'vs/workbench/contrib/mergeEditor/browser/model/editing';
14+
import { concatArrays, elementAtOrUndefined } from 'vs/workbench/contrib/mergeEditor/browser/utils';
15+
import { Range } from 'vs/editor/common/core/range';
16+
import { Position } from 'vs/editor/common/core/position';
1017

1118
/**
1219
* Describes modifications in input 1 and input 2 for a specific range in base.
@@ -72,8 +79,142 @@ export class ModifiedBaseRange {
7279
public get isConflicting(): boolean {
7380
return this.input1Diffs.length > 0 && this.input2Diffs.length > 0;
7481
}
82+
83+
public get canBeCombined(): boolean {
84+
return this.combineInputs(1) !== undefined;
85+
}
86+
87+
public get isOrderRelevant(): boolean {
88+
const input1 = this.combineInputs(1);
89+
const input2 = this.combineInputs(2);
90+
if (!input1 || !input2) {
91+
return false;
92+
}
93+
return !input1.equals(input2);
94+
}
95+
96+
public getEditForBase(state: ModifiedBaseRangeState): { edit: LineRangeEdit | undefined; effectiveState: ModifiedBaseRangeState } {
97+
const diffs = concatArrays(
98+
state.input1 && this.input1CombinedDiff ? [{ diff: this.input1CombinedDiff, inputNumber: 1 as const }] : [],
99+
state.input2 && this.input2CombinedDiff ? [{ diff: this.input2CombinedDiff, inputNumber: 2 as const }] : [],
100+
);
101+
102+
if (state.input2First) {
103+
diffs.reverse();
104+
}
105+
106+
const firstDiff = elementAtOrUndefined(diffs, 0);
107+
const secondDiff = elementAtOrUndefined(diffs, 1);
108+
109+
if (!firstDiff) {
110+
return { edit: undefined, effectiveState: ModifiedBaseRangeState.default };
111+
}
112+
if (!secondDiff) {
113+
return { edit: firstDiff.diff.getLineEdit(), effectiveState: ModifiedBaseRangeState.default.withInputValue(firstDiff.inputNumber, true) };
114+
}
115+
116+
const result = this.combineInputs(state.input2First ? 2 : 1);
117+
if (result) {
118+
return { edit: result, effectiveState: state };
119+
}
120+
121+
return {
122+
edit: secondDiff.diff.getLineEdit(),
123+
effectiveState: ModifiedBaseRangeState.default.withInputValue(
124+
secondDiff.inputNumber,
125+
true
126+
),
127+
};
128+
}
129+
130+
private input1LineRangeEdit: LineRangeEdit | undefined | null = null;
131+
private input2LineRangeEdit: LineRangeEdit | undefined | null = null;
132+
133+
private combineInputs(firstInput: 1 | 2): LineRangeEdit | undefined {
134+
if (firstInput === 1 && this.input1LineRangeEdit !== null) {
135+
return this.input1LineRangeEdit;
136+
} else if (firstInput === 2 && this.input2LineRangeEdit !== null) {
137+
return this.input2LineRangeEdit;
138+
}
139+
140+
const combinedDiffs = concatArrays(
141+
this.input1Diffs.flatMap((diffs) =>
142+
diffs.rangeMappings.map((diff) => ({ diff, input: 1 as const }))
143+
),
144+
this.input2Diffs.flatMap((diffs) =>
145+
diffs.rangeMappings.map((diff) => ({ diff, input: 2 as const }))
146+
)
147+
).sort(
148+
tieBreakComparators(
149+
compareBy((d) => d.diff.inputRange, Range.compareRangesUsingStarts),
150+
compareBy((d) => (d.input === firstInput ? 1 : 2), numberComparator)
151+
)
152+
);
153+
154+
const sortedEdits = combinedDiffs.map(d => {
155+
const sourceTextModel = d.input === 1 ? this.input1TextModel : this.input2TextModel;
156+
return new RangeEdit(d.diff.inputRange, sourceTextModel.getValueInRange(d.diff.outputRange));
157+
});
158+
159+
const result = editsToLineRangeEdit(this.baseRange, sortedEdits, this.baseTextModel);
160+
if (firstInput === 1) {
161+
this.input1LineRangeEdit = result;
162+
} else {
163+
this.input2LineRangeEdit = result;
164+
}
165+
return result;
166+
}
167+
}
168+
169+
function editsToLineRangeEdit(range: LineRange, sortedEdits: RangeEdit[], textModel: ITextModel): LineRangeEdit | undefined {
170+
let text = '';
171+
const startsLineBefore = range.startLineNumber > 1;
172+
let currentPosition = startsLineBefore
173+
? new Position(
174+
range.startLineNumber - 1,
175+
Constants.MAX_SAFE_SMALL_INTEGER
176+
)
177+
: new Position(range.startLineNumber, 1);
178+
179+
for (const edit of sortedEdits) {
180+
const diffStart = edit.range.getStartPosition();
181+
if (!currentPosition.isBeforeOrEqual(diffStart)) {
182+
return undefined;
183+
}
184+
let originalText = textModel.getValueInRange(Range.fromPositions(currentPosition, diffStart));
185+
if (diffStart.lineNumber > textModel.getLineCount()) {
186+
// assert diffStart.lineNumber === textModel.getLineCount() + 1
187+
// getValueInRange doesn't include this virtual line break, as the document ends the line before.
188+
// endsLineAfter will be false.
189+
originalText += '\n';
190+
}
191+
text += originalText;
192+
text += edit.newText;
193+
currentPosition = edit.range.getEndPosition();
194+
}
195+
196+
const endsLineAfter = range.endLineNumberExclusive <= textModel.getLineCount();
197+
const end = endsLineAfter ? new Position(
198+
range.endLineNumberExclusive,
199+
1
200+
) : new Position(range.endLineNumberExclusive - 1, Constants.MAX_SAFE_SMALL_INTEGER);
201+
202+
const originalText = textModel.getValueInRange(
203+
Range.fromPositions(currentPosition, end)
204+
);
205+
text += originalText;
206+
207+
const lines = splitLines(text);
208+
if (startsLineBefore) {
209+
lines.shift();
210+
}
211+
if (endsLineAfter) {
212+
lines.pop();
213+
}
214+
return new LineRangeEdit(range, lines);
75215
}
76216

217+
77218
export class ModifiedBaseRangeState {
78219
public static readonly default = new ModifiedBaseRangeState(false, false, false, false);
79220
public static readonly conflicting = new ModifiedBaseRangeState(false, false, false, true);

src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,15 @@ export class InputCodeEditorView extends CodeEditorView {
119119
id: idx.toString(),
120120
range: baseRange.getInputRange(this.inputNumber),
121121
enabled: model.isUpToDate,
122-
toggleState: derivedObservable('toggle', (reader) => model
123-
.getState(baseRange)
124-
.read(reader)
125-
.getInput(this.inputNumber)
122+
toggleState: derivedObservable('toggle', (reader) => {
123+
const input = model
124+
.getState(baseRange)
125+
.read(reader)
126+
.getInput(this.inputNumber);
127+
return input === InputState.second && !baseRange.isOrderRelevant
128+
? InputState.first
129+
: input;
130+
}
126131
),
127132
setState: (value, tx) => viewModel.setState(
128133
baseRange,
@@ -177,7 +182,7 @@ export class InputCodeEditorView extends CodeEditorView {
177182
state.withInput1(!both).withInput2(!both),
178183
both
179184
),
180-
{ enabled: true } // TODO
185+
{ enabled: baseRange.canBeCombined }
181186
)
182187
: undefined,
183188
baseRange.isConflicting
@@ -188,7 +193,7 @@ export class InputCodeEditorView extends CodeEditorView {
188193
state.swap(),
189194
false
190195
),
191-
{ enabled: !state.isEmpty }
196+
{ enabled: !state.isEmpty && (!both || baseRange.isOrderRelevant) }
192197
)
193198
: undefined,
194199

0 commit comments

Comments
 (0)