/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { WrappingIndent } from 'vs/editor/common/config/editorOptions'; import { LineTokens } from 'vs/editor/common/core/lineTokens'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { EndOfLinePreference, IActiveIndentGuideInfo, IModelDecoration, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; import { ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModel'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { PrefixSumComputerWithCache } from 'vs/editor/common/viewModel/prefixSumComputer'; import { ICoordinatesConverter, IOverviewRulerDecorations, ViewLineData } from 'vs/editor/common/viewModel/viewModel'; import { ITheme } from 'vs/platform/theme/common/themeService'; import { IDisposable } from 'vs/base/common/lifecycle'; export class OutputPosition { _outputPositionBrand: void; outputLineIndex: number; outputOffset: number; constructor(outputLineIndex: number, outputOffset: number) { this.outputLineIndex = outputLineIndex; this.outputOffset = outputOffset; } } export interface ILineMapping { getOutputLineCount(): number; getWrappedLinesIndent(): string; getInputOffsetOfOutputPosition(outputLineIndex: number, outputOffset: number): number; getOutputPositionOfInputOffset(inputOffset: number): OutputPosition; } export interface ILineMapperFactory { createLineMapping(lineText: string, tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ILineMapping | null; } export interface ISimpleModel { getLineTokens(lineNumber: number): LineTokens; getLineContent(lineNumber: number): string; getLineLength(lineNumber: number): number; getLineMinColumn(lineNumber: number): number; getLineMaxColumn(lineNumber: number): number; getValueInRange(range: IRange, eol?: EndOfLinePreference): string; } export interface ISplitLine { isVisible(): boolean; setVisible(isVisible: boolean): ISplitLine; getViewLineCount(): number; getViewLineContent(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): string; getViewLineLength(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number; getViewLineMinColumn(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number; getViewLineMaxColumn(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number; getViewLineData(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): ViewLineData; getViewLinesData(model: ISimpleModel, modelLineNumber: number, fromOuputLineIndex: number, toOutputLineIndex: number, globalStartIndex: number, needed: boolean[], result: Array): void; getModelColumnOfViewPosition(outputLineIndex: number, outputColumn: number): number; getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number): Position; getViewLineNumberOfModelPosition(deltaLineNumber: number, inputColumn: number): number; } export interface IViewModelLinesCollection extends IDisposable { createCoordinatesConverter(): ICoordinatesConverter; setWrappingSettings(wrappingIndent: WrappingIndent, wrappingColumn: number, columnsForFullWidthChar: number): boolean; setTabSize(newTabSize: number): boolean; getHiddenAreas(): Range[]; setHiddenAreas(_ranges: Range[]): boolean; onModelFlushed(): void; onModelLinesDeleted(versionId: number, fromLineNumber: number, toLineNumber: number): viewEvents.ViewLinesDeletedEvent | null; onModelLinesInserted(versionId: number, fromLineNumber: number, toLineNumber: number, text: string[]): viewEvents.ViewLinesInsertedEvent | null; onModelLineChanged(versionId: number, lineNumber: number, newText: string): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null]; acceptVersionId(versionId: number): void; getViewLineCount(): number; warmUpLookupCache(viewStartLineNumber: number, viewEndLineNumber: number): void; getActiveIndentGuide(viewLineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo; getViewLinesIndentGuides(viewStartLineNumber: number, viewEndLineNumber: number): number[]; getViewLineContent(viewLineNumber: number): string; getViewLineLength(viewLineNumber: number): number; getViewLineMinColumn(viewLineNumber: number): number; getViewLineMaxColumn(viewLineNumber: number): number; getViewLineData(viewLineNumber: number): ViewLineData; getViewLinesData(viewStartLineNumber: number, viewEndLineNumber: number, needed: boolean[]): Array; getAllOverviewRulerDecorations(ownerId: number, filterOutValidation: boolean, theme: ITheme): IOverviewRulerDecorations; getDecorationsInRange(range: Range, ownerId: number, filterOutValidation: boolean): IModelDecoration[]; } export class CoordinatesConverter implements ICoordinatesConverter { private readonly _lines: SplitLinesCollection; constructor(lines: SplitLinesCollection) { this._lines = lines; } // View -> Model conversion and related methods public convertViewPositionToModelPosition(viewPosition: Position): Position { return this._lines.convertViewPositionToModelPosition(viewPosition.lineNumber, viewPosition.column); } public convertViewRangeToModelRange(viewRange: Range): Range { let start = this._lines.convertViewPositionToModelPosition(viewRange.startLineNumber, viewRange.startColumn); let end = this._lines.convertViewPositionToModelPosition(viewRange.endLineNumber, viewRange.endColumn); return new Range(start.lineNumber, start.column, end.lineNumber, end.column); } public validateViewPosition(viewPosition: Position, expectedModelPosition: Position): Position { return this._lines.validateViewPosition(viewPosition.lineNumber, viewPosition.column, expectedModelPosition); } public validateViewRange(viewRange: Range, expectedModelRange: Range): Range { const validViewStart = this._lines.validateViewPosition(viewRange.startLineNumber, viewRange.startColumn, expectedModelRange.getStartPosition()); const validViewEnd = this._lines.validateViewPosition(viewRange.endLineNumber, viewRange.endColumn, expectedModelRange.getEndPosition()); return new Range(validViewStart.lineNumber, validViewStart.column, validViewEnd.lineNumber, validViewEnd.column); } // Model -> View conversion and related methods public convertModelPositionToViewPosition(modelPosition: Position): Position { return this._lines.convertModelPositionToViewPosition(modelPosition.lineNumber, modelPosition.column); } public convertModelRangeToViewRange(modelRange: Range): Range { let start = this._lines.convertModelPositionToViewPosition(modelRange.startLineNumber, modelRange.startColumn); let end = this._lines.convertModelPositionToViewPosition(modelRange.endLineNumber, modelRange.endColumn); return new Range(start.lineNumber, start.column, end.lineNumber, end.column); } public modelPositionIsVisible(modelPosition: Position): boolean { return this._lines.modelPositionIsVisible(modelPosition.lineNumber, modelPosition.column); } } const enum IndentGuideRepeatOption { BlockNone = 0, BlockSubsequent = 1, BlockAll = 2 } export class SplitLinesCollection implements IViewModelLinesCollection { private readonly model: ITextModel; private _validModelVersionId: number; private wrappingColumn: number; private columnsForFullWidthChar: number; private wrappingIndent: WrappingIndent; private tabSize: number; private lines!: ISplitLine[]; private prefixSumComputer!: PrefixSumComputerWithCache; private readonly linePositionMapperFactory: ILineMapperFactory; private hiddenAreasIds!: string[]; constructor(model: ITextModel, linePositionMapperFactory: ILineMapperFactory, tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent) { this.model = model; this._validModelVersionId = -1; this.tabSize = tabSize; this.wrappingColumn = wrappingColumn; this.columnsForFullWidthChar = columnsForFullWidthChar; this.wrappingIndent = wrappingIndent; this.linePositionMapperFactory = linePositionMapperFactory; this._constructLines(true); } public dispose(): void { this.hiddenAreasIds = this.model.deltaDecorations(this.hiddenAreasIds, []); } public createCoordinatesConverter(): ICoordinatesConverter { return new CoordinatesConverter(this); } private _ensureValidState(): void { let modelVersion = this.model.getVersionId(); if (modelVersion !== this._validModelVersionId) { // This is pretty bad, it means we lost track of the model... throw new Error(`ViewModel is out of sync with Model!`); } if (this.lines.length !== this.model.getLineCount()) { // This is pretty bad, it means we lost track of the model... this._constructLines(false); } } private _constructLines(resetHiddenAreas: boolean): void { this.lines = []; if (resetHiddenAreas) { this.hiddenAreasIds = []; } let linesContent = this.model.getLinesContent(); let lineCount = linesContent.length; let values = new Uint32Array(lineCount); let hiddenAreas = this.hiddenAreasIds.map((areaId) => this.model.getDecorationRange(areaId)!).sort(Range.compareRangesUsingStarts); let hiddenAreaStart = 1, hiddenAreaEnd = 0; let hiddenAreaIdx = -1; let nextLineNumberToUpdateHiddenArea = (hiddenAreaIdx + 1 < hiddenAreas.length) ? hiddenAreaEnd + 1 : lineCount + 2; for (let i = 0; i < lineCount; i++) { let lineNumber = i + 1; if (lineNumber === nextLineNumberToUpdateHiddenArea) { hiddenAreaIdx++; hiddenAreaStart = hiddenAreas[hiddenAreaIdx]!.startLineNumber; hiddenAreaEnd = hiddenAreas[hiddenAreaIdx]!.endLineNumber; nextLineNumberToUpdateHiddenArea = (hiddenAreaIdx + 1 < hiddenAreas.length) ? hiddenAreaEnd + 1 : lineCount + 2; } let isInHiddenArea = (lineNumber >= hiddenAreaStart && lineNumber <= hiddenAreaEnd); let line = createSplitLine(this.linePositionMapperFactory, linesContent[i], this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent, !isInHiddenArea); values[i] = line.getViewLineCount(); this.lines[i] = line; } this._validModelVersionId = this.model.getVersionId(); this.prefixSumComputer = new PrefixSumComputerWithCache(values); } public getHiddenAreas(): Range[] { return this.hiddenAreasIds.map((decId) => { return this.model.getDecorationRange(decId)!; }); } private _reduceRanges(_ranges: Range[]): Range[] { if (_ranges.length === 0) { return []; } let ranges = _ranges.map(r => this.model.validateRange(r)).sort(Range.compareRangesUsingStarts); let result: Range[] = []; let currentRangeStart = ranges[0].startLineNumber; let currentRangeEnd = ranges[0].endLineNumber; for (let i = 1, len = ranges.length; i < len; i++) { let range = ranges[i]; if (range.startLineNumber > currentRangeEnd + 1) { result.push(new Range(currentRangeStart, 1, currentRangeEnd, 1)); currentRangeStart = range.startLineNumber; currentRangeEnd = range.endLineNumber; } else if (range.endLineNumber > currentRangeEnd) { currentRangeEnd = range.endLineNumber; } } result.push(new Range(currentRangeStart, 1, currentRangeEnd, 1)); return result; } public setHiddenAreas(_ranges: Range[]): boolean { let newRanges = this._reduceRanges(_ranges); // BEGIN TODO@Martin: Please stop calling this method on each model change! let oldRanges = this.hiddenAreasIds.map((areaId) => this.model.getDecorationRange(areaId)!).sort(Range.compareRangesUsingStarts); if (newRanges.length === oldRanges.length) { let hasDifference = false; for (let i = 0; i < newRanges.length; i++) { if (!newRanges[i].equalsRange(oldRanges[i])) { hasDifference = true; break; } } if (!hasDifference) { return false; } } // END TODO@Martin: Please stop calling this method on each model change! let newDecorations: IModelDeltaDecoration[] = []; for (const newRange of newRanges) { newDecorations.push({ range: newRange, options: ModelDecorationOptions.EMPTY }); } this.hiddenAreasIds = this.model.deltaDecorations(this.hiddenAreasIds, newDecorations); let hiddenAreas = newRanges; let hiddenAreaStart = 1, hiddenAreaEnd = 0; let hiddenAreaIdx = -1; let nextLineNumberToUpdateHiddenArea = (hiddenAreaIdx + 1 < hiddenAreas.length) ? hiddenAreaEnd + 1 : this.lines.length + 2; let hasVisibleLine = false; for (let i = 0; i < this.lines.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; } 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); lineChanged = true; } } else { hasVisibleLine = true; // Line should be visible if (!this.lines[i].isVisible()) { this.lines[i] = this.lines[i].setVisible(true); lineChanged = true; } } if (lineChanged) { let newOutputLineCount = this.lines[i].getViewLineCount(); this.prefixSumComputer.changeValue(i, newOutputLineCount); } } if (!hasVisibleLine) { // Cannot have everything be hidden => reveal everything! this.setHiddenAreas([]); } return true; } public modelPositionIsVisible(modelLineNumber: number, _modelColumn: number): boolean { if (modelLineNumber < 1 || modelLineNumber > this.lines.length) { // invalid arguments return false; } return this.lines[modelLineNumber - 1].isVisible(); } public setTabSize(newTabSize: number): boolean { if (this.tabSize === newTabSize) { return false; } this.tabSize = newTabSize; this._constructLines(false); return true; } public setWrappingSettings(wrappingIndent: WrappingIndent, wrappingColumn: number, columnsForFullWidthChar: number): boolean { if (this.wrappingIndent === wrappingIndent && this.wrappingColumn === wrappingColumn && this.columnsForFullWidthChar === columnsForFullWidthChar) { return false; } this.wrappingIndent = wrappingIndent; this.wrappingColumn = wrappingColumn; this.columnsForFullWidthChar = columnsForFullWidthChar; this._constructLines(false); return true; } public onModelFlushed(): void { this._constructLines(true); } public onModelLinesDeleted(versionId: number, fromLineNumber: number, toLineNumber: number): viewEvents.ViewLinesDeletedEvent | null { if (versionId <= this._validModelVersionId) { // Here we check for versionId in case the lines were reconstructed in the meantime. // We don't want to apply stale change events on top of a newer read model state. return null; } let outputFromLineNumber = (fromLineNumber === 1 ? 1 : this.prefixSumComputer.getAccumulatedValue(fromLineNumber - 2) + 1); let outputToLineNumber = this.prefixSumComputer.getAccumulatedValue(toLineNumber - 1); this.lines.splice(fromLineNumber - 1, toLineNumber - fromLineNumber + 1); this.prefixSumComputer.removeValues(fromLineNumber - 1, toLineNumber - fromLineNumber + 1); return new viewEvents.ViewLinesDeletedEvent(outputFromLineNumber, outputToLineNumber); } public onModelLinesInserted(versionId: number, fromLineNumber: number, _toLineNumber: number, text: string[]): viewEvents.ViewLinesInsertedEvent | null { if (versionId <= this._validModelVersionId) { // Here we check for versionId in case the lines were reconstructed in the meantime. // We don't want to apply stale change events on top of a newer read model state. return null; } let hiddenAreas = this.getHiddenAreas(); let isInHiddenArea = false; let testPosition = new Position(fromLineNumber, 1); for (const hiddenArea of hiddenAreas) { if (hiddenArea.containsPosition(testPosition)) { isInHiddenArea = true; break; } } let outputFromLineNumber = (fromLineNumber === 1 ? 1 : this.prefixSumComputer.getAccumulatedValue(fromLineNumber - 2) + 1); let totalOutputLineCount = 0; let insertLines: ISplitLine[] = []; let insertPrefixSumValues = new Uint32Array(text.length); for (let i = 0, len = text.length; i < len; i++) { let line = createSplitLine(this.linePositionMapperFactory, text[i], this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent, !isInHiddenArea); insertLines.push(line); let outputLineCount = line.getViewLineCount(); totalOutputLineCount += outputLineCount; insertPrefixSumValues[i] = outputLineCount; } // TODO@Alex: use arrays.arrayInsert this.lines = this.lines.slice(0, fromLineNumber - 1).concat(insertLines).concat(this.lines.slice(fromLineNumber - 1)); this.prefixSumComputer.insertValues(fromLineNumber - 1, insertPrefixSumValues); return new viewEvents.ViewLinesInsertedEvent(outputFromLineNumber, outputFromLineNumber + totalOutputLineCount - 1); } public onModelLineChanged(versionId: number, lineNumber: number, newText: string): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] { if (versionId <= this._validModelVersionId) { // Here we check for versionId in case the lines were reconstructed in the meantime. // We don't want to apply stale change events on top of a newer read model state. return [false, null, null, null]; } let lineIndex = lineNumber - 1; let oldOutputLineCount = this.lines[lineIndex].getViewLineCount(); let isVisible = this.lines[lineIndex].isVisible(); let line = createSplitLine(this.linePositionMapperFactory, newText, this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent, isVisible); this.lines[lineIndex] = line; let newOutputLineCount = this.lines[lineIndex].getViewLineCount(); let lineMappingChanged = false; let changeFrom = 0; let changeTo = -1; let insertFrom = 0; let insertTo = -1; let deleteFrom = 0; let deleteTo = -1; if (oldOutputLineCount > newOutputLineCount) { changeFrom = (lineNumber === 1 ? 1 : this.prefixSumComputer.getAccumulatedValue(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); 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); changeTo = changeFrom + newOutputLineCount - 1; } this.prefixSumComputer.changeValue(lineIndex, newOutputLineCount); const viewLinesChangedEvent = (changeFrom <= changeTo ? new viewEvents.ViewLinesChangedEvent(changeFrom, changeTo) : null); const viewLinesInsertedEvent = (insertFrom <= insertTo ? new viewEvents.ViewLinesInsertedEvent(insertFrom, insertTo) : null); const viewLinesDeletedEvent = (deleteFrom <= deleteTo ? new viewEvents.ViewLinesDeletedEvent(deleteFrom, deleteTo) : null); return [lineMappingChanged, viewLinesChangedEvent, viewLinesInsertedEvent, viewLinesDeletedEvent]; } public acceptVersionId(versionId: number): void { this._validModelVersionId = versionId; if (this.lines.length === 1 && !this.lines[0].isVisible()) { // At least one line must be visible => reset hidden areas this.setHiddenAreas([]); } } public getViewLineCount(): number { this._ensureValidState(); return this.prefixSumComputer.getTotalValue(); } private _toValidViewLineNumber(viewLineNumber: number): number { if (viewLineNumber < 1) { return 1; } let viewLineCount = this.getViewLineCount(); if (viewLineNumber > viewLineCount) { return viewLineCount; } return viewLineNumber; } /** * Gives a hint that a lot of requests are about to come in for these line numbers. */ public warmUpLookupCache(viewStartLineNumber: number, viewEndLineNumber: number): void { this.prefixSumComputer.warmUpCache(viewStartLineNumber - 1, viewEndLineNumber - 1); } public getActiveIndentGuide(viewLineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo { this._ensureValidState(); viewLineNumber = this._toValidViewLineNumber(viewLineNumber); minLineNumber = this._toValidViewLineNumber(minLineNumber); maxLineNumber = this._toValidViewLineNumber(maxLineNumber); const modelPosition = this.convertViewPositionToModelPosition(viewLineNumber, this.getViewLineMinColumn(viewLineNumber)); const modelMinPosition = this.convertViewPositionToModelPosition(minLineNumber, this.getViewLineMinColumn(minLineNumber)); const modelMaxPosition = this.convertViewPositionToModelPosition(maxLineNumber, this.getViewLineMinColumn(maxLineNumber)); const result = this.model.getActiveIndentGuide(modelPosition.lineNumber, modelMinPosition.lineNumber, modelMaxPosition.lineNumber); const viewStartPosition = this.convertModelPositionToViewPosition(result.startLineNumber, 1); const viewEndPosition = this.convertModelPositionToViewPosition(result.endLineNumber, this.model.getLineMaxColumn(result.endLineNumber)); return { startLineNumber: viewStartPosition.lineNumber, endLineNumber: viewEndPosition.lineNumber, indent: result.indent }; } public getViewLinesIndentGuides(viewStartLineNumber: number, viewEndLineNumber: number): number[] { this._ensureValidState(); viewStartLineNumber = this._toValidViewLineNumber(viewStartLineNumber); viewEndLineNumber = this._toValidViewLineNumber(viewEndLineNumber); const modelStart = this.convertViewPositionToModelPosition(viewStartLineNumber, this.getViewLineMinColumn(viewStartLineNumber)); const modelEnd = this.convertViewPositionToModelPosition(viewEndLineNumber, this.getViewLineMaxColumn(viewEndLineNumber)); let result: number[] = []; let resultRepeatCount: number[] = []; let resultRepeatOption: IndentGuideRepeatOption[] = []; const modelStartLineIndex = modelStart.lineNumber - 1; const modelEndLineIndex = modelEnd.lineNumber - 1; let reqStart: Position | null = null; for (let modelLineIndex = modelStartLineIndex; modelLineIndex <= modelEndLineIndex; modelLineIndex++) { const line = this.lines[modelLineIndex]; if (line.isVisible()) { let viewLineStartIndex = line.getViewLineNumberOfModelPosition(0, modelLineIndex === modelStartLineIndex ? modelStart.column : 1); let viewLineEndIndex = line.getViewLineNumberOfModelPosition(0, this.model.getLineMaxColumn(modelLineIndex + 1)); let count = viewLineEndIndex - viewLineStartIndex + 1; let option = IndentGuideRepeatOption.BlockNone; if (count > 1 && line.getViewLineMinColumn(this.model, modelLineIndex + 1, viewLineEndIndex) === 1) { // wrapped lines should block indent guides option = (viewLineStartIndex === 0 ? IndentGuideRepeatOption.BlockSubsequent : IndentGuideRepeatOption.BlockAll); } resultRepeatCount.push(count); resultRepeatOption.push(option); // merge into previous request if (reqStart === null) { reqStart = new Position(modelLineIndex + 1, 0); } } else { // hit invisible line => flush request if (reqStart !== null) { result = result.concat(this.model.getLinesIndentGuides(reqStart.lineNumber, modelLineIndex)); reqStart = null; } } } if (reqStart !== null) { result = result.concat(this.model.getLinesIndentGuides(reqStart.lineNumber, modelEnd.lineNumber)); reqStart = null; } const viewLineCount = viewEndLineNumber - viewStartLineNumber + 1; let viewIndents = new Array(viewLineCount); let currIndex = 0; for (let i = 0, len = result.length; i < len; i++) { let value = result[i]; let count = Math.min(viewLineCount - currIndex, resultRepeatCount[i]); let option = resultRepeatOption[i]; let blockAtIndex: number; if (option === IndentGuideRepeatOption.BlockAll) { blockAtIndex = 0; } else if (option === IndentGuideRepeatOption.BlockSubsequent) { blockAtIndex = 1; } else { blockAtIndex = count; } for (let j = 0; j < count; j++) { if (j === blockAtIndex) { value = 0; } viewIndents[currIndex++] = value; } } return viewIndents; } public getViewLineContent(viewLineNumber: number): string { this._ensureValidState(); 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); } public getViewLineLength(viewLineNumber: number): number { this._ensureValidState(); 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); } public getViewLineMinColumn(viewLineNumber: number): number { this._ensureValidState(); 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); } public getViewLineMaxColumn(viewLineNumber: number): number { this._ensureValidState(); 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); } public getViewLineData(viewLineNumber: number): ViewLineData { this._ensureValidState(); 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); } public getViewLinesData(viewStartLineNumber: number, viewEndLineNumber: number, needed: boolean[]): ViewLineData[] { this._ensureValidState(); viewStartLineNumber = this._toValidViewLineNumber(viewStartLineNumber); viewEndLineNumber = this._toValidViewLineNumber(viewEndLineNumber); let start = this.prefixSumComputer.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]; if (!line.isVisible()) { continue; } let fromViewLineIndex = (modelLineIndex === startModelLineIndex ? startRemainder : 0); let remainingViewLineCount = line.getViewLineCount() - fromViewLineIndex; let lastLine = false; if (viewLineNumber + remainingViewLineCount > viewEndLineNumber) { lastLine = true; remainingViewLineCount = viewEndLineNumber - viewLineNumber + 1; } let toViewLineIndex = fromViewLineIndex + remainingViewLineCount; line.getViewLinesData(this.model, modelLineIndex + 1, fromViewLineIndex, toViewLineIndex, viewLineNumber - viewStartLineNumber, needed, result); viewLineNumber += remainingViewLineCount; if (lastLine) { break; } } return result; } public validateViewPosition(viewLineNumber: number, viewColumn: number, expectedModelPosition: Position): Position { this._ensureValidState(); viewLineNumber = this._toValidViewLineNumber(viewLineNumber); let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); let lineIndex = r.index; let remainder = r.remainder; let line = this.lines[lineIndex]; let minColumn = line.getViewLineMinColumn(this.model, lineIndex + 1, remainder); let maxColumn = line.getViewLineMaxColumn(this.model, lineIndex + 1, remainder); if (viewColumn < minColumn) { viewColumn = minColumn; } if (viewColumn > maxColumn) { viewColumn = maxColumn; } let computedModelColumn = line.getModelColumnOfViewPosition(remainder, viewColumn); let computedModelPosition = this.model.validatePosition(new Position(lineIndex + 1, computedModelColumn)); if (computedModelPosition.equals(expectedModelPosition)) { return new Position(viewLineNumber, viewColumn); } return this.convertModelPositionToViewPosition(expectedModelPosition.lineNumber, expectedModelPosition.column); } public convertViewPositionToModelPosition(viewLineNumber: number, viewColumn: number): Position { this._ensureValidState(); viewLineNumber = this._toValidViewLineNumber(viewLineNumber); let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); let lineIndex = r.index; let remainder = r.remainder; let inputColumn = this.lines[lineIndex].getModelColumnOfViewPosition(remainder, viewColumn); // console.log('out -> in ' + viewLineNumber + ',' + viewColumn + ' ===> ' + (lineIndex+1) + ',' + inputColumn); return this.model.validatePosition(new Position(lineIndex + 1, inputColumn)); } public convertModelPositionToViewPosition(_modelLineNumber: number, _modelColumn: number): Position { this._ensureValidState(); let validPosition = this.model.validatePosition(new Position(_modelLineNumber, _modelColumn)); let inputLineNumber = validPosition.lineNumber; let inputColumn = validPosition.column; let lineIndex = inputLineNumber - 1, lineIndexChanged = false; while (lineIndex > 0 && !this.lines[lineIndex].isVisible()) { lineIndex--; lineIndexChanged = true; } if (lineIndex === 0 && !this.lines[lineIndex].isVisible()) { // Could not reach a real line // console.log('in -> out ' + inputLineNumber + ',' + inputColumn + ' ===> ' + 1 + ',' + 1); return new Position(1, 1); } let deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getAccumulatedValue(lineIndex - 1)); let r: Position; if (lineIndexChanged) { r = this.lines[lineIndex].getViewPositionOfModelPosition(deltaLineNumber, this.model.getLineMaxColumn(lineIndex + 1)); } else { r = this.lines[inputLineNumber - 1].getViewPositionOfModelPosition(deltaLineNumber, inputColumn); } // console.log('in -> out ' + inputLineNumber + ',' + inputColumn + ' ===> ' + r.lineNumber + ',' + r); return r; } private _getViewLineNumberForModelPosition(inputLineNumber: number, inputColumn: number): number { 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)); return this.lines[lineIndex].getViewLineNumberOfModelPosition(deltaLineNumber, inputColumn); } // this model line is not visible while (lineIndex > 0 && !this.lines[lineIndex].isVisible()) { lineIndex--; } if (lineIndex === 0 && !this.lines[lineIndex].isVisible()) { // Could not reach a real line return 1; } const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getAccumulatedValue(lineIndex - 1)); return this.lines[lineIndex].getViewLineNumberOfModelPosition(deltaLineNumber, this.model.getLineMaxColumn(lineIndex + 1)); } public getAllOverviewRulerDecorations(ownerId: number, filterOutValidation: boolean, theme: ITheme): IOverviewRulerDecorations { const decorations = this.model.getOverviewRulerDecorations(ownerId, filterOutValidation); const result = new OverviewRulerDecorations(); for (const decoration of decorations) { const opts = decoration.options.overviewRuler; const lane = opts ? opts.position : 0; if (lane === 0) { continue; } const color = opts.getColor(theme); const viewStartLineNumber = this._getViewLineNumberForModelPosition(decoration.range.startLineNumber, decoration.range.startColumn); const viewEndLineNumber = this._getViewLineNumberForModelPosition(decoration.range.endLineNumber, decoration.range.endColumn); result.accept(color, viewStartLineNumber, viewEndLineNumber, lane); } return result.result; } public getDecorationsInRange(range: Range, ownerId: number, filterOutValidation: boolean): IModelDecoration[] { const modelStart = this.convertViewPositionToModelPosition(range.startLineNumber, range.startColumn); const modelEnd = this.convertViewPositionToModelPosition(range.endLineNumber, range.endColumn); if (modelEnd.lineNumber - modelStart.lineNumber <= range.endLineNumber - range.startLineNumber) { // most likely there are no hidden lines => fast path // fetch decorations from column 1 to cover the case of wrapped lines that have whole line decorations at column 1 return this.model.getDecorationsInRange(new Range(modelStart.lineNumber, 1, modelEnd.lineNumber, modelEnd.column), ownerId, filterOutValidation); } let result: IModelDecoration[] = []; const modelStartLineIndex = modelStart.lineNumber - 1; const modelEndLineIndex = modelEnd.lineNumber - 1; let reqStart: Position | null = null; for (let modelLineIndex = modelStartLineIndex; modelLineIndex <= modelEndLineIndex; modelLineIndex++) { const line = this.lines[modelLineIndex]; if (line.isVisible()) { // merge into previous request if (reqStart === null) { reqStart = new Position(modelLineIndex + 1, modelLineIndex === modelStartLineIndex ? modelStart.column : 1); } } else { // hit invisible line => flush request if (reqStart !== null) { const maxLineColumn = this.model.getLineMaxColumn(modelLineIndex); result = result.concat(this.model.getDecorationsInRange(new Range(reqStart.lineNumber, reqStart.column, modelLineIndex, maxLineColumn), ownerId, filterOutValidation)); reqStart = null; } } } if (reqStart !== null) { result = result.concat(this.model.getDecorationsInRange(new Range(reqStart.lineNumber, reqStart.column, modelEnd.lineNumber, modelEnd.column), ownerId, filterOutValidation)); reqStart = null; } result.sort((a, b) => { const res = Range.compareRangesUsingStarts(a.range, b.range); if (res === 0) { if (a.id < b.id) { return -1; } if (a.id > b.id) { return 1; } return 0; } return res; }); // Eliminate duplicate decorations that might have intersected our visible ranges multiple times let finalResult: IModelDecoration[] = [], finalResultLen = 0; let prevDecId: string | null = null; for (const dec of result) { const decId = dec.id; if (prevDecId === decId) { // skip continue; } prevDecId = decId; finalResult[finalResultLen++] = dec; } return finalResult; } } class VisibleIdentitySplitLine implements ISplitLine { public static readonly INSTANCE = new VisibleIdentitySplitLine(); private constructor() { } public isVisible(): boolean { return true; } public setVisible(isVisible: boolean): ISplitLine { if (isVisible) { return this; } return InvisibleIdentitySplitLine.INSTANCE; } public getViewLineCount(): number { return 1; } public getViewLineContent(model: ISimpleModel, modelLineNumber: number, _outputLineIndex: number): string { return model.getLineContent(modelLineNumber); } public getViewLineLength(model: ISimpleModel, modelLineNumber: number, _outputLineIndex: number): number { return model.getLineLength(modelLineNumber); } public getViewLineMinColumn(model: ISimpleModel, modelLineNumber: number, _outputLineIndex: number): number { return model.getLineMinColumn(modelLineNumber); } public getViewLineMaxColumn(model: ISimpleModel, modelLineNumber: number, _outputLineIndex: number): number { return model.getLineMaxColumn(modelLineNumber); } public getViewLineData(model: ISimpleModel, modelLineNumber: number, _outputLineIndex: number): ViewLineData { let lineTokens = model.getLineTokens(modelLineNumber); let lineContent = lineTokens.getLineContent(); return new ViewLineData( lineContent, false, 1, lineContent.length + 1, lineTokens.inflate() ); } public getViewLinesData(model: ISimpleModel, modelLineNumber: number, _fromOuputLineIndex: number, _toOutputLineIndex: number, globalStartIndex: number, needed: boolean[], result: Array): void { if (!needed[globalStartIndex]) { result[globalStartIndex] = null; return; } result[globalStartIndex] = this.getViewLineData(model, modelLineNumber, 0); } public getModelColumnOfViewPosition(_outputLineIndex: number, outputColumn: number): number { return outputColumn; } public getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number): Position { return new Position(deltaLineNumber, inputColumn); } public getViewLineNumberOfModelPosition(deltaLineNumber: number, _inputColumn: number): number { return deltaLineNumber; } } class InvisibleIdentitySplitLine implements ISplitLine { public static readonly INSTANCE = new InvisibleIdentitySplitLine(); private constructor() { } public isVisible(): boolean { return false; } public setVisible(isVisible: boolean): ISplitLine { if (!isVisible) { return this; } return VisibleIdentitySplitLine.INSTANCE; } public getViewLineCount(): number { return 0; } public getViewLineContent(_model: ISimpleModel, _modelLineNumber: number, _outputLineIndex: number): string { throw new Error('Not supported'); } public getViewLineLength(_model: ISimpleModel, _modelLineNumber: number, _outputLineIndex: number): number { throw new Error('Not supported'); } public getViewLineMinColumn(_model: ISimpleModel, _modelLineNumber: number, _outputLineIndex: number): number { throw new Error('Not supported'); } public getViewLineMaxColumn(_model: ISimpleModel, _modelLineNumber: number, _outputLineIndex: number): number { throw new Error('Not supported'); } public getViewLineData(_model: ISimpleModel, _modelLineNumber: number, _outputLineIndex: number): ViewLineData { throw new Error('Not supported'); } public getViewLinesData(_model: ISimpleModel, _modelLineNumber: number, _fromOuputLineIndex: number, _toOutputLineIndex: number, _globalStartIndex: number, _needed: boolean[], _result: ViewLineData[]): void { throw new Error('Not supported'); } public getModelColumnOfViewPosition(_outputLineIndex: number, _outputColumn: number): number { throw new Error('Not supported'); } public getViewPositionOfModelPosition(_deltaLineNumber: number, _inputColumn: number): Position { throw new Error('Not supported'); } public getViewLineNumberOfModelPosition(_deltaLineNumber: number, _inputColumn: number): number { throw new Error('Not supported'); } } export class SplitLine implements ISplitLine { private readonly positionMapper: ILineMapping; private readonly outputLineCount: number; private readonly wrappedIndent: string; private readonly wrappedIndentLength: number; private _isVisible: boolean; constructor(positionMapper: ILineMapping, isVisible: boolean) { this.positionMapper = positionMapper; this.wrappedIndent = this.positionMapper.getWrappedLinesIndent(); this.wrappedIndentLength = this.wrappedIndent.length; this.outputLineCount = this.positionMapper.getOutputLineCount(); this._isVisible = isVisible; } public isVisible(): boolean { return this._isVisible; } public setVisible(isVisible: boolean): ISplitLine { this._isVisible = isVisible; return this; } public getViewLineCount(): number { if (!this._isVisible) { return 0; } return this.outputLineCount; } private getInputStartOffsetOfOutputLineIndex(outputLineIndex: number): number { return this.positionMapper.getInputOffsetOfOutputPosition(outputLineIndex, 0); } private getInputEndOffsetOfOutputLineIndex(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number { if (outputLineIndex + 1 === this.outputLineCount) { return model.getLineMaxColumn(modelLineNumber) - 1; } return this.positionMapper.getInputOffsetOfOutputPosition(outputLineIndex + 1, 0); } public getViewLineContent(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): string { if (!this._isVisible) { throw new Error('Not supported'); } let startOffset = this.getInputStartOffsetOfOutputLineIndex(outputLineIndex); let endOffset = this.getInputEndOffsetOfOutputLineIndex(model, modelLineNumber, outputLineIndex); let r = model.getValueInRange({ startLineNumber: modelLineNumber, startColumn: startOffset + 1, endLineNumber: modelLineNumber, endColumn: endOffset + 1 }); if (outputLineIndex > 0) { r = this.wrappedIndent + r; } return r; } public getViewLineLength(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number { if (!this._isVisible) { throw new Error('Not supported'); } let startOffset = this.getInputStartOffsetOfOutputLineIndex(outputLineIndex); let endOffset = this.getInputEndOffsetOfOutputLineIndex(model, modelLineNumber, outputLineIndex); let r = endOffset - startOffset; if (outputLineIndex > 0) { r = this.wrappedIndent.length + r; } return r; } public getViewLineMinColumn(_model: ITextModel, _modelLineNumber: number, outputLineIndex: number): number { if (!this._isVisible) { throw new Error('Not supported'); } if (outputLineIndex > 0) { return this.wrappedIndentLength + 1; } return 1; } public getViewLineMaxColumn(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number { if (!this._isVisible) { throw new Error('Not supported'); } return this.getViewLineContent(model, modelLineNumber, outputLineIndex).length + 1; } public getViewLineData(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): ViewLineData { if (!this._isVisible) { throw new Error('Not supported'); } let startOffset = this.getInputStartOffsetOfOutputLineIndex(outputLineIndex); let endOffset = this.getInputEndOffsetOfOutputLineIndex(model, modelLineNumber, outputLineIndex); let lineContent = model.getValueInRange({ startLineNumber: modelLineNumber, startColumn: startOffset + 1, endLineNumber: modelLineNumber, endColumn: endOffset + 1 }); if (outputLineIndex > 0) { lineContent = this.wrappedIndent + lineContent; } let minColumn = (outputLineIndex > 0 ? this.wrappedIndentLength + 1 : 1); let maxColumn = lineContent.length + 1; let continuesWithWrappedLine = (outputLineIndex + 1 < this.getViewLineCount()); let deltaStartIndex = 0; if (outputLineIndex > 0) { deltaStartIndex = this.wrappedIndentLength; } let lineTokens = model.getLineTokens(modelLineNumber); return new ViewLineData( lineContent, continuesWithWrappedLine, minColumn, maxColumn, lineTokens.sliceAndInflate(startOffset, endOffset, deltaStartIndex) ); } public getViewLinesData(model: ITextModel, modelLineNumber: number, fromOuputLineIndex: number, toOutputLineIndex: number, globalStartIndex: number, needed: boolean[], result: Array): void { if (!this._isVisible) { throw new Error('Not supported'); } for (let outputLineIndex = fromOuputLineIndex; outputLineIndex < toOutputLineIndex; outputLineIndex++) { let globalIndex = globalStartIndex + outputLineIndex - fromOuputLineIndex; if (!needed[globalIndex]) { result[globalIndex] = null; continue; } result[globalIndex] = this.getViewLineData(model, modelLineNumber, outputLineIndex); } } public getModelColumnOfViewPosition(outputLineIndex: number, outputColumn: number): number { if (!this._isVisible) { throw new Error('Not supported'); } let adjustedColumn = outputColumn - 1; if (outputLineIndex > 0) { if (adjustedColumn < this.wrappedIndentLength) { adjustedColumn = 0; } else { adjustedColumn -= this.wrappedIndentLength; } } return this.positionMapper.getInputOffsetOfOutputPosition(outputLineIndex, adjustedColumn) + 1; } public getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number): Position { if (!this._isVisible) { throw new Error('Not supported'); } let r = this.positionMapper.getOutputPositionOfInputOffset(inputColumn - 1); let outputLineIndex = r.outputLineIndex; let outputColumn = r.outputOffset + 1; if (outputLineIndex > 0) { outputColumn += this.wrappedIndentLength; } // console.log('in -> out ' + deltaLineNumber + ',' + inputColumn + ' ===> ' + (deltaLineNumber+outputLineIndex) + ',' + outputColumn); return new Position(deltaLineNumber + outputLineIndex, outputColumn); } public getViewLineNumberOfModelPosition(deltaLineNumber: number, inputColumn: number): number { if (!this._isVisible) { throw new Error('Not supported'); } const r = this.positionMapper.getOutputPositionOfInputOffset(inputColumn - 1); return (deltaLineNumber + r.outputLineIndex); } } function createSplitLine(linePositionMapperFactory: ILineMapperFactory, text: string, tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent, isVisible: boolean): ISplitLine { let positionMapper = linePositionMapperFactory.createLineMapping(text, tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent); if (positionMapper === null) { // No mapping needed if (isVisible) { return VisibleIdentitySplitLine.INSTANCE; } return InvisibleIdentitySplitLine.INSTANCE; } else { return new SplitLine(positionMapper, isVisible); } } export class IdentityCoordinatesConverter implements ICoordinatesConverter { private readonly _lines: IdentityLinesCollection; constructor(lines: IdentityLinesCollection) { this._lines = lines; } private _validPosition(pos: Position): Position { return this._lines.model.validatePosition(pos); } private _validRange(range: Range): Range { return this._lines.model.validateRange(range); } // View -> Model conversion and related methods public convertViewPositionToModelPosition(viewPosition: Position): Position { return this._validPosition(viewPosition); } public convertViewRangeToModelRange(viewRange: Range): Range { return this._validRange(viewRange); } public validateViewPosition(_viewPosition: Position, expectedModelPosition: Position): Position { return this._validPosition(expectedModelPosition); } public validateViewRange(_viewRange: Range, expectedModelRange: Range): Range { return this._validRange(expectedModelRange); } // Model -> View conversion and related methods public convertModelPositionToViewPosition(modelPosition: Position): Position { return this._validPosition(modelPosition); } public convertModelRangeToViewRange(modelRange: Range): Range { return this._validRange(modelRange); } public modelPositionIsVisible(modelPosition: Position): boolean { const lineCount = this._lines.model.getLineCount(); if (modelPosition.lineNumber < 1 || modelPosition.lineNumber > lineCount) { // invalid arguments return false; } return true; } } export class IdentityLinesCollection implements IViewModelLinesCollection { public readonly model: ITextModel; constructor(model: ITextModel) { this.model = model; } public dispose(): void { } public createCoordinatesConverter(): ICoordinatesConverter { return new IdentityCoordinatesConverter(this); } public getHiddenAreas(): Range[] { return []; } public setHiddenAreas(_ranges: Range[]): boolean { return false; } public setTabSize(_newTabSize: number): boolean { return false; } public setWrappingSettings(_wrappingIndent: WrappingIndent, _wrappingColumn: number, _columnsForFullWidthChar: number): boolean { return false; } public onModelFlushed(): void { } public onModelLinesDeleted(_versionId: number, fromLineNumber: number, toLineNumber: number): viewEvents.ViewLinesDeletedEvent | null { return new viewEvents.ViewLinesDeletedEvent(fromLineNumber, toLineNumber); } public onModelLinesInserted(_versionId: number, fromLineNumber: number, toLineNumber: number, _text: string[]): viewEvents.ViewLinesInsertedEvent | null { return new viewEvents.ViewLinesInsertedEvent(fromLineNumber, toLineNumber); } public onModelLineChanged(_versionId: number, lineNumber: number, _newText: string): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] { return [false, new viewEvents.ViewLinesChangedEvent(lineNumber, lineNumber), null, null]; } public acceptVersionId(_versionId: number): void { } public getViewLineCount(): number { return this.model.getLineCount(); } public warmUpLookupCache(_viewStartLineNumber: number, _viewEndLineNumber: number): void { } public getActiveIndentGuide(viewLineNumber: number, _minLineNumber: number, _maxLineNumber: number): IActiveIndentGuideInfo { return { startLineNumber: viewLineNumber, endLineNumber: viewLineNumber, indent: 0 }; } public getViewLinesIndentGuides(viewStartLineNumber: number, viewEndLineNumber: number): number[] { const viewLineCount = viewEndLineNumber - viewStartLineNumber + 1; let result = new Array(viewLineCount); for (let i = 0; i < viewLineCount; i++) { result[i] = 0; } return result; } public getViewLineContent(viewLineNumber: number): string { return this.model.getLineContent(viewLineNumber); } public getViewLineLength(viewLineNumber: number): number { return this.model.getLineLength(viewLineNumber); } public getViewLineMinColumn(viewLineNumber: number): number { return this.model.getLineMinColumn(viewLineNumber); } public getViewLineMaxColumn(viewLineNumber: number): number { return this.model.getLineMaxColumn(viewLineNumber); } public getViewLineData(viewLineNumber: number): ViewLineData { let lineTokens = this.model.getLineTokens(viewLineNumber); let lineContent = lineTokens.getLineContent(); return new ViewLineData( lineContent, false, 1, lineContent.length + 1, lineTokens.inflate() ); } public getViewLinesData(viewStartLineNumber: number, viewEndLineNumber: number, needed: boolean[]): Array { const lineCount = this.model.getLineCount(); viewStartLineNumber = Math.min(Math.max(1, viewStartLineNumber), lineCount); viewEndLineNumber = Math.min(Math.max(1, viewEndLineNumber), lineCount); let result: Array = []; for (let lineNumber = viewStartLineNumber; lineNumber <= viewEndLineNumber; lineNumber++) { let idx = lineNumber - viewStartLineNumber; if (!needed[idx]) { result[idx] = null; } result[idx] = this.getViewLineData(lineNumber); } return result; } public getAllOverviewRulerDecorations(ownerId: number, filterOutValidation: boolean, theme: ITheme): IOverviewRulerDecorations { const decorations = this.model.getOverviewRulerDecorations(ownerId, filterOutValidation); const result = new OverviewRulerDecorations(); for (const decoration of decorations) { const opts = decoration.options.overviewRuler; const lane = opts ? opts.position : 0; if (lane === 0) { continue; } const color = opts.getColor(theme); const viewStartLineNumber = decoration.range.startLineNumber; const viewEndLineNumber = decoration.range.endLineNumber; result.accept(color, viewStartLineNumber, viewEndLineNumber, lane); } return result.result; } public getDecorationsInRange(range: Range, ownerId: number, filterOutValidation: boolean): IModelDecoration[] { return this.model.getDecorationsInRange(range, ownerId, filterOutValidation); } } class OverviewRulerDecorations { readonly result: IOverviewRulerDecorations = Object.create(null); public accept(color: string, startLineNumber: number, endLineNumber: number, lane: number): void { let prev = this.result[color]; if (prev) { const prevLane = prev[prev.length - 3]; const prevEndLineNumber = prev[prev.length - 1]; if (prevLane === lane && prevEndLineNumber + 1 >= startLineNumber) { // merge into prev if (endLineNumber > prevEndLineNumber) { prev[prev.length - 1] = endLineNumber; } return; } // push prev.push(lane, startLineNumber, endLineNumber); } else { this.result[color] = [lane, startLineNumber, endLineNumber]; } } }