mirror of
https://github.com/microsoft/vscode.git
synced 2026-10-07 09:50:12 +01: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,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user