/*--------------------------------------------------------------------------------------------- * 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 = 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 = this._register(new Emitter()); public readonly onDidDispose: Event = this._onDidDispose.event; private readonly _onDidUpdateDiff: Emitter = this._register(new Emitter()); public readonly onDidUpdateDiff: Event = this._onDidUpdateDiff.event; private readonly _onDidContentSizeChange: Emitter = this._register(new Emitter()); public readonly onDidContentSizeChange: Event = 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; 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, 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, 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('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, 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('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, 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): 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 = 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): 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): 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): 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('
'); 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('
'); 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, 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; } `); });