mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-13 19:48:37 -05:00
2845 lines
108 KiB
TypeScript
2845 lines
108 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import 'vs/css!./media/diffEditor';
|
|
import * as nls from 'vs/nls';
|
|
import * as dom from 'vs/base/browser/dom';
|
|
import * as assert from 'vs/base/common/assert';
|
|
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
|
|
import { ISashEvent, IVerticalSashLayoutProvider, Sash, SashState, Orientation } from 'vs/base/browser/ui/sash/sash';
|
|
import { RunOnceScheduler } from 'vs/base/common/async';
|
|
import { Color } from 'vs/base/common/color';
|
|
import { Emitter, Event } from 'vs/base/common/event';
|
|
import { Disposable } from 'vs/base/common/lifecycle';
|
|
import { URI } from 'vs/base/common/uri';
|
|
import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo';
|
|
import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll';
|
|
import * as editorBrowser from 'vs/editor/browser/editorBrowser';
|
|
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
|
import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
|
|
import { DiffReview } from 'vs/editor/browser/widget/diffReview';
|
|
import { IDiffEditorOptions, EditorLayoutInfo, EditorOption, EditorOptions, EditorFontLigatures, stringSet as validateStringSetOption, boolean as validateBooleanOption, ValidDiffEditorBaseOptions, clampedInt } from 'vs/editor/common/config/editorOptions';
|
|
import { IPosition, Position } from 'vs/editor/common/core/position';
|
|
import { IRange, Range } from 'vs/editor/common/core/range';
|
|
import { ISelection, Selection } from 'vs/editor/common/core/selection';
|
|
import { IStringBuilder, createStringBuilder } from 'vs/editor/common/core/stringBuilder';
|
|
import * as editorCommon from 'vs/editor/common/editorCommon';
|
|
import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model';
|
|
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
|
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
|
|
import { OverviewRulerZone } from 'vs/editor/common/viewModel/overviewZoneManager';
|
|
import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations';
|
|
import { RenderLineInput, renderViewLine } from 'vs/editor/common/viewLayout/viewLineRenderer';
|
|
import { IEditorWhitespace, InlineDecoration, InlineDecorationType, IViewModel, ViewLineRenderingData } from 'vs/editor/common/viewModel';
|
|
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
|
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
|
import { INotificationService } from 'vs/platform/notification/common/notification';
|
|
import { defaultInsertColor, defaultRemoveColor, diffBorder, diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, scrollbarShadow, scrollbarSliderBackground, scrollbarSliderHoverBackground, scrollbarSliderActiveBackground, diffDiagonalFill, diffInsertedLineGutter, diffRemovedLineGutter, diffInsertedLine, diffRemovedLine, diffOverviewRulerInserted, diffOverviewRulerRemoved } from 'vs/platform/theme/common/colorRegistry';
|
|
import { IColorTheme, IThemeService, getThemeTypeSelector, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
|
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
|
import { IDiffLinesChange, InlineDiffMargin } from 'vs/editor/browser/widget/inlineDiffMargin';
|
|
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
|
import { Constants } from 'vs/base/common/uint';
|
|
import { EditorExtensionsRegistry, IDiffEditorContributionDescription } from 'vs/editor/browser/editorExtensions';
|
|
import { onUnexpectedError } from 'vs/base/common/errors';
|
|
import { IEditorProgressService, IProgressRunner } from 'vs/platform/progress/common/progress';
|
|
import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver';
|
|
import { Codicon } from 'vs/base/common/codicons';
|
|
import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor';
|
|
import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens';
|
|
import { FontInfo } from 'vs/editor/common/config/fontInfo';
|
|
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
|
import { ILineBreaksComputer } from 'vs/editor/common/modelLineProjectionData';
|
|
import { IChange, ICharChange, IDiffComputationResult, ILineChange } from 'vs/editor/common/diff/diffComputer';
|
|
import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration';
|
|
import { IDimension } from 'vs/editor/common/core/dimension';
|
|
import { isHighContrast } from 'vs/platform/theme/common/theme';
|
|
|
|
export interface IDiffCodeEditorWidgetOptions {
|
|
originalEditor?: ICodeEditorWidgetOptions;
|
|
modifiedEditor?: ICodeEditorWidgetOptions;
|
|
}
|
|
|
|
interface IEditorDiffDecorations {
|
|
decorations: IModelDeltaDecoration[];
|
|
overviewZones: OverviewRulerZone[];
|
|
}
|
|
|
|
interface IEditorDiffDecorationsWithZones extends IEditorDiffDecorations {
|
|
zones: IMyViewZone[];
|
|
}
|
|
|
|
interface IEditorsDiffDecorationsWithZones {
|
|
original: IEditorDiffDecorationsWithZones;
|
|
modified: IEditorDiffDecorationsWithZones;
|
|
}
|
|
|
|
interface IEditorsZones {
|
|
original: IMyViewZone[];
|
|
modified: IMyViewZone[];
|
|
}
|
|
|
|
class VisualEditorState {
|
|
private _zones: string[];
|
|
private _inlineDiffMargins: InlineDiffMargin[];
|
|
private _zonesMap: { [zoneId: string]: boolean };
|
|
private _decorations: string[];
|
|
|
|
constructor(
|
|
private _contextMenuService: IContextMenuService,
|
|
private _clipboardService: IClipboardService
|
|
) {
|
|
this._zones = [];
|
|
this._inlineDiffMargins = [];
|
|
this._zonesMap = {};
|
|
this._decorations = [];
|
|
}
|
|
|
|
public getForeignViewZones(allViewZones: IEditorWhitespace[]): IEditorWhitespace[] {
|
|
return allViewZones.filter((z) => !this._zonesMap[String(z.id)]);
|
|
}
|
|
|
|
public clean(editor: CodeEditorWidget): void {
|
|
// (1) View zones
|
|
if (this._zones.length > 0) {
|
|
editor.changeViewZones((viewChangeAccessor: editorBrowser.IViewZoneChangeAccessor) => {
|
|
for (const zoneId of this._zones) {
|
|
viewChangeAccessor.removeZone(zoneId);
|
|
}
|
|
});
|
|
}
|
|
this._zones = [];
|
|
this._zonesMap = {};
|
|
|
|
// (2) Model decorations
|
|
editor.changeDecorations((changeAccessor) => {
|
|
this._decorations = changeAccessor.deltaDecorations(this._decorations, []);
|
|
});
|
|
}
|
|
|
|
public apply(editor: CodeEditorWidget, overviewRuler: editorBrowser.IOverviewRuler | null, newDecorations: IEditorDiffDecorationsWithZones, restoreScrollState: boolean): void {
|
|
|
|
const scrollState = restoreScrollState ? StableEditorScrollState.capture(editor) : null;
|
|
|
|
// view zones
|
|
editor.changeViewZones((viewChangeAccessor: editorBrowser.IViewZoneChangeAccessor) => {
|
|
for (const zoneId of this._zones) {
|
|
viewChangeAccessor.removeZone(zoneId);
|
|
}
|
|
for (const inlineDiffMargin of this._inlineDiffMargins) {
|
|
inlineDiffMargin.dispose();
|
|
}
|
|
this._zones = [];
|
|
this._zonesMap = {};
|
|
this._inlineDiffMargins = [];
|
|
for (let i = 0, length = newDecorations.zones.length; i < length; i++) {
|
|
const viewZone = <editorBrowser.IViewZone>newDecorations.zones[i];
|
|
viewZone.suppressMouseDown = true;
|
|
const zoneId = viewChangeAccessor.addZone(viewZone);
|
|
this._zones.push(zoneId);
|
|
this._zonesMap[String(zoneId)] = true;
|
|
|
|
if (newDecorations.zones[i].diff && viewZone.marginDomNode) {
|
|
viewZone.suppressMouseDown = false;
|
|
if (newDecorations.zones[i].diff?.originalModel.getValueLength() !== 0) {
|
|
// do not contribute diff margin actions for newly created files
|
|
this._inlineDiffMargins.push(new InlineDiffMargin(zoneId, viewZone.marginDomNode, editor, newDecorations.zones[i].diff!, this._contextMenuService, this._clipboardService));
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
scrollState?.restore(editor);
|
|
|
|
// decorations
|
|
editor.changeDecorations((changeAccessor) => {
|
|
this._decorations = changeAccessor.deltaDecorations(this._decorations, newDecorations.decorations);
|
|
});
|
|
|
|
// overview ruler
|
|
overviewRuler?.setZones(newDecorations.overviewZones);
|
|
}
|
|
}
|
|
|
|
let DIFF_EDITOR_ID = 0;
|
|
|
|
|
|
const diffInsertIcon = registerIcon('diff-insert', Codicon.add, nls.localize('diffInsertIcon', 'Line decoration for inserts in the diff editor.'));
|
|
const diffRemoveIcon = registerIcon('diff-remove', Codicon.remove, nls.localize('diffRemoveIcon', 'Line decoration for removals in the diff editor.'));
|
|
const ttPolicy = window.trustedTypes?.createPolicy('diffEditorWidget', { createHTML: value => value });
|
|
|
|
export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffEditor {
|
|
|
|
private static readonly ONE_OVERVIEW_WIDTH = 15;
|
|
public static readonly ENTIRE_DIFF_OVERVIEW_WIDTH = 30;
|
|
private static readonly UPDATE_DIFF_DECORATIONS_DELAY = 200; // ms
|
|
|
|
private readonly _onDidDispose: Emitter<void> = this._register(new Emitter<void>());
|
|
public readonly onDidDispose: Event<void> = this._onDidDispose.event;
|
|
|
|
private readonly _onDidUpdateDiff: Emitter<void> = this._register(new Emitter<void>());
|
|
public readonly onDidUpdateDiff: Event<void> = this._onDidUpdateDiff.event;
|
|
|
|
private readonly _onDidContentSizeChange: Emitter<editorCommon.IContentSizeChangedEvent> = this._register(new Emitter<editorCommon.IContentSizeChangedEvent>());
|
|
public readonly onDidContentSizeChange: Event<editorCommon.IContentSizeChangedEvent> = this._onDidContentSizeChange.event;
|
|
|
|
private readonly _id: number;
|
|
private _state: editorBrowser.DiffEditorState;
|
|
private _updatingDiffProgress: IProgressRunner | null;
|
|
|
|
private readonly _domElement: HTMLElement;
|
|
protected readonly _containerDomElement: HTMLElement;
|
|
private readonly _overviewDomElement: HTMLElement;
|
|
private readonly _overviewViewportDomElement: FastDomNode<HTMLElement>;
|
|
|
|
private readonly _elementSizeObserver: ElementSizeObserver;
|
|
|
|
private readonly _originalEditor: CodeEditorWidget;
|
|
private readonly _originalDomNode: HTMLElement;
|
|
private readonly _originalEditorState: VisualEditorState;
|
|
private _originalOverviewRuler: editorBrowser.IOverviewRuler | null;
|
|
|
|
private readonly _modifiedEditor: CodeEditorWidget;
|
|
private readonly _modifiedDomNode: HTMLElement;
|
|
private readonly _modifiedEditorState: VisualEditorState;
|
|
private _modifiedOverviewRuler: editorBrowser.IOverviewRuler | null;
|
|
|
|
private _currentlyChangingViewZones: boolean;
|
|
private _beginUpdateDecorationsTimeout: number;
|
|
private _diffComputationToken: number;
|
|
private _diffComputationResult: IDiffComputationResult | null;
|
|
|
|
private _isVisible: boolean;
|
|
private _isHandlingScrollEvent: boolean;
|
|
|
|
private _options: ValidDiffEditorBaseOptions;
|
|
|
|
private _strategy!: DiffEditorWidgetStyle;
|
|
|
|
private readonly _updateDecorationsRunner: RunOnceScheduler;
|
|
|
|
private readonly _editorWorkerService: IEditorWorkerService;
|
|
private readonly _contextKeyService: IContextKeyService;
|
|
private readonly _instantiationService: IInstantiationService;
|
|
private readonly _codeEditorService: ICodeEditorService;
|
|
private readonly _themeService: IThemeService;
|
|
private readonly _notificationService: INotificationService;
|
|
|
|
private readonly _reviewPane: DiffReview;
|
|
|
|
constructor(
|
|
domElement: HTMLElement,
|
|
options: Readonly<editorBrowser.IDiffEditorConstructionOptions>,
|
|
codeEditorWidgetOptions: IDiffCodeEditorWidgetOptions,
|
|
@IClipboardService clipboardService: IClipboardService,
|
|
@IEditorWorkerService editorWorkerService: IEditorWorkerService,
|
|
@IContextKeyService contextKeyService: IContextKeyService,
|
|
@IInstantiationService instantiationService: IInstantiationService,
|
|
@ICodeEditorService codeEditorService: ICodeEditorService,
|
|
@IThemeService themeService: IThemeService,
|
|
@INotificationService notificationService: INotificationService,
|
|
@IContextMenuService contextMenuService: IContextMenuService,
|
|
@IEditorProgressService private readonly _editorProgressService: IEditorProgressService
|
|
) {
|
|
super();
|
|
|
|
this._editorWorkerService = editorWorkerService;
|
|
this._codeEditorService = codeEditorService;
|
|
this._contextKeyService = this._register(contextKeyService.createScoped(domElement));
|
|
this._instantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this._contextKeyService]));
|
|
this._contextKeyService.createKey('isInDiffEditor', true);
|
|
this._themeService = themeService;
|
|
this._notificationService = notificationService;
|
|
|
|
this._id = (++DIFF_EDITOR_ID);
|
|
this._state = editorBrowser.DiffEditorState.Idle;
|
|
this._updatingDiffProgress = null;
|
|
|
|
this._domElement = domElement;
|
|
options = options || {};
|
|
|
|
let diffOptions: any = {
|
|
enableSplitViewResizing: true,
|
|
renderSideBySide: true,
|
|
renderMarginRevertIcon: true,
|
|
maxComputationTime: 5000,
|
|
maxFileSize: 50,
|
|
ignoreTrimWhitespace: true,
|
|
renderIndicators: true,
|
|
originalEditable: false,
|
|
diffCodeLens: false,
|
|
renderOverviewRuler: true,
|
|
diffWordWrap: 'inherit'
|
|
};
|
|
this._options = validateDiffEditorOptions(options, diffOptions);
|
|
|
|
if (typeof options.isInEmbeddedEditor !== 'undefined') {
|
|
this._contextKeyService.createKey('isInEmbeddedDiffEditor', options.isInEmbeddedEditor);
|
|
} else {
|
|
this._contextKeyService.createKey('isInEmbeddedDiffEditor', false);
|
|
}
|
|
|
|
this._updateDecorationsRunner = this._register(new RunOnceScheduler(() => this._updateDecorations(), 0));
|
|
|
|
this._containerDomElement = document.createElement('div');
|
|
this._containerDomElement.className = DiffEditorWidget._getClassName(this._themeService.getColorTheme(), this._options.renderSideBySide);
|
|
this._containerDomElement.style.position = 'relative';
|
|
this._containerDomElement.style.height = '100%';
|
|
this._domElement.appendChild(this._containerDomElement);
|
|
|
|
this._overviewViewportDomElement = createFastDomNode(document.createElement('div'));
|
|
this._overviewViewportDomElement.setClassName('diffViewport');
|
|
this._overviewViewportDomElement.setPosition('absolute');
|
|
|
|
this._overviewDomElement = document.createElement('div');
|
|
this._overviewDomElement.className = 'diffOverview';
|
|
this._overviewDomElement.style.position = 'absolute';
|
|
|
|
this._overviewDomElement.appendChild(this._overviewViewportDomElement.domNode);
|
|
|
|
this._register(dom.addStandardDisposableListener(this._overviewDomElement, dom.EventType.POINTER_DOWN, (e) => {
|
|
this._modifiedEditor.delegateVerticalScrollbarPointerDown(e);
|
|
}));
|
|
if (this._options.renderOverviewRuler) {
|
|
this._containerDomElement.appendChild(this._overviewDomElement);
|
|
}
|
|
|
|
// Create left side
|
|
this._originalDomNode = document.createElement('div');
|
|
this._originalDomNode.className = 'editor original';
|
|
this._originalDomNode.style.position = 'absolute';
|
|
this._originalDomNode.style.height = '100%';
|
|
this._containerDomElement.appendChild(this._originalDomNode);
|
|
|
|
// Create right side
|
|
this._modifiedDomNode = document.createElement('div');
|
|
this._modifiedDomNode.className = 'editor modified';
|
|
this._modifiedDomNode.style.position = 'absolute';
|
|
this._modifiedDomNode.style.height = '100%';
|
|
this._containerDomElement.appendChild(this._modifiedDomNode);
|
|
|
|
this._beginUpdateDecorationsTimeout = -1;
|
|
this._currentlyChangingViewZones = false;
|
|
this._diffComputationToken = 0;
|
|
|
|
this._originalEditorState = new VisualEditorState(contextMenuService, clipboardService);
|
|
this._modifiedEditorState = new VisualEditorState(contextMenuService, clipboardService);
|
|
|
|
this._isVisible = true;
|
|
this._isHandlingScrollEvent = false;
|
|
|
|
this._elementSizeObserver = this._register(new ElementSizeObserver(this._containerDomElement, options.dimension));
|
|
this._register(this._elementSizeObserver.onDidChange(() => this._onDidContainerSizeChanged()));
|
|
if (options.automaticLayout) {
|
|
this._elementSizeObserver.startObserving();
|
|
}
|
|
|
|
this._diffComputationResult = null;
|
|
|
|
this._originalEditor = this._createLeftHandSideEditor(options, codeEditorWidgetOptions.originalEditor || {});
|
|
this._modifiedEditor = this._createRightHandSideEditor(options, codeEditorWidgetOptions.modifiedEditor || {});
|
|
|
|
this._originalOverviewRuler = null;
|
|
this._modifiedOverviewRuler = null;
|
|
|
|
this._reviewPane = instantiationService.createInstance(DiffReview, this);
|
|
this._containerDomElement.appendChild(this._reviewPane.domNode.domNode);
|
|
this._containerDomElement.appendChild(this._reviewPane.shadow.domNode);
|
|
this._containerDomElement.appendChild(this._reviewPane.actionBarContainer.domNode);
|
|
|
|
if (this._options.renderSideBySide) {
|
|
this._setStrategy(new DiffEditorWidgetSideBySide(this._createDataSource(), this._options.enableSplitViewResizing));
|
|
} else {
|
|
this._setStrategy(new DiffEditorWidgetInline(this._createDataSource(), this._options.enableSplitViewResizing));
|
|
}
|
|
|
|
this._register(themeService.onDidColorThemeChange(t => {
|
|
if (this._strategy && this._strategy.applyColors(t)) {
|
|
this._updateDecorationsRunner.schedule();
|
|
}
|
|
this._containerDomElement.className = DiffEditorWidget._getClassName(this._themeService.getColorTheme(), this._options.renderSideBySide);
|
|
}));
|
|
|
|
const contributions: IDiffEditorContributionDescription[] = EditorExtensionsRegistry.getDiffEditorContributions();
|
|
for (const desc of contributions) {
|
|
try {
|
|
this._register(instantiationService.createInstance(desc.ctor, this));
|
|
} catch (err) {
|
|
onUnexpectedError(err);
|
|
}
|
|
}
|
|
|
|
this._codeEditorService.addDiffEditor(this);
|
|
}
|
|
|
|
public get ignoreTrimWhitespace(): boolean {
|
|
return this._options.ignoreTrimWhitespace;
|
|
}
|
|
|
|
public get maxComputationTime(): number {
|
|
return this._options.maxComputationTime;
|
|
}
|
|
|
|
public get renderSideBySide(): boolean {
|
|
return this._options.renderSideBySide;
|
|
}
|
|
|
|
public getContentHeight(): number {
|
|
return this._modifiedEditor.getContentHeight();
|
|
}
|
|
|
|
public getViewWidth(): number {
|
|
return this._elementSizeObserver.getWidth();
|
|
}
|
|
|
|
private _setState(newState: editorBrowser.DiffEditorState): void {
|
|
if (this._state === newState) {
|
|
return;
|
|
}
|
|
this._state = newState;
|
|
|
|
if (this._updatingDiffProgress) {
|
|
this._updatingDiffProgress.done();
|
|
this._updatingDiffProgress = null;
|
|
}
|
|
|
|
if (this._state === editorBrowser.DiffEditorState.ComputingDiff) {
|
|
this._updatingDiffProgress = this._editorProgressService.show(true, 1000);
|
|
}
|
|
}
|
|
|
|
public hasWidgetFocus(): boolean {
|
|
return dom.isAncestor(document.activeElement, this._domElement);
|
|
}
|
|
|
|
public diffReviewNext(): void {
|
|
this._reviewPane.next();
|
|
}
|
|
|
|
public diffReviewPrev(): void {
|
|
this._reviewPane.prev();
|
|
}
|
|
|
|
private static _getClassName(theme: IColorTheme, renderSideBySide: boolean): string {
|
|
let result = 'monaco-diff-editor monaco-editor-background ';
|
|
if (renderSideBySide) {
|
|
result += 'side-by-side ';
|
|
}
|
|
result += getThemeTypeSelector(theme.type);
|
|
return result;
|
|
}
|
|
|
|
private _disposeOverviewRulers(): void {
|
|
if (this._originalOverviewRuler) {
|
|
this._overviewDomElement.removeChild(this._originalOverviewRuler.getDomNode());
|
|
this._originalOverviewRuler.dispose();
|
|
this._originalOverviewRuler = null;
|
|
}
|
|
if (this._modifiedOverviewRuler) {
|
|
this._overviewDomElement.removeChild(this._modifiedOverviewRuler.getDomNode());
|
|
this._modifiedOverviewRuler.dispose();
|
|
this._modifiedOverviewRuler = null;
|
|
}
|
|
}
|
|
|
|
private _createOverviewRulers(): void {
|
|
if (!this._options.renderOverviewRuler) {
|
|
return;
|
|
}
|
|
|
|
assert.ok(!this._originalOverviewRuler && !this._modifiedOverviewRuler);
|
|
|
|
if (this._originalEditor.hasModel()) {
|
|
this._originalOverviewRuler = this._originalEditor.createOverviewRuler('original diffOverviewRuler')!;
|
|
this._overviewDomElement.appendChild(this._originalOverviewRuler.getDomNode());
|
|
}
|
|
if (this._modifiedEditor.hasModel()) {
|
|
this._modifiedOverviewRuler = this._modifiedEditor.createOverviewRuler('modified diffOverviewRuler')!;
|
|
this._overviewDomElement.appendChild(this._modifiedOverviewRuler.getDomNode());
|
|
}
|
|
|
|
this._layoutOverviewRulers();
|
|
}
|
|
|
|
private _createLeftHandSideEditor(options: Readonly<editorBrowser.IDiffEditorConstructionOptions>, codeEditorWidgetOptions: ICodeEditorWidgetOptions): CodeEditorWidget {
|
|
const editor = this._createInnerEditor(this._instantiationService, this._originalDomNode, this._adjustOptionsForLeftHandSide(options), codeEditorWidgetOptions);
|
|
|
|
this._register(editor.onDidScrollChange((e) => {
|
|
if (this._isHandlingScrollEvent) {
|
|
return;
|
|
}
|
|
if (!e.scrollTopChanged && !e.scrollLeftChanged && !e.scrollHeightChanged) {
|
|
return;
|
|
}
|
|
this._isHandlingScrollEvent = true;
|
|
this._modifiedEditor.setScrollPosition({
|
|
scrollLeft: e.scrollLeft,
|
|
scrollTop: e.scrollTop
|
|
});
|
|
this._isHandlingScrollEvent = false;
|
|
|
|
this._layoutOverviewViewport();
|
|
}));
|
|
|
|
this._register(editor.onDidChangeViewZones(() => {
|
|
this._onViewZonesChanged();
|
|
}));
|
|
|
|
this._register(editor.onDidChangeConfiguration((e) => {
|
|
if (!editor.getModel()) {
|
|
return;
|
|
}
|
|
if (e.hasChanged(EditorOption.fontInfo)) {
|
|
this._updateDecorationsRunner.schedule();
|
|
}
|
|
if (e.hasChanged(EditorOption.wrappingInfo)) {
|
|
this._updateDecorationsRunner.cancel();
|
|
this._updateDecorations();
|
|
}
|
|
}));
|
|
|
|
this._register(editor.onDidChangeHiddenAreas(() => {
|
|
this._updateDecorationsRunner.cancel();
|
|
this._updateDecorations();
|
|
}));
|
|
|
|
this._register(editor.onDidChangeModelContent(() => {
|
|
if (this._isVisible) {
|
|
this._beginUpdateDecorationsSoon();
|
|
}
|
|
}));
|
|
|
|
const isInDiffLeftEditorKey = this._contextKeyService.createKey<boolean>('isInDiffLeftEditor', editor.hasWidgetFocus());
|
|
this._register(editor.onDidFocusEditorWidget(() => isInDiffLeftEditorKey.set(true)));
|
|
this._register(editor.onDidBlurEditorWidget(() => isInDiffLeftEditorKey.set(false)));
|
|
|
|
this._register(editor.onDidContentSizeChange(e => {
|
|
const width = this._originalEditor.getContentWidth() + this._modifiedEditor.getContentWidth() + DiffEditorWidget.ONE_OVERVIEW_WIDTH;
|
|
const height = Math.max(this._modifiedEditor.getContentHeight(), this._originalEditor.getContentHeight());
|
|
|
|
this._onDidContentSizeChange.fire({
|
|
contentHeight: height,
|
|
contentWidth: width,
|
|
contentHeightChanged: e.contentHeightChanged,
|
|
contentWidthChanged: e.contentWidthChanged
|
|
});
|
|
}));
|
|
|
|
return editor;
|
|
}
|
|
|
|
private _createRightHandSideEditor(options: Readonly<editorBrowser.IDiffEditorConstructionOptions>, codeEditorWidgetOptions: ICodeEditorWidgetOptions): CodeEditorWidget {
|
|
const editor = this._createInnerEditor(this._instantiationService, this._modifiedDomNode, this._adjustOptionsForRightHandSide(options), codeEditorWidgetOptions);
|
|
|
|
this._register(editor.onDidScrollChange((e) => {
|
|
if (this._isHandlingScrollEvent) {
|
|
return;
|
|
}
|
|
if (!e.scrollTopChanged && !e.scrollLeftChanged && !e.scrollHeightChanged) {
|
|
return;
|
|
}
|
|
this._isHandlingScrollEvent = true;
|
|
this._originalEditor.setScrollPosition({
|
|
scrollLeft: e.scrollLeft,
|
|
scrollTop: e.scrollTop
|
|
});
|
|
this._isHandlingScrollEvent = false;
|
|
|
|
this._layoutOverviewViewport();
|
|
}));
|
|
|
|
this._register(editor.onDidChangeViewZones(() => {
|
|
this._onViewZonesChanged();
|
|
}));
|
|
|
|
this._register(editor.onDidChangeConfiguration((e) => {
|
|
if (!editor.getModel()) {
|
|
return;
|
|
}
|
|
if (e.hasChanged(EditorOption.fontInfo)) {
|
|
this._updateDecorationsRunner.schedule();
|
|
}
|
|
if (e.hasChanged(EditorOption.wrappingInfo)) {
|
|
this._updateDecorationsRunner.cancel();
|
|
this._updateDecorations();
|
|
}
|
|
}));
|
|
|
|
this._register(editor.onDidChangeHiddenAreas(() => {
|
|
this._updateDecorationsRunner.cancel();
|
|
this._updateDecorations();
|
|
}));
|
|
|
|
this._register(editor.onDidChangeModelContent(() => {
|
|
if (this._isVisible) {
|
|
this._beginUpdateDecorationsSoon();
|
|
}
|
|
}));
|
|
|
|
this._register(editor.onDidChangeModelOptions((e) => {
|
|
if (e.tabSize) {
|
|
this._updateDecorationsRunner.schedule();
|
|
}
|
|
}));
|
|
|
|
const isInDiffRightEditorKey = this._contextKeyService.createKey<boolean>('isInDiffRightEditor', editor.hasWidgetFocus());
|
|
this._register(editor.onDidFocusEditorWidget(() => isInDiffRightEditorKey.set(true)));
|
|
this._register(editor.onDidBlurEditorWidget(() => isInDiffRightEditorKey.set(false)));
|
|
|
|
this._register(editor.onDidContentSizeChange(e => {
|
|
const width = this._originalEditor.getContentWidth() + this._modifiedEditor.getContentWidth() + DiffEditorWidget.ONE_OVERVIEW_WIDTH;
|
|
const height = Math.max(this._modifiedEditor.getContentHeight(), this._originalEditor.getContentHeight());
|
|
|
|
this._onDidContentSizeChange.fire({
|
|
contentHeight: height,
|
|
contentWidth: width,
|
|
contentHeightChanged: e.contentHeightChanged,
|
|
contentWidthChanged: e.contentWidthChanged
|
|
});
|
|
}));
|
|
|
|
// Revert change when an arrow is clicked.
|
|
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 change = this._diffComputationResult?.changes.find(c => c.modifiedStartLineNumber === lineNumber - 1 || c.modifiedStartLineNumber === lineNumber);
|
|
if (change) {
|
|
this.revertChange(change);
|
|
}
|
|
event.event.stopPropagation();
|
|
this._updateDecorations();
|
|
return;
|
|
}
|
|
}));
|
|
|
|
return editor;
|
|
}
|
|
|
|
/**
|
|
* Reverts a change in the modified editor.
|
|
*/
|
|
revertChange(change: IChange) {
|
|
const editor = this._modifiedEditor;
|
|
const original = this._originalEditor.getModel();
|
|
const modified = this._modifiedEditor.getModel();
|
|
if (!original || !modified || !editor) {
|
|
return;
|
|
}
|
|
|
|
const originalRange = change.originalEndLineNumber > 0 ? new Range(change.originalStartLineNumber, 1, change.originalEndLineNumber, original.getLineMaxColumn(change.originalEndLineNumber)) : null;
|
|
const originalContent = originalRange ? original.getValueInRange(originalRange) : null;
|
|
|
|
const newRange = change.modifiedEndLineNumber > 0 ? new Range(change.modifiedStartLineNumber, 1, change.modifiedEndLineNumber, modified.getLineMaxColumn(change.modifiedEndLineNumber)) : null;
|
|
|
|
const eol = modified.getEOL();
|
|
|
|
if (change.originalEndLineNumber === 0 && newRange) {
|
|
// Insert change.
|
|
// To revert: delete the new content and a linebreak (if possible)
|
|
|
|
let range = newRange;
|
|
if (change.modifiedStartLineNumber > 1) {
|
|
// Try to include a linebreak from before.
|
|
range = newRange.setStartPosition(change.modifiedStartLineNumber - 1, modified.getLineMaxColumn(change.modifiedStartLineNumber - 1));
|
|
} else if (change.modifiedEndLineNumber < modified.getLineCount()) {
|
|
// Try to include the linebreak from after.
|
|
range = newRange.setEndPosition(change.modifiedEndLineNumber + 1, 1);
|
|
}
|
|
editor.executeEdits('diffEditor', [{
|
|
range,
|
|
text: '',
|
|
}]);
|
|
} else if (change.modifiedEndLineNumber === 0 && originalContent !== null) {
|
|
// Delete change.
|
|
// To revert: insert the old content and a linebreak.
|
|
|
|
const insertAt = change.modifiedStartLineNumber < modified.getLineCount() ? new Position(change.modifiedStartLineNumber + 1, 1) : new Position(change.modifiedStartLineNumber, modified.getLineMaxColumn(change.modifiedStartLineNumber));
|
|
editor.executeEdits('diffEditor', [{
|
|
range: Range.fromPositions(insertAt, insertAt),
|
|
text: change.modifiedStartLineNumber < modified.getLineCount() ? originalContent + eol : eol + originalContent,
|
|
}]);
|
|
} else if (newRange && originalContent !== null) {
|
|
// Modified change.
|
|
editor.executeEdits('diffEditor', [{
|
|
range: newRange,
|
|
text: originalContent,
|
|
}]);
|
|
}
|
|
}
|
|
|
|
protected _createInnerEditor(instantiationService: IInstantiationService, container: HTMLElement, options: Readonly<IEditorConstructionOptions>, editorWidgetOptions: ICodeEditorWidgetOptions): CodeEditorWidget {
|
|
return instantiationService.createInstance(CodeEditorWidget, container, options, editorWidgetOptions);
|
|
}
|
|
|
|
public override dispose(): void {
|
|
this._codeEditorService.removeDiffEditor(this);
|
|
|
|
if (this._beginUpdateDecorationsTimeout !== -1) {
|
|
window.clearTimeout(this._beginUpdateDecorationsTimeout);
|
|
this._beginUpdateDecorationsTimeout = -1;
|
|
}
|
|
|
|
this._cleanViewZonesAndDecorations();
|
|
|
|
if (this._originalOverviewRuler) {
|
|
this._overviewDomElement.removeChild(this._originalOverviewRuler.getDomNode());
|
|
this._originalOverviewRuler.dispose();
|
|
}
|
|
if (this._modifiedOverviewRuler) {
|
|
this._overviewDomElement.removeChild(this._modifiedOverviewRuler.getDomNode());
|
|
this._modifiedOverviewRuler.dispose();
|
|
}
|
|
this._overviewDomElement.removeChild(this._overviewViewportDomElement.domNode);
|
|
if (this._options.renderOverviewRuler) {
|
|
this._containerDomElement.removeChild(this._overviewDomElement);
|
|
}
|
|
|
|
this._containerDomElement.removeChild(this._originalDomNode);
|
|
this._originalEditor.dispose();
|
|
|
|
this._containerDomElement.removeChild(this._modifiedDomNode);
|
|
this._modifiedEditor.dispose();
|
|
|
|
this._strategy.dispose();
|
|
|
|
this._containerDomElement.removeChild(this._reviewPane.domNode.domNode);
|
|
this._containerDomElement.removeChild(this._reviewPane.shadow.domNode);
|
|
this._containerDomElement.removeChild(this._reviewPane.actionBarContainer.domNode);
|
|
this._reviewPane.dispose();
|
|
|
|
this._domElement.removeChild(this._containerDomElement);
|
|
|
|
this._onDidDispose.fire();
|
|
|
|
super.dispose();
|
|
}
|
|
|
|
//------------ begin IDiffEditor methods
|
|
|
|
public getId(): string {
|
|
return this.getEditorType() + ':' + this._id;
|
|
}
|
|
|
|
public getEditorType(): string {
|
|
return editorCommon.EditorType.IDiffEditor;
|
|
}
|
|
|
|
public getLineChanges(): ILineChange[] | null {
|
|
if (!this._diffComputationResult) {
|
|
return null;
|
|
}
|
|
return this._diffComputationResult.changes;
|
|
}
|
|
|
|
public getDiffComputationResult(): IDiffComputationResult | null {
|
|
return this._diffComputationResult;
|
|
}
|
|
|
|
public getOriginalEditor(): editorBrowser.ICodeEditor {
|
|
return this._originalEditor;
|
|
}
|
|
|
|
public getModifiedEditor(): editorBrowser.ICodeEditor {
|
|
return this._modifiedEditor;
|
|
}
|
|
|
|
public updateOptions(_newOptions: Readonly<IDiffEditorOptions>): void {
|
|
const newOptions = validateDiffEditorOptions(_newOptions, this._options);
|
|
const changed = changedDiffEditorOptions(this._options, newOptions);
|
|
this._options = newOptions;
|
|
|
|
const beginUpdateDecorations = (changed.ignoreTrimWhitespace || changed.renderIndicators || changed.renderMarginRevertIcon);
|
|
const beginUpdateDecorationsSoon = (this._isVisible && (changed.maxComputationTime || changed.maxFileSize));
|
|
|
|
if (beginUpdateDecorations) {
|
|
this._beginUpdateDecorations();
|
|
} else if (beginUpdateDecorationsSoon) {
|
|
this._beginUpdateDecorationsSoon();
|
|
}
|
|
|
|
this._modifiedEditor.updateOptions(this._adjustOptionsForRightHandSide(_newOptions));
|
|
this._originalEditor.updateOptions(this._adjustOptionsForLeftHandSide(_newOptions));
|
|
|
|
// enableSplitViewResizing
|
|
this._strategy.setEnableSplitViewResizing(this._options.enableSplitViewResizing);
|
|
|
|
// renderSideBySide
|
|
if (changed.renderSideBySide) {
|
|
if (this._options.renderSideBySide) {
|
|
this._setStrategy(new DiffEditorWidgetSideBySide(this._createDataSource(), this._options.enableSplitViewResizing));
|
|
} else {
|
|
this._setStrategy(new DiffEditorWidgetInline(this._createDataSource(), this._options.enableSplitViewResizing));
|
|
}
|
|
// Update class name
|
|
this._containerDomElement.className = DiffEditorWidget._getClassName(this._themeService.getColorTheme(), this._options.renderSideBySide);
|
|
}
|
|
|
|
// renderOverviewRuler
|
|
if (changed.renderOverviewRuler) {
|
|
if (this._options.renderOverviewRuler) {
|
|
this._containerDomElement.appendChild(this._overviewDomElement);
|
|
} else {
|
|
this._containerDomElement.removeChild(this._overviewDomElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
public getModel(): editorCommon.IDiffEditorModel {
|
|
return {
|
|
original: this._originalEditor.getModel()!,
|
|
modified: this._modifiedEditor.getModel()!
|
|
};
|
|
}
|
|
|
|
public setModel(model: editorCommon.IDiffEditorModel | null): void {
|
|
// Guard us against partial null model
|
|
if (model && (!model.original || !model.modified)) {
|
|
throw new Error(!model.original ? 'DiffEditorWidget.setModel: Original model is null' : 'DiffEditorWidget.setModel: Modified model is null');
|
|
}
|
|
|
|
// Remove all view zones & decorations
|
|
this._cleanViewZonesAndDecorations();
|
|
|
|
this._disposeOverviewRulers();
|
|
|
|
// Update code editor models
|
|
this._originalEditor.setModel(model ? model.original : null);
|
|
this._modifiedEditor.setModel(model ? model.modified : null);
|
|
this._updateDecorationsRunner.cancel();
|
|
|
|
// this.originalEditor.onDidChangeModelOptions
|
|
|
|
if (model) {
|
|
this._originalEditor.setScrollTop(0);
|
|
this._modifiedEditor.setScrollTop(0);
|
|
}
|
|
|
|
// Disable any diff computations that will come in
|
|
this._diffComputationResult = null;
|
|
this._diffComputationToken++;
|
|
this._setState(editorBrowser.DiffEditorState.Idle);
|
|
|
|
if (model) {
|
|
this._createOverviewRulers();
|
|
|
|
// Begin comparing
|
|
this._beginUpdateDecorations();
|
|
}
|
|
|
|
this._layoutOverviewViewport();
|
|
}
|
|
|
|
public getContainerDomNode(): HTMLElement {
|
|
return this._domElement;
|
|
}
|
|
|
|
public getVisibleColumnFromPosition(position: IPosition): number {
|
|
return this._modifiedEditor.getVisibleColumnFromPosition(position);
|
|
}
|
|
|
|
public getStatusbarColumn(position: IPosition): number {
|
|
return this._modifiedEditor.getStatusbarColumn(position);
|
|
}
|
|
|
|
public getPosition(): Position | null {
|
|
return this._modifiedEditor.getPosition();
|
|
}
|
|
|
|
public setPosition(position: IPosition, source: string = 'api'): void {
|
|
this._modifiedEditor.setPosition(position, source);
|
|
}
|
|
|
|
public revealLine(lineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
|
|
this._modifiedEditor.revealLine(lineNumber, scrollType);
|
|
}
|
|
|
|
public revealLineInCenter(lineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
|
|
this._modifiedEditor.revealLineInCenter(lineNumber, scrollType);
|
|
}
|
|
|
|
public revealLineInCenterIfOutsideViewport(lineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
|
|
this._modifiedEditor.revealLineInCenterIfOutsideViewport(lineNumber, scrollType);
|
|
}
|
|
|
|
public revealLineNearTop(lineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
|
|
this._modifiedEditor.revealLineNearTop(lineNumber, scrollType);
|
|
}
|
|
|
|
public revealPosition(position: IPosition, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
|
|
this._modifiedEditor.revealPosition(position, scrollType);
|
|
}
|
|
|
|
public revealPositionInCenter(position: IPosition, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
|
|
this._modifiedEditor.revealPositionInCenter(position, scrollType);
|
|
}
|
|
|
|
public revealPositionInCenterIfOutsideViewport(position: IPosition, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
|
|
this._modifiedEditor.revealPositionInCenterIfOutsideViewport(position, scrollType);
|
|
}
|
|
|
|
public revealPositionNearTop(position: IPosition, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
|
|
this._modifiedEditor.revealPositionNearTop(position, scrollType);
|
|
}
|
|
|
|
public getSelection(): Selection | null {
|
|
return this._modifiedEditor.getSelection();
|
|
}
|
|
|
|
public getSelections(): Selection[] | null {
|
|
return this._modifiedEditor.getSelections();
|
|
}
|
|
|
|
public setSelection(range: IRange, source?: string): void;
|
|
public setSelection(editorRange: Range, source?: string): void;
|
|
public setSelection(selection: ISelection, source?: string): void;
|
|
public setSelection(editorSelection: Selection, source?: string): void;
|
|
public setSelection(something: any, source: string = 'api'): void {
|
|
this._modifiedEditor.setSelection(something, source);
|
|
}
|
|
|
|
public setSelections(ranges: readonly ISelection[], source: string = 'api'): void {
|
|
this._modifiedEditor.setSelections(ranges, source);
|
|
}
|
|
|
|
public revealLines(startLineNumber: number, endLineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
|
|
this._modifiedEditor.revealLines(startLineNumber, endLineNumber, scrollType);
|
|
}
|
|
|
|
public revealLinesInCenter(startLineNumber: number, endLineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
|
|
this._modifiedEditor.revealLinesInCenter(startLineNumber, endLineNumber, scrollType);
|
|
}
|
|
|
|
public revealLinesInCenterIfOutsideViewport(startLineNumber: number, endLineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
|
|
this._modifiedEditor.revealLinesInCenterIfOutsideViewport(startLineNumber, endLineNumber, scrollType);
|
|
}
|
|
|
|
public revealLinesNearTop(startLineNumber: number, endLineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
|
|
this._modifiedEditor.revealLinesNearTop(startLineNumber, endLineNumber, scrollType);
|
|
}
|
|
|
|
public revealRange(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth, revealVerticalInCenter: boolean = false, revealHorizontal: boolean = true): void {
|
|
this._modifiedEditor.revealRange(range, scrollType, revealVerticalInCenter, revealHorizontal);
|
|
}
|
|
|
|
public revealRangeInCenter(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
|
|
this._modifiedEditor.revealRangeInCenter(range, scrollType);
|
|
}
|
|
|
|
public revealRangeInCenterIfOutsideViewport(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
|
|
this._modifiedEditor.revealRangeInCenterIfOutsideViewport(range, scrollType);
|
|
}
|
|
|
|
public revealRangeNearTop(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
|
|
this._modifiedEditor.revealRangeNearTop(range, scrollType);
|
|
}
|
|
|
|
public revealRangeNearTopIfOutsideViewport(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
|
|
this._modifiedEditor.revealRangeNearTopIfOutsideViewport(range, scrollType);
|
|
}
|
|
|
|
public revealRangeAtTop(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
|
|
this._modifiedEditor.revealRangeAtTop(range, scrollType);
|
|
}
|
|
|
|
public getSupportedActions(): editorCommon.IEditorAction[] {
|
|
return this._modifiedEditor.getSupportedActions();
|
|
}
|
|
|
|
public saveViewState(): editorCommon.IDiffEditorViewState {
|
|
const originalViewState = this._originalEditor.saveViewState();
|
|
const modifiedViewState = this._modifiedEditor.saveViewState();
|
|
return {
|
|
original: originalViewState,
|
|
modified: modifiedViewState
|
|
};
|
|
}
|
|
|
|
public restoreViewState(s: editorCommon.IDiffEditorViewState): void {
|
|
if (s && s.original && s.modified) {
|
|
const diffEditorState = <editorCommon.IDiffEditorViewState>s;
|
|
this._originalEditor.restoreViewState(diffEditorState.original);
|
|
this._modifiedEditor.restoreViewState(diffEditorState.modified);
|
|
}
|
|
}
|
|
|
|
public layout(dimension?: IDimension): void {
|
|
this._elementSizeObserver.observe(dimension);
|
|
}
|
|
|
|
public focus(): void {
|
|
this._modifiedEditor.focus();
|
|
}
|
|
|
|
public hasTextFocus(): boolean {
|
|
return this._originalEditor.hasTextFocus() || this._modifiedEditor.hasTextFocus();
|
|
}
|
|
|
|
public onVisible(): void {
|
|
this._isVisible = true;
|
|
this._originalEditor.onVisible();
|
|
this._modifiedEditor.onVisible();
|
|
// Begin comparing
|
|
this._beginUpdateDecorations();
|
|
}
|
|
|
|
public onHide(): void {
|
|
this._isVisible = false;
|
|
this._originalEditor.onHide();
|
|
this._modifiedEditor.onHide();
|
|
// Remove all view zones & decorations
|
|
this._cleanViewZonesAndDecorations();
|
|
}
|
|
|
|
public trigger(source: string | null | undefined, handlerId: string, payload: any): void {
|
|
this._modifiedEditor.trigger(source, handlerId, payload);
|
|
}
|
|
|
|
public createDecorationsCollection(decorations?: IModelDeltaDecoration[]): editorCommon.IEditorDecorationsCollection {
|
|
return this._modifiedEditor.createDecorationsCollection(decorations);
|
|
}
|
|
|
|
public changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => any): any {
|
|
return this._modifiedEditor.changeDecorations(callback);
|
|
}
|
|
|
|
//------------ end IDiffEditor methods
|
|
|
|
|
|
|
|
//------------ begin layouting methods
|
|
|
|
private _onDidContainerSizeChanged(): void {
|
|
this._doLayout();
|
|
}
|
|
|
|
private _getReviewHeight(): number {
|
|
return this._reviewPane.isVisible() ? this._elementSizeObserver.getHeight() : 0;
|
|
}
|
|
|
|
private _layoutOverviewRulers(): void {
|
|
if (!this._options.renderOverviewRuler) {
|
|
return;
|
|
}
|
|
|
|
if (!this._originalOverviewRuler || !this._modifiedOverviewRuler) {
|
|
return;
|
|
}
|
|
const height = this._elementSizeObserver.getHeight();
|
|
const reviewHeight = this._getReviewHeight();
|
|
|
|
const freeSpace = DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH - 2 * DiffEditorWidget.ONE_OVERVIEW_WIDTH;
|
|
const layoutInfo = this._modifiedEditor.getLayoutInfo();
|
|
if (layoutInfo) {
|
|
this._originalOverviewRuler.setLayout({
|
|
top: 0,
|
|
width: DiffEditorWidget.ONE_OVERVIEW_WIDTH,
|
|
right: freeSpace + DiffEditorWidget.ONE_OVERVIEW_WIDTH,
|
|
height: (height - reviewHeight)
|
|
});
|
|
this._modifiedOverviewRuler.setLayout({
|
|
top: 0,
|
|
right: 0,
|
|
width: DiffEditorWidget.ONE_OVERVIEW_WIDTH,
|
|
height: (height - reviewHeight)
|
|
});
|
|
}
|
|
}
|
|
|
|
//------------ end layouting methods
|
|
|
|
private _onViewZonesChanged(): void {
|
|
if (this._currentlyChangingViewZones) {
|
|
return;
|
|
}
|
|
this._updateDecorationsRunner.schedule();
|
|
}
|
|
|
|
private _beginUpdateDecorationsSoon(): void {
|
|
// Clear previous timeout if necessary
|
|
if (this._beginUpdateDecorationsTimeout !== -1) {
|
|
window.clearTimeout(this._beginUpdateDecorationsTimeout);
|
|
this._beginUpdateDecorationsTimeout = -1;
|
|
}
|
|
this._beginUpdateDecorationsTimeout = window.setTimeout(() => this._beginUpdateDecorations(), DiffEditorWidget.UPDATE_DIFF_DECORATIONS_DELAY);
|
|
}
|
|
|
|
private _lastOriginalWarning: URI | null = null;
|
|
private _lastModifiedWarning: URI | null = null;
|
|
|
|
private static _equals(a: URI | null, b: URI | null): boolean {
|
|
if (!a && !b) {
|
|
return true;
|
|
}
|
|
if (!a || !b) {
|
|
return false;
|
|
}
|
|
return (a.toString() === b.toString());
|
|
}
|
|
|
|
private _beginUpdateDecorations(): void {
|
|
this._beginUpdateDecorationsTimeout = -1;
|
|
const currentOriginalModel = this._originalEditor.getModel();
|
|
const currentModifiedModel = this._modifiedEditor.getModel();
|
|
if (!currentOriginalModel || !currentModifiedModel) {
|
|
return;
|
|
}
|
|
|
|
// Prevent old diff requests to come if a new request has been initiated
|
|
// The best method would be to call cancel on the Promise, but this is not
|
|
// yet supported, so using tokens for now.
|
|
this._diffComputationToken++;
|
|
const currentToken = this._diffComputationToken;
|
|
|
|
const diffLimit = this._options.maxFileSize * 1024 * 1024; // MB
|
|
const canSyncModelForDiff = (model: ITextModel): boolean => {
|
|
const bufferTextLength = model.getValueLength();
|
|
return (diffLimit === 0 || bufferTextLength <= diffLimit);
|
|
};
|
|
|
|
if (!canSyncModelForDiff(currentOriginalModel) || !canSyncModelForDiff(currentModifiedModel)) {
|
|
if (
|
|
!DiffEditorWidget._equals(currentOriginalModel.uri, this._lastOriginalWarning)
|
|
|| !DiffEditorWidget._equals(currentModifiedModel.uri, this._lastModifiedWarning)
|
|
) {
|
|
this._lastOriginalWarning = currentOriginalModel.uri;
|
|
this._lastModifiedWarning = currentModifiedModel.uri;
|
|
this._notificationService.warn(nls.localize("diff.tooLarge", "Cannot compare files because one file is too large."));
|
|
}
|
|
return;
|
|
}
|
|
|
|
this._setState(editorBrowser.DiffEditorState.ComputingDiff);
|
|
this._editorWorkerService.computeDiff(currentOriginalModel.uri, currentModifiedModel.uri, this._options.ignoreTrimWhitespace, this._options.maxComputationTime).then((result) => {
|
|
if (currentToken === this._diffComputationToken
|
|
&& currentOriginalModel === this._originalEditor.getModel()
|
|
&& currentModifiedModel === this._modifiedEditor.getModel()
|
|
) {
|
|
this._setState(editorBrowser.DiffEditorState.DiffComputed);
|
|
this._diffComputationResult = result;
|
|
this._updateDecorationsRunner.schedule();
|
|
this._onDidUpdateDiff.fire();
|
|
}
|
|
}, (error) => {
|
|
if (currentToken === this._diffComputationToken
|
|
&& currentOriginalModel === this._originalEditor.getModel()
|
|
&& currentModifiedModel === this._modifiedEditor.getModel()
|
|
) {
|
|
this._setState(editorBrowser.DiffEditorState.DiffComputed);
|
|
this._diffComputationResult = null;
|
|
this._updateDecorationsRunner.schedule();
|
|
}
|
|
});
|
|
}
|
|
|
|
private _cleanViewZonesAndDecorations(): void {
|
|
this._originalEditorState.clean(this._originalEditor);
|
|
this._modifiedEditorState.clean(this._modifiedEditor);
|
|
}
|
|
|
|
private _updateDecorations(): void {
|
|
if (!this._originalEditor.getModel() || !this._modifiedEditor.getModel()) {
|
|
return;
|
|
}
|
|
|
|
const lineChanges = (this._diffComputationResult ? this._diffComputationResult.changes : []);
|
|
|
|
const foreignOriginal = this._originalEditorState.getForeignViewZones(this._originalEditor.getWhitespaces());
|
|
const foreignModified = this._modifiedEditorState.getForeignViewZones(this._modifiedEditor.getWhitespaces());
|
|
|
|
// {{SQL CARBON EDIT}}
|
|
const diffDecorations = this._strategy.getEditorsDiffDecorations(lineChanges, this._options.ignoreTrimWhitespace, this._options.renderIndicators, this._options.renderMarginRevertIcon, foreignOriginal, foreignModified, this._options.reverse);
|
|
|
|
try {
|
|
this._currentlyChangingViewZones = true;
|
|
this._originalEditorState.apply(this._originalEditor, this._originalOverviewRuler, diffDecorations.original, false);
|
|
this._modifiedEditorState.apply(this._modifiedEditor, this._modifiedOverviewRuler, diffDecorations.modified, true);
|
|
} finally {
|
|
this._currentlyChangingViewZones = false;
|
|
}
|
|
}
|
|
|
|
private _adjustOptionsForSubEditor(options: Readonly<editorBrowser.IDiffEditorConstructionOptions>): IEditorConstructionOptions {
|
|
const clonedOptions = { ...options };
|
|
clonedOptions.inDiffEditor = true;
|
|
clonedOptions.automaticLayout = false;
|
|
// Clone scrollbar options before changing them
|
|
clonedOptions.scrollbar = { ...(clonedOptions.scrollbar || {}) };
|
|
clonedOptions.scrollbar.vertical = 'visible';
|
|
clonedOptions.folding = false;
|
|
clonedOptions.codeLens = this._options.diffCodeLens;
|
|
clonedOptions.fixedOverflowWidgets = true;
|
|
// clonedOptions.lineDecorationsWidth = '2ch';
|
|
// Clone minimap options before changing them
|
|
clonedOptions.minimap = { ...(clonedOptions.minimap || {}) };
|
|
clonedOptions.minimap.enabled = false;
|
|
return clonedOptions;
|
|
}
|
|
|
|
private _adjustOptionsForLeftHandSide(options: Readonly<editorBrowser.IDiffEditorConstructionOptions>): IEditorConstructionOptions {
|
|
const result = this._adjustOptionsForSubEditor(options);
|
|
if (!this._options.renderSideBySide) {
|
|
// never wrap hidden editor
|
|
result.wordWrapOverride1 = 'off';
|
|
result.wordWrapOverride2 = 'off';
|
|
} else {
|
|
result.wordWrapOverride1 = this._options.diffWordWrap;
|
|
}
|
|
if (options.originalAriaLabel) {
|
|
result.ariaLabel = options.originalAriaLabel;
|
|
}
|
|
result.readOnly = !this._options.originalEditable;
|
|
result.dropIntoEditor = { enabled: !result.readOnly };
|
|
result.extraEditorClassName = 'original-in-monaco-diff-editor';
|
|
return {
|
|
...result,
|
|
dimension: {
|
|
height: 0,
|
|
width: 0
|
|
}
|
|
};
|
|
}
|
|
|
|
private _adjustOptionsForRightHandSide(options: Readonly<editorBrowser.IDiffEditorConstructionOptions>): IEditorConstructionOptions {
|
|
const result = this._adjustOptionsForSubEditor(options);
|
|
if (options.modifiedAriaLabel) {
|
|
result.ariaLabel = options.modifiedAriaLabel;
|
|
}
|
|
|
|
result.wordWrapOverride1 = this._options.diffWordWrap;
|
|
result.revealHorizontalRightPadding = EditorOptions.revealHorizontalRightPadding.defaultValue + DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH;
|
|
result.scrollbar!.verticalHasArrows = false;
|
|
result.extraEditorClassName = 'modified-in-monaco-diff-editor';
|
|
return {
|
|
...result,
|
|
dimension: {
|
|
height: 0,
|
|
width: 0
|
|
}
|
|
};
|
|
}
|
|
|
|
public doLayout(): void {
|
|
this._elementSizeObserver.observe();
|
|
this._doLayout();
|
|
}
|
|
|
|
private _doLayout(): void {
|
|
const width = this._elementSizeObserver.getWidth();
|
|
const height = this._elementSizeObserver.getHeight();
|
|
const reviewHeight = this._getReviewHeight();
|
|
|
|
const splitPoint = this._strategy.layout();
|
|
|
|
this._originalDomNode.style.width = splitPoint + 'px';
|
|
this._originalDomNode.style.left = '0px';
|
|
|
|
this._modifiedDomNode.style.width = (width - splitPoint) + 'px';
|
|
this._modifiedDomNode.style.left = splitPoint + 'px';
|
|
|
|
this._overviewDomElement.style.top = '0px';
|
|
this._overviewDomElement.style.height = (height - reviewHeight) + 'px';
|
|
this._overviewDomElement.style.width = DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH + 'px';
|
|
this._overviewDomElement.style.left = (width - DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH) + 'px';
|
|
this._overviewViewportDomElement.setWidth(DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH);
|
|
this._overviewViewportDomElement.setHeight(30);
|
|
|
|
this._originalEditor.layout({ width: splitPoint, height: (height - reviewHeight) });
|
|
this._modifiedEditor.layout({ width: width - splitPoint - (this._options.renderOverviewRuler ? DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH : 0), height: (height - reviewHeight) });
|
|
|
|
if (this._originalOverviewRuler || this._modifiedOverviewRuler) {
|
|
this._layoutOverviewRulers();
|
|
}
|
|
|
|
this._reviewPane.layout(height - reviewHeight, width, reviewHeight);
|
|
|
|
this._layoutOverviewViewport();
|
|
}
|
|
|
|
private _layoutOverviewViewport(): void {
|
|
const layout = this._computeOverviewViewport();
|
|
if (!layout) {
|
|
this._overviewViewportDomElement.setTop(0);
|
|
this._overviewViewportDomElement.setHeight(0);
|
|
} else {
|
|
this._overviewViewportDomElement.setTop(layout.top);
|
|
this._overviewViewportDomElement.setHeight(layout.height);
|
|
}
|
|
}
|
|
|
|
private _computeOverviewViewport(): { height: number; top: number } | null {
|
|
const layoutInfo = this._modifiedEditor.getLayoutInfo();
|
|
if (!layoutInfo) {
|
|
return null;
|
|
}
|
|
|
|
const scrollTop = this._modifiedEditor.getScrollTop();
|
|
const scrollHeight = this._modifiedEditor.getScrollHeight();
|
|
|
|
const computedAvailableSize = Math.max(0, layoutInfo.height);
|
|
const computedRepresentableSize = Math.max(0, computedAvailableSize - 2 * 0);
|
|
const computedRatio = scrollHeight > 0 ? (computedRepresentableSize / scrollHeight) : 0;
|
|
|
|
const computedSliderSize = Math.max(0, Math.floor(layoutInfo.height * computedRatio));
|
|
const computedSliderPosition = Math.floor(scrollTop * computedRatio);
|
|
|
|
return {
|
|
height: computedSliderSize,
|
|
top: computedSliderPosition
|
|
};
|
|
}
|
|
|
|
private _createDataSource(): IDataSource {
|
|
return {
|
|
getWidth: () => {
|
|
return this._elementSizeObserver.getWidth();
|
|
},
|
|
|
|
getHeight: () => {
|
|
return (this._elementSizeObserver.getHeight() - this._getReviewHeight());
|
|
},
|
|
|
|
getOptions: () => {
|
|
return {
|
|
renderOverviewRuler: this._options.renderOverviewRuler
|
|
};
|
|
},
|
|
|
|
getContainerDomNode: () => {
|
|
return this._containerDomElement;
|
|
},
|
|
|
|
relayoutEditors: () => {
|
|
this._doLayout();
|
|
},
|
|
|
|
getOriginalEditor: () => {
|
|
return this._originalEditor;
|
|
},
|
|
|
|
getModifiedEditor: () => {
|
|
return this._modifiedEditor;
|
|
}
|
|
};
|
|
}
|
|
|
|
private _setStrategy(newStrategy: DiffEditorWidgetStyle): void {
|
|
if (this._strategy) {
|
|
this._strategy.dispose();
|
|
}
|
|
|
|
this._strategy = newStrategy;
|
|
newStrategy.applyColors(this._themeService.getColorTheme());
|
|
|
|
if (this._diffComputationResult) {
|
|
this._updateDecorations();
|
|
}
|
|
|
|
// Just do a layout, the strategy might need it
|
|
this._doLayout();
|
|
}
|
|
|
|
private _getLineChangeAtOrBeforeLineNumber(lineNumber: number, startLineNumberExtractor: (lineChange: ILineChange) => number): ILineChange | null {
|
|
const lineChanges = (this._diffComputationResult ? this._diffComputationResult.changes : []);
|
|
if (lineChanges.length === 0 || lineNumber < startLineNumberExtractor(lineChanges[0])) {
|
|
// There are no changes or `lineNumber` is before the first change
|
|
return null;
|
|
}
|
|
|
|
let min = 0;
|
|
let max = lineChanges.length - 1;
|
|
while (min < max) {
|
|
const mid = Math.floor((min + max) / 2);
|
|
const midStart = startLineNumberExtractor(lineChanges[mid]);
|
|
const midEnd = (mid + 1 <= max ? startLineNumberExtractor(lineChanges[mid + 1]) : Constants.MAX_SAFE_SMALL_INTEGER);
|
|
|
|
if (lineNumber < midStart) {
|
|
max = mid - 1;
|
|
} else if (lineNumber >= midEnd) {
|
|
min = mid + 1;
|
|
} else {
|
|
// HIT!
|
|
min = mid;
|
|
max = mid;
|
|
}
|
|
}
|
|
return lineChanges[min];
|
|
}
|
|
|
|
private _getEquivalentLineForOriginalLineNumber(lineNumber: number): number {
|
|
const lineChange = this._getLineChangeAtOrBeforeLineNumber(lineNumber, (lineChange) => lineChange.originalStartLineNumber);
|
|
|
|
if (!lineChange) {
|
|
return lineNumber;
|
|
}
|
|
|
|
const originalEquivalentLineNumber = lineChange.originalStartLineNumber + (lineChange.originalEndLineNumber > 0 ? -1 : 0);
|
|
const modifiedEquivalentLineNumber = lineChange.modifiedStartLineNumber + (lineChange.modifiedEndLineNumber > 0 ? -1 : 0);
|
|
const lineChangeOriginalLength = (lineChange.originalEndLineNumber > 0 ? (lineChange.originalEndLineNumber - lineChange.originalStartLineNumber + 1) : 0);
|
|
const lineChangeModifiedLength = (lineChange.modifiedEndLineNumber > 0 ? (lineChange.modifiedEndLineNumber - lineChange.modifiedStartLineNumber + 1) : 0);
|
|
|
|
|
|
const delta = lineNumber - originalEquivalentLineNumber;
|
|
|
|
if (delta <= lineChangeOriginalLength) {
|
|
return modifiedEquivalentLineNumber + Math.min(delta, lineChangeModifiedLength);
|
|
}
|
|
|
|
return modifiedEquivalentLineNumber + lineChangeModifiedLength - lineChangeOriginalLength + delta;
|
|
}
|
|
|
|
private _getEquivalentLineForModifiedLineNumber(lineNumber: number): number {
|
|
const lineChange = this._getLineChangeAtOrBeforeLineNumber(lineNumber, (lineChange) => lineChange.modifiedStartLineNumber);
|
|
|
|
if (!lineChange) {
|
|
return lineNumber;
|
|
}
|
|
|
|
const originalEquivalentLineNumber = lineChange.originalStartLineNumber + (lineChange.originalEndLineNumber > 0 ? -1 : 0);
|
|
const modifiedEquivalentLineNumber = lineChange.modifiedStartLineNumber + (lineChange.modifiedEndLineNumber > 0 ? -1 : 0);
|
|
const lineChangeOriginalLength = (lineChange.originalEndLineNumber > 0 ? (lineChange.originalEndLineNumber - lineChange.originalStartLineNumber + 1) : 0);
|
|
const lineChangeModifiedLength = (lineChange.modifiedEndLineNumber > 0 ? (lineChange.modifiedEndLineNumber - lineChange.modifiedStartLineNumber + 1) : 0);
|
|
|
|
|
|
const delta = lineNumber - modifiedEquivalentLineNumber;
|
|
|
|
if (delta <= lineChangeModifiedLength) {
|
|
return originalEquivalentLineNumber + Math.min(delta, lineChangeOriginalLength);
|
|
}
|
|
|
|
return originalEquivalentLineNumber + lineChangeOriginalLength - lineChangeModifiedLength + delta;
|
|
}
|
|
|
|
public getDiffLineInformationForOriginal(lineNumber: number): editorBrowser.IDiffLineInformation | null {
|
|
if (!this._diffComputationResult) {
|
|
// Cannot answer that which I don't know
|
|
return null;
|
|
}
|
|
return {
|
|
equivalentLineNumber: this._getEquivalentLineForOriginalLineNumber(lineNumber)
|
|
};
|
|
}
|
|
|
|
public getDiffLineInformationForModified(lineNumber: number): editorBrowser.IDiffLineInformation | null {
|
|
if (!this._diffComputationResult) {
|
|
// Cannot answer that which I don't know
|
|
return null;
|
|
}
|
|
return {
|
|
equivalentLineNumber: this._getEquivalentLineForModifiedLineNumber(lineNumber)
|
|
};
|
|
}
|
|
}
|
|
|
|
interface IDataSource {
|
|
getWidth(): number;
|
|
getHeight(): number;
|
|
getOptions(): { renderOverviewRuler: boolean };
|
|
getContainerDomNode(): HTMLElement;
|
|
relayoutEditors(): void;
|
|
|
|
getOriginalEditor(): CodeEditorWidget;
|
|
getModifiedEditor(): CodeEditorWidget;
|
|
}
|
|
|
|
abstract class DiffEditorWidgetStyle extends Disposable {
|
|
|
|
protected _dataSource: IDataSource;
|
|
protected _insertColor: Color | null;
|
|
protected _removeColor: Color | null;
|
|
|
|
constructor(dataSource: IDataSource) {
|
|
super();
|
|
this._dataSource = dataSource;
|
|
this._insertColor = null;
|
|
this._removeColor = null;
|
|
}
|
|
|
|
public applyColors(theme: IColorTheme): boolean {
|
|
const newInsertColor = theme.getColor(diffOverviewRulerInserted) || (theme.getColor(diffInserted) || defaultInsertColor).transparent(2);
|
|
const newRemoveColor = theme.getColor(diffOverviewRulerRemoved) || (theme.getColor(diffRemoved) || defaultRemoveColor).transparent(2);
|
|
const hasChanges = !newInsertColor.equals(this._insertColor) || !newRemoveColor.equals(this._removeColor);
|
|
this._insertColor = newInsertColor;
|
|
this._removeColor = newRemoveColor;
|
|
return hasChanges;
|
|
}
|
|
|
|
// {{SQL CARBON EDIT}} - add reverse parameter
|
|
public getEditorsDiffDecorations(lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, renderMarginRevertIcon: boolean, originalWhitespaces: IEditorWhitespace[], modifiedWhitespaces: IEditorWhitespace[], reverse?: boolean): IEditorsDiffDecorationsWithZones {
|
|
// Get view zones
|
|
modifiedWhitespaces = modifiedWhitespaces.sort((a, b) => {
|
|
return a.afterLineNumber - b.afterLineNumber;
|
|
});
|
|
originalWhitespaces = originalWhitespaces.sort((a, b) => {
|
|
return a.afterLineNumber - b.afterLineNumber;
|
|
});
|
|
const zones = this._getViewZones(lineChanges, originalWhitespaces, modifiedWhitespaces, renderIndicators);
|
|
|
|
// Get decorations & overview ruler zones
|
|
let originalDecorations = this._getOriginalEditorDecorations(zones, lineChanges, ignoreTrimWhitespace, renderIndicators);
|
|
const modifiedDecorations = this._getModifiedEditorDecorations(zones, lineChanges, ignoreTrimWhitespace, renderIndicators, renderMarginRevertIcon);
|
|
|
|
// {{SQL CARBON EDIT}} - reverse decorations
|
|
if (reverse) {
|
|
reverseDecorations(originalDecorations.decorations);
|
|
reverseDecorations(modifiedDecorations.decorations);
|
|
|
|
originalDecorations.overviewZones = setOverviewZonesColor(originalDecorations.overviewZones, String(this._insertColor));
|
|
modifiedDecorations.overviewZones = setOverviewZonesColor(modifiedDecorations.overviewZones, String(this._removeColor));
|
|
}
|
|
|
|
return {
|
|
original: {
|
|
decorations: originalDecorations.decorations,
|
|
overviewZones: originalDecorations.overviewZones,
|
|
zones: zones.original
|
|
},
|
|
modified: {
|
|
decorations: modifiedDecorations.decorations,
|
|
overviewZones: modifiedDecorations.overviewZones,
|
|
zones: zones.modified
|
|
}
|
|
};
|
|
}
|
|
|
|
protected abstract _getViewZones(lineChanges: ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[], renderIndicators: boolean): IEditorsZones;
|
|
protected abstract _getOriginalEditorDecorations(zones: IEditorsZones, lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean): IEditorDiffDecorations;
|
|
protected abstract _getModifiedEditorDecorations(zones: IEditorsZones, lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, renderMarginRevertIcon: boolean): IEditorDiffDecorations;
|
|
|
|
public abstract setEnableSplitViewResizing(enableSplitViewResizing: boolean): void;
|
|
public abstract layout(): number;
|
|
}
|
|
|
|
interface IMyViewZone {
|
|
shouldNotShrink?: boolean;
|
|
afterLineNumber: number;
|
|
afterColumn?: number;
|
|
heightInLines: number;
|
|
minWidthInPx?: number;
|
|
domNode: HTMLElement | null;
|
|
marginDomNode?: HTMLElement | null;
|
|
diff?: IDiffLinesChange;
|
|
}
|
|
|
|
class ForeignViewZonesIterator {
|
|
|
|
private _index: number;
|
|
private readonly _source: IEditorWhitespace[];
|
|
public current: IEditorWhitespace | null;
|
|
|
|
constructor(source: IEditorWhitespace[]) {
|
|
this._source = source;
|
|
this._index = -1;
|
|
this.current = null;
|
|
this.advance();
|
|
}
|
|
|
|
public advance(): void {
|
|
this._index++;
|
|
if (this._index < this._source.length) {
|
|
this.current = this._source[this._index];
|
|
} else {
|
|
this.current = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
abstract class ViewZonesComputer {
|
|
|
|
constructor(
|
|
private readonly _lineChanges: ILineChange[],
|
|
private readonly _originalForeignVZ: IEditorWhitespace[],
|
|
private readonly _modifiedForeignVZ: IEditorWhitespace[],
|
|
protected readonly _originalEditor: CodeEditorWidget,
|
|
protected readonly _modifiedEditor: CodeEditorWidget
|
|
) {
|
|
}
|
|
|
|
private static _getViewLineCount(editor: CodeEditorWidget, startLineNumber: number, endLineNumber: number): number {
|
|
const model = editor.getModel();
|
|
const viewModel = editor._getViewModel();
|
|
if (model && viewModel) {
|
|
const viewRange = getViewRange(model, viewModel, startLineNumber, endLineNumber);
|
|
return (viewRange.endLineNumber - viewRange.startLineNumber + 1);
|
|
}
|
|
|
|
return (endLineNumber - startLineNumber + 1);
|
|
}
|
|
|
|
public getViewZones(): IEditorsZones {
|
|
const originalLineHeight = this._originalEditor.getOption(EditorOption.lineHeight);
|
|
const modifiedLineHeight = this._modifiedEditor.getOption(EditorOption.lineHeight);
|
|
const originalHasWrapping = (this._originalEditor.getOption(EditorOption.wrappingInfo).wrappingColumn !== -1);
|
|
const modifiedHasWrapping = (this._modifiedEditor.getOption(EditorOption.wrappingInfo).wrappingColumn !== -1);
|
|
const hasWrapping = (originalHasWrapping || modifiedHasWrapping);
|
|
const originalModel = this._originalEditor.getModel()!;
|
|
const originalCoordinatesConverter = this._originalEditor._getViewModel()!.coordinatesConverter;
|
|
const modifiedCoordinatesConverter = this._modifiedEditor._getViewModel()!.coordinatesConverter;
|
|
|
|
const result: { original: IMyViewZone[]; modified: IMyViewZone[] } = {
|
|
original: [],
|
|
modified: []
|
|
};
|
|
|
|
let lineChangeModifiedLength: number = 0;
|
|
let lineChangeOriginalLength: number = 0;
|
|
let originalEquivalentLineNumber: number = 0;
|
|
let modifiedEquivalentLineNumber: number = 0;
|
|
let originalEndEquivalentLineNumber: number = 0;
|
|
let modifiedEndEquivalentLineNumber: number = 0;
|
|
|
|
const sortMyViewZones = (a: IMyViewZone, b: IMyViewZone) => {
|
|
return a.afterLineNumber - b.afterLineNumber;
|
|
};
|
|
|
|
const addAndCombineIfPossible = (destination: IMyViewZone[], item: IMyViewZone) => {
|
|
if (item.domNode === null && destination.length > 0) {
|
|
const lastItem = destination[destination.length - 1];
|
|
if (lastItem.afterLineNumber === item.afterLineNumber && lastItem.domNode === null) {
|
|
lastItem.heightInLines += item.heightInLines;
|
|
return;
|
|
}
|
|
}
|
|
destination.push(item);
|
|
};
|
|
|
|
const modifiedForeignVZ = new ForeignViewZonesIterator(this._modifiedForeignVZ);
|
|
const originalForeignVZ = new ForeignViewZonesIterator(this._originalForeignVZ);
|
|
|
|
let lastOriginalLineNumber = 1;
|
|
let lastModifiedLineNumber = 1;
|
|
|
|
// In order to include foreign view zones after the last line change, the for loop will iterate once more after the end of the `lineChanges` array
|
|
for (let i = 0, length = this._lineChanges.length; i <= length; i++) {
|
|
const lineChange = (i < length ? this._lineChanges[i] : null);
|
|
|
|
if (lineChange !== null) {
|
|
originalEquivalentLineNumber = lineChange.originalStartLineNumber + (lineChange.originalEndLineNumber > 0 ? -1 : 0);
|
|
modifiedEquivalentLineNumber = lineChange.modifiedStartLineNumber + (lineChange.modifiedEndLineNumber > 0 ? -1 : 0);
|
|
lineChangeOriginalLength = (lineChange.originalEndLineNumber > 0 ? ViewZonesComputer._getViewLineCount(this._originalEditor, lineChange.originalStartLineNumber, lineChange.originalEndLineNumber) : 0);
|
|
lineChangeModifiedLength = (lineChange.modifiedEndLineNumber > 0 ? ViewZonesComputer._getViewLineCount(this._modifiedEditor, lineChange.modifiedStartLineNumber, lineChange.modifiedEndLineNumber) : 0);
|
|
originalEndEquivalentLineNumber = Math.max(lineChange.originalStartLineNumber, lineChange.originalEndLineNumber);
|
|
modifiedEndEquivalentLineNumber = Math.max(lineChange.modifiedStartLineNumber, lineChange.modifiedEndLineNumber);
|
|
} else {
|
|
// Increase to very large value to get the producing tests of foreign view zones running
|
|
originalEquivalentLineNumber += 10000000 + lineChangeOriginalLength;
|
|
modifiedEquivalentLineNumber += 10000000 + lineChangeModifiedLength;
|
|
originalEndEquivalentLineNumber = originalEquivalentLineNumber;
|
|
modifiedEndEquivalentLineNumber = modifiedEquivalentLineNumber;
|
|
}
|
|
|
|
// Each step produces view zones, and after producing them, we try to cancel them out, to avoid empty-empty view zone cases
|
|
let stepOriginal: IMyViewZone[] = [];
|
|
let stepModified: IMyViewZone[] = [];
|
|
|
|
// ---------------------------- PRODUCE VIEW ZONES
|
|
|
|
// [PRODUCE] View zones due to line mapping differences (equal lines but wrapped differently)
|
|
if (hasWrapping) {
|
|
let count: number;
|
|
if (lineChange) {
|
|
if (lineChange.originalEndLineNumber > 0) {
|
|
count = lineChange.originalStartLineNumber - lastOriginalLineNumber;
|
|
} else {
|
|
count = lineChange.modifiedStartLineNumber - lastModifiedLineNumber;
|
|
}
|
|
} else {
|
|
// `lastOriginalLineNumber` has not been looked at yet
|
|
count = originalModel.getLineCount() - lastOriginalLineNumber + 1;
|
|
}
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
const originalLineNumber = lastOriginalLineNumber + i;
|
|
const modifiedLineNumber = lastModifiedLineNumber + i;
|
|
|
|
const originalViewLineCount = originalCoordinatesConverter.getModelLineViewLineCount(originalLineNumber);
|
|
const modifiedViewLineCount = modifiedCoordinatesConverter.getModelLineViewLineCount(modifiedLineNumber);
|
|
|
|
if (originalViewLineCount < modifiedViewLineCount) {
|
|
stepOriginal.push({
|
|
afterLineNumber: originalLineNumber,
|
|
heightInLines: modifiedViewLineCount - originalViewLineCount,
|
|
domNode: null,
|
|
marginDomNode: null
|
|
});
|
|
} else if (originalViewLineCount > modifiedViewLineCount) {
|
|
stepModified.push({
|
|
afterLineNumber: modifiedLineNumber,
|
|
heightInLines: originalViewLineCount - modifiedViewLineCount,
|
|
domNode: null,
|
|
marginDomNode: null
|
|
});
|
|
}
|
|
}
|
|
if (lineChange) {
|
|
lastOriginalLineNumber = (lineChange.originalEndLineNumber > 0 ? lineChange.originalEndLineNumber : lineChange.originalStartLineNumber) + 1;
|
|
lastModifiedLineNumber = (lineChange.modifiedEndLineNumber > 0 ? lineChange.modifiedEndLineNumber : lineChange.modifiedStartLineNumber) + 1;
|
|
}
|
|
}
|
|
|
|
// [PRODUCE] View zone(s) in original-side due to foreign view zone(s) in modified-side
|
|
while (modifiedForeignVZ.current && modifiedForeignVZ.current.afterLineNumber <= modifiedEndEquivalentLineNumber) {
|
|
let viewZoneLineNumber: number;
|
|
if (modifiedForeignVZ.current.afterLineNumber <= modifiedEquivalentLineNumber) {
|
|
viewZoneLineNumber = originalEquivalentLineNumber - modifiedEquivalentLineNumber + modifiedForeignVZ.current.afterLineNumber;
|
|
} else {
|
|
viewZoneLineNumber = originalEndEquivalentLineNumber;
|
|
}
|
|
|
|
let marginDomNode: HTMLDivElement | null = null;
|
|
if (lineChange && lineChange.modifiedStartLineNumber <= modifiedForeignVZ.current.afterLineNumber && modifiedForeignVZ.current.afterLineNumber <= lineChange.modifiedEndLineNumber) {
|
|
marginDomNode = this._createOriginalMarginDomNodeForModifiedForeignViewZoneInAddedRegion();
|
|
}
|
|
|
|
stepOriginal.push({
|
|
afterLineNumber: viewZoneLineNumber,
|
|
heightInLines: modifiedForeignVZ.current.height / modifiedLineHeight,
|
|
domNode: null,
|
|
marginDomNode: marginDomNode
|
|
});
|
|
modifiedForeignVZ.advance();
|
|
}
|
|
|
|
// [PRODUCE] View zone(s) in modified-side due to foreign view zone(s) in original-side
|
|
while (originalForeignVZ.current && originalForeignVZ.current.afterLineNumber <= originalEndEquivalentLineNumber) {
|
|
let viewZoneLineNumber: number;
|
|
if (originalForeignVZ.current.afterLineNumber <= originalEquivalentLineNumber) {
|
|
viewZoneLineNumber = modifiedEquivalentLineNumber - originalEquivalentLineNumber + originalForeignVZ.current.afterLineNumber;
|
|
} else {
|
|
viewZoneLineNumber = modifiedEndEquivalentLineNumber;
|
|
}
|
|
stepModified.push({
|
|
afterLineNumber: viewZoneLineNumber,
|
|
heightInLines: originalForeignVZ.current.height / originalLineHeight,
|
|
domNode: null
|
|
});
|
|
originalForeignVZ.advance();
|
|
}
|
|
|
|
if (lineChange !== null && isChangeOrInsert(lineChange)) {
|
|
const r = this._produceOriginalFromDiff(lineChange, lineChangeOriginalLength, lineChangeModifiedLength);
|
|
if (r) {
|
|
stepOriginal.push(r);
|
|
}
|
|
}
|
|
|
|
if (lineChange !== null && isChangeOrDelete(lineChange)) {
|
|
const r = this._produceModifiedFromDiff(lineChange, lineChangeOriginalLength, lineChangeModifiedLength);
|
|
if (r) {
|
|
stepModified.push(r);
|
|
}
|
|
}
|
|
|
|
// ---------------------------- END PRODUCE VIEW ZONES
|
|
|
|
|
|
// ---------------------------- EMIT MINIMAL VIEW ZONES
|
|
|
|
// [CANCEL & EMIT] Try to cancel view zones out
|
|
let stepOriginalIndex = 0;
|
|
let stepModifiedIndex = 0;
|
|
|
|
stepOriginal = stepOriginal.sort(sortMyViewZones);
|
|
stepModified = stepModified.sort(sortMyViewZones);
|
|
|
|
while (stepOriginalIndex < stepOriginal.length && stepModifiedIndex < stepModified.length) {
|
|
const original = stepOriginal[stepOriginalIndex];
|
|
const modified = stepModified[stepModifiedIndex];
|
|
|
|
const originalDelta = original.afterLineNumber - originalEquivalentLineNumber;
|
|
const modifiedDelta = modified.afterLineNumber - modifiedEquivalentLineNumber;
|
|
|
|
if (originalDelta < modifiedDelta) {
|
|
addAndCombineIfPossible(result.original, original);
|
|
stepOriginalIndex++;
|
|
} else if (modifiedDelta < originalDelta) {
|
|
addAndCombineIfPossible(result.modified, modified);
|
|
stepModifiedIndex++;
|
|
} else if (original.shouldNotShrink) {
|
|
addAndCombineIfPossible(result.original, original);
|
|
stepOriginalIndex++;
|
|
} else if (modified.shouldNotShrink) {
|
|
addAndCombineIfPossible(result.modified, modified);
|
|
stepModifiedIndex++;
|
|
} else {
|
|
if (original.heightInLines >= modified.heightInLines) {
|
|
// modified view zone gets removed
|
|
original.heightInLines -= modified.heightInLines;
|
|
stepModifiedIndex++;
|
|
} else {
|
|
// original view zone gets removed
|
|
modified.heightInLines -= original.heightInLines;
|
|
stepOriginalIndex++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// [EMIT] Remaining original view zones
|
|
while (stepOriginalIndex < stepOriginal.length) {
|
|
addAndCombineIfPossible(result.original, stepOriginal[stepOriginalIndex]);
|
|
stepOriginalIndex++;
|
|
}
|
|
|
|
// [EMIT] Remaining modified view zones
|
|
while (stepModifiedIndex < stepModified.length) {
|
|
addAndCombineIfPossible(result.modified, stepModified[stepModifiedIndex]);
|
|
stepModifiedIndex++;
|
|
}
|
|
|
|
// ---------------------------- END EMIT MINIMAL VIEW ZONES
|
|
}
|
|
|
|
return {
|
|
original: ViewZonesComputer._ensureDomNodes(result.original),
|
|
modified: ViewZonesComputer._ensureDomNodes(result.modified),
|
|
};
|
|
}
|
|
|
|
private static _ensureDomNodes(zones: IMyViewZone[]): IMyViewZone[] {
|
|
return zones.map((z) => {
|
|
if (!z.domNode) {
|
|
z.domNode = createFakeLinesDiv();
|
|
}
|
|
return z;
|
|
});
|
|
}
|
|
|
|
protected abstract _createOriginalMarginDomNodeForModifiedForeignViewZoneInAddedRegion(): HTMLDivElement | null;
|
|
|
|
protected abstract _produceOriginalFromDiff(lineChange: ILineChange, lineChangeOriginalLength: number, lineChangeModifiedLength: number): IMyViewZone | null;
|
|
|
|
protected abstract _produceModifiedFromDiff(lineChange: ILineChange, lineChangeOriginalLength: number, lineChangeModifiedLength: number): IMyViewZone | null;
|
|
}
|
|
|
|
function createDecoration(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, options: ModelDecorationOptions) {
|
|
return {
|
|
range: new Range(startLineNumber, startColumn, endLineNumber, endColumn),
|
|
options: options
|
|
};
|
|
}
|
|
|
|
const DECORATIONS = {
|
|
|
|
arrowRevertChange: ModelDecorationOptions.register({
|
|
description: 'diff-editor-arrow-revert-change',
|
|
glyphMarginClassName: 'arrow-revert-change ' + ThemeIcon.asClassName(Codicon.arrowRight),
|
|
}),
|
|
|
|
charDelete: ModelDecorationOptions.register({
|
|
description: 'diff-editor-char-delete',
|
|
className: 'char-delete'
|
|
}),
|
|
charDeleteWholeLine: ModelDecorationOptions.register({
|
|
description: 'diff-editor-char-delete-whole-line',
|
|
className: 'char-delete',
|
|
isWholeLine: true
|
|
}),
|
|
|
|
charInsert: ModelDecorationOptions.register({
|
|
description: 'diff-editor-char-insert',
|
|
className: 'char-insert'
|
|
}),
|
|
charInsertWholeLine: ModelDecorationOptions.register({
|
|
description: 'diff-editor-char-insert-whole-line',
|
|
className: 'char-insert',
|
|
isWholeLine: true
|
|
}),
|
|
|
|
lineInsert: ModelDecorationOptions.register({
|
|
description: 'diff-editor-line-insert',
|
|
className: 'line-insert',
|
|
marginClassName: 'gutter-insert',
|
|
isWholeLine: true
|
|
}),
|
|
lineInsertWithSign: ModelDecorationOptions.register({
|
|
description: 'diff-editor-line-insert-with-sign',
|
|
className: 'line-insert',
|
|
linesDecorationsClassName: 'insert-sign ' + ThemeIcon.asClassName(diffInsertIcon),
|
|
marginClassName: 'gutter-insert',
|
|
isWholeLine: true
|
|
}),
|
|
|
|
lineDelete: ModelDecorationOptions.register({
|
|
description: 'diff-editor-line-delete',
|
|
className: 'line-delete',
|
|
marginClassName: 'gutter-delete',
|
|
isWholeLine: true
|
|
}),
|
|
lineDeleteWithSign: ModelDecorationOptions.register({
|
|
description: 'diff-editor-line-delete-with-sign',
|
|
className: 'line-delete',
|
|
linesDecorationsClassName: 'delete-sign ' + ThemeIcon.asClassName(diffRemoveIcon),
|
|
marginClassName: 'gutter-delete',
|
|
isWholeLine: true
|
|
|
|
}),
|
|
lineDeleteMargin: ModelDecorationOptions.register({
|
|
description: 'diff-editor-line-delete-margin',
|
|
marginClassName: 'gutter-delete',
|
|
})
|
|
|
|
};
|
|
|
|
// {{SQL CARBON EDIT}} - helper function for reversing colors for schema compare
|
|
/**
|
|
* Reverses the decorations of the given array of decorations, so that deletes and inserts are swapped
|
|
* @param decorations
|
|
*/
|
|
function reverseDecorations(decorations: IModelDeltaDecoration[]): void {
|
|
for (let dec of decorations) {
|
|
switch (dec.options.description) {
|
|
case 'diff-editor-char-delete': {
|
|
dec.options = DECORATIONS.charInsert;
|
|
break;
|
|
}
|
|
case 'diff-editor-char-delete-whole-line': {
|
|
dec.options = DECORATIONS.charInsertWholeLine;
|
|
break;
|
|
}
|
|
case 'diff-editor-char-insert': {
|
|
dec.options = DECORATIONS.charDelete;
|
|
break;
|
|
}
|
|
case 'diff-editor-char-insert-whole-line': {
|
|
dec.options = DECORATIONS.charDeleteWholeLine;
|
|
break;
|
|
}
|
|
case 'diff-editor-line-insert': {
|
|
dec.options = DECORATIONS.lineDelete;
|
|
break;
|
|
}
|
|
case 'diff-editor-line-insert-with-sign': {
|
|
dec.options = DECORATIONS.lineDeleteWithSign;
|
|
break;
|
|
}
|
|
case 'diff-editor-line-delete': {
|
|
dec.options = DECORATIONS.lineInsert;
|
|
break;
|
|
}
|
|
case 'diff-editor-line-delete-with-sign': {
|
|
dec.options = DECORATIONS.lineInsertWithSign;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// {{SQL CARBON EDIT}} - helper function for reversing colors for schema compare
|
|
/**
|
|
* Sets the overview zones to the provided color
|
|
* @param zones Array of zones to update
|
|
* @param color Color to set the overview zones to
|
|
* @returns
|
|
*/
|
|
function setOverviewZonesColor(zones: OverviewRulerZone[], color: string): OverviewRulerZone[] {
|
|
let reversedZones = [];
|
|
|
|
for (const zone of zones) {
|
|
// There color of an overview zone is readonly, so create a new one with the updated color
|
|
reversedZones.push(new OverviewRulerZone(zone.startLineNumber, zone.endLineNumber, zone.heightInLines, color));
|
|
}
|
|
|
|
return reversedZones;
|
|
}
|
|
|
|
class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IVerticalSashLayoutProvider {
|
|
|
|
static readonly MINIMUM_EDITOR_WIDTH = 100;
|
|
|
|
private _disableSash: boolean;
|
|
private readonly _sash: Sash;
|
|
private _sashRatio: number | null;
|
|
private _sashPosition: number | null;
|
|
private _startSashPosition: number | null;
|
|
|
|
constructor(dataSource: IDataSource, enableSplitViewResizing: boolean) {
|
|
super(dataSource);
|
|
|
|
this._disableSash = (enableSplitViewResizing === false);
|
|
this._sashRatio = null;
|
|
this._sashPosition = null;
|
|
this._startSashPosition = null;
|
|
this._sash = this._register(new Sash(this._dataSource.getContainerDomNode(), this, { orientation: Orientation.VERTICAL }));
|
|
|
|
if (this._disableSash) {
|
|
this._sash.state = SashState.Disabled;
|
|
}
|
|
|
|
this._sash.onDidStart(() => this._onSashDragStart());
|
|
this._sash.onDidChange((e: ISashEvent) => this._onSashDrag(e));
|
|
this._sash.onDidEnd(() => this._onSashDragEnd());
|
|
this._sash.onDidReset(() => this._onSashReset());
|
|
}
|
|
|
|
public setEnableSplitViewResizing(enableSplitViewResizing: boolean): void {
|
|
const newDisableSash = (enableSplitViewResizing === false);
|
|
if (this._disableSash !== newDisableSash) {
|
|
this._disableSash = newDisableSash;
|
|
this._sash.state = this._disableSash ? SashState.Disabled : SashState.Enabled;
|
|
}
|
|
}
|
|
|
|
public layout(sashRatio: number | null = this._sashRatio): number {
|
|
const w = this._dataSource.getWidth();
|
|
const contentWidth = w - (this._dataSource.getOptions().renderOverviewRuler ? DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH : 0);
|
|
|
|
let sashPosition = Math.floor((sashRatio || 0.5) * contentWidth);
|
|
const midPoint = Math.floor(0.5 * contentWidth);
|
|
|
|
sashPosition = this._disableSash ? midPoint : sashPosition || midPoint;
|
|
|
|
if (contentWidth > DiffEditorWidgetSideBySide.MINIMUM_EDITOR_WIDTH * 2) {
|
|
if (sashPosition < DiffEditorWidgetSideBySide.MINIMUM_EDITOR_WIDTH) {
|
|
sashPosition = DiffEditorWidgetSideBySide.MINIMUM_EDITOR_WIDTH;
|
|
}
|
|
|
|
if (sashPosition > contentWidth - DiffEditorWidgetSideBySide.MINIMUM_EDITOR_WIDTH) {
|
|
sashPosition = contentWidth - DiffEditorWidgetSideBySide.MINIMUM_EDITOR_WIDTH;
|
|
}
|
|
} else {
|
|
sashPosition = midPoint;
|
|
}
|
|
|
|
if (this._sashPosition !== sashPosition) {
|
|
this._sashPosition = sashPosition;
|
|
}
|
|
this._sash.layout();
|
|
|
|
return this._sashPosition;
|
|
}
|
|
|
|
private _onSashDragStart(): void {
|
|
this._startSashPosition = this._sashPosition!;
|
|
}
|
|
|
|
private _onSashDrag(e: ISashEvent): void {
|
|
const w = this._dataSource.getWidth();
|
|
const contentWidth = w - (this._dataSource.getOptions().renderOverviewRuler ? DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH : 0);
|
|
const sashPosition = this.layout((this._startSashPosition! + (e.currentX - e.startX)) / contentWidth);
|
|
|
|
this._sashRatio = sashPosition / contentWidth;
|
|
|
|
this._dataSource.relayoutEditors();
|
|
}
|
|
|
|
private _onSashDragEnd(): void {
|
|
this._sash.layout();
|
|
}
|
|
|
|
private _onSashReset(): void {
|
|
this._sashRatio = 0.5;
|
|
this._dataSource.relayoutEditors();
|
|
this._sash.layout();
|
|
}
|
|
|
|
public getVerticalSashTop(sash: Sash): number {
|
|
return 0;
|
|
}
|
|
|
|
public getVerticalSashLeft(sash: Sash): number {
|
|
return this._sashPosition!;
|
|
}
|
|
|
|
public getVerticalSashHeight(sash: Sash): number {
|
|
return this._dataSource.getHeight();
|
|
}
|
|
|
|
protected _getViewZones(lineChanges: ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[]): IEditorsZones {
|
|
const originalEditor = this._dataSource.getOriginalEditor();
|
|
const modifiedEditor = this._dataSource.getModifiedEditor();
|
|
const c = new SideBySideViewZonesComputer(lineChanges, originalForeignVZ, modifiedForeignVZ, originalEditor, modifiedEditor);
|
|
return c.getViewZones();
|
|
}
|
|
|
|
protected _getOriginalEditorDecorations(zones: IEditorsZones, lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean): IEditorDiffDecorations {
|
|
const originalEditor = this._dataSource.getOriginalEditor();
|
|
const overviewZoneColor = String(this._removeColor);
|
|
|
|
const result: IEditorDiffDecorations = {
|
|
decorations: [],
|
|
overviewZones: []
|
|
};
|
|
|
|
const originalModel = originalEditor.getModel()!;
|
|
const originalViewModel = originalEditor._getViewModel()!;
|
|
|
|
for (const lineChange of lineChanges) {
|
|
|
|
if (isChangeOrDelete(lineChange)) {
|
|
result.decorations.push({
|
|
range: new Range(lineChange.originalStartLineNumber, 1, lineChange.originalEndLineNumber, Constants.MAX_SAFE_SMALL_INTEGER),
|
|
options: (renderIndicators ? DECORATIONS.lineDeleteWithSign : DECORATIONS.lineDelete)
|
|
});
|
|
if (!isChangeOrInsert(lineChange) || !lineChange.charChanges) {
|
|
result.decorations.push(createDecoration(lineChange.originalStartLineNumber, 1, lineChange.originalEndLineNumber, Constants.MAX_SAFE_SMALL_INTEGER, DECORATIONS.charDeleteWholeLine));
|
|
}
|
|
|
|
const viewRange = getViewRange(originalModel, originalViewModel, lineChange.originalStartLineNumber, lineChange.originalEndLineNumber);
|
|
result.overviewZones.push(new OverviewRulerZone(viewRange.startLineNumber, viewRange.endLineNumber, /*use endLineNumber*/0, overviewZoneColor));
|
|
|
|
if (lineChange.charChanges) {
|
|
for (const charChange of lineChange.charChanges) {
|
|
if (isCharChangeOrDelete(charChange)) {
|
|
if (ignoreTrimWhitespace) {
|
|
for (let lineNumber = charChange.originalStartLineNumber; lineNumber <= charChange.originalEndLineNumber; lineNumber++) {
|
|
let startColumn: number;
|
|
let endColumn: number;
|
|
if (lineNumber === charChange.originalStartLineNumber) {
|
|
startColumn = charChange.originalStartColumn;
|
|
} else {
|
|
startColumn = originalModel.getLineFirstNonWhitespaceColumn(lineNumber);
|
|
}
|
|
if (lineNumber === charChange.originalEndLineNumber) {
|
|
endColumn = charChange.originalEndColumn;
|
|
} else {
|
|
endColumn = originalModel.getLineLastNonWhitespaceColumn(lineNumber);
|
|
}
|
|
result.decorations.push(createDecoration(lineNumber, startColumn, lineNumber, endColumn, DECORATIONS.charDelete));
|
|
}
|
|
} else {
|
|
result.decorations.push(createDecoration(charChange.originalStartLineNumber, charChange.originalStartColumn, charChange.originalEndLineNumber, charChange.originalEndColumn, DECORATIONS.charDelete));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
protected _getModifiedEditorDecorations(zones: IEditorsZones, lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, renderMarginRevertIcon: boolean): IEditorDiffDecorations {
|
|
const modifiedEditor = this._dataSource.getModifiedEditor();
|
|
const overviewZoneColor = String(this._insertColor);
|
|
|
|
const result: IEditorDiffDecorations = {
|
|
decorations: [],
|
|
overviewZones: []
|
|
};
|
|
|
|
const modifiedModel = modifiedEditor.getModel()!;
|
|
const modifiedViewModel = modifiedEditor._getViewModel()!;
|
|
|
|
for (const lineChange of lineChanges) {
|
|
|
|
// Arrows for reverting changes.
|
|
if (renderMarginRevertIcon) {
|
|
if (lineChange.modifiedEndLineNumber > 0) {
|
|
result.decorations.push({
|
|
range: new Range(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedStartLineNumber, 1),
|
|
options: DECORATIONS.arrowRevertChange
|
|
});
|
|
} else {
|
|
const viewZone = zones.modified.find(z => z.afterLineNumber === lineChange.modifiedStartLineNumber);
|
|
if (viewZone) {
|
|
viewZone.marginDomNode = createViewZoneMarginArrow();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isChangeOrInsert(lineChange)) {
|
|
|
|
result.decorations.push({
|
|
range: new Range(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedEndLineNumber, Constants.MAX_SAFE_SMALL_INTEGER),
|
|
options: (renderIndicators ? DECORATIONS.lineInsertWithSign : DECORATIONS.lineInsert)
|
|
});
|
|
if (!isChangeOrDelete(lineChange) || !lineChange.charChanges) {
|
|
result.decorations.push(createDecoration(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedEndLineNumber, Constants.MAX_SAFE_SMALL_INTEGER, DECORATIONS.charInsertWholeLine));
|
|
}
|
|
|
|
const viewRange = getViewRange(modifiedModel, modifiedViewModel, lineChange.modifiedStartLineNumber, lineChange.modifiedEndLineNumber);
|
|
result.overviewZones.push(new OverviewRulerZone(viewRange.startLineNumber, viewRange.endLineNumber,/*use endLineNumber*/0, overviewZoneColor));
|
|
|
|
if (lineChange.charChanges) {
|
|
for (const charChange of lineChange.charChanges) {
|
|
if (isCharChangeOrInsert(charChange)) {
|
|
if (ignoreTrimWhitespace) {
|
|
for (let lineNumber = charChange.modifiedStartLineNumber; lineNumber <= charChange.modifiedEndLineNumber; lineNumber++) {
|
|
let startColumn: number;
|
|
let endColumn: number;
|
|
if (lineNumber === charChange.modifiedStartLineNumber) {
|
|
startColumn = charChange.modifiedStartColumn;
|
|
} else {
|
|
startColumn = modifiedModel.getLineFirstNonWhitespaceColumn(lineNumber);
|
|
}
|
|
if (lineNumber === charChange.modifiedEndLineNumber) {
|
|
endColumn = charChange.modifiedEndColumn;
|
|
} else {
|
|
endColumn = modifiedModel.getLineLastNonWhitespaceColumn(lineNumber);
|
|
}
|
|
result.decorations.push(createDecoration(lineNumber, startColumn, lineNumber, endColumn, DECORATIONS.charInsert));
|
|
}
|
|
} else {
|
|
result.decorations.push(createDecoration(charChange.modifiedStartLineNumber, charChange.modifiedStartColumn, charChange.modifiedEndLineNumber, charChange.modifiedEndColumn, DECORATIONS.charInsert));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
class SideBySideViewZonesComputer extends ViewZonesComputer {
|
|
|
|
constructor(
|
|
lineChanges: ILineChange[],
|
|
originalForeignVZ: IEditorWhitespace[],
|
|
modifiedForeignVZ: IEditorWhitespace[],
|
|
originalEditor: CodeEditorWidget,
|
|
modifiedEditor: CodeEditorWidget,
|
|
) {
|
|
super(lineChanges, originalForeignVZ, modifiedForeignVZ, originalEditor, modifiedEditor);
|
|
}
|
|
|
|
protected _createOriginalMarginDomNodeForModifiedForeignViewZoneInAddedRegion(): HTMLDivElement | null {
|
|
return null;
|
|
}
|
|
|
|
protected _produceOriginalFromDiff(lineChange: ILineChange, lineChangeOriginalLength: number, lineChangeModifiedLength: number): IMyViewZone | null {
|
|
if (lineChangeModifiedLength > lineChangeOriginalLength) {
|
|
return {
|
|
afterLineNumber: Math.max(lineChange.originalStartLineNumber, lineChange.originalEndLineNumber),
|
|
heightInLines: (lineChangeModifiedLength - lineChangeOriginalLength),
|
|
domNode: null
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
|
|
protected _produceModifiedFromDiff(lineChange: ILineChange, lineChangeOriginalLength: number, lineChangeModifiedLength: number): IMyViewZone | null {
|
|
if (lineChangeOriginalLength > lineChangeModifiedLength) {
|
|
return {
|
|
afterLineNumber: Math.max(lineChange.modifiedStartLineNumber, lineChange.modifiedEndLineNumber),
|
|
heightInLines: (lineChangeOriginalLength - lineChangeModifiedLength),
|
|
domNode: null
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
class DiffEditorWidgetInline extends DiffEditorWidgetStyle {
|
|
|
|
private _decorationsLeft: number;
|
|
|
|
constructor(dataSource: IDataSource, enableSplitViewResizing: boolean) {
|
|
super(dataSource);
|
|
|
|
this._decorationsLeft = dataSource.getOriginalEditor().getLayoutInfo().decorationsLeft;
|
|
|
|
this._register(dataSource.getOriginalEditor().onDidLayoutChange((layoutInfo: EditorLayoutInfo) => {
|
|
if (this._decorationsLeft !== layoutInfo.decorationsLeft) {
|
|
this._decorationsLeft = layoutInfo.decorationsLeft;
|
|
dataSource.relayoutEditors();
|
|
}
|
|
}));
|
|
}
|
|
|
|
public setEnableSplitViewResizing(enableSplitViewResizing: boolean): void {
|
|
// Nothing to do..
|
|
}
|
|
|
|
protected _getViewZones(lineChanges: ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[], renderIndicators: boolean): IEditorsZones {
|
|
const originalEditor = this._dataSource.getOriginalEditor();
|
|
const modifiedEditor = this._dataSource.getModifiedEditor();
|
|
const computer = new InlineViewZonesComputer(lineChanges, originalForeignVZ, modifiedForeignVZ, originalEditor, modifiedEditor, renderIndicators);
|
|
return computer.getViewZones();
|
|
}
|
|
|
|
protected _getOriginalEditorDecorations(zones: IEditorsZones, lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean): IEditorDiffDecorations {
|
|
const overviewZoneColor = String(this._removeColor);
|
|
|
|
const result: IEditorDiffDecorations = {
|
|
decorations: [],
|
|
overviewZones: []
|
|
};
|
|
|
|
const originalEditor = this._dataSource.getOriginalEditor();
|
|
const originalModel = originalEditor.getModel()!;
|
|
const originalViewModel = originalEditor._getViewModel()!;
|
|
let zoneIndex = 0;
|
|
|
|
for (const lineChange of lineChanges) {
|
|
|
|
// Add overview zones in the overview ruler
|
|
if (isChangeOrDelete(lineChange)) {
|
|
result.decorations.push({
|
|
range: new Range(lineChange.originalStartLineNumber, 1, lineChange.originalEndLineNumber, Constants.MAX_SAFE_SMALL_INTEGER),
|
|
options: DECORATIONS.lineDeleteMargin
|
|
});
|
|
|
|
while (zoneIndex < zones.modified.length) {
|
|
const zone = zones.modified[zoneIndex];
|
|
if (zone.diff && zone.diff.originalStartLineNumber >= lineChange.originalStartLineNumber) {
|
|
break;
|
|
}
|
|
zoneIndex++;
|
|
}
|
|
|
|
let zoneHeightInLines = 0;
|
|
if (zoneIndex < zones.modified.length) {
|
|
const zone = zones.modified[zoneIndex];
|
|
if (
|
|
zone.diff
|
|
&& zone.diff.originalStartLineNumber === lineChange.originalStartLineNumber
|
|
&& zone.diff.originalEndLineNumber === lineChange.originalEndLineNumber
|
|
&& zone.diff.modifiedStartLineNumber === lineChange.modifiedStartLineNumber
|
|
&& zone.diff.modifiedEndLineNumber === lineChange.modifiedEndLineNumber
|
|
) {
|
|
zoneHeightInLines = zone.heightInLines;
|
|
}
|
|
}
|
|
|
|
const viewRange = getViewRange(originalModel, originalViewModel, lineChange.originalStartLineNumber, lineChange.originalEndLineNumber);
|
|
result.overviewZones.push(new OverviewRulerZone(viewRange.startLineNumber, viewRange.endLineNumber, zoneHeightInLines, overviewZoneColor));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
protected _getModifiedEditorDecorations(zones: IEditorsZones, lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, renderMarginRevertIcon: boolean): IEditorDiffDecorations {
|
|
const modifiedEditor = this._dataSource.getModifiedEditor();
|
|
const overviewZoneColor = String(this._insertColor);
|
|
|
|
const result: IEditorDiffDecorations = {
|
|
decorations: [],
|
|
overviewZones: []
|
|
};
|
|
|
|
const modifiedModel = modifiedEditor.getModel()!;
|
|
const modifiedViewModel = modifiedEditor._getViewModel()!;
|
|
|
|
for (const lineChange of lineChanges) {
|
|
|
|
// Add decorations & overview zones
|
|
if (isChangeOrInsert(lineChange)) {
|
|
result.decorations.push({
|
|
range: new Range(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedEndLineNumber, Constants.MAX_SAFE_SMALL_INTEGER),
|
|
options: (renderIndicators ? DECORATIONS.lineInsertWithSign : DECORATIONS.lineInsert)
|
|
});
|
|
|
|
const viewRange = getViewRange(modifiedModel, modifiedViewModel, lineChange.modifiedStartLineNumber, lineChange.modifiedEndLineNumber);
|
|
result.overviewZones.push(new OverviewRulerZone(viewRange.startLineNumber, viewRange.endLineNumber, /*use endLineNumber*/0, overviewZoneColor));
|
|
|
|
if (lineChange.charChanges) {
|
|
for (const charChange of lineChange.charChanges) {
|
|
if (isCharChangeOrInsert(charChange)) {
|
|
if (ignoreTrimWhitespace) {
|
|
for (let lineNumber = charChange.modifiedStartLineNumber; lineNumber <= charChange.modifiedEndLineNumber; lineNumber++) {
|
|
let startColumn: number;
|
|
let endColumn: number;
|
|
if (lineNumber === charChange.modifiedStartLineNumber) {
|
|
startColumn = charChange.modifiedStartColumn;
|
|
} else {
|
|
startColumn = modifiedModel.getLineFirstNonWhitespaceColumn(lineNumber);
|
|
}
|
|
if (lineNumber === charChange.modifiedEndLineNumber) {
|
|
endColumn = charChange.modifiedEndColumn;
|
|
} else {
|
|
endColumn = modifiedModel.getLineLastNonWhitespaceColumn(lineNumber);
|
|
}
|
|
result.decorations.push(createDecoration(lineNumber, startColumn, lineNumber, endColumn, DECORATIONS.charInsert));
|
|
}
|
|
} else {
|
|
result.decorations.push(createDecoration(charChange.modifiedStartLineNumber, charChange.modifiedStartColumn, charChange.modifiedEndLineNumber, charChange.modifiedEndColumn, DECORATIONS.charInsert));
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
result.decorations.push(createDecoration(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedEndLineNumber, Constants.MAX_SAFE_SMALL_INTEGER, DECORATIONS.charInsertWholeLine));
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public layout(): number {
|
|
// An editor should not be smaller than 5px
|
|
return Math.max(5, this._decorationsLeft);
|
|
}
|
|
|
|
}
|
|
|
|
interface InlineModifiedViewZone extends IMyViewZone {
|
|
shouldNotShrink: boolean;
|
|
afterLineNumber: number;
|
|
heightInLines: number;
|
|
minWidthInPx: number;
|
|
domNode: HTMLElement;
|
|
marginDomNode: HTMLElement;
|
|
diff: IDiffLinesChange;
|
|
}
|
|
|
|
class InlineViewZonesComputer extends ViewZonesComputer {
|
|
|
|
private readonly _originalModel: ITextModel;
|
|
private readonly _renderIndicators: boolean;
|
|
private readonly _pendingLineChange: ILineChange[];
|
|
private readonly _pendingViewZones: InlineModifiedViewZone[];
|
|
private readonly _lineBreaksComputer: ILineBreaksComputer;
|
|
|
|
constructor(
|
|
lineChanges: ILineChange[],
|
|
originalForeignVZ: IEditorWhitespace[],
|
|
modifiedForeignVZ: IEditorWhitespace[],
|
|
originalEditor: CodeEditorWidget,
|
|
modifiedEditor: CodeEditorWidget,
|
|
renderIndicators: boolean
|
|
) {
|
|
super(lineChanges, originalForeignVZ, modifiedForeignVZ, originalEditor, modifiedEditor);
|
|
this._originalModel = originalEditor.getModel()!;
|
|
this._renderIndicators = renderIndicators;
|
|
this._pendingLineChange = [];
|
|
this._pendingViewZones = [];
|
|
this._lineBreaksComputer = this._modifiedEditor._getViewModel()!.createLineBreaksComputer();
|
|
}
|
|
|
|
public override getViewZones(): IEditorsZones {
|
|
const result = super.getViewZones();
|
|
this._finalize(result);
|
|
return result;
|
|
}
|
|
|
|
protected _createOriginalMarginDomNodeForModifiedForeignViewZoneInAddedRegion(): HTMLDivElement | null {
|
|
const result = document.createElement('div');
|
|
result.className = 'inline-added-margin-view-zone';
|
|
return result;
|
|
}
|
|
|
|
protected _produceOriginalFromDiff(lineChange: ILineChange, lineChangeOriginalLength: number, lineChangeModifiedLength: number): IMyViewZone | null {
|
|
const marginDomNode = document.createElement('div');
|
|
marginDomNode.className = 'inline-added-margin-view-zone';
|
|
|
|
return {
|
|
afterLineNumber: Math.max(lineChange.originalStartLineNumber, lineChange.originalEndLineNumber),
|
|
heightInLines: lineChangeModifiedLength,
|
|
domNode: document.createElement('div'),
|
|
marginDomNode: marginDomNode
|
|
};
|
|
}
|
|
|
|
protected _produceModifiedFromDiff(lineChange: ILineChange, lineChangeOriginalLength: number, lineChangeModifiedLength: number): IMyViewZone | null {
|
|
const domNode = document.createElement('div');
|
|
domNode.className = `view-lines line-delete ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`;
|
|
|
|
const marginDomNode = document.createElement('div');
|
|
marginDomNode.className = 'inline-deleted-margin-view-zone';
|
|
|
|
const viewZone: InlineModifiedViewZone = {
|
|
shouldNotShrink: true,
|
|
afterLineNumber: (lineChange.modifiedEndLineNumber === 0 ? lineChange.modifiedStartLineNumber : lineChange.modifiedStartLineNumber - 1),
|
|
heightInLines: lineChangeOriginalLength,
|
|
minWidthInPx: 0,
|
|
domNode: domNode,
|
|
marginDomNode: marginDomNode,
|
|
diff: {
|
|
originalStartLineNumber: lineChange.originalStartLineNumber,
|
|
originalEndLineNumber: lineChange.originalEndLineNumber,
|
|
modifiedStartLineNumber: lineChange.modifiedStartLineNumber,
|
|
modifiedEndLineNumber: lineChange.modifiedEndLineNumber,
|
|
originalModel: this._originalModel,
|
|
viewLineCounts: null,
|
|
}
|
|
};
|
|
|
|
for (let lineNumber = lineChange.originalStartLineNumber; lineNumber <= lineChange.originalEndLineNumber; lineNumber++) {
|
|
this._lineBreaksComputer.addRequest(this._originalModel.getLineContent(lineNumber), null, null);
|
|
}
|
|
|
|
this._pendingLineChange.push(lineChange);
|
|
this._pendingViewZones.push(viewZone);
|
|
|
|
return viewZone;
|
|
}
|
|
|
|
private _finalize(result: IEditorsZones): void {
|
|
const modifiedEditorOptions = this._modifiedEditor.getOptions();
|
|
const tabSize = this._modifiedEditor.getModel()!.getOptions().tabSize;
|
|
const fontInfo = modifiedEditorOptions.get(EditorOption.fontInfo);
|
|
const disableMonospaceOptimizations = modifiedEditorOptions.get(EditorOption.disableMonospaceOptimizations);
|
|
const typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth;
|
|
const scrollBeyondLastColumn = modifiedEditorOptions.get(EditorOption.scrollBeyondLastColumn);
|
|
const mightContainNonBasicASCII = this._originalModel.mightContainNonBasicASCII();
|
|
const mightContainRTL = this._originalModel.mightContainRTL();
|
|
const lineHeight = modifiedEditorOptions.get(EditorOption.lineHeight);
|
|
const layoutInfo = modifiedEditorOptions.get(EditorOption.layoutInfo);
|
|
const lineDecorationsWidth = layoutInfo.decorationsWidth;
|
|
const stopRenderingLineAfter = modifiedEditorOptions.get(EditorOption.stopRenderingLineAfter);
|
|
const renderWhitespace = modifiedEditorOptions.get(EditorOption.renderWhitespace);
|
|
const renderControlCharacters = modifiedEditorOptions.get(EditorOption.renderControlCharacters);
|
|
const fontLigatures = modifiedEditorOptions.get(EditorOption.fontLigatures);
|
|
|
|
const lineBreaks = this._lineBreaksComputer.finalize();
|
|
let lineBreakIndex = 0;
|
|
|
|
for (let i = 0; i < this._pendingLineChange.length; i++) {
|
|
const lineChange = this._pendingLineChange[i];
|
|
const viewZone = this._pendingViewZones[i];
|
|
const domNode = viewZone.domNode;
|
|
applyFontInfo(domNode, fontInfo);
|
|
|
|
const marginDomNode = viewZone.marginDomNode;
|
|
applyFontInfo(marginDomNode, fontInfo);
|
|
|
|
const decorations: InlineDecoration[] = [];
|
|
if (lineChange.charChanges) {
|
|
for (const charChange of lineChange.charChanges) {
|
|
if (isCharChangeOrDelete(charChange)) {
|
|
decorations.push(new InlineDecoration(
|
|
new Range(charChange.originalStartLineNumber, charChange.originalStartColumn, charChange.originalEndLineNumber, charChange.originalEndColumn),
|
|
'char-delete',
|
|
InlineDecorationType.Regular
|
|
));
|
|
}
|
|
}
|
|
}
|
|
const hasCharChanges = (decorations.length > 0);
|
|
|
|
const sb = createStringBuilder(10000);
|
|
let maxCharsPerLine = 0;
|
|
let renderedLineCount = 0;
|
|
let viewLineCounts: number[] | null = null;
|
|
for (let lineNumber = lineChange.originalStartLineNumber; lineNumber <= lineChange.originalEndLineNumber; lineNumber++) {
|
|
const lineIndex = lineNumber - lineChange.originalStartLineNumber;
|
|
const lineTokens = this._originalModel.tokenization.getLineTokens(lineNumber);
|
|
const lineContent = lineTokens.getLineContent();
|
|
const lineBreakData = lineBreaks[lineBreakIndex++];
|
|
const actualDecorations = LineDecoration.filter(decorations, lineNumber, 1, lineContent.length + 1);
|
|
|
|
if (lineBreakData) {
|
|
let lastBreakOffset = 0;
|
|
for (const breakOffset of lineBreakData.breakOffsets) {
|
|
const viewLineTokens = lineTokens.sliceAndInflate(lastBreakOffset, breakOffset, 0);
|
|
const viewLineContent = lineContent.substring(lastBreakOffset, breakOffset);
|
|
maxCharsPerLine = Math.max(maxCharsPerLine, this._renderOriginalLine(
|
|
renderedLineCount++,
|
|
viewLineContent,
|
|
viewLineTokens,
|
|
LineDecoration.extractWrapped(actualDecorations, lastBreakOffset, breakOffset),
|
|
hasCharChanges,
|
|
mightContainNonBasicASCII,
|
|
mightContainRTL,
|
|
fontInfo,
|
|
disableMonospaceOptimizations,
|
|
lineHeight,
|
|
lineDecorationsWidth,
|
|
stopRenderingLineAfter,
|
|
renderWhitespace,
|
|
renderControlCharacters,
|
|
fontLigatures,
|
|
tabSize,
|
|
sb,
|
|
marginDomNode
|
|
));
|
|
lastBreakOffset = breakOffset;
|
|
}
|
|
if (!viewLineCounts) {
|
|
viewLineCounts = [];
|
|
}
|
|
// make sure all lines before this one have an entry in `viewLineCounts`
|
|
while (viewLineCounts.length < lineIndex) {
|
|
viewLineCounts[viewLineCounts.length] = 1;
|
|
}
|
|
viewLineCounts[lineIndex] = lineBreakData.breakOffsets.length;
|
|
viewZone.heightInLines += (lineBreakData.breakOffsets.length - 1);
|
|
const marginDomNode2 = document.createElement('div');
|
|
marginDomNode2.className = 'gutter-delete';
|
|
result.original.push({
|
|
afterLineNumber: lineNumber,
|
|
afterColumn: 0,
|
|
heightInLines: lineBreakData.breakOffsets.length - 1,
|
|
domNode: createFakeLinesDiv(),
|
|
marginDomNode: marginDomNode2
|
|
});
|
|
} else {
|
|
maxCharsPerLine = Math.max(maxCharsPerLine, this._renderOriginalLine(
|
|
renderedLineCount++,
|
|
lineContent,
|
|
lineTokens,
|
|
actualDecorations,
|
|
hasCharChanges,
|
|
mightContainNonBasicASCII,
|
|
mightContainRTL,
|
|
fontInfo,
|
|
disableMonospaceOptimizations,
|
|
lineHeight,
|
|
lineDecorationsWidth,
|
|
stopRenderingLineAfter,
|
|
renderWhitespace,
|
|
renderControlCharacters,
|
|
fontLigatures,
|
|
tabSize,
|
|
sb,
|
|
marginDomNode
|
|
));
|
|
}
|
|
}
|
|
maxCharsPerLine += scrollBeyondLastColumn;
|
|
|
|
const html = sb.build();
|
|
const trustedhtml = ttPolicy ? ttPolicy.createHTML(html) : html;
|
|
domNode.innerHTML = trustedhtml as string;
|
|
viewZone.minWidthInPx = (maxCharsPerLine * typicalHalfwidthCharacterWidth);
|
|
|
|
if (viewLineCounts) {
|
|
// make sure all lines have an entry in `viewLineCounts`
|
|
const cnt = lineChange.originalEndLineNumber - lineChange.originalStartLineNumber;
|
|
while (viewLineCounts.length <= cnt) {
|
|
viewLineCounts[viewLineCounts.length] = 1;
|
|
}
|
|
}
|
|
viewZone.diff.viewLineCounts = viewLineCounts;
|
|
}
|
|
|
|
result.original.sort((a, b) => {
|
|
return a.afterLineNumber - b.afterLineNumber;
|
|
});
|
|
}
|
|
|
|
private _renderOriginalLine(
|
|
renderedLineCount: number,
|
|
lineContent: string,
|
|
lineTokens: IViewLineTokens,
|
|
decorations: LineDecoration[],
|
|
hasCharChanges: boolean,
|
|
mightContainNonBasicASCII: boolean,
|
|
mightContainRTL: boolean,
|
|
fontInfo: FontInfo,
|
|
disableMonospaceOptimizations: boolean,
|
|
lineHeight: number,
|
|
lineDecorationsWidth: number,
|
|
stopRenderingLineAfter: number,
|
|
renderWhitespace: 'selection' | 'none' | 'boundary' | 'trailing' | 'all',
|
|
renderControlCharacters: boolean,
|
|
fontLigatures: string,
|
|
tabSize: number,
|
|
sb: IStringBuilder,
|
|
marginDomNode: HTMLElement
|
|
): number {
|
|
|
|
sb.appendASCIIString('<div class="view-line');
|
|
if (!hasCharChanges) {
|
|
// No char changes
|
|
sb.appendASCIIString(' char-delete');
|
|
}
|
|
sb.appendASCIIString('" style="top:');
|
|
sb.appendASCIIString(String(renderedLineCount * lineHeight));
|
|
sb.appendASCIIString('px;width:1000000px;">');
|
|
|
|
const isBasicASCII = ViewLineRenderingData.isBasicASCII(lineContent, mightContainNonBasicASCII);
|
|
const containsRTL = ViewLineRenderingData.containsRTL(lineContent, isBasicASCII, mightContainRTL);
|
|
const output = renderViewLine(new RenderLineInput(
|
|
(fontInfo.isMonospace && !disableMonospaceOptimizations),
|
|
fontInfo.canUseHalfwidthRightwardsArrow,
|
|
lineContent,
|
|
false,
|
|
isBasicASCII,
|
|
containsRTL,
|
|
0,
|
|
lineTokens,
|
|
decorations,
|
|
tabSize,
|
|
0,
|
|
fontInfo.spaceWidth,
|
|
fontInfo.middotWidth,
|
|
fontInfo.wsmiddotWidth,
|
|
stopRenderingLineAfter,
|
|
renderWhitespace,
|
|
renderControlCharacters,
|
|
fontLigatures !== EditorFontLigatures.OFF,
|
|
null // Send no selections, original line cannot be selected
|
|
), sb);
|
|
|
|
sb.appendASCIIString('</div>');
|
|
|
|
if (this._renderIndicators) {
|
|
const marginElement = document.createElement('div');
|
|
marginElement.className = `delete-sign ${ThemeIcon.asClassName(diffRemoveIcon)}`;
|
|
marginElement.setAttribute('style', `position:absolute;top:${renderedLineCount * lineHeight}px;width:${lineDecorationsWidth}px;height:${lineHeight}px;right:0;`);
|
|
marginDomNode.appendChild(marginElement);
|
|
}
|
|
|
|
return output.characterMapping.getHorizontalOffset(output.characterMapping.length);
|
|
}
|
|
}
|
|
|
|
function validateDiffWordWrap(value: 'off' | 'on' | 'inherit' | undefined, defaultValue: 'off' | 'on' | 'inherit'): 'off' | 'on' | 'inherit' {
|
|
return validateStringSetOption<'off' | 'on' | 'inherit'>(value, defaultValue, ['off', 'on', 'inherit']);
|
|
}
|
|
|
|
function isChangeOrInsert(lineChange: ILineChange): boolean {
|
|
return lineChange.modifiedEndLineNumber > 0;
|
|
}
|
|
|
|
function isChangeOrDelete(lineChange: ILineChange): boolean {
|
|
return lineChange.originalEndLineNumber > 0;
|
|
}
|
|
|
|
function isCharChangeOrInsert(charChange: ICharChange): boolean {
|
|
if (charChange.modifiedStartLineNumber === charChange.modifiedEndLineNumber) {
|
|
return charChange.modifiedEndColumn - charChange.modifiedStartColumn > 0;
|
|
}
|
|
return charChange.modifiedEndLineNumber - charChange.modifiedStartLineNumber > 0;
|
|
}
|
|
|
|
function isCharChangeOrDelete(charChange: ICharChange): boolean {
|
|
if (charChange.originalStartLineNumber === charChange.originalEndLineNumber) {
|
|
return charChange.originalEndColumn - charChange.originalStartColumn > 0;
|
|
}
|
|
return charChange.originalEndLineNumber - charChange.originalStartLineNumber > 0;
|
|
}
|
|
|
|
function createFakeLinesDiv(): HTMLElement {
|
|
const r = document.createElement('div');
|
|
r.className = 'diagonal-fill';
|
|
return r;
|
|
}
|
|
|
|
function createViewZoneMarginArrow(): HTMLElement {
|
|
const arrow = document.createElement('div');
|
|
arrow.className = 'arrow-revert-change ' + ThemeIcon.asClassName(Codicon.arrowRight);
|
|
return dom.$('div', {}, arrow);
|
|
}
|
|
|
|
function getViewRange(model: ITextModel, viewModel: IViewModel, startLineNumber: number, endLineNumber: number): Range {
|
|
const lineCount = model.getLineCount();
|
|
startLineNumber = Math.min(lineCount, Math.max(1, startLineNumber));
|
|
endLineNumber = Math.min(lineCount, Math.max(1, endLineNumber));
|
|
return viewModel.coordinatesConverter.convertModelRangeToViewRange(new Range(
|
|
startLineNumber, model.getLineMinColumn(startLineNumber),
|
|
endLineNumber, model.getLineMaxColumn(endLineNumber)
|
|
));
|
|
}
|
|
|
|
function validateDiffEditorOptions(options: Readonly<IDiffEditorOptions>, defaults: ValidDiffEditorBaseOptions): ValidDiffEditorBaseOptions {
|
|
let outOptions: any = {
|
|
enableSplitViewResizing: validateBooleanOption(options.enableSplitViewResizing, defaults.enableSplitViewResizing),
|
|
renderSideBySide: validateBooleanOption(options.renderSideBySide, defaults.renderSideBySide),
|
|
renderMarginRevertIcon: validateBooleanOption(options.renderMarginRevertIcon, defaults.renderMarginRevertIcon),
|
|
maxComputationTime: clampedInt(options.maxComputationTime, defaults.maxComputationTime, 0, Constants.MAX_SAFE_SMALL_INTEGER),
|
|
maxFileSize: clampedInt(options.maxFileSize, defaults.maxFileSize, 0, Constants.MAX_SAFE_SMALL_INTEGER),
|
|
ignoreTrimWhitespace: validateBooleanOption(options.ignoreTrimWhitespace, defaults.ignoreTrimWhitespace),
|
|
renderIndicators: validateBooleanOption(options.renderIndicators, defaults.renderIndicators),
|
|
originalEditable: validateBooleanOption(options.originalEditable, defaults.originalEditable),
|
|
diffCodeLens: validateBooleanOption(options.diffCodeLens, defaults.diffCodeLens),
|
|
renderOverviewRuler: validateBooleanOption(options.renderOverviewRuler, defaults.renderOverviewRuler),
|
|
diffWordWrap: validateDiffWordWrap(options.diffWordWrap, defaults.diffWordWrap),
|
|
reverse: validateBooleanOption(options.reverse, defaults.reverse), // {{SQL CARBON EDIT}}
|
|
};
|
|
return outOptions;
|
|
}
|
|
|
|
function changedDiffEditorOptions(a: ValidDiffEditorBaseOptions, b: ValidDiffEditorBaseOptions) {
|
|
return {
|
|
enableSplitViewResizing: (a.enableSplitViewResizing !== b.enableSplitViewResizing),
|
|
renderSideBySide: (a.renderSideBySide !== b.renderSideBySide),
|
|
renderMarginRevertIcon: (a.renderMarginRevertIcon !== b.renderMarginRevertIcon),
|
|
maxComputationTime: (a.maxComputationTime !== b.maxComputationTime),
|
|
maxFileSize: (a.maxFileSize !== b.maxFileSize),
|
|
ignoreTrimWhitespace: (a.ignoreTrimWhitespace !== b.ignoreTrimWhitespace),
|
|
renderIndicators: (a.renderIndicators !== b.renderIndicators),
|
|
originalEditable: (a.originalEditable !== b.originalEditable),
|
|
diffCodeLens: (a.diffCodeLens !== b.diffCodeLens),
|
|
renderOverviewRuler: (a.renderOverviewRuler !== b.renderOverviewRuler),
|
|
diffWordWrap: (a.diffWordWrap !== b.diffWordWrap),
|
|
};
|
|
}
|
|
|
|
registerThemingParticipant((theme, collector) => {
|
|
const added = theme.getColor(diffInserted);
|
|
if (added) {
|
|
collector.addRule(`.monaco-editor .char-insert, .monaco-diff-editor .char-insert { background-color: ${added}; }`);
|
|
}
|
|
const lineAdded = theme.getColor(diffInsertedLine) || added;
|
|
if (lineAdded) {
|
|
collector.addRule(`.monaco-editor .line-insert, .monaco-diff-editor .line-insert { background-color: ${lineAdded}; }`);
|
|
}
|
|
const gutterAdded = theme.getColor(diffInsertedLineGutter) || lineAdded;
|
|
if (gutterAdded) {
|
|
collector.addRule(`.monaco-editor .inline-added-margin-view-zone { background-color: ${gutterAdded}; }`);
|
|
collector.addRule(`.monaco-editor .gutter-insert, .monaco-diff-editor .gutter-insert { background-color: ${gutterAdded}; }`);
|
|
}
|
|
|
|
const removed = theme.getColor(diffRemoved);
|
|
if (removed) {
|
|
collector.addRule(`.monaco-editor .char-delete, .monaco-diff-editor .char-delete { background-color: ${removed}; }`);
|
|
}
|
|
const lineRemoved = theme.getColor(diffRemovedLine) || removed;
|
|
if (lineRemoved) {
|
|
collector.addRule(`.monaco-editor .line-delete, .monaco-diff-editor .line-delete { background-color: ${lineRemoved}; }`);
|
|
}
|
|
const gutterRemoved = theme.getColor(diffRemovedLineGutter) || lineRemoved;
|
|
if (gutterRemoved) {
|
|
collector.addRule(`.monaco-editor .inline-deleted-margin-view-zone { background-color: ${gutterRemoved}; }`);
|
|
collector.addRule(`.monaco-editor .gutter-delete, .monaco-diff-editor .gutter-delete { background-color: ${gutterRemoved}; }`);
|
|
}
|
|
|
|
const addedOutline = theme.getColor(diffInsertedOutline);
|
|
if (addedOutline) {
|
|
collector.addRule(`.monaco-editor .line-insert, .monaco-editor .char-insert { border: 1px ${isHighContrast(theme.type) ? 'dashed' : 'solid'} ${addedOutline}; }`);
|
|
}
|
|
|
|
const removedOutline = theme.getColor(diffRemovedOutline);
|
|
if (removedOutline) {
|
|
collector.addRule(`.monaco-editor .line-delete, .monaco-editor .char-delete { border: 1px ${isHighContrast(theme.type) ? 'dashed' : 'solid'} ${removedOutline}; }`);
|
|
}
|
|
|
|
const shadow = theme.getColor(scrollbarShadow);
|
|
if (shadow) {
|
|
collector.addRule(`.monaco-diff-editor.side-by-side .editor.modified { box-shadow: -6px 0 5px -5px ${shadow}; }`);
|
|
}
|
|
|
|
const border = theme.getColor(diffBorder);
|
|
if (border) {
|
|
collector.addRule(`.monaco-diff-editor.side-by-side .editor.modified { border-left: 1px solid ${border}; }`);
|
|
}
|
|
|
|
const scrollbarSliderBackgroundColor = theme.getColor(scrollbarSliderBackground);
|
|
if (scrollbarSliderBackgroundColor) {
|
|
collector.addRule(`
|
|
.monaco-diff-editor .diffViewport {
|
|
background: ${scrollbarSliderBackgroundColor};
|
|
}
|
|
`);
|
|
}
|
|
|
|
const scrollbarSliderHoverBackgroundColor = theme.getColor(scrollbarSliderHoverBackground);
|
|
if (scrollbarSliderHoverBackgroundColor) {
|
|
collector.addRule(`
|
|
.monaco-diff-editor .diffViewport:hover {
|
|
background: ${scrollbarSliderHoverBackgroundColor};
|
|
}
|
|
`);
|
|
}
|
|
|
|
const scrollbarSliderActiveBackgroundColor = theme.getColor(scrollbarSliderActiveBackground);
|
|
if (scrollbarSliderActiveBackgroundColor) {
|
|
collector.addRule(`
|
|
.monaco-diff-editor .diffViewport:active {
|
|
background: ${scrollbarSliderActiveBackgroundColor};
|
|
}
|
|
`);
|
|
}
|
|
|
|
const diffDiagonalFillColor = theme.getColor(diffDiagonalFill);
|
|
collector.addRule(`
|
|
.monaco-editor .diagonal-fill {
|
|
background-image: linear-gradient(
|
|
-45deg,
|
|
${diffDiagonalFillColor} 12.5%,
|
|
#0000 12.5%, #0000 50%,
|
|
${diffDiagonalFillColor} 50%, ${diffDiagonalFillColor} 62.5%,
|
|
#0000 62.5%, #0000 100%
|
|
);
|
|
background-size: 8px 8px;
|
|
}
|
|
`);
|
|
});
|