UNPKG

monaco-editor-core

Version:

A browser based code editor

1,024 lines • 91.6 kB
/*--------------------------------------------------------------------------------------------- * 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); } }; var TextModel_1; import { ArrayQueue, pushMany } from '../../../base/common/arrays.js'; import { Color } from '../../../base/common/color.js'; import { BugIndicatingError, illegalArgument, onUnexpectedError } from '../../../base/common/errors.js'; import { Emitter } from '../../../base/common/event.js'; import { Disposable, MutableDisposable, combinedDisposable } from '../../../base/common/lifecycle.js'; import * as strings from '../../../base/common/strings.js'; import { URI } from '../../../base/common/uri.js'; import { countEOL } from '../core/eolCounter.js'; import { normalizeIndentation } from '../core/indentation.js'; import { Position } from '../core/position.js'; import { Range } from '../core/range.js'; import { Selection } from '../core/selection.js'; import { EDITOR_MODEL_DEFAULTS } from '../core/textModelDefaults.js'; import { ILanguageService } from '../languages/language.js'; import { ILanguageConfigurationService } from '../languages/languageConfigurationRegistry.js'; import * as model from '../model.js'; import { BracketPairsTextModelPart } from './bracketPairsTextModelPart/bracketPairsImpl.js'; import { ColorizedBracketPairsDecorationProvider } from './bracketPairsTextModelPart/colorizedBracketPairsDecorationProvider.js'; import { EditStack } from './editStack.js'; import { GuidesTextModelPart } from './guidesTextModelPart.js'; import { guessIndentation } from './indentationGuesser.js'; import { IntervalNode, IntervalTree, recomputeMaxEnd } from './intervalTree.js'; import { PieceTreeTextBuffer } from './pieceTreeTextBuffer/pieceTreeTextBuffer.js'; import { PieceTreeTextBufferBuilder } from './pieceTreeTextBuffer/pieceTreeTextBufferBuilder.js'; import { SearchParams, TextModelSearch } from './textModelSearch.js'; import { TokenizationTextModelPart } from './tokenizationTextModelPart.js'; import { AttachedViews } from './tokens.js'; import { InternalModelContentChangeEvent, LineInjectedText, ModelInjectedTextChangedEvent, ModelRawContentChangedEvent, ModelRawEOLChanged, ModelRawFlush, ModelRawLineChanged, ModelRawLinesDeleted, ModelRawLinesInserted } from '../textModelEvents.js'; import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js'; import { IUndoRedoService } from '../../../platform/undoRedo/common/undoRedo.js'; export function createTextBufferFactory(text) { const builder = new PieceTreeTextBufferBuilder(); builder.acceptChunk(text); return builder.finish(); } export function createTextBufferFactoryFromSnapshot(snapshot) { const builder = new PieceTreeTextBufferBuilder(); let chunk; while (typeof (chunk = snapshot.read()) === 'string') { builder.acceptChunk(chunk); } return builder.finish(); } export function createTextBuffer(value, defaultEOL) { let factory; if (typeof value === 'string') { factory = createTextBufferFactory(value); } else if (model.isITextSnapshot(value)) { factory = createTextBufferFactoryFromSnapshot(value); } else { factory = value; } return factory.create(defaultEOL); } let MODEL_ID = 0; const LIMIT_FIND_COUNT = 999; const LONG_LINE_BOUNDARY = 10000; class TextModelSnapshot { constructor(source) { this._source = source; this._eos = false; } read() { if (this._eos) { return null; } const result = []; let resultCnt = 0; let resultLength = 0; do { const 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 { static { TextModel_1 = this; } static { this._MODEL_SYNC_LIMIT = 50 * 1024 * 1024; } // 50 MB, // used in tests static { this.LARGE_FILE_SIZE_THRESHOLD = 20 * 1024 * 1024; } // 20 MB; static { this.LARGE_FILE_LINE_COUNT_THRESHOLD = 300 * 1000; } // 300K lines static { this.LARGE_FILE_HEAP_OPERATION_THRESHOLD = 256 * 1024 * 1024; } // 256M characters, usually ~> 512MB memory usage static { this.DEFAULT_CREATION_OPTIONS = { isForSimpleWidget: false, tabSize: EDITOR_MODEL_DEFAULTS.tabSize, indentSize: EDITOR_MODEL_DEFAULTS.indentSize, insertSpaces: EDITOR_MODEL_DEFAULTS.insertSpaces, detectIndentation: false, defaultEOL: 1 /* model.DefaultEndOfLine.LF */, trimAutoWhitespace: EDITOR_MODEL_DEFAULTS.trimAutoWhitespace, largeFileOptimizations: EDITOR_MODEL_DEFAULTS.largeFileOptimizations, bracketPairColorizationOptions: EDITOR_MODEL_DEFAULTS.bracketPairColorizationOptions, }; } static resolveOptions(textBuffer, options) { if (options.detectIndentation) { const guessedIndentation = guessIndentation(textBuffer, options.tabSize, options.insertSpaces); return new model.TextModelResolvedOptions({ tabSize: guessedIndentation.tabSize, indentSize: 'tabSize', // TODO@Alex: guess indentSize independent of tabSize insertSpaces: guessedIndentation.insertSpaces, trimAutoWhitespace: options.trimAutoWhitespace, defaultEOL: options.defaultEOL, bracketPairColorizationOptions: options.bracketPairColorizationOptions, }); } return new model.TextModelResolvedOptions(options); } get onDidChangeLanguage() { return this._tokenizationTextModelPart.onDidChangeLanguage; } get onDidChangeLanguageConfiguration() { return this._tokenizationTextModelPart.onDidChangeLanguageConfiguration; } get onDidChangeTokens() { return this._tokenizationTextModelPart.onDidChangeTokens; } onDidChangeContent(listener) { return this._eventEmitter.slowEvent((e) => listener(e.contentChangedEvent)); } onDidChangeContentOrInjectedText(listener) { return combinedDisposable(this._eventEmitter.fastEvent(e => listener(e)), this._onDidChangeInjectedText.event(e => listener(e))); } _isDisposing() { return this.__isDisposing; } get tokenization() { return this._tokenizationTextModelPart; } get bracketPairs() { return this._bracketPairs; } get guides() { return this._guidesTextModelPart; } constructor(source, languageIdOrSelection, creationOptions, associatedResource = null, _undoRedoService, _languageService, _languageConfigurationService, instantiationService) { super(); this._undoRedoService = _undoRedoService; this._languageService = _languageService; this._languageConfigurationService = _languageConfigurationService; this.instantiationService = instantiationService; //#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._onDidChangeOptions = this._register(new Emitter()); this.onDidChangeOptions = this._onDidChangeOptions.event; this._onDidChangeAttached = this._register(new Emitter()); this.onDidChangeAttached = this._onDidChangeAttached.event; this._onDidChangeInjectedText = this._register(new Emitter()); this._eventEmitter = this._register(new DidChangeContentEmitter()); this._languageSelectionListener = this._register(new MutableDisposable()); this._deltaDecorationCallCnt = 0; this._attachedViews = new AttachedViews(); // 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_1.resolveOptions(this._buffer, creationOptions); const languageId = (typeof languageIdOrSelection === 'string' ? languageIdOrSelection : languageIdOrSelection.languageId); if (typeof languageIdOrSelection !== 'string') { this._languageSelectionListener.value = languageIdOrSelection.onDidChange(() => this._setLanguage(languageIdOrSelection.languageId)); } this._bracketPairs = this._register(new BracketPairsTextModelPart(this, this._languageConfigurationService)); this._guidesTextModelPart = this._register(new GuidesTextModelPart(this, this._languageConfigurationService)); this._decorationProvider = this._register(new ColorizedBracketPairsDecorationProvider(this)); this._tokenizationTextModelPart = this.instantiationService.createInstance(TokenizationTextModelPart, this, this._bracketPairs, languageId, this._attachedViews); const bufferLineCount = this._buffer.getLineCount(); const bufferTextLength = this._buffer.getValueLengthInRange(new Range(1, 1, bufferLineCount, this._buffer.getLineLength(bufferLineCount) + 1), 0 /* model.EndOfLinePreference.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_1.LARGE_FILE_SIZE_THRESHOLD) || (bufferLineCount > TextModel_1.LARGE_FILE_LINE_COUNT_THRESHOLD)); this._isTooLargeForHeapOperation = bufferTextLength > TextModel_1.LARGE_FILE_HEAP_OPERATION_THRESHOLD; } else { this._isTooLargeForTokenization = false; this._isTooLargeForHeapOperation = false; } this._isTooLargeForSyncing = (bufferTextLength > TextModel_1._MODEL_SYNC_LIMIT); this._versionId = 1; this._alternativeVersionId = 1; this._initialUndoRedoSnapshot = null; this._isDisposed = false; this.__isDisposing = false; 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._register(this._decorationProvider.onDidChange(() => { this._onDidChangeDecorations.beginDeferredEmit(); this._onDidChangeDecorations.fire(); this._onDidChangeDecorations.endDeferredEmit(); })); this._languageService.requestRichLanguageFeatures(languageId); this._register(this._languageConfigurationService.onDidChange(e => { this._bracketPairs.handleLanguageConfigurationServiceChange(e); this._tokenizationTextModelPart.handleLanguageConfigurationServiceChange(e); })); } dispose() { this.__isDisposing = true; this._onWillDispose.fire(); this._tokenizationTextModelPart.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; this._bufferDisposable = Disposable.None; } _assertNotDisposed() { if (this._isDisposed) { throw new BugIndicatingError('Model is disposed!'); } } _emitContentChangedEvent(rawChange, change) { if (this.__isDisposing) { // Do not confuse listeners by emitting any event after disposing return; } this._tokenizationTextModelPart.handleDidChangeContent(change); this._bracketPairs.handleDidChangeContent(change); this._eventEmitter.fire(new InternalModelContentChangeEvent(rawChange, change)); } setValue(value) { this._assertNotDisposed(); if (value === null || value === undefined) { throw illegalArgument(); } const { textBuffer, disposable } = createTextBuffer(value, this._options.defaultEOL); this._setValueFromTextBuffer(textBuffer, disposable); } _createContentChanged2(range, rangeOffset, rangeLength, text, isUndoing, isRedoing, isFlush, isEolChange) { return { changes: [{ range: range, rangeOffset: rangeOffset, rangeLength: rangeLength, text: text, }], eol: this._buffer.getEOL(), isEolChange: isEolChange, 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(); // 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, false)); } setEOL(eol) { this._assertNotDisposed(); const newEOL = (eol === 1 /* model.EndOfLineSequence.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, true)); } _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._tokenizationTextModelPart.handleDidChangeAttached(); this._onDidChangeAttached.fire(undefined); } return this._attachedViews.attachView(); } onBeforeDetached(view) { this._attachedEditorCount--; if (this._attachedEditorCount === 0) { this._tokenizationTextModelPart.handleDidChangeAttached(); this._onDidChangeAttached.fire(undefined); } this._attachedViews.detachView(view); } isAttachedToEditor() { return this._attachedEditorCount > 0; } getAttachedEditorCount() { return this._attachedEditorCount; } isTooLargeForSyncing() { return this._isTooLargeForSyncing; } isTooLargeForTokenization() { return this._isTooLargeForTokenization; } isTooLargeForHeapOperation() { return this._isTooLargeForHeapOperation; } 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(); const tabSize = (typeof _newOpts.tabSize !== 'undefined') ? _newOpts.tabSize : this._options.tabSize; const indentSize = (typeof _newOpts.indentSize !== 'undefined') ? _newOpts.indentSize : this._options.originalIndentSize; const insertSpaces = (typeof _newOpts.insertSpaces !== 'undefined') ? _newOpts.insertSpaces : this._options.insertSpaces; const trimAutoWhitespace = (typeof _newOpts.trimAutoWhitespace !== 'undefined') ? _newOpts.trimAutoWhitespace : this._options.trimAutoWhitespace; const bracketPairColorizationOptions = (typeof _newOpts.bracketColorizationOptions !== 'undefined') ? _newOpts.bracketColorizationOptions : this._options.bracketPairColorizationOptions; const newOpts = new model.TextModelResolvedOptions({ tabSize: tabSize, indentSize: indentSize, insertSpaces: insertSpaces, defaultEOL: this._options.defaultEOL, trimAutoWhitespace: trimAutoWhitespace, bracketPairColorizationOptions, }); if (this._options.equals(newOpts)) { return; } const e = this._options.createChangeEvent(newOpts); this._options = newOpts; this._bracketPairs.handleDidChangeOptions(e); this._decorationProvider.handleDidChangeOptions(e); this._onDidChangeOptions.fire(e); } detectIndentation(defaultInsertSpaces, defaultTabSize) { this._assertNotDisposed(); const guessedIndentation = guessIndentation(this._buffer, defaultTabSize, defaultInsertSpaces); this.updateOptions({ insertSpaces: guessedIndentation.insertSpaces, tabSize: guessedIndentation.tabSize, indentSize: guessedIndentation.tabSize, // TODO@Alex: guess indentSize independent of tabSize }); } normalizeIndentation(str) { this._assertNotDisposed(); return 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 /* Constants.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(); const position = this._validatePosition(rawPosition.lineNumber, rawPosition.column, 0 /* StringOffsetValidationType.Relaxed */); return this._buffer.getOffsetAt(position.lineNumber, position.column); } getPositionAt(rawOffset) { this._assertNotDisposed(); const 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(); if (this.isTooLargeForHeapOperation()) { throw new BugIndicatingError('Operation would exceed heap memory limits'); } 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 /* model.EndOfLinePreference.TextDefined */) { this._assertNotDisposed(); return this._buffer.getValueInRange(this.validateRange(rawRange), eol); } getValueLengthInRange(rawRange, eol = 0 /* model.EndOfLinePreference.TextDefined */) { this._assertNotDisposed(); return this._buffer.getValueLengthInRange(this.validateRange(rawRange), eol); } getCharacterCountInRange(rawRange, eol = 0 /* model.EndOfLinePreference.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 BugIndicatingError('Illegal value for lineNumber'); } return this._buffer.getLineContent(lineNumber); } getLineLength(lineNumber) { this._assertNotDisposed(); if (lineNumber < 1 || lineNumber > this.getLineCount()) { throw new BugIndicatingError('Illegal value for lineNumber'); } return this._buffer.getLineLength(lineNumber); } getLinesContent() { this._assertNotDisposed(); if (this.isTooLargeForHeapOperation()) { throw new BugIndicatingError('Operation would exceed heap memory limits'); } return this._buffer.getLinesContent(); } getEOL() { this._assertNotDisposed(); return this._buffer.getEOL(); } getEndOfLineSequence() { this._assertNotDisposed(); return (this._buffer.getEOL() === '\n' ? 0 /* model.EndOfLineSequence.LF */ : 1 /* model.EndOfLineSequence.CRLF */); } getLineMinColumn(lineNumber) { this._assertNotDisposed(); return 1; } getLineMaxColumn(lineNumber) { this._assertNotDisposed(); if (lineNumber < 1 || lineNumber > this.getLineCount()) { throw new BugIndicatingError('Illegal value for lineNumber'); } return this._buffer.getLineLength(lineNumber) + 1; } getLineFirstNonWhitespaceColumn(lineNumber) { this._assertNotDisposed(); if (lineNumber < 1 || lineNumber > this.getLineCount()) { throw new BugIndicatingError('Illegal value for lineNumber'); } return this._buffer.getLineFirstNonWhitespaceColumn(lineNumber); } getLineLastNonWhitespaceColumn(lineNumber) { this._assertNotDisposed(); if (lineNumber < 1 || lineNumber > this.getLineCount()) { throw new BugIndicatingError('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 /* StringOffsetValidationType.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 /* StringOffsetValidationType.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 /* StringOffsetValidationType.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 /* StringOffsetValidationType.Relaxed */)) { return false; } if (!this._isValidPosition(endLineNumber, endColumn, 0 /* StringOffsetValidationType.Relaxed */)) { return false; } if (validationType === 1 /* StringOffsetValidationType.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 /* StringOffsetValidationType.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 /* StringOffsetValidationType.Relaxed */); const end = this._validatePosition(_range.endLineNumber, _range.endColumn, 0 /* StringOffsetValidationType.Relaxed */); const startLineNumber = start.lineNumber; const startColumn = start.column; const endLineNumber = end.lineNumber; const endColumn = end.column; if (validationType === 1 /* StringOffsetValidationType.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(); const 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 /* model.EndOfLineSequence.LF */ : 1 /* model.EndOfLineSequence.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, group) { try { this._onDidChangeDecorations.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit(); return this._pushEditOperations(beforeCursorState, this._validateEditOperations(editOperations), cursorStateComputer, group); } finally { this._eventEmitter.endDeferredEmit(); this._onDidChangeDecorations.endDeferredEmit(); } } _pushEditOperations(beforeCursorState, editOperations, cursorStateComputer, group) { 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). const 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++) { const sel = beforeCursorState[i]; let foundEditNearSel = false; for (let j = 0, lenJ = incomingEdits.length; j < lenJ; j++) { const editRange = incomingEdits[j].range; const selIsAbove = editRange.startLineNumber > sel.endLineNumber; const 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++) { const trimLineNumber = this._trimAutoWhitespaceLines[i]; const maxLineColumn = this.getLineMaxColumn(trimLineNumber); let allowTrimLine = true; for (let j = 0, lenJ = incomingEdits.length; j < lenJ; j++) { const editRange = incomingEdits[j].range; const 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, group); } _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