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

Diff Editor v2 Bugfixes #184797

Merged
merged 1 commit into from Jun 10, 2023
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 @@ -20,7 +20,7 @@ import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/wi
import { IDiffCodeEditorWidgetOptions } from 'vs/editor/browser/widget/diffEditorWidget';
import { diffAddDecoration, diffDeleteDecoration, diffFullLineAddDecoration, diffFullLineDeleteDecoration } from 'vs/editor/browser/widget/diffEditorWidget2/decorations';
import { DiffEditorSash } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorSash';
import { ViewZoneAlignment } from 'vs/editor/browser/widget/diffEditorWidget2/lineAlignment';
import { ViewZoneManager } from 'vs/editor/browser/widget/diffEditorWidget2/lineAlignment';
import { MovedBlocksLinesPart } from 'vs/editor/browser/widget/diffEditorWidget2/movedBlocksLines';
import { OverviewRulerPart } from 'vs/editor/browser/widget/diffEditorWidget2/overviewRulerPart';
import { UnchangedRangesFeature } from 'vs/editor/browser/widget/diffEditorWidget2/unchangedRanges';
Expand Down Expand Up @@ -140,7 +140,7 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {


this._register(new UnchangedRangesFeature(this._originalEditor, this._modifiedEditor, this._diffModel));
this._register(this._instantiationService.createInstance(ViewZoneAlignment, this._originalEditor, this._modifiedEditor, this._diffModel, this._options.map(o => o.renderSideBySide)));
this._register(this._instantiationService.createInstance(ViewZoneManager, this._originalEditor, this._modifiedEditor, this._diffModel, this._options.map(o => o.renderSideBySide)));

this._register(this._instantiationService.createInstance(OverviewRulerPart,
this._originalEditor,
Expand Down Expand Up @@ -442,13 +442,21 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
return this._originalEditor.hasTextFocus() || this._modifiedEditor.hasTextFocus();
}

override saveViewState(): IDiffEditorViewState | null {
return null;
//throw new Error('Method not implemented.');
public override saveViewState(): IDiffEditorViewState {
const originalViewState = this._originalEditor.saveViewState();
const modifiedViewState = this._modifiedEditor.saveViewState();
return {
original: originalViewState,
modified: modifiedViewState
};
}

override restoreViewState(state: IDiffEditorViewState | null): void {
//throw new Error('Method not implemented.');
public override restoreViewState(s: IDiffEditorViewState): void {
if (s && s.original && s.modified) {
const diffEditorState = s as IDiffEditorViewState;
this._originalEditor.restoreViewState(diffEditorState.original);
this._modifiedEditor.restoreViewState(diffEditorState.modified);
}
}

override getModel(): IDiffEditorModel | null { return this._model.get(); }
Expand Down
107 changes: 56 additions & 51 deletions src/vs/editor/browser/widget/diffEditorWidget2/lineAlignment.ts
Expand Up @@ -8,8 +8,10 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IObservable, derived, observableFromEvent, observableSignalFromEvent, observableValue } from 'vs/base/common/observable';
import { autorun, autorunWithStore2 } from 'vs/base/common/observableImpl/autorun';
import { ThemeIcon } from 'vs/base/common/themables';
import { assertIsDefined } from 'vs/base/common/types';
import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo';
import { IViewZone } from 'vs/editor/browser/editorBrowser';
import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { diffDeleteDecoration, diffRemoveIcon } from 'vs/editor/browser/widget/diffEditorWidget2/decorations';
import { DiffMapping, DiffModel } from 'vs/editor/browser/widget/diffEditorWidget2/diffModel';
Expand All @@ -26,14 +28,18 @@ import { InlineDecoration, InlineDecorationType } from 'vs/editor/common/viewMod
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';

export class ViewZoneAlignment extends Disposable {
private readonly _origExtraHeight = observableValue('origExtraHeight', 0);
private readonly _modExtraHeight = observableValue('modExtraHeight', 0);

/**
* Ensures both editors have the same height by aligning unchanged lines.
* In inline view mode, inserts viewzones to show deleted code from the original text model in the modified code editor.
* Synchronizes scrolling.
*/
export class ViewZoneManager extends Disposable {
private readonly _originalTopPadding = observableValue('originalTopPadding', 0);
private readonly _originalScrollTop: IObservable<number>;
private readonly _originalScrollOffset = observableValue<number, boolean>('originalScrollOffset', 0);
private readonly _originalScrollOffsetAnimated = animatedObservable(this._originalScrollOffset, this._store);

private readonly _modifiedTopPadding = observableValue('modifiedTopPadding', 0);
private readonly _modifiedScrollTop: IObservable<number>;
private readonly _modifiedScrollOffset = observableValue<number, boolean>('modifiedScrollOffset', 0);
private readonly _modifiedScrollOffsetAnimated = animatedObservable(this._modifiedScrollOffset, this._store);
Expand All @@ -50,11 +56,11 @@ export class ViewZoneAlignment extends Disposable {

let isChangingViewZones = false;

const origViewZonesChanged = observableSignalFromEvent(
const originalViewZonesChanged = observableSignalFromEvent(
'origViewZonesChanged',
e => this._originalEditor.onDidChangeViewZones((args) => { if (!isChangingViewZones) { e(args); } })
);
const modViewZonesChanged = observableSignalFromEvent(
const modifiedViewZonesChanged = observableSignalFromEvent(
'modViewZonesChanged',
e => this._modifiedEditor.onDidChangeViewZones((args) => { if (!isChangingViewZones) { e(args); } })
);
Expand All @@ -70,24 +76,18 @@ export class ViewZoneAlignment extends Disposable {
const diffModel = this._diffModel.read(reader);
const diff = diffModel?.diff.read(reader);
if (!diffModel || !diff) { return null; }

origViewZonesChanged.read(reader);
modViewZonesChanged.read(reader);

originalViewZonesChanged.read(reader);
modifiedViewZonesChanged.read(reader);
return computeRangeAlignment(this._originalEditor, this._modifiedEditor, diff.mappings, alignmentViewZoneIdsOrig, alignmentViewZoneIdsMod);
});

const alignmentsSyncedMovedText = derived<ILineRangeAlignment[] | null>('alignments', (reader) => {
origViewZonesChanged.read(reader);
modViewZonesChanged.read(reader);

const syncedMovedText = this._diffModel.read(reader)?.syncedMovedTexts.read(reader);
if (!syncedMovedText) {
return null;
}
if (!syncedMovedText) { return null; }
originalViewZonesChanged.read(reader);
modifiedViewZonesChanged.read(reader);
const mappings = syncedMovedText.changes.map(c => new DiffMapping(c));

// TOD dont include alignments outside syncedMovedText
// TODO dont include alignments outside syncedMovedText
return computeRangeAlignment(this._originalEditor, this._modifiedEditor, mappings, alignmentViewZoneIdsOrig, alignmentViewZoneIdsMod);
});

Expand All @@ -101,33 +101,33 @@ export class ViewZoneAlignment extends Disposable {
const alignmentViewZones = derived<{ orig: IViewZoneWithZoneId[]; mod: IViewZoneWithZoneId[] }>('alignment viewzones', (reader) => {
alignmentViewZonesDisposables.clear();

const curAlignments = alignments.read(reader) || [];
const alignmentsVal = alignments.read(reader) || [];

const origViewZones: IViewZoneWithZoneId[] = [];
const modViewZones: IViewZoneWithZoneId[] = [];

const curModExtraHeight = this._modExtraHeight.read(reader);
if (curModExtraHeight > 0) {
const modifiedTopPaddingVal = this._modifiedTopPadding.read(reader);
if (modifiedTopPaddingVal > 0) {
modViewZones.push({
afterLineNumber: 0,
domNode: document.createElement('div'),
heightInPx: curModExtraHeight,
heightInPx: modifiedTopPaddingVal,
});
}
const curOrigExtraHeight = this._origExtraHeight.read(reader);
if (curOrigExtraHeight > 0) {
const originalTopPaddingVal = this._originalTopPadding.read(reader);
if (originalTopPaddingVal > 0) {
origViewZones.push({
afterLineNumber: 0,
domNode: document.createElement('div'),
heightInPx: curOrigExtraHeight,
heightInPx: originalTopPaddingVal,
});
}

const renderSideBySide = this._renderSideBySide.read(reader);

const deletedCodeLineBreaksComputer = !renderSideBySide ? this._modifiedEditor._getViewModel()?.createLineBreaksComputer() : undefined;
if (deletedCodeLineBreaksComputer) {
for (const a of curAlignments) {
for (const a of alignmentsVal) {
if (a.diff) {
for (let i = a.originalRange.startLineNumber; i < a.originalRange.endLineNumberExclusive; i++) {
deletedCodeLineBreaksComputer?.addRequest(this._originalEditor.getModel()!.getLineContent(i), null, null);
Expand All @@ -147,13 +147,13 @@ export class ViewZoneAlignment extends Disposable {
const mightContainRTL = this._originalEditor.getModel()?.mightContainRTL() ?? false;
const renderOptions = RenderOptions.fromEditor(this._modifiedEditor);

for (const a of curAlignments) {
for (const a of alignmentsVal) {
if (a.diff && !renderSideBySide) {
if (!a.originalRange.isEmpty) {
originalModelTokenizationCompleted.read(reader); // Update view-zones once tokenization completes

const domNode = document.createElement('div');
domNode.classList.add('view-lines', 'line-delete', 'monaco-mouse-cursor-text');
const deletedCodeDomNode = document.createElement('div');
deletedCodeDomNode.classList.add('view-lines', 'line-delete', 'monaco-mouse-cursor-text');
const source = new LineSource(
a.originalRange.mapToLineArray(l => this._originalEditor.getModel()!.tokenization.getLineTokens(l)),
a.originalRange.mapToLineArray(_ => lineBreakData[lineBreakDataIdx++]),
Expand All @@ -168,7 +168,7 @@ export class ViewZoneAlignment extends Disposable {
InlineDecorationType.Regular
));
}
const result = renderLines(source, renderOptions, decorations, domNode);
const result = renderLines(source, renderOptions, decorations, deletedCodeDomNode);

const marginDomNode = document.createElement('div');
marginDomNode.className = 'inline-deleted-margin-view-zone';
Expand All @@ -183,18 +183,10 @@ export class ViewZoneAlignment extends Disposable {
}
//}

let zoneId: string;
modViewZones.push({
afterLineNumber: a.modifiedRange.startLineNumber - 1,
domNode: domNode,
heightInPx: result.heightInLines * modLineHeight,
minWidthInPx: result.minWidthInPx,
marginDomNode,
setZoneId(id) { zoneId = id; },
});
let zoneId: string | undefined = undefined;
alignmentViewZonesDisposables.add(
new InlineDiffDeletedCodeMargin(
() => zoneId,
() => assertIsDefined(zoneId),
marginDomNode,
this._modifiedEditor,
a.diff,
Expand All @@ -207,7 +199,7 @@ export class ViewZoneAlignment extends Disposable {

for (let i = 0; i < result.viewLineCounts.length; i++) {
const count = result.viewLineCounts[i];
// Account for wrapped lines in the (collapsed) original editor (that does not have line wraps).
// Account for wrapped lines in the (collapsed) original editor (which doesn't wrap lines).
if (count > 1) {
origViewZones.push({
afterLineNumber: a.originalRange.startLineNumber + i,
Expand All @@ -216,6 +208,15 @@ export class ViewZoneAlignment extends Disposable {
});
}
}

modViewZones.push({
afterLineNumber: a.modifiedRange.startLineNumber - 1,
domNode: deletedCodeDomNode,
heightInPx: result.heightInLines * modLineHeight,
minWidthInPx: result.minWidthInPx,
marginDomNode,
setZoneId(id) { zoneId = id; },
});
}

const marginDomNode = document.createElement('div');
Expand Down Expand Up @@ -280,6 +281,8 @@ export class ViewZoneAlignment extends Disposable {
});

this._register(autorunWithStore2('alignment viewzones', (reader) => {
const scrollState = StableEditorScrollState.capture(this._modifiedEditor);

const alignmentViewZones_ = alignmentViewZones.read(reader);
isChangingViewZones = true;
this._originalEditor.changeViewZones((aOrig) => {
Expand All @@ -305,6 +308,8 @@ export class ViewZoneAlignment extends Disposable {
}
});
isChangingViewZones = false;

scrollState.restore(this._modifiedEditor);
}));

this._originalScrollTop = observableFromEvent(this._originalEditor.onDidScrollChange, () => this._originalEditor.getScrollTop());
Expand All @@ -321,7 +326,7 @@ export class ViewZoneAlignment extends Disposable {
this._register(autorun('update scroll modified', (reader) => {
const newScrollTopModified = this._originalScrollTop.read(reader)
- (this._originalScrollOffsetAnimated.get() - this._modifiedScrollOffsetAnimated.read(reader))
- (this._origExtraHeight.get() - this._modExtraHeight.read(reader));
- (this._originalTopPadding.get() - this._modifiedTopPadding.read(reader));
if (newScrollTopModified !== this._modifiedEditor.getScrollTop()) {
this._modifiedEditor.setScrollTop(newScrollTopModified, ScrollType.Immediate);
}
Expand All @@ -330,7 +335,7 @@ export class ViewZoneAlignment extends Disposable {
this._register(autorun('update scroll original', (reader) => {
const newScrollTopOriginal = this._modifiedScrollTop.read(reader)
- (this._modifiedScrollOffsetAnimated.get() - this._originalScrollOffsetAnimated.read(reader))
- (this._modExtraHeight.get() - this._origExtraHeight.read(reader));
- (this._modifiedTopPadding.get() - this._originalTopPadding.read(reader));
if (newScrollTopOriginal !== this._originalEditor.getScrollTop()) {
this._originalEditor.setScrollTop(newScrollTopOriginal, ScrollType.Immediate);
}
Expand All @@ -342,21 +347,21 @@ export class ViewZoneAlignment extends Disposable {

let deltaOrigToMod = 0;
if (m) {
const trueTopOriginal = this._originalEditor.getTopForLineNumber(m.lineRangeMapping.originalRange.startLineNumber, true) - this._origExtraHeight.get();
const trueTopModified = this._modifiedEditor.getTopForLineNumber(m.lineRangeMapping.modifiedRange.startLineNumber, true) - this._modExtraHeight.get();
const trueTopOriginal = this._originalEditor.getTopForLineNumber(m.lineRangeMapping.originalRange.startLineNumber, true) - this._originalTopPadding.get();
const trueTopModified = this._modifiedEditor.getTopForLineNumber(m.lineRangeMapping.modifiedRange.startLineNumber, true) - this._modifiedTopPadding.get();
deltaOrigToMod = trueTopModified - trueTopOriginal;
}

if (deltaOrigToMod > 0) {
this._modExtraHeight.set(0, undefined);
this._origExtraHeight.set(deltaOrigToMod, undefined);
this._modifiedTopPadding.set(0, undefined);
this._originalTopPadding.set(deltaOrigToMod, undefined);
} else if (deltaOrigToMod < 0) {
this._modExtraHeight.set(-deltaOrigToMod, undefined);
this._origExtraHeight.set(0, undefined);
this._modifiedTopPadding.set(-deltaOrigToMod, undefined);
this._originalTopPadding.set(0, undefined);
} else {
setTimeout(() => {
this._modExtraHeight.set(0, undefined);
this._origExtraHeight.set(0, undefined);
this._modifiedTopPadding.set(0, undefined);
this._originalTopPadding.set(0, undefined);
}, 400);
}

Expand Down