UNPKG

monaco-editor

Version:
1,047 lines • 113 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); } return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); import { onUnexpectedError } from '../../../base/common/errors.js'; import { Emitter } from '../../../base/common/event.js'; import { Disposable } from '../../../base/common/lifecycle.js'; import { StopWatch } from '../../../base/common/stopwatch.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, getNodeIsInOverviewRuler, recomputeMaxEnd } from './intervalTree.js'; import { PieceTreeTextBufferBuilder } from './pieceTreeTextBuffer/pieceTreeTextBufferBuilder.js'; import { InternalModelContentChangeEvent, ModelRawContentChangedEvent, ModelRawEOLChanged, ModelRawFlush, ModelRawLineChanged, ModelRawLinesDeleted, ModelRawLinesInserted } from './textModelEvents.js'; import { SearchParams, TextModelSearch } from './textModelSearch.js'; import { ModelLinesTokens, ModelTokensChangedEventBuilder } from './textModelTokens.js'; import { getWordAtText } from './wordHelper.js'; import { TokenizationRegistry } from '../modes.js'; import { LanguageConfigurationRegistry } from '../modes/languageConfigurationRegistry.js'; import { NULL_LANGUAGE_IDENTIFIER } from '../modes/nullMode.js'; import { ignoreBracketsInToken } from '../modes/supports.js'; import { BracketsUtils } from '../modes/supports/richEditBrackets.js'; var CHEAP_TOKENIZATION_LENGTH_LIMIT = 2048; function createTextBufferBuilder() { return new PieceTreeTextBufferBuilder(); } export function createTextBufferFactory(text) { var builder = createTextBufferBuilder(); builder.acceptChunk(text); return builder.finish(); } export function createTextBuffer(value, defaultEOL) { var factory = (typeof value === 'string' ? createTextBufferFactory(value) : value); return factory.create(defaultEOL); } var MODEL_ID = 0; /** * Produces 'a'-'z', followed by 'A'-'Z'... followed by 'a'-'z', etc. */ function singleLetter(result) { var LETTERS_CNT = (90 /* Z */ - 65 /* A */ + 1); result = result % (2 * LETTERS_CNT); if (result < LETTERS_CNT) { return String.fromCharCode(97 /* a */ + result); } return String.fromCharCode(65 /* A */ + result - LETTERS_CNT); } var LIMIT_FIND_COUNT = 999; export var LONG_LINE_BOUNDARY = 10000; var invalidFunc = function () { throw new Error("Invalid change accessor"); }; var TextModel = /** @class */ (function (_super) { __extends(TextModel, _super); //#endregion function TextModel(source, creationOptions, languageIdentifier, associatedResource) { if (associatedResource === void 0) { associatedResource = null; } var _this = _super.call(this) || this; //#region Events _this._onWillDispose = _this._register(new Emitter()); _this.onWillDispose = _this._onWillDispose.event; _this._onDidChangeDecorations = _this._register(new DidChangeDecorationsEmitter()); _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._eventEmitter = _this._register(new DidChangeContentEmitter()); // 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; _this._buffer = createTextBuffer(source, creationOptions.defaultEOL); _this._options = TextModel.resolveOptions(_this._buffer, creationOptions); var bufferLineCount = _this._buffer.getLineCount(); var 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._setVersionId(1); _this._isDisposed = false; _this._isDisposing = false; _this._languageIdentifier = languageIdentifier || NULL_LANGUAGE_IDENTIFIER; _this._tokenizationListener = TokenizationRegistry.onDidChange(function (e) { if (e.changedLanguages.indexOf(_this._languageIdentifier.language) === -1) { return; } _this._resetTokenizationState(); _this.emitModelTokensChangedEvent({ ranges: [{ fromLineNumber: 1, toLineNumber: _this.getLineCount() }] }); if (_this._shouldAutoTokenize()) { _this._warmUpTokens(); } }); _this._revalidateTokensTimeout = -1; _this._languageRegistryListener = LanguageConfigurationRegistry.onDidChange(function (e) { if (e.languageIdentifier.id === _this._languageIdentifier.id) { _this._onDidChangeLanguageConfiguration.fire({}); } }); _this._resetTokenizationState(); _this._instanceId = singleLetter(MODEL_ID); _this._lastDecorationId = 0; _this._decorations = Object.create(null); _this._decorationsTree = new DecorationsTrees(); _this._commandManager = new EditStack(_this); _this._isUndoing = false; _this._isRedoing = false; _this._trimAutoWhitespaceLines = null; return _this; } TextModel.createFromString = function (text, options, languageIdentifier, uri) { if (options === void 0) { options = TextModel.DEFAULT_CREATION_OPTIONS; } if (languageIdentifier === void 0) { languageIdentifier = null; } if (uri === void 0) { uri = null; } return new TextModel(text, options, languageIdentifier, uri); }; TextModel.resolveOptions = function (textBuffer, options) { if (options.detectIndentation) { var guessedIndentation = guessIndentation(textBuffer, options.tabSize, options.insertSpaces); return new model.TextModelResolvedOptions({ tabSize: guessedIndentation.tabSize, insertSpaces: guessedIndentation.insertSpaces, trimAutoWhitespace: options.trimAutoWhitespace, defaultEOL: options.defaultEOL }); } return new model.TextModelResolvedOptions({ tabSize: options.tabSize, insertSpaces: options.insertSpaces, trimAutoWhitespace: options.trimAutoWhitespace, defaultEOL: options.defaultEOL }); }; TextModel.prototype.onDidChangeRawContentFast = function (listener) { return this._eventEmitter.fastEvent(function (e) { return listener(e.rawContentChangedEvent); }); }; TextModel.prototype.onDidChangeRawContent = function (listener) { return this._eventEmitter.slowEvent(function (e) { return listener(e.rawContentChangedEvent); }); }; TextModel.prototype.onDidChangeContent = function (listener) { return this._eventEmitter.slowEvent(function (e) { return listener(e.contentChangedEvent); }); }; TextModel.prototype.dispose = function () { this._isDisposing = true; this._onWillDispose.fire(); this._tokenizationListener.dispose(); this._languageRegistryListener.dispose(); this._clearTimers(); this._isDisposed = true; // Null out members, such that any use of a disposed model will throw exceptions sooner rather than later _super.prototype.dispose.call(this); this._isDisposing = false; }; TextModel.prototype._assertNotDisposed = function () { if (this._isDisposed) { throw new Error('Model is disposed!'); } }; TextModel.prototype._emitContentChangedEvent = function (rawChange, change) { if (this._isDisposing) { // Do not confuse listeners by emitting any event after disposing return; } this._eventEmitter.fire(new InternalModelContentChangeEvent(rawChange, change)); }; TextModel.prototype.setValue = function (value) { this._assertNotDisposed(); if (value === null) { // There's nothing to do return; } var textBuffer = createTextBuffer(value, this._options.defaultEOL); this.setValueFromTextBuffer(textBuffer); }; TextModel.prototype._createContentChanged2 = function (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 }; }; TextModel.prototype.setValueFromTextBuffer = function (textBuffer) { this._assertNotDisposed(); if (textBuffer === null) { // There's nothing to do return; } var oldFullModelRange = this.getFullModelRange(); var oldModelValueLength = this.getValueLengthInRange(oldFullModelRange); var endLineNumber = this.getLineCount(); var endColumn = this.getLineMaxColumn(endLineNumber); this._buffer = textBuffer; this._increaseVersionId(); // Cancel tokenization, clear all tokens and begin tokenizing this._resetTokenizationState(); // Destroy all my decorations this._decorations = Object.create(null); this._decorationsTree = new DecorationsTrees(); // Destroy my edit history and settings this._commandManager = new EditStack(this); 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)); }; TextModel.prototype.setEOL = function (eol) { this._assertNotDisposed(); var newEOL = (eol === 1 /* CRLF */ ? '\r\n' : '\n'); if (this._buffer.getEOL() === newEOL) { // Nothing to do return; } var oldFullModelRange = this.getFullModelRange(); var oldModelValueLength = this.getValueLengthInRange(oldFullModelRange); var endLineNumber = this.getLineCount(); var 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)); }; TextModel.prototype._onBeforeEOLChange = function () { // Ensure all decorations get their `range` set. var versionId = this.getVersionId(); var allDecorations = this._decorationsTree.search(0, false, false, versionId); this._ensureNodesHaveRanges(allDecorations); }; TextModel.prototype._onAfterEOLChange = function () { // Transform back `range` to offsets var versionId = this.getVersionId(); var allDecorations = this._decorationsTree.collectNodesPostOrder(); for (var i = 0, len = allDecorations.length; i < len; i++) { var node = allDecorations[i]; var delta = node.cachedAbsoluteStart - node.start; var startOffset = this._buffer.getOffsetAt(node.range.startLineNumber, node.range.startColumn); var endOffset = this._buffer.getOffsetAt(node.range.endLineNumber, node.range.endColumn); node.cachedAbsoluteStart = startOffset; node.cachedAbsoluteEnd = endOffset; node.cachedVersionId = versionId; node.start = startOffset - delta; node.end = endOffset - delta; recomputeMaxEnd(node); } }; TextModel.prototype._resetTokenizationState = function () { this._clearTimers(); var tokenizationSupport = (this._isTooLargeForTokenization ? null : TokenizationRegistry.get(this._languageIdentifier.language)); this._tokens = new ModelLinesTokens(this._languageIdentifier, tokenizationSupport); this._beginBackgroundTokenization(); }; TextModel.prototype._clearTimers = function () { if (this._revalidateTokensTimeout !== -1) { clearTimeout(this._revalidateTokensTimeout); this._revalidateTokensTimeout = -1; } }; TextModel.prototype.onBeforeAttached = function () { this._attachedEditorCount++; // Warm up tokens for the editor this._warmUpTokens(); }; TextModel.prototype.onBeforeDetached = function () { this._attachedEditorCount--; }; TextModel.prototype._shouldAutoTokenize = function () { return this.isAttachedToEditor(); }; TextModel.prototype.isAttachedToEditor = function () { return this._attachedEditorCount > 0; }; TextModel.prototype.getAttachedEditorCount = function () { return this._attachedEditorCount; }; TextModel.prototype.isTooLargeForSyncing = function () { return this._isTooLargeForSyncing; }; TextModel.prototype.isTooLargeForTokenization = function () { return this._isTooLargeForTokenization; }; TextModel.prototype.isDisposed = function () { return this._isDisposed; }; TextModel.prototype.isDominatedByLongLines = function () { this._assertNotDisposed(); if (this.isTooLargeForTokenization()) { // Cannot word wrap huge files anyways, so it doesn't really matter return false; } var smallLineCharCount = 0; var longLineCharCount = 0; var lineCount = this._buffer.getLineCount(); for (var lineNumber = 1; lineNumber <= lineCount; lineNumber++) { var lineLength = this._buffer.getLineLength(lineNumber); if (lineLength >= LONG_LINE_BOUNDARY) { longLineCharCount += lineLength; } else { smallLineCharCount += lineLength; } } return (longLineCharCount > smallLineCharCount); }; Object.defineProperty(TextModel.prototype, "uri", { get: function () { return this._associatedResource; }, enumerable: true, configurable: true }); //#region Options TextModel.prototype.getOptions = function () { this._assertNotDisposed(); return this._options; }; TextModel.prototype.updateOptions = function (_newOpts) { this._assertNotDisposed(); var tabSize = (typeof _newOpts.tabSize !== 'undefined') ? _newOpts.tabSize : this._options.tabSize; var insertSpaces = (typeof _newOpts.insertSpaces !== 'undefined') ? _newOpts.insertSpaces : this._options.insertSpaces; var trimAutoWhitespace = (typeof _newOpts.trimAutoWhitespace !== 'undefined') ? _newOpts.trimAutoWhitespace : this._options.trimAutoWhitespace; var newOpts = new model.TextModelResolvedOptions({ tabSize: tabSize, insertSpaces: insertSpaces, defaultEOL: this._options.defaultEOL, trimAutoWhitespace: trimAutoWhitespace }); if (this._options.equals(newOpts)) { return; } var e = this._options.createChangeEvent(newOpts); this._options = newOpts; this._onDidChangeOptions.fire(e); }; TextModel.prototype.detectIndentation = function (defaultInsertSpaces, defaultTabSize) { this._assertNotDisposed(); var guessedIndentation = guessIndentation(this._buffer, defaultTabSize, defaultInsertSpaces); this.updateOptions({ insertSpaces: guessedIndentation.insertSpaces, tabSize: guessedIndentation.tabSize }); }; TextModel._normalizeIndentationFromWhitespace = function (str, tabSize, insertSpaces) { var spacesCnt = 0; for (var i = 0; i < str.length; i++) { if (str.charAt(i) === '\t') { spacesCnt += tabSize; } else { spacesCnt++; } } var result = ''; if (!insertSpaces) { var tabsCnt = Math.floor(spacesCnt / tabSize); spacesCnt = spacesCnt % tabSize; for (var i = 0; i < tabsCnt; i++) { result += '\t'; } } for (var i = 0; i < spacesCnt; i++) { result += ' '; } return result; }; TextModel.normalizeIndentation = function (str, tabSize, insertSpaces) { var firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(str); if (firstNonWhitespaceIndex === -1) { firstNonWhitespaceIndex = str.length; } return TextModel._normalizeIndentationFromWhitespace(str.substring(0, firstNonWhitespaceIndex), tabSize, insertSpaces) + str.substring(firstNonWhitespaceIndex); }; TextModel.prototype.normalizeIndentation = function (str) { this._assertNotDisposed(); return TextModel.normalizeIndentation(str, this._options.tabSize, this._options.insertSpaces); }; TextModel.prototype.getOneIndent = function () { this._assertNotDisposed(); var tabSize = this._options.tabSize; var insertSpaces = this._options.insertSpaces; if (insertSpaces) { var result = ''; for (var i = 0; i < tabSize; i++) { result += ' '; } return result; } else { return '\t'; } }; //#endregion //#region Reading TextModel.prototype.getVersionId = function () { this._assertNotDisposed(); return this._versionId; }; TextModel.prototype.mightContainRTL = function () { return this._buffer.mightContainRTL(); }; TextModel.prototype.mightContainNonBasicASCII = function () { return this._buffer.mightContainNonBasicASCII(); }; TextModel.prototype.getAlternativeVersionId = function () { this._assertNotDisposed(); return this._alternativeVersionId; }; TextModel.prototype.getOffsetAt = function (rawPosition) { this._assertNotDisposed(); var position = this._validatePosition(rawPosition.lineNumber, rawPosition.column, false); return this._buffer.getOffsetAt(position.lineNumber, position.column); }; TextModel.prototype.getPositionAt = function (rawOffset) { this._assertNotDisposed(); var offset = (Math.min(this._buffer.getLength(), Math.max(0, rawOffset))); return this._buffer.getPositionAt(offset); }; TextModel.prototype._increaseVersionId = function () { this._setVersionId(this._versionId + 1); }; TextModel.prototype._setVersionId = function (newVersionId) { this._versionId = newVersionId; this._alternativeVersionId = this._versionId; }; TextModel.prototype._overwriteAlternativeVersionId = function (newAlternativeVersionId) { this._alternativeVersionId = newAlternativeVersionId; }; TextModel.prototype.getValue = function (eol, preserveBOM) { if (preserveBOM === void 0) { preserveBOM = false; } this._assertNotDisposed(); var fullModelRange = this.getFullModelRange(); var fullModelValue = this.getValueInRange(fullModelRange, eol); if (preserveBOM) { return this._buffer.getBOM() + fullModelValue; } return fullModelValue; }; TextModel.prototype.getValueLength = function (eol, preserveBOM) { if (preserveBOM === void 0) { preserveBOM = false; } this._assertNotDisposed(); var fullModelRange = this.getFullModelRange(); var fullModelValue = this.getValueLengthInRange(fullModelRange, eol); if (preserveBOM) { return this._buffer.getBOM().length + fullModelValue; } return fullModelValue; }; TextModel.prototype.getValueInRange = function (rawRange, eol) { if (eol === void 0) { eol = 0 /* TextDefined */; } this._assertNotDisposed(); return this._buffer.getValueInRange(this.validateRange(rawRange), eol); }; TextModel.prototype.getValueLengthInRange = function (rawRange, eol) { if (eol === void 0) { eol = 0 /* TextDefined */; } this._assertNotDisposed(); return this._buffer.getValueLengthInRange(this.validateRange(rawRange), eol); }; TextModel.prototype.getLineCount = function () { this._assertNotDisposed(); return this._buffer.getLineCount(); }; TextModel.prototype.getLineContent = function (lineNumber) { this._assertNotDisposed(); if (lineNumber < 1 || lineNumber > this.getLineCount()) { throw new Error('Illegal value for lineNumber'); } return this._buffer.getLineContent(lineNumber); }; TextModel.prototype.getLineLength = function (lineNumber) { this._assertNotDisposed(); if (lineNumber < 1 || lineNumber > this.getLineCount()) { throw new Error('Illegal value for lineNumber'); } return this._buffer.getLineLength(lineNumber); }; TextModel.prototype.getLinesContent = function () { this._assertNotDisposed(); return this._buffer.getLinesContent(); }; TextModel.prototype.getEOL = function () { this._assertNotDisposed(); return this._buffer.getEOL(); }; TextModel.prototype.getLineMinColumn = function (lineNumber) { this._assertNotDisposed(); return 1; }; TextModel.prototype.getLineMaxColumn = function (lineNumber) { this._assertNotDisposed(); if (lineNumber < 1 || lineNumber > this.getLineCount()) { throw new Error('Illegal value for lineNumber'); } return this._buffer.getLineLength(lineNumber) + 1; }; TextModel.prototype.getLineFirstNonWhitespaceColumn = function (lineNumber) { this._assertNotDisposed(); if (lineNumber < 1 || lineNumber > this.getLineCount()) { throw new Error('Illegal value for lineNumber'); } return this._buffer.getLineFirstNonWhitespaceColumn(lineNumber); }; TextModel.prototype.getLineLastNonWhitespaceColumn = function (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. */ TextModel.prototype._validateRangeRelaxedNoAllocations = function (range) { var linesCount = this._buffer.getLineCount(); var initialStartLineNumber = range.startLineNumber; var initialStartColumn = range.startColumn; var startLineNumber; var startColumn; if (initialStartLineNumber < 1) { startLineNumber = 1; startColumn = 1; } else if (initialStartLineNumber > linesCount) { startLineNumber = linesCount; startColumn = this.getLineMaxColumn(startLineNumber); } else { startLineNumber = initialStartLineNumber | 0; if (initialStartColumn <= 1) { startColumn = 1; } else { var maxColumn = this.getLineMaxColumn(startLineNumber); if (initialStartColumn >= maxColumn) { startColumn = maxColumn; } else { startColumn = initialStartColumn | 0; } } } var initialEndLineNumber = range.endLineNumber; var initialEndColumn = range.endColumn; var endLineNumber; var endColumn; if (initialEndLineNumber < 1) { endLineNumber = 1; endColumn = 1; } else if (initialEndLineNumber > linesCount) { endLineNumber = linesCount; endColumn = this.getLineMaxColumn(endLineNumber); } else { endLineNumber = initialEndLineNumber | 0; if (initialEndColumn <= 1) { endColumn = 1; } else { var maxColumn = this.getLineMaxColumn(endLineNumber); if (initialEndColumn >= maxColumn) { endColumn = maxColumn; } else { endColumn = initialEndColumn | 0; } } } if (initialStartLineNumber === startLineNumber && initialStartColumn === startColumn && initialEndLineNumber === endLineNumber && initialEndColumn === endColumn && range instanceof Range && !(range instanceof Selection)) { return range; } return new Range(startLineNumber, startColumn, endLineNumber, endColumn); }; /** * @param strict Do NOT allow a position inside a high-low surrogate pair */ TextModel.prototype._isValidPosition = function (lineNumber, column, strict) { if (isNaN(lineNumber)) { return false; } if (lineNumber < 1) { return false; } var lineCount = this._buffer.getLineCount(); if (lineNumber > lineCount) { return false; } if (isNaN(column)) { return false; } if (column < 1) { return false; } var maxColumn = this.getLineMaxColumn(lineNumber); if (column > maxColumn) { return false; } if (strict) { if (column > 1) { var charCodeBefore = this._buffer.getLineCharCode(lineNumber, column - 2); if (strings.isHighSurrogate(charCodeBefore)) { return false; } } } return true; }; /** * @param strict Do NOT allow a position inside a high-low surrogate pair */ TextModel.prototype._validatePosition = function (_lineNumber, _column, strict) { var lineNumber = Math.floor((typeof _lineNumber === 'number' && !isNaN(_lineNumber)) ? _lineNumber : 1); var column = Math.floor((typeof _column === 'number' && !isNaN(_column)) ? _column : 1); var 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); } var maxColumn = this.getLineMaxColumn(lineNumber); if (column >= maxColumn) { return new Position(lineNumber, maxColumn); } if (strict) { // 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 var charCodeBefore = this._buffer.getLineCharCode(lineNumber, column - 2); if (strings.isHighSurrogate(charCodeBefore)) { return new Position(lineNumber, column - 1); } } return new Position(lineNumber, column); }; TextModel.prototype.validatePosition = function (position) { this._assertNotDisposed(); // Avoid object allocation and cover most likely case if (position instanceof Position) { if (this._isValidPosition(position.lineNumber, position.column, true)) { return position; } } return this._validatePosition(position.lineNumber, position.column, true); }; /** * @param strict Do NOT allow a range to have its boundaries inside a high-low surrogate pair */ TextModel.prototype._isValidRange = function (range, strict) { var startLineNumber = range.startLineNumber; var startColumn = range.startColumn; var endLineNumber = range.endLineNumber; var endColumn = range.endColumn; if (!this._isValidPosition(startLineNumber, startColumn, false)) { return false; } if (!this._isValidPosition(endLineNumber, endColumn, false)) { return false; } if (strict) { var charCodeBeforeStart = (startColumn > 1 ? this._buffer.getLineCharCode(startLineNumber, startColumn - 2) : 0); var charCodeBeforeEnd = (endColumn > 1 && endColumn <= this._buffer.getLineLength(endLineNumber) ? this._buffer.getLineCharCode(endLineNumber, endColumn - 2) : 0); var startInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeStart); var endInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeEnd); if (!startInsideSurrogatePair && !endInsideSurrogatePair) { return true; } return false; } return true; }; TextModel.prototype.validateRange = function (_range) { this._assertNotDisposed(); // Avoid object allocation and cover most likely case if ((_range instanceof Range) && !(_range instanceof Selection)) { if (this._isValidRange(_range, true)) { return _range; } } var start = this._validatePosition(_range.startLineNumber, _range.startColumn, false); var end = this._validatePosition(_range.endLineNumber, _range.endColumn, false); var startLineNumber = start.lineNumber; var startColumn = start.column; var endLineNumber = end.lineNumber; var endColumn = end.column; var charCodeBeforeStart = (startColumn > 1 ? this._buffer.getLineCharCode(startLineNumber, startColumn - 2) : 0); var charCodeBeforeEnd = (endColumn > 1 && endColumn <= this._buffer.getLineLength(endLineNumber) ? this._buffer.getLineCharCode(endLineNumber, endColumn - 2) : 0); var startInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeStart); var 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); }; TextModel.prototype.modifyPosition = function (rawPosition, offset) { this._assertNotDisposed(); var candidate = this.getOffsetAt(rawPosition) + offset; return this.getPositionAt(Math.min(this._buffer.getLength(), Math.max(0, candidate))); }; TextModel.prototype.getFullModelRange = function () { this._assertNotDisposed(); var lineCount = this.getLineCount(); return new Range(1, 1, lineCount, this.getLineMaxColumn(lineCount)); }; TextModel.prototype.findMatchesLineByLine = function (searchRange, searchData, captureMatches, limitResultCount) { return this._buffer.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount); }; TextModel.prototype.findMatches = function (searchString, rawSearchScope, isRegex, matchCase, wordSeparators, captureMatches, limitResultCount) { if (limitResultCount === void 0) { limitResultCount = LIMIT_FIND_COUNT; } this._assertNotDisposed(); var searchRange; if (Range.isIRange(rawSearchScope)) { searchRange = this.validateRange(rawSearchScope); } else { searchRange = this.getFullModelRange(); } if (!isRegex && searchString.indexOf('\n') < 0) { // not regex, not multi line var searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators); var searchData = searchParams.parseSearchRequest(); if (!searchData) { return []; } return this.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount); } return TextModelSearch.findMatches(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchRange, captureMatches, limitResultCount); }; TextModel.prototype.findNextMatch = function (searchString, rawSearchStart, isRegex, matchCase, wordSeparators, captureMatches) { this._assertNotDisposed(); var searchStart = this.validatePosition(rawSearchStart); if (!isRegex && searchString.indexOf('\n') < 0) { var searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators); var searchData = searchParams.parseSearchRequest(); if (!searchData) { return null; } var lineCount = this.getLineCount(); var searchRange = new Range(searchStart.lineNumber, searchStart.column, lineCount, this.getLineMaxColumn(lineCount)); var 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); }; TextModel.prototype.findPreviousMatch = function (searchString, rawSearchStart, isRegex, matchCase, wordSeparators, captureMatches) { this._assertNotDisposed(); var searchStart = this.validatePosition(rawSearchStart); return TextModelSearch.findPreviousMatch(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchStart, captureMatches); }; //#endregion //#region Editing TextModel.prototype.pushStackElement = function () { this._commandManager.pushStackElement(); }; TextModel.prototype.pushEOL = function (eol) { var currentEOL = (this.getEOL() === '\n' ? 0 /* LF */ : 1 /* CRLF */); if (currentEOL === eol) { return; } try { this._onDidChangeDecorations.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit(); this._commandManager.pushEOL(eol); } finally { this._eventEmitter.endDeferredEmit(); this._onDidChangeDecorations.endDeferredEmit(); } }; TextModel.prototype.pushEditOperations = function (beforeCursorState, editOperations, cursorStateComputer) { try { this._onDidChangeDecorations.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit(); return this._pushEditOperations(beforeCursorState, editOperations, cursorStateComputer); } finally { this._eventEmitter.endDeferredEmit(); this._onDidChangeDecorations.endDeferredEmit(); } }; TextModel.prototype._pushEditOperations = function (beforeCursorState, editOperations, cursorStateComputer) { var _this = this; 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). var incomingEdits = editOperations.map(function (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 var editsAreNearCursors = true; for (var i = 0, len = beforeCursorState.length; i < len; i++) { var sel = beforeCursorState[i]; var foundEditNearSel = false; for (var j = 0, lenJ = incomingEdits.length; j < lenJ; j++) { var editRange = incomingEdits[j].range; var selIsAbove = editRange.startLineNumber > sel.endLineNumber; var selIsBelow = sel.startLineNumber > editRange.endLineNumber; if (!selIsAbove && !selIsBelow) { foundEditNearSel = true; break; } } if (!foundEditNearSel) { editsAreNearCursors = false; break; } } if (editsAreNearCursors) { for (var i = 0, len = this._trimAutoWhitespaceLines.length; i < len; i++) { var trimLineNumber = this._trimAutoWhitespaceLines[i]; var maxLineColumn = this.getLineMaxColumn(trimLineNumber); var allowTrimLine = true; for (var j = 0, lenJ = incomingEdits.length; j < lenJ; j++) { var editRange = incomingEdits[j].range; var 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) { editOperations.push({ range: new Range(trimLineNumber, 1, trimLineNumber, maxLineColumn), text: null }); } } } this._trimAutoWhitespaceLines = null; } return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer); }; TextModel.prototype.applyEdits = function (rawOperations) { try { this._onDidChangeDecorations.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit(); return this._applyEdits(rawOperations); } finally { this._eventEmitter.endDeferredEmit(); this._onDidChangeDecorations.endDeferredEmit(); } }; TextModel._eolCount = function (text) { var eolCount = 0; var firstLineLength = 0; for (var i = 0, len = text.length; i < len; i++) { var chr = text.charCodeAt(i); if (chr === 13 /* CarriageReturn */) { if (eolCount === 0) { firstLineLength = i; } eolCount++; if (i + 1 < len && text.charCodeAt(i + 1) === 10 /* LineFeed */) { // \r\n... case i++; // skip \n } else { // \r... case } } else if (chr === 10 /* LineFeed */) { if (eolCount === 0) { firstLineLength = i; } eolCount++; } } if (eolCount === 0) { firstLineLength = text.length; } return [eolCount, firstLineLength]; }; TextModel.prototype._applyEdits = function (rawOperations) { for (var i = 0, len = rawOperations.length; i < len; i++) { rawOperations[i].range = this.validateRange(rawOperations[i].range); } var oldLineCount = this._buffer.getLineCount(); var result = this._buffer.applyEdits(rawOperations, this._options.trimAutoWhitespace); var newLineCount = this._buffer.getLineCount(); var contentChanges = result.changes; this._trimAutoWhitespaceLines = result.trimAutoWhitespaceLineNumbers; if (contentChanges.length !== 0) { var rawContentChanges = []; var lineCount = oldLineCount; for (var i = 0, len = contentChanges.length; i < len; i++) { var change = contentChanges[i]; var _a = TextModel._eolCount(change.text), eolCount = _a[0], firstLineLength = _a[1]; try { this._tokens.applyEdits(change.range, eolCount, firstLineLength); } catch (err) { // emergency recovery => reset tokens this._tokens = new ModelLinesTokens(this._tokens.languageIdentifier, this._tokens.tokenizationSupport); } this._onDidChangeDecorations.fire(); this._decorationsTree.acceptReplace(change.rangeOffset, change.rangeLength, change.text.length, change.forceMoveMarkers); var startLineNumber = change.range.startLineNumber; var endLineNumber = change.range.endLineNumber; var deletingLinesCnt = endLineNumber - startLineNumber; var insertingLinesCnt = eolCount; var editingLinesCnt = Math.min(deletingLinesCnt, insertingLinesCnt); var changeLineCountDelta = (insertingLinesCnt - deletingLinesCnt); for (var j = editingLinesCnt; j >= 0; j--) { var editLineNumber = startLineNumber + j; var currentEditLineNumber = newLineCount - lineCount - changeLineCountDelta + editLineNumber; rawContentChanges.push(new ModelRawLineChanged(editLineNumber, this.getLineContent(currentEditLineNumber))); } if (editingLinesCnt < deletingLinesCnt) { // Must delete some lines var spliceStartLineNumber = startLineNumber + editingLinesCnt; rawContentChanges.push(new ModelRawLinesDeleted(spliceStartLineNumber + 1, endLineNumber)); } if (editingLinesCnt < insertingLinesCnt) { // Must insert some lines var spliceLineNumber = startLineNumber + editingLinesCnt; var cnt = insertingLinesCnt - editingLinesCnt; var fromLineNumber = newLineCount - lineCount - cnt + spliceLineNumber + 1; var newLines = []; for (var i_1 = 0; i_1 < cnt; i_1++) { var lineNumber = fromLineNumber + i_1; newLines[lineNumber - fromLineNumber] = this.getLineContent(lineNumber); } rawContentChanges.push(new ModelRawLinesInserted(spliceLineNumber + 1, startLineNumber + insertingLinesCnt, newLines)); } lineCount += changeLineCountDelta; } this._increaseVersionId(); 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 }); } if (this._tokens.hasLinesToTokenize(this._buffer)) { this._beginBackgroundTokenization(); } return result.reverseEdits; }; TextModel.prototype._undo = function () { this._isUndoing = true; var r = this._commandManager.undo(); this._