mirror of
https://github.com/Microsoft/vscode
synced 2024-10-06 03:17:00 +00:00
Implements initial version of moved code detection. (#184336)
* Implements initial version of moved code detection. * Fixes monaco.d.ts * Fixes tests.
This commit is contained in:
parent
feab5843d9
commit
aa88e727da
|
@ -96,6 +96,7 @@
|
|||
"--vscode-diffEditor-insertedLineBackground",
|
||||
"--vscode-diffEditor-insertedTextBackground",
|
||||
"--vscode-diffEditor-insertedTextBorder",
|
||||
"--vscode-diffEditor-move-border",
|
||||
"--vscode-diffEditor-removedLineBackground",
|
||||
"--vscode-diffEditor-removedTextBackground",
|
||||
"--vscode-diffEditor-removedTextBorder",
|
||||
|
|
|
@ -110,7 +110,7 @@ export interface ICommandHandler {
|
|||
#include(vs/editor/common/diff/smartLinesDiffComputer): IChange, ICharChange, ILineChange
|
||||
#include(vs/editor/common/diff/documentDiffProvider): IDocumentDiffProvider, IDocumentDiffProviderOptions, IDocumentDiff
|
||||
#include(vs/editor/common/core/lineRange): LineRange
|
||||
#include(vs/editor/common/diff/linesDiffComputer): LineRangeMapping, RangeMapping
|
||||
#include(vs/editor/common/diff/linesDiffComputer): LineRangeMapping, RangeMapping, MovedText, SimpleLineRangeMapping
|
||||
#include(vs/editor/common/core/dimension): IDimension
|
||||
#includeAll(vs/editor/common/editorCommon): IScrollEvent
|
||||
#includeAll(vs/editor/common/textModelEvents):
|
||||
|
|
|
@ -14,7 +14,7 @@ import { ITextModel } from 'vs/editor/common/model';
|
|||
import * as languages from 'vs/editor/common/languages';
|
||||
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
|
||||
import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker';
|
||||
import { DiffAlgorithmName, IDiffComputationResult, IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker';
|
||||
import { DiffAlgorithmName, IDiffComputationResult, IEditorWorkerService, ILineChange, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker';
|
||||
import { IModelService } from 'vs/editor/common/services/model';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration';
|
||||
import { regExpFlags } from 'vs/base/common/strings';
|
||||
|
@ -27,7 +27,7 @@ import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost';
|
|||
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
||||
import { IChange } from 'vs/editor/common/diff/smartLinesDiffComputer';
|
||||
import { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider';
|
||||
import { ILinesDiffComputerOptions, LineRangeMapping, RangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
|
||||
import { ILinesDiffComputerOptions, LineRangeMapping, MovedText, RangeMapping, SimpleLineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
|
||||
import { LineRange } from 'vs/editor/common/core/lineRange';
|
||||
|
||||
/**
|
||||
|
@ -106,22 +106,28 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ
|
|||
const diff: IDocumentDiff = {
|
||||
identical: result.identical,
|
||||
quitEarly: result.quitEarly,
|
||||
changes: result.changes.map(
|
||||
(c) =>
|
||||
new LineRangeMapping(
|
||||
new LineRange(c[0], c[1]),
|
||||
new LineRange(c[2], c[3]),
|
||||
c[4]?.map(
|
||||
(c) =>
|
||||
new RangeMapping(
|
||||
new Range(c[0], c[1], c[2], c[3]),
|
||||
new Range(c[4], c[5], c[6], c[7])
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
changes: toLineRangeMappings(result.changes),
|
||||
moves: result.moves.map(m => new MovedText(
|
||||
new SimpleLineRangeMapping(new LineRange(m[0], m[1]), new LineRange(m[2], m[3])),
|
||||
toLineRangeMappings(m[4])
|
||||
))
|
||||
};
|
||||
return diff;
|
||||
|
||||
function toLineRangeMappings(changes: readonly ILineChange[]): readonly LineRangeMapping[] {
|
||||
return changes.map(
|
||||
(c) => new LineRangeMapping(
|
||||
new LineRange(c[0], c[1]),
|
||||
new LineRange(c[2], c[3]),
|
||||
c[4]?.map(
|
||||
(c) => new RangeMapping(
|
||||
new Range(c[0], c[1], c[2], c[3]),
|
||||
new Range(c[4], c[5], c[6], c[7])
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public canComputeDirtyDiff(original: URI, modified: URI): boolean {
|
||||
|
@ -153,11 +159,12 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ
|
|||
return Promise.resolve(edits); // File too large
|
||||
}
|
||||
const sw = StopWatch.create(true);
|
||||
const result = this._workerManager.withWorker().then(client => client.computeHumanReadableDiff(resource, edits, { ignoreTrimWhitespace: false, maxComputationTimeMs: 1000 })).catch((err) => {
|
||||
onUnexpectedError(err);
|
||||
// In case of an exception, fall back to computeMoreMinimalEdits
|
||||
return this.computeMoreMinimalEdits(resource, edits, true);
|
||||
});
|
||||
const result = this._workerManager.withWorker().then(client => client.computeHumanReadableDiff(resource, edits,
|
||||
{ ignoreTrimWhitespace: false, maxComputationTimeMs: 1000, computeMoves: false, })).catch((err) => {
|
||||
onUnexpectedError(err);
|
||||
// In case of an exception, fall back to computeMoreMinimalEdits
|
||||
return this.computeMoreMinimalEdits(resource, edits, true);
|
||||
});
|
||||
result.finally(() => this._logService.trace('FORMAT#computeHumanReadableDiff', resource.toString(true), sw.elapsed()));
|
||||
return result;
|
||||
|
||||
|
|
|
@ -1167,6 +1167,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
|
|||
this._documentDiffProvider.computeDiff(currentOriginalModel, currentModifiedModel, {
|
||||
ignoreTrimWhitespace: this._options.ignoreTrimWhitespace,
|
||||
maxComputationTimeMs: this._options.maxComputationTime,
|
||||
computeMoves: false,
|
||||
}).then(result => {
|
||||
if (currentToken === this._diffComputationToken
|
||||
&& currentOriginalModel === this._originalEditor.getModel()
|
||||
|
|
13
src/vs/editor/browser/widget/diffEditorWidget2/colors.ts
Normal file
13
src/vs/editor/browser/widget/diffEditorWidget2/colors.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { registerColor } from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
export const diffMoveBorder = registerColor(
|
||||
'diffEditor.move.border',
|
||||
{ dark: '#8b8b8b9c', light: '#8b8b8b9c', hcDark: '#8b8b8b9c', hcLight: '#8b8b8b9c', },
|
||||
localize('diffEditor.move.border', 'The border color for text that got moved in the diff editor.')
|
||||
);
|
|
@ -10,6 +10,7 @@ import { localize } from 'vs/nls';
|
|||
import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import './colors';
|
||||
|
||||
export class ToggleCollapseUnchangedRegions extends Action2 {
|
||||
constructor() {
|
||||
|
|
|
@ -8,6 +8,8 @@ import { findLast } from 'vs/base/common/arrays';
|
|||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IObservable, ISettableObservable, derived, keepAlive, observableValue, waitForState } from 'vs/base/common/observable';
|
||||
import { disposableObservableValue } from 'vs/base/common/observableImpl/base';
|
||||
import { isDefined } from 'vs/base/common/types';
|
||||
import { Constants } from 'vs/base/common/uint';
|
||||
import 'vs/css!./style';
|
||||
import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration';
|
||||
|
@ -19,14 +21,15 @@ import { IDiffCodeEditorWidgetOptions } from 'vs/editor/browser/widget/diffEdito
|
|||
import { diffAddDecoration, diffDeleteDecoration, diffFullLineAddDecoration, diffFullLineDeleteDecoration } from 'vs/editor/browser/widget/diffEditorWidget2/decorations';
|
||||
import { DiffEditorSash } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorSash';
|
||||
import { ViewZoneAlignment } from 'vs/editor/browser/widget/diffEditorWidget2/lineAlignment';
|
||||
import { MovedBlocksLinesPart } from 'vs/editor/browser/widget/diffEditorWidget2/movedBlocksLines';
|
||||
import { OverviewRulerPart } from 'vs/editor/browser/widget/diffEditorWidget2/overviewRulerPart';
|
||||
import { UnchangedRangesFeature } from 'vs/editor/browser/widget/diffEditorWidget2/unchangedRanges';
|
||||
import { ObservableElementSizeObserver, applyObservableDecorations } from 'vs/editor/browser/widget/diffEditorWidget2/utils';
|
||||
import { WorkerBasedDocumentDiffProvider } from 'vs/editor/browser/widget/workerBasedDocumentDiffProvider';
|
||||
import { EditorOptions, IDiffEditorOptions, ValidDiffEditorBaseOptions, clampedFloat, clampedInt, boolean as validateBooleanOption, stringSet as validateStringSetOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { IDimension } from 'vs/editor/common/core/dimension';
|
||||
import { LineRange } from 'vs/editor/common/core/lineRange';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { LineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
|
||||
import { IDiffComputationResult, ILineChange } from 'vs/editor/common/diff/smartLinesDiffComputer';
|
||||
import { EditorType, IContentSizeChangedEvent, IDiffEditorModel, IDiffEditorViewState } from 'vs/editor/common/editorCommon';
|
||||
import { IModelDeltaDecoration } from 'vs/editor/common/model';
|
||||
|
@ -35,7 +38,7 @@ 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 { DelegatingEditor } from './delegatingEditorImpl';
|
||||
import { DiffModel } from './diffModel';
|
||||
import { DiffMapping, DiffModel } from './diffModel';
|
||||
|
||||
const diffEditorDefaultOptions: ValidDiffEditorBaseOptions = {
|
||||
enableSplitViewResizing: true,
|
||||
|
@ -54,6 +57,7 @@ const diffEditorDefaultOptions: ValidDiffEditorBaseOptions = {
|
|||
accessibilityVerbose: false,
|
||||
experimental: {
|
||||
collapseUnchangedRegions: false,
|
||||
showMoves: false,
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -64,7 +68,7 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
|
|||
]);
|
||||
private readonly _model = observableValue<IDiffEditorModel | null>('diffEditorModel', null);
|
||||
public readonly onDidChangeModel = Event.fromObservableLight(this._model);
|
||||
private readonly _diffModel = observableValue<DiffModel | null>('diffModel', null);
|
||||
private readonly _diffModel = this._register(disposableObservableValue<DiffModel | undefined>('diffModel', undefined));
|
||||
private readonly _onDidContentSizeChange = this._register(new Emitter<IContentSizeChangedEvent>());
|
||||
public readonly onDidContentSizeChange = this._onDidContentSizeChange.event;
|
||||
private readonly _modifiedEditor: CodeEditorWidget;
|
||||
|
@ -75,7 +79,6 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
|
|||
);
|
||||
private readonly _rootSizeObserver: ObservableElementSizeObserver;
|
||||
private readonly _options: ISettableObservable<ValidDiffEditorBaseOptions>;
|
||||
private _isHandlingScrollEvent = false;
|
||||
private readonly _sash: DiffEditorSash;
|
||||
private readonly _renderOverviewRuler: IObservable<boolean>;
|
||||
|
||||
|
@ -124,7 +127,6 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
|
|||
this._register(new UnchangedRangesFeature(this._originalEditor, this._modifiedEditor, this._diffModel));
|
||||
this._register(new ViewZoneAlignment(this._originalEditor, this._modifiedEditor, this._diffModel));
|
||||
|
||||
|
||||
this._register(this._instantiationService.createInstance(OverviewRulerPart,
|
||||
this._originalEditor,
|
||||
this._modifiedEditor,
|
||||
|
@ -141,6 +143,15 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
|
|||
codeEditorService.addDiffEditor(this);
|
||||
|
||||
this._register(keepAlive(this._layoutInfo, true));
|
||||
|
||||
this._register(new MovedBlocksLinesPart(
|
||||
this.elements.root,
|
||||
this._diffModel,
|
||||
this._layoutInfo.map(i => i.originalEditor),
|
||||
this._layoutInfo.map(i => i.modifiedEditor),
|
||||
this._originalEditor,
|
||||
this._modifiedEditor,
|
||||
));
|
||||
}
|
||||
|
||||
private readonly _layoutInfo = derived('modifiedEditorLayoutInfo', (reader) => {
|
||||
|
@ -161,7 +172,10 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
|
|||
height
|
||||
});
|
||||
|
||||
return { modifiedEditor: this._modifiedEditor.getLayoutInfo() };
|
||||
return {
|
||||
modifiedEditor: this._modifiedEditor.getLayoutInfo(),
|
||||
originalEditor: this._originalEditor.getLayoutInfo(),
|
||||
};
|
||||
});
|
||||
|
||||
private readonly _decorations = derived('decorations', (reader) => {
|
||||
|
@ -170,23 +184,70 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
|
|||
return null;
|
||||
}
|
||||
|
||||
const currentMove = this._diffModel.read(reader)!.syncedMovedTexts.read(reader);
|
||||
|
||||
const originalDecorations: IModelDeltaDecoration[] = [];
|
||||
const modifiedDecorations: IModelDeltaDecoration[] = [];
|
||||
for (const c of diff.changes) {
|
||||
const fullRangeOriginal = c.originalRange.toInclusiveRange();
|
||||
if (fullRangeOriginal) {
|
||||
originalDecorations.push({ range: fullRangeOriginal, options: diffFullLineDeleteDecoration });
|
||||
}
|
||||
const fullRangeModified = c.modifiedRange.toInclusiveRange();
|
||||
if (fullRangeModified) {
|
||||
modifiedDecorations.push({ range: fullRangeModified, options: diffFullLineAddDecoration });
|
||||
for (const m of diff.mappings) {
|
||||
const fullRangeOriginal = LineRange.subtract(m.lineRangeMapping.originalRange, currentMove?.lineRangeMapping.originalRange)
|
||||
.map(i => i.toInclusiveRange()).filter(isDefined);
|
||||
for (const range of fullRangeOriginal) {
|
||||
originalDecorations.push({ range, options: diffFullLineDeleteDecoration });
|
||||
}
|
||||
|
||||
for (const i of c.innerChanges || []) {
|
||||
const fullRangeModified = LineRange.subtract(m.lineRangeMapping.modifiedRange, currentMove?.lineRangeMapping.modifiedRange)
|
||||
.map(i => i.toInclusiveRange()).filter(isDefined);
|
||||
for (const range of fullRangeModified) {
|
||||
modifiedDecorations.push({ range, options: diffFullLineAddDecoration });
|
||||
}
|
||||
|
||||
for (const i of m.lineRangeMapping.innerChanges || []) {
|
||||
if (currentMove
|
||||
&& (currentMove.lineRangeMapping.originalRange.intersect(new LineRange(i.originalRange.startLineNumber, i.originalRange.endLineNumber))
|
||||
|| currentMove.lineRangeMapping.modifiedRange.intersect(new LineRange(i.modifiedRange.startLineNumber, i.modifiedRange.endLineNumber)))) {
|
||||
continue;
|
||||
}
|
||||
originalDecorations.push({ range: i.originalRange, options: diffDeleteDecoration });
|
||||
modifiedDecorations.push({ range: i.modifiedRange, options: diffAddDecoration });
|
||||
}
|
||||
}
|
||||
|
||||
if (currentMove) {
|
||||
for (const m of currentMove.changes) {
|
||||
const fullRangeOriginal = m.originalRange.toInclusiveRange();
|
||||
if (fullRangeOriginal) {
|
||||
originalDecorations.push({ range: fullRangeOriginal, options: diffFullLineDeleteDecoration });
|
||||
}
|
||||
const fullRangeModified = m.modifiedRange.toInclusiveRange();
|
||||
if (fullRangeModified) {
|
||||
modifiedDecorations.push({ range: fullRangeModified, options: diffFullLineAddDecoration });
|
||||
}
|
||||
|
||||
for (const i of m.innerChanges || []) {
|
||||
originalDecorations.push({ range: i.originalRange, options: diffDeleteDecoration });
|
||||
modifiedDecorations.push({ range: i.modifiedRange, options: diffAddDecoration });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const m of diff.movedTexts) {
|
||||
originalDecorations.push({
|
||||
range: m.lineRangeMapping.originalRange.toInclusiveRange()!, options: {
|
||||
description: 'moved',
|
||||
blockClassName: 'movedOriginal',
|
||||
blockPadding: [MovedBlocksLinesPart.movedCodeBlockPadding, 0, MovedBlocksLinesPart.movedCodeBlockPadding, MovedBlocksLinesPart.movedCodeBlockPadding],
|
||||
}
|
||||
});
|
||||
|
||||
modifiedDecorations.push({
|
||||
range: m.lineRangeMapping.modifiedRange.toInclusiveRange()!, options: {
|
||||
description: 'moved',
|
||||
blockClassName: 'movedModified',
|
||||
blockPadding: [4, 0, 4, 4],
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return { originalDecorations, modifiedDecorations };
|
||||
});
|
||||
|
||||
|
@ -202,19 +263,34 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
|
|||
}
|
||||
|
||||
private _createLeftHandSideEditor(options: Readonly<IDiffEditorConstructionOptions>, codeEditorWidgetOptions: ICodeEditorWidgetOptions): CodeEditorWidget {
|
||||
const editor = this._createInnerEditor(this._instantiationService, this.elements.original, this._adjustOptionsForLeftHandSide(options), codeEditorWidgetOptions);
|
||||
const editor = this._constructInnerEditor(this._instantiationService, this.elements.original, this._adjustOptionsForLeftHandSide(options), codeEditorWidgetOptions);
|
||||
const isInDiffLeftEditorKey = this._contextKeyService.createKey<boolean>('isInDiffLeftEditor', editor.hasWidgetFocus());
|
||||
this._register(editor.onDidFocusEditorWidget(() => isInDiffLeftEditorKey.set(true)));
|
||||
this._register(editor.onDidBlurEditorWidget(() => isInDiffLeftEditorKey.set(false)));
|
||||
this._register(editor.onDidChangeCursorPosition(e => {
|
||||
const m = this._diffModel.get();
|
||||
if (!m) { return; }
|
||||
|
||||
const movedText = m.diff.get()!.movedTexts.find(m => m.lineRangeMapping.originalRange.contains(e.position.lineNumber));
|
||||
|
||||
m.syncedMovedTexts.set(movedText, undefined);
|
||||
}));
|
||||
return editor;
|
||||
}
|
||||
|
||||
private _createRightHandSideEditor(options: Readonly<IDiffEditorConstructionOptions>, codeEditorWidgetOptions: ICodeEditorWidgetOptions): CodeEditorWidget {
|
||||
const editor = this._createInnerEditor(this._instantiationService, this.elements.modified, this._adjustOptionsForRightHandSide(options), codeEditorWidgetOptions);
|
||||
const editor = this._constructInnerEditor(this._instantiationService, this.elements.modified, this._adjustOptionsForRightHandSide(options), codeEditorWidgetOptions);
|
||||
const isInDiffRightEditorKey = this._contextKeyService.createKey<boolean>('isInDiffRightEditor', editor.hasWidgetFocus());
|
||||
this._register(editor.onDidFocusEditorWidget(() => isInDiffRightEditorKey.set(true)));
|
||||
this._register(editor.onDidBlurEditorWidget(() => isInDiffRightEditorKey.set(false)));
|
||||
this._register(editor.onDidChangeCursorPosition(e => {
|
||||
const m = this._diffModel.get();
|
||||
if (!m) { return; }
|
||||
|
||||
const movedText = m.diff.get()!.movedTexts.find(m => m.lineRangeMapping.modifiedRange.contains(e.position.lineNumber));
|
||||
|
||||
m.syncedMovedTexts.set(movedText, undefined);
|
||||
}));
|
||||
// Revert change when an arrow is clicked.
|
||||
/*TODO
|
||||
this._register(editor.onMouseDown(event => {
|
||||
|
@ -238,8 +314,8 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
|
|||
return editor;
|
||||
}
|
||||
|
||||
protected _createInnerEditor(instantiationService: IInstantiationService, container: HTMLElement, options: Readonly<IEditorConstructionOptions>, editorWidgetOptions: ICodeEditorWidgetOptions): CodeEditorWidget {
|
||||
const editor = instantiationService.createInstance(CodeEditorWidget, container, options, editorWidgetOptions);
|
||||
protected _constructInnerEditor(instantiationService: IInstantiationService, container: HTMLElement, options: Readonly<IEditorConstructionOptions>, editorWidgetOptions: ICodeEditorWidgetOptions): CodeEditorWidget {
|
||||
const editor = this._createInnerEditor(instantiationService, container, options, editorWidgetOptions);
|
||||
|
||||
this._register(editor.onDidContentSizeChange(e => {
|
||||
const width = this._originalEditor.getContentWidth() + this._modifiedEditor.getContentWidth() + OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH;
|
||||
|
@ -252,32 +328,17 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
|
|||
contentWidthChanged: e.contentWidthChanged
|
||||
});
|
||||
}));
|
||||
return editor;
|
||||
}
|
||||
|
||||
this._register(editor.onDidScrollChange((e) => {
|
||||
if (this._isHandlingScrollEvent) {
|
||||
return;
|
||||
}
|
||||
if (!e.scrollTopChanged && !e.scrollLeftChanged && !e.scrollHeightChanged) {
|
||||
return;
|
||||
}
|
||||
this._isHandlingScrollEvent = true;
|
||||
try {
|
||||
const otherEditor = editor === this._originalEditor ? this._modifiedEditor : this._originalEditor;
|
||||
otherEditor.setScrollPosition({
|
||||
scrollLeft: e.scrollLeft,
|
||||
scrollTop: e.scrollTop
|
||||
});
|
||||
} finally {
|
||||
this._isHandlingScrollEvent = false;
|
||||
}
|
||||
}));
|
||||
|
||||
protected _createInnerEditor(instantiationService: IInstantiationService, container: HTMLElement, options: Readonly<IEditorConstructionOptions>, editorWidgetOptions: ICodeEditorWidgetOptions): CodeEditorWidget {
|
||||
const editor = instantiationService.createInstance(CodeEditorWidget, container, options, editorWidgetOptions);
|
||||
return editor;
|
||||
}
|
||||
|
||||
private _adjustOptionsForLeftHandSide(options: Readonly<IDiffEditorConstructionOptions>): IEditorConstructionOptions {
|
||||
const result = this._adjustOptionsForSubEditor(options);
|
||||
if (!options.renderSideBySide) {
|
||||
if (!this._options.get().renderSideBySide) {
|
||||
// never wrap hidden editor
|
||||
result.wordWrapOverride1 = 'off';
|
||||
result.wordWrapOverride2 = 'off';
|
||||
|
@ -289,7 +350,7 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
|
|||
result.ariaLabel = options.originalAriaLabel;
|
||||
}
|
||||
result.ariaLabel = this._updateAriaLabel(result.ariaLabel);
|
||||
result.readOnly = !options.originalEditable;
|
||||
result.readOnly = !this._options.get().originalEditable;
|
||||
result.dropIntoEditor = { enabled: !result.readOnly };
|
||||
result.extraEditorClassName = 'original-in-monaco-diff-editor';
|
||||
return result;
|
||||
|
@ -386,8 +447,9 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
|
|||
this._options.map(o => o.ignoreTrimWhitespace),
|
||||
this._options.map(o => o.maxComputationTime),
|
||||
this._options.map(o => o.experimental.collapseUnchangedRegions!),
|
||||
this._options.map(o => o.experimental.showMoves!),
|
||||
this._instantiationService.createInstance(WorkerBasedDocumentDiffProvider, this._options.get())
|
||||
) : null, undefined);
|
||||
) : undefined, undefined);
|
||||
}
|
||||
|
||||
override updateOptions(_newOptions: IDiffEditorOptions): void {
|
||||
|
@ -439,24 +501,24 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
|
|||
//throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
private _goTo(diff: LineRangeMapping): void {
|
||||
this._modifiedEditor.setPosition(new Position(diff.modifiedRange.startLineNumber, 1));
|
||||
this._modifiedEditor.revealRangeInCenter(diff.modifiedRange.toExclusiveRange());
|
||||
private _goTo(diff: DiffMapping): void {
|
||||
this._modifiedEditor.setPosition(new Position(diff.lineRangeMapping.modifiedRange.startLineNumber, 1));
|
||||
this._modifiedEditor.revealRangeInCenter(diff.lineRangeMapping.modifiedRange.toExclusiveRange());
|
||||
}
|
||||
|
||||
goToDiff(target: 'previous' | 'next'): void {
|
||||
const diffs = this._diffModel.get()?.diff.get()?.changes;
|
||||
const diffs = this._diffModel.get()?.diff.get()?.mappings;
|
||||
if (!diffs || diffs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const curLineNumber = this._modifiedEditor.getPosition()!.lineNumber;
|
||||
|
||||
let diff: LineRangeMapping | undefined;
|
||||
let diff: DiffMapping | undefined;
|
||||
if (target === 'next') {
|
||||
diff = diffs.find(d => d.modifiedRange.startLineNumber > curLineNumber) ?? diffs[0];
|
||||
diff = diffs.find(d => d.lineRangeMapping.modifiedRange.startLineNumber > curLineNumber) ?? diffs[0];
|
||||
} else {
|
||||
diff = findLast(diffs, d => d.modifiedRange.startLineNumber < curLineNumber) ?? diffs[diffs.length - 1];
|
||||
diff = findLast(diffs, d => d.lineRangeMapping.modifiedRange.startLineNumber < curLineNumber) ?? diffs[diffs.length - 1];
|
||||
}
|
||||
this._goTo(diff);
|
||||
}
|
||||
|
@ -468,7 +530,7 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
|
|||
}
|
||||
// wait for the diff computation to finish
|
||||
waitForState(diffModel.isDiffUpToDate, s => s).then(() => {
|
||||
const diffs = diffModel.diff.get()?.changes;
|
||||
const diffs = diffModel.diff.get()?.mappings;
|
||||
if (!diffs || diffs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
@ -495,6 +557,7 @@ function validateDiffEditorOptions(options: Readonly<IDiffEditorOptions>, defaul
|
|||
accessibilityVerbose: validateBooleanOption(options.accessibilityVerbose, defaults.accessibilityVerbose),
|
||||
experimental: {
|
||||
collapseUnchangedRegions: validateBooleanOption(options.experimental?.collapseUnchangedRegions, defaults.experimental.collapseUnchangedRegions!),
|
||||
showMoves: validateBooleanOption(options.experimental?.showMoves, defaults.experimental.showMoves!),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import { autorunWithStore2 } from 'vs/base/common/observableImpl/autorun';
|
|||
import { LineRange } from 'vs/editor/common/core/lineRange';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IDocumentDiff, IDocumentDiffProvider } from 'vs/editor/common/diff/documentDiffProvider';
|
||||
import { LineRangeMapping, RangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
|
||||
import { LineRangeMapping, MovedText, RangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
|
||||
import { lineRangeMappingFromRangeMappings } from 'vs/editor/common/diff/standardLinesDiffComputer';
|
||||
import { IDiffEditorModel } from 'vs/editor/common/editorCommon';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
|
@ -22,35 +22,39 @@ export class DiffModel extends Disposable {
|
|||
private readonly _isDiffUpToDate = observableValue<boolean>('isDiffUpToDate', false);
|
||||
public readonly isDiffUpToDate: IObservable<boolean> = this._isDiffUpToDate;
|
||||
|
||||
private readonly _diff = observableValue<IDocumentDiff | undefined>('diff', undefined);
|
||||
public readonly diff: IObservable<IDocumentDiff | undefined> = this._diff;
|
||||
private readonly _diff = observableValue<DiffState | undefined>('diff', undefined);
|
||||
public readonly diff: IObservable<DiffState | undefined> = this._diff;
|
||||
|
||||
private readonly _unchangedRegions = observableValue<{ regions: UnchangedRegion[]; originalDecorationIds: string[]; modifiedDecorationIds: string[] }>('unchangedRegion', { regions: [], originalDecorationIds: [], modifiedDecorationIds: [] });
|
||||
public readonly unchangedRegions: IObservable<UnchangedRegion[]> = derived('unchangedRegions', r =>
|
||||
this.hideUnchangedRegions.read(r) ? this._unchangedRegions.read(r).regions : []
|
||||
this._hideUnchangedRegions.read(r) ? this._unchangedRegions.read(r).regions : []
|
||||
);
|
||||
|
||||
public readonly syncedMovedTexts = observableValue<MovedText | undefined>('syncedMovedText', undefined);
|
||||
|
||||
constructor(
|
||||
model: IDiffEditorModel,
|
||||
ignoreTrimWhitespace: IObservable<boolean>,
|
||||
maxComputationTimeMs: IObservable<number>,
|
||||
private readonly hideUnchangedRegions: IObservable<boolean>,
|
||||
private readonly _hideUnchangedRegions: IObservable<boolean>,
|
||||
private readonly _showMoves: IObservable<boolean>,
|
||||
documentDiffProvider: IDocumentDiffProvider,
|
||||
) {
|
||||
super();
|
||||
|
||||
const contentChangedSignal = observableSignal('contentChangedSignal');
|
||||
const debouncer = this._register(new RunOnceScheduler(() => contentChangedSignal.trigger(undefined), 200));
|
||||
|
||||
this._register(model.modified.onDidChangeContent((e) => {
|
||||
const diff = this._diff.get();
|
||||
if (!diff) {
|
||||
return;
|
||||
}
|
||||
const textEdits = TextEditInfo.fromModelContentChanges(e.changes);
|
||||
/*const textEdits = TextEditInfo.fromModelContentChanges(e.changes);
|
||||
this._diff.set(
|
||||
applyModifiedEdits(diff, textEdits, model.original, model.modified),
|
||||
undefined
|
||||
);
|
||||
);*/
|
||||
debouncer.schedule();
|
||||
}));
|
||||
this._register(model.original.onDidChangeContent((e) => {
|
||||
|
@ -58,11 +62,11 @@ export class DiffModel extends Disposable {
|
|||
if (!diff) {
|
||||
return;
|
||||
}
|
||||
const textEdits = TextEditInfo.fromModelContentChanges(e.changes);
|
||||
/*const textEdits = TextEditInfo.fromModelContentChanges(e.changes);
|
||||
this._diff.set(
|
||||
applyOriginalEdits(diff, textEdits, model.original, model.modified),
|
||||
undefined
|
||||
);
|
||||
);*/
|
||||
debouncer.schedule();
|
||||
}));
|
||||
|
||||
|
@ -71,10 +75,7 @@ export class DiffModel extends Disposable {
|
|||
this._register(autorunWithStore2('compute diff', async (reader, store) => {
|
||||
debouncer.cancel();
|
||||
contentChangedSignal.read(reader);
|
||||
|
||||
documentDiffProviderOptionChanged.read(reader);
|
||||
const ignoreTrimWhitespaceVal = ignoreTrimWhitespace.read(reader);
|
||||
const maxComputationTimeMsVal = maxComputationTimeMs.read(reader);
|
||||
|
||||
this._isDiffUpToDate.set(false, undefined);
|
||||
|
||||
|
@ -91,8 +92,9 @@ export class DiffModel extends Disposable {
|
|||
}));
|
||||
|
||||
let result = await documentDiffProvider.computeDiff(model.original, model.modified, {
|
||||
ignoreTrimWhitespace: ignoreTrimWhitespaceVal,
|
||||
maxComputationTimeMs: maxComputationTimeMsVal,
|
||||
ignoreTrimWhitespace: ignoreTrimWhitespace.read(reader),
|
||||
maxComputationTimeMs: maxComputationTimeMs.read(reader),
|
||||
computeMoves: this._showMoves.read(reader),
|
||||
});
|
||||
|
||||
result = applyOriginalEdits(result, originalTextEditInfos, model.original, model.modified);
|
||||
|
@ -135,7 +137,7 @@ export class DiffModel extends Disposable {
|
|||
);
|
||||
|
||||
transaction(tx => {
|
||||
this._diff.set(result, tx);
|
||||
this._diff.set(DiffState.fromDiffResult(result), tx);
|
||||
this._isDiffUpToDate.set(true, tx);
|
||||
|
||||
this._unchangedRegions.set(
|
||||
|
@ -171,8 +173,47 @@ export class DiffModel extends Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
export class DiffState {
|
||||
public static fromDiffResult(result: IDocumentDiff): DiffState {
|
||||
return new DiffState(
|
||||
result.changes.map(c => new DiffMapping(c)),
|
||||
result.moves || []
|
||||
);
|
||||
}
|
||||
|
||||
constructor(
|
||||
public readonly mappings: readonly DiffMapping[],
|
||||
public readonly movedTexts: readonly MovedText[],
|
||||
) { }
|
||||
}
|
||||
|
||||
export class DiffMapping {
|
||||
constructor(
|
||||
readonly lineRangeMapping: LineRangeMapping,
|
||||
) {
|
||||
/*
|
||||
readonly movedTo: MovedText | undefined,
|
||||
readonly movedFrom: MovedText | undefined,
|
||||
|
||||
if (movedTo) {
|
||||
assertFn(() =>
|
||||
movedTo.lineRangeMapping.modifiedRange.equals(lineRangeMapping.modifiedRange)
|
||||
&& lineRangeMapping.originalRange.isEmpty
|
||||
&& !movedFrom
|
||||
);
|
||||
} else if (movedFrom) {
|
||||
assertFn(() =>
|
||||
movedFrom.lineRangeMapping.originalRange.equals(lineRangeMapping.originalRange)
|
||||
&& lineRangeMapping.modifiedRange.isEmpty
|
||||
&& !movedTo
|
||||
);
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
export class UnchangedRegion {
|
||||
public static fromDiffs(changes: LineRangeMapping[], originalLineCount: number, modifiedLineCount: number): UnchangedRegion[] {
|
||||
public static fromDiffs(changes: readonly LineRangeMapping[], originalLineCount: number, modifiedLineCount: number): UnchangedRegion[] {
|
||||
const inversedMappings = LineRangeMapping.inverse(changes, originalLineCount, modifiedLineCount);
|
||||
const result: UnchangedRegion[] = [];
|
||||
|
||||
|
@ -301,6 +342,7 @@ function applyOriginalEdits(diff: IDocumentDiff, textEdits: TextEditInfo[], orig
|
|||
identical: false,
|
||||
quitEarly: false,
|
||||
changes,
|
||||
moves: [],
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -340,5 +382,6 @@ function applyModifiedEdits(diff: IDocumentDiff, textEdits: TextEditInfo[], orig
|
|||
identical: false,
|
||||
quitEarly: false,
|
||||
changes,
|
||||
moves: [],
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,22 +5,33 @@
|
|||
|
||||
import { ArrayQueue } from 'vs/base/common/arrays';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IObservable, observableSignalFromEvent, derived } from 'vs/base/common/observable';
|
||||
import { autorunWithStore2 } from 'vs/base/common/observableImpl/autorun';
|
||||
import { IObservable, observableSignalFromEvent, derived, observableValue, observableFromEvent } from 'vs/base/common/observable';
|
||||
import { autorun, autorunWithStore2 } from 'vs/base/common/observableImpl/autorun';
|
||||
import { IViewZone } from 'vs/editor/browser/editorBrowser';
|
||||
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||
import { DiffModel } from 'vs/editor/browser/widget/diffEditorWidget2/diffModel';
|
||||
import { joinCombine } from 'vs/editor/browser/widget/diffEditorWidget2/utils';
|
||||
import { DiffMapping, DiffModel } from 'vs/editor/browser/widget/diffEditorWidget2/diffModel';
|
||||
import { animatedObservable, joinCombine } from 'vs/editor/browser/widget/diffEditorWidget2/utils';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { LineRange } from 'vs/editor/common/core/lineRange';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { LineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
|
||||
import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
|
||||
export class ViewZoneAlignment extends Disposable {
|
||||
private readonly _origExtraHeight = observableValue('origExtraHeight', 0);
|
||||
private readonly _modExtraHeight = observableValue('modExtraHeight', 0);
|
||||
|
||||
private readonly _originalScrollTop: IObservable<number>;
|
||||
private readonly _originalScrollOffset = observableValue<number, boolean>('originalScrollOffset', 0);
|
||||
private readonly _originalScrollOffsetAnimated = animatedObservable(this._originalScrollOffset, this._store);
|
||||
|
||||
private readonly _modifiedScrollTop: IObservable<number>;
|
||||
private readonly _modifiedScrollOffset = observableValue<number, boolean>('modifiedScrollOffset', 0);
|
||||
private readonly _modifiedScrollOffsetAnimated = animatedObservable(this._modifiedScrollOffset, this._store);
|
||||
|
||||
constructor(
|
||||
private readonly _originalEditor: CodeEditorWidget,
|
||||
private readonly _modifiedEditor: CodeEditorWidget,
|
||||
private readonly _diffModel: IObservable<DiffModel | null>,
|
||||
private readonly _diffModel: IObservable<DiffModel | undefined>,
|
||||
) {
|
||||
super();
|
||||
|
||||
|
@ -39,13 +50,28 @@ export class ViewZoneAlignment extends Disposable {
|
|||
const alignmentViewZoneIdsMod = new Set<string>();
|
||||
|
||||
const alignments = derived<IRangeAlignment[] | null>('alignments', (reader) => {
|
||||
const diff = this._diffModel.read(reader)?.diff.read(reader);
|
||||
if (!diff) { return null; }
|
||||
const diffModel = this._diffModel.read(reader);
|
||||
const diff = diffModel?.diff.read(reader);
|
||||
if (!diffModel || !diff) { return null; }
|
||||
|
||||
origViewZonesChanged.read(reader);
|
||||
modViewZonesChanged.read(reader);
|
||||
|
||||
return computeRangeAlignment(this._originalEditor, this._modifiedEditor, diff.changes, alignmentViewZoneIdsOrig, alignmentViewZoneIdsMod);
|
||||
return computeRangeAlignment(this._originalEditor, this._modifiedEditor, diff.mappings, alignmentViewZoneIdsOrig, alignmentViewZoneIdsMod);
|
||||
});
|
||||
|
||||
const alignmentsSyncedMovedText = derived<IRangeAlignment[] | null>('alignments', (reader) => {
|
||||
origViewZonesChanged.read(reader);
|
||||
modViewZonesChanged.read(reader);
|
||||
|
||||
const syncedMovedText = this._diffModel.read(reader)?.syncedMovedTexts.read(reader);
|
||||
if (!syncedMovedText) {
|
||||
return null;
|
||||
}
|
||||
const mappings = syncedMovedText.changes.map(c => new DiffMapping(c));
|
||||
|
||||
// TOD dont include alignments outside syncedMovedText
|
||||
return computeRangeAlignment(this._originalEditor, this._modifiedEditor, mappings, alignmentViewZoneIdsOrig, alignmentViewZoneIdsMod);
|
||||
});
|
||||
|
||||
function createFakeLinesDiv(): HTMLElement {
|
||||
|
@ -60,16 +86,43 @@ export class ViewZoneAlignment extends Disposable {
|
|||
const origViewZones: IViewZone[] = [];
|
||||
const modViewZones: IViewZone[] = [];
|
||||
|
||||
const _modExtraHeight = this._modExtraHeight.read(reader);
|
||||
if (_modExtraHeight > 0) {
|
||||
modViewZones.push({
|
||||
afterLineNumber: 0,
|
||||
domNode: document.createElement('div'),
|
||||
heightInPx: _modExtraHeight,
|
||||
});
|
||||
}
|
||||
const _origExtraHeight = this._origExtraHeight.read(reader);
|
||||
if (_origExtraHeight > 0) {
|
||||
origViewZones.push({
|
||||
afterLineNumber: 0,
|
||||
domNode: document.createElement('div'),
|
||||
heightInPx: _origExtraHeight,
|
||||
});
|
||||
}
|
||||
|
||||
const syncedMovedText = this._diffModel.read(reader)?.syncedMovedTexts.read(reader);
|
||||
|
||||
if (alignments_) {
|
||||
for (const a of alignments_) {
|
||||
const delta = a.modifiedHeightInPx - a.originalHeightInPx;
|
||||
if (delta > 0) {
|
||||
if (syncedMovedText?.lineRangeMapping.originalRange.contains(a.originalRange.endLineNumberExclusive - 1)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
origViewZones.push({
|
||||
afterLineNumber: a.originalRange.endLineNumberExclusive - 1,
|
||||
domNode: createFakeLinesDiv(),
|
||||
heightInPx: delta,
|
||||
});
|
||||
} else {
|
||||
if (syncedMovedText?.lineRangeMapping.modifiedRange.contains(a.modifiedRange.endLineNumberExclusive - 1)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
modViewZones.push({
|
||||
afterLineNumber: a.modifiedRange.endLineNumberExclusive - 1,
|
||||
domNode: createFakeLinesDiv(),
|
||||
|
@ -79,6 +132,28 @@ export class ViewZoneAlignment extends Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
for (const a of alignmentsSyncedMovedText.read(reader) ?? []) {
|
||||
if (!syncedMovedText?.lineRangeMapping.originalRange.intersect(a.originalRange)
|
||||
&& !syncedMovedText?.lineRangeMapping.modifiedRange.intersect(a.modifiedRange)) {
|
||||
// ignore unrelated alignments outside the synced moved text
|
||||
continue;
|
||||
}
|
||||
const delta = a.modifiedHeightInPx - a.originalHeightInPx;
|
||||
if (delta > 0) {
|
||||
origViewZones.push({
|
||||
afterLineNumber: a.originalRange.endLineNumberExclusive - 1,
|
||||
domNode: createFakeLinesDiv(),
|
||||
heightInPx: delta,
|
||||
});
|
||||
} else {
|
||||
modViewZones.push({
|
||||
afterLineNumber: a.modifiedRange.endLineNumberExclusive - 1,
|
||||
domNode: createFakeLinesDiv(),
|
||||
heightInPx: -delta,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { orig: origViewZones, mod: modViewZones };
|
||||
});
|
||||
|
||||
|
@ -98,47 +173,66 @@ export class ViewZoneAlignment extends Disposable {
|
|||
isChangingViewZones = false;
|
||||
}));
|
||||
|
||||
}
|
||||
}
|
||||
this._originalScrollTop = observableFromEvent(this._originalEditor.onDidScrollChange, () => this._originalEditor.getScrollTop());
|
||||
this._modifiedScrollTop = observableFromEvent(this._modifiedEditor.onDidScrollChange, () => this._modifiedEditor.getScrollTop());
|
||||
|
||||
interface AdditionalLineHeightInfo {
|
||||
lineNumber: number;
|
||||
heightInPx: number;
|
||||
}
|
||||
// origExtraHeight + origOffset - origScrollTop = modExtraHeight + modOffset - modScrollTop
|
||||
|
||||
function getAdditionalLineHeights(editor: CodeEditorWidget, viewZonesToIgnore: ReadonlySet<string>): readonly AdditionalLineHeightInfo[] {
|
||||
const viewZoneHeights: { lineNumber: number; heightInPx: number }[] = [];
|
||||
const wrappingZoneHeights: { lineNumber: number; heightInPx: number }[] = [];
|
||||
// origScrollTop = origExtraHeight + origOffset - modExtraHeight - modOffset + modScrollTop
|
||||
// modScrollTop = modExtraHeight + modOffset - origExtraHeight - origOffset + origScrollTop
|
||||
|
||||
const hasWrapping = editor.getOption(EditorOption.wrappingInfo).wrappingColumn !== -1;
|
||||
const coordinatesConverter = editor._getViewModel()!.coordinatesConverter;
|
||||
if (hasWrapping) {
|
||||
for (let i = 1; i <= editor.getModel()!.getLineCount(); i++) {
|
||||
const lineCount = coordinatesConverter.getModelLineViewLineCount(i);
|
||||
if (lineCount > 1) {
|
||||
wrappingZoneHeights.push({ lineNumber: i, heightInPx: lineCount - 1 });
|
||||
// origOffset - modOffset = heightOfLines(1..Y) - heightOfLines(1..X)
|
||||
// origScrollTop >= 0, modScrollTop >= 0
|
||||
|
||||
this._register(autorun('update scroll modified', (reader) => {
|
||||
const newScrollTopModified = this._originalScrollTop.read(reader)
|
||||
- (this._originalScrollOffsetAnimated.get() - this._modifiedScrollOffsetAnimated.read(reader))
|
||||
- (this._origExtraHeight.get() - this._modExtraHeight.read(reader));
|
||||
if (newScrollTopModified !== this._modifiedEditor.getScrollTop()) {
|
||||
this._modifiedEditor.setScrollTop(newScrollTopModified, ScrollType.Immediate);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(autorun('update scroll original', (reader) => {
|
||||
const newScrollTopOriginal = this._modifiedScrollTop.read(reader)
|
||||
- (this._modifiedScrollOffsetAnimated.get() - this._originalScrollOffsetAnimated.read(reader))
|
||||
- (this._modExtraHeight.get() - this._origExtraHeight.read(reader));
|
||||
if (newScrollTopOriginal !== this._originalEditor.getScrollTop()) {
|
||||
this._originalEditor.setScrollTop(newScrollTopOriginal, ScrollType.Immediate);
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
this._register(autorun('update', reader => {
|
||||
const m = this._diffModel.read(reader)?.syncedMovedTexts.read(reader);
|
||||
|
||||
let deltaOrigToMod = 0;
|
||||
if (m) {
|
||||
const trueTopOriginal = this._originalEditor.getTopForLineNumber(m.lineRangeMapping.originalRange.startLineNumber, true) - this._origExtraHeight.get();
|
||||
const trueTopModified = this._modifiedEditor.getTopForLineNumber(m.lineRangeMapping.modifiedRange.startLineNumber, true) - this._modExtraHeight.get();
|
||||
deltaOrigToMod = trueTopModified - trueTopOriginal;
|
||||
}
|
||||
|
||||
if (deltaOrigToMod > 0) {
|
||||
this._modExtraHeight.set(0, undefined);
|
||||
this._origExtraHeight.set(deltaOrigToMod, undefined);
|
||||
} else if (deltaOrigToMod < 0) {
|
||||
this._modExtraHeight.set(-deltaOrigToMod, undefined);
|
||||
this._origExtraHeight.set(0, undefined);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this._modExtraHeight.set(0, undefined);
|
||||
this._origExtraHeight.set(0, undefined);
|
||||
}, 400);
|
||||
}
|
||||
|
||||
if (this._modifiedEditor.hasTextFocus()) {
|
||||
this._originalScrollOffset.set(this._modifiedScrollOffset.get() - deltaOrigToMod, undefined, true);
|
||||
} else {
|
||||
this._modifiedScrollOffset.set(this._originalScrollOffset.get() + deltaOrigToMod, undefined, true);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
for (const w of editor.getWhitespaces()) {
|
||||
if (viewZonesToIgnore.has(w.id)) {
|
||||
continue;
|
||||
}
|
||||
const modelLineNumber = coordinatesConverter.convertViewPositionToModelPosition(
|
||||
new Position(w.afterLineNumber, 1)
|
||||
).lineNumber;
|
||||
viewZoneHeights.push({ lineNumber: modelLineNumber, heightInPx: w.height });
|
||||
}
|
||||
|
||||
const result = joinCombine(
|
||||
viewZoneHeights,
|
||||
wrappingZoneHeights,
|
||||
v => v.lineNumber,
|
||||
(v1, v2) => ({ lineNumber: v1.lineNumber, heightInPx: v1.heightInPx + v2.heightInPx })
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
interface IRangeAlignment {
|
||||
|
@ -153,7 +247,7 @@ interface IRangeAlignment {
|
|||
function computeRangeAlignment(
|
||||
originalEditor: CodeEditorWidget,
|
||||
modifiedEditor: CodeEditorWidget,
|
||||
diffs: LineRangeMapping[],
|
||||
diffs: readonly DiffMapping[],
|
||||
originalEditorAlignmentViewZones: ReadonlySet<string>,
|
||||
modifiedEditorAlignmentViewZones: ReadonlySet<string>,
|
||||
): IRangeAlignment[] {
|
||||
|
@ -211,7 +305,8 @@ function computeRangeAlignment(
|
|||
}
|
||||
}
|
||||
|
||||
for (const c of diffs) {
|
||||
for (const m of diffs) {
|
||||
const c = m.lineRangeMapping;
|
||||
handleAlignmentsOutsideOfDiffs(c.originalRange.startLineNumber, c.modifiedRange.startLineNumber);
|
||||
|
||||
const originalAdditionalHeight = originalLineHeightOverrides
|
||||
|
@ -235,3 +330,44 @@ function computeRangeAlignment(
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
interface AdditionalLineHeightInfo {
|
||||
lineNumber: number;
|
||||
heightInPx: number;
|
||||
}
|
||||
|
||||
function getAdditionalLineHeights(editor: CodeEditorWidget, viewZonesToIgnore: ReadonlySet<string>): readonly AdditionalLineHeightInfo[] {
|
||||
const viewZoneHeights: { lineNumber: number; heightInPx: number }[] = [];
|
||||
const wrappingZoneHeights: { lineNumber: number; heightInPx: number }[] = [];
|
||||
|
||||
const hasWrapping = editor.getOption(EditorOption.wrappingInfo).wrappingColumn !== -1;
|
||||
const coordinatesConverter = editor._getViewModel()!.coordinatesConverter;
|
||||
const editorLineHeight = editor.getOption(EditorOption.lineHeight);
|
||||
if (hasWrapping) {
|
||||
for (let i = 1; i <= editor.getModel()!.getLineCount(); i++) {
|
||||
const lineCount = coordinatesConverter.getModelLineViewLineCount(i);
|
||||
if (lineCount > 1) {
|
||||
wrappingZoneHeights.push({ lineNumber: i, heightInPx: editorLineHeight * (lineCount - 1) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const w of editor.getWhitespaces()) {
|
||||
if (viewZonesToIgnore.has(w.id)) {
|
||||
continue;
|
||||
}
|
||||
const modelLineNumber = coordinatesConverter.convertViewPositionToModelPosition(
|
||||
new Position(w.afterLineNumber, 1)
|
||||
).lineNumber;
|
||||
viewZoneHeights.push({ lineNumber: modelLineNumber, heightInPx: w.height });
|
||||
}
|
||||
|
||||
const result = joinCombine(
|
||||
viewZoneHeights,
|
||||
wrappingZoneHeights,
|
||||
v => v.lineNumber,
|
||||
(v1, v2) => ({ lineNumber: v1.lineNumber, heightInPx: v1.heightInPx + v2.heightInPx })
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IObservable, autorun, observableFromEvent } from 'vs/base/common/observable';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { DiffModel } from 'vs/editor/browser/widget/diffEditorWidget2/diffModel';
|
||||
import { EditorLayoutInfo } from 'vs/editor/common/config/editorOptions';
|
||||
import { LineRange } from 'vs/editor/common/core/lineRange';
|
||||
|
||||
export class MovedBlocksLinesPart extends Disposable {
|
||||
public static readonly movedCodeBlockPadding = 4;
|
||||
|
||||
constructor(
|
||||
private readonly _rootElement: HTMLElement,
|
||||
private readonly _diffModel: IObservable<DiffModel | undefined>,
|
||||
private readonly _originalEditorLayoutInfo: IObservable<EditorLayoutInfo | null>,
|
||||
private readonly _modifiedEditorLayoutInfo: IObservable<EditorLayoutInfo | null>,
|
||||
private readonly _originalEditor: ICodeEditor,
|
||||
private readonly _modifiedEditor: ICodeEditor,
|
||||
) {
|
||||
super();
|
||||
|
||||
const element = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
element.setAttribute('class', 'moved-blocks-lines');
|
||||
this._rootElement.appendChild(element);
|
||||
|
||||
this._register(autorun('update', (reader) => {
|
||||
const info = this._originalEditorLayoutInfo.read(reader);
|
||||
const info2 = this._modifiedEditorLayoutInfo.read(reader);
|
||||
if (!info || !info2) {
|
||||
return;
|
||||
}
|
||||
|
||||
element.style.left = `${info.width - info.verticalScrollbarWidth}px`;
|
||||
element.style.height = `${info.height}px`;
|
||||
element.style.width = `${info.verticalScrollbarWidth + info.contentLeft - MovedBlocksLinesPart.movedCodeBlockPadding}px`;
|
||||
}));
|
||||
|
||||
const originalScrollTop = observableFromEvent(this._originalEditor.onDidScrollChange, () => this._originalEditor.getScrollTop());
|
||||
const modifiedScrollTop = observableFromEvent(this._modifiedEditor.onDidScrollChange, () => this._modifiedEditor.getScrollTop());
|
||||
|
||||
|
||||
this._register(autorun('update', (reader) => {
|
||||
const info = this._originalEditorLayoutInfo.read(reader);
|
||||
const info2 = this._modifiedEditorLayoutInfo.read(reader);
|
||||
if (!info || !info2) {
|
||||
return;
|
||||
}
|
||||
const width = info.verticalScrollbarWidth + info.contentLeft - MovedBlocksLinesPart.movedCodeBlockPadding;
|
||||
|
||||
const moves = this._diffModel.read(reader)?.diff.read(reader)?.movedTexts;
|
||||
if (!moves) {
|
||||
return;
|
||||
}
|
||||
|
||||
element.replaceChildren();
|
||||
|
||||
let idx = 0;
|
||||
for (const m of moves) {
|
||||
function computeLineStart(range: LineRange, editor: ICodeEditor) {
|
||||
const t1 = editor.getTopForLineNumber(range.startLineNumber);
|
||||
const t2 = editor.getTopForLineNumber(range.endLineNumberExclusive);
|
||||
return (t1 + t2) / 2;
|
||||
}
|
||||
|
||||
const start = computeLineStart(m.lineRangeMapping.originalRange, this._originalEditor);
|
||||
const startOffset = originalScrollTop.read(reader);
|
||||
const end = computeLineStart(m.lineRangeMapping.modifiedRange, this._modifiedEditor);
|
||||
const endOffset = modifiedScrollTop.read(reader);
|
||||
|
||||
const top = start - startOffset;
|
||||
const bottom = end - endOffset;
|
||||
|
||||
const center = (width / 2) - moves.length * 5 + idx * 10;
|
||||
idx++;
|
||||
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
path.setAttribute('d', `M ${0} ${top} L ${center} ${top} L ${center} ${bottom} L ${width} ${bottom}`);
|
||||
|
||||
path.setAttribute('fill', 'none');
|
||||
element.appendChild(path);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ export class OverviewRulerPart extends Disposable {
|
|||
private readonly _originalEditor: CodeEditorWidget,
|
||||
private readonly _modifiedEditor: CodeEditorWidget,
|
||||
private readonly _rootElement: HTMLElement,
|
||||
private readonly _diffModel: IObservable<DiffModel | null>,
|
||||
private readonly _diffModel: IObservable<DiffModel | undefined>,
|
||||
private readonly _rootWidth: IObservable<number>,
|
||||
private readonly _rootHeight: IObservable<number>,
|
||||
private readonly _modifiedEditorLayoutInfo: IObservable<EditorLayoutInfo | null>,
|
||||
|
@ -92,7 +92,7 @@ export class OverviewRulerPart extends Disposable {
|
|||
|
||||
store.add(autorun('set overview ruler zones', (reader) => {
|
||||
const colors = currentColors.read(reader);
|
||||
const diff = m?.diff.read(reader)?.changes;
|
||||
const diff = m?.diff.read(reader)?.mappings;
|
||||
|
||||
function createZones(ranges: LineRange[], color: Color) {
|
||||
return ranges
|
||||
|
@ -100,8 +100,8 @@ export class OverviewRulerPart extends Disposable {
|
|||
.map(r => new OverviewRulerZone(r.startLineNumber, r.endLineNumberExclusive, r.length, color.toString()));
|
||||
}
|
||||
|
||||
originalOverviewRuler?.setZones(createZones((diff || []).map(d => d.originalRange), colors.removeColor));
|
||||
modifiedOverviewRuler?.setZones(createZones((diff || []).map(d => d.modifiedRange), colors.insertColor));
|
||||
originalOverviewRuler?.setZones(createZones((diff || []).map(d => d.lineRangeMapping.originalRange), colors.removeColor));
|
||||
modifiedOverviewRuler?.setZones(createZones((diff || []).map(d => d.lineRangeMapping.modifiedRange), colors.insertColor));
|
||||
}));
|
||||
|
||||
store.add(autorun('layout overview ruler', (reader) => {
|
||||
|
|
|
@ -59,3 +59,22 @@
|
|||
.merge-editor-conflict-actions > a:hover .codicon::before {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.movedOriginal {
|
||||
border: 2px solid var(--vscode-diffEditor-move-border);
|
||||
}
|
||||
|
||||
.movedModified {
|
||||
border: 2px solid var(--vscode-diffEditor-move-border);
|
||||
}
|
||||
|
||||
.moved-blocks-lines {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.moved-blocks-lines path {
|
||||
fill: none;
|
||||
stroke: var(--vscode-diffEditor-move-border);
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ export class UnchangedRangesFeature extends Disposable {
|
|||
constructor(
|
||||
private readonly _originalEditor: CodeEditorWidget,
|
||||
private readonly _modifiedEditor: CodeEditorWidget,
|
||||
private readonly _diffModel: IObservable<DiffModel | null>,
|
||||
private readonly _diffModel: IObservable<DiffModel | undefined>,
|
||||
) {
|
||||
super();
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { IDimension } from 'vs/base/browser/dom';
|
||||
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IObservable, ISettableObservable, autorun, observableFromEvent, observableValue, transaction } from 'vs/base/common/observable';
|
||||
import { IObservable, ISettableObservable, autorun, autorunHandleChanges, observableFromEvent, observableValue, transaction } from 'vs/base/common/observable';
|
||||
import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IModelDeltaDecoration } from 'vs/editor/common/model';
|
||||
|
@ -119,3 +119,54 @@ export class ObservableElementSizeObserver extends Disposable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function animatedObservable(base: IObservable<number, boolean>, store: DisposableStore): IObservable<number> {
|
||||
let targetVal = base.get();
|
||||
let startVal = targetVal;
|
||||
let curVal = targetVal;
|
||||
const result = observableValue('animatedValue', targetVal);
|
||||
|
||||
let animationStartMs: number = -1;
|
||||
const durationMs = 300;
|
||||
let animationFrame: number | undefined = undefined;
|
||||
|
||||
store.add(autorunHandleChanges('update value', {
|
||||
createEmptyChangeSummary: () => ({ animate: false }),
|
||||
handleChange: (ctx, s) => {
|
||||
if (ctx.didChange(base)) {
|
||||
s.animate = s.animate || ctx.change;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}, (reader, s) => {
|
||||
if (animationFrame !== undefined) {
|
||||
cancelAnimationFrame(animationFrame);
|
||||
animationFrame = undefined;
|
||||
}
|
||||
|
||||
startVal = curVal;
|
||||
targetVal = base.read(reader);
|
||||
animationStartMs = Date.now() - (s.animate ? 0 : durationMs);
|
||||
|
||||
update();
|
||||
}));
|
||||
|
||||
function update() {
|
||||
const passedMs = Date.now() - animationStartMs;
|
||||
curVal = Math.floor(easeOutExpo(passedMs, startVal, targetVal - startVal, durationMs));
|
||||
|
||||
if (passedMs < durationMs) {
|
||||
animationFrame = requestAnimationFrame(update);
|
||||
} else {
|
||||
curVal = targetVal;
|
||||
}
|
||||
|
||||
result.set(curVal, undefined);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function easeOutExpo(t: number, b: number, c: number, d: number): number {
|
||||
return t === d ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ export class WorkerBasedDocumentDiffProvider implements IDocumentDiffProvider, I
|
|||
],
|
||||
identical: false,
|
||||
quitEarly: false,
|
||||
moves: [],
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -207,6 +207,11 @@ const editorConfiguration: IConfigurationNode = {
|
|||
default: false,
|
||||
description: nls.localize('collapseUnchangedRegions', "Controls whether the diff editor shows unchanged regions. Only works when 'diffEditor.experimental.useVersion2' is set."),
|
||||
},
|
||||
'diffEditor.experimental.showMoves': {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: nls.localize('showMoves', "Controls whether the diff editor should show detected code moves. Only works when 'diffEditor.experimental.useVersion2' is set."),
|
||||
},
|
||||
'diffEditor.experimental.useVersion2': {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
|
|
|
@ -804,6 +804,10 @@ export interface IDiffEditorBaseOptions {
|
|||
* Defaults to false.
|
||||
*/
|
||||
collapseUnchangedRegions?: boolean;
|
||||
/**
|
||||
* Defaults to false.
|
||||
*/
|
||||
showMoves?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,24 @@ export class LineRange {
|
|||
return new LineRange(range.startLineNumber, range.endLineNumber);
|
||||
}
|
||||
|
||||
public static subtract(a: LineRange, b: LineRange | undefined): LineRange[] {
|
||||
if (!b) {
|
||||
return [a];
|
||||
}
|
||||
if (a.startLineNumber < b.startLineNumber && b.endLineNumberExclusive < a.endLineNumberExclusive) {
|
||||
return [
|
||||
new LineRange(a.startLineNumber, b.startLineNumber),
|
||||
new LineRange(b.endLineNumberExclusive, a.endLineNumberExclusive)
|
||||
];
|
||||
} else if (b.startLineNumber <= a.startLineNumber && a.endLineNumberExclusive <= b.endLineNumberExclusive) {
|
||||
return [];
|
||||
} else if (b.endLineNumberExclusive < a.endLineNumberExclusive) {
|
||||
return [new LineRange(Math.max(b.endLineNumberExclusive, a.startLineNumber), a.endLineNumberExclusive)];
|
||||
} else {
|
||||
return [new LineRange(a.startLineNumber, Math.min(b.startLineNumber, a.endLineNumberExclusive))];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param lineRanges An array of sorted line ranges.
|
||||
*/
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { LineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
|
||||
import { LineRangeMapping, MovedText } from 'vs/editor/common/diff/linesDiffComputer';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
|
||||
/**
|
||||
|
@ -36,6 +36,11 @@ export interface IDocumentDiffProviderOptions {
|
|||
* A diff computation should throw if it takes longer than this value.
|
||||
*/
|
||||
maxComputationTimeMs: number;
|
||||
|
||||
/**
|
||||
* If set, the diff computation should compute moves in addition to insertions and deletions.
|
||||
*/
|
||||
computeMoves: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -55,5 +60,11 @@ export interface IDocumentDiff {
|
|||
/**
|
||||
* Maps all modified line ranges in the original to the corresponding line ranges in the modified text model.
|
||||
*/
|
||||
readonly changes: LineRangeMapping[];
|
||||
readonly changes: readonly LineRangeMapping[];
|
||||
|
||||
/**
|
||||
* Sorted by original line ranges.
|
||||
* The original line ranges and the modified line ranges must be disjoint (but can be touching).
|
||||
*/
|
||||
readonly moves: readonly MovedText[];
|
||||
}
|
||||
|
|
|
@ -13,12 +13,19 @@ export interface ILinesDiffComputer {
|
|||
export interface ILinesDiffComputerOptions {
|
||||
readonly ignoreTrimWhitespace: boolean;
|
||||
readonly maxComputationTimeMs: number;
|
||||
readonly computeMoves: boolean;
|
||||
}
|
||||
|
||||
export class LinesDiff {
|
||||
constructor(
|
||||
readonly changes: readonly LineRangeMapping[],
|
||||
|
||||
/**
|
||||
* Sorted by original line ranges.
|
||||
* The original line ranges and the modified line ranges must be disjoint (but can be touching).
|
||||
*/
|
||||
readonly moves: readonly MovedText[],
|
||||
|
||||
/**
|
||||
* Indicates if the time out was reached.
|
||||
* In that case, the diffs might be an approximation and the user should be asked to rerun the diff with more time.
|
||||
|
@ -32,7 +39,7 @@ export class LinesDiff {
|
|||
* Maps a line range in the original text model to a line range in the modified text model.
|
||||
*/
|
||||
export class LineRangeMapping {
|
||||
public static inverse(mapping: LineRangeMapping[], originalLineCount: number, modifiedLineCount: number): LineRangeMapping[] {
|
||||
public static inverse(mapping: readonly LineRangeMapping[], originalLineCount: number, modifiedLineCount: number): LineRangeMapping[] {
|
||||
const result: LineRangeMapping[] = [];
|
||||
let lastOriginalEndLineNumber = 1;
|
||||
let lastModifiedEndLineNumber = 1;
|
||||
|
@ -124,3 +131,34 @@ export class RangeMapping {
|
|||
return `{${this.originalRange.toString()}->${this.modifiedRange.toString()}}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class SimpleLineRangeMapping {
|
||||
constructor(
|
||||
public readonly originalRange: LineRange,
|
||||
public readonly modifiedRange: LineRange,
|
||||
) {
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return `{${this.originalRange.toString()}->${this.modifiedRange.toString()}}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class MovedText {
|
||||
public readonly lineRangeMapping: SimpleLineRangeMapping;
|
||||
|
||||
/**
|
||||
* The diff from the original text to the moved text.
|
||||
* Must be contained in the original/modified line range.
|
||||
* Can be empty if the text didn't change (only moved).
|
||||
*/
|
||||
public readonly changes: readonly LineRangeMapping[];
|
||||
|
||||
constructor(
|
||||
lineRangeMapping: SimpleLineRangeMapping,
|
||||
changes: readonly LineRangeMapping[],
|
||||
) {
|
||||
this.lineRangeMapping = lineRangeMapping;
|
||||
this.changes = changes;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ export class SmartLinesDiffComputer implements ILinesDiffComputer {
|
|||
);
|
||||
});
|
||||
|
||||
return new LinesDiff(changes, result.quitEarly);
|
||||
return new LinesDiff(changes, [], result.quitEarly);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,7 +91,7 @@ export interface IDiffComputationResult {
|
|||
/**
|
||||
* The changes as (modern) line range mapping array.
|
||||
*/
|
||||
changes2: LineRangeMapping[];
|
||||
changes2: readonly LineRangeMapping[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,7 +13,7 @@ import { DateTimeout, ISequence, ITimeout, InfiniteTimeout, SequenceDiff } from
|
|||
import { DynamicProgrammingDiffing } from 'vs/editor/common/diff/algorithms/dynamicProgrammingDiffing';
|
||||
import { optimizeSequenceDiffs, smoothenSequenceDiffs } from 'vs/editor/common/diff/algorithms/joinSequenceDiffs';
|
||||
import { MyersDiffAlgorithm } from 'vs/editor/common/diff/algorithms/myersDiffAlgorithm';
|
||||
import { ILinesDiffComputer, ILinesDiffComputerOptions, LineRangeMapping, LinesDiff, RangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
|
||||
import { ILinesDiffComputer, ILinesDiffComputerOptions, LineRangeMapping, LinesDiff, MovedText, RangeMapping, SimpleLineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
|
||||
|
||||
export class StandardLinesDiffComputer implements ILinesDiffComputer {
|
||||
private readonly dynamicProgrammingDiffing = new DynamicProgrammingDiffing();
|
||||
|
@ -116,7 +116,41 @@ export class StandardLinesDiffComputer implements ILinesDiffComputer {
|
|||
scanForWhitespaceChanges(originalLines.length - seq1LastStart);
|
||||
|
||||
const changes = lineRangeMappingFromRangeMappings(alignments, originalLines, modifiedLines);
|
||||
return new LinesDiff(changes, hitTimeout);
|
||||
|
||||
const moves: MovedText[] = [];
|
||||
if (options.computeMoves) {
|
||||
const deletions = changes
|
||||
.filter(c => c.modifiedRange.isEmpty && c.originalRange.length >= 3)
|
||||
.map(d => new LineRangeFragment(d.originalRange, originalLines));
|
||||
const insertions = new Set(changes
|
||||
.filter(c => c.originalRange.isEmpty && c.modifiedRange.length >= 3)
|
||||
.map(d => new LineRangeFragment(d.modifiedRange, modifiedLines)));
|
||||
|
||||
for (const deletion of deletions) {
|
||||
let highestSimilarity = -1;
|
||||
let best: LineRangeFragment | undefined;
|
||||
for (const insertion of insertions) {
|
||||
const similarity = deletion.computeSimilarity(insertion);
|
||||
if (similarity > highestSimilarity) {
|
||||
highestSimilarity = similarity;
|
||||
best = insertion;
|
||||
}
|
||||
}
|
||||
|
||||
if (highestSimilarity > 0.90 && best) {
|
||||
const moveChanges = this.refineDiff(originalLines, modifiedLines, new SequenceDiff(
|
||||
new OffsetRange(deletion.range.startLineNumber - 1, deletion.range.endLineNumberExclusive - 1),
|
||||
new OffsetRange(best.range.startLineNumber - 1, best.range.endLineNumberExclusive - 1),
|
||||
), timeout, considerWhitespaceChanges);
|
||||
const mappings = lineRangeMappingFromRangeMappings(moveChanges.mappings, originalLines, modifiedLines);
|
||||
|
||||
insertions.delete(best);
|
||||
moves.push(new MovedText(new SimpleLineRangeMapping(deletion.range, best.range), mappings));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new LinesDiff(changes, moves, hitTimeout);
|
||||
}
|
||||
|
||||
private refineDiff(originalLines: string[], modifiedLines: string[], diff: SequenceDiff, timeout: ITimeout, considerWhitespaceChanges: boolean): { mappings: RangeMapping[]; hitTimeout: boolean } {
|
||||
|
@ -467,8 +501,8 @@ class Slice implements ISequence {
|
|||
}
|
||||
}
|
||||
|
||||
const offsetOfPrevLineBreak = i === 0 ? 0 : this.firstCharOffsetByLineMinusOne[i - 1];
|
||||
return new Position(this.lineRange.start + i + 1, offset - offsetOfPrevLineBreak + 1 + this.offsetByLine[i]);
|
||||
const offsetOfFirstCharInLine = i === 0 ? 0 : this.firstCharOffsetByLineMinusOne[i - 1];
|
||||
return new Position(this.lineRange.start + i + 1, offset - offsetOfFirstCharInLine + 1 + this.offsetByLine[i]);
|
||||
}
|
||||
|
||||
public translateRange(range: OffsetRange): Range {
|
||||
|
@ -558,3 +592,47 @@ function getCategory(charCode: number): CharBoundaryCategory {
|
|||
function isSpace(charCode: number): boolean {
|
||||
return charCode === CharCode.Space || charCode === CharCode.Tab;
|
||||
}
|
||||
|
||||
const chrKeys = new Map<string, number>();
|
||||
function getKey(chr: string): number {
|
||||
let key = chrKeys.get(chr);
|
||||
if (key === undefined) {
|
||||
key = chrKeys.size;
|
||||
chrKeys.set(chr, key);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
class LineRangeFragment {
|
||||
private readonly totalCount: number;
|
||||
private readonly histogram: number[] = [];
|
||||
constructor(
|
||||
public readonly range: LineRange,
|
||||
public readonly lines: string[],
|
||||
) {
|
||||
let counter = 0;
|
||||
for (let i = range.startLineNumber - 1; i < range.endLineNumberExclusive - 1; i++) {
|
||||
const line = lines[i];
|
||||
for (let j = 0; j < line.length; j++) {
|
||||
counter++;
|
||||
const chr = line[j];
|
||||
const key = getKey(chr);
|
||||
this.histogram[key] = (this.histogram[key] || 0) + 1;
|
||||
}
|
||||
counter++;
|
||||
const key = getKey('\n');
|
||||
this.histogram[key] = (this.histogram[key] || 0) + 1;
|
||||
}
|
||||
|
||||
this.totalCount = counter;
|
||||
}
|
||||
|
||||
public computeSimilarity(other: LineRangeFragment): number {
|
||||
let sumDifferences = 0;
|
||||
const maxLength = Math.max(this.histogram.length, other.histogram.length);
|
||||
for (let i = 0; i < maxLength; i++) {
|
||||
sumDifferences += Math.abs((this.histogram[i] ?? 0) - (other.histogram[i] ?? 0));
|
||||
}
|
||||
return 1 - (sumDifferences / (this.totalCount + other.totalCount));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,13 +15,13 @@ import { ensureValidWordDefinition, getWordAtText, IWordAtPosition } from 'vs/ed
|
|||
import { IColorInformation, IInplaceReplaceSupportResult, ILink, TextEdit } from 'vs/editor/common/languages';
|
||||
import { ILinkComputerTarget, computeLinks } from 'vs/editor/common/languages/linkComputer';
|
||||
import { BasicInplaceReplace } from 'vs/editor/common/languages/supports/inplaceReplaceSupport';
|
||||
import { DiffAlgorithmName, IDiffComputationResult, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker';
|
||||
import { DiffAlgorithmName, IDiffComputationResult, ILineChange, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker';
|
||||
import { createMonacoBaseAPI } from 'vs/editor/common/services/editorBaseApi';
|
||||
import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
import { UnicodeTextModelHighlighter, UnicodeHighlighterOptions } from 'vs/editor/common/services/unicodeTextModelHighlighter';
|
||||
import { DiffComputer, IChange } from 'vs/editor/common/diff/smartLinesDiffComputer';
|
||||
import { ILinesDiffComputer, ILinesDiffComputerOptions } from 'vs/editor/common/diff/linesDiffComputer';
|
||||
import { ILinesDiffComputer, ILinesDiffComputerOptions, LineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
|
||||
import { linesDiffComputers } from 'vs/editor/common/diff/linesDiffComputers';
|
||||
import { createProxyObject, getAllMethodNames } from 'vs/base/common/objects';
|
||||
import { IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider';
|
||||
|
@ -422,10 +422,8 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
|
|||
|
||||
const identical = (result.changes.length > 0 ? false : this._modelsAreIdentical(originalTextModel, modifiedTextModel));
|
||||
|
||||
return {
|
||||
identical,
|
||||
quitEarly: result.hitTimeout,
|
||||
changes: result.changes.map(m => ([m.originalRange.startLineNumber, m.originalRange.endLineNumberExclusive, m.modifiedRange.startLineNumber, m.modifiedRange.endLineNumberExclusive, m.innerChanges?.map(m => [
|
||||
function getLineChanges(changes: readonly LineRangeMapping[]): ILineChange[] {
|
||||
return changes.map(m => ([m.originalRange.startLineNumber, m.originalRange.endLineNumberExclusive, m.modifiedRange.startLineNumber, m.modifiedRange.endLineNumberExclusive, m.innerChanges?.map(m => [
|
||||
m.originalRange.startLineNumber,
|
||||
m.originalRange.startColumn,
|
||||
m.originalRange.endLineNumber,
|
||||
|
@ -434,7 +432,20 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
|
|||
m.modifiedRange.startColumn,
|
||||
m.modifiedRange.endLineNumber,
|
||||
m.modifiedRange.endColumn,
|
||||
])]))
|
||||
])]));
|
||||
}
|
||||
|
||||
return {
|
||||
identical,
|
||||
quitEarly: result.hitTimeout,
|
||||
changes: getLineChanges(result.changes),
|
||||
moves: result.moves.map(m => ([
|
||||
m.lineRangeMapping.originalRange.startLineNumber,
|
||||
m.lineRangeMapping.originalRange.endLineNumberExclusive,
|
||||
m.lineRangeMapping.modifiedRange.startLineNumber,
|
||||
m.lineRangeMapping.modifiedRange.endLineNumberExclusive,
|
||||
getLineChanges(m.changes)
|
||||
])),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ export interface IDiffComputationResult {
|
|||
quitEarly: boolean;
|
||||
changes: ILineChange[];
|
||||
identical: boolean;
|
||||
moves: ITextMove[];
|
||||
}
|
||||
|
||||
export type ILineChange = [
|
||||
|
@ -64,6 +65,14 @@ export type ICharChange = [
|
|||
modifiedEndColumn: number,
|
||||
];
|
||||
|
||||
export type ITextMove = [
|
||||
originalStartLine: number,
|
||||
originalEndLine: number,
|
||||
modifiedStartLine: number,
|
||||
modifiedEndLine: number,
|
||||
changes: ILineChange[],
|
||||
];
|
||||
|
||||
export interface IUnicodeHighlightsResult {
|
||||
ranges: IRange[];
|
||||
hasMore: boolean;
|
||||
|
|
|
@ -34,7 +34,7 @@ import { EditorCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensi
|
|||
import { IMenuItem, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry';
|
||||
import { LineRangeMapping, RangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
|
||||
import { LineRangeMapping, MovedText, RangeMapping, SimpleLineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
|
||||
import { LineRange } from 'vs/editor/common/core/lineRange';
|
||||
import { EditorZoom } from 'vs/editor/common/config/editorZoom';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
|
@ -586,6 +586,8 @@ export function createMonacoEditorAPI(): typeof monaco.editor {
|
|||
LineRangeMapping: <any>LineRangeMapping,
|
||||
RangeMapping: <any>RangeMapping,
|
||||
EditorZoom: <any>EditorZoom,
|
||||
MovedText: <any>MovedText,
|
||||
SimpleLineRangeMapping: <any>SimpleLineRangeMapping,
|
||||
|
||||
// vars
|
||||
EditorType: EditorType,
|
||||
|
|
|
@ -149,7 +149,7 @@ suite('EditorSimpleWorker', () => {
|
|||
const smallerEdits = await worker.computeHumanReadableDiff(
|
||||
model.uri.toString(),
|
||||
edits,
|
||||
{ ignoreTrimWhitespace: false, maxComputationTimeMs: 0 }
|
||||
{ ignoreTrimWhitespace: false, maxComputationTimeMs: 0, computeMoves: false }
|
||||
);
|
||||
|
||||
const t1 = applyEdits(model.getValue(), edits);
|
||||
|
|
|
@ -7,6 +7,7 @@ import * as assert from 'assert';
|
|||
import { readdirSync, readFileSync, existsSync, writeFileSync, rmSync } from 'fs';
|
||||
import { join, resolve } from 'path';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
import { LineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
|
||||
import { SmartLinesDiffComputer } from 'vs/editor/common/diff/smartLinesDiffComputer';
|
||||
import { StandardLinesDiffComputer } from 'vs/editor/common/diff/standardLinesDiffComputer';
|
||||
|
||||
|
@ -31,20 +32,32 @@ suite('diff fixtures', () => {
|
|||
|
||||
const diffingAlgo = diffingAlgoName === 'legacy' ? new SmartLinesDiffComputer() : new StandardLinesDiffComputer();
|
||||
|
||||
const diff = diffingAlgo.computeDiff(firstContentLines, secondContentLines, { ignoreTrimWhitespace: false, maxComputationTimeMs: Number.MAX_SAFE_INTEGER });
|
||||
const diff = diffingAlgo.computeDiff(firstContentLines, secondContentLines, { ignoreTrimWhitespace: false, maxComputationTimeMs: Number.MAX_SAFE_INTEGER, computeMoves: false });
|
||||
|
||||
const actualDiffingResult: DiffingResult = {
|
||||
original: { content: firstContent, fileName: `./${firstFileName}` },
|
||||
modified: { content: secondContent, fileName: `./${secondFileName}` },
|
||||
diffs: diff.changes.map<IDetailedDiff>(c => ({
|
||||
function getDiffs(changes: readonly LineRangeMapping[]): IDetailedDiff[] {
|
||||
return changes.map<IDetailedDiff>(c => ({
|
||||
originalRange: c.originalRange.toString(),
|
||||
modifiedRange: c.modifiedRange.toString(),
|
||||
innerChanges: c.innerChanges?.map<IDiff>(c => ({
|
||||
originalRange: c.originalRange.toString(),
|
||||
modifiedRange: c.modifiedRange.toString(),
|
||||
})) || null
|
||||
}));
|
||||
}
|
||||
|
||||
const actualDiffingResult: DiffingResult = {
|
||||
original: { content: firstContent, fileName: `./${firstFileName}` },
|
||||
modified: { content: secondContent, fileName: `./${secondFileName}` },
|
||||
diffs: getDiffs(diff.changes),
|
||||
moves: diff.moves.map(v => ({
|
||||
originalRange: v.lineRangeMapping.originalRange.toString(),
|
||||
modifiedRange: v.lineRangeMapping.modifiedRange.toString(),
|
||||
changes: getDiffs(v.changes),
|
||||
}))
|
||||
};
|
||||
if (actualDiffingResult.moves?.length === 0) {
|
||||
delete actualDiffingResult.moves;
|
||||
}
|
||||
|
||||
const expectedFilePath = join(folderPath, `${diffingAlgoName}.expected.diff.json`);
|
||||
const invalidFilePath = join(folderPath, `${diffingAlgoName}.invalid.diff.json`);
|
||||
|
@ -90,8 +103,8 @@ suite('diff fixtures', () => {
|
|||
}
|
||||
}
|
||||
|
||||
test(`uiae`, () => {
|
||||
runTest('subword', 'advanced');
|
||||
test(`test`, () => {
|
||||
runTest('move-1', 'advanced');
|
||||
});
|
||||
|
||||
for (const folder of folders) {
|
||||
|
@ -108,6 +121,7 @@ interface DiffingResult {
|
|||
modified: { content: string; fileName: string };
|
||||
|
||||
diffs: IDetailedDiff[];
|
||||
moves?: IMoveInfo[];
|
||||
}
|
||||
|
||||
interface IDetailedDiff {
|
||||
|
@ -120,3 +134,10 @@ interface IDiff {
|
|||
originalRange: string; // [1,18 -> 1,19]
|
||||
modifiedRange: string; // [1,18 -> 1,19]
|
||||
}
|
||||
|
||||
interface IMoveInfo {
|
||||
originalRange: string; // [startLineNumber, endLineNumberExclusive)
|
||||
modifiedRange: string; // [startLineNumber, endLineNumberExclusive)
|
||||
|
||||
changes?: IDetailedDiff[];
|
||||
}
|
||||
|
|
92
src/vs/editor/test/node/diffing/fixtures/move-1/1.tst
Normal file
92
src/vs/editor/test/node/diffing/fixtures/move-1/1.tst
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { IdleDeadline, runWhenIdle } from 'vs/base/common/async';
|
||||
import { BugIndicatingError, onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { setTimeout0 } from 'vs/base/common/platform';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
import { countEOL } from 'vs/editor/common/core/eolCounter';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes';
|
||||
import { EncodedTokenizationResult, IBackgroundTokenizationStore, IBackgroundTokenizer, ILanguageIdCodec, IState, ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/languages';
|
||||
import { nullTokenizeEncoded } from 'vs/editor/common/languages/nullTokenize';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { TokenizationTextModelPart } from 'vs/editor/common/model/tokenizationTextModelPart';
|
||||
import { IModelContentChangedEvent, IModelLanguageChangedEvent } from 'vs/editor/common/textModelEvents';
|
||||
import { ContiguousMultilineTokensBuilder } from 'vs/editor/common/tokens/contiguousMultilineTokensBuilder';
|
||||
import { LineTokens } from 'vs/editor/common/tokens/lineTokens';
|
||||
|
||||
const enum Constants {
|
||||
CHEAP_TOKENIZATION_LENGTH_LIMIT = 2048
|
||||
}
|
||||
|
||||
/**
|
||||
* An array that avoids being sparse by always
|
||||
* filling up unused indices with a default value.
|
||||
*/
|
||||
export class ContiguousGrowingArray<T> {
|
||||
|
||||
private _store: T[] = [];
|
||||
|
||||
constructor(
|
||||
private readonly _default: T
|
||||
) { }
|
||||
|
||||
public get(index: number): T {
|
||||
if (index < this._store.length) {
|
||||
return this._store[index];
|
||||
}
|
||||
return this._default;
|
||||
}
|
||||
|
||||
public set(index: number, value: T): void {
|
||||
while (index >= this._store.length) {
|
||||
this._store[this._store.length] = this._default;
|
||||
}
|
||||
this._store[index] = value;
|
||||
}
|
||||
|
||||
// TODO have `replace` instead of `delete` and `insert`
|
||||
public delete(deleteIndex: number, deleteCount: number): void {
|
||||
if (deleteCount === 0 || deleteIndex >= this._store.length) {
|
||||
return;
|
||||
}
|
||||
this._store.splice(deleteIndex, deleteCount);
|
||||
}
|
||||
|
||||
public insert(insertIndex: number, insertCount: number): void {
|
||||
if (insertCount === 0 || insertIndex >= this._store.length) {
|
||||
return;
|
||||
}
|
||||
const arr: T[] = [];
|
||||
for (let i = 0; i < insertCount; i++) {
|
||||
arr[i] = this._default;
|
||||
}
|
||||
this._store = arrays.arrayInsert(this._store, insertIndex, arr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the states at the start of each line and keeps track of which lines
|
||||
* must be re-tokenized. Also uses state equality to quickly validate lines
|
||||
* that don't need to be re-tokenized.
|
||||
*
|
||||
* For example, when typing on a line, the line gets marked as needing to be tokenized.
|
||||
* Once the line is tokenized, the end state is checked for equality against the begin
|
||||
* state of the next line. If the states are equal, tokenization doesn't need to run
|
||||
* again over the rest of the file. If the states are not equal, the next line gets marked
|
||||
* as needing to be tokenized.
|
||||
*/
|
||||
export class TokenizationStateStore {
|
||||
requestTokens(startLineNumber: number, endLineNumberExclusive: number): void {
|
||||
for (let lineNumber = startLineNumber; lineNumber < endLineNumberExclusive; lineNumber++) {
|
||||
this._stateStore.markMustBeTokenized(lineNumber - 1);
|
||||
}
|
||||
}
|
||||
}
|
92
src/vs/editor/test/node/diffing/fixtures/move-1/2.tst
Normal file
92
src/vs/editor/test/node/diffing/fixtures/move-1/2.tst
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { IdleDeadline, runWhenIdle } from 'vs/base/common/async';
|
||||
import { BugIndicatingError, onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { setTimeout0 } from 'vs/base/common/platform';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
import { countEOL } from 'vs/editor/common/core/eolCounter';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes';
|
||||
import { EncodedTokenizationResult, IBackgroundTokenizationStore, IBackgroundTokenizer, ILanguageIdCodec, IState, ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/languages';
|
||||
import { nullTokenizeEncoded } from 'vs/editor/common/languages/nullTokenize';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { TokenizationTextModelPart } from 'vs/editor/common/model/tokenizationTextModelPart';
|
||||
import { IModelContentChangedEvent, IModelLanguageChangedEvent } from 'vs/editor/common/textModelEvents';
|
||||
import { ContiguousMultilineTokensBuilder } from 'vs/editor/common/tokens/contiguousMultilineTokensBuilder';
|
||||
import { LineTokens } from 'vs/editor/common/tokens/lineTokens';
|
||||
|
||||
/**
|
||||
* An array that avoids being sparse by always
|
||||
* filling up unused indices with a default value.
|
||||
*/
|
||||
export class ContiguousGrowingArray<T> {
|
||||
|
||||
private _store: T[] = [];
|
||||
|
||||
constructor(
|
||||
private readonly _default: T
|
||||
) { }
|
||||
|
||||
public get(index: number): T {
|
||||
if (index < this._store.length) {
|
||||
return this._store[index];
|
||||
}
|
||||
return this._default;
|
||||
}
|
||||
|
||||
public set(index: number, value: T): void {
|
||||
while (index >= this._store.length) {
|
||||
this._store[this._store.length] = this._default;
|
||||
}
|
||||
this._store[index] = value;
|
||||
}
|
||||
|
||||
// TODO have `replace` instead of `delete` and `insert`
|
||||
public delete(deleteIndex: number, deleteCount: number): void {
|
||||
if (deleteCount === 0 || deleteIndex >= this._store.length) {
|
||||
return;
|
||||
}
|
||||
this._store.splice(deleteIndex, deleteCount);
|
||||
}
|
||||
|
||||
public insert(insertIndex: number, insertCount: number): void {
|
||||
if (insertCount === 0 || insertIndex >= this._store.length) {
|
||||
return;
|
||||
}
|
||||
const arr: T[] = [];
|
||||
for (let i = 0; i < insertCount; i++) {
|
||||
arr[i] = this._default;
|
||||
}
|
||||
this._store = arrays.arrayInsert(this._store, insertIndex, arr);
|
||||
}
|
||||
}
|
||||
|
||||
const enum Constants {
|
||||
CHEAP_TOKENIZATION_LENGTH_LIMIT = 1024
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the states at the start of each line and keeps track of which lines
|
||||
* must be re-tokenized. Also uses state equality to quickly validate lines
|
||||
* that don't need to be re-tokenized.
|
||||
*
|
||||
* For example, when typing on a line, the line gets marked as needing to be tokenized.
|
||||
* Once the line is tokenized, the end state is checked for equality against the begin
|
||||
* state of the next line. If the states are equal, tokenization doesn't need to run
|
||||
* again over the rest of the file. If the states are not equal, the next line gets marked
|
||||
* as needing to be tokenized.
|
||||
*/
|
||||
export class TokenizationStateStore {
|
||||
requestTokens(startLineNumber: number, endLineNumberExclusive: number): void {
|
||||
for (let lineNumber = startLineNumber; lineNumber < endLineNumberExclusive; lineNumber++) {
|
||||
this._stateStore.markMustBeTokenized(lineNumber - 1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"original": {
|
||||
"content": "/*---------------------------------------------------------------------------------------------\n * Copyright (c) Microsoft Corporation. All rights reserved.\n * Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport * as arrays from 'vs/base/common/arrays';\nimport { IdleDeadline, runWhenIdle } from 'vs/base/common/async';\nimport { BugIndicatingError, onUnexpectedError } from 'vs/base/common/errors';\nimport { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';\nimport { setTimeout0 } from 'vs/base/common/platform';\nimport { StopWatch } from 'vs/base/common/stopwatch';\nimport { countEOL } from 'vs/editor/common/core/eolCounter';\nimport { Position } from 'vs/editor/common/core/position';\nimport { IRange } from 'vs/editor/common/core/range';\nimport { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes';\nimport { EncodedTokenizationResult, IBackgroundTokenizationStore, IBackgroundTokenizer, ILanguageIdCodec, IState, ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/languages';\nimport { nullTokenizeEncoded } from 'vs/editor/common/languages/nullTokenize';\nimport { ITextModel } from 'vs/editor/common/model';\nimport { TextModel } from 'vs/editor/common/model/textModel';\nimport { TokenizationTextModelPart } from 'vs/editor/common/model/tokenizationTextModelPart';\nimport { IModelContentChangedEvent, IModelLanguageChangedEvent } from 'vs/editor/common/textModelEvents';\nimport { ContiguousMultilineTokensBuilder } from 'vs/editor/common/tokens/contiguousMultilineTokensBuilder';\nimport { LineTokens } from 'vs/editor/common/tokens/lineTokens';\n\nconst enum Constants {\n\tCHEAP_TOKENIZATION_LENGTH_LIMIT = 2048\n}\n\n/**\n * An array that avoids being sparse by always\n * filling up unused indices with a default value.\n */\nexport class ContiguousGrowingArray<T> {\n\n\tprivate _store: T[] = [];\n\n\tconstructor(\n\t\tprivate readonly _default: T\n\t) { }\n\n\tpublic get(index: number): T {\n\t\tif (index < this._store.length) {\n\t\t\treturn this._store[index];\n\t\t}\n\t\treturn this._default;\n\t}\n\n\tpublic set(index: number, value: T): void {\n\t\twhile (index >= this._store.length) {\n\t\t\tthis._store[this._store.length] = this._default;\n\t\t}\n\t\tthis._store[index] = value;\n\t}\n\n\t// TODO have `replace` instead of `delete` and `insert`\n\tpublic delete(deleteIndex: number, deleteCount: number): void {\n\t\tif (deleteCount === 0 || deleteIndex >= this._store.length) {\n\t\t\treturn;\n\t\t}\n\t\tthis._store.splice(deleteIndex, deleteCount);\n\t}\n\n\tpublic insert(insertIndex: number, insertCount: number): void {\n\t\tif (insertCount === 0 || insertIndex >= this._store.length) {\n\t\t\treturn;\n\t\t}\n\t\tconst arr: T[] = [];\n\t\tfor (let i = 0; i < insertCount; i++) {\n\t\t\tarr[i] = this._default;\n\t\t}\n\t\tthis._store = arrays.arrayInsert(this._store, insertIndex, arr);\n\t}\n}\n\n/**\n * Stores the states at the start of each line and keeps track of which lines\n * must be re-tokenized. Also uses state equality to quickly validate lines\n * that don't need to be re-tokenized.\n *\n * For example, when typing on a line, the line gets marked as needing to be tokenized.\n * Once the line is tokenized, the end state is checked for equality against the begin\n * state of the next line. If the states are equal, tokenization doesn't need to run\n * again over the rest of the file. If the states are not equal, the next line gets marked\n * as needing to be tokenized.\n */\nexport class TokenizationStateStore {\n\trequestTokens(startLineNumber: number, endLineNumberExclusive: number): void {\n\t\tfor (let lineNumber = startLineNumber; lineNumber < endLineNumberExclusive; lineNumber++) {\n\t\t\tthis._stateStore.markMustBeTokenized(lineNumber - 1);\n\t\t}\n\t}\n}\n",
|
||||
"fileName": "./1.tst"
|
||||
},
|
||||
"modified": {
|
||||
"content": "/*---------------------------------------------------------------------------------------------\n * Copyright (c) Microsoft Corporation. All rights reserved.\n * Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport * as arrays from 'vs/base/common/arrays';\nimport { IdleDeadline, runWhenIdle } from 'vs/base/common/async';\nimport { BugIndicatingError, onUnexpectedError } from 'vs/base/common/errors';\nimport { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';\nimport { setTimeout0 } from 'vs/base/common/platform';\nimport { StopWatch } from 'vs/base/common/stopwatch';\nimport { countEOL } from 'vs/editor/common/core/eolCounter';\nimport { Position } from 'vs/editor/common/core/position';\nimport { IRange } from 'vs/editor/common/core/range';\nimport { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes';\nimport { EncodedTokenizationResult, IBackgroundTokenizationStore, IBackgroundTokenizer, ILanguageIdCodec, IState, ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/languages';\nimport { nullTokenizeEncoded } from 'vs/editor/common/languages/nullTokenize';\nimport { ITextModel } from 'vs/editor/common/model';\nimport { TextModel } from 'vs/editor/common/model/textModel';\nimport { TokenizationTextModelPart } from 'vs/editor/common/model/tokenizationTextModelPart';\nimport { IModelContentChangedEvent, IModelLanguageChangedEvent } from 'vs/editor/common/textModelEvents';\nimport { ContiguousMultilineTokensBuilder } from 'vs/editor/common/tokens/contiguousMultilineTokensBuilder';\nimport { LineTokens } from 'vs/editor/common/tokens/lineTokens';\n\n/**\n * An array that avoids being sparse by always\n * filling up unused indices with a default value.\n */\nexport class ContiguousGrowingArray<T> {\n\n\tprivate _store: T[] = [];\n\n\tconstructor(\n\t\tprivate readonly _default: T\n\t) { }\n\n\tpublic get(index: number): T {\n\t\tif (index < this._store.length) {\n\t\t\treturn this._store[index];\n\t\t}\n\t\treturn this._default;\n\t}\n\n\tpublic set(index: number, value: T): void {\n\t\twhile (index >= this._store.length) {\n\t\t\tthis._store[this._store.length] = this._default;\n\t\t}\n\t\tthis._store[index] = value;\n\t}\n\n\t// TODO have `replace` instead of `delete` and `insert`\n\tpublic delete(deleteIndex: number, deleteCount: number): void {\n\t\tif (deleteCount === 0 || deleteIndex >= this._store.length) {\n\t\t\treturn;\n\t\t}\n\t\tthis._store.splice(deleteIndex, deleteCount);\n\t}\n\n\tpublic insert(insertIndex: number, insertCount: number): void {\n\t\tif (insertCount === 0 || insertIndex >= this._store.length) {\n\t\t\treturn;\n\t\t}\n\t\tconst arr: T[] = [];\n\t\tfor (let i = 0; i < insertCount; i++) {\n\t\t\tarr[i] = this._default;\n\t\t}\n\t\tthis._store = arrays.arrayInsert(this._store, insertIndex, arr);\n\t}\n}\n\nconst enum Constants {\n\tCHEAP_TOKENIZATION_LENGTH_LIMIT = 1024\n}\n\n/**\n * Stores the states at the start of each line and keeps track of which lines\n * must be re-tokenized. Also uses state equality to quickly validate lines\n * that don't need to be re-tokenized.\n *\n * For example, when typing on a line, the line gets marked as needing to be tokenized.\n * Once the line is tokenized, the end state is checked for equality against the begin\n * state of the next line. If the states are equal, tokenization doesn't need to run\n * again over the rest of the file. If the states are not equal, the next line gets marked\n * as needing to be tokenized.\n */\nexport class TokenizationStateStore {\n\trequestTokens(startLineNumber: number, endLineNumberExclusive: number): void {\n\t\tfor (let lineNumber = startLineNumber; lineNumber < endLineNumberExclusive; lineNumber++) {\n\t\t\tthis._stateStore.markMustBeTokenized(lineNumber - 1);\n\t\t}\n\t}\n}\n",
|
||||
"fileName": "./2.tst"
|
||||
},
|
||||
"diffs": [
|
||||
{
|
||||
"originalRange": "[24,28)",
|
||||
"modifiedRange": "[24,24)",
|
||||
"innerChanges": [
|
||||
{
|
||||
"originalRange": "[24,1 -> 28,1]",
|
||||
"modifiedRange": "[24,1 -> 24,1]"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"originalRange": "[74,74)",
|
||||
"modifiedRange": "[70,74)",
|
||||
"innerChanges": [
|
||||
{
|
||||
"originalRange": "[74,1 -> 74,1]",
|
||||
"modifiedRange": "[70,1 -> 74,1]"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"original": {
|
||||
"content": "/*---------------------------------------------------------------------------------------------\n * Copyright (c) Microsoft Corporation. All rights reserved.\n * Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport * as arrays from 'vs/base/common/arrays';\nimport { IdleDeadline, runWhenIdle } from 'vs/base/common/async';\nimport { BugIndicatingError, onUnexpectedError } from 'vs/base/common/errors';\nimport { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';\nimport { setTimeout0 } from 'vs/base/common/platform';\nimport { StopWatch } from 'vs/base/common/stopwatch';\nimport { countEOL } from 'vs/editor/common/core/eolCounter';\nimport { Position } from 'vs/editor/common/core/position';\nimport { IRange } from 'vs/editor/common/core/range';\nimport { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes';\nimport { EncodedTokenizationResult, IBackgroundTokenizationStore, IBackgroundTokenizer, ILanguageIdCodec, IState, ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/languages';\nimport { nullTokenizeEncoded } from 'vs/editor/common/languages/nullTokenize';\nimport { ITextModel } from 'vs/editor/common/model';\nimport { TextModel } from 'vs/editor/common/model/textModel';\nimport { TokenizationTextModelPart } from 'vs/editor/common/model/tokenizationTextModelPart';\nimport { IModelContentChangedEvent, IModelLanguageChangedEvent } from 'vs/editor/common/textModelEvents';\nimport { ContiguousMultilineTokensBuilder } from 'vs/editor/common/tokens/contiguousMultilineTokensBuilder';\nimport { LineTokens } from 'vs/editor/common/tokens/lineTokens';\n\nconst enum Constants {\n\tCHEAP_TOKENIZATION_LENGTH_LIMIT = 2048\n}\n\n/**\n * An array that avoids being sparse by always\n * filling up unused indices with a default value.\n */\nexport class ContiguousGrowingArray<T> {\n\n\tprivate _store: T[] = [];\n\n\tconstructor(\n\t\tprivate readonly _default: T\n\t) { }\n\n\tpublic get(index: number): T {\n\t\tif (index < this._store.length) {\n\t\t\treturn this._store[index];\n\t\t}\n\t\treturn this._default;\n\t}\n\n\tpublic set(index: number, value: T): void {\n\t\twhile (index >= this._store.length) {\n\t\t\tthis._store[this._store.length] = this._default;\n\t\t}\n\t\tthis._store[index] = value;\n\t}\n\n\t// TODO have `replace` instead of `delete` and `insert`\n\tpublic delete(deleteIndex: number, deleteCount: number): void {\n\t\tif (deleteCount === 0 || deleteIndex >= this._store.length) {\n\t\t\treturn;\n\t\t}\n\t\tthis._store.splice(deleteIndex, deleteCount);\n\t}\n\n\tpublic insert(insertIndex: number, insertCount: number): void {\n\t\tif (insertCount === 0 || insertIndex >= this._store.length) {\n\t\t\treturn;\n\t\t}\n\t\tconst arr: T[] = [];\n\t\tfor (let i = 0; i < insertCount; i++) {\n\t\t\tarr[i] = this._default;\n\t\t}\n\t\tthis._store = arrays.arrayInsert(this._store, insertIndex, arr);\n\t}\n}\n\n/**\n * Stores the states at the start of each line and keeps track of which lines\n * must be re-tokenized. Also uses state equality to quickly validate lines\n * that don't need to be re-tokenized.\n *\n * For example, when typing on a line, the line gets marked as needing to be tokenized.\n * Once the line is tokenized, the end state is checked for equality against the begin\n * state of the next line. If the states are equal, tokenization doesn't need to run\n * again over the rest of the file. If the states are not equal, the next line gets marked\n * as needing to be tokenized.\n */\nexport class TokenizationStateStore {\n\trequestTokens(startLineNumber: number, endLineNumberExclusive: number): void {\n\t\tfor (let lineNumber = startLineNumber; lineNumber < endLineNumberExclusive; lineNumber++) {\n\t\t\tthis._stateStore.markMustBeTokenized(lineNumber - 1);\n\t\t}\n\t}\n}\n",
|
||||
"fileName": "./1.tst"
|
||||
},
|
||||
"modified": {
|
||||
"content": "/*---------------------------------------------------------------------------------------------\n * Copyright (c) Microsoft Corporation. All rights reserved.\n * Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport * as arrays from 'vs/base/common/arrays';\nimport { IdleDeadline, runWhenIdle } from 'vs/base/common/async';\nimport { BugIndicatingError, onUnexpectedError } from 'vs/base/common/errors';\nimport { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';\nimport { setTimeout0 } from 'vs/base/common/platform';\nimport { StopWatch } from 'vs/base/common/stopwatch';\nimport { countEOL } from 'vs/editor/common/core/eolCounter';\nimport { Position } from 'vs/editor/common/core/position';\nimport { IRange } from 'vs/editor/common/core/range';\nimport { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes';\nimport { EncodedTokenizationResult, IBackgroundTokenizationStore, IBackgroundTokenizer, ILanguageIdCodec, IState, ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/languages';\nimport { nullTokenizeEncoded } from 'vs/editor/common/languages/nullTokenize';\nimport { ITextModel } from 'vs/editor/common/model';\nimport { TextModel } from 'vs/editor/common/model/textModel';\nimport { TokenizationTextModelPart } from 'vs/editor/common/model/tokenizationTextModelPart';\nimport { IModelContentChangedEvent, IModelLanguageChangedEvent } from 'vs/editor/common/textModelEvents';\nimport { ContiguousMultilineTokensBuilder } from 'vs/editor/common/tokens/contiguousMultilineTokensBuilder';\nimport { LineTokens } from 'vs/editor/common/tokens/lineTokens';\n\n/**\n * An array that avoids being sparse by always\n * filling up unused indices with a default value.\n */\nexport class ContiguousGrowingArray<T> {\n\n\tprivate _store: T[] = [];\n\n\tconstructor(\n\t\tprivate readonly _default: T\n\t) { }\n\n\tpublic get(index: number): T {\n\t\tif (index < this._store.length) {\n\t\t\treturn this._store[index];\n\t\t}\n\t\treturn this._default;\n\t}\n\n\tpublic set(index: number, value: T): void {\n\t\twhile (index >= this._store.length) {\n\t\t\tthis._store[this._store.length] = this._default;\n\t\t}\n\t\tthis._store[index] = value;\n\t}\n\n\t// TODO have `replace` instead of `delete` and `insert`\n\tpublic delete(deleteIndex: number, deleteCount: number): void {\n\t\tif (deleteCount === 0 || deleteIndex >= this._store.length) {\n\t\t\treturn;\n\t\t}\n\t\tthis._store.splice(deleteIndex, deleteCount);\n\t}\n\n\tpublic insert(insertIndex: number, insertCount: number): void {\n\t\tif (insertCount === 0 || insertIndex >= this._store.length) {\n\t\t\treturn;\n\t\t}\n\t\tconst arr: T[] = [];\n\t\tfor (let i = 0; i < insertCount; i++) {\n\t\t\tarr[i] = this._default;\n\t\t}\n\t\tthis._store = arrays.arrayInsert(this._store, insertIndex, arr);\n\t}\n}\n\nconst enum Constants {\n\tCHEAP_TOKENIZATION_LENGTH_LIMIT = 1024\n}\n\n/**\n * Stores the states at the start of each line and keeps track of which lines\n * must be re-tokenized. Also uses state equality to quickly validate lines\n * that don't need to be re-tokenized.\n *\n * For example, when typing on a line, the line gets marked as needing to be tokenized.\n * Once the line is tokenized, the end state is checked for equality against the begin\n * state of the next line. If the states are equal, tokenization doesn't need to run\n * again over the rest of the file. If the states are not equal, the next line gets marked\n * as needing to be tokenized.\n */\nexport class TokenizationStateStore {\n\trequestTokens(startLineNumber: number, endLineNumberExclusive: number): void {\n\t\tfor (let lineNumber = startLineNumber; lineNumber < endLineNumberExclusive; lineNumber++) {\n\t\t\tthis._stateStore.markMustBeTokenized(lineNumber - 1);\n\t\t}\n\t}\n}\n",
|
||||
"fileName": "./2.tst"
|
||||
},
|
||||
"diffs": [
|
||||
{
|
||||
"originalRange": "[25,29)",
|
||||
"modifiedRange": "[25,25)",
|
||||
"innerChanges": null
|
||||
},
|
||||
{
|
||||
"originalRange": "[75,75)",
|
||||
"modifiedRange": "[71,75)",
|
||||
"innerChanges": null
|
||||
}
|
||||
]
|
||||
}
|
36
src/vs/monaco.d.ts
vendored
36
src/vs/monaco.d.ts
vendored
|
@ -2390,6 +2390,10 @@ declare namespace monaco.editor {
|
|||
* A diff computation should throw if it takes longer than this value.
|
||||
*/
|
||||
maxComputationTimeMs: number;
|
||||
/**
|
||||
* If set, the diff computation should compute moves in addition to insertions and deletions.
|
||||
*/
|
||||
computeMoves: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2407,7 +2411,12 @@ declare namespace monaco.editor {
|
|||
/**
|
||||
* Maps all modified line ranges in the original to the corresponding line ranges in the modified text model.
|
||||
*/
|
||||
readonly changes: LineRangeMapping[];
|
||||
readonly changes: readonly LineRangeMapping[];
|
||||
/**
|
||||
* Sorted by original line ranges.
|
||||
* The original line ranges and the modified line ranges must be disjoint (but can be touching).
|
||||
*/
|
||||
readonly moves: readonly MovedText[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2415,6 +2424,7 @@ declare namespace monaco.editor {
|
|||
*/
|
||||
export class LineRange {
|
||||
static fromRange(range: Range): LineRange;
|
||||
static subtract(a: LineRange, b: LineRange | undefined): LineRange[];
|
||||
/**
|
||||
* @param lineRanges An array of sorted line ranges.
|
||||
*/
|
||||
|
@ -2471,7 +2481,7 @@ declare namespace monaco.editor {
|
|||
* Maps a line range in the original text model to a line range in the modified text model.
|
||||
*/
|
||||
export class LineRangeMapping {
|
||||
static inverse(mapping: LineRangeMapping[], originalLineCount: number, modifiedLineCount: number): LineRangeMapping[];
|
||||
static inverse(mapping: readonly LineRangeMapping[], originalLineCount: number, modifiedLineCount: number): LineRangeMapping[];
|
||||
/**
|
||||
* The line range in the original text model.
|
||||
*/
|
||||
|
@ -2507,6 +2517,24 @@ declare namespace monaco.editor {
|
|||
constructor(originalRange: Range, modifiedRange: Range);
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
export class MovedText {
|
||||
readonly lineRangeMapping: SimpleLineRangeMapping;
|
||||
/**
|
||||
* The diff from the original text to the moved text.
|
||||
* Must be contained in the original/modified line range.
|
||||
* Can be empty if the text didn't change (only moved).
|
||||
*/
|
||||
readonly changes: readonly LineRangeMapping[];
|
||||
constructor(lineRangeMapping: SimpleLineRangeMapping, changes: readonly LineRangeMapping[]);
|
||||
}
|
||||
|
||||
export class SimpleLineRangeMapping {
|
||||
readonly originalRange: LineRange;
|
||||
readonly modifiedRange: LineRange;
|
||||
constructor(originalRange: LineRange, modifiedRange: LineRange);
|
||||
toString(): string;
|
||||
}
|
||||
export interface IDimension {
|
||||
width: number;
|
||||
height: number;
|
||||
|
@ -3911,6 +3939,10 @@ declare namespace monaco.editor {
|
|||
* Defaults to false.
|
||||
*/
|
||||
collapseUnchangedRegions?: boolean;
|
||||
/**
|
||||
* Defaults to false.
|
||||
*/
|
||||
showMoves?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -514,7 +514,7 @@ export class InteractiveEditorController implements IEditorContribution {
|
|||
|
||||
const textModelNplus1 = this._modelService.createModel(createTextBufferFactoryFromSnapshot(this._activeSession.textModelN.createSnapshot()), null, undefined, true);
|
||||
textModelNplus1.applyEdits(editOperations);
|
||||
const diff = await this._editorWorkerService.computeDiff(this._activeSession.textModel0.uri, textModelNplus1.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: 5000 }, 'advanced');
|
||||
const diff = await this._editorWorkerService.computeDiff(this._activeSession.textModel0.uri, textModelNplus1.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: 5000, computeMoves: false }, 'advanced');
|
||||
this._activeSession.lastTextModelChanges = diff?.changes ?? [];
|
||||
textModelNplus1.dispose();
|
||||
|
||||
|
|
|
@ -150,7 +150,7 @@ export class InteractiveEditorLivePreviewWidget extends ZoneWidget {
|
|||
this._isVisible = true;
|
||||
}
|
||||
|
||||
private _updateFromChanges(range: Range, changes: LineRangeMapping[]): void {
|
||||
private _updateFromChanges(range: Range, changes: readonly LineRangeMapping[]): void {
|
||||
assertType(this.editor.hasModel());
|
||||
|
||||
if (changes.length === 0 || this._session.textModel0.getValueLength() === 0) {
|
||||
|
@ -174,7 +174,7 @@ export class InteractiveEditorLivePreviewWidget extends ZoneWidget {
|
|||
|
||||
// --- inline diff
|
||||
|
||||
private _renderChangesWithInlineDiff(changes: LineRangeMapping[]) {
|
||||
private _renderChangesWithInlineDiff(changes: readonly LineRangeMapping[]) {
|
||||
const original = this._session.textModel0;
|
||||
|
||||
const decorations: IModelDeltaDecoration[] = [];
|
||||
|
@ -221,7 +221,7 @@ export class InteractiveEditorLivePreviewWidget extends ZoneWidget {
|
|||
|
||||
// --- full diff
|
||||
|
||||
private _renderChangesWithFullDiff(changes: LineRangeMapping[], range: Range) {
|
||||
private _renderChangesWithFullDiff(changes: readonly LineRangeMapping[], range: Range) {
|
||||
|
||||
const modified = this.editor.getModel()!;
|
||||
const ranges = this._computeHiddenRanges(modified, range, changes);
|
||||
|
@ -250,7 +250,7 @@ export class InteractiveEditorLivePreviewWidget extends ZoneWidget {
|
|||
super.hide();
|
||||
}
|
||||
|
||||
private _computeHiddenRanges(model: ITextModel, range: Range, changes: LineRangeMapping[]) {
|
||||
private _computeHiddenRanges(model: ITextModel, range: Range, changes: readonly LineRangeMapping[]) {
|
||||
assertType(changes.length > 0);
|
||||
|
||||
let originalLineRange = changes[0].originalRange;
|
||||
|
|
|
@ -110,7 +110,7 @@ export class Session {
|
|||
|
||||
private _lastInput: string | undefined;
|
||||
private _lastExpansionState: ExpansionState | undefined;
|
||||
private _lastTextModelChanges: LineRangeMapping[] | undefined;
|
||||
private _lastTextModelChanges: readonly LineRangeMapping[] | undefined;
|
||||
private _isUnstashed: boolean = false;
|
||||
private readonly _exchange: SessionExchange[] = [];
|
||||
private readonly _startTime = new Date();
|
||||
|
@ -185,7 +185,7 @@ export class Session {
|
|||
return this._lastTextModelChanges ?? [];
|
||||
}
|
||||
|
||||
set lastTextModelChanges(changes: LineRangeMapping[]) {
|
||||
set lastTextModelChanges(changes: readonly LineRangeMapping[]) {
|
||||
this._lastTextModelChanges = changes;
|
||||
}
|
||||
|
||||
|
|
|
@ -554,7 +554,7 @@ export class InteractiveEditorWidget {
|
|||
|
||||
// --- preview
|
||||
|
||||
showEditsPreview(textModelv0: ITextModel, edits: ISingleEditOperation[], changes: LineRangeMapping[]) {
|
||||
showEditsPreview(textModelv0: ITextModel, edits: ISingleEditOperation[], changes: readonly LineRangeMapping[]) {
|
||||
if (changes.length === 0) {
|
||||
this.hideEditsPreview();
|
||||
return;
|
||||
|
|
|
@ -41,6 +41,7 @@ export class MergeDiffComputer implements IMergeDiffComputer {
|
|||
{
|
||||
ignoreTrimWhitespace: false,
|
||||
maxComputationTimeMs: 0,
|
||||
computeMoves: false,
|
||||
},
|
||||
diffAlgorithm,
|
||||
);
|
||||
|
|
|
@ -286,7 +286,7 @@ class MergeModelInterface extends Disposable {
|
|||
const result = await linesDiffComputers.legacy.computeDiff(
|
||||
textModel1.getLinesContent(),
|
||||
textModel2.getLinesContent(),
|
||||
{ ignoreTrimWhitespace: false, maxComputationTimeMs: 10000 }
|
||||
{ ignoreTrimWhitespace: false, maxComputationTimeMs: 10000, computeMoves: false }
|
||||
);
|
||||
const changes = result.changes.map(c =>
|
||||
new DetailedLineRangeMapping(
|
||||
|
|
Loading…
Reference in a new issue