This commit is contained in:
Henning Dieterichs authored and GitHub committed 2022-10-13 15:36:42 +02:00
1 parent 73e5ad2edf
commit bf85140dfd
4 files changed
+421 -283

No files matched your search

@@ -74,118 +74,32 @@ export class ConflictActionsFactory extends Disposable {
};
}
addResultWidget(viewZoneChangeAccessor: IViewZoneChangeAccessor, lineNumber: number, viewModel: MergeEditorViewModel, modifiedBaseRange: ModifiedBaseRange): IDisposable {
function command(title: string, action: () => Promise<void>, tooltip?: string): IContentWidgetAction {
return {
text: title,
action,
tooltip
};
}
const items = derived('items', reader => {
const state = viewModel.model.getState(modifiedBaseRange).read(reader);
const model = viewModel.model;
const result: IContentWidgetAction[] = [];
if (state.conflicting) {
result.push({
text: localize('manualResolution', "Manual Resolution"),
tooltip: localize('manualResolutionTooltip', "This conflict has been resolved manually."),
});
} else if (state.isEmpty) {
result.push({
text: localize('noChangesAccepted', 'No Changes Accepted'),
tooltip: localize(
'noChangesAcceptedTooltip',
'The current resolution of this conflict equals the common ancestor of both the right and left changes.'
),
});
} else {
const labels = [];
if (state.input1) {
labels.push(model.input1.title);
}
if (state.input2) {
labels.push(model.input2.title);
}
if (state.input2First) {
labels.reverse();
}
result.push({
text: `${labels.join(' + ')}`
});
}
const stateToggles: IContentWidgetAction[] = [];
if (state.input1) {
result.push(command(localize('remove', "Remove {0}", model.input1.title), async () => {
transaction((tx) => {
model.setState(
modifiedBaseRange,
state.withInputValue(1, false),
true,
tx
);
});
}, localize('removeTooltip', "Remove {0} from the result document.", model.input1.title)),
);
}
if (state.input2) {
result.push(command(localize('remove', "Remove {0}", model.input2.title), async () => {
transaction((tx) => {
model.setState(
modifiedBaseRange,
state.withInputValue(2, false),
true,
tx
);
});
}, localize('removeTooltip', "Remove {0} from the result document.", model.input2.title)),
);
}
if (state.input2First) {
stateToggles.reverse();
}
result.push(...stateToggles);
if (state.conflicting) {
result.push(
command(localize('resetToBase', "Reset to base"), async () => {
transaction((tx) => {
model.setState(
modifiedBaseRange,
ModifiedBaseRangeState.default,
true,
tx
);
});
}, localize('resetToBaseTooltip', "Reset this conflict to the common ancestor of both the right and left changes.")),
);
}
return result;
});
public createWidget(viewZoneChangeAccessor: IViewZoneChangeAccessor, lineNumber: number, items: IObservable<IContentWidgetAction[]>, viewZoneIdsToCleanUp: string[]): IDisposable {
const layoutInfo = this._getLayoutInfo();
return new ActionsContentWidget(
this._editor,
viewZoneChangeAccessor,
lineNumber,
layoutInfo.codeLensHeight + 2,
this._styleClassName,
items,
viewZoneIdsToCleanUp,
);
}
}
return new ActionsContentWidget(this._editor, viewZoneChangeAccessor, lineNumber, layoutInfo.codeLensHeight + 2, this._styleClassName, items);
export class ActionsSource {
constructor(
private readonly viewModel: MergeEditorViewModel,
private readonly modifiedBaseRange: ModifiedBaseRange,
) {
}
addContentWidget(viewZoneChangeAccessor: IViewZoneChangeAccessor, lineNumber: number, viewModel: MergeEditorViewModel, modifiedBaseRange: ModifiedBaseRange, inputNumber: 1 | 2): IDisposable {
function command(title: string, action: () => Promise<void>, tooltip?: string): IContentWidgetAction {
return {
text: title,
action,
tooltip,
};
}
private getItemsInput(inputNumber: 1 | 2): IObservable<IContentWidgetAction[]> {
return derived('items', reader => {
const viewModel = this.viewModel;
const modifiedBaseRange = this.modifiedBaseRange;
const items = derived('items', reader => {
if (!viewModel.model.hasBaseRange(modifiedBaseRange)) {
return [];
}
@@ -260,15 +174,120 @@ export class ConflictActionsFactory extends Disposable {
}
return result;
});
const layoutInfo = this._getLayoutInfo();
return new ActionsContentWidget(this._editor, viewZoneChangeAccessor, lineNumber, layoutInfo.codeLensHeight + 2, this._styleClassName, items);
}
public readonly itemsInput1 = this.getItemsInput(1);
public readonly itemsInput2 = this.getItemsInput(2);
public readonly resultItems = derived('items', reader => {
const viewModel = this.viewModel;
const modifiedBaseRange = this.modifiedBaseRange;
const state = viewModel.model.getState(modifiedBaseRange).read(reader);
const model = viewModel.model;
const result: IContentWidgetAction[] = [];
if (state.conflicting) {
result.push({
text: localize('manualResolution', "Manual Resolution"),
tooltip: localize('manualResolutionTooltip', "This conflict has been resolved manually."),
});
} else if (state.isEmpty) {
result.push({
text: localize('noChangesAccepted', 'No Changes Accepted'),
tooltip: localize(
'noChangesAcceptedTooltip',
'The current resolution of this conflict equals the common ancestor of both the right and left changes.'
),
});
} else {
const labels = [];
if (state.input1) {
labels.push(model.input1.title);
}
if (state.input2) {
labels.push(model.input2.title);
}
if (state.input2First) {
labels.reverse();
}
result.push({
text: `${labels.join(' + ')}`
});
}
const stateToggles: IContentWidgetAction[] = [];
if (state.input1) {
result.push(command(localize('remove', "Remove {0}", model.input1.title), async () => {
transaction((tx) => {
model.setState(
modifiedBaseRange,
state.withInputValue(1, false),
true,
tx
);
});
}, localize('removeTooltip', "Remove {0} from the result document.", model.input1.title)),
);
}
if (state.input2) {
result.push(command(localize('remove', "Remove {0}", model.input2.title), async () => {
transaction((tx) => {
model.setState(
modifiedBaseRange,
state.withInputValue(2, false),
true,
tx
);
});
}, localize('removeTooltip', "Remove {0} from the result document.", model.input2.title)),
);
}
if (state.input2First) {
stateToggles.reverse();
}
result.push(...stateToggles);
if (state.conflicting) {
result.push(
command(localize('resetToBase', "Reset to base"), async () => {
transaction((tx) => {
model.setState(
modifiedBaseRange,
ModifiedBaseRangeState.default,
true,
tx
);
});
}, localize('resetToBaseTooltip', "Reset this conflict to the common ancestor of both the right and left changes.")),
);
}
return result;
});
public readonly isEmpty = derived('isEmpty', reader => {
return this.itemsInput1.read(reader).length + this.itemsInput2.read(reader).length + this.resultItems.read(reader).length === 0;
});
public readonly inputIsEmpty = derived('inputIsEmpty', reader => {
return this.itemsInput1.read(reader).length + this.itemsInput2.read(reader).length === 0;
});
}
function command(title: string, action: () => Promise<void>, tooltip?: string): IContentWidgetAction {
return {
text: title,
action,
tooltip,
};
}
interface IContentWidgetAction {
export interface IContentWidgetAction {
text: string;
tooltip?: string;
action?: () => Promise<void>;
@@ -285,8 +304,9 @@ class ActionsContentWidget extends FixedZoneWidget {
className: string,
items: IObservable<IContentWidgetAction[]>,
viewZoneIdsToCleanUp: string[],
) {
super(editor, viewZoneAccessor, afterLineNumber, height);
super(editor, viewZoneAccessor, afterLineNumber, height, viewZoneIdsToCleanUp);
this.widgetDomNode.appendChild(this._domNode);
@@ -24,6 +24,7 @@ export abstract class FixedZoneWidget extends Disposable {
viewZoneAccessor: IViewZoneChangeAccessor,
afterLineNumber: number,
height: number,
viewZoneIdsToCleanUp: string[],
) {
super();
@@ -38,6 +39,7 @@ export abstract class FixedZoneWidget extends Disposable {
this.widgetDomNode.style.top = `${top}px`;
}
});
viewZoneIdsToCleanUp.push(this.viewZoneId);
this.widgetDomNode.style.left = this.editor.getLayoutInfo().contentLeft + 'px';
@@ -46,9 +48,6 @@ export abstract class FixedZoneWidget extends Disposable {
this._register({
dispose: () => {
this.editor.removeOverlayWidget(this.overlayWidget);
this.editor.changeViewZones(accessor => {
accessor.removeZone(this.viewZoneId);
});
},
});
}
@@ -3,10 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { $, Dimension, reset } from 'vs/base/browser/dom';
import { Dimension, reset } from 'vs/base/browser/dom';
import { Grid, GridNodeDescriptor, IView, SerializableGrid } from 'vs/base/browser/ui/grid/grid';
import { Orientation } from 'vs/base/browser/ui/splitview/splitview';
import { CompareResult, lastOrDefault } from 'vs/base/common/arrays';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Color } from 'vs/base/common/color';
import { BugIndicatingError, onUnexpectedError } from 'vs/base/common/errors';
@@ -38,24 +37,20 @@ import { applyTextEditorOptions } from 'vs/workbench/common/editor/editorOptions
import { readTransientState, writeTransientState } from 'vs/workbench/contrib/codeEditor/browser/toggleWordWrap';
import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput';
import { IMergeEditorInputModel } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInputModel';
import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';
import { DetailedLineRangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping';
import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel';
import { ModifiedBaseRange } from 'vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange';
import { deepMerge, join, PersistentStore, thenIfNotDisposed } from 'vs/workbench/contrib/mergeEditor/browser/utils';
import { deepMerge, PersistentStore, thenIfNotDisposed } from 'vs/workbench/contrib/mergeEditor/browser/utils';
import { BaseCodeEditorView } from 'vs/workbench/contrib/mergeEditor/browser/view/editors/baseCodeEditorView';
import { ScrollSynchronizer } from 'vs/workbench/contrib/mergeEditor/browser/view/scrollSynchronizer';
import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/view/viewModel';
import { ViewZoneComputer } from 'vs/workbench/contrib/mergeEditor/browser/view/viewZones';
import { ctxIsMergeEditor, ctxMergeBaseUri, ctxMergeEditorLayout, ctxMergeEditorShowBase, ctxMergeEditorShowBaseAtTop, ctxMergeEditorShowNonConflictingChanges, ctxMergeResultUri, MergeEditorLayoutKind } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor';
import { settingsSashBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorResolverService, MergeEditorInputFactoryFunction, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import './colors';
import { ConflictActionsFactory } from './conflictActions';
import { InputCodeEditorView } from './editors/inputCodeEditorView';
import { ResultCodeEditorView } from './editors/resultCodeEditorView';
import { getAlignments } from './lineAlignment';
export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
@@ -97,9 +92,11 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
return !!this._configurationService.getValue<boolean>('mergeEditor.writableInputs');
}
private readonly conflictActionsFactoryInput1 = new ConflictActionsFactory(this.input1View.editor);
private readonly conflictActionsFactoryInput2 = new ConflictActionsFactory(this.input2View.editor);
private readonly conflictActionsFactoryResult = new ConflictActionsFactory(this.inputResultView.editor);
private readonly viewZoneComputer = new ViewZoneComputer(
this.input1View.editor,
this.input2View.editor,
this.inputResultView.editor,
);
protected readonly codeLensesVisible = observableFromEvent<boolean>(
this.configurationService.onDidChangeConfiguration,
@@ -351,32 +348,38 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
resultViewZoneAccessor: IViewZoneChangeAccessor,
shouldAlignResult: boolean,
): IDisposable {
let input1LinesAdded = 0;
let input2LinesAdded = 0;
let baseLinesAdded = 0;
let resultLinesAdded = 0;
const model = viewModel.model;
const input1ViewZoneIds: string[] = [];
const input2ViewZoneIds: string[] = [];
const baseViewZoneIds: string[] = [];
const resultViewZoneIds: string[] = [];
const resultDiffs = model.baseResultDiffs.read(reader);
const baseRangeWithStoreAndTouchingDiffs = join(
model.modifiedBaseRanges.read(reader),
resultDiffs,
(baseRange, diff) =>
baseRange.baseRange.touches(diff.inputRange)
? CompareResult.neitherLessOrGreaterThan
: LineRange.compareByStart(
baseRange.baseRange,
diff.inputRange
)
);
const viewZones = this.viewZoneComputer.computeViewZones(reader, viewModel, {
codeLensesVisible: this.codeLensesVisible.read(reader),
showNonConflictingChanges: this.showNonConflictingChanges.read(reader),
shouldAlignBase,
shouldAlignResult,
});
const disposableStore = new DisposableStore();
if (baseViewZoneAccessor) {
for (const v of viewZones.baseViewZones) {
v.create(baseViewZoneAccessor, baseViewZoneIds, disposableStore);
}
}
for (const v of viewZones.resultViewZones) {
v.create(resultViewZoneAccessor, resultViewZoneIds, disposableStore);
}
for (const v of viewZones.input1ViewZones) {
v.create(input1ViewZoneAccessor, input1ViewZoneIds, disposableStore);
}
for (const v of viewZones.input2ViewZones) {
v.create(input2ViewZoneAccessor, input2ViewZoneIds, disposableStore);
}
disposableStore.add({
dispose: () => {
input1Editor.changeViewZones(a => {
@@ -402,143 +405,6 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
}
});
const shouldShowCodeLenses = this.codeLensesVisible.read(reader);
const showNonConflictingChanges = this.showNonConflictingChanges.read(reader);
let lastModifiedBaseRange: ModifiedBaseRange | undefined = undefined;
let lastBaseResultDiff: DetailedLineRangeMapping | undefined = undefined;
for (const m of baseRangeWithStoreAndTouchingDiffs) {
if (shouldShowCodeLenses && m.left && (m.left.isConflicting || showNonConflictingChanges || !model.isHandled(m.left).read(reader))) {
disposableStore.add(
this.conflictActionsFactoryInput1.addContentWidget(
input1ViewZoneAccessor, m.left.input1Range.startLineNumber - 1, viewModel, m.left, this.input1View.inputNumber
)
);
disposableStore.add(
this.conflictActionsFactoryInput2.addContentWidget(
input2ViewZoneAccessor, m.left.input2Range.startLineNumber - 1, viewModel, m.left, this.input2View.inputNumber
)
);
const afterLineNumber = m.left.baseRange.startLineNumber + (lastBaseResultDiff?.resultingDeltaFromOriginalToModified ?? 0) - 1;
disposableStore.add(this.conflictActionsFactoryResult.addResultWidget(resultViewZoneAccessor, afterLineNumber, viewModel, m.left));
if (shouldAlignBase && baseViewZoneAccessor) {
baseViewZoneIds.push(baseViewZoneAccessor.addZone({
afterLineNumber: m.left.baseRange.startLineNumber - 1,
heightInPx: 16,
domNode: $('div.conflict-actions-placeholder'),
}));
}
}
interface LineAlignment {
baseLine: number;
input1Line?: number;
input2Line?: number;
resultLine?: number;
}
const lastResultDiff = lastOrDefault(m.rights)!;
if (lastResultDiff) {
lastBaseResultDiff = lastResultDiff;
}
let alignedLines: LineAlignment[];
if (m.left) {
alignedLines = getAlignments(m.left).map(a => ({
input1Line: a[0],
baseLine: a[1],
input2Line: a[2],
resultLine: undefined,
}));
lastModifiedBaseRange = m.left;
// This is a total hack.
alignedLines[alignedLines.length - 1].resultLine =
m.left.baseRange.endLineNumberExclusive
+ (lastBaseResultDiff ? lastBaseResultDiff.resultingDeltaFromOriginalToModified : 0);
} else {
alignedLines = [{
baseLine: lastResultDiff.inputRange.endLineNumberExclusive,
input1Line: lastResultDiff.inputRange.endLineNumberExclusive + (lastModifiedBaseRange ? (lastModifiedBaseRange.input1Range.endLineNumberExclusive - lastModifiedBaseRange.baseRange.endLineNumberExclusive) : 0),
input2Line: lastResultDiff.inputRange.endLineNumberExclusive + (lastModifiedBaseRange ? (lastModifiedBaseRange.input2Range.endLineNumberExclusive - lastModifiedBaseRange.baseRange.endLineNumberExclusive) : 0),
resultLine: lastResultDiff.outputRange.endLineNumberExclusive,
}];
}
for (const { input1Line, baseLine, input2Line, resultLine } of alignedLines) {
if (!shouldAlignBase && (input1Line === undefined || input2Line === undefined)) {
continue;
}
const input1Line_ =
input1Line !== undefined ? input1Line + input1LinesAdded : -1;
const input2Line_ =
input2Line !== undefined ? input2Line + input2LinesAdded : -1;
const baseLine_ = baseLine + baseLinesAdded;
const resultLine_ = resultLine !== undefined ? resultLine + resultLinesAdded : -1;
const max = Math.max(shouldAlignBase ? baseLine_ : 0, input1Line_, input2Line_, shouldAlignResult ? resultLine_ : 0);
if (input1Line !== undefined) {
const diffInput1 = max - input1Line_;
if (diffInput1 > 0) {
input1ViewZoneIds.push(
input1ViewZoneAccessor.addZone({
afterLineNumber: input1Line - 1,
heightInLines: diffInput1,
domNode: $('div.diagonal-fill'),
})
);
input1LinesAdded += diffInput1;
}
}
if (input2Line !== undefined) {
const diffInput2 = max - input2Line_;
if (diffInput2 > 0) {
input2ViewZoneIds.push(
input2ViewZoneAccessor.addZone({
afterLineNumber: input2Line - 1,
heightInLines: diffInput2,
domNode: $('div.diagonal-fill'),
})
);
input2LinesAdded += diffInput2;
}
}
if (shouldAlignBase && baseViewZoneAccessor) {
const diffBase = max - baseLine_;
if (diffBase > 0) {
baseViewZoneIds.push(
baseViewZoneAccessor.addZone({
afterLineNumber: baseLine - 1,
heightInLines: diffBase,
domNode: $('div.diagonal-fill'),
})
);
baseLinesAdded += diffBase;
}
}
if (shouldAlignResult && resultViewZoneAccessor && resultLine !== undefined) {
const diffResult = max - resultLine_;
if (diffResult > 0) {
resultViewZoneIds.push(
resultViewZoneAccessor.addZone({
afterLineNumber: resultLine - 1,
heightInLines: diffResult,
domNode: $('div.diagonal-fill'),
})
);
resultLinesAdded += diffResult;
}
}
}
}
return disposableStore;
}
@@ -0,0 +1,253 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { $ } from 'vs/base/browser/dom';
import { CompareResult, lastOrDefault } from 'vs/base/common/arrays';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IObservable, IReader } from 'vs/base/common/observable';
import { ICodeEditor, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser';
import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';
import { DetailedLineRangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping';
import { ModifiedBaseRange } from 'vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange';
import { join } from 'vs/workbench/contrib/mergeEditor/browser/utils';
import { ActionsSource, ConflictActionsFactory, IContentWidgetAction } from 'vs/workbench/contrib/mergeEditor/browser/view/conflictActions';
import { getAlignments } from 'vs/workbench/contrib/mergeEditor/browser/view/lineAlignment';
import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/view/viewModel';
export class ViewZoneComputer {
private readonly conflictActionsFactoryInput1 = new ConflictActionsFactory(this.input1Editor);
private readonly conflictActionsFactoryInput2 = new ConflictActionsFactory(this.input2Editor);
private readonly conflictActionsFactoryResult = new ConflictActionsFactory(this.resultEditor);
constructor(
private readonly input1Editor: ICodeEditor,
private readonly input2Editor: ICodeEditor,
private readonly resultEditor: ICodeEditor,
) { }
public computeViewZones(
reader: IReader,
viewModel: MergeEditorViewModel,
options: {
shouldAlignResult: boolean;
shouldAlignBase: boolean;
codeLensesVisible: boolean;
showNonConflictingChanges: boolean;
}
): MergeEditorViewZones {
let input1LinesAdded = 0;
let input2LinesAdded = 0;
let baseLinesAdded = 0;
let resultLinesAdded = 0;
const input1ViewZones: MergeEditorViewZone[] = [];
const input2ViewZones: MergeEditorViewZone[] = [];
const baseViewZones: MergeEditorViewZone[] = [];
const resultViewZones: MergeEditorViewZone[] = [];
const model = viewModel.model;
const resultDiffs = model.baseResultDiffs.read(reader);
const baseRangeWithStoreAndTouchingDiffs = join(
model.modifiedBaseRanges.read(reader),
resultDiffs,
(baseRange, diff) =>
baseRange.baseRange.touches(diff.inputRange)
? CompareResult.neitherLessOrGreaterThan
: LineRange.compareByStart(
baseRange.baseRange,
diff.inputRange
)
);
const shouldShowCodeLenses = options.codeLensesVisible;
const showNonConflictingChanges = options.showNonConflictingChanges;
let lastModifiedBaseRange: ModifiedBaseRange | undefined = undefined;
let lastBaseResultDiff: DetailedLineRangeMapping | undefined = undefined;
for (const m of baseRangeWithStoreAndTouchingDiffs) {
if (shouldShowCodeLenses && m.left && (m.left.isConflicting || showNonConflictingChanges || !model.isHandled(m.left).read(reader))) {
const actions = new ActionsSource(viewModel, m.left);
if (options.shouldAlignResult || !actions.inputIsEmpty.read(reader)) {
input1ViewZones.push(new CommandViewZone(this.conflictActionsFactoryInput1, m.left.input1Range.startLineNumber - 1, actions.itemsInput1));
input2ViewZones.push(new CommandViewZone(this.conflictActionsFactoryInput2, m.left.input2Range.startLineNumber - 1, actions.itemsInput2));
if (options.shouldAlignBase) {
baseViewZones.push(new Placeholder(m.left.baseRange.startLineNumber - 1, 16));
}
}
const afterLineNumber = m.left.baseRange.startLineNumber + (lastBaseResultDiff?.resultingDeltaFromOriginalToModified ?? 0) - 1;
resultViewZones.push(new CommandViewZone(this.conflictActionsFactoryResult, afterLineNumber, actions.resultItems));
}
const lastResultDiff = lastOrDefault(m.rights)!;
if (lastResultDiff) {
lastBaseResultDiff = lastResultDiff;
}
let alignedLines: LineAlignment[];
if (m.left) {
alignedLines = getAlignments(m.left).map(a => ({
input1Line: a[0],
baseLine: a[1],
input2Line: a[2],
resultLine: undefined,
}));
lastModifiedBaseRange = m.left;
// This is a total hack.
alignedLines[alignedLines.length - 1].resultLine =
m.left.baseRange.endLineNumberExclusive
+ (lastBaseResultDiff ? lastBaseResultDiff.resultingDeltaFromOriginalToModified : 0);
} else {
alignedLines = [{
baseLine: lastResultDiff.inputRange.endLineNumberExclusive,
input1Line: lastResultDiff.inputRange.endLineNumberExclusive + (lastModifiedBaseRange ? (lastModifiedBaseRange.input1Range.endLineNumberExclusive - lastModifiedBaseRange.baseRange.endLineNumberExclusive) : 0),
input2Line: lastResultDiff.inputRange.endLineNumberExclusive + (lastModifiedBaseRange ? (lastModifiedBaseRange.input2Range.endLineNumberExclusive - lastModifiedBaseRange.baseRange.endLineNumberExclusive) : 0),
resultLine: lastResultDiff.outputRange.endLineNumberExclusive,
}];
}
for (const { input1Line, baseLine, input2Line, resultLine } of alignedLines) {
if (!options.shouldAlignBase && (input1Line === undefined || input2Line === undefined)) {
continue;
}
const input1Line_ =
input1Line !== undefined ? input1Line + input1LinesAdded : -1;
const input2Line_ =
input2Line !== undefined ? input2Line + input2LinesAdded : -1;
const baseLine_ = baseLine + baseLinesAdded;
const resultLine_ = resultLine !== undefined ? resultLine + resultLinesAdded : -1;
const max = Math.max(options.shouldAlignBase ? baseLine_ : 0, input1Line_, input2Line_, options.shouldAlignResult ? resultLine_ : 0);
if (input1Line !== undefined) {
const diffInput1 = max - input1Line_;
if (diffInput1 > 0) {
input1ViewZones.push(new Spacer(input1Line - 1, diffInput1));
input1LinesAdded += diffInput1;
}
}
if (input2Line !== undefined) {
const diffInput2 = max - input2Line_;
if (diffInput2 > 0) {
input2ViewZones.push(new Spacer(input2Line - 1, diffInput2));
input2LinesAdded += diffInput2;
}
}
if (options.shouldAlignBase) {
const diffBase = max - baseLine_;
if (diffBase > 0) {
baseViewZones.push(new Spacer(baseLine - 1, diffBase));
baseLinesAdded += diffBase;
}
}
if (options.shouldAlignResult && resultLine !== undefined) {
const diffResult = max - resultLine_;
if (diffResult > 0) {
resultViewZones.push(new Spacer(resultLine - 1, diffResult));
resultLinesAdded += diffResult;
}
}
}
}
return new MergeEditorViewZones(input1ViewZones, input2ViewZones, baseViewZones, resultViewZones);
}
}
interface LineAlignment {
baseLine: number;
input1Line?: number;
input2Line?: number;
resultLine?: number;
}
export class MergeEditorViewZones {
constructor(
public readonly input1ViewZones: readonly MergeEditorViewZone[],
public readonly input2ViewZones: readonly MergeEditorViewZone[],
public readonly baseViewZones: readonly MergeEditorViewZone[],
public readonly resultViewZones: readonly MergeEditorViewZone[],
) { }
}
/**
* This is an abstract class to create various editor view zones.
*/
export abstract class MergeEditorViewZone {
abstract create(viewZoneChangeAccessor: IViewZoneChangeAccessor, viewZoneIdsToCleanUp: string[], disposableStore: DisposableStore): void;
}
class Spacer extends MergeEditorViewZone {
constructor(
private readonly afterLineNumber: number,
private readonly heightInLines: number
) {
super();
}
override create(
viewZoneChangeAccessor: IViewZoneChangeAccessor,
viewZoneIdsToCleanUp: string[],
disposableStore: DisposableStore
): void {
viewZoneIdsToCleanUp.push(
viewZoneChangeAccessor.addZone({
afterLineNumber: this.afterLineNumber,
heightInLines: this.heightInLines,
domNode: $('div.diagonal-fill'),
})
);
}
}
class Placeholder extends MergeEditorViewZone {
constructor(
private readonly afterLineNumber: number,
private readonly heightPx: number
) {
super();
}
override create(
viewZoneChangeAccessor: IViewZoneChangeAccessor,
viewZoneIdsToCleanUp: string[],
disposableStore: DisposableStore
): void {
viewZoneIdsToCleanUp.push(
viewZoneChangeAccessor.addZone({
afterLineNumber: this.afterLineNumber,
heightInPx: this.heightPx,
domNode: $('div.conflict-actions-placeholder'),
})
);
}
}
class CommandViewZone extends MergeEditorViewZone {
constructor(
private readonly conflictActionsFactory: ConflictActionsFactory,
private readonly lineNumber: number,
private readonly items: IObservable<IContentWidgetAction[]>,
) {
super();
}
override create(viewZoneChangeAccessor: IViewZoneChangeAccessor, viewZoneIdsToCleanUp: string[], disposableStore: DisposableStore): void {
disposableStore.add(
this.conflictActionsFactory.createWidget(
viewZoneChangeAccessor,
this.lineNumber,
this.items,
viewZoneIdsToCleanUp,
)
);
}
}