Skip to content

Commit

Permalink
Implements diff editor revert button. (#184909)
Browse files Browse the repository at this point in the history
  • Loading branch information
hediet committed Jun 12, 2023
1 parent 02ec02e commit 4dd7a52
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 41 deletions.
8 changes: 8 additions & 0 deletions src/vs/editor/browser/widget/diffEditorWidget2/decorations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { Codicon } from 'vs/base/common/codicons';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { ThemeIcon } from 'vs/base/common/themables';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { localize } from 'vs/nls';
Expand Down Expand Up @@ -37,3 +38,10 @@ export const diffDeleteDecoration = ModelDecorationOptions.register({
className: 'char-delete',
description: 'char-delete',
});

export const arrowRevertChange = ModelDecorationOptions.register({
description: 'diff-editor-arrow-revert-change',
glyphMarginHoverMessage: new MarkdownString(undefined, { isTrusted: true, supportThemeIcons: true }).appendMarkdown(localize('revertChangeHoverMessage', 'Click to revert change')),
glyphMarginClassName: 'arrow-revert-change ' + ThemeIcon.asClassName(Codicon.arrowRight),
zIndex: 10001,
});
62 changes: 48 additions & 14 deletions src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ import { isDefined } from 'vs/base/common/types';
import { Constants } from 'vs/base/common/uint';
import 'vs/css!./style';
import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration';
import { ICodeEditor, IDiffEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser';
import { ICodeEditor, IDiffEditor, IDiffEditorConstructionOptions, IMouseTargetViewZone } from 'vs/editor/browser/editorBrowser';
import { EditorExtensionsRegistry, IDiffEditorContributionDescription } from 'vs/editor/browser/editorExtensions';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
import { IDiffCodeEditorWidgetOptions } from 'vs/editor/browser/widget/diffEditorWidget';
import { diffAddDecoration, diffDeleteDecoration, diffFullLineAddDecoration, diffFullLineDeleteDecoration } from 'vs/editor/browser/widget/diffEditorWidget2/decorations';
import { arrowRevertChange, diffAddDecoration, diffDeleteDecoration, diffFullLineAddDecoration, diffFullLineDeleteDecoration } from 'vs/editor/browser/widget/diffEditorWidget2/decorations';
import { DiffEditorSash } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorSash';
import { ViewZoneManager } from 'vs/editor/browser/widget/diffEditorWidget2/lineAlignment';
import { MovedBlocksLinesPart } from 'vs/editor/browser/widget/diffEditorWidget2/movedBlocksLines';
Expand All @@ -40,6 +40,8 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { DelegatingEditor } from './delegatingEditorImpl';
import { DiffMapping, DiffModel } from './diffModel';
import { Range } from 'vs/editor/common/core/range';
import { LineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer';

const diffEditorDefaultOptions: ValidDiffEditorBaseOptions = {
enableSplitViewResizing: true,
Expand Down Expand Up @@ -142,7 +144,16 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
this._register(keepAlive(this._sash, true));

this._register(new UnchangedRangesFeature(this._originalEditor, this._modifiedEditor, this._diffModel));
this._register(this._instantiationService.createInstance(ViewZoneManager, 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
)
);

this._register(this._instantiationService.createInstance(OverviewRulerPart,
this._originalEditor,
Expand Down Expand Up @@ -229,6 +240,10 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
originalDecorations.push({ range: i.originalRange, options: diffDeleteDecoration });
modifiedDecorations.push({ range: i.modifiedRange, options: diffAddDecoration });
}

if (!m.lineRangeMapping.modifiedRange.isEmpty) {
modifiedDecorations.push({ range: Range.fromPositions(new Position(m.lineRangeMapping.modifiedRange.startLineNumber, 1)), options: arrowRevertChange });
}
}

if (currentMove) {
Expand Down Expand Up @@ -311,24 +326,32 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
m.syncedMovedTexts.set(movedText, undefined);
}));
// Revert change when an arrow is clicked.
/*TODO
this._register(editor.onMouseDown(event => {
if (!event.event.rightButton && event.target.position && event.target.element?.className.includes('arrow-revert-change')) {
const lineNumber = event.target.position.lineNumber;
const viewZone = event.target as editorBrowser.IMouseTargetViewZone | undefined;
const change = this._diffComputationResult?.changes.find(c =>
// delete change
viewZone?.detail.afterLineNumber === c.modifiedStartLineNumber ||
// other changes
(c.modifiedEndLineNumber > 0 && c.modifiedStartLineNumber === lineNumber));
if (change) {
this.revertChange(change);
const viewZone = event.target as IMouseTargetViewZone | undefined;

const model = this._diffModel.get();
if (!model) {
return;
}
const diffs = model.diff.get()?.mappings;
if (!diffs) {
return;
}
const diff = diffs.find(d =>
viewZone?.detail.afterLineNumber === d.lineRangeMapping.modifiedRange.startLineNumber - 1 ||
d.lineRangeMapping.modifiedRange.startLineNumber === lineNumber
);
if (!diff) {
return;
}
this.revert(diff.lineRangeMapping);

event.event.stopPropagation();
this._updateDecorations();
return;
}
}));*/
}));

return editor;
}
Expand Down Expand Up @@ -579,6 +602,17 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
};
}

public revert(diff: LineRangeMapping): void {
const model = this._model.get();
if (!model) {
return;
}
const originalText = model.original.getValueInRange(diff.originalRange.toExclusiveRange());
this._modifiedEditor.executeEdits('diffEditor', [
{ range: diff.modifiedRange.toExclusiveRange(), text: originalText }
]);
}

private _goTo(diff: DiffMapping): void {
this._modifiedEditor.setPosition(new Position(diff.lineRangeMapping.modifiedRange.startLineNumber, 1));
this._modifiedEditor.revealRangeInCenter(diff.lineRangeMapping.modifiedRange.toExclusiveRange());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { isIOS } from 'vs/base/common/platform';
import { ThemeIcon } from 'vs/base/common/themables';
import { IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { DiffEditorWidget2 } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { LineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
import { EndOfLineSequence, ITextModel } from 'vs/editor/common/model';
Expand Down Expand Up @@ -42,9 +43,10 @@ export class InlineDiffDeletedCodeMargin extends Disposable {
constructor(
private readonly _getViewZoneId: () => string,
private readonly _marginDomNode: HTMLElement,
private readonly editor: CodeEditorWidget,
private readonly diff: LineRangeMapping,
private readonly viewLineCounts: number[],
private readonly _modifiedEditor: CodeEditorWidget,
private readonly _diff: LineRangeMapping,
private readonly _editor: DiffEditorWidget2,
private readonly _viewLineCounts: number[],
private readonly _originalTextModel: ITextModel,
private readonly _contextMenuService: IContextMenuService,
private readonly _clipboardService: IClipboardService,
Expand All @@ -57,46 +59,46 @@ export class InlineDiffDeletedCodeMargin extends Disposable {
this._diffActions = document.createElement('div');
this._diffActions.className = ThemeIcon.asClassName(Codicon.lightBulb) + ' lightbulb-glyph';
this._diffActions.style.position = 'absolute';
const lineHeight = this.editor.getOption(EditorOption.lineHeight);
const lineHeight = this._modifiedEditor.getOption(EditorOption.lineHeight);
this._diffActions.style.right = '0px';
this._diffActions.style.visibility = 'hidden';
this._diffActions.style.height = `${lineHeight}px`;
this._diffActions.style.lineHeight = `${lineHeight}px`;
this._marginDomNode.appendChild(this._diffActions);

const actions: Action[] = [];
const isDeletion = diff.modifiedRange.isEmpty;
const isDeletion = _diff.modifiedRange.isEmpty;

// default action
actions.push(new Action(
'diff.clipboard.copyDeletedContent',
isDeletion
? (diff.originalRange.length > 1
? (_diff.originalRange.length > 1
? localize('diff.clipboard.copyDeletedLinesContent.label', "Copy deleted lines")
: localize('diff.clipboard.copyDeletedLinesContent.single.label', "Copy deleted line"))
: (diff.originalRange.length > 1
: (_diff.originalRange.length > 1
? localize('diff.clipboard.copyChangedLinesContent.label', "Copy changed lines")
: localize('diff.clipboard.copyChangedLinesContent.single.label', "Copy changed line")),
undefined,
true,
async () => {
const originalText = this._originalTextModel.getValueInRange(diff.originalRange.toExclusiveRange());
const originalText = this._originalTextModel.getValueInRange(_diff.originalRange.toExclusiveRange());
await this._clipboardService.writeText(originalText);
}
));

let currentLineNumberOffset = 0;
let copyLineAction: Action | undefined = undefined;
if (diff.originalRange.length > 1) {
if (_diff.originalRange.length > 1) {
copyLineAction = new Action(
'diff.clipboard.copyDeletedLineContent',
isDeletion
? localize('diff.clipboard.copyDeletedLineContent.label', "Copy deleted line ({0})", diff.originalRange.startLineNumber)
: localize('diff.clipboard.copyChangedLineContent.label', "Copy changed line ({0})", diff.originalRange.startLineNumber),
? localize('diff.clipboard.copyDeletedLineContent.label', "Copy deleted line ({0})", _diff.originalRange.startLineNumber)
: localize('diff.clipboard.copyChangedLineContent.label', "Copy changed line ({0})", _diff.originalRange.startLineNumber),
undefined,
true,
async () => {
let lineContent = this._originalTextModel.getLineContent(diff.originalRange.startLineNumber + currentLineNumberOffset);
let lineContent = this._originalTextModel.getLineContent(_diff.originalRange.startLineNumber + currentLineNumberOffset);
if (lineContent === '') {
// empty line -> new line
const eof = this._originalTextModel.getEndOfLineSequence();
Expand All @@ -109,21 +111,18 @@ export class InlineDiffDeletedCodeMargin extends Disposable {
actions.push(copyLineAction);
}

const readOnly = editor.getOption(EditorOption.readOnly);
const readOnly = _modifiedEditor.getOption(EditorOption.readOnly);
if (!readOnly) {
actions.push(new Action('diff.inline.revertChange', localize('diff.inline.revertChange.label', "Revert this change"), undefined, true, async () => {
const originalText = this._originalTextModel.getValueInRange(this.diff.originalRange.toExclusiveRange());
editor.executeEdits('diffEditor', [
{ range: this.diff.modifiedRange.toExclusiveRange(), text: originalText }
]);
this._editor.revert(this._diff);
}));
}

const useShadowDOM = editor.getOption(EditorOption.useShadowDOM) && !isIOS; // Do not use shadow dom on IOS #122035
const useShadowDOM = _modifiedEditor.getOption(EditorOption.useShadowDOM) && !isIOS; // Do not use shadow dom on IOS #122035

const showContextMenu = (x: number, y: number) => {
this._contextMenuService.showContextMenu({
domForShadowRoot: useShadowDOM ? editor.getDomNode() ?? undefined : undefined,
domForShadowRoot: useShadowDOM ? _modifiedEditor.getDomNode() ?? undefined : undefined,
getAnchor: () => {
return {
x,
Expand All @@ -134,8 +133,8 @@ export class InlineDiffDeletedCodeMargin extends Disposable {
if (copyLineAction) {
copyLineAction.label =
isDeletion
? localize('diff.clipboard.copyDeletedLineContent.label', "Copy deleted line ({0})", diff.originalRange.startLineNumber + currentLineNumberOffset)
: localize('diff.clipboard.copyChangedLineContent.label', "Copy changed line ({0})", diff.originalRange.startLineNumber + currentLineNumberOffset);
? localize('diff.clipboard.copyDeletedLineContent.label', "Copy deleted line ({0})", _diff.originalRange.startLineNumber + currentLineNumberOffset)
: localize('diff.clipboard.copyChangedLineContent.label', "Copy changed line ({0})", _diff.originalRange.startLineNumber + currentLineNumberOffset);
}
return actions;
},
Expand All @@ -150,7 +149,7 @@ export class InlineDiffDeletedCodeMargin extends Disposable {
showContextMenu(e.posx, top + height + pad);
}));

this._register(editor.onMouseMove((e: IEditorMouseEvent) => {
this._register(_modifiedEditor.onMouseMove((e: IEditorMouseEvent) => {
if ((e.target.type === MouseTargetType.CONTENT_VIEW_ZONE || e.target.type === MouseTargetType.GUTTER_VIEW_ZONE) && e.target.detail.viewZoneId === this._getViewZoneId()) {
currentLineNumberOffset = this._updateLightBulbPosition(this._marginDomNode, e.event.browserEvent.y, lineHeight);
this.visibility = true;
Expand All @@ -159,7 +158,7 @@ export class InlineDiffDeletedCodeMargin extends Disposable {
}
}));

this._register(editor.onMouseDown((e: IEditorMouseEvent) => {
this._register(_modifiedEditor.onMouseDown((e: IEditorMouseEvent) => {
if (!e.event.rightButton) {
return;
}
Expand All @@ -182,10 +181,10 @@ export class InlineDiffDeletedCodeMargin extends Disposable {
const lineNumberOffset = Math.floor(offset / lineHeight);
const newTop = lineNumberOffset * lineHeight;
this._diffActions.style.top = `${newTop}px`;
if (this.viewLineCounts) {
if (this._viewLineCounts) {
let acc = 0;
for (let i = 0; i < this.viewLineCounts.length; i++) {
acc += this.viewLineCounts[i];
for (let i = 0; i < this._viewLineCounts.length; i++) {
acc += this._viewLineCounts[i];
if (lineNumberOffset < acc) {
return i;
}
Expand Down
19 changes: 18 additions & 1 deletion src/vs/editor/browser/widget/diffEditorWidget2/lineAlignment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { $ } from 'vs/base/browser/dom';
import { ArrayQueue } from 'vs/base/common/arrays';
import { Codicon } from 'vs/base/common/codicons';
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';
Expand All @@ -14,6 +16,7 @@ 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 { DiffEditorWidget2 } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2';
import { DiffMapping, DiffModel } from 'vs/editor/browser/widget/diffEditorWidget2/diffModel';
import { InlineDiffDeletedCodeMargin } from 'vs/editor/browser/widget/diffEditorWidget2/inlineDiffDeletedCodeMargin';
import { LineSource, RenderOptions, renderLines } from 'vs/editor/browser/widget/diffEditorWidget2/renderLines';
Expand Down Expand Up @@ -49,6 +52,7 @@ export class ViewZoneManager extends Disposable {
private readonly _modifiedEditor: CodeEditorWidget,
private readonly _diffModel: IObservable<DiffModel | undefined>,
private readonly _renderSideBySide: IObservable<boolean>,
private readonly _diffEditorWidget: DiffEditorWidget2,
@IClipboardService private readonly _clipboardService: IClipboardService,
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
) {
Expand Down Expand Up @@ -190,10 +194,11 @@ export class ViewZoneManager extends Disposable {
marginDomNode,
this._modifiedEditor,
a.diff,
this._diffEditorWidget,
result.viewLineCounts,
this._originalEditor.getModel()!,
this._contextMenuService,
this._clipboardService
this._clipboardService,
)
);

Expand Down Expand Up @@ -245,10 +250,22 @@ export class ViewZoneManager extends Disposable {
continue;
}

function createViewZoneMarginArrow(): HTMLElement {
const arrow = document.createElement('div');
arrow.className = 'arrow-revert-change ' + ThemeIcon.asClassName(Codicon.arrowRight);
return $('div', {}, arrow);
}

let marginDomNode: HTMLElement | undefined = undefined;
if (a.diff && a.diff.modifiedRange.isEmpty) {
marginDomNode = createViewZoneMarginArrow();
}

modViewZones.push({
afterLineNumber: a.modifiedRange.endLineNumberExclusive - 1,
domNode: createFakeLinesDiv(),
heightInPx: -delta,
marginDomNode,
});
}
}
Expand Down

0 comments on commit 4dd7a52

Please sign in to comment.