/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; import { onUnexpectedError } from '../../../base/common/errors.js'; import { Emitter } from '../../../base/common/event.js'; import { Disposable } from '../../../base/common/lifecycle.js'; import * as strings from '../../../base/common/strings.js'; import { URI } from '../../../base/common/uri.js'; import { EDITOR_MODEL_DEFAULTS } from '../config/editorOptions.js'; import { Position } from '../core/position.js'; import { Range } from '../core/range.js'; import { Selection } from '../core/selection.js'; import * as model from '../model.js'; import { EditStack } from './editStack.js'; import { guessIndentation } from './indentationGuesser.js'; import { IntervalNode, IntervalTree, recomputeMaxEnd } from './intervalTree.js'; import { PieceTreeTextBufferBuilder } from './pieceTreeTextBuffer/pieceTreeTextBufferBuilder.js'; import { InternalModelContentChangeEvent, LineInjectedText, ModelInjectedTextChangedEvent, ModelRawContentChangedEvent, ModelRawEOLChanged, ModelRawFlush, ModelRawLineChanged, ModelRawLinesDeleted, ModelRawLinesInserted } from './textModelEvents.js'; import { SearchParams, TextModelSearch } from './textModelSearch.js'; import { TextModelTokenization } from './textModelTokens.js'; import { getWordAtText } from './wordHelper.js'; import { ILanguageConfigurationService } from '../modes/languageConfigurationRegistry.js'; import { NULL_MODE_ID } from '../modes/nullMode.js'; import { TokensStore, countEOL, TokensStore2 } from './tokensStore.js'; import { Color } from '../../../base/common/color.js'; import { IUndoRedoService } from '../../../platform/undoRedo/common/undoRedo.js'; import { PieceTreeTextBuffer } from './pieceTreeTextBuffer/pieceTreeTextBuffer.js'; import { ArrayQueue, findLast } from '../../../base/common/arrays.js'; import { BracketPairs } from './bracketPairs/bracketPairsImpl.js'; import { ColorizedBracketPairsDecorationProvider } from './bracketPairs/colorizedBracketPairsDecorationProvider.js'; import { CursorColumns } from '../controller/cursorColumns.js'; import { IModeService } from '../services/modeService.js'; function createTextBufferBuilder() { return new PieceTreeTextBufferBuilder(); } export function createTextBufferFactory(text) { const builder = createTextBufferBuilder(); builder.acceptChunk(text); return builder.finish(); } export function createTextBuffer(value, defaultEOL) { const factory = (typeof value === 'string' ? createTextBufferFactory(value) : value); return factory.create(defaultEOL); } let MODEL_ID = 0; const LIMIT_FIND_COUNT = 999; export const LONG_LINE_BOUNDARY = 10000; class TextModelSnapshot { constructor(source) { this._source = source; this._eos = false; } read() { if (this._eos) { return null; } let result = [], resultCnt = 0, resultLength = 0; do { let tmp = this._source.read(); if (tmp === null) { // end-of-stream this._eos = true; if (resultCnt === 0) { return null; } else { return result.join(''); } } if (tmp.length > 0) { result[resultCnt++] = tmp; resultLength += tmp.length; } if (resultLength >= 64 * 1024) { return result.join(''); } } while (true); } } const invalidFunc = () => { throw new Error(`Invalid change accessor`); }; let TextModel = class TextModel extends Disposable { constructor(source, creationOptions, languageId, associatedResource = null, _undoRedoService, _modeService, _languageConfigurationService) { super(); this._undoRedoService = _undoRedoService; this._modeService = _modeService; this._languageConfigurationService = _languageConfigurationService; //#region Events this._onWillDispose = this._register(new Emitter()); this.onWillDispose = this._onWillDispose.event; this._onDidChangeDecorations = this._register(new DidChangeDecorationsEmitter(affectedInjectedTextLines => this.handleBeforeFireDecorationsChangedEvent(affectedInjectedTextLines))); this.onDidChangeDecorations = this._onDidChangeDecorations.event; this._onDidChangeLanguage = this._register(new Emitter()); this.onDidChangeLanguage = this._onDidChangeLanguage.event; this._onDidChangeLanguageConfiguration = this._register(new Emitter()); this.onDidChangeLanguageConfiguration = this._onDidChangeLanguageConfiguration.event; this._onDidChangeTokens = this._register(new Emitter()); this.onDidChangeTokens = this._onDidChangeTokens.event; this._onDidChangeOptions = this._register(new Emitter()); this.onDidChangeOptions = this._onDidChangeOptions.event; this._onDidChangeAttached = this._register(new Emitter()); this.onDidChangeAttached = this._onDidChangeAttached.event; this._onDidChangeContentOrInjectedText = this._register(new Emitter()); this.onDidChangeContentOrInjectedText = this._onDidChangeContentOrInjectedText.event; this._eventEmitter = this._register(new DidChangeContentEmitter()); this._backgroundTokenizationState = 0 /* Uninitialized */; this._onBackgroundTokenizationStateChanged = this._register(new Emitter()); this.onBackgroundTokenizationStateChanged = this._onBackgroundTokenizationStateChanged.event; this._register(this._eventEmitter.fastEvent((e) => { this._onDidChangeContentOrInjectedText.fire(e.rawContentChangedEvent); })); // Generate a new unique model id MODEL_ID++; this.id = '$model' + MODEL_ID; this.isForSimpleWidget = creationOptions.isForSimpleWidget; if (typeof associatedResource === 'undefined' || associatedResource === null) { this._associatedResource = URI.parse('inmemory://model/' + MODEL_ID); } else { this._associatedResource = associatedResource; } this._attachedEditorCount = 0; const { textBuffer, disposable } = createTextBuffer(source, creationOptions.defaultEOL); this._buffer = textBuffer; this._bufferDisposable = disposable; this._options = TextModel.resolveOptions(this._buffer, creationOptions); const bufferLineCount = this._buffer.getLineCount(); const bufferTextLength = this._buffer.getValueLengthInRange(new Range(1, 1, bufferLineCount, this._buffer.getLineLength(bufferLineCount) + 1), 0 /* TextDefined */); // !!! Make a decision in the ctor and permanently respect this decision !!! // If a model is too large at construction time, it will never get tokenized, // under no circumstances. if (creationOptions.largeFileOptimizations) { this._isTooLargeForTokenization = ((bufferTextLength > TextModel.LARGE_FILE_SIZE_THRESHOLD) || (bufferLineCount > TextModel.LARGE_FILE_LINE_COUNT_THRESHOLD)); } else { this._isTooLargeForTokenization = false; } this._isTooLargeForSyncing = (bufferTextLength > TextModel.MODEL_SYNC_LIMIT); this._versionId = 1; this._alternativeVersionId = 1; this._initialUndoRedoSnapshot = null; this._isDisposed = false; this._isDisposing = false; this._languageId = languageId || NULL_MODE_ID; this._languageRegistryListener = this._languageConfigurationService.onDidChange(e => { if (e.affects(this._languageId)) { this._onDidChangeLanguageConfiguration.fire({}); } }); this._instanceId = strings.singleLetterHash(MODEL_ID); this._lastDecorationId = 0; this._decorations = Object.create(null); this._decorationsTree = new DecorationsTrees(); this._commandManager = new EditStack(this, this._undoRedoService); this._isUndoing = false; this._isRedoing = false; this._trimAutoWhitespaceLines = null; this._tokens = new TokensStore(this._modeService.languageIdCodec); this._tokens2 = new TokensStore2(this._modeService.languageIdCodec); this._tokenization = new TextModelTokenization(this, this._modeService.languageIdCodec); this._bracketPairColorizer = this._register(new BracketPairs(this, this._languageConfigurationService)); this._decorationProvider = this._register(new ColorizedBracketPairsDecorationProvider(this)); this._register(this._decorationProvider.onDidChange(() => { this._onDidChangeDecorations.beginDeferredEmit(); this._onDidChangeDecorations.fire(); this._onDidChangeDecorations.endDeferredEmit(); })); } static resolveOptions(textBuffer, options) { if (options.detectIndentation) { const guessedIndentation = guessIndentation(textBuffer, options.tabSize, options.insertSpaces); return new model.TextModelResolvedOptions({ tabSize: guessedIndentation.tabSize, indentSize: guessedIndentation.tabSize, insertSpaces: guessedIndentation.insertSpaces, trimAutoWhitespace: options.trimAutoWhitespace, defaultEOL: options.defaultEOL, bracketPairColorizationOptions: options.bracketPairColorizationOptions, }); } return new model.TextModelResolvedOptions({ tabSize: options.tabSize, indentSize: options.indentSize, insertSpaces: options.insertSpaces, trimAutoWhitespace: options.trimAutoWhitespace, defaultEOL: options.defaultEOL, bracketPairColorizationOptions: options.bracketPairColorizationOptions, }); } onDidChangeContentFast(listener) { return this._eventEmitter.fastEvent((e) => listener(e.contentChangedEvent)); } onDidChangeContent(listener) { return this._eventEmitter.slowEvent((e) => listener(e.contentChangedEvent)); } get bracketPairs() { return this._bracketPairColorizer; } get backgroundTokenizationState() { return this._backgroundTokenizationState; } handleTokenizationProgress(completed) { if (this._backgroundTokenizationState === 2 /* Completed */) { // We already did a full tokenization and don't go back to progressing. return; } const newState = completed ? 2 /* Completed */ : 1 /* InProgress */; if (this._backgroundTokenizationState !== newState) { this._backgroundTokenizationState = newState; this._onBackgroundTokenizationStateChanged.fire(); } } dispose() { this._isDisposing = true; this._onWillDispose.fire(); this._languageRegistryListener.dispose(); this._tokenization.dispose(); this._isDisposed = true; super.dispose(); this._bufferDisposable.dispose(); this._isDisposing = false; // Manually release reference to previous text buffer to avoid large leaks // in case someone leaks a TextModel reference const emptyDisposedTextBuffer = new PieceTreeTextBuffer([], '', '\n', false, false, true, true); emptyDisposedTextBuffer.dispose(); this._buffer = emptyDisposedTextBuffer; } _assertNotDisposed() { if (this._isDisposed) { throw new Error('Model is disposed!'); } } _emitContentChangedEvent(rawChange, change) { this._bracketPairColorizer.handleContentChanged(change); if (this._isDisposing) { // Do not confuse listeners by emitting any event after disposing return; } this._eventEmitter.fire(new InternalModelContentChangeEvent(rawChange, change)); } setValue(value) { this._assertNotDisposed(); if (value === null) { // There's nothing to do return; } const { textBuffer, disposable } = createTextBuffer(value, this._options.defaultEOL); this._setValueFromTextBuffer(textBuffer, disposable); } _createContentChanged2(range, rangeOffset, rangeLength, text, isUndoing, isRedoing, isFlush) { return { changes: [{ range: range, rangeOffset: rangeOffset, rangeLength: rangeLength, text: text, }], eol: this._buffer.getEOL(), versionId: this.getVersionId(), isUndoing: isUndoing, isRedoing: isRedoing, isFlush: isFlush }; } _setValueFromTextBuffer(textBuffer, textBufferDisposable) { this._assertNotDisposed(); const oldFullModelRange = this.getFullModelRange(); const oldModelValueLength = this.getValueLengthInRange(oldFullModelRange); const endLineNumber = this.getLineCount(); const endColumn = this.getLineMaxColumn(endLineNumber); this._buffer = textBuffer; this._bufferDisposable.dispose(); this._bufferDisposable = textBufferDisposable; this._increaseVersionId(); // Flush all tokens this._tokens.flush(); this._tokens2.flush(); // Destroy all my decorations this._decorations = Object.create(null); this._decorationsTree = new DecorationsTrees(); // Destroy my edit history and settings this._commandManager.clear(); this._trimAutoWhitespaceLines = null; this._emitContentChangedEvent(new ModelRawContentChangedEvent([ new ModelRawFlush() ], this._versionId, false, false), this._createContentChanged2(new Range(1, 1, endLineNumber, endColumn), 0, oldModelValueLength, this.getValue(), false, false, true)); } setEOL(eol) { this._assertNotDisposed(); const newEOL = (eol === 1 /* CRLF */ ? '\r\n' : '\n'); if (this._buffer.getEOL() === newEOL) { // Nothing to do return; } const oldFullModelRange = this.getFullModelRange(); const oldModelValueLength = this.getValueLengthInRange(oldFullModelRange); const endLineNumber = this.getLineCount(); const endColumn = this.getLineMaxColumn(endLineNumber); this._onBeforeEOLChange(); this._buffer.setEOL(newEOL); this._increaseVersionId(); this._onAfterEOLChange(); this._emitContentChangedEvent(new ModelRawContentChangedEvent([ new ModelRawEOLChanged() ], this._versionId, false, false), this._createContentChanged2(new Range(1, 1, endLineNumber, endColumn), 0, oldModelValueLength, this.getValue(), false, false, false)); } _onBeforeEOLChange() { // Ensure all decorations get their `range` set. this._decorationsTree.ensureAllNodesHaveRanges(this); } _onAfterEOLChange() { // Transform back `range` to offsets const versionId = this.getVersionId(); const allDecorations = this._decorationsTree.collectNodesPostOrder(); for (let i = 0, len = allDecorations.length; i < len; i++) { const node = allDecorations[i]; const range = node.range; // the range is defined due to `_onBeforeEOLChange` const delta = node.cachedAbsoluteStart - node.start; const startOffset = this._buffer.getOffsetAt(range.startLineNumber, range.startColumn); const endOffset = this._buffer.getOffsetAt(range.endLineNumber, range.endColumn); node.cachedAbsoluteStart = startOffset; node.cachedAbsoluteEnd = endOffset; node.cachedVersionId = versionId; node.start = startOffset - delta; node.end = endOffset - delta; recomputeMaxEnd(node); } } onBeforeAttached() { this._attachedEditorCount++; if (this._attachedEditorCount === 1) { this._onDidChangeAttached.fire(undefined); } } onBeforeDetached() { this._attachedEditorCount--; if (this._attachedEditorCount === 0) { this._onDidChangeAttached.fire(undefined); } } isAttachedToEditor() { return this._attachedEditorCount > 0; } getAttachedEditorCount() { return this._attachedEditorCount; } isTooLargeForSyncing() { return this._isTooLargeForSyncing; } isTooLargeForTokenization() { return this._isTooLargeForTokenization; } isDisposed() { return this._isDisposed; } isDominatedByLongLines() { this._assertNotDisposed(); if (this.isTooLargeForTokenization()) { // Cannot word wrap huge files anyways, so it doesn't really matter return false; } let smallLineCharCount = 0; let longLineCharCount = 0; const lineCount = this._buffer.getLineCount(); for (let lineNumber = 1; lineNumber <= lineCount; lineNumber++) { const lineLength = this._buffer.getLineLength(lineNumber); if (lineLength >= LONG_LINE_BOUNDARY) { longLineCharCount += lineLength; } else { smallLineCharCount += lineLength; } } return (longLineCharCount > smallLineCharCount); } get uri() { return this._associatedResource; } //#region Options getOptions() { this._assertNotDisposed(); return this._options; } getFormattingOptions() { return { tabSize: this._options.indentSize, insertSpaces: this._options.insertSpaces }; } updateOptions(_newOpts) { this._assertNotDisposed(); let tabSize = (typeof _newOpts.tabSize !== 'undefined') ? _newOpts.tabSize : this._options.tabSize; let indentSize = (typeof _newOpts.indentSize !== 'undefined') ? _newOpts.indentSize : this._options.indentSize; let insertSpaces = (typeof _newOpts.insertSpaces !== 'undefined') ? _newOpts.insertSpaces : this._options.insertSpaces; let trimAutoWhitespace = (typeof _newOpts.trimAutoWhitespace !== 'undefined') ? _newOpts.trimAutoWhitespace : this._options.trimAutoWhitespace; let bracketPairColorizationOptions = (typeof _newOpts.bracketColorizationOptions !== 'undefined') ? _newOpts.bracketColorizationOptions : this._options.bracketPairColorizationOptions; let newOpts = new model.TextModelResolvedOptions({ tabSize: tabSize, indentSize: indentSize, insertSpaces: insertSpaces, defaultEOL: this._options.defaultEOL, trimAutoWhitespace: trimAutoWhitespace, bracketPairColorizationOptions, }); if (this._options.equals(newOpts)) { return; } let e = this._options.createChangeEvent(newOpts); this._options = newOpts; this._onDidChangeOptions.fire(e); } detectIndentation(defaultInsertSpaces, defaultTabSize) { this._assertNotDisposed(); let guessedIndentation = guessIndentation(this._buffer, defaultTabSize, defaultInsertSpaces); this.updateOptions({ insertSpaces: guessedIndentation.insertSpaces, tabSize: guessedIndentation.tabSize, indentSize: guessedIndentation.tabSize, // TODO@Alex: guess indentSize independent of tabSize }); } static _normalizeIndentationFromWhitespace(str, indentSize, insertSpaces) { let spacesCnt = 0; for (let i = 0; i < str.length; i++) { if (str.charAt(i) === '\t') { spacesCnt += indentSize; } else { spacesCnt++; } } let result = ''; if (!insertSpaces) { let tabsCnt = Math.floor(spacesCnt / indentSize); spacesCnt = spacesCnt % indentSize; for (let i = 0; i < tabsCnt; i++) { result += '\t'; } } for (let i = 0; i < spacesCnt; i++) { result += ' '; } return result; } static normalizeIndentation(str, indentSize, insertSpaces) { let firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(str); if (firstNonWhitespaceIndex === -1) { firstNonWhitespaceIndex = str.length; } return TextModel._normalizeIndentationFromWhitespace(str.substring(0, firstNonWhitespaceIndex), indentSize, insertSpaces) + str.substring(firstNonWhitespaceIndex); } normalizeIndentation(str) { this._assertNotDisposed(); return TextModel.normalizeIndentation(str, this._options.indentSize, this._options.insertSpaces); } //#endregion //#region Reading getVersionId() { this._assertNotDisposed(); return this._versionId; } mightContainRTL() { return this._buffer.mightContainRTL(); } mightContainUnusualLineTerminators() { return this._buffer.mightContainUnusualLineTerminators(); } removeUnusualLineTerminators(selections = null) { const matches = this.findMatches(strings.UNUSUAL_LINE_TERMINATORS.source, false, true, false, null, false, 1073741824 /* MAX_SAFE_SMALL_INTEGER */); this._buffer.resetMightContainUnusualLineTerminators(); this.pushEditOperations(selections, matches.map(m => ({ range: m.range, text: null })), () => null); } mightContainNonBasicASCII() { return this._buffer.mightContainNonBasicASCII(); } getAlternativeVersionId() { this._assertNotDisposed(); return this._alternativeVersionId; } getInitialUndoRedoSnapshot() { this._assertNotDisposed(); return this._initialUndoRedoSnapshot; } getOffsetAt(rawPosition) { this._assertNotDisposed(); let position = this._validatePosition(rawPosition.lineNumber, rawPosition.column, 0 /* Relaxed */); return this._buffer.getOffsetAt(position.lineNumber, position.column); } getPositionAt(rawOffset) { this._assertNotDisposed(); let offset = (Math.min(this._buffer.getLength(), Math.max(0, rawOffset))); return this._buffer.getPositionAt(offset); } _increaseVersionId() { this._versionId = this._versionId + 1; this._alternativeVersionId = this._versionId; } _overwriteVersionId(versionId) { this._versionId = versionId; } _overwriteAlternativeVersionId(newAlternativeVersionId) { this._alternativeVersionId = newAlternativeVersionId; } _overwriteInitialUndoRedoSnapshot(newInitialUndoRedoSnapshot) { this._initialUndoRedoSnapshot = newInitialUndoRedoSnapshot; } getValue(eol, preserveBOM = false) { this._assertNotDisposed(); const fullModelRange = this.getFullModelRange(); const fullModelValue = this.getValueInRange(fullModelRange, eol); if (preserveBOM) { return this._buffer.getBOM() + fullModelValue; } return fullModelValue; } createSnapshot(preserveBOM = false) { return new TextModelSnapshot(this._buffer.createSnapshot(preserveBOM)); } getValueLength(eol, preserveBOM = false) { this._assertNotDisposed(); const fullModelRange = this.getFullModelRange(); const fullModelValue = this.getValueLengthInRange(fullModelRange, eol); if (preserveBOM) { return this._buffer.getBOM().length + fullModelValue; } return fullModelValue; } getValueInRange(rawRange, eol = 0 /* TextDefined */) { this._assertNotDisposed(); return this._buffer.getValueInRange(this.validateRange(rawRange), eol); } getValueLengthInRange(rawRange, eol = 0 /* TextDefined */) { this._assertNotDisposed(); return this._buffer.getValueLengthInRange(this.validateRange(rawRange), eol); } getCharacterCountInRange(rawRange, eol = 0 /* TextDefined */) { this._assertNotDisposed(); return this._buffer.getCharacterCountInRange(this.validateRange(rawRange), eol); } getLineCount() { this._assertNotDisposed(); return this._buffer.getLineCount(); } getLineContent(lineNumber) { this._assertNotDisposed(); if (lineNumber < 1 || lineNumber > this.getLineCount()) { throw new Error('Illegal value for lineNumber'); } return this._buffer.getLineContent(lineNumber); } getLineLength(lineNumber) { this._assertNotDisposed(); if (lineNumber < 1 || lineNumber > this.getLineCount()) { throw new Error('Illegal value for lineNumber'); } return this._buffer.getLineLength(lineNumber); } getLinesContent() { this._assertNotDisposed(); return this._buffer.getLinesContent(); } getEOL() { this._assertNotDisposed(); return this._buffer.getEOL(); } getEndOfLineSequence() { this._assertNotDisposed(); return (this._buffer.getEOL() === '\n' ? 0 /* LF */ : 1 /* CRLF */); } getLineMinColumn(lineNumber) { this._assertNotDisposed(); return 1; } getLineMaxColumn(lineNumber) { this._assertNotDisposed(); if (lineNumber < 1 || lineNumber > this.getLineCount()) { throw new Error('Illegal value for lineNumber'); } return this._buffer.getLineLength(lineNumber) + 1; } getLineFirstNonWhitespaceColumn(lineNumber) { this._assertNotDisposed(); if (lineNumber < 1 || lineNumber > this.getLineCount()) { throw new Error('Illegal value for lineNumber'); } return this._buffer.getLineFirstNonWhitespaceColumn(lineNumber); } getLineLastNonWhitespaceColumn(lineNumber) { this._assertNotDisposed(); if (lineNumber < 1 || lineNumber > this.getLineCount()) { throw new Error('Illegal value for lineNumber'); } return this._buffer.getLineLastNonWhitespaceColumn(lineNumber); } /** * Validates `range` is within buffer bounds, but allows it to sit in between surrogate pairs, etc. * Will try to not allocate if possible. */ _validateRangeRelaxedNoAllocations(range) { const linesCount = this._buffer.getLineCount(); const initialStartLineNumber = range.startLineNumber; const initialStartColumn = range.startColumn; let startLineNumber = Math.floor((typeof initialStartLineNumber === 'number' && !isNaN(initialStartLineNumber)) ? initialStartLineNumber : 1); let startColumn = Math.floor((typeof initialStartColumn === 'number' && !isNaN(initialStartColumn)) ? initialStartColumn : 1); if (startLineNumber < 1) { startLineNumber = 1; startColumn = 1; } else if (startLineNumber > linesCount) { startLineNumber = linesCount; startColumn = this.getLineMaxColumn(startLineNumber); } else { if (startColumn <= 1) { startColumn = 1; } else { const maxColumn = this.getLineMaxColumn(startLineNumber); if (startColumn >= maxColumn) { startColumn = maxColumn; } } } const initialEndLineNumber = range.endLineNumber; const initialEndColumn = range.endColumn; let endLineNumber = Math.floor((typeof initialEndLineNumber === 'number' && !isNaN(initialEndLineNumber)) ? initialEndLineNumber : 1); let endColumn = Math.floor((typeof initialEndColumn === 'number' && !isNaN(initialEndColumn)) ? initialEndColumn : 1); if (endLineNumber < 1) { endLineNumber = 1; endColumn = 1; } else if (endLineNumber > linesCount) { endLineNumber = linesCount; endColumn = this.getLineMaxColumn(endLineNumber); } else { if (endColumn <= 1) { endColumn = 1; } else { const maxColumn = this.getLineMaxColumn(endLineNumber); if (endColumn >= maxColumn) { endColumn = maxColumn; } } } if (initialStartLineNumber === startLineNumber && initialStartColumn === startColumn && initialEndLineNumber === endLineNumber && initialEndColumn === endColumn && range instanceof Range && !(range instanceof Selection)) { return range; } return new Range(startLineNumber, startColumn, endLineNumber, endColumn); } _isValidPosition(lineNumber, column, validationType) { if (typeof lineNumber !== 'number' || typeof column !== 'number') { return false; } if (isNaN(lineNumber) || isNaN(column)) { return false; } if (lineNumber < 1 || column < 1) { return false; } if ((lineNumber | 0) !== lineNumber || (column | 0) !== column) { return false; } const lineCount = this._buffer.getLineCount(); if (lineNumber > lineCount) { return false; } if (column === 1) { return true; } const maxColumn = this.getLineMaxColumn(lineNumber); if (column > maxColumn) { return false; } if (validationType === 1 /* SurrogatePairs */) { // !!At this point, column > 1 const charCodeBefore = this._buffer.getLineCharCode(lineNumber, column - 2); if (strings.isHighSurrogate(charCodeBefore)) { return false; } } return true; } _validatePosition(_lineNumber, _column, validationType) { const lineNumber = Math.floor((typeof _lineNumber === 'number' && !isNaN(_lineNumber)) ? _lineNumber : 1); const column = Math.floor((typeof _column === 'number' && !isNaN(_column)) ? _column : 1); const lineCount = this._buffer.getLineCount(); if (lineNumber < 1) { return new Position(1, 1); } if (lineNumber > lineCount) { return new Position(lineCount, this.getLineMaxColumn(lineCount)); } if (column <= 1) { return new Position(lineNumber, 1); } const maxColumn = this.getLineMaxColumn(lineNumber); if (column >= maxColumn) { return new Position(lineNumber, maxColumn); } if (validationType === 1 /* SurrogatePairs */) { // If the position would end up in the middle of a high-low surrogate pair, // we move it to before the pair // !!At this point, column > 1 const charCodeBefore = this._buffer.getLineCharCode(lineNumber, column - 2); if (strings.isHighSurrogate(charCodeBefore)) { return new Position(lineNumber, column - 1); } } return new Position(lineNumber, column); } validatePosition(position) { const validationType = 1 /* SurrogatePairs */; this._assertNotDisposed(); // Avoid object allocation and cover most likely case if (position instanceof Position) { if (this._isValidPosition(position.lineNumber, position.column, validationType)) { return position; } } return this._validatePosition(position.lineNumber, position.column, validationType); } _isValidRange(range, validationType) { const startLineNumber = range.startLineNumber; const startColumn = range.startColumn; const endLineNumber = range.endLineNumber; const endColumn = range.endColumn; if (!this._isValidPosition(startLineNumber, startColumn, 0 /* Relaxed */)) { return false; } if (!this._isValidPosition(endLineNumber, endColumn, 0 /* Relaxed */)) { return false; } if (validationType === 1 /* SurrogatePairs */) { const charCodeBeforeStart = (startColumn > 1 ? this._buffer.getLineCharCode(startLineNumber, startColumn - 2) : 0); const charCodeBeforeEnd = (endColumn > 1 && endColumn <= this._buffer.getLineLength(endLineNumber) ? this._buffer.getLineCharCode(endLineNumber, endColumn - 2) : 0); const startInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeStart); const endInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeEnd); if (!startInsideSurrogatePair && !endInsideSurrogatePair) { return true; } return false; } return true; } validateRange(_range) { const validationType = 1 /* SurrogatePairs */; this._assertNotDisposed(); // Avoid object allocation and cover most likely case if ((_range instanceof Range) && !(_range instanceof Selection)) { if (this._isValidRange(_range, validationType)) { return _range; } } const start = this._validatePosition(_range.startLineNumber, _range.startColumn, 0 /* Relaxed */); const end = this._validatePosition(_range.endLineNumber, _range.endColumn, 0 /* Relaxed */); const startLineNumber = start.lineNumber; const startColumn = start.column; const endLineNumber = end.lineNumber; const endColumn = end.column; if (validationType === 1 /* SurrogatePairs */) { const charCodeBeforeStart = (startColumn > 1 ? this._buffer.getLineCharCode(startLineNumber, startColumn - 2) : 0); const charCodeBeforeEnd = (endColumn > 1 && endColumn <= this._buffer.getLineLength(endLineNumber) ? this._buffer.getLineCharCode(endLineNumber, endColumn - 2) : 0); const startInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeStart); const endInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeEnd); if (!startInsideSurrogatePair && !endInsideSurrogatePair) { return new Range(startLineNumber, startColumn, endLineNumber, endColumn); } if (startLineNumber === endLineNumber && startColumn === endColumn) { // do not expand a collapsed range, simply move it to a valid location return new Range(startLineNumber, startColumn - 1, endLineNumber, endColumn - 1); } if (startInsideSurrogatePair && endInsideSurrogatePair) { // expand range at both ends return new Range(startLineNumber, startColumn - 1, endLineNumber, endColumn + 1); } if (startInsideSurrogatePair) { // only expand range at the start return new Range(startLineNumber, startColumn - 1, endLineNumber, endColumn); } // only expand range at the end return new Range(startLineNumber, startColumn, endLineNumber, endColumn + 1); } return new Range(startLineNumber, startColumn, endLineNumber, endColumn); } modifyPosition(rawPosition, offset) { this._assertNotDisposed(); let candidate = this.getOffsetAt(rawPosition) + offset; return this.getPositionAt(Math.min(this._buffer.getLength(), Math.max(0, candidate))); } getFullModelRange() { this._assertNotDisposed(); const lineCount = this.getLineCount(); return new Range(1, 1, lineCount, this.getLineMaxColumn(lineCount)); } findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount) { return this._buffer.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount); } findMatches(searchString, rawSearchScope, isRegex, matchCase, wordSeparators, captureMatches, limitResultCount = LIMIT_FIND_COUNT) { this._assertNotDisposed(); let searchRanges = null; if (rawSearchScope !== null) { if (!Array.isArray(rawSearchScope)) { rawSearchScope = [rawSearchScope]; } if (rawSearchScope.every((searchScope) => Range.isIRange(searchScope))) { searchRanges = rawSearchScope.map((searchScope) => this.validateRange(searchScope)); } } if (searchRanges === null) { searchRanges = [this.getFullModelRange()]; } searchRanges = searchRanges.sort((d1, d2) => d1.startLineNumber - d2.startLineNumber || d1.startColumn - d2.startColumn); const uniqueSearchRanges = []; uniqueSearchRanges.push(searchRanges.reduce((prev, curr) => { if (Range.areIntersecting(prev, curr)) { return prev.plusRange(curr); } uniqueSearchRanges.push(prev); return curr; })); let matchMapper; if (!isRegex && searchString.indexOf('\n') < 0) { // not regex, not multi line const searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators); const searchData = searchParams.parseSearchRequest(); if (!searchData) { return []; } matchMapper = (searchRange) => this.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount); } else { matchMapper = (searchRange) => TextModelSearch.findMatches(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchRange, captureMatches, limitResultCount); } return uniqueSearchRanges.map(matchMapper).reduce((arr, matches) => arr.concat(matches), []); } findNextMatch(searchString, rawSearchStart, isRegex, matchCase, wordSeparators, captureMatches) { this._assertNotDisposed(); const searchStart = this.validatePosition(rawSearchStart); if (!isRegex && searchString.indexOf('\n') < 0) { const searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators); const searchData = searchParams.parseSearchRequest(); if (!searchData) { return null; } const lineCount = this.getLineCount(); let searchRange = new Range(searchStart.lineNumber, searchStart.column, lineCount, this.getLineMaxColumn(lineCount)); let ret = this.findMatchesLineByLine(searchRange, searchData, captureMatches, 1); TextModelSearch.findNextMatch(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchStart, captureMatches); if (ret.length > 0) { return ret[0]; } searchRange = new Range(1, 1, searchStart.lineNumber, this.getLineMaxColumn(searchStart.lineNumber)); ret = this.findMatchesLineByLine(searchRange, searchData, captureMatches, 1); if (ret.length > 0) { return ret[0]; } return null; } return TextModelSearch.findNextMatch(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchStart, captureMatches); } findPreviousMatch(searchString, rawSearchStart, isRegex, matchCase, wordSeparators, captureMatches) { this._assertNotDisposed(); const searchStart = this.validatePosition(rawSearchStart); return TextModelSearch.findPreviousMatch(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchStart, captureMatches); } //#endregion //#region Editing pushStackElement() { this._commandManager.pushStackElement(); } popStackElement() { this._commandManager.popStackElement(); } pushEOL(eol) { const currentEOL = (this.getEOL() === '\n' ? 0 /* LF */ : 1 /* CRLF */); if (currentEOL === eol) { return; } try { this._onDidChangeDecorations.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit(); if (this._initialUndoRedoSnapshot === null) { this._initialUndoRedoSnapshot = this._undoRedoService.createSnapshot(this.uri); } this._commandManager.pushEOL(eol); } finally { this._eventEmitter.endDeferredEmit(); this._onDidChangeDecorations.endDeferredEmit(); } } _validateEditOperation(rawOperation) { if (rawOperation instanceof model.ValidAnnotatedEditOperation) { return rawOperation; } return new model.ValidAnnotatedEditOperation(rawOperation.identifier || null, this.validateRange(rawOperation.range), rawOperation.text, rawOperation.forceMoveMarkers || false, rawOperation.isAutoWhitespaceEdit || false, rawOperation._isTracked || false); } _validateEditOperations(rawOperations) { const result = []; for (let i = 0, len = rawOperations.length; i < len; i++) { result[i] = this._validateEditOperation(rawOperations[i]); } return result; } pushEditOperations(beforeCursorState, editOperations, cursorStateComputer) { try { this._onDidChangeDecorations.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit(); return this._pushEditOperations(beforeCursorState, this._validateEditOperations(editOperations), cursorStateComputer); } finally { this._eventEmitter.endDeferredEmit(); this._onDidChangeDecorations.endDeferredEmit(); } } _pushEditOperations(beforeCursorState, editOperations, cursorStateComputer) { if (this._options.trimAutoWhitespace && this._trimAutoWhitespaceLines) { // Go through each saved line number and insert a trim whitespace edit // if it is safe to do so (no conflicts with other edits). let incomingEdits = editOperations.map((op) => { return { range: this.validateRange(op.range), text: op.text }; }); // Sometimes, auto-formatters change ranges automatically which can cause undesired auto whitespace trimming near the cursor // We'll use the following heuristic: if the edits occur near the cursor, then it's ok to trim auto whitespace let editsAreNearCursors = true; if (beforeCursorState) { for (let i = 0, len = beforeCursorState.length; i < len; i++) { let sel = beforeCursorState[i]; let foundEditNearSel = false; for (let j = 0, lenJ = incomingEdits.length; j < lenJ; j++) { let editRange = incomingEdits[j].range; let selIsAbove = editRange.startLineNumber > sel.endLineNumber; let selIsBelow = sel.startLineNumber > editRange.endLineNumber; if (!selIsAbove && !selIsBelow) { foundEditNearSel = true; break; } } if (!foundEditNearSel) { editsAreNearCursors = false; break; } } } if (editsAreNearCursors) { for (let i = 0, len = this._trimAutoWhitespaceLines.length; i < len; i++) { let trimLineNumber = this._trimAutoWhitespaceLines[i]; let maxLineColumn = this.getLineMaxColumn(trimLineNumber); let allowTrimLine = true; for (let j = 0, lenJ = incomingEdits.length; j < lenJ; j++) { let editRange = incomingEdits[j].range; let editText = incomingEdits[j].text; if (trimLineNumber < editRange.startLineNumber || trimLineNumber > editRange.endLineNumber) { // `trimLine` is completely outside this edit continue; } // At this point: // editRange.startLineNumber <= trimLine <= editRange.endLineNumber if (trimLineNumber === editRange.startLineNumber && editRange.startColumn === maxLineColumn && editRange.isEmpty() && editText && editText.length > 0 && editText.charAt(0) === '\n') { // This edit inserts a new line (and maybe other text) after `trimLine` continue; } if (trimLineNumber === editRange.startLineNumber && editRange.startColumn === 1 && editRange.isEmpty() && editText && editText.length > 0 && editText.charAt(editText.length - 1) === '\n') { // This edit inserts a new line (and maybe other text) before `trimLine` continue; } // Looks like we can't trim this line as it would interfere with an incoming edit allowTrimLine = false; break; } if (allowTrimLine) { const trimRange = new Range(trimLineNumber, 1, trimLineNumber, maxLineColumn); editOperations.push(new model.ValidAnnotatedEditOperation(null, trimRange, null, false, false, false)); } } } this._trimAutoWhitespaceLines = null; } if (this._initialUndoRedoSnapshot === null) { this._initialUndoRedoSnapshot = this._undoRedoService.createSnapshot(this.uri); } return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer); } _applyUndo(changes, eol, resultingAlternativeVersionId, resultingSelection) { const edits = changes.map((change) => { const rangeStart = this.getPositionAt(change.newPosition); const rangeEnd = this.getPositionAt(change.newEnd); return { range: new Range(rangeStart.lineNumber, rangeStart.column, rangeEnd.lineNumber, rangeEnd.column), text: change.oldText }; }); this._applyUndoRedoEdits(edits, eol, true, false, resultingAlternativeVersionId, resultingSelection); } _applyRedo(changes, eol, resultingAlternativeVersionId, resultingSelection) { const edits = changes.map((change) => { const rangeStart = this.getPositionAt(change.oldPosition); const rangeEnd = this.getPositionAt(change.oldEnd); return { range: new Range(rangeStart.lineNumber, rangeStart.column, rangeEnd.lineNumber, rangeEnd.column), text: change.newText }; }); this._applyUndoRedoEdits(edits, eol, false, true, resultingAlternativeVersionId, resultingSelection); } _applyUndoRedoEdits(edits, eol, isUndoing, isRedoing, resultingAlternativeVersionId, resultingSelection) { try { this._onDidChangeDecorations.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit(); this._isUndoing = isUndoing; this._isRedoing = isRedoing; this.applyEdits(edits, false); this.setEOL(eol); this._overwriteAlternativeVersionId(resultingAlternativeVersionId); } finally { this._isUndoing = false; this._isRedoing = false; this._eventEmitter.endDeferredEmit(resultingSelection); this._onDidChangeDecorations.endDeferredEmit(); } } applyEdits(rawOperations, computeUndoEdits = false) { try { this._onDidChangeDecorations.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit(); const operations = this._validateEditOperations(rawOperations); return this._doApplyEdits(operations, computeUndoEdits); } finally { this._eventEmitter.endDeferredEmit(); this._onDidChangeDecorations.endDeferredEmit(); } } _doApplyEdits(rawOperations, computeUndoEdits) { const oldLineCount = this._buffer.getLineCount(); const result = this._buffer.applyEdits(rawOperations, this._options.trimAutoWhitespace, computeUndoEdits); const newLineCount = this._buffer.getLineCount(); const contentChanges = result.changes; this._trimAutoWhitespaceLines = result.trimAutoWhitespaceLineNumbers; if (contentChanges.length !== 0) { // We do a first pass to update tokens and decorations // because we want to read decorations in the second pass // where we will emit content change events // and we want to read the final decorations for (let i = 0, len = contentChanges.length; i < len; i++) { const change = contentChanges[i]; const [eolCount, firstLineLength, lastLineLength] = countEOL(change.text); this._tokens.acceptEdit(change.range, eolCount, firstLineLength); this._tokens2.acceptEdit(change.range, eolCount, firstLineLength, lastLineLength, change.text.length > 0 ? change.text.charCodeAt(0) : 0 /* Null */); this._decorationsTree.acceptReplace(change.rangeOffset, change.rangeLength, change.text.length, change.forceMoveMarkers); } let rawContentChanges = []; this._increaseVersionId(); let lineCount = oldLineCount; for (let i = 0, len = contentChanges.length; i < len; i++) { const change = contentChanges[i]; const [eolCount] = countEOL(change.text); this._onDidChangeDecorations.fire(); const startLineNumber = change.range.startLineNumber; const endLineNumber = change.range.endLineNumber; const deletingLinesCnt = endLineNumber - startLineNumber; const insertingLinesCnt = eolCount; const editingLinesCnt = Math.min(deletingLinesCnt, insertingLinesCnt); const changeLineCountDelta = (insertingLinesCnt - deletingLinesCnt); const currentEditStartLineNumber = newLineCount - lineCount - changeLineCountDelta + startLineNumber; const firstEditLineNumber = currentEditStartLineNumber; const lastInsertedLineNumber = currentEditStartLineNumber + insertingLinesCnt; const decorationsWithInjectedTextInEditedRange = this._decorationsTree.getInjectedTextInInterval(this, this.getOffsetAt(new Position(firstEditLineNumber, 1)), this.getOffsetAt(new Position(lastInsertedLineNumber, this.getLineMaxColumn(lastInsertedLineNumber))), 0); const injectedTextInEditedRange = LineInjectedText.fromDecorations(decorationsWithInjectedTextInEditedRange); const injectedTextInEditedRangeQueue = new ArrayQueue(injectedTextInEditedRange); for (let j = editingLinesCnt; j >= 0; j--) { const editLineNumber = startLineNumber + j; const currentEditLineNumber = currentEditStartLineNumber + j; injectedTextInEditedRangeQueue.takeFromEndWhile(r => r.lineNumber > currentEditLineNumber); const decorationsInCurrentLine = injectedTextInEditedRangeQueue.takeFromEndWhile(r => r.lineNumber === currentEditLineNumber); rawContentChanges.push(new ModelRawLineChanged(editLineNumber, this.getLineContent(currentEditLineNumber), decorationsInCurrentLine)); } if (editingLinesCnt < deletingLinesCnt) { // Must delete some lines const spliceStartLineNumber = startLineNumber + editingLinesCnt; rawContentChanges.push(new ModelRawLinesDeleted(spliceStartLineNumber + 1, endLineNumber)); } if (editingLinesCnt < insertingLinesCnt) { const injectedTextInEditedRangeQueue = new ArrayQueue(injectedTextInEditedRange); // Must insert some lines const spliceLineNumber = startLineNumber + editingLinesCnt; const cnt = insertingLinesCnt - editingLinesCnt; const fromLineNumber = newLineCount - lineCount - cnt + spliceLineNumber + 1; let injectedTexts = []; let newLines = []; for (let i = 0; i < cnt; i++) { let lineNumber = fromLineNumber + i; newLines[i] = this.getLineContent(lineNumber); injectedTextInEditedRangeQueue.takeWhile(r => r.lineNumber < lineNumber); injectedTexts[i] = injectedTextInEditedRangeQueue.takeWhile(r => r.lineNumber === lineNumber); } rawContentChanges.push(new ModelRawLinesInserted(spliceLineNumber + 1, startLineNumber + insertingLinesCnt, newLines, injectedTexts)); } lineCount += changeLineCountDelta; } this._emitContentChangedEvent(new ModelRawContentChangedEvent(rawContentChanges, this.getVersionId(), this._isUndoing, this._isRedoing), { changes: contentChanges, eol: this._buffer.getEOL(), versionId: this.getVersionId(), isUndoing: this._isUndoing, isRedoing: this._isRedoing, isFlush: false }); } return (result.reverseEdits === null ? undefined : result.reverseEdits); } undo() { return this._undoRedoService.undo(this.uri); } canUndo() { return this._undoRedoService.canUndo(this.uri); } redo() { return this._undoRedoService.redo(this.uri); } canRedo() { return this._undoRedoService.canRedo(this.uri); } //#endregion //#region Decorations handleBeforeFireDecorationsChangedEvent(affectedInjectedTextLines) { // This is called before the decoration changed event is fired. if (affectedInjectedTextLines === null || affectedInjectedTextLines.size === 0) { return; } const affectedLines = [...affectedInjectedTextLines]; const lineChangeEvents = affectedLines.map(lineNumber => new ModelRawLineChanged(lineNumber, this.getLineContent(lineNumber), this._getInjectedTextInLine(lineNumber))); this._onDidChangeContentOrInjectedText.fire(new ModelInjectedTextChangedEvent(lineChangeEvents)); } changeDecorations(callback, ownerId = 0) { this._assertNotDisposed(); try { this._onDidChangeDecorations.beginDeferredEmit(); return this._changeDecorations(ownerId, callback); } finally { this._onDidChangeDecorations.endDeferredEmit(); } } _changeDecorations(ownerId, callback) { let changeAccessor = { addDecoration: (range, options) => { return this._deltaDecorationsImpl(ownerId, [], [{ range: range, options: options }])[0]; }, changeDecoration: (id, newRange) => { this._changeDecorationImpl(id, newRange); }, changeDecorationOptions: (id, options) => { this._changeDecorationOptionsImpl(id, _normalizeOptions(options)); }, removeDecoration: (id) => { this._deltaDecorationsImpl(ownerId, [id], []); }, deltaDecorations: (oldDecorations, newDecorations) => { if (oldDecorations.length === 0 && newDecorations.length === 0) { // nothing to do return []; } return this._deltaDecorationsImpl(ownerId, oldDecorations, newDecorations); } }; let result = null; try { result = callback(changeAccessor); } catch (e) { onUnexpectedError(e); } // Invalidate change accessor changeAccessor.addDecoration = invalidFunc; changeAccessor.changeDecoration = invalidFunc; changeAccessor.changeDecorationOptions = invalidFunc; changeAccessor.removeDecoration = invalidFunc; changeAccessor.deltaDecorations = invalidFunc; return result; } deltaDecorations(oldDecorations, newDecorations, ownerId = 0) { this._assertNotDisposed(); if (!oldDecorations) { oldDecorations = []; } if (oldDecorations.length === 0 && newDecorations.length === 0) { // nothing to do return []; } try { this._onDidChangeDecorations.beginDeferredEmit(); return this._deltaDecorationsImpl(ownerId, oldDecorations, newDecorations); } finally { this._onDidChangeDecorations.endDeferredEmit(); } } _getTrackedRange(id) { return this.getDecorationRange(id); } _setTrackedRange(id, newRange, newStickiness) { const node = (id ? this._decorations[id] : null); if (!node) { if (!newRange) { // node doesn't exist, the request is to delete => nothing to do return null; } // node doesn't exist, the request is to set => add the tracked range return this._deltaDecorationsImpl(0, [], [{ range: newRange, options: TRACKED_RANGE_OPTIONS[newStickiness] }])[0]; } if (!newRange) { // node exists, the request is to delete => delete node this._decorationsTree.delete(node); delete this._decorations[node.id]; return null; } // node exists, the request is to set => change the tracked range and its options const range = this._validateRangeRelaxedNoAllocations(newRange); const startOffset = this._buffer.getOffsetAt(range.startLineNumber, range.startColumn); const endOffset = this._buffer.getOffsetAt(range.endLineNumber, range.endColumn); this._decorationsTree.delete(node); node.reset(this.getVersionId(), startOffset, endOffset, range); node.setOptions(TRACKED_RANGE_OPTIONS[newStickiness]); this._decorationsTree.insert(node); return node.id; } removeAllDecorationsWithOwnerId(ownerId) { if (this._isDisposed) { return; } const nodes = this._decorationsTree.collectNodesFromOwner(ownerId); for (let i = 0, len = nodes.length; i < len; i++) { const node = nodes[i]; this._decorationsTree.delete(node); delete this._decorations[node.id]; } } getDecorationOptions(decorationId) { const node = this._decorations[decorationId]; if (!node) { return null; } return node.options; } getDecorationRange(decorationId) { const node = this._decorations[decorationId]; if (!node) { return null; } return this._decorationsTree.getNodeRange(this, node); } getLineDecorations(lineNumber, ownerId = 0, filterOutValidation = false) { if (lineNumber < 1 || lineNumber > this.getLineCount()) { return []; } return this.getLinesDecorations(lineNumber, lineNumber, ownerId, filterOutValidation); } getLinesDecorations(_startLineNumber, _endLineNumber, ownerId = 0, filterOutValidation = false) { let lineCount = this.getLineCount(); let startLineNumber = Math.min(lineCount, Math.max(1, _startLineNumber)); let endLineNumber = Math.min(lineCount, Math.max(1, _endLineNumber)); let endColumn = this.getLineMaxColumn(endLineNumber); const range = new Range(startLineNumber, 1, endLineNumber, endColumn); const decorations = this._getDecorationsInRange(range, ownerId, filterOutValidation); decorations.push(...this._decorationProvider.getDecorationsInRange(range, ownerId, filterOutValidation)); return decorations; } getDecorationsInRange(range, ownerId = 0, filterOutValidation = false) { let validatedRange = this.validateRange(range); const decorations = this._getDecorationsInRange(validatedRange, ownerId, filterOutValidation); decorations.push(...this._decorationProvider.getDecorationsInRange(validatedRange, ownerId, filterOutValidation)); return decorations; } getOverviewRulerDecorations(ownerId = 0, filterOutValidation = false) { return this._decorationsTree.getAll(this, ownerId, filterOutValidation, true); } getInjectedTextDecorations(ownerId = 0) { return this._decorationsTree.getAllInjectedText(this, ownerId); } _getInjectedTextInLine(lineNumber) { const startOffset = this._buffer.getOffsetAt(lineNumber, 1); const endOffset = startOffset + this._buffer.getLineLength(lineNumber); const result = this._decorationsTree.getInjectedTextInInterval(this, startOffset, endOffset, 0); return LineInjectedText.fromDecorations(result).filter(t => t.lineNumber === lineNumber); } getAllDecorations(ownerId = 0, filterOutValidation = false) { let result = this._decorationsTree.getAll(this, ownerId, filterOutValidation, false); result = result.concat(this._decorationProvider.getAllDecorations(ownerId, filterOutValidation)); return result; } _getDecorationsInRange(filterRange, filterOwnerId, filterOutValidation) { const startOffset = this._buffer.getOffsetAt(filterRange.startLineNumber, filterRange.startColumn); const endOffset = this._buffer.getOffsetAt(filterRange.endLineNumber, filterRange.endColumn); return this._decorationsTree.getAllInInterval(this, startOffset, endOffset, filterOwnerId, filterOutValidation); } getRangeAt(start, end) { return this._buffer.getRangeAt(start, end - start); } _changeDecorationImpl(decorationId, _range) { const node = this._decorations[decorationId]; if (!node) { return; } if (node.options.after) { const oldRange = this.getDecorationRange(decorationId); this._onDidChangeDecorations.recordLineAffectedByInjectedText(oldRange.endLineNumber); } if (node.options.before) { const oldRange = this.getDecorationRange(decorationId); this._onDidChangeDecorations.recordLineAffectedByInjectedText(oldRange.startLineNumber); } const range = this._validateRangeRelaxedNoAllocations(_range); const startOffset = this._buffer.getOffsetAt(range.startLineNumber, range.startColumn); const endOffset = this._buffer.getOffsetAt(range.endLineNumber, range.endColumn); this._decorationsTree.delete(node); node.reset(this.getVersionId(), startOffset, endOffset, range); this._decorationsTree.insert(node); this._onDidChangeDecorations.checkAffectedAndFire(node.options); if (node.options.after) { this._onDidChangeDecorations.recordLineAffectedByInjectedText(range.endLineNumber); } if (node.options.before) { this._onDidChangeDecorations.recordLineAffectedByInjectedText(range.startLineNumber); } } _changeDecorationOptionsImpl(decorationId, options) { const node = this._decorations[decorationId]; if (!node) { return; } const nodeWasInOverviewRuler = (node.options.overviewRuler && node.options.overviewRuler.color ? true : false); const nodeIsInOverviewRuler = (options.overviewRuler && options.overviewRuler.color ? true : false); this._onDidChangeDecorations.checkAffectedAndFire(node.options); this._onDidChangeDecorations.checkAffectedAndFire(options); if (node.options.after || options.after) { const nodeRange = this._decorationsTree.getNodeRange(this, node); this._onDidChangeDecorations.recordLineAffectedByInjectedText(nodeRange.endLineNumber); } if (node.options.before || options.before) { const nodeRange = this._decorationsTree.getNodeRange(this, node); this._onDidChangeDecorations.recordLineAffectedByInjectedText(nodeRange.startLineNumber); } if (nodeWasInOverviewRuler !== nodeIsInOverviewRuler) { // Delete + Insert due to an overview ruler status change this._decorationsTree.delete(node); node.setOptions(options); this._decorationsTree.insert(node); } else { node.setOptions(options); } } _deltaDecorationsImpl(ownerId, oldDecorationsIds, newDecorations) { const versionId = this.getVersionId(); const oldDecorationsLen = oldDecorationsIds.length; let oldDecorationIndex = 0; const newDecorationsLen = newDecorations.length; let newDecorationIndex = 0; let result = new Array(newDecorationsLen); while (oldDecorationIndex < oldDecorationsLen || newDecorationIndex < newDecorationsLen) { let node = null; if (oldDecorationIndex < oldDecorationsLen) { // (1) get ourselves an old node do { node = this._decorations[oldDecorationsIds[oldDecorationIndex++]]; } while (!node && oldDecorationIndex < oldDecorationsLen); // (2) remove the node from the tree (if it exists) if (node) { if (node.options.after) { const nodeRange = this._decorationsTree.getNodeRange(this, node); this._onDidChangeDecorations.recordLineAffectedByInjectedText(nodeRange.endLineNumber); } if (node.options.before) { const nodeRange = this._decorationsTree.getNodeRange(this, node); this._onDidChangeDecorations.recordLineAffectedByInjectedText(nodeRange.startLineNumber); } this._decorationsTree.delete(node); this._onDidChangeDecorations.checkAffectedAndFire(node.options); } } if (newDecorationIndex < newDecorationsLen) { // (3) create a new node if necessary if (!node) { const internalDecorationId = (++this._lastDecorationId); const decorationId = `${this._instanceId};${internalDecorationId}`; node = new IntervalNode(decorationId, 0, 0); this._decorations[decorationId] = node; } // (4) initialize node const newDecoration = newDecorations[newDecorationIndex]; const range = this._validateRangeRelaxedNoAllocations(newDecoration.range); const options = _normalizeOptions(newDecoration.options); const startOffset = this._buffer.getOffsetAt(range.startLineNumber, range.startColumn); const endOffset = this._buffer.getOffsetAt(range.endLineNumber, range.endColumn); node.ownerId = ownerId; node.reset(versionId, startOffset, endOffset, range); node.setOptions(options); if (node.options.after) { this._onDidChangeDecorations.recordLineAffectedByInjectedText(range.endLineNumber); } if (node.options.before) { this._onDidChangeDecorations.recordLineAffectedByInjectedText(range.startLineNumber); } this._onDidChangeDecorations.checkAffectedAndFire(options); this._decorationsTree.insert(node); result[newDecorationIndex] = node.id; newDecorationIndex++; } else { if (node) { delete this._decorations[node.id]; } } } return result; } setTokens(tokens, backgroundTokenizationCompleted = false) { if (tokens.length !== 0) { let ranges = []; for (let i = 0, len = tokens.length; i < len; i++) { const element = tokens[i]; let minChangedLineNumber = 0; let maxChangedLineNumber = 0; let hasChange = false; for (let j = 0, lenJ = element.tokens.length; j < lenJ; j++) { const lineNumber = element.startLineNumber + j; if (hasChange) { this._tokens.setTokens(this._languageId, lineNumber - 1, this._buffer.getLineLength(lineNumber), element.tokens[j], false); maxChangedLineNumber = lineNumber; } else { const lineHasChange = this._tokens.setTokens(this._languageId, lineNumber - 1, this._buffer.getLineLength(lineNumber), element.tokens[j], true); if (lineHasChange) { hasChange = true; minChangedLineNumber = lineNumber; maxChangedLineNumber = lineNumber; } } } if (hasChange) { ranges.push({ fromLineNumber: minChangedLineNumber, toLineNumber: maxChangedLineNumber }); } } if (ranges.length > 0) { this._emitModelTokensChangedEvent({ tokenizationSupportChanged: false, semanticTokensApplied: false, ranges: ranges }); } } this.handleTokenizationProgress(backgroundTokenizationCompleted); } setSemanticTokens(tokens, isComplete) { this._tokens2.set(tokens, isComplete); this._emitModelTokensChangedEvent({ tokenizationSupportChanged: false, semanticTokensApplied: tokens !== null, ranges: [{ fromLineNumber: 1, toLineNumber: this.getLineCount() }] }); } hasCompleteSemanticTokens() { return this._tokens2.isComplete(); } hasSomeSemanticTokens() { return !this._tokens2.isEmpty(); } setPartialSemanticTokens(range, tokens) { if (this.hasCompleteSemanticTokens()) { return; } const changedRange = this._tokens2.setPartial(range, tokens); this._emitModelTokensChangedEvent({ tokenizationSupportChanged: false, semanticTokensApplied: true, ranges: [{ fromLineNumber: changedRange.startLineNumber, toLineNumber: changedRange.endLineNumber }] }); } tokenizeViewport(startLineNumber, endLineNumber) { startLineNumber = Math.max(1, startLineNumber); endLineNumber = Math.min(this._buffer.getLineCount(), endLineNumber); this._tokenization.tokenizeViewport(startLineNumber, endLineNumber); } clearTokens() { this._tokens.flush(); this._emitModelTokensChangedEvent({ tokenizationSupportChanged: true, semanticTokensApplied: false, ranges: [{ fromLineNumber: 1, toLineNumber: this._buffer.getLineCount() }] }); } _emitModelTokensChangedEvent(e) { if (!this._isDisposing) { this._onDidChangeTokens.fire(e); } } resetTokenization() { this._tokenization.reset(); } forceTokenization(lineNumber) { if (lineNumber < 1 || lineNumber > this.getLineCount()) { throw new Error('Illegal value for lineNumber'); } this._tokenization.forceTokenization(lineNumber); } isCheapToTokenize(lineNumber) { return this._tokenization.isCheapToTokenize(lineNumber); } tokenizeIfCheap(lineNumber) { if (this.isCheapToTokenize(lineNumber)) { this.forceTokenization(lineNumber); } } getLineTokens(lineNumber) { if (lineNumber < 1 || lineNumber > this.getLineCount()) { throw new Error('Illegal value for lineNumber'); } return this._getLineTokens(lineNumber); } _getLineTokens(lineNumber) { const lineText = this.getLineContent(lineNumber); const syntacticTokens = this._tokens.getTokens(this._languageId, lineNumber - 1, lineText); return this._tokens2.addSemanticTokens(lineNumber, syntacticTokens); } getLanguageId() { return this._languageId; } setMode(languageId) { if (this._languageId === languageId) { // There's nothing to do return; } let e = { oldLanguage: this._languageId, newLanguage: languageId }; this._languageId = languageId; this._onDidChangeLanguage.fire(e); this._onDidChangeLanguageConfiguration.fire({}); } getLanguageIdAtPosition(lineNumber, column) { const position = this.validatePosition(new Position(lineNumber, column)); const lineTokens = this.getLineTokens(position.lineNumber); return lineTokens.getLanguageId(lineTokens.findTokenIndexAtOffset(position.column - 1)); } getTokenTypeIfInsertingCharacter(lineNumber, column, character) { const position = this.validatePosition(new Position(lineNumber, column)); return this._tokenization.getTokenTypeIfInsertingCharacter(position, character); } getLanguageConfiguration(languageId) { return this._languageConfigurationService.getLanguageConfiguration(languageId); } // Having tokens allows implementing additional helper methods getWordAtPosition(_position) { this._assertNotDisposed(); const position = this.validatePosition(_position); const lineContent = this.getLineContent(position.lineNumber); const lineTokens = this._getLineTokens(position.lineNumber); const tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1); // (1). First try checking right biased word const [rbStartOffset, rbEndOffset] = TextModel._findLanguageBoundaries(lineTokens, tokenIndex); const rightBiasedWord = getWordAtText(position.column, this.getLanguageConfiguration(lineTokens.getLanguageId(tokenIndex)).getWordDefinition(), lineContent.substring(rbStartOffset, rbEndOffset), rbStartOffset); // Make sure the result touches the original passed in position if (rightBiasedWord && rightBiasedWord.startColumn <= _position.column && _position.column <= rightBiasedWord.endColumn) { return rightBiasedWord; } // (2). Else, if we were at a language boundary, check the left biased word if (tokenIndex > 0 && rbStartOffset === position.column - 1) { // edge case, where `position` sits between two tokens belonging to two different languages const [lbStartOffset, lbEndOffset] = TextModel._findLanguageBoundaries(lineTokens, tokenIndex - 1); const leftBiasedWord = getWordAtText(position.column, this.getLanguageConfiguration(lineTokens.getLanguageId(tokenIndex - 1)).getWordDefinition(), lineContent.substring(lbStartOffset, lbEndOffset), lbStartOffset); // Make sure the result touches the original passed in position if (leftBiasedWord && leftBiasedWord.startColumn <= _position.column && _position.column <= leftBiasedWord.endColumn) { return leftBiasedWord; } } return null; } static _findLanguageBoundaries(lineTokens, tokenIndex) { const languageId = lineTokens.getLanguageId(tokenIndex); // go left until a different language is hit let startOffset = 0; for (let i = tokenIndex; i >= 0 && lineTokens.getLanguageId(i) === languageId; i--) { startOffset = lineTokens.getStartOffset(i); } // go right until a different language is hit let endOffset = lineTokens.getLineContent().length; for (let i = tokenIndex, tokenCount = lineTokens.getCount(); i < tokenCount && lineTokens.getLanguageId(i) === languageId; i++) { endOffset = lineTokens.getEndOffset(i); } return [startOffset, endOffset]; } getWordUntilPosition(position) { const wordAtPosition = this.getWordAtPosition(position); if (!wordAtPosition) { return { word: '', startColumn: position.column, endColumn: position.column }; } return { word: wordAtPosition.word.substr(0, position.column - wordAtPosition.startColumn), startColumn: wordAtPosition.startColumn, endColumn: position.column }; } /** * Returns: * - -1 => the line consists of whitespace * - otherwise => the indent level is returned value */ static computeIndentLevel(line, tabSize) { let indent = 0; let i = 0; let len = line.length; while (i < len) { let chCode = line.charCodeAt(i); if (chCode === 32 /* Space */) { indent++; } else if (chCode === 9 /* Tab */) { indent = indent - indent % tabSize + tabSize; } else { break; } i++; } if (i === len) { return -1; // line only consists of whitespace } return indent; } _computeIndentLevel(lineIndex) { return TextModel.computeIndentLevel(this._buffer.getLineContent(lineIndex + 1), this._options.tabSize); } getActiveIndentGuide(lineNumber, minLineNumber, maxLineNumber) { this._assertNotDisposed(); const lineCount = this.getLineCount(); if (lineNumber < 1 || lineNumber > lineCount) { throw new Error('Illegal value for lineNumber'); } const foldingRules = this.getLanguageConfiguration(this._languageId).foldingRules; const offSide = Boolean(foldingRules && foldingRules.offSide); let up_aboveContentLineIndex = -2; /* -2 is a marker for not having computed it */ let up_aboveContentLineIndent = -1; let up_belowContentLineIndex = -2; /* -2 is a marker for not having computed it */ let up_belowContentLineIndent = -1; const up_resolveIndents = (lineNumber) => { if (up_aboveContentLineIndex !== -1 && (up_aboveContentLineIndex === -2 || up_aboveContentLineIndex > lineNumber - 1)) { up_aboveContentLineIndex = -1; up_aboveContentLineIndent = -1; // must find previous line with content for (let lineIndex = lineNumber - 2; lineIndex >= 0; lineIndex--) { let indent = this._computeIndentLevel(lineIndex); if (indent >= 0) { up_aboveContentLineIndex = lineIndex; up_aboveContentLineIndent = indent; break; } } } if (up_belowContentLineIndex === -2) { up_belowContentLineIndex = -1; up_belowContentLineIndent = -1; // must find next line with content for (let lineIndex = lineNumber; lineIndex < lineCount; lineIndex++) { let indent = this._computeIndentLevel(lineIndex); if (indent >= 0) { up_belowContentLineIndex = lineIndex; up_belowContentLineIndent = indent; break; } } } }; let down_aboveContentLineIndex = -2; /* -2 is a marker for not having computed it */ let down_aboveContentLineIndent = -1; let down_belowContentLineIndex = -2; /* -2 is a marker for not having computed it */ let down_belowContentLineIndent = -1; const down_resolveIndents = (lineNumber) => { if (down_aboveContentLineIndex === -2) { down_aboveContentLineIndex = -1; down_aboveContentLineIndent = -1; // must find previous line with content for (let lineIndex = lineNumber - 2; lineIndex >= 0; lineIndex--) { let indent = this._computeIndentLevel(lineIndex); if (indent >= 0) { down_aboveContentLineIndex = lineIndex; down_aboveContentLineIndent = indent; break; } } } if (down_belowContentLineIndex !== -1 && (down_belowContentLineIndex === -2 || down_belowContentLineIndex < lineNumber - 1)) { down_belowContentLineIndex = -1; down_belowContentLineIndent = -1; // must find next line with content for (let lineIndex = lineNumber; lineIndex < lineCount; lineIndex++) { let indent = this._computeIndentLevel(lineIndex); if (indent >= 0) { down_belowContentLineIndex = lineIndex; down_belowContentLineIndent = indent; break; } } } }; let startLineNumber = 0; let goUp = true; let endLineNumber = 0; let goDown = true; let indent = 0; let initialIndent = 0; for (let distance = 0; goUp || goDown; distance++) { const upLineNumber = lineNumber - distance; const downLineNumber = lineNumber + distance; if (distance > 1 && (upLineNumber < 1 || upLineNumber < minLineNumber)) { goUp = false; } if (distance > 1 && (downLineNumber > lineCount || downLineNumber > maxLineNumber)) { goDown = false; } if (distance > 50000) { // stop processing goUp = false; goDown = false; } let upLineIndentLevel = -1; if (goUp) { // compute indent level going up const currentIndent = this._computeIndentLevel(upLineNumber - 1); if (currentIndent >= 0) { // This line has content (besides whitespace) // Use the line's indent up_belowContentLineIndex = upLineNumber - 1; up_belowContentLineIndent = currentIndent; upLineIndentLevel = Math.ceil(currentIndent / this._options.indentSize); } else { up_resolveIndents(upLineNumber); upLineIndentLevel = this._getIndentLevelForWhitespaceLine(offSide, up_aboveContentLineIndent, up_belowContentLineIndent); } } let downLineIndentLevel = -1; if (goDown) { // compute indent level going down const currentIndent = this._computeIndentLevel(downLineNumber - 1); if (currentIndent >= 0) { // This line has content (besides whitespace) // Use the line's indent down_aboveContentLineIndex = downLineNumber - 1; down_aboveContentLineIndent = currentIndent; downLineIndentLevel = Math.ceil(currentIndent / this._options.indentSize); } else { down_resolveIndents(downLineNumber); downLineIndentLevel = this._getIndentLevelForWhitespaceLine(offSide, down_aboveContentLineIndent, down_belowContentLineIndent); } } if (distance === 0) { initialIndent = upLineIndentLevel; continue; } if (distance === 1) { if (downLineNumber <= lineCount && downLineIndentLevel >= 0 && initialIndent + 1 === downLineIndentLevel) { // This is the beginning of a scope, we have special handling here, since we want the // child scope indent to be active, not the parent scope goUp = false; startLineNumber = downLineNumber; endLineNumber = downLineNumber; indent = downLineIndentLevel; continue; } if (upLineNumber >= 1 && upLineIndentLevel >= 0 && upLineIndentLevel - 1 === initialIndent) { // This is the end of a scope, just like above goDown = false; startLineNumber = upLineNumber; endLineNumber = upLineNumber; indent = upLineIndentLevel; continue; } startLineNumber = lineNumber; endLineNumber = lineNumber; indent = initialIndent; if (indent === 0) { // No need to continue return { startLineNumber, endLineNumber, indent }; } } if (goUp) { if (upLineIndentLevel >= indent) { startLineNumber = upLineNumber; } else { goUp = false; } } if (goDown) { if (downLineIndentLevel >= indent) { endLineNumber = downLineNumber; } else { goDown = false; } } } return { startLineNumber, endLineNumber, indent }; } getLinesBracketGuides(startLineNumber, endLineNumber, activePosition, options) { var _a, _b, _c, _d, _e; const result = []; const bracketPairs = this._bracketPairColorizer.getBracketPairsInRangeWithMinIndentation(new Range(startLineNumber, 1, endLineNumber, this.getLineMaxColumn(endLineNumber))); let activeBracketPairRange = undefined; if (activePosition && bracketPairs.length > 0) { const bracketsContainingActivePosition = (startLineNumber <= activePosition.lineNumber && activePosition.lineNumber <= endLineNumber) // Does active position intersect with the view port? -> Intersect bracket pairs with activePosition ? bracketPairs.filter(bp => Range.strictContainsPosition(bp.range, activePosition)) : this._bracketPairColorizer.getBracketPairsInRange(Range.fromPositions(activePosition)); activeBracketPairRange = (_a = findLast(bracketsContainingActivePosition, /* Exclude single line bracket pairs for cases such as * ``` * function test() { * if (true) { | } * } * ``` */ (i) => i.range.startLineNumber !== i.range.endLineNumber)) === null || _a === void 0 ? void 0 : _a.range; } const queue = new ArrayQueue(bracketPairs); /** Indexed by nesting level */ const activeGuides = new Array(); const nextGuides = new Array(); const colorProvider = new BracketPairGuidesClassNames(); for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) { let guides = new Array(); if (nextGuides.length > 0) { guides = guides.concat(nextGuides); nextGuides.length = 0; } result.push(guides); // Update activeGuides for (const pair of queue.takeWhile(b => b.openingBracketRange.startLineNumber <= lineNumber) || []) { if (pair.range.startLineNumber === pair.range.endLineNumber) { // ignore single line brackets continue; } const guideVisibleColumn = Math.min(this.getVisibleColumnFromPosition(pair.openingBracketRange.getStartPosition()), this.getVisibleColumnFromPosition((_c = (_b = pair.closingBracketRange) === null || _b === void 0 ? void 0 : _b.getStartPosition()) !== null && _c !== void 0 ? _c : pair.range.getEndPosition()), pair.minVisibleColumnIndentation + 1); let renderHorizontalEndLineAtTheBottom = false; if (pair.closingBracketRange) { const firstNonWsIndex = strings.firstNonWhitespaceIndex(this.getLineContent(pair.closingBracketRange.startLineNumber)); if (firstNonWsIndex < pair.closingBracketRange.startColumn - 1) { renderHorizontalEndLineAtTheBottom = true; } } const start = pair.openingBracketRange.getStartPosition(); const end = ((_e = (_d = pair.closingBracketRange) === null || _d === void 0 ? void 0 : _d.getStartPosition()) !== null && _e !== void 0 ? _e : pair.range.getEndPosition()); if (pair.closingBracketRange === undefined) { // Don't show guides for bracket pairs that are not balanced. // See #135125. activeGuides[pair.nestingLevel] = null; } else { activeGuides[pair.nestingLevel] = { nestingLevel: pair.nestingLevel, guideVisibleColumn, start, visibleStartColumn: this.getVisibleColumnFromPosition(start), end, visibleEndColumn: this.getVisibleColumnFromPosition(end), bracketPair: pair, renderHorizontalEndLineAtTheBottom }; } } for (const line of activeGuides) { if (!line) { continue; } const isActive = activeBracketPairRange && line.bracketPair.range.equalsRange(activeBracketPairRange); const className = colorProvider.getInlineClassNameOfLevel(line.nestingLevel) + (options.highlightActive && isActive ? ' ' + colorProvider.activeClassName : ''); if ((isActive && options.horizontalGuides !== model.HorizontalGuidesState.Disabled) || (options.includeInactive && options.horizontalGuides === model.HorizontalGuidesState.Enabled)) { if (line.start.lineNumber === lineNumber) { if (line.guideVisibleColumn < line.visibleStartColumn) { guides.push(new model.IndentGuide(line.guideVisibleColumn, className, new model.IndentGuideHorizontalLine(false, line.start.column))); } } if (line.end.lineNumber === lineNumber + 1) { // The next line might have horizontal guides. // However, the next line might also have a new bracket pair with the same indentation, // so the current bracket pair might get replaced. That's why we push the guide to nextGuides one line ahead. if (line.guideVisibleColumn < line.visibleEndColumn) { nextGuides.push(new model.IndentGuide(line.guideVisibleColumn, className, new model.IndentGuideHorizontalLine(!line.renderHorizontalEndLineAtTheBottom, line.end.column))); } } } } let lastVisibleColumnCount = Number.MAX_SAFE_INTEGER; // Going backwards, so the last guide potentially replaces others for (let i = activeGuides.length - 1; i >= 0; i--) { const line = activeGuides[i]; if (!line) { continue; } const isActive = options.highlightActive && activeBracketPairRange && line.bracketPair.range.equalsRange(activeBracketPairRange); const className = colorProvider.getInlineClassNameOfLevel(line.nestingLevel) + (isActive ? ' ' + colorProvider.activeClassName : ''); if (isActive || options.includeInactive) { if (line.renderHorizontalEndLineAtTheBottom && line.end.lineNumber === lineNumber + 1) { nextGuides.push(new model.IndentGuide(line.guideVisibleColumn, className, null)); } } if (line.end.lineNumber <= lineNumber || line.start.lineNumber >= lineNumber) { continue; } if (line.guideVisibleColumn >= lastVisibleColumnCount && !isActive) { // Don't render a guide on top of an existing guide, unless it is active. continue; } lastVisibleColumnCount = line.guideVisibleColumn; if (isActive || options.includeInactive) { guides.push(new model.IndentGuide(line.guideVisibleColumn, className, null)); } } guides.sort((a, b) => a.visibleColumn - b.visibleColumn); } return result; } getVisibleColumnFromPosition(position) { return CursorColumns.visibleColumnFromColumn(this.getLineContent(position.lineNumber), position.column, this._options.tabSize) + 1; } getLinesIndentGuides(startLineNumber, endLineNumber) { this._assertNotDisposed(); const lineCount = this.getLineCount(); if (startLineNumber < 1 || startLineNumber > lineCount) { throw new Error('Illegal value for startLineNumber'); } if (endLineNumber < 1 || endLineNumber > lineCount) { throw new Error('Illegal value for endLineNumber'); } const foldingRules = this.getLanguageConfiguration(this._languageId).foldingRules; const offSide = Boolean(foldingRules && foldingRules.offSide); let result = new Array(endLineNumber - startLineNumber + 1); let aboveContentLineIndex = -2; /* -2 is a marker for not having computed it */ let aboveContentLineIndent = -1; let belowContentLineIndex = -2; /* -2 is a marker for not having computed it */ let belowContentLineIndent = -1; for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) { let resultIndex = lineNumber - startLineNumber; const currentIndent = this._computeIndentLevel(lineNumber - 1); if (currentIndent >= 0) { // This line has content (besides whitespace) // Use the line's indent aboveContentLineIndex = lineNumber - 1; aboveContentLineIndent = currentIndent; result[resultIndex] = Math.ceil(currentIndent / this._options.indentSize); continue; } if (aboveContentLineIndex === -2) { aboveContentLineIndex = -1; aboveContentLineIndent = -1; // must find previous line with content for (let lineIndex = lineNumber - 2; lineIndex >= 0; lineIndex--) { let indent = this._computeIndentLevel(lineIndex); if (indent >= 0) { aboveContentLineIndex = lineIndex; aboveContentLineIndent = indent; break; } } } if (belowContentLineIndex !== -1 && (belowContentLineIndex === -2 || belowContentLineIndex < lineNumber - 1)) { belowContentLineIndex = -1; belowContentLineIndent = -1; // must find next line with content for (let lineIndex = lineNumber; lineIndex < lineCount; lineIndex++) { let indent = this._computeIndentLevel(lineIndex); if (indent >= 0) { belowContentLineIndex = lineIndex; belowContentLineIndent = indent; break; } } } result[resultIndex] = this._getIndentLevelForWhitespaceLine(offSide, aboveContentLineIndent, belowContentLineIndent); } return result; } _getIndentLevelForWhitespaceLine(offSide, aboveContentLineIndent, belowContentLineIndent) { if (aboveContentLineIndent === -1 || belowContentLineIndent === -1) { // At the top or bottom of the file return 0; } else if (aboveContentLineIndent < belowContentLineIndent) { // we are inside the region above return (1 + Math.floor(aboveContentLineIndent / this._options.indentSize)); } else if (aboveContentLineIndent === belowContentLineIndent) { // we are in between two regions return Math.ceil(belowContentLineIndent / this._options.indentSize); } else { if (offSide) { // same level as region below return Math.ceil(belowContentLineIndent / this._options.indentSize); } else { // we are inside the region that ends below return (1 + Math.floor(belowContentLineIndent / this._options.indentSize)); } } } //#endregion normalizePosition(position, affinity) { return position; } /** * Gets the column at which indentation stops at a given line. * @internal */ getLineIndentColumn(lineNumber) { // Columns start with 1. return indentOfLine(this.getLineContent(lineNumber)) + 1; } }; TextModel.MODEL_SYNC_LIMIT = 50 * 1024 * 1024; // 50 MB TextModel.LARGE_FILE_SIZE_THRESHOLD = 20 * 1024 * 1024; // 20 MB; TextModel.LARGE_FILE_LINE_COUNT_THRESHOLD = 300 * 1000; // 300K lines TextModel.DEFAULT_CREATION_OPTIONS = { isForSimpleWidget: false, tabSize: EDITOR_MODEL_DEFAULTS.tabSize, indentSize: EDITOR_MODEL_DEFAULTS.indentSize, insertSpaces: EDITOR_MODEL_DEFAULTS.insertSpaces, detectIndentation: false, defaultEOL: 1 /* LF */, trimAutoWhitespace: EDITOR_MODEL_DEFAULTS.trimAutoWhitespace, largeFileOptimizations: EDITOR_MODEL_DEFAULTS.largeFileOptimizations, bracketPairColorizationOptions: EDITOR_MODEL_DEFAULTS.bracketPairColorizationOptions, }; TextModel = __decorate([ __param(4, IUndoRedoService), __param(5, IModeService), __param(6, ILanguageConfigurationService) ], TextModel); export { TextModel }; function indentOfLine(line) { let indent = 0; for (const c of line) { if (c === ' ' || c === '\t') { indent++; } else { break; } } return indent; } export class BracketPairGuidesClassNames { constructor() { this.activeClassName = 'indent-active'; } getInlineClassNameOfLevel(level) { // To support a dynamic amount of colors up to 6 colors, // we use a number that is a lcm of all numbers from 1 to 6. return `bracket-indent-guide lvl-${level % 30}`; } } //#region Decorations function isNodeInOverviewRuler(node) { return (node.options.overviewRuler && node.options.overviewRuler.color ? true : false); } function isNodeInjectedText(node) { return !!node.options.after || !!node.options.before; } class DecorationsTrees { constructor() { this._decorationsTree0 = new IntervalTree(); this._decorationsTree1 = new IntervalTree(); this._injectedTextDecorationsTree = new IntervalTree(); } ensureAllNodesHaveRanges(host) { this.getAll(host, 0, false, false); } _ensureNodesHaveRanges(host, nodes) { for (const node of nodes) { if (node.range === null) { node.range = host.getRangeAt(node.cachedAbsoluteStart, node.cachedAbsoluteEnd); } } return nodes; } getAllInInterval(host, start, end, filterOwnerId, filterOutValidation) { const versionId = host.getVersionId(); const result = this._intervalSearch(start, end, filterOwnerId, filterOutValidation, versionId); return this._ensureNodesHaveRanges(host, result); } _intervalSearch(start, end, filterOwnerId, filterOutValidation, cachedVersionId) { const r0 = this._decorationsTree0.intervalSearch(start, end, filterOwnerId, filterOutValidation, cachedVersionId); const r1 = this._decorationsTree1.intervalSearch(start, end, filterOwnerId, filterOutValidation, cachedVersionId); const r2 = this._injectedTextDecorationsTree.intervalSearch(start, end, filterOwnerId, filterOutValidation, cachedVersionId); return r0.concat(r1).concat(r2); } getInjectedTextInInterval(host, start, end, filterOwnerId) { const versionId = host.getVersionId(); const result = this._injectedTextDecorationsTree.intervalSearch(start, end, filterOwnerId, false, versionId); return this._ensureNodesHaveRanges(host, result).filter((i) => i.options.showIfCollapsed || !i.range.isEmpty()); } getAllInjectedText(host, filterOwnerId) { const versionId = host.getVersionId(); const result = this._injectedTextDecorationsTree.search(filterOwnerId, false, versionId); return this._ensureNodesHaveRanges(host, result).filter((i) => i.options.showIfCollapsed || !i.range.isEmpty()); } getAll(host, filterOwnerId, filterOutValidation, overviewRulerOnly) { const versionId = host.getVersionId(); const result = this._search(filterOwnerId, filterOutValidation, overviewRulerOnly, versionId); return this._ensureNodesHaveRanges(host, result); } _search(filterOwnerId, filterOutValidation, overviewRulerOnly, cachedVersionId) { if (overviewRulerOnly) { return this._decorationsTree1.search(filterOwnerId, filterOutValidation, cachedVersionId); } else { const r0 = this._decorationsTree0.search(filterOwnerId, filterOutValidation, cachedVersionId); const r1 = this._decorationsTree1.search(filterOwnerId, filterOutValidation, cachedVersionId); const r2 = this._injectedTextDecorationsTree.search(filterOwnerId, filterOutValidation, cachedVersionId); return r0.concat(r1).concat(r2); } } collectNodesFromOwner(ownerId) { const r0 = this._decorationsTree0.collectNodesFromOwner(ownerId); const r1 = this._decorationsTree1.collectNodesFromOwner(ownerId); const r2 = this._injectedTextDecorationsTree.collectNodesFromOwner(ownerId); return r0.concat(r1).concat(r2); } collectNodesPostOrder() { const r0 = this._decorationsTree0.collectNodesPostOrder(); const r1 = this._decorationsTree1.collectNodesPostOrder(); const r2 = this._injectedTextDecorationsTree.collectNodesPostOrder(); return r0.concat(r1).concat(r2); } insert(node) { if (isNodeInjectedText(node)) { this._injectedTextDecorationsTree.insert(node); } else if (isNodeInOverviewRuler(node)) { this._decorationsTree1.insert(node); } else { this._decorationsTree0.insert(node); } } delete(node) { if (isNodeInjectedText(node)) { this._injectedTextDecorationsTree.delete(node); } else if (isNodeInOverviewRuler(node)) { this._decorationsTree1.delete(node); } else { this._decorationsTree0.delete(node); } } getNodeRange(host, node) { const versionId = host.getVersionId(); if (node.cachedVersionId !== versionId) { this._resolveNode(node, versionId); } if (node.range === null) { node.range = host.getRangeAt(node.cachedAbsoluteStart, node.cachedAbsoluteEnd); } return node.range; } _resolveNode(node, cachedVersionId) { if (isNodeInjectedText(node)) { this._injectedTextDecorationsTree.resolveNode(node, cachedVersionId); } else if (isNodeInOverviewRuler(node)) { this._decorationsTree1.resolveNode(node, cachedVersionId); } else { this._decorationsTree0.resolveNode(node, cachedVersionId); } } acceptReplace(offset, length, textLength, forceMoveMarkers) { this._decorationsTree0.acceptReplace(offset, length, textLength, forceMoveMarkers); this._decorationsTree1.acceptReplace(offset, length, textLength, forceMoveMarkers); this._injectedTextDecorationsTree.acceptReplace(offset, length, textLength, forceMoveMarkers); } } function cleanClassName(className) { return className.replace(/[^a-z0-9\-_]/gi, ' '); } class DecorationOptions { constructor(options) { this.color = options.color || ''; this.darkColor = options.darkColor || ''; } } export class ModelDecorationOverviewRulerOptions extends DecorationOptions { constructor(options) { super(options); this._resolvedColor = null; this.position = (typeof options.position === 'number' ? options.position : model.OverviewRulerLane.Center); } getColor(theme) { if (!this._resolvedColor) { if (theme.type !== 'light' && this.darkColor) { this._resolvedColor = this._resolveColor(this.darkColor, theme); } else { this._resolvedColor = this._resolveColor(this.color, theme); } } return this._resolvedColor; } invalidateCachedColor() { this._resolvedColor = null; } _resolveColor(color, theme) { if (typeof color === 'string') { return color; } let c = color ? theme.getColor(color.id) : null; if (!c) { return ''; } return c.toString(); } } export class ModelDecorationMinimapOptions extends DecorationOptions { constructor(options) { super(options); this.position = options.position; } getColor(theme) { if (!this._resolvedColor) { if (theme.type !== 'light' && this.darkColor) { this._resolvedColor = this._resolveColor(this.darkColor, theme); } else { this._resolvedColor = this._resolveColor(this.color, theme); } } return this._resolvedColor; } invalidateCachedColor() { this._resolvedColor = undefined; } _resolveColor(color, theme) { if (typeof color === 'string') { return Color.fromHex(color); } return theme.getColor(color.id); } } export class ModelDecorationInjectedTextOptions { constructor(options) { this.content = options.content || ''; this.inlineClassName = options.inlineClassName || null; this.inlineClassNameAffectsLetterSpacing = options.inlineClassNameAffectsLetterSpacing || false; } static from(options) { if (options instanceof ModelDecorationInjectedTextOptions) { return options; } return new ModelDecorationInjectedTextOptions(options); } } export class ModelDecorationOptions { constructor(options) { var _a; this.description = options.description; this.stickiness = options.stickiness || 0 /* AlwaysGrowsWhenTypingAtEdges */; this.zIndex = options.zIndex || 0; this.className = options.className ? cleanClassName(options.className) : null; this.hoverMessage = options.hoverMessage || null; this.glyphMarginHoverMessage = options.glyphMarginHoverMessage || null; this.isWholeLine = options.isWholeLine || false; this.showIfCollapsed = options.showIfCollapsed || false; this.collapseOnReplaceEdit = options.collapseOnReplaceEdit || false; this.overviewRuler = options.overviewRuler ? new ModelDecorationOverviewRulerOptions(options.overviewRuler) : null; this.minimap = options.minimap ? new ModelDecorationMinimapOptions(options.minimap) : null; this.glyphMarginClassName = options.glyphMarginClassName ? cleanClassName(options.glyphMarginClassName) : null; this.linesDecorationsClassName = options.linesDecorationsClassName ? cleanClassName(options.linesDecorationsClassName) : null; this.firstLineDecorationClassName = options.firstLineDecorationClassName ? cleanClassName(options.firstLineDecorationClassName) : null; this.marginClassName = options.marginClassName ? cleanClassName(options.marginClassName) : null; this.inlineClassName = options.inlineClassName ? cleanClassName(options.inlineClassName) : null; this.inlineClassNameAffectsLetterSpacing = options.inlineClassNameAffectsLetterSpacing || false; this.beforeContentClassName = options.beforeContentClassName ? cleanClassName(options.beforeContentClassName) : null; this.afterContentClassName = options.afterContentClassName ? cleanClassName(options.afterContentClassName) : null; this.after = options.after ? ModelDecorationInjectedTextOptions.from(options.after) : null; this.before = options.before ? ModelDecorationInjectedTextOptions.from(options.before) : null; this.hideInCommentTokens = (_a = options.hideInCommentTokens) !== null && _a !== void 0 ? _a : false; } static register(options) { return new ModelDecorationOptions(options); } static createDynamic(options) { return new ModelDecorationOptions(options); } } ModelDecorationOptions.EMPTY = ModelDecorationOptions.register({ description: 'empty' }); /** * The order carefully matches the values of the enum. */ const TRACKED_RANGE_OPTIONS = [ ModelDecorationOptions.register({ description: 'tracked-range-always-grows-when-typing-at-edges', stickiness: 0 /* AlwaysGrowsWhenTypingAtEdges */ }), ModelDecorationOptions.register({ description: 'tracked-range-never-grows-when-typing-at-edges', stickiness: 1 /* NeverGrowsWhenTypingAtEdges */ }), ModelDecorationOptions.register({ description: 'tracked-range-grows-only-when-typing-before', stickiness: 2 /* GrowsOnlyWhenTypingBefore */ }), ModelDecorationOptions.register({ description: 'tracked-range-grows-only-when-typing-after', stickiness: 3 /* GrowsOnlyWhenTypingAfter */ }), ]; function _normalizeOptions(options) { if (options instanceof ModelDecorationOptions) { return options; } return ModelDecorationOptions.createDynamic(options); } export class DidChangeDecorationsEmitter extends Disposable { constructor(handleBeforeFire) { super(); this.handleBeforeFire = handleBeforeFire; this._actual = this._register(new Emitter()); this.event = this._actual.event; this._affectedInjectedTextLines = null; this._deferredCnt = 0; this._shouldFire = false; this._affectsMinimap = false; this._affectsOverviewRuler = false; } beginDeferredEmit() { this._deferredCnt++; } endDeferredEmit() { var _a; this._deferredCnt--; if (this._deferredCnt === 0) { if (this._shouldFire) { this.handleBeforeFire(this._affectedInjectedTextLines); const event = { affectsMinimap: this._affectsMinimap, affectsOverviewRuler: this._affectsOverviewRuler }; this._shouldFire = false; this._affectsMinimap = false; this._affectsOverviewRuler = false; this._actual.fire(event); } (_a = this._affectedInjectedTextLines) === null || _a === void 0 ? void 0 : _a.clear(); this._affectedInjectedTextLines = null; } } recordLineAffectedByInjectedText(lineNumber) { if (!this._affectedInjectedTextLines) { this._affectedInjectedTextLines = new Set(); } this._affectedInjectedTextLines.add(lineNumber); } checkAffectedAndFire(options) { if (!this._affectsMinimap) { this._affectsMinimap = options.minimap && options.minimap.position ? true : false; } if (!this._affectsOverviewRuler) { this._affectsOverviewRuler = options.overviewRuler && options.overviewRuler.color ? true : false; } this._shouldFire = true; } fire() { this._affectsMinimap = true; this._affectsOverviewRuler = true; this._shouldFire = true; } } //#endregion export class DidChangeContentEmitter extends Disposable { constructor() { super(); /** * Both `fastEvent` and `slowEvent` work the same way and contain the same events, but first we invoke `fastEvent` and then `slowEvent`. */ this._fastEmitter = this._register(new Emitter()); this.fastEvent = this._fastEmitter.event; this._slowEmitter = this._register(new Emitter()); this.slowEvent = this._slowEmitter.event; this._deferredCnt = 0; this._deferredEvent = null; } beginDeferredEmit() { this._deferredCnt++; } endDeferredEmit(resultingSelection = null) { this._deferredCnt--; if (this._deferredCnt === 0) { if (this._deferredEvent !== null) { this._deferredEvent.rawContentChangedEvent.resultingSelection = resultingSelection; const e = this._deferredEvent; this._deferredEvent = null; this._fastEmitter.fire(e); this._slowEmitter.fire(e); } } } fire(e) { if (this._deferredCnt > 0) { if (this._deferredEvent) { this._deferredEvent = this._deferredEvent.merge(e); } else { this._deferredEvent = e; } return; } this._fastEmitter.fire(e); this._slowEmitter.fire(e); } }