From c95268b65d5ed432199f11171218c74cf56b22ba Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 1 Nov 2021 15:37:52 +0100 Subject: [PATCH 1/4] new Selection(...) -> Selection.fromPositions/fromRange --- .../editor/common/commands/replaceCommand.ts | 25 +++---------- .../editor/common/controller/cursorCommon.ts | 36 ++++--------------- src/vs/editor/common/controller/oneCursor.ts | 7 ++-- src/vs/editor/common/core/selection.ts | 18 ++++++++++ src/vs/monaco.d.ts | 8 +++++ 5 files changed, 40 insertions(+), 54 deletions(-) diff --git a/src/vs/editor/common/commands/replaceCommand.ts b/src/vs/editor/common/commands/replaceCommand.ts index 3e628f2db76..4d036be228c 100644 --- a/src/vs/editor/common/commands/replaceCommand.ts +++ b/src/vs/editor/common/commands/replaceCommand.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Range } from 'vs/editor/common/core/range'; -import { Selection } from 'vs/editor/common/core/selection'; +import { Selection, SelectionDirection } from 'vs/editor/common/core/selection'; import { ICommand, ICursorStateComputerData, IEditOperationBuilder } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; @@ -27,12 +27,7 @@ export class ReplaceCommand implements ICommand { public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { let inverseEditOperations = helper.getInverseEditOperations(); let srcRange = inverseEditOperations[0].range; - return new Selection( - srcRange.endLineNumber, - srcRange.endColumn, - srcRange.endLineNumber, - srcRange.endColumn - ); + return Selection.fromPositions(srcRange.getEndPosition()); } } @@ -53,7 +48,7 @@ export class ReplaceCommandThatSelectsText implements ICommand { public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { const inverseEditOperations = helper.getInverseEditOperations(); const srcRange = inverseEditOperations[0].range; - return new Selection(srcRange.startLineNumber, srcRange.startColumn, srcRange.endLineNumber, srcRange.endColumn); + return Selection.fromRange(srcRange, SelectionDirection.LTR); } } @@ -76,12 +71,7 @@ export class ReplaceCommandWithoutChangingPosition implements ICommand { public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { let inverseEditOperations = helper.getInverseEditOperations(); let srcRange = inverseEditOperations[0].range; - return new Selection( - srcRange.startLineNumber, - srcRange.startColumn, - srcRange.startLineNumber, - srcRange.startColumn - ); + return Selection.fromPositions(srcRange.getStartPosition()); } } @@ -108,12 +98,7 @@ export class ReplaceCommandWithOffsetCursorState implements ICommand { public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { let inverseEditOperations = helper.getInverseEditOperations(); let srcRange = inverseEditOperations[0].range; - return new Selection( - srcRange.endLineNumber + this._lineNumberDeltaOffset, - srcRange.endColumn + this._columnDeltaOffset, - srcRange.endLineNumber + this._lineNumberDeltaOffset, - srcRange.endColumn + this._columnDeltaOffset - ); + return Selection.fromPositions(srcRange.getEndPosition().delta(this._lineNumberDeltaOffset, this._columnDeltaOffset)); } } diff --git a/src/vs/editor/common/controller/cursorCommon.ts b/src/vs/editor/common/controller/cursorCommon.ts index f1d27757e25..b2ce0c7282b 100644 --- a/src/vs/editor/common/controller/cursorCommon.ts +++ b/src/vs/editor/common/controller/cursorCommon.ts @@ -287,31 +287,11 @@ export class SingleCursorState { } private static _computeSelection(selectionStart: Range, position: Position): Selection { - let startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number; - if (selectionStart.isEmpty()) { - startLineNumber = selectionStart.startLineNumber; - startColumn = selectionStart.startColumn; - endLineNumber = position.lineNumber; - endColumn = position.column; + if (selectionStart.isEmpty() || !position.isBeforeOrEqual(selectionStart.getStartPosition())) { + return Selection.fromPositions(selectionStart.getStartPosition(), position); } else { - if (position.isBeforeOrEqual(selectionStart.getStartPosition())) { - startLineNumber = selectionStart.endLineNumber; - startColumn = selectionStart.endColumn; - endLineNumber = position.lineNumber; - endColumn = position.column; - } else { - startLineNumber = selectionStart.startLineNumber; - startColumn = selectionStart.startColumn; - endLineNumber = position.lineNumber; - endColumn = position.column; - } + return Selection.fromPositions(selectionStart.getEndPosition(), position); } - return new Selection( - startLineNumber, - startColumn, - endLineNumber, - endColumn - ); } } @@ -365,13 +345,11 @@ export class CursorState { } public static fromModelSelection(modelSelection: ISelection): PartialModelCursorState { - const selectionStartLineNumber = modelSelection.selectionStartLineNumber; - const selectionStartColumn = modelSelection.selectionStartColumn; - const positionLineNumber = modelSelection.positionLineNumber; - const positionColumn = modelSelection.positionColumn; + const selection = Selection.liftSelection(modelSelection); const modelState = new SingleCursorState( - new Range(selectionStartLineNumber, selectionStartColumn, selectionStartLineNumber, selectionStartColumn), 0, - new Position(positionLineNumber, positionColumn), 0 + Range.fromPositions(selection.getSelectionStart()), + 0, + selection.getPosition(), 0 ); return CursorState.fromModelState(modelState); } diff --git a/src/vs/editor/common/controller/oneCursor.ts b/src/vs/editor/common/controller/oneCursor.ts index bb07a4da717..8335e7abfec 100644 --- a/src/vs/editor/common/controller/oneCursor.ts +++ b/src/vs/editor/common/controller/oneCursor.ts @@ -6,7 +6,7 @@ import { CursorContext, CursorState, ICursorSimpleModel, SingleCursorState } from 'vs/editor/common/controller/cursorCommon'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { Selection, SelectionDirection } from 'vs/editor/common/core/selection'; +import { Selection } from 'vs/editor/common/core/selection'; import { PositionAffinity, TrackedRangeStickiness } from 'vs/editor/common/model'; /** @@ -63,10 +63,7 @@ export class Cursor { public readSelectionFromMarkers(context: CursorContext): Selection { const range = context.model._getTrackedRange(this._selTrackedRange!)!; - if (this.modelState.selection.getDirection() === SelectionDirection.LTR) { - return new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn); - } - return new Selection(range.endLineNumber, range.endColumn, range.startLineNumber, range.startColumn); + return Selection.fromRange(range, this.modelState.selection.getDirection()); } public ensureValidState(context: CursorContext): void { diff --git a/src/vs/editor/common/core/selection.ts b/src/vs/editor/common/core/selection.ts index 3ee2e70af29..1e0db95c8f2 100644 --- a/src/vs/editor/common/core/selection.ts +++ b/src/vs/editor/common/core/selection.ts @@ -128,6 +128,13 @@ export class Selection extends Range { return new Position(this.positionLineNumber, this.positionColumn); } + /** + * Get the position at the start of the selection. + */ + public getSelectionStart(): Position { + return new Position(this.selectionStartLineNumber, this.selectionStartColumn); + } + /** * Create a new selection with a different `selectionStartLineNumber` and `selectionStartColumn`. */ @@ -147,6 +154,17 @@ export class Selection extends Range { return new Selection(start.lineNumber, start.column, end.lineNumber, end.column); } + /** + * Creates a `Selection` from a range, given a direction. + */ + public static fromRange(range: Range, direction: SelectionDirection): Selection { + if (direction === SelectionDirection.LTR) { + return new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn); + } else { + return new Selection(range.endLineNumber, range.endColumn, range.startLineNumber, range.startColumn); + } + } + /** * Create a `Selection` from an `ISelection`. */ diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 42a1e515946..a2b0b59250b 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -802,6 +802,10 @@ declare namespace monaco { * Get the position at `positionLineNumber` and `positionColumn`. */ getPosition(): Position; + /** + * Get the position at the start of the selection. + */ + getSelectionStart(): Position; /** * Create a new selection with a different `selectionStartLineNumber` and `selectionStartColumn`. */ @@ -810,6 +814,10 @@ declare namespace monaco { * Create a `Selection` from one or two positions */ static fromPositions(start: IPosition, end?: IPosition): Selection; + /** + * Creates a `Selection` from a range, given a direction. + */ + static fromRange(range: Range, direction: SelectionDirection): Selection; /** * Create a `Selection` from an `ISelection`. */ From 754c262f9cad6f4ffd7d5007d087dcc529d16ae3 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 28 Oct 2021 16:53:49 +0200 Subject: [PATCH 2/4] PrefixSumComputer renames, LineNumberMapper -> ConstantTimePrefixSumComputer --- src/vs/editor/common/model/mirrorTextModel.ts | 2 +- .../common/viewModel/prefixSumComputer.ts | 131 ++++++++++++++++-- .../common/viewModel/splitLinesCollection.ts | 113 ++------------- .../viewModel/prefixSumComputer.test.ts | 14 +- .../common/extHostNotebookConcatDocument.ts | 4 +- .../browser/diff/diffNestedCellViewModel.ts | 2 +- .../browser/viewModel/codeCellViewModel.ts | 2 +- 7 files changed, 145 insertions(+), 123 deletions(-) diff --git a/src/vs/editor/common/model/mirrorTextModel.ts b/src/vs/editor/common/model/mirrorTextModel.ts index 4fefa551a1a..9ff680f395f 100644 --- a/src/vs/editor/common/model/mirrorTextModel.ts +++ b/src/vs/editor/common/model/mirrorTextModel.ts @@ -106,7 +106,7 @@ export class MirrorTextModel implements IMirrorTextModel { this._lines[lineIndex] = newValue; if (this._lineStarts) { // update prefix sum - this._lineStarts.changeValue(lineIndex, this._lines[lineIndex].length + this._eol.length); + this._lineStarts.setValue(lineIndex, this._lines[lineIndex].length + this._eol.length); } } diff --git a/src/vs/editor/common/viewModel/prefixSumComputer.ts b/src/vs/editor/common/viewModel/prefixSumComputer.ts index c64f57195b9..52d6f316dcf 100644 --- a/src/vs/editor/common/viewModel/prefixSumComputer.ts +++ b/src/vs/editor/common/viewModel/prefixSumComputer.ts @@ -3,20 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { arrayInsert } from 'vs/base/common/arrays'; import { toUint32 } from 'vs/base/common/uint'; -export class PrefixSumIndexOfResult { - _prefixSumIndexOfResultBrand: void = undefined; - - index: number; - remainder: number; - - constructor(index: number, remainder: number) { - this.index = index; - this.remainder = remainder; - } -} - export class PrefixSumComputer { /** @@ -71,7 +60,7 @@ export class PrefixSumComputer { return true; } - public changeValue(index: number, value: number): boolean { + public setValue(index: number, value: number): boolean { index = toUint32(index); value = toUint32(value); @@ -187,3 +176,119 @@ export class PrefixSumComputer { return new PrefixSumIndexOfResult(mid, sum - midStart); } } + +/** + * {@link getIndexOf} has an amortized runtime complexity of O(1). + * + * ({@link PrefixSumComputer.getIndexOf} is just O(log n)) +*/ +export class ConstantTimePrefixSumComputer { + private _values: number[]; + private _isValid: boolean; + private _validEndIndex: number; + + /** + * _prefixSum[i] = SUM(values[j]), 0 <= j <= i + */ + private _prefixSum: number[]; + + /** + * _indexBySum[sum] = idx => _prefixSum[idx - 1] <= sum < _prefixSum[idx] + */ + private _indexBySum: number[]; + + constructor(values: number[]) { + this._values = values; + this._isValid = false; + this._validEndIndex = -1; + this._prefixSum = []; + this._indexBySum = []; + } + + /** + * @returns SUM(0 <= j < values.length, values[j]) + */ + public getTotalSum(): number { + this._ensureValid(); + return this._indexBySum.length; + } + + /** + * @returns `SUM(0 <= j <= index, values[j])`. Includes `values[index]`! + */ + public getPrefixSum(index: number): number { + this._ensureValid(); + return this._prefixSum[index]; + } + + /** + * @returns `result`, such that `getPrefixSum(result.index - 1) + result.remainder = sum` + */ + public getIndexOf(sum: number): PrefixSumIndexOfResult { + this._ensureValid(); + const idx = this._indexBySum[sum]; + const viewLinesAbove = idx > 0 ? this._prefixSum[idx - 1] : 0; + return new PrefixSumIndexOfResult(idx, sum - viewLinesAbove); + } + + public removeValues(start: number, deleteCount: number): void { + this._values.splice(start, deleteCount); + this._invalidate(start); + } + + public insertValues(insertIndex: number, insertArr: number[]): void { + this._values = arrayInsert(this._values, insertIndex, insertArr); + this._invalidate(insertIndex); + } + + private _invalidate(index: number): void { + this._isValid = false; + this._validEndIndex = Math.min(this._validEndIndex, index - 1); + } + + private _ensureValid(): void { + if (this._isValid) { + return; + } + + for (let i = this._validEndIndex + 1, len = this._values.length; i < len; i++) { + const value = this._values[i]; + const sumAbove = i > 0 ? this._prefixSum[i - 1] : 0; + + this._prefixSum[i] = sumAbove + value; + for (let j = 0; j < value; j++) { + this._indexBySum[sumAbove + j] = i; + } + } + + // trim things + this._prefixSum.length = this._values.length; + this._indexBySum.length = this._prefixSum[this._prefixSum.length - 1]; + + // mark as valid + this._isValid = true; + this._validEndIndex = this._values.length - 1; + } + + public setValue(index: number, value: number): void { + if (this._values[index] === value) { + // no change + return; + } + this._values[index] = value; + this._invalidate(index); + } +} + + +export class PrefixSumIndexOfResult { + _prefixSumIndexOfResultBrand: void = undefined; + + constructor( + public readonly index: number, + public readonly remainder: number + ) { + this.index = index; + this.remainder = remainder; + } +} diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 6fb158d78a3..e24e889f1be 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -11,11 +11,11 @@ import { IRange, Range } from 'vs/editor/common/core/range'; import { BracketGuideOptions, EndOfLinePreference, IActiveIndentGuideInfo, IModelDecoration, IModelDeltaDecoration, IndentGuide, IndentGuideHorizontalLine, ITextModel, PositionAffinity } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; -import { PrefixSumIndexOfResult } from 'vs/editor/common/viewModel/prefixSumComputer'; import { ICoordinatesConverter, InjectedText, ILineBreaksComputer, LineBreakData, SingleLineInlineDecoration, ViewLineData } from 'vs/editor/common/viewModel/viewModel'; import { IDisposable } from 'vs/base/common/lifecycle'; import { FontInfo } from 'vs/editor/common/config/fontInfo'; import { LineInjectedText } from 'vs/editor/common/model/textModelEvents'; +import { ConstantTimePrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; export interface ILineBreaksComputerFactory { createLineBreaksComputer(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent): ILineBreaksComputer; @@ -144,89 +144,6 @@ const enum IndentGuideRepeatOption { BlockAll = 2 } -class LineNumberMapper { - - private _counts: number[]; - private _isValid: boolean; - private _validEndIndex: number; - - private _modelToView: number[]; - private _viewToModel: number[]; - - constructor(viewLineCounts: number[]) { - this._counts = viewLineCounts; - this._isValid = false; - this._validEndIndex = -1; - this._modelToView = []; - this._viewToModel = []; - } - - private _invalidate(index: number): void { - this._isValid = false; - this._validEndIndex = Math.min(this._validEndIndex, index - 1); - } - - private _ensureValid(): void { - if (this._isValid) { - return; - } - - for (let i = this._validEndIndex + 1, len = this._counts.length; i < len; i++) { - const viewLineCount = this._counts[i]; - const viewLinesAbove = (i > 0 ? this._modelToView[i - 1] : 0); - - this._modelToView[i] = viewLinesAbove + viewLineCount; - for (let j = 0; j < viewLineCount; j++) { - this._viewToModel[viewLinesAbove + j] = i; - } - } - - // trim things - this._modelToView.length = this._counts.length; - this._viewToModel.length = this._modelToView[this._modelToView.length - 1]; - - // mark as valid - this._isValid = true; - this._validEndIndex = this._counts.length - 1; - } - - public changeValue(index: number, value: number): void { - if (this._counts[index] === value) { - // no change - return; - } - this._counts[index] = value; - this._invalidate(index); - } - - public removeValues(start: number, deleteCount: number): void { - this._counts.splice(start, deleteCount); - this._invalidate(start); - } - - public insertValues(insertIndex: number, insertArr: number[]): void { - this._counts = arrays.arrayInsert(this._counts, insertIndex, insertArr); - this._invalidate(insertIndex); - } - - public getTotalValue(): number { - this._ensureValid(); - return this._viewToModel.length; - } - - public getAccumulatedValue(index: number): number { - this._ensureValid(); - return this._modelToView[index]; - } - - public getIndexOf(accumulatedValue: number): PrefixSumIndexOfResult { - this._ensureValid(); - const modelLineIndex = this._viewToModel[accumulatedValue]; - const viewLinesAbove = (modelLineIndex > 0 ? this._modelToView[modelLineIndex - 1] : 0); - return new PrefixSumIndexOfResult(modelLineIndex, accumulatedValue - viewLinesAbove); - } -} - export class SplitLinesCollection implements IViewModelLinesCollection { private readonly _editorId: number; @@ -243,7 +160,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { private wrappingStrategy: 'simple' | 'advanced'; private lines!: ISplitLine[]; - private prefixSumComputer!: LineNumberMapper; + private prefixSumComputer!: ConstantTimePrefixSumComputer; private hiddenAreasIds!: string[]; @@ -324,7 +241,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { this._validModelVersionId = this.model.getVersionId(); - this.prefixSumComputer = new LineNumberMapper(values); + this.prefixSumComputer = new ConstantTimePrefixSumComputer(values); } public getHiddenAreas(): Range[] { @@ -422,7 +339,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } if (lineChanged) { let newOutputLineCount = this.lines[i].getViewLineCount(); - this.prefixSumComputer.changeValue(i, newOutputLineCount); + this.prefixSumComputer.setValue(i, newOutputLineCount); } } @@ -510,8 +427,8 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return null; } - let outputFromLineNumber = (fromLineNumber === 1 ? 1 : this.prefixSumComputer.getAccumulatedValue(fromLineNumber - 2) + 1); - let outputToLineNumber = this.prefixSumComputer.getAccumulatedValue(toLineNumber - 1); + let outputFromLineNumber = (fromLineNumber === 1 ? 1 : this.prefixSumComputer.getPrefixSum(fromLineNumber - 2) + 1); + let outputToLineNumber = this.prefixSumComputer.getPrefixSum(toLineNumber - 1); this.lines.splice(fromLineNumber - 1, toLineNumber - fromLineNumber + 1); this.prefixSumComputer.removeValues(fromLineNumber - 1, toLineNumber - fromLineNumber + 1); @@ -529,7 +446,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { // cannot use this.getHiddenAreas() because those decorations have already seen the effect of this model change const isInHiddenArea = (fromLineNumber > 2 && !this.lines[fromLineNumber - 2].isVisible()); - let outputFromLineNumber = (fromLineNumber === 1 ? 1 : this.prefixSumComputer.getAccumulatedValue(fromLineNumber - 2) + 1); + let outputFromLineNumber = (fromLineNumber === 1 ? 1 : this.prefixSumComputer.getPrefixSum(fromLineNumber - 2) + 1); let totalOutputLineCount = 0; let insertLines: ISplitLine[] = []; @@ -576,23 +493,23 @@ export class SplitLinesCollection implements IViewModelLinesCollection { let deleteTo = -1; if (oldOutputLineCount > newOutputLineCount) { - changeFrom = (lineNumber === 1 ? 1 : this.prefixSumComputer.getAccumulatedValue(lineNumber - 2) + 1); + changeFrom = (lineNumber === 1 ? 1 : this.prefixSumComputer.getPrefixSum(lineNumber - 2) + 1); changeTo = changeFrom + newOutputLineCount - 1; deleteFrom = changeTo + 1; deleteTo = deleteFrom + (oldOutputLineCount - newOutputLineCount) - 1; lineMappingChanged = true; } else if (oldOutputLineCount < newOutputLineCount) { - changeFrom = (lineNumber === 1 ? 1 : this.prefixSumComputer.getAccumulatedValue(lineNumber - 2) + 1); + changeFrom = (lineNumber === 1 ? 1 : this.prefixSumComputer.getPrefixSum(lineNumber - 2) + 1); changeTo = changeFrom + oldOutputLineCount - 1; insertFrom = changeTo + 1; insertTo = insertFrom + (newOutputLineCount - oldOutputLineCount) - 1; lineMappingChanged = true; } else { - changeFrom = (lineNumber === 1 ? 1 : this.prefixSumComputer.getAccumulatedValue(lineNumber - 2) + 1); + changeFrom = (lineNumber === 1 ? 1 : this.prefixSumComputer.getPrefixSum(lineNumber - 2) + 1); changeTo = changeFrom + newOutputLineCount - 1; } - this.prefixSumComputer.changeValue(lineIndex, newOutputLineCount); + this.prefixSumComputer.setValue(lineIndex, newOutputLineCount); const viewLinesChangedEvent = (changeFrom <= changeTo ? new viewEvents.ViewLinesChangedEvent(changeFrom, changeTo) : null); const viewLinesInsertedEvent = (insertFrom <= insertTo ? new viewEvents.ViewLinesInsertedEvent(insertFrom, insertTo) : null); @@ -610,7 +527,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } public getViewLineCount(): number { - return this.prefixSumComputer.getTotalValue(); + return this.prefixSumComputer.getTotalSum(); } private _toValidViewLineNumber(viewLineNumber: number): number { @@ -1000,7 +917,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { // console.log('in -> out ' + inputLineNumber + ',' + inputColumn + ' ===> ' + 1 + ',' + 1); return new Position(1, 1); } - const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getAccumulatedValue(lineIndex - 1)); + const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getPrefixSum(lineIndex - 1)); let r: Position; if (lineIndexChanged) { @@ -1031,7 +948,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { let lineIndex = inputLineNumber - 1; if (this.lines[lineIndex].isVisible()) { // this model line is visible - const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getAccumulatedValue(lineIndex - 1)); + const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getPrefixSum(lineIndex - 1)); return this.lines[lineIndex].getViewLineNumberOfModelPosition(deltaLineNumber, inputColumn); } @@ -1043,7 +960,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { // Could not reach a real line return 1; } - const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getAccumulatedValue(lineIndex - 1)); + const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getPrefixSum(lineIndex - 1)); return this.lines[lineIndex].getViewLineNumberOfModelPosition(deltaLineNumber, this.model.getLineMaxColumn(lineIndex + 1)); } diff --git a/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts b/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts index 0ac5ed2672d..f8dfc3eceec 100644 --- a/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts +++ b/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts @@ -58,7 +58,7 @@ suite('Editor ViewModel - PrefixSumComputer', () => { assert.strictEqual(indexOfResult.remainder, 3); // [1, 2, 2, 1, 3] - psc.changeValue(1, 2); + psc.setValue(1, 2); assert.strictEqual(psc.getTotalSum(), 9); assert.strictEqual(psc.getPrefixSum(0), 1); assert.strictEqual(psc.getPrefixSum(1), 3); @@ -67,7 +67,7 @@ suite('Editor ViewModel - PrefixSumComputer', () => { assert.strictEqual(psc.getPrefixSum(4), 9); // [1, 0, 2, 1, 3] - psc.changeValue(1, 0); + psc.setValue(1, 0); assert.strictEqual(psc.getTotalSum(), 7); assert.strictEqual(psc.getPrefixSum(0), 1); assert.strictEqual(psc.getPrefixSum(1), 1); @@ -100,7 +100,7 @@ suite('Editor ViewModel - PrefixSumComputer', () => { assert.strictEqual(indexOfResult.remainder, 3); // [1, 0, 0, 1, 3] - psc.changeValue(2, 0); + psc.setValue(2, 0); assert.strictEqual(psc.getTotalSum(), 5); assert.strictEqual(psc.getPrefixSum(0), 1); assert.strictEqual(psc.getPrefixSum(1), 1); @@ -127,7 +127,7 @@ suite('Editor ViewModel - PrefixSumComputer', () => { assert.strictEqual(indexOfResult.remainder, 3); // [1, 0, 0, 0, 3] - psc.changeValue(3, 0); + psc.setValue(3, 0); assert.strictEqual(psc.getTotalSum(), 4); assert.strictEqual(psc.getPrefixSum(0), 1); assert.strictEqual(psc.getPrefixSum(1), 1); @@ -151,9 +151,9 @@ suite('Editor ViewModel - PrefixSumComputer', () => { assert.strictEqual(indexOfResult.remainder, 3); // [1, 1, 0, 1, 1] - psc.changeValue(1, 1); - psc.changeValue(3, 1); - psc.changeValue(4, 1); + psc.setValue(1, 1); + psc.setValue(3, 1); + psc.setValue(4, 1); assert.strictEqual(psc.getTotalSum(), 4); assert.strictEqual(psc.getPrefixSum(0), 1); assert.strictEqual(psc.getPrefixSum(1), 2); diff --git a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts index bf2bbb30e97..aa0d9c70151 100644 --- a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts @@ -42,8 +42,8 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD this._disposables.add(extHostDocuments.onDidChangeDocument(e => { const cellIdx = this._cellUris.get(e.document.uri); if (cellIdx !== undefined) { - this._cellLengths.changeValue(cellIdx, this._cells[cellIdx].document.getText().length + 1); - this._cellLines.changeValue(cellIdx, this._cells[cellIdx].document.lineCount); + this._cellLengths.setValue(cellIdx, this._cells[cellIdx].document.getText().length + 1); + this._cellLines.setValue(cellIdx, this._cells[cellIdx].document.lineCount); this._versionId += 1; this._onDidChange.fire(undefined); } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel.ts index 1d2e892001c..771b9a3c060 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel.ts @@ -119,7 +119,7 @@ export class DiffNestedCellViewModel extends Disposable implements IDiffNestedCe this._ensureOutputsTop(); this._outputCollection[index] = height; - if (this._outputsTop!.changeValue(index, height)) { + if (this._outputsTop!.setValue(index, height)) { this._onDidChangeOutputLayout.fire(); } } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts index eadd68541a7..6cfcdb12f91 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -383,7 +383,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod } this._outputCollection[index] = height; - if (this._outputsTop!.changeValue(index, height)) { + if (this._outputsTop!.setValue(index, height)) { this.layoutChange({ outputHeight: true }, source); } } From ba93b4cc8ef33220ef07b9f793dbb745f30a941e Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 28 Oct 2021 17:36:16 +0200 Subject: [PATCH 3/4] More adoption of this.getViewLineInfo. --- .../common/viewModel/splitLinesCollection.ts | 84 ++++++------------- 1 file changed, 24 insertions(+), 60 deletions(-) diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index e24e889f1be..9ad85a437e7 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -562,7 +562,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { // #region ViewLineInfo - public getViewLineInfo(viewLineNumber: number): ViewLineInfo { + private getViewLineInfo(viewLineNumber: number): ViewLineInfo { viewLineNumber = this._toValidViewLineNumber(viewLineNumber); let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); let lineIndex = r.index; @@ -767,48 +767,28 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } public getViewLineContent(viewLineNumber: number): string { - viewLineNumber = this._toValidViewLineNumber(viewLineNumber); - let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); - let lineIndex = r.index; - let remainder = r.remainder; - - return this.lines[lineIndex].getViewLineContent(this.model, lineIndex + 1, remainder); + const info = this.getViewLineInfo(viewLineNumber); + return this.lines[info.modelLineNumber - 1].getViewLineContent(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx); } public getViewLineLength(viewLineNumber: number): number { - viewLineNumber = this._toValidViewLineNumber(viewLineNumber); - let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); - let lineIndex = r.index; - let remainder = r.remainder; - - return this.lines[lineIndex].getViewLineLength(this.model, lineIndex + 1, remainder); + const info = this.getViewLineInfo(viewLineNumber); + return this.lines[info.modelLineNumber - 1].getViewLineLength(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx); } public getViewLineMinColumn(viewLineNumber: number): number { - viewLineNumber = this._toValidViewLineNumber(viewLineNumber); - let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); - let lineIndex = r.index; - let remainder = r.remainder; - - return this.lines[lineIndex].getViewLineMinColumn(this.model, lineIndex + 1, remainder); + const info = this.getViewLineInfo(viewLineNumber); + return this.lines[info.modelLineNumber - 1].getViewLineMinColumn(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx); } public getViewLineMaxColumn(viewLineNumber: number): number { - viewLineNumber = this._toValidViewLineNumber(viewLineNumber); - let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); - let lineIndex = r.index; - let remainder = r.remainder; - - return this.lines[lineIndex].getViewLineMaxColumn(this.model, lineIndex + 1, remainder); + const info = this.getViewLineInfo(viewLineNumber); + return this.lines[info.modelLineNumber - 1].getViewLineMaxColumn(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx); } public getViewLineData(viewLineNumber: number): ViewLineData { - viewLineNumber = this._toValidViewLineNumber(viewLineNumber); - let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); - let lineIndex = r.index; - let remainder = r.remainder; - - return this.lines[lineIndex].getViewLineData(this.model, lineIndex + 1, remainder); + const info = this.getViewLineInfo(viewLineNumber); + return this.lines[info.modelLineNumber - 1].getViewLineData(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx); } public getViewLinesData(viewStartLineNumber: number, viewEndLineNumber: number, needed: boolean[]): ViewLineData[] { @@ -884,15 +864,11 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } public convertViewPositionToModelPosition(viewLineNumber: number, viewColumn: number): Position { - viewLineNumber = this._toValidViewLineNumber(viewLineNumber); + const info = this.getViewLineInfo(viewLineNumber); - let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); - let lineIndex = r.index; - let remainder = r.remainder; - - let inputColumn = this.lines[lineIndex].getModelColumnOfViewPosition(remainder, viewColumn); + let inputColumn = this.lines[info.modelLineNumber - 1].getModelColumnOfViewPosition(info.modelLineWrappedLineIdx, viewColumn); // console.log('out -> in ' + viewLineNumber + ',' + viewColumn + ' ===> ' + (lineIndex+1) + ',' + inputColumn); - return this.model.validatePosition(new Position(lineIndex + 1, inputColumn)); + return this.model.validatePosition(new Position(info.modelLineNumber, inputColumn)); } public convertViewRangeToModelRange(viewRange: Range): Range { @@ -944,12 +920,12 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } } - public getViewLineNumberOfModelPosition(inputLineNumber: number, inputColumn: number): number { - let lineIndex = inputLineNumber - 1; + public getViewLineNumberOfModelPosition(modelLineNumber: number, modelColumn: number): number { + let lineIndex = modelLineNumber - 1; if (this.lines[lineIndex].isVisible()) { // this model line is visible const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getPrefixSum(lineIndex - 1)); - return this.lines[lineIndex].getViewLineNumberOfModelPosition(deltaLineNumber, inputColumn); + return this.lines[lineIndex].getViewLineNumberOfModelPosition(deltaLineNumber, modelColumn); } // this model line is not visible @@ -1032,31 +1008,19 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } public getInjectedTextAt(position: Position): InjectedText | null { - const viewLineNumber = this._toValidViewLineNumber(position.lineNumber); - const r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); - const lineIndex = r.index; - const remainder = r.remainder; - - return this.lines[lineIndex].getInjectedTextAt(remainder, position.column); + const info = this.getViewLineInfo(position.lineNumber); + return this.lines[info.modelLineNumber - 1].getInjectedTextAt(info.modelLineWrappedLineIdx, position.column); } normalizePosition(position: Position, affinity: PositionAffinity): Position { - const viewLineNumber = this._toValidViewLineNumber(position.lineNumber); - const r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); - const lineIndex = r.index; - const remainder = r.remainder; - - return this.lines[lineIndex].normalizePosition(this.model, lineIndex + 1, remainder, position, affinity); + const info = this.getViewLineInfo(position.lineNumber); + return this.lines[info.modelLineNumber - 1].normalizePosition(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx, position, affinity); } public getLineIndentColumn(lineNumber: number): number { - const viewLineNumber = this._toValidViewLineNumber(lineNumber); - const r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); - const lineIndex = r.index; - const remainder = r.remainder; - - if (remainder === 0) { - return this.model.getLineIndentColumn(lineIndex + 1); + const info = this.getViewLineInfo(lineNumber); + if (info.modelLineWrappedLineIdx === 0) { + return this.model.getLineIndentColumn(info.modelLineNumber); } // wrapped lines have no indentation. From cf1b304fcd48b57b29e08ba8506664639ecf32cf Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 28 Oct 2021 17:54:46 +0200 Subject: [PATCH 4/4] ISplitLine -> IModelLineProjection --- .../common/viewModel/splitLinesCollection.ts | 193 ++++++++++-------- .../viewModel/splitLinesCollection.test.ts | 6 +- 2 files changed, 107 insertions(+), 92 deletions(-) diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 9ad85a437e7..7b998c5a808 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -30,9 +30,9 @@ export interface ISimpleModel { getValueInRange(range: IRange, eol?: EndOfLinePreference): string; } -export interface ISplitLine { +export interface IModelLineProjection { isVisible(): boolean; - setVisible(isVisible: boolean): ISplitLine; + setVisible(isVisible: boolean): IModelLineProjection; getLineBreakData(): LineBreakData | null; getViewLineCount(): number; @@ -158,9 +158,13 @@ export class SplitLinesCollection implements IViewModelLinesCollection { private wrappingColumn: number; private wrappingIndent: WrappingIndent; private wrappingStrategy: 'simple' | 'advanced'; - private lines!: ISplitLine[]; - private prefixSumComputer!: ConstantTimePrefixSumComputer; + private modelLineProjections!: IModelLineProjection[]; + + /** + * Reflects the sum of the line counts of all . + */ + private projectedModelLineLineCounts!: ConstantTimePrefixSumComputer; private hiddenAreasIds!: string[]; @@ -198,7 +202,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } private _constructLines(resetHiddenAreas: boolean, previousLineBreaks: ((LineBreakData | null)[]) | null): void { - this.lines = []; + this.modelLineProjections = []; if (resetHiddenAreas) { this.hiddenAreasIds = []; @@ -234,14 +238,14 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } let isInHiddenArea = (lineNumber >= hiddenAreaStart && lineNumber <= hiddenAreaEnd); - let line = createSplitLine(linesBreaks[i], !isInHiddenArea); + let line = createModelLineProjection(linesBreaks[i], !isInHiddenArea); values[i] = line.getViewLineCount(); - this.lines[i] = line; + this.modelLineProjections[i] = line; } this._validModelVersionId = this.model.getVersionId(); - this.prefixSumComputer = new ConstantTimePrefixSumComputer(values); + this.projectedModelLineLineCounts = new ConstantTimePrefixSumComputer(values); } public getHiddenAreas(): Range[] { @@ -309,37 +313,37 @@ export class SplitLinesCollection implements IViewModelLinesCollection { let hiddenAreas = newRanges; let hiddenAreaStart = 1, hiddenAreaEnd = 0; let hiddenAreaIdx = -1; - let nextLineNumberToUpdateHiddenArea = (hiddenAreaIdx + 1 < hiddenAreas.length) ? hiddenAreaEnd + 1 : this.lines.length + 2; + let nextLineNumberToUpdateHiddenArea = (hiddenAreaIdx + 1 < hiddenAreas.length) ? hiddenAreaEnd + 1 : this.modelLineProjections.length + 2; let hasVisibleLine = false; - for (let i = 0; i < this.lines.length; i++) { + for (let i = 0; i < this.modelLineProjections.length; i++) { let lineNumber = i + 1; if (lineNumber === nextLineNumberToUpdateHiddenArea) { hiddenAreaIdx++; hiddenAreaStart = hiddenAreas[hiddenAreaIdx].startLineNumber; hiddenAreaEnd = hiddenAreas[hiddenAreaIdx].endLineNumber; - nextLineNumberToUpdateHiddenArea = (hiddenAreaIdx + 1 < hiddenAreas.length) ? hiddenAreaEnd + 1 : this.lines.length + 2; + nextLineNumberToUpdateHiddenArea = (hiddenAreaIdx + 1 < hiddenAreas.length) ? hiddenAreaEnd + 1 : this.modelLineProjections.length + 2; } let lineChanged = false; if (lineNumber >= hiddenAreaStart && lineNumber <= hiddenAreaEnd) { // Line should be hidden - if (this.lines[i].isVisible()) { - this.lines[i] = this.lines[i].setVisible(false); + if (this.modelLineProjections[i].isVisible()) { + this.modelLineProjections[i] = this.modelLineProjections[i].setVisible(false); lineChanged = true; } } else { hasVisibleLine = true; // Line should be visible - if (!this.lines[i].isVisible()) { - this.lines[i] = this.lines[i].setVisible(true); + if (!this.modelLineProjections[i].isVisible()) { + this.modelLineProjections[i] = this.modelLineProjections[i].setVisible(true); lineChanged = true; } } if (lineChanged) { - let newOutputLineCount = this.lines[i].getViewLineCount(); - this.prefixSumComputer.setValue(i, newOutputLineCount); + let newOutputLineCount = this.modelLineProjections[i].getViewLineCount(); + this.projectedModelLineLineCounts.setValue(i, newOutputLineCount); } } @@ -352,19 +356,19 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } public modelPositionIsVisible(modelLineNumber: number, _modelColumn: number): boolean { - if (modelLineNumber < 1 || modelLineNumber > this.lines.length) { + if (modelLineNumber < 1 || modelLineNumber > this.modelLineProjections.length) { // invalid arguments return false; } - return this.lines[modelLineNumber - 1].isVisible(); + return this.modelLineProjections[modelLineNumber - 1].isVisible(); } public getModelLineViewLineCount(modelLineNumber: number): number { - if (modelLineNumber < 1 || modelLineNumber > this.lines.length) { + if (modelLineNumber < 1 || modelLineNumber > this.modelLineProjections.length) { // invalid arguments return 1; } - return this.lines[modelLineNumber - 1].getViewLineCount(); + return this.modelLineProjections[modelLineNumber - 1].getViewLineCount(); } public setTabSize(newTabSize: number): boolean { @@ -397,8 +401,8 @@ export class SplitLinesCollection implements IViewModelLinesCollection { let previousLineBreaks: ((LineBreakData | null)[]) | null = null; if (onlyWrappingColumnChanged) { previousLineBreaks = []; - for (let i = 0, len = this.lines.length; i < len; i++) { - previousLineBreaks[i] = this.lines[i].getLineBreakData(); + for (let i = 0, len = this.modelLineProjections.length; i < len; i++) { + previousLineBreaks[i] = this.modelLineProjections[i].getLineBreakData(); } } @@ -427,11 +431,11 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return null; } - let outputFromLineNumber = (fromLineNumber === 1 ? 1 : this.prefixSumComputer.getPrefixSum(fromLineNumber - 2) + 1); - let outputToLineNumber = this.prefixSumComputer.getPrefixSum(toLineNumber - 1); + let outputFromLineNumber = (fromLineNumber === 1 ? 1 : this.projectedModelLineLineCounts.getPrefixSum(fromLineNumber - 2) + 1); + let outputToLineNumber = this.projectedModelLineLineCounts.getPrefixSum(toLineNumber - 1); - this.lines.splice(fromLineNumber - 1, toLineNumber - fromLineNumber + 1); - this.prefixSumComputer.removeValues(fromLineNumber - 1, toLineNumber - fromLineNumber + 1); + this.modelLineProjections.splice(fromLineNumber - 1, toLineNumber - fromLineNumber + 1); + this.projectedModelLineLineCounts.removeValues(fromLineNumber - 1, toLineNumber - fromLineNumber + 1); return new viewEvents.ViewLinesDeletedEvent(outputFromLineNumber, outputToLineNumber); } @@ -444,16 +448,16 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } // cannot use this.getHiddenAreas() because those decorations have already seen the effect of this model change - const isInHiddenArea = (fromLineNumber > 2 && !this.lines[fromLineNumber - 2].isVisible()); + const isInHiddenArea = (fromLineNumber > 2 && !this.modelLineProjections[fromLineNumber - 2].isVisible()); - let outputFromLineNumber = (fromLineNumber === 1 ? 1 : this.prefixSumComputer.getPrefixSum(fromLineNumber - 2) + 1); + let outputFromLineNumber = (fromLineNumber === 1 ? 1 : this.projectedModelLineLineCounts.getPrefixSum(fromLineNumber - 2) + 1); let totalOutputLineCount = 0; - let insertLines: ISplitLine[] = []; + let insertLines: IModelLineProjection[] = []; let insertPrefixSumValues: number[] = []; for (let i = 0, len = lineBreaks.length; i < len; i++) { - let line = createSplitLine(lineBreaks[i], !isInHiddenArea); + let line = createModelLineProjection(lineBreaks[i], !isInHiddenArea); insertLines.push(line); let outputLineCount = line.getViewLineCount(); @@ -462,9 +466,9 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } // TODO@Alex: use arrays.arrayInsert - this.lines = this.lines.slice(0, fromLineNumber - 1).concat(insertLines).concat(this.lines.slice(fromLineNumber - 1)); + this.modelLineProjections = this.modelLineProjections.slice(0, fromLineNumber - 1).concat(insertLines).concat(this.modelLineProjections.slice(fromLineNumber - 1)); - this.prefixSumComputer.insertValues(fromLineNumber - 1, insertPrefixSumValues); + this.projectedModelLineLineCounts.insertValues(fromLineNumber - 1, insertPrefixSumValues); return new viewEvents.ViewLinesInsertedEvent(outputFromLineNumber, outputFromLineNumber + totalOutputLineCount - 1); } @@ -478,11 +482,11 @@ export class SplitLinesCollection implements IViewModelLinesCollection { let lineIndex = lineNumber - 1; - let oldOutputLineCount = this.lines[lineIndex].getViewLineCount(); - let isVisible = this.lines[lineIndex].isVisible(); - let line = createSplitLine(lineBreakData, isVisible); - this.lines[lineIndex] = line; - let newOutputLineCount = this.lines[lineIndex].getViewLineCount(); + let oldOutputLineCount = this.modelLineProjections[lineIndex].getViewLineCount(); + let isVisible = this.modelLineProjections[lineIndex].isVisible(); + let line = createModelLineProjection(lineBreakData, isVisible); + this.modelLineProjections[lineIndex] = line; + let newOutputLineCount = this.modelLineProjections[lineIndex].getViewLineCount(); let lineMappingChanged = false; let changeFrom = 0; @@ -493,23 +497,23 @@ export class SplitLinesCollection implements IViewModelLinesCollection { let deleteTo = -1; if (oldOutputLineCount > newOutputLineCount) { - changeFrom = (lineNumber === 1 ? 1 : this.prefixSumComputer.getPrefixSum(lineNumber - 2) + 1); + changeFrom = (lineNumber === 1 ? 1 : this.projectedModelLineLineCounts.getPrefixSum(lineNumber - 2) + 1); changeTo = changeFrom + newOutputLineCount - 1; deleteFrom = changeTo + 1; deleteTo = deleteFrom + (oldOutputLineCount - newOutputLineCount) - 1; lineMappingChanged = true; } else if (oldOutputLineCount < newOutputLineCount) { - changeFrom = (lineNumber === 1 ? 1 : this.prefixSumComputer.getPrefixSum(lineNumber - 2) + 1); + changeFrom = (lineNumber === 1 ? 1 : this.projectedModelLineLineCounts.getPrefixSum(lineNumber - 2) + 1); changeTo = changeFrom + oldOutputLineCount - 1; insertFrom = changeTo + 1; insertTo = insertFrom + (newOutputLineCount - oldOutputLineCount) - 1; lineMappingChanged = true; } else { - changeFrom = (lineNumber === 1 ? 1 : this.prefixSumComputer.getPrefixSum(lineNumber - 2) + 1); + changeFrom = (lineNumber === 1 ? 1 : this.projectedModelLineLineCounts.getPrefixSum(lineNumber - 2) + 1); changeTo = changeFrom + newOutputLineCount - 1; } - this.prefixSumComputer.setValue(lineIndex, newOutputLineCount); + this.projectedModelLineLineCounts.setValue(lineIndex, newOutputLineCount); const viewLinesChangedEvent = (changeFrom <= changeTo ? new viewEvents.ViewLinesChangedEvent(changeFrom, changeTo) : null); const viewLinesInsertedEvent = (insertFrom <= insertTo ? new viewEvents.ViewLinesInsertedEvent(insertFrom, insertTo) : null); @@ -520,14 +524,14 @@ export class SplitLinesCollection implements IViewModelLinesCollection { public acceptVersionId(versionId: number): void { this._validModelVersionId = versionId; - if (this.lines.length === 1 && !this.lines[0].isVisible()) { + if (this.modelLineProjections.length === 1 && !this.modelLineProjections[0].isVisible()) { // At least one line must be visible => reset hidden areas this.setHiddenAreas([]); } } public getViewLineCount(): number { - return this.prefixSumComputer.getTotalSum(); + return this.projectedModelLineLineCounts.getTotalSum(); } private _toValidViewLineNumber(viewLineNumber: number): number { @@ -564,14 +568,14 @@ export class SplitLinesCollection implements IViewModelLinesCollection { private getViewLineInfo(viewLineNumber: number): ViewLineInfo { viewLineNumber = this._toValidViewLineNumber(viewLineNumber); - let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); + let r = this.projectedModelLineLineCounts.getIndexOf(viewLineNumber - 1); let lineIndex = r.index; let remainder = r.remainder; return new ViewLineInfo(lineIndex + 1, remainder); } private getMinColumnOfViewLine(viewLineInfo: ViewLineInfo): number { - return this.lines[viewLineInfo.modelLineNumber - 1].getViewLineMinColumn( + return this.modelLineProjections[viewLineInfo.modelLineNumber - 1].getViewLineMinColumn( this.model, viewLineInfo.modelLineNumber, viewLineInfo.modelLineWrappedLineIdx @@ -579,7 +583,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } private getModelStartPositionOfViewLine(viewLineInfo: ViewLineInfo): Position { - const line = this.lines[viewLineInfo.modelLineNumber - 1]; + const line = this.modelLineProjections[viewLineInfo.modelLineNumber - 1]; const minViewColumn = line.getViewLineMinColumn( this.model, viewLineInfo.modelLineNumber, @@ -593,7 +597,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } private getModelEndPositionOfViewLine(viewLineInfo: ViewLineInfo): Position { - const line = this.lines[viewLineInfo.modelLineNumber - 1]; + const line = this.modelLineProjections[viewLineInfo.modelLineNumber - 1]; const maxViewColumn = line.getViewLineMaxColumn( this.model, viewLineInfo.modelLineNumber, @@ -615,7 +619,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { let viewLines = new Array(); for (let curModelLine = startViewLine.modelLineNumber; curModelLine <= endViewLine.modelLineNumber; curModelLine++) { - const line = this.lines[curModelLine - 1]; + const line = this.modelLineProjections[curModelLine - 1]; if (line.isVisible()) { let startOffset = @@ -711,7 +715,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { let reqStart: Position | null = null; for (let modelLineIndex = modelStartLineIndex; modelLineIndex <= modelEndLineIndex; modelLineIndex++) { - const line = this.lines[modelLineIndex]; + const line = this.modelLineProjections[modelLineIndex]; if (line.isVisible()) { let viewLineStartIndex = line.getViewLineNumberOfModelPosition(0, modelLineIndex === modelStartLineIndex ? modelStart.column : 1); let viewLineEndIndex = line.getViewLineNumberOfModelPosition(0, this.model.getLineMaxColumn(modelLineIndex + 1)); @@ -768,27 +772,27 @@ export class SplitLinesCollection implements IViewModelLinesCollection { public getViewLineContent(viewLineNumber: number): string { const info = this.getViewLineInfo(viewLineNumber); - return this.lines[info.modelLineNumber - 1].getViewLineContent(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx); + return this.modelLineProjections[info.modelLineNumber - 1].getViewLineContent(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx); } public getViewLineLength(viewLineNumber: number): number { const info = this.getViewLineInfo(viewLineNumber); - return this.lines[info.modelLineNumber - 1].getViewLineLength(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx); + return this.modelLineProjections[info.modelLineNumber - 1].getViewLineLength(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx); } public getViewLineMinColumn(viewLineNumber: number): number { const info = this.getViewLineInfo(viewLineNumber); - return this.lines[info.modelLineNumber - 1].getViewLineMinColumn(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx); + return this.modelLineProjections[info.modelLineNumber - 1].getViewLineMinColumn(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx); } public getViewLineMaxColumn(viewLineNumber: number): number { const info = this.getViewLineInfo(viewLineNumber); - return this.lines[info.modelLineNumber - 1].getViewLineMaxColumn(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx); + return this.modelLineProjections[info.modelLineNumber - 1].getViewLineMaxColumn(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx); } public getViewLineData(viewLineNumber: number): ViewLineData { const info = this.getViewLineInfo(viewLineNumber); - return this.lines[info.modelLineNumber - 1].getViewLineData(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx); + return this.modelLineProjections[info.modelLineNumber - 1].getViewLineData(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx); } public getViewLinesData(viewStartLineNumber: number, viewEndLineNumber: number, needed: boolean[]): ViewLineData[] { @@ -796,14 +800,14 @@ export class SplitLinesCollection implements IViewModelLinesCollection { viewStartLineNumber = this._toValidViewLineNumber(viewStartLineNumber); viewEndLineNumber = this._toValidViewLineNumber(viewEndLineNumber); - let start = this.prefixSumComputer.getIndexOf(viewStartLineNumber - 1); + let start = this.projectedModelLineLineCounts.getIndexOf(viewStartLineNumber - 1); let viewLineNumber = viewStartLineNumber; let startModelLineIndex = start.index; let startRemainder = start.remainder; let result: ViewLineData[] = []; for (let modelLineIndex = startModelLineIndex, len = this.model.getLineCount(); modelLineIndex < len; modelLineIndex++) { - let line = this.lines[modelLineIndex]; + let line = this.modelLineProjections[modelLineIndex]; if (!line.isVisible()) { continue; } @@ -832,11 +836,11 @@ export class SplitLinesCollection implements IViewModelLinesCollection { public validateViewPosition(viewLineNumber: number, viewColumn: number, expectedModelPosition: Position): Position { viewLineNumber = this._toValidViewLineNumber(viewLineNumber); - let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); + let r = this.projectedModelLineLineCounts.getIndexOf(viewLineNumber - 1); let lineIndex = r.index; let remainder = r.remainder; - let line = this.lines[lineIndex]; + let line = this.modelLineProjections[lineIndex]; let minColumn = line.getViewLineMinColumn(this.model, lineIndex + 1, remainder); let maxColumn = line.getViewLineMaxColumn(this.model, lineIndex + 1, remainder); @@ -866,7 +870,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { public convertViewPositionToModelPosition(viewLineNumber: number, viewColumn: number): Position { const info = this.getViewLineInfo(viewLineNumber); - let inputColumn = this.lines[info.modelLineNumber - 1].getModelColumnOfViewPosition(info.modelLineWrappedLineIdx, viewColumn); + let inputColumn = this.modelLineProjections[info.modelLineNumber - 1].getModelColumnOfViewPosition(info.modelLineWrappedLineIdx, viewColumn); // console.log('out -> in ' + viewLineNumber + ',' + viewColumn + ' ===> ' + (lineIndex+1) + ',' + inputColumn); return this.model.validatePosition(new Position(info.modelLineNumber, inputColumn)); } @@ -884,22 +888,22 @@ export class SplitLinesCollection implements IViewModelLinesCollection { const inputColumn = validPosition.column; let lineIndex = inputLineNumber - 1, lineIndexChanged = false; - while (lineIndex > 0 && !this.lines[lineIndex].isVisible()) { + while (lineIndex > 0 && !this.modelLineProjections[lineIndex].isVisible()) { lineIndex--; lineIndexChanged = true; } - if (lineIndex === 0 && !this.lines[lineIndex].isVisible()) { + if (lineIndex === 0 && !this.modelLineProjections[lineIndex].isVisible()) { // Could not reach a real line // console.log('in -> out ' + inputLineNumber + ',' + inputColumn + ' ===> ' + 1 + ',' + 1); return new Position(1, 1); } - const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getPrefixSum(lineIndex - 1)); + const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.projectedModelLineLineCounts.getPrefixSum(lineIndex - 1)); let r: Position; if (lineIndexChanged) { - r = this.lines[lineIndex].getViewPositionOfModelPosition(deltaLineNumber, this.model.getLineMaxColumn(lineIndex + 1), affinity); + r = this.modelLineProjections[lineIndex].getViewPositionOfModelPosition(deltaLineNumber, this.model.getLineMaxColumn(lineIndex + 1), affinity); } else { - r = this.lines[inputLineNumber - 1].getViewPositionOfModelPosition(deltaLineNumber, inputColumn, affinity); + r = this.modelLineProjections[inputLineNumber - 1].getViewPositionOfModelPosition(deltaLineNumber, inputColumn, affinity); } // console.log('in -> out ' + inputLineNumber + ',' + inputColumn + ' ===> ' + r.lineNumber + ',' + r); @@ -922,22 +926,22 @@ export class SplitLinesCollection implements IViewModelLinesCollection { public getViewLineNumberOfModelPosition(modelLineNumber: number, modelColumn: number): number { let lineIndex = modelLineNumber - 1; - if (this.lines[lineIndex].isVisible()) { + if (this.modelLineProjections[lineIndex].isVisible()) { // this model line is visible - const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getPrefixSum(lineIndex - 1)); - return this.lines[lineIndex].getViewLineNumberOfModelPosition(deltaLineNumber, modelColumn); + const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.projectedModelLineLineCounts.getPrefixSum(lineIndex - 1)); + return this.modelLineProjections[lineIndex].getViewLineNumberOfModelPosition(deltaLineNumber, modelColumn); } // this model line is not visible - while (lineIndex > 0 && !this.lines[lineIndex].isVisible()) { + while (lineIndex > 0 && !this.modelLineProjections[lineIndex].isVisible()) { lineIndex--; } - if (lineIndex === 0 && !this.lines[lineIndex].isVisible()) { + if (lineIndex === 0 && !this.modelLineProjections[lineIndex].isVisible()) { // Could not reach a real line return 1; } - const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getPrefixSum(lineIndex - 1)); - return this.lines[lineIndex].getViewLineNumberOfModelPosition(deltaLineNumber, this.model.getLineMaxColumn(lineIndex + 1)); + const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.projectedModelLineLineCounts.getPrefixSum(lineIndex - 1)); + return this.modelLineProjections[lineIndex].getViewLineNumberOfModelPosition(deltaLineNumber, this.model.getLineMaxColumn(lineIndex + 1)); } public getDecorationsInRange(range: Range, ownerId: number, filterOutValidation: boolean): IModelDecoration[] { @@ -956,7 +960,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { let reqStart: Position | null = null; for (let modelLineIndex = modelStartLineIndex; modelLineIndex <= modelEndLineIndex; modelLineIndex++) { - const line = this.lines[modelLineIndex]; + const line = this.modelLineProjections[modelLineIndex]; if (line.isVisible()) { // merge into previous request if (reqStart === null) { @@ -1009,12 +1013,12 @@ export class SplitLinesCollection implements IViewModelLinesCollection { public getInjectedTextAt(position: Position): InjectedText | null { const info = this.getViewLineInfo(position.lineNumber); - return this.lines[info.modelLineNumber - 1].getInjectedTextAt(info.modelLineWrappedLineIdx, position.column); + return this.modelLineProjections[info.modelLineNumber - 1].getInjectedTextAt(info.modelLineWrappedLineIdx, position.column); } normalizePosition(position: Position, affinity: PositionAffinity): Position { const info = this.getViewLineInfo(position.lineNumber); - return this.lines[info.modelLineNumber - 1].normalizePosition(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx, position, affinity); + return this.modelLineProjections[info.modelLineNumber - 1].normalizePosition(this.model, info.modelLineNumber, info.modelLineWrappedLineIdx, position, affinity); } public getLineIndentColumn(lineNumber: number): number { @@ -1052,9 +1056,12 @@ class ViewLineInfoGroupedByModelRange { } } -class VisibleIdentitySplitLine implements ISplitLine { +/** + * This projection does not change the model line. +*/ +class IdentityModelLineProjection implements IModelLineProjection { - public static readonly INSTANCE = new VisibleIdentitySplitLine(); + public static readonly INSTANCE = new IdentityModelLineProjection(); private constructor() { } @@ -1062,11 +1069,11 @@ class VisibleIdentitySplitLine implements ISplitLine { return true; } - public setVisible(isVisible: boolean): ISplitLine { + public setVisible(isVisible: boolean): IModelLineProjection { if (isVisible) { return this; } - return InvisibleIdentitySplitLine.INSTANCE; + return HiddenModelLineProjection.INSTANCE; } public getLineBreakData(): LineBreakData | null { @@ -1136,9 +1143,12 @@ class VisibleIdentitySplitLine implements ISplitLine { } } -class InvisibleIdentitySplitLine implements ISplitLine { +/** + * This projection hides the model line. + */ +class HiddenModelLineProjection implements IModelLineProjection { - public static readonly INSTANCE = new InvisibleIdentitySplitLine(); + public static readonly INSTANCE = new HiddenModelLineProjection(); private constructor() { } @@ -1146,11 +1156,11 @@ class InvisibleIdentitySplitLine implements ISplitLine { return false; } - public setVisible(isVisible: boolean): ISplitLine { + public setVisible(isVisible: boolean): IModelLineProjection { if (!isVisible) { return this; } - return VisibleIdentitySplitLine.INSTANCE; + return IdentityModelLineProjection.INSTANCE; } public getLineBreakData(): LineBreakData | null { @@ -1206,7 +1216,12 @@ class InvisibleIdentitySplitLine implements ISplitLine { } } -export class SplitLine implements ISplitLine { +/** + * This projection is used to + * * wrap model lines + * * inject text + */ +export class ModelLineProjection implements IModelLineProjection { private readonly _lineBreakData: LineBreakData; private _isVisible: boolean; @@ -1220,7 +1235,7 @@ export class SplitLine implements ISplitLine { return this._isVisible; } - public setVisible(isVisible: boolean): ISplitLine { + public setVisible(isVisible: boolean): IModelLineProjection { this._isVisible = isVisible; return this; } @@ -1507,15 +1522,15 @@ function _makeSpaces(count: number): string { return new Array(count + 1).join(' '); } -function createSplitLine(lineBreakData: LineBreakData | null, isVisible: boolean): ISplitLine { +function createModelLineProjection(lineBreakData: LineBreakData | null, isVisible: boolean): IModelLineProjection { if (lineBreakData === null) { // No mapping needed if (isVisible) { - return VisibleIdentitySplitLine.INSTANCE; + return IdentityModelLineProjection.INSTANCE; } - return InvisibleIdentitySplitLine.INSTANCE; + return HiddenModelLineProjection.INSTANCE; } else { - return new SplitLine(lineBreakData, isVisible); + return new ModelLineProjection(lineBreakData, isVisible); } } diff --git a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts index a65305637ab..ab25a2773aa 100644 --- a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts +++ b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts @@ -14,7 +14,7 @@ import { TextModel } from 'vs/editor/common/model/textModel'; import * as modes from 'vs/editor/common/modes'; import { NULL_STATE } from 'vs/editor/common/modes/nullMode'; import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; -import { ISimpleModel, SplitLine, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection'; +import { ISimpleModel, ModelLineProjection, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection'; import { LineBreakData, ViewLineData } from 'vs/editor/common/viewModel/viewModel'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -946,8 +946,8 @@ function pos(lineNumber: number, column: number): Position { return new Position(lineNumber, column); } -function createSplitLine(splitLengths: number[], breakingOffsetsVisibleColumn: number[], wrappedTextIndentWidth: number, isVisible: boolean = true): SplitLine { - return new SplitLine(createLineBreakData(splitLengths, breakingOffsetsVisibleColumn, wrappedTextIndentWidth), isVisible); +function createSplitLine(splitLengths: number[], breakingOffsetsVisibleColumn: number[], wrappedTextIndentWidth: number, isVisible: boolean = true): ModelLineProjection { + return new ModelLineProjection(createLineBreakData(splitLengths, breakingOffsetsVisibleColumn, wrappedTextIndentWidth), isVisible); } function createLineBreakData(breakingLengths: number[], breakingOffsetsVisibleColumn: number[], wrappedTextIndentWidth: number): LineBreakData {