UNPKG

@21epub/epub-thirdparty

Version:
1,008 lines 66.4 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as arrays from '../../../base/common/arrays.js'; import { LineTokens } from '../core/lineTokens.js'; import { Position } from '../core/position.js'; import { Range } from '../core/range.js'; import { IndentGuide, IndentGuideHorizontalLine } from '../model.js'; import { ModelDecorationOptions } from '../model/textModel.js'; import * as viewEvents from '../view/viewEvents.js'; import { PrefixSumIndexOfResult } from './prefixSumComputer.js'; import { SingleLineInlineDecoration, ViewLineData } from './viewModel.js'; import { LineInjectedText } from '../model/textModelEvents.js'; export class CoordinatesConverter { constructor(lines) { this._lines = lines; } // View -> Model conversion and related methods convertViewPositionToModelPosition(viewPosition) { return this._lines.convertViewPositionToModelPosition(viewPosition.lineNumber, viewPosition.column); } convertViewRangeToModelRange(viewRange) { return this._lines.convertViewRangeToModelRange(viewRange); } validateViewPosition(viewPosition, expectedModelPosition) { return this._lines.validateViewPosition(viewPosition.lineNumber, viewPosition.column, expectedModelPosition); } validateViewRange(viewRange, expectedModelRange) { return this._lines.validateViewRange(viewRange, expectedModelRange); } // Model -> View conversion and related methods convertModelPositionToViewPosition(modelPosition, affinity) { return this._lines.convertModelPositionToViewPosition(modelPosition.lineNumber, modelPosition.column, affinity); } convertModelRangeToViewRange(modelRange, affinity) { return this._lines.convertModelRangeToViewRange(modelRange, affinity); } modelPositionIsVisible(modelPosition) { return this._lines.modelPositionIsVisible(modelPosition.lineNumber, modelPosition.column); } getModelLineViewLineCount(modelLineNumber) { return this._lines.getModelLineViewLineCount(modelLineNumber); } getViewLineNumberOfModelPosition(modelLineNumber, modelColumn) { return this._lines.getViewLineNumberOfModelPosition(modelLineNumber, modelColumn); } } class LineNumberMapper { constructor(viewLineCounts) { this._counts = viewLineCounts; this._isValid = false; this._validEndIndex = -1; this._modelToView = []; this._viewToModel = []; } _invalidate(index) { this._isValid = false; this._validEndIndex = Math.min(this._validEndIndex, index - 1); } _ensureValid() { 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; } changeValue(index, value) { if (this._counts[index] === value) { // no change return; } this._counts[index] = value; this._invalidate(index); } removeValues(start, deleteCount) { this._counts.splice(start, deleteCount); this._invalidate(start); } insertValues(insertIndex, insertArr) { this._counts = arrays.arrayInsert(this._counts, insertIndex, insertArr); this._invalidate(insertIndex); } getTotalValue() { this._ensureValid(); return this._viewToModel.length; } getAccumulatedValue(index) { this._ensureValid(); return this._modelToView[index]; } getIndexOf(accumulatedValue) { 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 { constructor(editorId, model, domLineBreaksComputerFactory, monospaceLineBreaksComputerFactory, fontInfo, tabSize, wrappingStrategy, wrappingColumn, wrappingIndent) { this._editorId = editorId; this.model = model; this._validModelVersionId = -1; this._domLineBreaksComputerFactory = domLineBreaksComputerFactory; this._monospaceLineBreaksComputerFactory = monospaceLineBreaksComputerFactory; this.fontInfo = fontInfo; this.tabSize = tabSize; this.wrappingStrategy = wrappingStrategy; this.wrappingColumn = wrappingColumn; this.wrappingIndent = wrappingIndent; this._constructLines(/*resetHiddenAreas*/ true, null); } dispose() { this.hiddenAreasIds = this.model.deltaDecorations(this.hiddenAreasIds, []); } createCoordinatesConverter() { return new CoordinatesConverter(this); } _constructLines(resetHiddenAreas, previousLineBreaks) { this.lines = []; if (resetHiddenAreas) { this.hiddenAreasIds = []; } const linesContent = this.model.getLinesContent(); const injectedTextDecorations = this.model.getInjectedTextDecorations(this._editorId); const lineCount = linesContent.length; const lineBreaksComputer = this.createLineBreaksComputer(); const injectedTextQueue = new arrays.ArrayQueue(LineInjectedText.fromDecorations(injectedTextDecorations)); for (let i = 0; i < lineCount; i++) { const lineInjectedText = injectedTextQueue.takeWhile(t => t.lineNumber === i + 1); lineBreaksComputer.addRequest(linesContent[i], lineInjectedText, previousLineBreaks ? previousLineBreaks[i] : null); } const linesBreaks = lineBreaksComputer.finalize(); let values = []; 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(linesBreaks[i], !isInHiddenArea); values[i] = line.getViewLineCount(); this.lines[i] = line; } this._validModelVersionId = this.model.getVersionId(); this.prefixSumComputer = new LineNumberMapper(values); } getHiddenAreas() { return this.hiddenAreasIds.map((decId) => { return this.model.getDecorationRange(decId); }); } _reduceRanges(_ranges) { if (_ranges.length === 0) { return []; } let ranges = _ranges.map(r => this.model.validateRange(r)).sort(Range.compareRangesUsingStarts); let result = []; 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; } setHiddenAreas(_ranges) { 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 = []; 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; } modelPositionIsVisible(modelLineNumber, _modelColumn) { if (modelLineNumber < 1 || modelLineNumber > this.lines.length) { // invalid arguments return false; } return this.lines[modelLineNumber - 1].isVisible(); } getModelLineViewLineCount(modelLineNumber) { if (modelLineNumber < 1 || modelLineNumber > this.lines.length) { // invalid arguments return 1; } return this.lines[modelLineNumber - 1].getViewLineCount(); } setTabSize(newTabSize) { if (this.tabSize === newTabSize) { return false; } this.tabSize = newTabSize; this._constructLines(/*resetHiddenAreas*/ false, null); return true; } setWrappingSettings(fontInfo, wrappingStrategy, wrappingColumn, wrappingIndent) { const equalFontInfo = this.fontInfo.equals(fontInfo); const equalWrappingStrategy = (this.wrappingStrategy === wrappingStrategy); const equalWrappingColumn = (this.wrappingColumn === wrappingColumn); const equalWrappingIndent = (this.wrappingIndent === wrappingIndent); if (equalFontInfo && equalWrappingStrategy && equalWrappingColumn && equalWrappingIndent) { return false; } const onlyWrappingColumnChanged = (equalFontInfo && equalWrappingStrategy && !equalWrappingColumn && equalWrappingIndent); this.fontInfo = fontInfo; this.wrappingStrategy = wrappingStrategy; this.wrappingColumn = wrappingColumn; this.wrappingIndent = wrappingIndent; let previousLineBreaks = null; if (onlyWrappingColumnChanged) { previousLineBreaks = []; for (let i = 0, len = this.lines.length; i < len; i++) { previousLineBreaks[i] = this.lines[i].getLineBreakData(); } } this._constructLines(/*resetHiddenAreas*/ false, previousLineBreaks); return true; } createLineBreaksComputer() { const lineBreaksComputerFactory = (this.wrappingStrategy === 'advanced' ? this._domLineBreaksComputerFactory : this._monospaceLineBreaksComputerFactory); return lineBreaksComputerFactory.createLineBreaksComputer(this.fontInfo, this.tabSize, this.wrappingColumn, this.wrappingIndent); } onModelFlushed() { this._constructLines(/*resetHiddenAreas*/ true, null); } onModelLinesDeleted(versionId, fromLineNumber, toLineNumber) { if (!versionId || 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); } onModelLinesInserted(versionId, fromLineNumber, _toLineNumber, lineBreaks) { if (!versionId || 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; } // 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 totalOutputLineCount = 0; let insertLines = []; let insertPrefixSumValues = []; for (let i = 0, len = lineBreaks.length; i < len; i++) { let line = createSplitLine(lineBreaks[i], !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); } onModelLineChanged(versionId, lineNumber, lineBreakData) { if (versionId !== null && 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(lineBreakData, 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]; } acceptVersionId(versionId) { this._validModelVersionId = versionId; if (this.lines.length === 1 && !this.lines[0].isVisible()) { // At least one line must be visible => reset hidden areas this.setHiddenAreas([]); } } getViewLineCount() { return this.prefixSumComputer.getTotalValue(); } _toValidViewLineNumber(viewLineNumber) { if (viewLineNumber < 1) { return 1; } const viewLineCount = this.getViewLineCount(); if (viewLineNumber > viewLineCount) { return viewLineCount; } return viewLineNumber | 0; } getActiveIndentGuide(viewLineNumber, minLineNumber, maxLineNumber) { 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 }; } // #region ViewLineInfo getViewLineInfo(viewLineNumber) { viewLineNumber = this._toValidViewLineNumber(viewLineNumber); let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); let lineIndex = r.index; let remainder = r.remainder; return new ViewLineInfo(lineIndex + 1, remainder); } getMinColumnOfViewLine(viewLineInfo) { return this.lines[viewLineInfo.modelLineNumber - 1].getViewLineMinColumn(this.model, viewLineInfo.modelLineNumber, viewLineInfo.modelLineWrappedLineIdx); } getModelStartPositionOfViewLine(viewLineInfo) { const line = this.lines[viewLineInfo.modelLineNumber - 1]; const minViewColumn = line.getViewLineMinColumn(this.model, viewLineInfo.modelLineNumber, viewLineInfo.modelLineWrappedLineIdx); const column = line.getModelColumnOfViewPosition(viewLineInfo.modelLineWrappedLineIdx, minViewColumn); return new Position(viewLineInfo.modelLineNumber, column); } getModelEndPositionOfViewLine(viewLineInfo) { const line = this.lines[viewLineInfo.modelLineNumber - 1]; const maxViewColumn = line.getViewLineMaxColumn(this.model, viewLineInfo.modelLineNumber, viewLineInfo.modelLineWrappedLineIdx); const column = line.getModelColumnOfViewPosition(viewLineInfo.modelLineWrappedLineIdx, maxViewColumn); return new Position(viewLineInfo.modelLineNumber, column); } getViewLineInfosGroupedByModelRanges(viewStartLineNumber, viewEndLineNumber) { const startViewLine = this.getViewLineInfo(viewStartLineNumber); const endViewLine = this.getViewLineInfo(viewEndLineNumber); const result = new Array(); let lastVisibleModelPos = this.getModelStartPositionOfViewLine(startViewLine); let viewLines = new Array(); for (let curModelLine = startViewLine.modelLineNumber; curModelLine <= endViewLine.modelLineNumber; curModelLine++) { const line = this.lines[curModelLine - 1]; if (line.isVisible()) { let startOffset = curModelLine === startViewLine.modelLineNumber ? startViewLine.modelLineWrappedLineIdx : 0; let endOffset = curModelLine === endViewLine.modelLineNumber ? endViewLine.modelLineWrappedLineIdx + 1 : line.getViewLineCount(); for (let i = startOffset; i < endOffset; i++) { viewLines.push(new ViewLineInfo(curModelLine, i)); } } if (!line.isVisible() && lastVisibleModelPos) { const lastVisibleModelPos2 = new Position(curModelLine - 1, this.model.getLineMaxColumn(curModelLine - 1) + 1); const modelRange = Range.fromPositions(lastVisibleModelPos, lastVisibleModelPos2); result.push(new ViewLineInfoGroupedByModelRange(modelRange, viewLines)); viewLines = []; lastVisibleModelPos = null; } else if (line.isVisible() && !lastVisibleModelPos) { lastVisibleModelPos = new Position(curModelLine, 1); } } if (lastVisibleModelPos) { const modelRange = Range.fromPositions(lastVisibleModelPos, this.getModelEndPositionOfViewLine(endViewLine)); result.push(new ViewLineInfoGroupedByModelRange(modelRange, viewLines)); } return result; } // #endregion getViewLinesBracketGuides(viewStartLineNumber, viewEndLineNumber, activeViewPosition, options) { const modelActivePosition = activeViewPosition ? this.convertViewPositionToModelPosition(activeViewPosition.lineNumber, activeViewPosition.column) : null; const resultPerViewLine = []; for (const group of this.getViewLineInfosGroupedByModelRanges(viewStartLineNumber, viewEndLineNumber)) { const modelRangeStartLineNumber = group.modelRange.startLineNumber; const bracketGuidesPerModelLine = this.model.getLinesBracketGuides(modelRangeStartLineNumber, group.modelRange.endLineNumber, modelActivePosition, options); for (const viewLineInfo of group.viewLines) { if (viewLineInfo.isWrappedLineContinuation && this.getMinColumnOfViewLine(viewLineInfo) === 1) { // Don't add indent guides when the wrapped line continuation has no wrapping-indentation. resultPerViewLine.push([]); } else { let bracketGuides = bracketGuidesPerModelLine[viewLineInfo.modelLineNumber - modelRangeStartLineNumber]; // visibleColumns stay as they are (this is a bug and needs to be fixed, but it is not a regression) // model-columns must be converted to view-model columns. bracketGuides = bracketGuides.map(g => g.horizontalLine ? new IndentGuide(g.visibleColumn, g.className, new IndentGuideHorizontalLine(g.horizontalLine.top, this.convertModelPositionToViewPosition(viewLineInfo.modelLineNumber, g.horizontalLine.endColumn).column)) : g); resultPerViewLine.push(bracketGuides); } } } return resultPerViewLine; } getViewLinesIndentGuides(viewStartLineNumber, viewEndLineNumber) { // TODO: Use the same code as in `getViewLinesBracketGuides`. // Future TODO: Merge with `getViewLinesBracketGuides`. // However, this requires more refactoring of indent guides. 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 = []; let resultRepeatCount = []; let resultRepeatOption = []; const modelStartLineIndex = modelStart.lineNumber - 1; const modelEndLineIndex = modelEnd.lineNumber - 1; let reqStart = 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 = 0 /* BlockNone */; if (count > 1 && line.getViewLineMinColumn(this.model, modelLineIndex + 1, viewLineEndIndex) === 1) { // wrapped lines should block indent guides option = (viewLineStartIndex === 0 ? 1 /* BlockSubsequent */ : 2 /* 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; if (option === 2 /* BlockAll */) { blockAtIndex = 0; } else if (option === 1 /* BlockSubsequent */) { blockAtIndex = 1; } else { blockAtIndex = count; } for (let j = 0; j < count; j++) { if (j === blockAtIndex) { value = 0; } viewIndents[currIndex++] = value; } } return viewIndents; } getViewLineContent(viewLineNumber) { 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); } getViewLineLength(viewLineNumber) { 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); } getViewLineMinColumn(viewLineNumber) { 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); } getViewLineMaxColumn(viewLineNumber) { 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); } getViewLineData(viewLineNumber) { 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); } getViewLinesData(viewStartLineNumber, viewEndLineNumber, needed) { 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 = []; 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; } validateViewPosition(viewLineNumber, viewColumn, expectedModelPosition) { 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); } validateViewRange(viewRange, expectedModelRange) { const validViewStart = this.validateViewPosition(viewRange.startLineNumber, viewRange.startColumn, expectedModelRange.getStartPosition()); const validViewEnd = this.validateViewPosition(viewRange.endLineNumber, viewRange.endColumn, expectedModelRange.getEndPosition()); return new Range(validViewStart.lineNumber, validViewStart.column, validViewEnd.lineNumber, validViewEnd.column); } convertViewPositionToModelPosition(viewLineNumber, viewColumn) { 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)); } convertViewRangeToModelRange(viewRange) { const start = this.convertViewPositionToModelPosition(viewRange.startLineNumber, viewRange.startColumn); const end = this.convertViewPositionToModelPosition(viewRange.endLineNumber, viewRange.endColumn); return new Range(start.lineNumber, start.column, end.lineNumber, end.column); } convertModelPositionToViewPosition(_modelLineNumber, _modelColumn, affinity = 2 /* None */) { const validPosition = this.model.validatePosition(new Position(_modelLineNumber, _modelColumn)); const inputLineNumber = validPosition.lineNumber; const 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); } const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getAccumulatedValue(lineIndex - 1)); let r; if (lineIndexChanged) { r = this.lines[lineIndex].getViewPositionOfModelPosition(deltaLineNumber, this.model.getLineMaxColumn(lineIndex + 1), affinity); } else { r = this.lines[inputLineNumber - 1].getViewPositionOfModelPosition(deltaLineNumber, inputColumn, affinity); } // console.log('in -> out ' + inputLineNumber + ',' + inputColumn + ' ===> ' + r.lineNumber + ',' + r); return r; } /** * @param affinity The affinity in case of an empty range. Has no effect for non-empty ranges. */ convertModelRangeToViewRange(modelRange, affinity = 0 /* Left */) { if (modelRange.isEmpty()) { const start = this.convertModelPositionToViewPosition(modelRange.startLineNumber, modelRange.startColumn, affinity); return Range.fromPositions(start); } else { const start = this.convertModelPositionToViewPosition(modelRange.startLineNumber, modelRange.startColumn, 1 /* Right */); const end = this.convertModelPositionToViewPosition(modelRange.endLineNumber, modelRange.endColumn, 0 /* Left */); return new Range(start.lineNumber, start.column, end.lineNumber, end.column); } } getViewLineNumberOfModelPosition(inputLineNumber, inputColumn) { 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)); } getDecorationsInRange(range, ownerId, filterOutValidation) { 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 = []; const modelStartLineIndex = modelStart.lineNumber - 1; const modelEndLineIndex = modelEnd.lineNumber - 1; let reqStart = 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 = [], finalResultLen = 0; let prevDecId = null; for (const dec of result) { const decId = dec.id; if (prevDecId === decId) { // skip continue; } prevDecId = decId; finalResult[finalResultLen++] = dec; } return finalResult; } getInjectedTextAt(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].getInjectedTextAt(remainder, position.column); } normalizePosition(position, affinity) { 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); } getLineIndentColumn(lineNumber) { 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); } // wrapped lines have no indentation. // We deliberately don't handle the case that indentation is wrapped // to avoid two view lines reporting indentation for the very same model line. return 0; } } /** * Represents a view line. Can be used to efficiently query more information about it. */ class ViewLineInfo { constructor(modelLineNumber, modelLineWrappedLineIdx) { this.modelLineNumber = modelLineNumber; this.modelLineWrappedLineIdx = modelLineWrappedLineIdx; } get isWrappedLineContinuation() { return this.modelLineWrappedLineIdx > 0; } } /** * A list of view lines that have a contiguous span in the model. */ class ViewLineInfoGroupedByModelRange { constructor(modelRange, viewLines) { this.modelRange = modelRange; this.viewLines = viewLines; } } class VisibleIdentitySplitLine { constructor() { } isVisible() { return true; } setVisible(isVisible) { if (isVisible) { return this; } return InvisibleIdentitySplitLine.INSTANCE; } getLineBreakData() { return null; } getViewLineCount() { return 1; } getViewLineContent(model, modelLineNumber, _outputLineIndex) { return model.getLineContent(modelLineNumber); } getViewLineLength(model, modelLineNumber, _outputLineIndex) { return model.getLineLength(modelLineNumber); } getViewLineMinColumn(model, modelLineNumber, _outputLineIndex) { return model.getLineMinColumn(modelLineNumber); } getViewLineMaxColumn(model, modelLineNumber, _outputLineIndex) { return model.getLineMaxColumn(modelLineNumber); } getViewLineData(model, modelLineNumber, _outputLineIndex) { let lineTokens = model.getLineTokens(modelLineNumber); let lineContent = lineTokens.getLineContent(); return new ViewLineData(lineContent, false, 1, lineContent.length + 1, 0, lineTokens.inflate(), null); } getViewLinesData(model, modelLineNumber, _fromOuputLineIndex, _toOutputLineIndex, globalStartIndex, needed, result) { if (!needed[globalStartIndex]) { result[globalStartIndex] = null; return; } result[globalStartIndex] = this.getViewLineData(model, modelLineNumber, 0); } getModelColumnOfViewPosition(_outputLineIndex, outputColumn) { return outputColumn; } getViewPositionOfModelPosition(deltaLineNumber, inputColumn) { return new Position(deltaLineNumber, inputColumn); } getViewLineNumberOfModelPosition(deltaLineNumber, _inputColumn) { return deltaLineNumber; } normalizePosition(model, modelLineNumber, outputLineIndex, outputPosition, affinity) { return outputPosition; } getInjectedTextAt(_outputLineIndex, _outputColumn) { return null; } } VisibleIdentitySplitLine.INSTANCE = new VisibleIdentitySplitLine(); class InvisibleIdentitySplitLine { constructor() { } isVisible() { return false; } setVisible(isVisible) { if (!isVisible) { return this; } return VisibleIdentitySplitLine.INSTANCE; } getLineBreakData() { return null; } getViewLineCount() { return 0; } getViewLineContent(_model, _modelLineNumber, _outputLineIndex) { throw new Error('Not supported'); } getViewLineLength(_model, _modelLineNumber, _outputLineIndex) { throw new Error('Not supported'); } getViewLineMinColumn(_model, _modelLineNumber, _outputLineIndex) { throw new Error('Not supported'); } getViewLineMaxColumn(_model, _modelLineNumber, _outputLineIndex) { throw new Error('Not supported'); } getViewLineData(_model, _modelLineNumber, _outputLineIndex) { throw new Error('Not supported'); } getViewLinesData(_model, _modelLineNumber, _fromOuputLineIndex, _toOutputLineIndex, _globalStartIndex, _needed, _result) { throw new Error('Not supported'); } getModelColumnOfViewPosition(_outputLineIndex, _outputColumn) { throw new Error('Not supported'); } getViewPositionOfModelPosition(_deltaLineNumber, _inputColumn) { throw new Error('Not supported'); } getViewLineNumberOfModelPosition(_deltaLineNumber, _inputColumn) { throw new Error('Not supported'); } normalizePosition(model, modelLineNumber, outputLineIndex, outputPosition, affinity) { throw new Error('Not supported'); } getInjectedTextAt(_outputLineIndex, _outputColumn) { throw new Error('Not supported'); } } InvisibleIdentitySplitLine.INSTANCE = new InvisibleIdentitySplitLine(); export class SplitLine { constructor(lineBreakData, isVisible) { this._lineBreakData = lineBreakData; this._isVisible = isVisible; } isVisible() { return this._isVisible; } setVisible(isVisible) { this._isVisible = isVisible; return this; } getLineBreakData() { return this._lineBreakData; } getViewLineCount() { if (!this._isVisible) { return 0; } return this._lineBreakData.breakOffsets.length; } getInputStartOffsetOfOutputLineIndex(outputLineIndex) { return this._lineBreakData.getInputOffsetOfOutputPosition(outputLineIndex, 0); } getInputEndOffsetOfOutputLineIndex(model, modelLineNumber, outputLineIndex) { if (outputLineIndex + 1 === this._lineBreakData.breakOffsets.length) { return model.getLineMaxColumn(modelLineNumber) - 1; } return this._lineBreakData.getInputOffsetOfOutputPosition(outputLineIndex + 1, 0); } getViewLineContent(model, modelLineNumber, outputLineIndex) { if (!this._isVisible) { throw new Error('Not supported'); } // These offsets refer to model text with injected text. const startOffset = outputLineIndex > 0 ? this._lineBreakData.breakOffsets[outputLineIndex - 1] : 0; const endOffset = outputLineIndex < this._lineBreakData.breakOffsets.length ? this._lineBreakData.breakOffsets[outputLineIndex] // This case might not be possible anyway, but we clamp the value to be on the safe side. : this._lineBreakData.breakOffsets[this._lineBreakData.breakOffsets.length - 1]; let r; if (this._lineBreakData.injectionOffsets !== null) { const injectedTexts = this._lineBreakData.injectionOffsets.map((offset, idx) => new LineInjectedText(0, 0, offset + 1, this._lineBreakData.injectionOptions[idx], 0)); r = LineInjectedText.applyInjectedText(model.getLineContent(modelLineNumber), injectedTexts).substring(startOffset, endOffset); } else { r = model.getValueInRange({ start