Skip to content

Commit

Permalink
Merge pull request #150709 from microsoft/3wm
Browse files Browse the repository at this point in the history
3 Way Merge Editor Bugfixes
  • Loading branch information
hediet committed May 31, 2022
2 parents 28f5543 + 49ffe90 commit 3d23b1b
Show file tree
Hide file tree
Showing 7 changed files with 257 additions and 103 deletions.
14 changes: 13 additions & 1 deletion src/vs/base/browser/ui/toggle/toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export class Toggle extends Widget {
readonly onKeyDown: Event<IKeyboardEvent> = this._onKeyDown.event;

private readonly _opts: IToggleOpts;
private _icon: CSSIcon | undefined;
readonly domNode: HTMLElement;

private _checked: boolean;
Expand All @@ -110,7 +111,8 @@ export class Toggle extends Widget {

const classes = ['monaco-custom-toggle'];
if (this._opts.icon) {
classes.push(...CSSIcon.asClassNameArray(this._opts.icon));
this._icon = this._opts.icon;
classes.push(...CSSIcon.asClassNameArray(this._icon));
}
if (this._opts.actionClassName) {
classes.push(...this._opts.actionClassName.split(' '));
Expand Down Expand Up @@ -174,6 +176,16 @@ export class Toggle extends Widget {
this.applyStyles();
}

setIcon(icon: CSSIcon | undefined): void {
if (this._icon) {
this.domNode.classList.remove(...CSSIcon.asClassNameArray(this._icon));
}
this._icon = icon;
if (this._icon) {
this.domNode.classList.add(...CSSIcon.asClassNameArray(this._icon));
}
}

width(): number {
return 2 /*margin left*/ + 2 /*border*/ + 2 /*padding*/ + 16 /* icon width */;
}
Expand Down
8 changes: 5 additions & 3 deletions src/vs/workbench/contrib/mergeEditor/browser/editorGutter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,11 @@ export class EditorGutter<
scrollTop +
lineHeight;

const bottom =
this._editor.getTopForLineNumber(gutterItem.range.endLineNumberExclusive) -
scrollTop;
const bottom = (
gutterItem.range.endLineNumberExclusive <= this._editor.getModel()!.getLineCount()
? this._editor.getTopForLineNumber(gutterItem.range.endLineNumberExclusive)
: this._editor.getTopForLineNumber(gutterItem.range.endLineNumberExclusive - 1) + lineHeight
) - scrollTop;

const height = bottom - top;

Expand Down
17 changes: 14 additions & 3 deletions src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,19 @@
justify-content: center;
}

.merge-accept-gutter-marker .checkbox .accept-conflict-group.monaco-custom-toggle.monaco-checkbox {
margin: 0;
padding: 0;
.accept-conflict-group.monaco-custom-toggle {
height: 18px;
width: 18px;
border: 1px solid transparent;
border-radius: 3px;
margin-right: 0px;
margin-left: 0px;
padding: 0px;
opacity: 1;
background-size: 16px !important;
background-color: var(--vscode-checkbox-border);
}

.checkbox-background {
background: var(--vscode-editor-background);
}
146 changes: 82 additions & 64 deletions src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,48 +106,40 @@ export class MergeEditor extends EditorPane {
this._register(keepAlive(input1ResultMapping));
this._register(keepAlive(input2ResultMapping));

this._store.add(this.input1View.editor.onDidScrollChange(c => {
if (c.scrollTopChanged) {
reentrancyBarrier.runExclusively(() => {
const mapping = input1ResultMapping.get();
if (!mapping) {
return;
this._store.add(
this.input1View.editor.onDidScrollChange(
reentrancyBarrier.makeExclusive((c) => {
if (c.scrollTopChanged) {
const mapping = input1ResultMapping.get();
synchronizeScrolling(this.input1View.editor, this.inputResultView.editor, mapping, 1);
this.input2View.editor.setScrollTop(c.scrollTop, ScrollType.Immediate);
}
synchronizeScrolling(this.input1View.editor, this.inputResultView.editor, mapping, 1);
this.input2View.editor.setScrollTop(c.scrollTop, ScrollType.Immediate);
});
}
}));

this._store.add(this.input2View.editor.onDidScrollChange(c => {
if (c.scrollTopChanged) {
reentrancyBarrier.runExclusively(() => {
const mapping = input2ResultMapping.get();
if (!mapping) {
return;
}
synchronizeScrolling(this.input2View.editor, this.inputResultView.editor, mapping, 1);
this.input1View.editor.setScrollTop(c.scrollTop, ScrollType.Immediate);
});
}
}));
this._store.add(this.inputResultView.editor.onDidScrollChange(c => {
if (c.scrollTopChanged) {
reentrancyBarrier.runExclusively(() => {
const mapping = input1ResultMapping.get();
if (!mapping) {
return;
})
)
);
this._store.add(
this.input2View.editor.onDidScrollChange(
reentrancyBarrier.makeExclusive((c) => {
if (c.scrollTopChanged) {
const mapping = input2ResultMapping.get();
synchronizeScrolling(this.input2View.editor, this.inputResultView.editor, mapping, 1);
this.input1View.editor.setScrollTop(c.scrollTop, ScrollType.Immediate);
}
synchronizeScrolling(this.inputResultView.editor, this.input1View.editor, mapping, 2);

const mapping2 = input2ResultMapping.get();
if (!mapping2) {
return;
})
)
);
this._store.add(
this.inputResultView.editor.onDidScrollChange(
reentrancyBarrier.makeExclusive((c) => {
if (c.scrollTopChanged) {
const mapping1 = input1ResultMapping.get();
synchronizeScrolling(this.inputResultView.editor, this.input1View.editor, mapping1, 2);
const mapping2 = input2ResultMapping.get();
synchronizeScrolling(this.inputResultView.editor, this.input2View.editor, mapping2, 2);
}
synchronizeScrolling(this.inputResultView.editor, this.input2View.editor, mapping2, 2);
});
}
}));
})
)
);


// TODO@jrieken make this proper: add menu id and allow extensions to contribute
Expand Down Expand Up @@ -307,11 +299,11 @@ export class MergeEditor extends EditorPane {
}
}

function flip(value: 1 | 2): 1 | 2 {
return value === 1 ? 2 : 1;
}
function synchronizeScrolling(scrollingEditor: CodeEditorWidget, targetEditor: CodeEditorWidget, mapping: ModifiedBaseRange[] | undefined, sourceNumber: 1 | 2) {
if (!mapping) {
return;
}

function synchronizeScrolling(scrollingEditor: CodeEditorWidget, targetEditor: CodeEditorWidget, mapping: ModifiedBaseRange[], sourceNumber: 1 | 2) {
const visibleRanges = scrollingEditor.getVisibleRanges();
if (visibleRanges.length === 0) {
return;
Expand All @@ -322,30 +314,46 @@ function synchronizeScrolling(scrollingEditor: CodeEditorWidget, targetEditor: C
let sourceRange: LineRange;
let targetRange: LineRange;

const targetNumber = flip(sourceNumber);
const targetNumber = sourceNumber === 1 ? 2 : 1;

const firstBeforeSourceRange = firstBefore?.getInputRange(sourceNumber);
const firstBeforeTargetRange = firstBefore?.getInputRange(targetNumber);

if (firstBefore && firstBefore.getInputRange(sourceNumber).contains(topLineNumber)) {
sourceRange = firstBefore.getInputRange(sourceNumber);
targetRange = firstBefore.getInputRange(targetNumber);
} else if (firstBefore && firstBefore.getInputRange(sourceNumber).isEmpty && firstBefore.getInputRange(sourceNumber).startLineNumber === topLineNumber) {
sourceRange = firstBefore.getInputRange(sourceNumber).deltaEnd(1);
targetRange = firstBefore.getInputRange(targetNumber).deltaEnd(1);
if (firstBeforeSourceRange && firstBeforeSourceRange.contains(topLineNumber)) {
sourceRange = firstBeforeSourceRange;
targetRange = firstBeforeTargetRange!;
} else if (firstBeforeSourceRange && firstBeforeSourceRange.isEmpty && firstBeforeSourceRange.startLineNumber === topLineNumber) {
sourceRange = firstBeforeSourceRange.deltaEnd(1);
targetRange = firstBeforeTargetRange!.deltaEnd(1);
} else {
const delta = firstBefore ? firstBefore.getInputRange(targetNumber).endLineNumberExclusive - firstBefore.getInputRange(sourceNumber).endLineNumberExclusive : 0;
const delta = firstBeforeSourceRange ? firstBeforeTargetRange!.endLineNumberExclusive - firstBeforeSourceRange.endLineNumberExclusive : 0;
sourceRange = new LineRange(topLineNumber, 1);
targetRange = new LineRange(topLineNumber + delta, 1);
}

// sourceRange is not empty!
// sourceRange contains topLineNumber!

const resultStartTopPx = targetEditor.getTopForLineNumber(targetRange.startLineNumber);
const resultEndPx = targetEditor.getTopForLineNumber(targetRange.endLineNumberExclusive);

const sourceStartTopPx = scrollingEditor.getTopForLineNumber(sourceRange.startLineNumber);
const sourceEndPx = scrollingEditor.getTopForLineNumber(sourceRange.endLineNumberExclusive);

const factor = (scrollingEditor.getScrollTop() - sourceStartTopPx) / (sourceEndPx - sourceStartTopPx);
const factor = Math.min((scrollingEditor.getScrollTop() - sourceStartTopPx) / (sourceEndPx - sourceStartTopPx), 1);
const resultScrollPosition = resultStartTopPx + (resultEndPx - resultStartTopPx) * factor;
/*
console.log({
topLineNumber,
sourceRange: sourceRange.toString(),
targetRange: targetRange.toString(),
// resultStartTopPx,
// resultEndPx,
// sourceStartTopPx,
// sourceEndPx,
factor,
resultScrollPosition,
top: scrollingEditor.getScrollTop(),
});*/

targetEditor.setScrollTop(resultScrollPosition, ScrollType.Immediate);
}
Expand Down Expand Up @@ -468,7 +476,7 @@ class InputCodeEditorView extends CodeEditorView {
if (!model) { return []; }
return model.modifiedBaseRanges
.filter((r) => r.getInputDiffs(this.inputNumber).length > 0)
.map<MergeConflictData>((baseRange, idx) => ({
.map<ModifiedBaseRangeGutterItemInfo>((baseRange, idx) => ({
id: idx.toString(),
additionalHeightInPx: 0,
offsetInPx: 0,
Expand Down Expand Up @@ -497,25 +505,31 @@ class InputCodeEditorView extends CodeEditorView {
}
}

interface MergeConflictData extends IGutterItemInfo {
interface ModifiedBaseRangeGutterItemInfo extends IGutterItemInfo {
toggleState: IObservable<boolean | undefined>;
setState(value: boolean, tx: ITransaction | undefined): void;
}

class MergeConflictGutterItemView extends Disposable implements IGutterItemView<MergeConflictData> {
constructor(private item: MergeConflictData, target: HTMLElement) {
class MergeConflictGutterItemView extends Disposable implements IGutterItemView<ModifiedBaseRangeGutterItemInfo> {
constructor(private item: ModifiedBaseRangeGutterItemInfo, private readonly target: HTMLElement) {
super();

target.classList.add('merge-accept-gutter-marker');
target.classList.add(item.range.lineCount > 1 ? 'multi-line' : 'single-line');

// TODO: Tri-State-Toggle, localized title
const checkBox = new Toggle({ isChecked: false, title: 'Accept Merge', icon: Codicon.check, actionClassName: 'monaco-checkbox' });
// TODO: localized title
const checkBox = new Toggle({ isChecked: false, title: 'Accept Merge', icon: Codicon.check });
checkBox.domNode.classList.add('accept-conflict-group');

this._register(
autorun((reader) => {
const value = this.item.toggleState.read(reader);
checkBox.setIcon(
value === true
? Codicon.check
: value === false
? undefined
: Codicon.circleFilled
);
checkBox.checked = value === true;
}, 'Update Toggle State')
);
Expand All @@ -524,15 +538,19 @@ class MergeConflictGutterItemView extends Disposable implements IGutterItemView<
this.item.setState(checkBox.checked, undefined);
}));

target.appendChild($('div.background', {}, noBreakWhitespace));
target.appendChild($('div.checkbox', {}, checkBox.domNode));
target.appendChild(n('div.background', [noBreakWhitespace]).root);
target.appendChild(
n('div.checkbox', [n('div.checkbox-background', [checkBox.domNode])]).root
);
}

layout(top: number, height: number, viewTop: number, viewHeight: number): void {

this.target.classList.remove('multi-line');
this.target.classList.remove('single-line');
this.target.classList.add(height > 30 ? 'multi-line' : 'single-line');
}

update(baseRange: MergeConflictData): void {
update(baseRange: ModifiedBaseRangeGutterItemInfo): void {
this.item = baseRange;
}
}
Expand Down

0 comments on commit 3d23b1b

Please sign in to comment.