Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge Editor: Alignment & code lens improvements #161603

Merged
merged 2 commits into from Sep 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -59,6 +59,16 @@ export abstract class CodeEditorView extends Disposable {
// snap?: boolean | undefined;
};

protected readonly checkboxesVisible = observableFromEvent<boolean>(
this.configurationService.onDidChangeConfiguration,
() => /** @description checkboxesVisible */ this.configurationService.getValue('mergeEditor.showCheckboxes') ?? true
);

protected readonly codeLensesVisible = observableFromEvent<boolean>(
this.configurationService.onDidChangeConfiguration,
() => /** @description codeLensesVisible */ this.configurationService.getValue('mergeEditor.showCodeLenses') ?? false
);

public readonly editor = this.instantiationService.createInstance(
CodeEditorWidget,
this.htmlElements.editor,
Expand Down Expand Up @@ -89,16 +99,6 @@ export abstract class CodeEditorView extends Disposable {

public readonly cursorLineNumber = this.cursorPosition.map(p => /** @description cursorPosition.lineNumber */ p?.lineNumber);

protected readonly checkboxesVisible = observableFromEvent<boolean>(
this.configurationService.onDidChangeConfiguration,
() => /** @description checkboxesVisible */ this.configurationService.getValue('mergeEditor.showCheckboxes') ?? true
);

protected readonly codeLensesVisible = observableFromEvent<boolean>(
this.configurationService.onDidChangeConfiguration,
() => /** @description codeLensesVisible */ this.configurationService.getValue('mergeEditor.showCodeLenses') ?? false
);

constructor(
private readonly instantiationService: IInstantiationService,
public readonly viewModel: IObservable<undefined | MergeEditorViewModel>,
Expand Down
Expand Up @@ -216,7 +216,7 @@ export class InputCodeEditorView extends CodeEditorView {
});

protected override getEditorContributions(): IEditorContributionDescription[] | undefined {
if (this.codeLensesVisible) {
if (this.codeLensesVisible.get()) {
return undefined;
}
return EditorExtensionsRegistry.getEditorContributions().filter(c => c.id !== CodeLensContribution.ID);
Expand Down Expand Up @@ -296,7 +296,7 @@ class CodeLensPart extends Disposable {
}
);

if (r.canBeCombined) {
if (r.canBeCombined && state.isEmpty) {
result.push({
range,
command:
Expand Down Expand Up @@ -326,6 +326,12 @@ class CodeLensPart extends Disposable {
});
}
}
if (result.length === 0) {
result.push({
range: range,
command: command(` `, async () => { })
});
}
return result;
}),
uri: inputData.textModel.uri
Expand Down
234 changes: 165 additions & 69 deletions src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts
Expand Up @@ -6,6 +6,7 @@
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';
Expand Down Expand Up @@ -37,8 +38,11 @@ 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 { deepMerge, PersistentStore, thenIfNotDisposed } from 'vs/workbench/contrib/mergeEditor/browser/utils';
import { ModifiedBaseRange } from 'vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange';
import { deepMerge, join, 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';
Expand Down Expand Up @@ -71,7 +75,8 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
private readonly input2View = this._register(this.instantiationService.createInstance(InputCodeEditorView, 2, this._viewModel));

private readonly inputResultView = this._register(this.instantiationService.createInstance(ResultCodeEditorView, this._viewModel));
private readonly _layoutMode: MergeEditorLayoutStore;
private readonly _layoutMode = this.instantiationService.createInstance(MergeEditorLayoutStore);
private readonly _layoutModeObs = observableValue('layoutMode', this._layoutMode.value);
private readonly _ctxIsMergeEditor: IContextKey<boolean>;
private readonly _ctxUsesColumnLayout: IContextKey<string>;
private readonly _ctxShowBase: IContextKey<boolean>;
Expand Down Expand Up @@ -112,8 +117,6 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
this._ctxShowBase = ctxMergeEditorShowBase.bindTo(contextKeyService);
this._ctxShowNonConflictingChanges = ctxMergeEditorShowNonConflictingChanges.bindTo(contextKeyService);

this._layoutMode = instantiation.createInstance(MergeEditorLayoutStore);

this._register(new ScrollSynchronizer(this._viewModel, this.input1View, this.input2View, this.baseView, this.inputResultView));
}

Expand Down Expand Up @@ -215,17 +218,35 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
const input1ViewZoneIds: string[] = [];
const input2ViewZoneIds: string[] = [];
const baseViewZoneIds: string[] = [];
const resultViewZoneIds: string[] = [];
const baseView = this.baseView.read(reader);

this.input1View.editor.changeViewZones(input1ViewZoneAccessor => {
this.input2View.editor.changeViewZones(input2ViewZoneAccessor => {
if (baseView) {
baseView.editor.changeViewZones(baseViewZoneAccessor => {
setViewZones(reader, input1ViewZoneIds, input1ViewZoneAccessor, input2ViewZoneIds, input2ViewZoneAccessor, baseViewZoneIds, baseViewZoneAccessor);
});
} else {
setViewZones(reader, input1ViewZoneIds, input1ViewZoneAccessor, input2ViewZoneIds, input2ViewZoneAccessor, baseViewZoneIds, undefined);
}
this.inputResultView.editor.changeViewZones(resultViewZoneAccessor => {
const actualResultViewZoneAccessor = this._layoutModeObs.read(reader).kind === 'columns' ? resultViewZoneAccessor : undefined;

this.input1View.editor.changeViewZones(input1ViewZoneAccessor => {
this.input2View.editor.changeViewZones(input2ViewZoneAccessor => {
if (baseView) {
baseView.editor.changeViewZones(baseViewZoneAccessor => {
setViewZones(reader,
input1ViewZoneIds, input1ViewZoneAccessor,
input2ViewZoneIds, input2ViewZoneAccessor,
baseViewZoneIds, /*baseViewZoneAccessor,*/ undefined,
resultViewZoneIds, actualResultViewZoneAccessor,
);
});
} else {
setViewZones(reader,
input1ViewZoneIds,
input1ViewZoneAccessor,
input2ViewZoneIds,
input2ViewZoneAccessor,
baseViewZoneIds,
undefined,
resultViewZoneIds, actualResultViewZoneAccessor,
);
}
});
});
});

Expand All @@ -246,6 +267,11 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
a.removeZone(zone);
}
});
this.inputResultView.editor.changeViewZones(a => {
for (const zone of resultViewZoneIds) {
a.removeZone(zone);
}
});
}
});

Expand Down Expand Up @@ -322,17 +348,67 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
input2ViewZoneIds: string[],
input2ViewZoneAccessor: IViewZoneChangeAccessor,
baseViewZoneIds: string[],
baseViewZoneAccessor: IViewZoneChangeAccessor | undefined
baseViewZoneAccessor: IViewZoneChangeAccessor | undefined,
resultViewZoneIds: string[],
resultViewZoneAccessor: IViewZoneChangeAccessor | undefined,
) {
let input1LinesAdded = 0;
let input2LinesAdded = 0;
let baseLinesAdded = 0;
let resultLinesAdded = 0;

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
)
);

for (const m of model.modifiedBaseRanges.read(reader)) {
const alignedLines: [number | undefined, number, number | undefined][] =
getAlignments(m);
let lastModifiedBaseRange: ModifiedBaseRange | undefined = undefined;
let lastBaseResultDiff: DetailedLineRangeMapping | undefined = undefined;
for (const m of baseRangeWithStoreAndTouchingDiffs) {
interface LineAlignment {
baseLine: number;
input1Line?: number;
input2Line?: number;
resultLine?: number;
}

for (const [input1Line, baseLine, input2Line] of alignedLines) {
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 (!baseViewZoneAccessor && (input1Line === undefined || input2Line === undefined)) {
continue;
}
Expand All @@ -342,8 +418,9 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
const input2Line_ =
input2Line !== undefined ? input2Line + input2LinesAdded : -1;
const baseLine_ = baseLine + baseLinesAdded;
const resultLine_ = resultLine !== undefined ? resultLine + resultLinesAdded : -1;

const max = Math.max(baseViewZoneAccessor ? baseLine_ : 0, input1Line_, input2Line_);
const max = Math.max(baseViewZoneAccessor ? baseLine_ : 0, input1Line_, input2Line_, resultLine_);

if (input1Line !== undefined) {
const diffInput1 = max - input1Line_;
Expand Down Expand Up @@ -386,6 +463,20 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
baseLinesAdded += diffBase;
}
}

if (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;
}
}
}
}
}
Expand Down Expand Up @@ -474,58 +565,63 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
private readonly baseViewDisposables = this._register(new DisposableStore());

private applyLayout(layout: IMergeEditorLayout): void {
if (layout.showBase && !this.baseView.get()) {
this.baseViewDisposables.clear();
const baseView = this.baseViewDisposables.add(
this.instantiationService.createInstance(
BaseCodeEditorView,
this.viewModel
)
);
this.baseViewDisposables.add(autorun('Update base view options', reader => {
const options = this.baseViewOptions.read(reader);
if (options) {
baseView.updateOptions(options);
}
}));
this.baseView.set(baseView, undefined);
} else if (!layout.showBase && this.baseView.get()) {
this.baseView.set(undefined, undefined);
this.baseViewDisposables.clear();
}
transaction(tx => {
/** @description applyLayout */

if (layout.showBase && !this.baseView.get()) {
this.baseViewDisposables.clear();
const baseView = this.baseViewDisposables.add(
this.instantiationService.createInstance(
BaseCodeEditorView,
this.viewModel
)
);
this.baseViewDisposables.add(autorun('Update base view options', reader => {
const options = this.baseViewOptions.read(reader);
if (options) {
baseView.updateOptions(options);
}
}));
this.baseView.set(baseView, tx);
} else if (!layout.showBase && this.baseView.get()) {
this.baseView.set(undefined, tx);
this.baseViewDisposables.clear();
}

if (layout.kind === 'mixed') {
this.setGrid([
layout.showBase ? {
size: 38,
data: this.baseView.get()!.view
} : undefined,
{
size: 38,
groups: [{ data: this.input1View.view }, { data: this.input2View.view }]
},
{
size: 62,
data: this.inputResultView.view
},
].filter(isDefined));
} else if (layout.kind === 'columns') {
this.setGrid([
layout.showBase ? {
size: 40,
data: this.baseView.get()!.view
} : undefined,
{
size: 60,
groups: [{ data: this.input1View.view }, { data: this.inputResultView.view }, { data: this.input2View.view }]
},
].filter(isDefined));
}
if (layout.kind === 'mixed') {
this.setGrid([
layout.showBase ? {
size: 38,
data: this.baseView.get()!.view
} : undefined,
{
size: 38,
groups: [{ data: this.input1View.view }, { data: this.input2View.view }]
},
{
size: 62,
data: this.inputResultView.view
},
].filter(isDefined));
} else if (layout.kind === 'columns') {
this.setGrid([
layout.showBase ? {
size: 40,
data: this.baseView.get()!.view
} : undefined,
{
size: 60,
groups: [{ data: this.input1View.view }, { data: this.inputResultView.view }, { data: this.input2View.view }]
},
].filter(isDefined));
}

this._layoutMode.value = layout;
this._ctxUsesColumnLayout.set(layout.kind);
this._ctxShowBase.set(layout.showBase);
this._onDidChangeSizeConstraints.fire();
this._layoutMode.value = layout;
this._ctxUsesColumnLayout.set(layout.kind);
this._ctxShowBase.set(layout.showBase);
this._onDidChangeSizeConstraints.fire();
this._layoutModeObs.set(layout, tx);
});
}

private setGrid(descriptor: GridNodeDescriptor<any>[]) {
Expand Down