Path: blob/main/src/vs/editor/common/model/textModel.ts
5221 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { ArrayQueue, pushMany } from '../../../base/common/arrays.js';6import { VSBuffer, VSBufferReadableStream } from '../../../base/common/buffer.js';7import { CharCode } from '../../../base/common/charCode.js';8import { SetWithKey } from '../../../base/common/collections.js';9import { Color } from '../../../base/common/color.js';10import { BugIndicatingError, illegalArgument, onUnexpectedError } from '../../../base/common/errors.js';11import { Emitter, Event } from '../../../base/common/event.js';12import { IMarkdownString } from '../../../base/common/htmlContent.js';13import { Disposable, IDisposable, MutableDisposable } from '../../../base/common/lifecycle.js';14import { listenStream } from '../../../base/common/stream.js';15import * as strings from '../../../base/common/strings.js';16import { ThemeColor } from '../../../base/common/themables.js';17import { Constants } from '../../../base/common/uint.js';18import { URI } from '../../../base/common/uri.js';19import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';20import { isDark } from '../../../platform/theme/common/theme.js';21import { IColorTheme } from '../../../platform/theme/common/themeService.js';22import { IUndoRedoService, ResourceEditStackSnapshot, UndoRedoGroup } from '../../../platform/undoRedo/common/undoRedo.js';23import { ISingleEditOperation } from '../core/editOperation.js';24import { TextEdit } from '../core/edits/textEdit.js';25import { countEOL } from '../core/misc/eolCounter.js';26import { normalizeIndentation } from '../core/misc/indentation.js';27import { EDITOR_MODEL_DEFAULTS } from '../core/misc/textModelDefaults.js';28import { IPosition, Position } from '../core/position.js';29import { IRange, Range } from '../core/range.js';30import { Selection } from '../core/selection.js';31import { TextChange } from '../core/textChange.js';32import { IWordAtPosition } from '../core/wordHelper.js';33import { FormattingOptions } from '../languages.js';34import { ILanguageSelection, ILanguageService } from '../languages/language.js';35import { ILanguageConfigurationService } from '../languages/languageConfigurationRegistry.js';36import * as model from '../model.js';37import { IBracketPairsTextModelPart } from '../textModelBracketPairs.js';38import { EditSources, TextModelEditSource } from '../textModelEditSource.js';39import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelOptionsChangedEvent, InternalModelContentChangeEvent, LineInjectedText, ModelFontChanged, ModelFontChangedEvent, ModelInjectedTextChangedEvent, ModelLineHeightChanged, ModelLineHeightChangedEvent, ModelRawChange, ModelRawContentChangedEvent, ModelRawEOLChanged, ModelRawFlush, ModelRawLineChanged, ModelRawLinesDeleted, ModelRawLinesInserted } from '../textModelEvents.js';40import { IGuidesTextModelPart } from '../textModelGuides.js';41import { ITokenizationTextModelPart } from '../tokenizationTextModelPart.js';42import { TokenArray } from '../tokens/lineTokens.js';43import { BracketPairsTextModelPart } from './bracketPairsTextModelPart/bracketPairsImpl.js';44import { ColorizedBracketPairsDecorationProvider } from './bracketPairsTextModelPart/colorizedBracketPairsDecorationProvider.js';45import { EditStack } from './editStack.js';46import { GuidesTextModelPart } from './guidesTextModelPart.js';47import { guessIndentation } from './indentationGuesser.js';48import { IntervalNode, IntervalTree, recomputeMaxEnd } from './intervalTree.js';49import { PieceTreeTextBuffer } from './pieceTreeTextBuffer/pieceTreeTextBuffer.js';50import { PieceTreeTextBufferBuilder } from './pieceTreeTextBuffer/pieceTreeTextBufferBuilder.js';51import { SearchParams, TextModelSearch } from './textModelSearch.js';52import { AttachedViews } from './tokens/abstractSyntaxTokenBackend.js';53import { TokenizationFontDecorationProvider } from './tokens/tokenizationFontDecorationsProvider.js';54import { LineFontChangingDecoration, LineHeightChangingDecoration } from './decorationProvider.js';55import { TokenizationTextModelPart } from './tokens/tokenizationTextModelPart.js';56import { IViewModel } from '../viewModel.js';5758export function createTextBufferFactory(text: string): model.ITextBufferFactory {59const builder = new PieceTreeTextBufferBuilder();60builder.acceptChunk(text);61return builder.finish();62}6364interface ITextStream {65on(event: 'data', callback: (data: string) => void): void;66on(event: 'error', callback: (err: Error) => void): void;67on(event: 'end', callback: () => void): void;68on(event: string, callback: (...args: unknown[]) => void): void;69}7071export function createTextBufferFactoryFromStream(stream: ITextStream): Promise<model.ITextBufferFactory>;72export function createTextBufferFactoryFromStream(stream: VSBufferReadableStream): Promise<model.ITextBufferFactory>;73export function createTextBufferFactoryFromStream(stream: ITextStream | VSBufferReadableStream): Promise<model.ITextBufferFactory> {74return new Promise<model.ITextBufferFactory>((resolve, reject) => {75const builder = new PieceTreeTextBufferBuilder();7677let done = false;7879listenStream<string | VSBuffer>(stream, {80onData: chunk => {81builder.acceptChunk((typeof chunk === 'string') ? chunk : chunk.toString());82},83onError: error => {84if (!done) {85done = true;86reject(error);87}88},89onEnd: () => {90if (!done) {91done = true;92resolve(builder.finish());93}94}95});96});97}9899export function createTextBufferFactoryFromSnapshot(snapshot: model.ITextSnapshot): model.ITextBufferFactory {100const builder = new PieceTreeTextBufferBuilder();101102let chunk: string | null;103while (typeof (chunk = snapshot.read()) === 'string') {104builder.acceptChunk(chunk);105}106107return builder.finish();108}109110export function createTextBuffer(value: string | model.ITextBufferFactory | model.ITextSnapshot, defaultEOL: model.DefaultEndOfLine): { textBuffer: model.ITextBuffer; disposable: IDisposable } {111let factory: model.ITextBufferFactory;112if (typeof value === 'string') {113factory = createTextBufferFactory(value);114} else if (model.isITextSnapshot(value)) {115factory = createTextBufferFactoryFromSnapshot(value);116} else {117factory = value;118}119return factory.create(defaultEOL);120}121122let MODEL_ID = 0;123124const LIMIT_FIND_COUNT = 999;125const LONG_LINE_BOUNDARY = 10000;126const LINE_HEIGHT_CEILING = 300;127128class TextModelSnapshot implements model.ITextSnapshot {129130private readonly _source: model.ITextSnapshot;131private _eos: boolean;132133constructor(source: model.ITextSnapshot) {134this._source = source;135this._eos = false;136}137138public read(): string | null {139if (this._eos) {140return null;141}142143const result: string[] = [];144let resultCnt = 0;145let resultLength = 0;146147do {148const tmp = this._source.read();149150if (tmp === null) {151// end-of-stream152this._eos = true;153if (resultCnt === 0) {154return null;155} else {156return result.join('');157}158}159160if (tmp.length > 0) {161result[resultCnt++] = tmp;162resultLength += tmp.length;163}164165if (resultLength >= 64 * 1024) {166return result.join('');167}168} while (true);169}170}171172const invalidFunc = () => { throw new Error(`Invalid change accessor`); };173174const enum StringOffsetValidationType {175/**176* Even allowed in surrogate pairs177*/178Relaxed = 0,179/**180* Not allowed in surrogate pairs181*/182SurrogatePairs = 1,183}184185export class TextModel extends Disposable implements model.ITextModel, IDecorationsTreesHost {186187static _MODEL_SYNC_LIMIT = 50 * 1024 * 1024; // 50 MB, // used in tests188private static readonly LARGE_FILE_SIZE_THRESHOLD = 20 * 1024 * 1024; // 20 MB;189private static readonly LARGE_FILE_LINE_COUNT_THRESHOLD = 300 * 1000; // 300K lines190private static readonly LARGE_FILE_HEAP_OPERATION_THRESHOLD = 256 * 1024 * 1024; // 256M characters, usually ~> 512MB memory usage191192public static DEFAULT_CREATION_OPTIONS: model.ITextModelCreationOptions = {193isForSimpleWidget: false,194tabSize: EDITOR_MODEL_DEFAULTS.tabSize,195indentSize: EDITOR_MODEL_DEFAULTS.indentSize,196insertSpaces: EDITOR_MODEL_DEFAULTS.insertSpaces,197detectIndentation: false,198defaultEOL: model.DefaultEndOfLine.LF,199trimAutoWhitespace: EDITOR_MODEL_DEFAULTS.trimAutoWhitespace,200largeFileOptimizations: EDITOR_MODEL_DEFAULTS.largeFileOptimizations,201bracketPairColorizationOptions: EDITOR_MODEL_DEFAULTS.bracketPairColorizationOptions,202};203204public static resolveOptions(textBuffer: model.ITextBuffer, options: model.ITextModelCreationOptions): model.TextModelResolvedOptions {205if (options.detectIndentation) {206const guessedIndentation = guessIndentation(textBuffer, options.tabSize, options.insertSpaces);207return new model.TextModelResolvedOptions({208tabSize: guessedIndentation.tabSize,209indentSize: 'tabSize', // TODO@Alex: guess indentSize independent of tabSize210insertSpaces: guessedIndentation.insertSpaces,211trimAutoWhitespace: options.trimAutoWhitespace,212defaultEOL: options.defaultEOL,213bracketPairColorizationOptions: options.bracketPairColorizationOptions,214});215}216217return new model.TextModelResolvedOptions(options);218}219220//#region Events221private readonly _onWillDispose: Emitter<void> = this._register(new Emitter<void>());222public readonly onWillDispose: Event<void> = this._onWillDispose.event;223224private readonly _onDidChangeDecorations: DidChangeDecorationsEmitter = this._register(new DidChangeDecorationsEmitter((affectedInjectedTextLines, affectedLineHeights, affectedFontLines) => this.handleBeforeFireDecorationsChangedEvent(affectedInjectedTextLines, affectedLineHeights, affectedFontLines)));225public readonly onDidChangeDecorations: Event<IModelDecorationsChangedEvent> = this._onDidChangeDecorations.event;226227public get onDidChangeLanguage() { return this._tokenizationTextModelPart.onDidChangeLanguage; }228public get onDidChangeLanguageConfiguration() { return this._tokenizationTextModelPart.onDidChangeLanguageConfiguration; }229public get onDidChangeTokens() { return this._tokenizationTextModelPart.onDidChangeTokens; }230231private readonly _onDidChangeOptions: Emitter<IModelOptionsChangedEvent> = this._register(new Emitter<IModelOptionsChangedEvent>());232public get onDidChangeOptions(): Event<IModelOptionsChangedEvent> { return this._onDidChangeOptions.event; }233234private readonly _onDidChangeAttached: Emitter<void> = this._register(new Emitter<void>());235public get onDidChangeAttached(): Event<void> { return this._onDidChangeAttached.event; }236237private readonly _onDidChangeLineHeight: Emitter<ModelLineHeightChangedEvent> = this._register(new Emitter<ModelLineHeightChangedEvent>());238public get onDidChangeLineHeight(): Event<ModelLineHeightChangedEvent> { return this._onDidChangeLineHeight.event; }239240private readonly _onDidChangeFont: Emitter<ModelFontChangedEvent> = this._register(new Emitter<ModelFontChangedEvent>());241public get onDidChangeFont(): Event<ModelFontChangedEvent> { return this._onDidChangeFont.event; }242243private readonly _eventEmitter: DidChangeContentEmitter = this._register(new DidChangeContentEmitter());244public onDidChangeContent(listener: (e: IModelContentChangedEvent) => void): IDisposable {245return this._eventEmitter.event((e: InternalModelContentChangeEvent) => listener(e.contentChangedEvent));246}247//#endregion248249public readonly id: string;250public readonly isForSimpleWidget: boolean;251private readonly _associatedResource: URI;252private _attachedEditorCount: number;253private _buffer: model.ITextBuffer;254private _bufferDisposable: IDisposable;255private _options: model.TextModelResolvedOptions;256private readonly _languageSelectionListener = this._register(new MutableDisposable<IDisposable>());257258private _isDisposed: boolean;259private __isDisposing: boolean;260public _isDisposing(): boolean { return this.__isDisposing; }261private _versionId: number;262/**263* Unlike, versionId, this can go down (via undo) or go to previous values (via redo)264*/265private _alternativeVersionId: number;266private _initialUndoRedoSnapshot: ResourceEditStackSnapshot | null;267private readonly _isTooLargeForSyncing: boolean;268private readonly _isTooLargeForTokenization: boolean;269private readonly _isTooLargeForHeapOperation: boolean;270271//#region Editing272private readonly _commandManager: EditStack;273private _isUndoing: boolean;274private _isRedoing: boolean;275private _trimAutoWhitespaceLines: number[] | null;276//#endregion277278//#region Decorations279/**280* Used to workaround broken clients that might attempt using a decoration id generated by a different model.281* It is not globally unique in order to limit it to one character.282*/283private readonly _instanceId: string;284private _deltaDecorationCallCnt: number = 0;285private _lastDecorationId: number;286private _decorations: { [decorationId: string]: IntervalNode };287private _decorationsTree: DecorationsTrees;288private readonly _decorationProvider: ColorizedBracketPairsDecorationProvider;289private readonly _fontTokenDecorationsProvider: TokenizationFontDecorationProvider;290//#endregion291292private readonly _tokenizationTextModelPart: TokenizationTextModelPart;293public get tokenization(): ITokenizationTextModelPart { return this._tokenizationTextModelPart; }294295private readonly _bracketPairs: BracketPairsTextModelPart;296public get bracketPairs(): IBracketPairsTextModelPart { return this._bracketPairs; }297298private readonly _guidesTextModelPart: GuidesTextModelPart;299public get guides(): IGuidesTextModelPart { return this._guidesTextModelPart; }300301private readonly _attachedViews = new AttachedViews();302private readonly _viewModels = new Set<IViewModel>();303304constructor(305source: string | model.ITextBufferFactory,306languageIdOrSelection: string | ILanguageSelection,307creationOptions: model.ITextModelCreationOptions,308associatedResource: URI | null = null,309@IUndoRedoService private readonly _undoRedoService: IUndoRedoService,310@ILanguageService private readonly _languageService: ILanguageService,311@ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService,312@IInstantiationService private readonly instantiationService: IInstantiationService313) {314super();315316// Generate a new unique model id317MODEL_ID++;318this.id = '$model' + MODEL_ID;319this.isForSimpleWidget = creationOptions.isForSimpleWidget;320if (typeof associatedResource === 'undefined' || associatedResource === null) {321this._associatedResource = URI.parse('inmemory://model/' + MODEL_ID);322} else {323this._associatedResource = associatedResource;324}325this._attachedEditorCount = 0;326327const { textBuffer, disposable } = createTextBuffer(source, creationOptions.defaultEOL);328this._buffer = textBuffer;329this._bufferDisposable = disposable;330331const bufferLineCount = this._buffer.getLineCount();332const bufferTextLength = this._buffer.getValueLengthInRange(new Range(1, 1, bufferLineCount, this._buffer.getLineLength(bufferLineCount) + 1), model.EndOfLinePreference.TextDefined);333334// !!! Make a decision in the ctor and permanently respect this decision !!!335// If a model is too large at construction time, it will never get tokenized,336// under no circumstances.337if (creationOptions.largeFileOptimizations) {338this._isTooLargeForTokenization = (339(bufferTextLength > TextModel.LARGE_FILE_SIZE_THRESHOLD)340|| (bufferLineCount > TextModel.LARGE_FILE_LINE_COUNT_THRESHOLD)341);342343this._isTooLargeForHeapOperation = bufferTextLength > TextModel.LARGE_FILE_HEAP_OPERATION_THRESHOLD;344} else {345this._isTooLargeForTokenization = false;346this._isTooLargeForHeapOperation = false;347}348349this._options = TextModel.resolveOptions(this._buffer, creationOptions);350351const languageId = (typeof languageIdOrSelection === 'string' ? languageIdOrSelection : languageIdOrSelection.languageId);352if (typeof languageIdOrSelection !== 'string') {353this._languageSelectionListener.value = languageIdOrSelection.onDidChange(() => this._setLanguage(languageIdOrSelection.languageId));354}355356this._bracketPairs = this._register(new BracketPairsTextModelPart(this, this._languageConfigurationService));357this._guidesTextModelPart = this._register(new GuidesTextModelPart(this, this._languageConfigurationService));358this._decorationProvider = this._register(new ColorizedBracketPairsDecorationProvider(this));359this._tokenizationTextModelPart = this.instantiationService.createInstance(TokenizationTextModelPart,360this,361this._bracketPairs,362languageId,363this._attachedViews364);365this._fontTokenDecorationsProvider = this._register(new TokenizationFontDecorationProvider(this, this._tokenizationTextModelPart));366367this._isTooLargeForSyncing = (bufferTextLength > TextModel._MODEL_SYNC_LIMIT);368369this._versionId = 1;370this._alternativeVersionId = 1;371this._initialUndoRedoSnapshot = null;372373this._isDisposed = false;374this.__isDisposing = false;375376this._instanceId = strings.singleLetterHash(MODEL_ID);377this._lastDecorationId = 0;378this._decorations = Object.create(null);379this._decorationsTree = new DecorationsTrees();380381this._commandManager = new EditStack(this, this._undoRedoService);382this._isUndoing = false;383this._isRedoing = false;384this._trimAutoWhitespaceLines = null;385386387this._register(this._decorationProvider.onDidChange(() => {388this._onDidChangeDecorations.beginDeferredEmit();389this._onDidChangeDecorations.fire();390this._onDidChangeDecorations.endDeferredEmit();391}));392this._register(this._fontTokenDecorationsProvider.onDidChangeLineHeight((affectedLineHeights) => {393this._onDidChangeDecorations.beginDeferredEmit();394this._onDidChangeDecorations.fire();395this._fireOnDidChangeLineHeight(affectedLineHeights);396this._onDidChangeDecorations.endDeferredEmit();397}));398this._register(this._fontTokenDecorationsProvider.onDidChangeFont((affectedFontLines) => {399this._onDidChangeDecorations.beginDeferredEmit();400this._onDidChangeDecorations.fire();401this._fireOnDidChangeFont(affectedFontLines);402this._onDidChangeDecorations.endDeferredEmit();403}));404405this._languageService.requestRichLanguageFeatures(languageId);406407this._register(this._languageConfigurationService.onDidChange(e => {408this._bracketPairs.handleLanguageConfigurationServiceChange(e);409this._tokenizationTextModelPart.handleLanguageConfigurationServiceChange(e);410}));411}412413public override dispose(): void {414this.__isDisposing = true;415this._onWillDispose.fire();416this._tokenizationTextModelPart.dispose();417this._isDisposed = true;418super.dispose();419this._bufferDisposable.dispose();420this.__isDisposing = false;421// Manually release reference to previous text buffer to avoid large leaks422// in case someone leaks a TextModel reference423const emptyDisposedTextBuffer = new PieceTreeTextBuffer([], '', '\n', false, false, true, true);424emptyDisposedTextBuffer.dispose();425this._buffer = emptyDisposedTextBuffer;426this._bufferDisposable = Disposable.None;427}428429_hasListeners(): boolean {430return (431this._onWillDispose.hasListeners()432|| this._onDidChangeDecorations.hasListeners()433|| this._tokenizationTextModelPart._hasListeners()434|| this._onDidChangeOptions.hasListeners()435|| this._onDidChangeAttached.hasListeners()436|| this._onDidChangeLineHeight.hasListeners()437|| this._onDidChangeFont.hasListeners()438|| this._eventEmitter.hasListeners()439);440}441442private _assertNotDisposed(): void {443if (this._isDisposed) {444throw new BugIndicatingError('Model is disposed!');445}446}447448public registerViewModel(viewModel: IViewModel): void {449this._viewModels.add(viewModel);450}451452public unregisterViewModel(viewModel: IViewModel): void {453this._viewModels.delete(viewModel);454}455456public equalsTextBuffer(other: model.ITextBuffer): boolean {457this._assertNotDisposed();458return this._buffer.equals(other);459}460461public getTextBuffer(): model.ITextBuffer {462this._assertNotDisposed();463return this._buffer;464}465466private _emitContentChangedEvent(rawChange: ModelRawContentChangedEvent, change: IModelContentChangedEvent, resultingSelection: Selection[] | null = null): void {467if (this.__isDisposing) {468// Do not confuse listeners by emitting any event after disposing469return;470}471this._tokenizationTextModelPart.handleDidChangeContent(change);472this._bracketPairs.handleDidChangeContent(change);473this._fontTokenDecorationsProvider.handleDidChangeContent(change);474const contentChangeEvent = new InternalModelContentChangeEvent(rawChange, change);475// Set resultingSelection early so viewModels can use it for cursor positioning476if (resultingSelection) {477contentChangeEvent.rawContentChangedEvent.resultingSelection = resultingSelection;478}479this._onDidChangeContentOrInjectedText(contentChangeEvent);480this._eventEmitter.fire(contentChangeEvent);481}482483public setValue(value: string | model.ITextSnapshot, reason = EditSources.setValue()): void {484this._assertNotDisposed();485486if (value === null || value === undefined) {487throw illegalArgument();488}489490const { textBuffer, disposable } = createTextBuffer(value, this._options.defaultEOL);491this._setValueFromTextBuffer(textBuffer, disposable, reason);492}493494private _createContentChanged2(range: Range, rangeOffset: number, rangeLength: number, rangeEndPosition: Position, text: string, isUndoing: boolean, isRedoing: boolean, isFlush: boolean, isEolChange: boolean, reason: TextModelEditSource): IModelContentChangedEvent {495return {496changes: [{497range: range,498rangeOffset: rangeOffset,499rangeLength: rangeLength,500text: text,501}],502eol: this._buffer.getEOL(),503isEolChange: isEolChange,504versionId: this.getVersionId(),505isUndoing: isUndoing,506isRedoing: isRedoing,507isFlush: isFlush,508detailedReasons: [reason],509detailedReasonsChangeLengths: [1],510};511}512513private _setValueFromTextBuffer(textBuffer: model.ITextBuffer, textBufferDisposable: IDisposable, reason: TextModelEditSource): void {514this._assertNotDisposed();515const oldFullModelRange = this.getFullModelRange();516const oldModelValueLength = this.getValueLengthInRange(oldFullModelRange);517const endLineNumber = this.getLineCount();518const endColumn = this.getLineMaxColumn(endLineNumber);519520this._buffer = textBuffer;521this._bufferDisposable.dispose();522this._bufferDisposable = textBufferDisposable;523this._increaseVersionId();524525// Destroy all my decorations526this._decorations = Object.create(null);527this._decorationsTree = new DecorationsTrees();528529// Destroy my edit history and settings530this._commandManager.clear();531this._trimAutoWhitespaceLines = null;532533this._emitContentChangedEvent(534new ModelRawContentChangedEvent(535[536new ModelRawFlush()537],538this._versionId,539false,540false541),542this._createContentChanged2(new Range(1, 1, endLineNumber, endColumn), 0, oldModelValueLength, new Position(endLineNumber, endColumn), this.getValue(), false, false, true, false, reason)543);544}545546public setEOL(eol: model.EndOfLineSequence): void {547this._assertNotDisposed();548const newEOL = (eol === model.EndOfLineSequence.CRLF ? '\r\n' : '\n');549if (this._buffer.getEOL() === newEOL) {550// Nothing to do551return;552}553554const oldFullModelRange = this.getFullModelRange();555const oldModelValueLength = this.getValueLengthInRange(oldFullModelRange);556const endLineNumber = this.getLineCount();557const endColumn = this.getLineMaxColumn(endLineNumber);558559this._onBeforeEOLChange();560this._buffer.setEOL(newEOL);561this._increaseVersionId();562this._onAfterEOLChange();563564this._emitContentChangedEvent(565new ModelRawContentChangedEvent(566[567new ModelRawEOLChanged()568],569this._versionId,570false,571false572),573this._createContentChanged2(new Range(1, 1, endLineNumber, endColumn), 0, oldModelValueLength, new Position(endLineNumber, endColumn), this.getValue(), false, false, false, true, EditSources.eolChange())574);575}576577private _onBeforeEOLChange(): void {578// Ensure all decorations get their `range` set.579this._decorationsTree.ensureAllNodesHaveRanges(this);580}581582private _onAfterEOLChange(): void {583// Transform back `range` to offsets584const versionId = this.getVersionId();585const allDecorations = this._decorationsTree.collectNodesPostOrder();586for (let i = 0, len = allDecorations.length; i < len; i++) {587const node = allDecorations[i];588const range = node.range!; // the range is defined due to `_onBeforeEOLChange`589590const delta = node.cachedAbsoluteStart - node.start;591592const startOffset = this._buffer.getOffsetAt(range.startLineNumber, range.startColumn);593const endOffset = this._buffer.getOffsetAt(range.endLineNumber, range.endColumn);594595node.cachedAbsoluteStart = startOffset;596node.cachedAbsoluteEnd = endOffset;597node.cachedVersionId = versionId;598599node.start = startOffset - delta;600node.end = endOffset - delta;601602recomputeMaxEnd(node);603}604}605606public onBeforeAttached(): model.IAttachedView {607this._attachedEditorCount++;608if (this._attachedEditorCount === 1) {609this._tokenizationTextModelPart.handleDidChangeAttached();610this._onDidChangeAttached.fire(undefined);611}612return this._attachedViews.attachView();613}614615public onBeforeDetached(view: model.IAttachedView): void {616this._attachedEditorCount--;617if (this._attachedEditorCount === 0) {618this._tokenizationTextModelPart.handleDidChangeAttached();619this._onDidChangeAttached.fire(undefined);620}621this._attachedViews.detachView(view);622}623624public isAttachedToEditor(): boolean {625return this._attachedEditorCount > 0;626}627628public getAttachedEditorCount(): number {629return this._attachedEditorCount;630}631632public isTooLargeForSyncing(): boolean {633return this._isTooLargeForSyncing;634}635636public isTooLargeForTokenization(): boolean {637return this._isTooLargeForTokenization;638}639640public isTooLargeForHeapOperation(): boolean {641return this._isTooLargeForHeapOperation;642}643644public isDisposed(): boolean {645return this._isDisposed;646}647648public isDominatedByLongLines(): boolean {649this._assertNotDisposed();650if (this.isTooLargeForTokenization()) {651// Cannot word wrap huge files anyways, so it doesn't really matter652return false;653}654let smallLineCharCount = 0;655let longLineCharCount = 0;656657const lineCount = this._buffer.getLineCount();658for (let lineNumber = 1; lineNumber <= lineCount; lineNumber++) {659const lineLength = this._buffer.getLineLength(lineNumber);660if (lineLength >= LONG_LINE_BOUNDARY) {661longLineCharCount += lineLength;662} else {663smallLineCharCount += lineLength;664}665}666667return (longLineCharCount > smallLineCharCount);668}669670public get uri(): URI {671return this._associatedResource;672}673674//#region Options675676public getOptions(): model.TextModelResolvedOptions {677this._assertNotDisposed();678return this._options;679}680681public getFormattingOptions(): FormattingOptions {682return {683tabSize: this._options.indentSize,684insertSpaces: this._options.insertSpaces685};686}687688public updateOptions(_newOpts: model.ITextModelUpdateOptions): void {689this._assertNotDisposed();690const tabSize = (typeof _newOpts.tabSize !== 'undefined') ? _newOpts.tabSize : this._options.tabSize;691const indentSize = (typeof _newOpts.indentSize !== 'undefined') ? _newOpts.indentSize : this._options.originalIndentSize;692const insertSpaces = (typeof _newOpts.insertSpaces !== 'undefined') ? _newOpts.insertSpaces : this._options.insertSpaces;693const trimAutoWhitespace = (typeof _newOpts.trimAutoWhitespace !== 'undefined') ? _newOpts.trimAutoWhitespace : this._options.trimAutoWhitespace;694const bracketPairColorizationOptions = (typeof _newOpts.bracketColorizationOptions !== 'undefined') ? _newOpts.bracketColorizationOptions : this._options.bracketPairColorizationOptions;695696const newOpts = new model.TextModelResolvedOptions({697tabSize: tabSize,698indentSize: indentSize,699insertSpaces: insertSpaces,700defaultEOL: this._options.defaultEOL,701trimAutoWhitespace: trimAutoWhitespace,702bracketPairColorizationOptions,703});704705if (this._options.equals(newOpts)) {706return;707}708709const e = this._options.createChangeEvent(newOpts);710this._options = newOpts;711712this._bracketPairs.handleDidChangeOptions(e);713this._decorationProvider.handleDidChangeOptions(e);714this._onDidChangeOptions.fire(e);715}716717public detectIndentation(defaultInsertSpaces: boolean, defaultTabSize: number): void {718this._assertNotDisposed();719const guessedIndentation = guessIndentation(this._buffer, defaultTabSize, defaultInsertSpaces);720this.updateOptions({721insertSpaces: guessedIndentation.insertSpaces,722tabSize: guessedIndentation.tabSize,723indentSize: guessedIndentation.tabSize, // TODO@Alex: guess indentSize independent of tabSize724});725}726727public normalizeIndentation(str: string): string {728this._assertNotDisposed();729return normalizeIndentation(str, this._options.indentSize, this._options.insertSpaces);730}731732//#endregion733734//#region Reading735736public getVersionId(): number {737this._assertNotDisposed();738return this._versionId;739}740741public mightContainRTL(): boolean {742return this._buffer.mightContainRTL();743}744745public mightContainUnusualLineTerminators(): boolean {746return this._buffer.mightContainUnusualLineTerminators();747}748749public removeUnusualLineTerminators(selections: Selection[] | null = null): void {750const matches = this.findMatches(strings.UNUSUAL_LINE_TERMINATORS.source, false, true, false, null, false, Constants.MAX_SAFE_SMALL_INTEGER);751this._buffer.resetMightContainUnusualLineTerminators();752this.pushEditOperations(selections, matches.map(m => ({ range: m.range, text: null })), () => null);753}754755public mightContainNonBasicASCII(): boolean {756return this._buffer.mightContainNonBasicASCII();757}758759public getAlternativeVersionId(): number {760this._assertNotDisposed();761return this._alternativeVersionId;762}763764public getInitialUndoRedoSnapshot(): ResourceEditStackSnapshot | null {765this._assertNotDisposed();766return this._initialUndoRedoSnapshot;767}768769public getOffsetAt(rawPosition: IPosition): number {770this._assertNotDisposed();771const position = this._validatePosition(rawPosition.lineNumber, rawPosition.column, StringOffsetValidationType.Relaxed);772return this._buffer.getOffsetAt(position.lineNumber, position.column);773}774775public getPositionAt(rawOffset: number): Position {776this._assertNotDisposed();777const offset = (Math.min(this._buffer.getLength(), Math.max(0, rawOffset)));778return this._buffer.getPositionAt(offset);779}780781private _increaseVersionId(): void {782this._versionId = this._versionId + 1;783this._alternativeVersionId = this._versionId;784}785786public _overwriteVersionId(versionId: number): void {787this._versionId = versionId;788}789790public _overwriteAlternativeVersionId(newAlternativeVersionId: number): void {791this._alternativeVersionId = newAlternativeVersionId;792}793794public _overwriteInitialUndoRedoSnapshot(newInitialUndoRedoSnapshot: ResourceEditStackSnapshot | null): void {795this._initialUndoRedoSnapshot = newInitialUndoRedoSnapshot;796}797798public getValue(eol?: model.EndOfLinePreference, preserveBOM: boolean = false): string {799this._assertNotDisposed();800if (this.isTooLargeForHeapOperation()) {801throw new BugIndicatingError('Operation would exceed heap memory limits');802}803804const fullModelRange = this.getFullModelRange();805const fullModelValue = this.getValueInRange(fullModelRange, eol);806807if (preserveBOM) {808return this._buffer.getBOM() + fullModelValue;809}810811return fullModelValue;812}813814public createSnapshot(preserveBOM: boolean = false): model.ITextSnapshot {815return new TextModelSnapshot(this._buffer.createSnapshot(preserveBOM));816}817818public getValueLength(eol?: model.EndOfLinePreference, preserveBOM: boolean = false): number {819this._assertNotDisposed();820const fullModelRange = this.getFullModelRange();821const fullModelValue = this.getValueLengthInRange(fullModelRange, eol);822823if (preserveBOM) {824return this._buffer.getBOM().length + fullModelValue;825}826827return fullModelValue;828}829830public getValueInRange(rawRange: IRange, eol: model.EndOfLinePreference = model.EndOfLinePreference.TextDefined): string {831this._assertNotDisposed();832return this._buffer.getValueInRange(this.validateRange(rawRange), eol);833}834835public getValueLengthInRange(rawRange: IRange, eol: model.EndOfLinePreference = model.EndOfLinePreference.TextDefined): number {836this._assertNotDisposed();837return this._buffer.getValueLengthInRange(this.validateRange(rawRange), eol);838}839840public getCharacterCountInRange(rawRange: IRange, eol: model.EndOfLinePreference = model.EndOfLinePreference.TextDefined): number {841this._assertNotDisposed();842return this._buffer.getCharacterCountInRange(this.validateRange(rawRange), eol);843}844845public getLineCount(): number {846this._assertNotDisposed();847return this._buffer.getLineCount();848}849850public getLineContent(lineNumber: number): string {851this._assertNotDisposed();852if (lineNumber < 1 || lineNumber > this.getLineCount()) {853throw new BugIndicatingError('Illegal value for lineNumber');854}855856return this._buffer.getLineContent(lineNumber);857}858859public getLineLength(lineNumber: number): number {860this._assertNotDisposed();861if (lineNumber < 1 || lineNumber > this.getLineCount()) {862throw new BugIndicatingError('Illegal value for lineNumber');863}864865return this._buffer.getLineLength(lineNumber);866}867868public getLinesContent(): string[] {869this._assertNotDisposed();870if (this.isTooLargeForHeapOperation()) {871throw new BugIndicatingError('Operation would exceed heap memory limits');872}873874return this._buffer.getLinesContent();875}876877public getEOL(): string {878this._assertNotDisposed();879return this._buffer.getEOL();880}881882public getEndOfLineSequence(): model.EndOfLineSequence {883this._assertNotDisposed();884return (885this._buffer.getEOL() === '\n'886? model.EndOfLineSequence.LF887: model.EndOfLineSequence.CRLF888);889}890891public getLineMinColumn(lineNumber: number): number {892this._assertNotDisposed();893return 1;894}895896public getLineMaxColumn(lineNumber: number): number {897this._assertNotDisposed();898if (lineNumber < 1 || lineNumber > this.getLineCount()) {899throw new BugIndicatingError('Illegal value for lineNumber');900}901return this._buffer.getLineLength(lineNumber) + 1;902}903904public getLineFirstNonWhitespaceColumn(lineNumber: number): number {905this._assertNotDisposed();906if (lineNumber < 1 || lineNumber > this.getLineCount()) {907throw new BugIndicatingError('Illegal value for lineNumber');908}909return this._buffer.getLineFirstNonWhitespaceColumn(lineNumber);910}911912public getLineLastNonWhitespaceColumn(lineNumber: number): number {913this._assertNotDisposed();914if (lineNumber < 1 || lineNumber > this.getLineCount()) {915throw new BugIndicatingError('Illegal value for lineNumber');916}917return this._buffer.getLineLastNonWhitespaceColumn(lineNumber);918}919920/**921* Validates `range` is within buffer bounds, but allows it to sit in between surrogate pairs, etc.922* Will try to not allocate if possible.923*/924public _validateRangeRelaxedNoAllocations(range: IRange): Range {925const linesCount = this._buffer.getLineCount();926927const initialStartLineNumber = range.startLineNumber;928const initialStartColumn = range.startColumn;929let startLineNumber = Math.floor((typeof initialStartLineNumber === 'number' && !isNaN(initialStartLineNumber)) ? initialStartLineNumber : 1);930let startColumn = Math.floor((typeof initialStartColumn === 'number' && !isNaN(initialStartColumn)) ? initialStartColumn : 1);931932if (startLineNumber < 1) {933startLineNumber = 1;934startColumn = 1;935} else if (startLineNumber > linesCount) {936startLineNumber = linesCount;937startColumn = this.getLineMaxColumn(startLineNumber);938} else {939if (startColumn <= 1) {940startColumn = 1;941} else {942const maxColumn = this.getLineMaxColumn(startLineNumber);943if (startColumn >= maxColumn) {944startColumn = maxColumn;945}946}947}948949const initialEndLineNumber = range.endLineNumber;950const initialEndColumn = range.endColumn;951let endLineNumber = Math.floor((typeof initialEndLineNumber === 'number' && !isNaN(initialEndLineNumber)) ? initialEndLineNumber : 1);952let endColumn = Math.floor((typeof initialEndColumn === 'number' && !isNaN(initialEndColumn)) ? initialEndColumn : 1);953954if (endLineNumber < 1) {955endLineNumber = 1;956endColumn = 1;957} else if (endLineNumber > linesCount) {958endLineNumber = linesCount;959endColumn = this.getLineMaxColumn(endLineNumber);960} else {961if (endColumn <= 1) {962endColumn = 1;963} else {964const maxColumn = this.getLineMaxColumn(endLineNumber);965if (endColumn >= maxColumn) {966endColumn = maxColumn;967}968}969}970971if (972initialStartLineNumber === startLineNumber973&& initialStartColumn === startColumn974&& initialEndLineNumber === endLineNumber975&& initialEndColumn === endColumn976&& range instanceof Range977&& !(range instanceof Selection)978) {979return range;980}981982return new Range(startLineNumber, startColumn, endLineNumber, endColumn);983}984985private _isValidPosition(lineNumber: number, column: number, validationType: StringOffsetValidationType): boolean {986if (typeof lineNumber !== 'number' || typeof column !== 'number') {987return false;988}989990if (isNaN(lineNumber) || isNaN(column)) {991return false;992}993994if (lineNumber < 1 || column < 1) {995return false;996}997998if ((lineNumber | 0) !== lineNumber || (column | 0) !== column) {999return false;1000}10011002const lineCount = this._buffer.getLineCount();1003if (lineNumber > lineCount) {1004return false;1005}10061007if (column === 1) {1008return true;1009}10101011const maxColumn = this.getLineMaxColumn(lineNumber);1012if (column > maxColumn) {1013return false;1014}10151016if (validationType === StringOffsetValidationType.SurrogatePairs) {1017// !!At this point, column > 11018const charCodeBefore = this._buffer.getLineCharCode(lineNumber, column - 2);1019if (strings.isHighSurrogate(charCodeBefore)) {1020return false;1021}1022}10231024return true;1025}10261027private _validatePosition(_lineNumber: number, _column: number, validationType: StringOffsetValidationType): Position {1028const lineNumber = Math.floor((typeof _lineNumber === 'number' && !isNaN(_lineNumber)) ? _lineNumber : 1);1029const column = Math.floor((typeof _column === 'number' && !isNaN(_column)) ? _column : 1);1030const lineCount = this._buffer.getLineCount();10311032if (lineNumber < 1) {1033return new Position(1, 1);1034}10351036if (lineNumber > lineCount) {1037return new Position(lineCount, this.getLineMaxColumn(lineCount));1038}10391040if (column <= 1) {1041return new Position(lineNumber, 1);1042}10431044const maxColumn = this.getLineMaxColumn(lineNumber);1045if (column >= maxColumn) {1046return new Position(lineNumber, maxColumn);1047}10481049if (validationType === StringOffsetValidationType.SurrogatePairs) {1050// If the position would end up in the middle of a high-low surrogate pair,1051// we move it to before the pair1052// !!At this point, column > 11053const charCodeBefore = this._buffer.getLineCharCode(lineNumber, column - 2);1054if (strings.isHighSurrogate(charCodeBefore)) {1055return new Position(lineNumber, column - 1);1056}1057}10581059return new Position(lineNumber, column);1060}10611062public validatePosition(position: IPosition): Position {1063const validationType = StringOffsetValidationType.SurrogatePairs;1064this._assertNotDisposed();10651066// Avoid object allocation and cover most likely case1067if (position instanceof Position) {1068if (this._isValidPosition(position.lineNumber, position.column, validationType)) {1069return position;1070}1071}10721073return this._validatePosition(position.lineNumber, position.column, validationType);1074}10751076public isValidRange(range: Range): boolean {1077return this._isValidRange(range, StringOffsetValidationType.SurrogatePairs);1078}10791080private _isValidRange(range: Range, validationType: StringOffsetValidationType): boolean {1081const startLineNumber = range.startLineNumber;1082const startColumn = range.startColumn;1083const endLineNumber = range.endLineNumber;1084const endColumn = range.endColumn;10851086if (!this._isValidPosition(startLineNumber, startColumn, StringOffsetValidationType.Relaxed)) {1087return false;1088}1089if (!this._isValidPosition(endLineNumber, endColumn, StringOffsetValidationType.Relaxed)) {1090return false;1091}10921093if (validationType === StringOffsetValidationType.SurrogatePairs) {1094const charCodeBeforeStart = (startColumn > 1 ? this._buffer.getLineCharCode(startLineNumber, startColumn - 2) : 0);1095const charCodeBeforeEnd = (endColumn > 1 && endColumn <= this._buffer.getLineLength(endLineNumber) ? this._buffer.getLineCharCode(endLineNumber, endColumn - 2) : 0);10961097const startInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeStart);1098const endInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeEnd);10991100if (!startInsideSurrogatePair && !endInsideSurrogatePair) {1101return true;1102}1103return false;1104}11051106return true;1107}11081109public validateRange(_range: IRange): Range {1110const validationType = StringOffsetValidationType.SurrogatePairs;1111this._assertNotDisposed();11121113// Avoid object allocation and cover most likely case1114if ((_range instanceof Range) && !(_range instanceof Selection)) {1115if (this._isValidRange(_range, validationType)) {1116return _range;1117}1118}11191120const start = this._validatePosition(_range.startLineNumber, _range.startColumn, StringOffsetValidationType.Relaxed);1121const end = this._validatePosition(_range.endLineNumber, _range.endColumn, StringOffsetValidationType.Relaxed);11221123const startLineNumber = start.lineNumber;1124const startColumn = start.column;1125const endLineNumber = end.lineNumber;1126const endColumn = end.column;11271128if (validationType === StringOffsetValidationType.SurrogatePairs) {1129const charCodeBeforeStart = (startColumn > 1 ? this._buffer.getLineCharCode(startLineNumber, startColumn - 2) : 0);1130const charCodeBeforeEnd = (endColumn > 1 && endColumn <= this._buffer.getLineLength(endLineNumber) ? this._buffer.getLineCharCode(endLineNumber, endColumn - 2) : 0);11311132const startInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeStart);1133const endInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeEnd);11341135if (!startInsideSurrogatePair && !endInsideSurrogatePair) {1136return new Range(startLineNumber, startColumn, endLineNumber, endColumn);1137}11381139if (startLineNumber === endLineNumber && startColumn === endColumn) {1140// do not expand a collapsed range, simply move it to a valid location1141return new Range(startLineNumber, startColumn - 1, endLineNumber, endColumn - 1);1142}11431144if (startInsideSurrogatePair && endInsideSurrogatePair) {1145// expand range at both ends1146return new Range(startLineNumber, startColumn - 1, endLineNumber, endColumn + 1);1147}11481149if (startInsideSurrogatePair) {1150// only expand range at the start1151return new Range(startLineNumber, startColumn - 1, endLineNumber, endColumn);1152}11531154// only expand range at the end1155return new Range(startLineNumber, startColumn, endLineNumber, endColumn + 1);1156}11571158return new Range(startLineNumber, startColumn, endLineNumber, endColumn);1159}11601161public modifyPosition(rawPosition: IPosition, offset: number): Position {1162this._assertNotDisposed();1163const candidate = this.getOffsetAt(rawPosition) + offset;1164return this.getPositionAt(Math.min(this._buffer.getLength(), Math.max(0, candidate)));1165}11661167public getFullModelRange(): Range {1168this._assertNotDisposed();1169const lineCount = this.getLineCount();1170return new Range(1, 1, lineCount, this.getLineMaxColumn(lineCount));1171}11721173private findMatchesLineByLine(searchRange: Range, searchData: model.SearchData, captureMatches: boolean, limitResultCount: number): model.FindMatch[] {1174return this._buffer.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount);1175}11761177public findMatches(searchString: string, rawSearchScope: boolean | IRange | IRange[] | null, isRegex: boolean, matchCase: boolean, wordSeparators: string | null, captureMatches: boolean, limitResultCount: number = LIMIT_FIND_COUNT): model.FindMatch[] {1178this._assertNotDisposed();11791180let searchRanges: Range[] | null = null;11811182if (rawSearchScope !== null && typeof rawSearchScope !== 'boolean') {1183if (!Array.isArray(rawSearchScope)) {1184rawSearchScope = [rawSearchScope];1185}11861187if (rawSearchScope.every((searchScope: IRange) => Range.isIRange(searchScope))) {1188searchRanges = rawSearchScope.map((searchScope: IRange) => this.validateRange(searchScope));1189}1190}11911192if (searchRanges === null) {1193searchRanges = [this.getFullModelRange()];1194}11951196searchRanges = searchRanges.sort((d1, d2) => d1.startLineNumber - d2.startLineNumber || d1.startColumn - d2.startColumn);11971198const uniqueSearchRanges: Range[] = [];1199uniqueSearchRanges.push(searchRanges.reduce((prev, curr) => {1200if (Range.areIntersecting(prev, curr)) {1201return prev.plusRange(curr);1202}12031204uniqueSearchRanges.push(prev);1205return curr;1206}));12071208let matchMapper: (value: Range, index: number, array: Range[]) => model.FindMatch[];1209if (!isRegex && searchString.indexOf('\n') < 0) {1210// not regex, not multi line1211const searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators);1212const searchData = searchParams.parseSearchRequest();12131214if (!searchData) {1215return [];1216}12171218matchMapper = (searchRange: Range) => this.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount);1219} else {1220matchMapper = (searchRange: Range) => TextModelSearch.findMatches(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchRange, captureMatches, limitResultCount);1221}12221223return uniqueSearchRanges.map(matchMapper).reduce((arr, matches: model.FindMatch[]) => arr.concat(matches), []);1224}12251226public findNextMatch(searchString: string, rawSearchStart: IPosition, isRegex: boolean, matchCase: boolean, wordSeparators: string, captureMatches: boolean): model.FindMatch | null {1227this._assertNotDisposed();1228const searchStart = this.validatePosition(rawSearchStart);12291230if (!isRegex && searchString.indexOf('\n') < 0) {1231const searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators);1232const searchData = searchParams.parseSearchRequest();1233if (!searchData) {1234return null;1235}12361237const lineCount = this.getLineCount();1238let searchRange = new Range(searchStart.lineNumber, searchStart.column, lineCount, this.getLineMaxColumn(lineCount));1239let ret = this.findMatchesLineByLine(searchRange, searchData, captureMatches, 1);1240TextModelSearch.findNextMatch(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchStart, captureMatches);1241if (ret.length > 0) {1242return ret[0];1243}12441245searchRange = new Range(1, 1, searchStart.lineNumber, this.getLineMaxColumn(searchStart.lineNumber));1246ret = this.findMatchesLineByLine(searchRange, searchData, captureMatches, 1);12471248if (ret.length > 0) {1249return ret[0];1250}12511252return null;1253}12541255return TextModelSearch.findNextMatch(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchStart, captureMatches);1256}12571258public findPreviousMatch(searchString: string, rawSearchStart: IPosition, isRegex: boolean, matchCase: boolean, wordSeparators: string, captureMatches: boolean): model.FindMatch | null {1259this._assertNotDisposed();1260const searchStart = this.validatePosition(rawSearchStart);1261return TextModelSearch.findPreviousMatch(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchStart, captureMatches);1262}12631264//#endregion12651266//#region Editing12671268public pushStackElement(): void {1269this._commandManager.pushStackElement();1270}12711272public popStackElement(): void {1273this._commandManager.popStackElement();1274}12751276public pushEOL(eol: model.EndOfLineSequence): void {1277const currentEOL = (this.getEOL() === '\n' ? model.EndOfLineSequence.LF : model.EndOfLineSequence.CRLF);1278if (currentEOL === eol) {1279return;1280}1281try {1282this._onDidChangeDecorations.beginDeferredEmit();1283this._eventEmitter.beginDeferredEmit();1284if (this._initialUndoRedoSnapshot === null) {1285this._initialUndoRedoSnapshot = this._undoRedoService.createSnapshot(this.uri);1286}1287this._commandManager.pushEOL(eol);1288} finally {1289this._eventEmitter.endDeferredEmit();1290this._onDidChangeDecorations.endDeferredEmit();1291}1292}12931294private _validateEditOperation(rawOperation: model.IIdentifiedSingleEditOperation): model.ValidAnnotatedEditOperation {1295if (rawOperation instanceof model.ValidAnnotatedEditOperation) {1296return rawOperation;1297}12981299const validatedRange = this.validateRange(rawOperation.range);13001301// Normalize edit when replacement text ends with lone CR1302// and the range ends right before a CRLF in the buffer.1303// We strip the trailing CR from the replacement text.1304let opText = rawOperation.text;1305if (opText) {1306const endsWithLoneCR = (1307opText.length > 0 && opText.charCodeAt(opText.length - 1) === CharCode.CarriageReturn1308);1309const removeTrailingCR = (1310this.getEOL() === '\r\n' && endsWithLoneCR && validatedRange.endColumn === this.getLineMaxColumn(validatedRange.endLineNumber)1311);1312if (removeTrailingCR) {1313opText = opText.substring(0, opText.length - 1);1314}1315}13161317return new model.ValidAnnotatedEditOperation(1318rawOperation.identifier || null,1319validatedRange,1320opText,1321rawOperation.forceMoveMarkers || false,1322rawOperation.isAutoWhitespaceEdit || false,1323rawOperation._isTracked || false1324);1325}13261327private _validateEditOperations(rawOperations: readonly model.IIdentifiedSingleEditOperation[]): model.ValidAnnotatedEditOperation[] {1328const result: model.ValidAnnotatedEditOperation[] = [];1329for (let i = 0, len = rawOperations.length; i < len; i++) {1330result[i] = this._validateEditOperation(rawOperations[i]);1331}1332return result;1333}13341335public edit(edit: TextEdit, options?: { reason?: TextModelEditSource }): void {1336this.pushEditOperations(null, edit.replacements.map(r => ({ range: r.range, text: r.text })), null);1337}13381339public pushEditOperations(beforeCursorState: Selection[] | null, editOperations: model.IIdentifiedSingleEditOperation[], cursorStateComputer: model.ICursorStateComputer | null, group?: UndoRedoGroup, reason?: TextModelEditSource): Selection[] | null {1340try {1341this._onDidChangeDecorations.beginDeferredEmit();1342this._eventEmitter.beginDeferredEmit();1343return this._pushEditOperations(beforeCursorState, this._validateEditOperations(editOperations), cursorStateComputer, group, reason);1344} finally {1345this._eventEmitter.endDeferredEmit();1346this._onDidChangeDecorations.endDeferredEmit();1347}1348}13491350private _pushEditOperations(beforeCursorState: Selection[] | null, editOperations: model.ValidAnnotatedEditOperation[], cursorStateComputer: model.ICursorStateComputer | null, group?: UndoRedoGroup, reason?: TextModelEditSource): Selection[] | null {1351if (this._options.trimAutoWhitespace && this._trimAutoWhitespaceLines) {1352// Go through each saved line number and insert a trim whitespace edit1353// if it is safe to do so (no conflicts with other edits).13541355const incomingEdits = editOperations.map((op) => {1356return {1357range: this.validateRange(op.range),1358text: op.text1359};1360});13611362// Sometimes, auto-formatters change ranges automatically which can cause undesired auto whitespace trimming near the cursor1363// We'll use the following heuristic: if the edits occur near the cursor, then it's ok to trim auto whitespace1364let editsAreNearCursors = true;1365if (beforeCursorState) {1366for (let i = 0, len = beforeCursorState.length; i < len; i++) {1367const sel = beforeCursorState[i];1368let foundEditNearSel = false;1369for (let j = 0, lenJ = incomingEdits.length; j < lenJ; j++) {1370const editRange = incomingEdits[j].range;1371const selIsAbove = editRange.startLineNumber > sel.endLineNumber;1372const selIsBelow = sel.startLineNumber > editRange.endLineNumber;1373if (!selIsAbove && !selIsBelow) {1374foundEditNearSel = true;1375break;1376}1377}1378if (!foundEditNearSel) {1379editsAreNearCursors = false;1380break;1381}1382}1383}13841385if (editsAreNearCursors) {1386for (let i = 0, len = this._trimAutoWhitespaceLines.length; i < len; i++) {1387const trimLineNumber = this._trimAutoWhitespaceLines[i];1388const maxLineColumn = this.getLineMaxColumn(trimLineNumber);13891390let allowTrimLine = true;1391for (let j = 0, lenJ = incomingEdits.length; j < lenJ; j++) {1392const editRange = incomingEdits[j].range;1393const editText = incomingEdits[j].text;13941395if (trimLineNumber < editRange.startLineNumber || trimLineNumber > editRange.endLineNumber) {1396// `trimLine` is completely outside this edit1397continue;1398}13991400// At this point:1401// editRange.startLineNumber <= trimLine <= editRange.endLineNumber14021403if (1404trimLineNumber === editRange.startLineNumber && editRange.startColumn === maxLineColumn1405&& editRange.isEmpty() && editText && editText.length > 0 && editText.charAt(0) === '\n'1406) {1407// This edit inserts a new line (and maybe other text) after `trimLine`1408continue;1409}14101411if (1412trimLineNumber === editRange.startLineNumber && editRange.startColumn === 11413&& editRange.isEmpty() && editText && editText.length > 0 && editText.charAt(editText.length - 1) === '\n'1414) {1415// This edit inserts a new line (and maybe other text) before `trimLine`1416continue;1417}14181419// Looks like we can't trim this line as it would interfere with an incoming edit1420allowTrimLine = false;1421break;1422}14231424if (allowTrimLine) {1425const trimRange = new Range(trimLineNumber, 1, trimLineNumber, maxLineColumn);1426editOperations.push(new model.ValidAnnotatedEditOperation(null, trimRange, null, false, false, false));1427}14281429}1430}14311432this._trimAutoWhitespaceLines = null;1433}1434if (this._initialUndoRedoSnapshot === null) {1435this._initialUndoRedoSnapshot = this._undoRedoService.createSnapshot(this.uri);1436}1437return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer, group, reason);1438}14391440_applyUndo(changes: TextChange[], eol: model.EndOfLineSequence, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void {1441const edits = changes.map<ISingleEditOperation>((change) => {1442const rangeStart = this.getPositionAt(change.newPosition);1443const rangeEnd = this.getPositionAt(change.newEnd);1444return {1445range: new Range(rangeStart.lineNumber, rangeStart.column, rangeEnd.lineNumber, rangeEnd.column),1446text: change.oldText1447};1448});1449this._applyUndoRedoEdits(edits, eol, true, false, resultingAlternativeVersionId, resultingSelection);1450}14511452_applyRedo(changes: TextChange[], eol: model.EndOfLineSequence, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void {1453const edits = changes.map<ISingleEditOperation>((change) => {1454const rangeStart = this.getPositionAt(change.oldPosition);1455const rangeEnd = this.getPositionAt(change.oldEnd);1456return {1457range: new Range(rangeStart.lineNumber, rangeStart.column, rangeEnd.lineNumber, rangeEnd.column),1458text: change.newText1459};1460});1461this._applyUndoRedoEdits(edits, eol, false, true, resultingAlternativeVersionId, resultingSelection);1462}14631464private _applyUndoRedoEdits(edits: ISingleEditOperation[], eol: model.EndOfLineSequence, isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void {1465try {1466this._onDidChangeDecorations.beginDeferredEmit();1467this._eventEmitter.beginDeferredEmit();1468this._isUndoing = isUndoing;1469this._isRedoing = isRedoing;1470const operations = this._validateEditOperations(edits);1471this._doApplyEdits(operations, false, EditSources.applyEdits(), resultingSelection);1472this.setEOL(eol);1473this._overwriteAlternativeVersionId(resultingAlternativeVersionId);1474} finally {1475this._isUndoing = false;1476this._isRedoing = false;1477this._eventEmitter.endDeferredEmit(resultingSelection);1478this._onDidChangeDecorations.endDeferredEmit();1479}1480}14811482public applyEdits(operations: readonly model.IIdentifiedSingleEditOperation[]): void;1483public applyEdits(operations: readonly model.IIdentifiedSingleEditOperation[], computeUndoEdits: false): void;1484public applyEdits(operations: readonly model.IIdentifiedSingleEditOperation[], computeUndoEdits: true): model.IValidEditOperation[];1485/** @internal */1486public applyEdits(operations: readonly model.IIdentifiedSingleEditOperation[], computeUndoEdits: false, reason: TextModelEditSource): void;1487/** @internal */1488public applyEdits(operations: readonly model.IIdentifiedSingleEditOperation[], computeUndoEdits: true, reason: TextModelEditSource): model.IValidEditOperation[];1489public applyEdits(rawOperations: readonly model.IIdentifiedSingleEditOperation[], computeUndoEdits?: boolean, reason?: TextModelEditSource): void | model.IValidEditOperation[] {1490try {1491this._onDidChangeDecorations.beginDeferredEmit();1492this._eventEmitter.beginDeferredEmit();1493const operations = this._validateEditOperations(rawOperations);14941495return this._doApplyEdits(operations, computeUndoEdits ?? false, reason ?? EditSources.applyEdits());1496} finally {1497this._eventEmitter.endDeferredEmit();1498this._onDidChangeDecorations.endDeferredEmit();1499}1500}15011502private _doApplyEdits(rawOperations: model.ValidAnnotatedEditOperation[], computeUndoEdits: boolean, reason: TextModelEditSource, resultingSelection: Selection[] | null = null): void | model.IValidEditOperation[] {15031504const oldLineCount = this._buffer.getLineCount();1505const result = this._buffer.applyEdits(rawOperations, this._options.trimAutoWhitespace, computeUndoEdits);1506const newLineCount = this._buffer.getLineCount();15071508const contentChanges = result.changes;1509this._trimAutoWhitespaceLines = result.trimAutoWhitespaceLineNumbers;15101511if (contentChanges.length !== 0) {1512// We do a first pass to update decorations1513// because we want to read decorations in the second pass1514// where we will emit content change events1515// and we want to read the final decorations1516for (let i = 0, len = contentChanges.length; i < len; i++) {1517const change = contentChanges[i];1518this._decorationsTree.acceptReplace(change.rangeOffset, change.rangeLength, change.text.length, change.forceMoveMarkers);1519}15201521const rawContentChanges: ModelRawChange[] = [];15221523this._increaseVersionId();15241525let lineCount = oldLineCount;1526for (let i = 0, len = contentChanges.length; i < len; i++) {1527const change = contentChanges[i];1528const [eolCount] = countEOL(change.text);1529this._onDidChangeDecorations.fire();15301531const startLineNumber = change.range.startLineNumber;1532const endLineNumber = change.range.endLineNumber;15331534const deletingLinesCnt = endLineNumber - startLineNumber;1535const insertingLinesCnt = eolCount;1536const editingLinesCnt = Math.min(deletingLinesCnt, insertingLinesCnt);15371538const changeLineCountDelta = (insertingLinesCnt - deletingLinesCnt);15391540const currentEditStartLineNumber = newLineCount - lineCount - changeLineCountDelta + startLineNumber;1541const firstEditLineNumber = currentEditStartLineNumber;1542const lastInsertedLineNumber = currentEditStartLineNumber + insertingLinesCnt;15431544const decorationsWithInjectedTextInEditedRange = this._decorationsTree.getInjectedTextInInterval(1545this,1546this.getOffsetAt(new Position(firstEditLineNumber, 1)),1547this.getOffsetAt(new Position(lastInsertedLineNumber, this.getLineMaxColumn(lastInsertedLineNumber))),154801549);155015511552const injectedTextInEditedRange = LineInjectedText.fromDecorations(decorationsWithInjectedTextInEditedRange);1553const injectedTextInEditedRangeQueue = new ArrayQueue(injectedTextInEditedRange);15541555for (let j = editingLinesCnt; j >= 0; j--) {1556const editLineNumber = startLineNumber + j;1557const currentEditLineNumber = currentEditStartLineNumber + j;15581559injectedTextInEditedRangeQueue.takeFromEndWhile(r => r.lineNumber > currentEditLineNumber);1560const decorationsInCurrentLine = injectedTextInEditedRangeQueue.takeFromEndWhile(r => r.lineNumber === currentEditLineNumber);15611562rawContentChanges.push(1563new ModelRawLineChanged(1564editLineNumber,1565this.getLineContent(currentEditLineNumber),1566decorationsInCurrentLine1567));1568}15691570if (editingLinesCnt < deletingLinesCnt) {1571// Must delete some lines1572const spliceStartLineNumber = startLineNumber + editingLinesCnt;1573rawContentChanges.push(new ModelRawLinesDeleted(spliceStartLineNumber + 1, endLineNumber));1574}15751576if (editingLinesCnt < insertingLinesCnt) {1577const injectedTextInEditedRangeQueue = new ArrayQueue(injectedTextInEditedRange);1578// Must insert some lines1579const spliceLineNumber = startLineNumber + editingLinesCnt;1580const cnt = insertingLinesCnt - editingLinesCnt;1581const fromLineNumber = newLineCount - lineCount - cnt + spliceLineNumber + 1;1582const injectedTexts: (LineInjectedText[] | null)[] = [];1583const newLines: string[] = [];1584for (let i = 0; i < cnt; i++) {1585const lineNumber = fromLineNumber + i;1586newLines[i] = this.getLineContent(lineNumber);15871588injectedTextInEditedRangeQueue.takeWhile(r => r.lineNumber < lineNumber);1589injectedTexts[i] = injectedTextInEditedRangeQueue.takeWhile(r => r.lineNumber === lineNumber);1590}15911592rawContentChanges.push(1593new ModelRawLinesInserted(1594spliceLineNumber + 1,1595startLineNumber + insertingLinesCnt,1596newLines,1597injectedTexts1598)1599);1600}16011602lineCount += changeLineCountDelta;1603}16041605this._emitContentChangedEvent(1606new ModelRawContentChangedEvent(1607rawContentChanges,1608this.getVersionId(),1609this._isUndoing,1610this._isRedoing1611),1612{1613changes: contentChanges,1614eol: this._buffer.getEOL(),1615isEolChange: false,1616versionId: this.getVersionId(),1617isUndoing: this._isUndoing,1618isRedoing: this._isRedoing,1619isFlush: false,1620detailedReasons: [reason],1621detailedReasonsChangeLengths: [contentChanges.length],1622},1623resultingSelection1624);1625}16261627return (result.reverseEdits === null ? undefined : result.reverseEdits);1628}16291630public undo(): void | Promise<void> {1631return this._undoRedoService.undo(this.uri);1632}16331634public canUndo(): boolean {1635return this._undoRedoService.canUndo(this.uri);1636}16371638public redo(): void | Promise<void> {1639return this._undoRedoService.redo(this.uri);1640}16411642public canRedo(): boolean {1643return this._undoRedoService.canRedo(this.uri);1644}16451646//#endregion16471648//#region Decorations16491650private handleBeforeFireDecorationsChangedEvent(affectedInjectedTextLines: Set<number> | null, affectedLineHeights: Set<LineHeightChangingDecoration> | null, affectedFontLines: Set<LineFontChangingDecoration> | null): void {1651// This is called before the decoration changed event is fired.16521653if (affectedInjectedTextLines && affectedInjectedTextLines.size > 0) {1654const affectedLines = Array.from(affectedInjectedTextLines);1655const lineChangeEvents = affectedLines.map(lineNumber => new ModelRawLineChanged(lineNumber, this.getLineContent(lineNumber), this._getInjectedTextInLine(lineNumber)));1656this._onDidChangeContentOrInjectedText(new ModelInjectedTextChangedEvent(lineChangeEvents));1657}1658this._fireOnDidChangeLineHeight(affectedLineHeights);1659this._fireOnDidChangeFont(affectedFontLines);1660}16611662private _fireOnDidChangeLineHeight(affectedLineHeights: Set<LineHeightChangingDecoration> | null): void {1663if (affectedLineHeights && affectedLineHeights.size > 0) {1664const affectedLines = Array.from(affectedLineHeights);1665const lineHeightChangeEvent = affectedLines.map(specialLineHeightChange => new ModelLineHeightChanged(specialLineHeightChange.ownerId, specialLineHeightChange.decorationId, specialLineHeightChange.lineNumber, specialLineHeightChange.lineHeight));1666this._onDidChangeLineHeight.fire(new ModelLineHeightChangedEvent(lineHeightChangeEvent));1667}1668}16691670private _fireOnDidChangeFont(affectedFontLines: Set<LineFontChangingDecoration> | null): void {1671if (affectedFontLines && affectedFontLines.size > 0) {1672const affectedLines = Array.from(affectedFontLines);1673const fontChangeEvent = affectedLines.map(fontChange => new ModelFontChanged(fontChange.ownerId, fontChange.lineNumber));1674this._onDidChangeFont.fire(new ModelFontChangedEvent(fontChangeEvent));1675}1676}16771678private _onDidChangeContentOrInjectedText(e: InternalModelContentChangeEvent | ModelInjectedTextChangedEvent): void {1679for (const viewModel of this._viewModels) {1680viewModel.onDidChangeContentOrInjectedText(e);1681}1682for (const viewModel of this._viewModels) {1683viewModel.emitContentChangeEvent(e);1684}1685}16861687public changeDecorations<T>(callback: (changeAccessor: model.IModelDecorationsChangeAccessor) => T, ownerId: number = 0): T | null {1688this._assertNotDisposed();16891690try {1691this._onDidChangeDecorations.beginDeferredEmit();1692return this._changeDecorations(ownerId, callback);1693} finally {1694this._onDidChangeDecorations.endDeferredEmit();1695}1696}16971698private _changeDecorations<T>(ownerId: number, callback: (changeAccessor: model.IModelDecorationsChangeAccessor) => T): T | null {1699const changeAccessor: model.IModelDecorationsChangeAccessor = {1700addDecoration: (range: IRange, options: model.IModelDecorationOptions): string => {1701return this._deltaDecorationsImpl(ownerId, [], [{ range: range, options: options }])[0];1702},1703changeDecoration: (id: string, newRange: IRange): void => {1704this._changeDecorationImpl(ownerId, id, newRange);1705},1706changeDecorationOptions: (id: string, options: model.IModelDecorationOptions) => {1707this._changeDecorationOptionsImpl(ownerId, id, _normalizeOptions(options));1708},1709removeDecoration: (id: string): void => {1710this._deltaDecorationsImpl(ownerId, [id], []);1711},1712deltaDecorations: (oldDecorations: string[], newDecorations: model.IModelDeltaDecoration[]): string[] => {1713if (oldDecorations.length === 0 && newDecorations.length === 0) {1714// nothing to do1715return [];1716}1717return this._deltaDecorationsImpl(ownerId, oldDecorations, newDecorations);1718}1719};1720let result: T | null = null;1721try {1722result = callback(changeAccessor);1723} catch (e) {1724onUnexpectedError(e);1725}1726// Invalidate change accessor1727changeAccessor.addDecoration = invalidFunc;1728changeAccessor.changeDecoration = invalidFunc;1729changeAccessor.changeDecorationOptions = invalidFunc;1730changeAccessor.removeDecoration = invalidFunc;1731changeAccessor.deltaDecorations = invalidFunc;1732return result;1733}17341735public deltaDecorations(oldDecorations: string[], newDecorations: model.IModelDeltaDecoration[], ownerId: number = 0): string[] {1736this._assertNotDisposed();1737if (!oldDecorations) {1738oldDecorations = [];1739}1740if (oldDecorations.length === 0 && newDecorations.length === 0) {1741// nothing to do1742return [];1743}17441745try {1746this._deltaDecorationCallCnt++;1747if (this._deltaDecorationCallCnt > 1) {1748console.warn(`Invoking deltaDecorations recursively could lead to leaking decorations.`);1749onUnexpectedError(new Error(`Invoking deltaDecorations recursively could lead to leaking decorations.`));1750}1751this._onDidChangeDecorations.beginDeferredEmit();1752return this._deltaDecorationsImpl(ownerId, oldDecorations, newDecorations);1753} finally {1754this._onDidChangeDecorations.endDeferredEmit();1755this._deltaDecorationCallCnt--;1756}1757}17581759_getTrackedRange(id: string): Range | null {1760return this.getDecorationRange(id);1761}17621763_setTrackedRange(id: string | null, newRange: null, newStickiness: model.TrackedRangeStickiness): null;1764_setTrackedRange(id: string | null, newRange: Range, newStickiness: model.TrackedRangeStickiness): string;1765_setTrackedRange(id: string | null, newRange: Range | null, newStickiness: model.TrackedRangeStickiness): string | null {1766const node = (id ? this._decorations[id] : null);17671768if (!node) {1769if (!newRange) {1770// node doesn't exist, the request is to delete => nothing to do1771return null;1772}1773// node doesn't exist, the request is to set => add the tracked range1774return this._deltaDecorationsImpl(0, [], [{ range: newRange, options: TRACKED_RANGE_OPTIONS[newStickiness] }], true)[0];1775}17761777if (!newRange) {1778// node exists, the request is to delete => delete node1779this._decorationsTree.delete(node);1780delete this._decorations[node.id];1781return null;1782}17831784// node exists, the request is to set => change the tracked range and its options1785const range = this._validateRangeRelaxedNoAllocations(newRange);1786const startOffset = this._buffer.getOffsetAt(range.startLineNumber, range.startColumn);1787const endOffset = this._buffer.getOffsetAt(range.endLineNumber, range.endColumn);1788this._decorationsTree.delete(node);1789node.reset(this.getVersionId(), startOffset, endOffset, range);1790node.setOptions(TRACKED_RANGE_OPTIONS[newStickiness]);1791this._decorationsTree.insert(node);1792return node.id;1793}17941795public removeAllDecorationsWithOwnerId(ownerId: number): void {1796if (this._isDisposed) {1797return;1798}1799const nodes = this._decorationsTree.collectNodesFromOwner(ownerId);1800for (let i = 0, len = nodes.length; i < len; i++) {1801const node = nodes[i];18021803this._decorationsTree.delete(node);1804delete this._decorations[node.id];1805}1806}18071808public getDecorationOptions(decorationId: string): model.IModelDecorationOptions | null {1809const node = this._decorations[decorationId];1810if (!node) {1811return null;1812}1813return node.options;1814}18151816public getDecorationRange(decorationId: string): Range | null {1817const node = this._decorations[decorationId];1818if (!node) {1819return null;1820}1821return this._decorationsTree.getNodeRange(this, node);1822}18231824public getLineDecorations(lineNumber: number, ownerId: number = 0, filterOutValidation: boolean = false, filterFontDecorations: boolean = false): model.IModelDecoration[] {1825if (lineNumber < 1 || lineNumber > this.getLineCount()) {1826return [];1827}1828return this.getLinesDecorations(lineNumber, lineNumber, ownerId, filterOutValidation, filterFontDecorations);1829}18301831public getLinesDecorations(_startLineNumber: number, _endLineNumber: number, ownerId: number = 0, filterOutValidation: boolean = false, filterFontDecorations: boolean = false, onlyMarginDecorations: boolean = false): model.IModelDecoration[] {1832const lineCount = this.getLineCount();1833const startLineNumber = Math.min(lineCount, Math.max(1, _startLineNumber));1834const endLineNumber = Math.min(lineCount, Math.max(1, _endLineNumber));1835const endColumn = this.getLineMaxColumn(endLineNumber);1836const range = new Range(startLineNumber, 1, endLineNumber, endColumn);18371838const decorations = this._getDecorationsInRange(range, ownerId, filterOutValidation, filterFontDecorations, onlyMarginDecorations);1839pushMany(decorations, this._decorationProvider.getDecorationsInRange(range, ownerId, filterOutValidation, filterFontDecorations));1840pushMany(decorations, this._fontTokenDecorationsProvider.getDecorationsInRange(range, ownerId, filterOutValidation, filterFontDecorations));1841return decorations;1842}18431844public getDecorationsInRange(range: IRange, ownerId: number = 0, filterOutValidation: boolean = false, filterFontDecorations: boolean = false, onlyMinimapDecorations: boolean = false, onlyMarginDecorations: boolean = false): model.IModelDecoration[] {1845const validatedRange = this.validateRange(range);18461847const decorations = this._getDecorationsInRange(validatedRange, ownerId, filterOutValidation, filterFontDecorations, onlyMarginDecorations);1848pushMany(decorations, this._decorationProvider.getDecorationsInRange(validatedRange, ownerId, filterOutValidation, filterFontDecorations, onlyMinimapDecorations));1849pushMany(decorations, this._fontTokenDecorationsProvider.getDecorationsInRange(validatedRange, ownerId, filterOutValidation, filterFontDecorations, onlyMinimapDecorations));1850return decorations;1851}18521853public getOverviewRulerDecorations(ownerId: number = 0, filterOutValidation: boolean = false, filterFontDecorations: boolean = false): model.IModelDecoration[] {1854return this._decorationsTree.getAll(this, ownerId, filterOutValidation, filterFontDecorations, true, false);1855}18561857public getInjectedTextDecorations(ownerId: number = 0): model.IModelDecoration[] {1858return this._decorationsTree.getAllInjectedText(this, ownerId);1859}18601861public getCustomLineHeightsDecorations(ownerId: number = 0): model.IModelDecoration[] {1862const decs = this._decorationsTree.getAllCustomLineHeights(this, ownerId);1863pushMany(decs, this._fontTokenDecorationsProvider.getAllDecorations(ownerId));1864return decs;1865}18661867private _getInjectedTextInLine(lineNumber: number): LineInjectedText[] {1868const startOffset = this._buffer.getOffsetAt(lineNumber, 1);1869const endOffset = startOffset + this._buffer.getLineLength(lineNumber);18701871const result = this._decorationsTree.getInjectedTextInInterval(this, startOffset, endOffset, 0);1872return LineInjectedText.fromDecorations(result).filter(t => t.lineNumber === lineNumber);1873}18741875public getFontDecorationsInRange(range: IRange, ownerId: number = 0): model.IModelDecoration[] {1876const startOffset = this._buffer.getOffsetAt(range.startLineNumber, range.startColumn);1877const endOffset = this._buffer.getOffsetAt(range.endLineNumber, range.endColumn);1878return this._decorationsTree.getFontDecorationsInInterval(this, startOffset, endOffset, ownerId);1879}18801881public getAllDecorations(ownerId: number = 0, filterOutValidation: boolean = false, filterFontDecorations: boolean = false): model.IModelDecoration[] {1882let result = this._decorationsTree.getAll(this, ownerId, filterOutValidation, filterFontDecorations, false, false);1883result = result.concat(this._decorationProvider.getAllDecorations(ownerId, filterOutValidation));1884result = result.concat(this._fontTokenDecorationsProvider.getAllDecorations(ownerId, filterOutValidation));1885return result;1886}18871888public getAllMarginDecorations(ownerId: number = 0): model.IModelDecoration[] {1889return this._decorationsTree.getAll(this, ownerId, false, false, false, true);1890}18911892private _getDecorationsInRange(filterRange: Range, filterOwnerId: number, filterOutValidation: boolean, filterFontDecorations: boolean, onlyMarginDecorations: boolean): model.IModelDecoration[] {1893const startOffset = this._buffer.getOffsetAt(filterRange.startLineNumber, filterRange.startColumn);1894const endOffset = this._buffer.getOffsetAt(filterRange.endLineNumber, filterRange.endColumn);1895return this._decorationsTree.getAllInInterval(this, startOffset, endOffset, filterOwnerId, filterOutValidation, filterFontDecorations, onlyMarginDecorations);1896}18971898public getRangeAt(start: number, end: number): Range {1899return this._buffer.getRangeAt(start, end - start);1900}19011902private _changeDecorationImpl(ownerId: number, decorationId: string, _range: IRange): void {1903const node = this._decorations[decorationId];1904if (!node) {1905return;1906}19071908if (node.options.after) {1909const oldRange = this.getDecorationRange(decorationId);1910this._onDidChangeDecorations.recordLineAffectedByInjectedText(oldRange!.endLineNumber);1911}1912if (node.options.before) {1913const oldRange = this.getDecorationRange(decorationId);1914this._onDidChangeDecorations.recordLineAffectedByInjectedText(oldRange!.startLineNumber);1915}1916if (node.options.lineHeight !== null) {1917const oldRange = this.getDecorationRange(decorationId);1918this._onDidChangeDecorations.recordLineAffectedByLineHeightChange(ownerId, decorationId, oldRange!.startLineNumber, null);1919}1920if (node.options.affectsFont) {1921const oldRange = this.getDecorationRange(decorationId);1922this._onDidChangeDecorations.recordLineAffectedByFontChange(ownerId, node.id, oldRange!.startLineNumber);1923}19241925const range = this._validateRangeRelaxedNoAllocations(_range);1926const startOffset = this._buffer.getOffsetAt(range.startLineNumber, range.startColumn);1927const endOffset = this._buffer.getOffsetAt(range.endLineNumber, range.endColumn);19281929this._decorationsTree.delete(node);1930node.reset(this.getVersionId(), startOffset, endOffset, range);1931this._decorationsTree.insert(node);1932this._onDidChangeDecorations.checkAffectedAndFire(node.options);19331934if (node.options.after) {1935this._onDidChangeDecorations.recordLineAffectedByInjectedText(range.endLineNumber);1936}1937if (node.options.before) {1938this._onDidChangeDecorations.recordLineAffectedByInjectedText(range.startLineNumber);1939}1940if (node.options.lineHeight !== null) {1941this._onDidChangeDecorations.recordLineAffectedByLineHeightChange(ownerId, decorationId, range.startLineNumber, node.options.lineHeight);1942}1943if (node.options.affectsFont) {1944this._onDidChangeDecorations.recordLineAffectedByFontChange(ownerId, node.id, range.startLineNumber);1945}1946}19471948private _changeDecorationOptionsImpl(ownerId: number, decorationId: string, options: ModelDecorationOptions): void {1949const node = this._decorations[decorationId];1950if (!node) {1951return;1952}19531954const nodeWasInOverviewRuler = (node.options.overviewRuler && node.options.overviewRuler.color ? true : false);1955const nodeIsInOverviewRuler = (options.overviewRuler && options.overviewRuler.color ? true : false);19561957this._onDidChangeDecorations.checkAffectedAndFire(node.options);1958this._onDidChangeDecorations.checkAffectedAndFire(options);19591960if (node.options.after || options.after) {1961const nodeRange = this._decorationsTree.getNodeRange(this, node);1962this._onDidChangeDecorations.recordLineAffectedByInjectedText(nodeRange.endLineNumber);1963}1964if (node.options.before || options.before) {1965const nodeRange = this._decorationsTree.getNodeRange(this, node);1966this._onDidChangeDecorations.recordLineAffectedByInjectedText(nodeRange.startLineNumber);1967}1968if (node.options.lineHeight !== null || options.lineHeight !== null) {1969const nodeRange = this._decorationsTree.getNodeRange(this, node);1970this._onDidChangeDecorations.recordLineAffectedByLineHeightChange(ownerId, decorationId, nodeRange.startLineNumber, options.lineHeight);1971}1972if (node.options.affectsFont || options.affectsFont) {1973const nodeRange = this._decorationsTree.getNodeRange(this, node);1974this._onDidChangeDecorations.recordLineAffectedByFontChange(ownerId, decorationId, nodeRange.startLineNumber);1975}19761977const movedInOverviewRuler = nodeWasInOverviewRuler !== nodeIsInOverviewRuler;1978const changedWhetherInjectedText = isOptionsInjectedText(options) !== isNodeInjectedText(node);1979if (movedInOverviewRuler || changedWhetherInjectedText) {1980this._decorationsTree.delete(node);1981node.setOptions(options);1982this._decorationsTree.insert(node);1983} else {1984node.setOptions(options);1985}1986}19871988private _deltaDecorationsImpl(ownerId: number, oldDecorationsIds: string[], newDecorations: model.IModelDeltaDecoration[], suppressEvents: boolean = false): string[] {1989const versionId = this.getVersionId();19901991const oldDecorationsLen = oldDecorationsIds.length;1992let oldDecorationIndex = 0;19931994const newDecorationsLen = newDecorations.length;1995let newDecorationIndex = 0;19961997this._onDidChangeDecorations.beginDeferredEmit();1998try {1999const result = new Array<string>(newDecorationsLen);2000while (oldDecorationIndex < oldDecorationsLen || newDecorationIndex < newDecorationsLen) {20012002let node: IntervalNode | null = null;20032004if (oldDecorationIndex < oldDecorationsLen) {2005// (1) get ourselves an old node2006let decorationId: string;2007do {2008decorationId = oldDecorationsIds[oldDecorationIndex++];2009node = this._decorations[decorationId];2010} while (!node && oldDecorationIndex < oldDecorationsLen);20112012// (2) remove the node from the tree (if it exists)2013if (node) {2014if (node.options.after) {2015const nodeRange = this._decorationsTree.getNodeRange(this, node);2016this._onDidChangeDecorations.recordLineAffectedByInjectedText(nodeRange.endLineNumber);2017}2018if (node.options.before) {2019const nodeRange = this._decorationsTree.getNodeRange(this, node);2020this._onDidChangeDecorations.recordLineAffectedByInjectedText(nodeRange.startLineNumber);2021}2022if (node.options.lineHeight !== null) {2023const nodeRange = this._decorationsTree.getNodeRange(this, node);2024this._onDidChangeDecorations.recordLineAffectedByLineHeightChange(ownerId, decorationId, nodeRange.startLineNumber, null);2025}2026if (node.options.affectsFont) {2027const nodeRange = this._decorationsTree.getNodeRange(this, node);2028this._onDidChangeDecorations.recordLineAffectedByFontChange(ownerId, decorationId, nodeRange.startLineNumber);2029}2030this._decorationsTree.delete(node);20312032if (!suppressEvents) {2033this._onDidChangeDecorations.checkAffectedAndFire(node.options);2034}2035}2036}20372038if (newDecorationIndex < newDecorationsLen) {2039// (3) create a new node if necessary2040if (!node) {2041const internalDecorationId = (++this._lastDecorationId);2042const decorationId = `${this._instanceId};${internalDecorationId}`;2043node = new IntervalNode(decorationId, 0, 0);2044this._decorations[decorationId] = node;2045}20462047// (4) initialize node2048const newDecoration = newDecorations[newDecorationIndex];2049const range = this._validateRangeRelaxedNoAllocations(newDecoration.range);2050const options = _normalizeOptions(newDecoration.options);2051const startOffset = this._buffer.getOffsetAt(range.startLineNumber, range.startColumn);2052const endOffset = this._buffer.getOffsetAt(range.endLineNumber, range.endColumn);20532054node.ownerId = ownerId;2055node.reset(versionId, startOffset, endOffset, range);2056node.setOptions(options);20572058if (node.options.after) {2059this._onDidChangeDecorations.recordLineAffectedByInjectedText(range.endLineNumber);2060}2061if (node.options.before) {2062this._onDidChangeDecorations.recordLineAffectedByInjectedText(range.startLineNumber);2063}2064if (node.options.lineHeight !== null) {2065this._onDidChangeDecorations.recordLineAffectedByLineHeightChange(ownerId, node.id, range.startLineNumber, node.options.lineHeight);2066}2067if (node.options.affectsFont) {2068this._onDidChangeDecorations.recordLineAffectedByFontChange(ownerId, node.id, range.startLineNumber);2069}2070if (!suppressEvents) {2071this._onDidChangeDecorations.checkAffectedAndFire(options);2072}20732074this._decorationsTree.insert(node);20752076result[newDecorationIndex] = node.id;20772078newDecorationIndex++;2079} else {2080if (node) {2081delete this._decorations[node.id];2082}2083}2084}20852086return result;2087} finally {2088this._onDidChangeDecorations.endDeferredEmit();2089}2090}20912092//#endregion20932094//#region Tokenization20952096// TODO move them to the tokenization part.2097public getLanguageId(): string {2098return this.tokenization.getLanguageId();2099}21002101public setLanguage(languageIdOrSelection: string | ILanguageSelection, source?: string): void {2102if (typeof languageIdOrSelection === 'string') {2103this._languageSelectionListener.clear();2104this._setLanguage(languageIdOrSelection, source);2105} else {2106this._languageSelectionListener.value = languageIdOrSelection.onDidChange(() => this._setLanguage(languageIdOrSelection.languageId, source));2107this._setLanguage(languageIdOrSelection.languageId, source);2108}2109}21102111private _setLanguage(languageId: string, source?: string): void {2112this.tokenization.setLanguageId(languageId, source);2113this._languageService.requestRichLanguageFeatures(languageId);2114}21152116public getLanguageIdAtPosition(lineNumber: number, column: number): string {2117return this.tokenization.getLanguageIdAtPosition(lineNumber, column);2118}21192120public getWordAtPosition(position: IPosition): IWordAtPosition | null {2121return this._tokenizationTextModelPart.getWordAtPosition(position);2122}21232124public getWordUntilPosition(position: IPosition): IWordAtPosition {2125return this._tokenizationTextModelPart.getWordUntilPosition(position);2126}21272128//#endregion2129normalizePosition(position: Position, affinity: model.PositionAffinity): Position {2130return position;2131}21322133/**2134* Gets the column at which indentation stops at a given line.2135* @internal2136*/2137public getLineIndentColumn(lineNumber: number): number {2138// Columns start with 1.2139return indentOfLine(this.getLineContent(lineNumber)) + 1;2140}21412142public override toString(): string {2143return `TextModel(${this.uri.toString()})`;2144}2145}21462147export function indentOfLine(line: string): number {2148let indent = 0;2149for (const c of line) {2150if (c === ' ' || c === '\t') {2151indent++;2152} else {2153break;2154}2155}2156return indent;2157}21582159//#region Decorations21602161function isNodeInOverviewRuler(node: IntervalNode): boolean {2162return (node.options.overviewRuler && node.options.overviewRuler.color ? true : false);2163}21642165function isOptionsInjectedText(options: ModelDecorationOptions): boolean {2166return !!options.after || !!options.before;2167}21682169function isNodeInjectedText(node: IntervalNode): boolean {2170return !!node.options.after || !!node.options.before;2171}21722173export interface IDecorationsTreesHost {2174getVersionId(): number;2175getRangeAt(start: number, end: number): Range;2176}21772178class DecorationsTrees {21792180/**2181* This tree holds decorations that do not show up in the overview ruler.2182*/2183private readonly _decorationsTree0: IntervalTree;21842185/**2186* This tree holds decorations that show up in the overview ruler.2187*/2188private readonly _decorationsTree1: IntervalTree;21892190/**2191* This tree holds decorations that contain injected text.2192*/2193private readonly _injectedTextDecorationsTree: IntervalTree;21942195constructor() {2196this._decorationsTree0 = new IntervalTree();2197this._decorationsTree1 = new IntervalTree();2198this._injectedTextDecorationsTree = new IntervalTree();2199}22002201public ensureAllNodesHaveRanges(host: IDecorationsTreesHost): void {2202this.getAll(host, 0, false, false, false, false);2203}22042205private _ensureNodesHaveRanges(host: IDecorationsTreesHost, nodes: IntervalNode[]): model.IModelDecoration[] {2206for (const node of nodes) {2207if (node.range === null) {2208node.range = host.getRangeAt(node.cachedAbsoluteStart, node.cachedAbsoluteEnd);2209}2210}2211return <model.IModelDecoration[]>nodes;2212}22132214public getAllInInterval(host: IDecorationsTreesHost, start: number, end: number, filterOwnerId: number, filterOutValidation: boolean, filterFontDecorations: boolean, onlyMarginDecorations: boolean): model.IModelDecoration[] {2215const versionId = host.getVersionId();2216const result = this._intervalSearch(start, end, filterOwnerId, filterOutValidation, filterFontDecorations, versionId, onlyMarginDecorations);2217return this._ensureNodesHaveRanges(host, result);2218}22192220private _intervalSearch(start: number, end: number, filterOwnerId: number, filterOutValidation: boolean, filterFontDecorations: boolean, cachedVersionId: number, onlyMarginDecorations: boolean): IntervalNode[] {2221const r0 = this._decorationsTree0.intervalSearch(start, end, filterOwnerId, filterOutValidation, filterFontDecorations, cachedVersionId, onlyMarginDecorations);2222const r1 = this._decorationsTree1.intervalSearch(start, end, filterOwnerId, filterOutValidation, filterFontDecorations, cachedVersionId, onlyMarginDecorations);2223const r2 = this._injectedTextDecorationsTree.intervalSearch(start, end, filterOwnerId, filterOutValidation, filterFontDecorations, cachedVersionId, onlyMarginDecorations);2224return r0.concat(r1).concat(r2);2225}22262227public getInjectedTextInInterval(host: IDecorationsTreesHost, start: number, end: number, filterOwnerId: number): model.IModelDecoration[] {2228const versionId = host.getVersionId();2229const result = this._injectedTextDecorationsTree.intervalSearch(start, end, filterOwnerId, false, false, versionId, false);2230return this._ensureNodesHaveRanges(host, result).filter((i) => i.options.showIfCollapsed || !i.range.isEmpty());2231}22322233public getFontDecorationsInInterval(host: IDecorationsTreesHost, start: number, end: number, filterOwnerId: number): model.IModelDecoration[] {2234const versionId = host.getVersionId();2235const decorations = this._decorationsTree0.intervalSearch(start, end, filterOwnerId, false, false, versionId, false);2236return this._ensureNodesHaveRanges(host, decorations).filter((i) => i.options.affectsFont);2237}22382239public getAllInjectedText(host: IDecorationsTreesHost, filterOwnerId: number): model.IModelDecoration[] {2240const versionId = host.getVersionId();2241const result = this._injectedTextDecorationsTree.search(filterOwnerId, false, false, versionId, false);2242return this._ensureNodesHaveRanges(host, result).filter((i) => i.options.showIfCollapsed || !i.range.isEmpty());2243}22442245public getAllCustomLineHeights(host: IDecorationsTreesHost, filterOwnerId: number): model.IModelDecoration[] {2246const versionId = host.getVersionId();2247const result = this._search(filterOwnerId, false, false, false, versionId, false);2248return this._ensureNodesHaveRanges(host, result).filter((i) => typeof i.options.lineHeight === 'number');2249}22502251public getAll(host: IDecorationsTreesHost, filterOwnerId: number, filterOutValidation: boolean, filterFontDecorations: boolean, overviewRulerOnly: boolean, onlyMarginDecorations: boolean): model.IModelDecoration[] {2252const versionId = host.getVersionId();2253const result = this._search(filterOwnerId, filterOutValidation, filterFontDecorations, overviewRulerOnly, versionId, onlyMarginDecorations);2254return this._ensureNodesHaveRanges(host, result);2255}22562257private _search(filterOwnerId: number, filterOutValidation: boolean, filterFontDecorations: boolean, overviewRulerOnly: boolean, cachedVersionId: number, onlyMarginDecorations: boolean): IntervalNode[] {2258if (overviewRulerOnly) {2259return this._decorationsTree1.search(filterOwnerId, filterOutValidation, filterFontDecorations, cachedVersionId, onlyMarginDecorations);2260} else {2261const r0 = this._decorationsTree0.search(filterOwnerId, filterOutValidation, filterFontDecorations, cachedVersionId, onlyMarginDecorations);2262const r1 = this._decorationsTree1.search(filterOwnerId, filterOutValidation, filterFontDecorations, cachedVersionId, onlyMarginDecorations);2263const r2 = this._injectedTextDecorationsTree.search(filterOwnerId, filterOutValidation, filterFontDecorations, cachedVersionId, onlyMarginDecorations);2264return r0.concat(r1).concat(r2);2265}2266}22672268public collectNodesFromOwner(ownerId: number): IntervalNode[] {2269const r0 = this._decorationsTree0.collectNodesFromOwner(ownerId);2270const r1 = this._decorationsTree1.collectNodesFromOwner(ownerId);2271const r2 = this._injectedTextDecorationsTree.collectNodesFromOwner(ownerId);2272return r0.concat(r1).concat(r2);2273}22742275public collectNodesPostOrder(): IntervalNode[] {2276const r0 = this._decorationsTree0.collectNodesPostOrder();2277const r1 = this._decorationsTree1.collectNodesPostOrder();2278const r2 = this._injectedTextDecorationsTree.collectNodesPostOrder();2279return r0.concat(r1).concat(r2);2280}22812282public insert(node: IntervalNode): void {2283if (isNodeInjectedText(node)) {2284this._injectedTextDecorationsTree.insert(node);2285} else if (isNodeInOverviewRuler(node)) {2286this._decorationsTree1.insert(node);2287} else {2288this._decorationsTree0.insert(node);2289}2290}22912292public delete(node: IntervalNode): void {2293if (isNodeInjectedText(node)) {2294this._injectedTextDecorationsTree.delete(node);2295} else if (isNodeInOverviewRuler(node)) {2296this._decorationsTree1.delete(node);2297} else {2298this._decorationsTree0.delete(node);2299}2300}23012302public getNodeRange(host: IDecorationsTreesHost, node: IntervalNode): Range {2303const versionId = host.getVersionId();2304if (node.cachedVersionId !== versionId) {2305this._resolveNode(node, versionId);2306}2307if (node.range === null) {2308node.range = host.getRangeAt(node.cachedAbsoluteStart, node.cachedAbsoluteEnd);2309}2310return node.range;2311}23122313private _resolveNode(node: IntervalNode, cachedVersionId: number): void {2314if (isNodeInjectedText(node)) {2315this._injectedTextDecorationsTree.resolveNode(node, cachedVersionId);2316} else if (isNodeInOverviewRuler(node)) {2317this._decorationsTree1.resolveNode(node, cachedVersionId);2318} else {2319this._decorationsTree0.resolveNode(node, cachedVersionId);2320}2321}23222323public acceptReplace(offset: number, length: number, textLength: number, forceMoveMarkers: boolean): void {2324this._decorationsTree0.acceptReplace(offset, length, textLength, forceMoveMarkers);2325this._decorationsTree1.acceptReplace(offset, length, textLength, forceMoveMarkers);2326this._injectedTextDecorationsTree.acceptReplace(offset, length, textLength, forceMoveMarkers);2327}2328}23292330function cleanClassName(className: string): string {2331return className.replace(/[^a-z0-9\-_]/gi, ' ');2332}23332334class DecorationOptions implements model.IDecorationOptions {2335readonly color: string | ThemeColor;2336readonly darkColor: string | ThemeColor;23372338constructor(options: model.IDecorationOptions) {2339this.color = options.color || '';2340this.darkColor = options.darkColor || '';23412342}2343}23442345export class ModelDecorationOverviewRulerOptions extends DecorationOptions {2346readonly position: model.OverviewRulerLane;2347private _resolvedColor: string | null;23482349constructor(options: model.IModelDecorationOverviewRulerOptions) {2350super(options);2351this._resolvedColor = null;2352this.position = (typeof options.position === 'number' ? options.position : model.OverviewRulerLane.Center);2353}23542355public getColor(theme: IColorTheme): string {2356if (!this._resolvedColor) {2357if (isDark(theme.type) && this.darkColor) {2358this._resolvedColor = this._resolveColor(this.darkColor, theme);2359} else {2360this._resolvedColor = this._resolveColor(this.color, theme);2361}2362}2363return this._resolvedColor;2364}23652366public invalidateCachedColor(): void {2367this._resolvedColor = null;2368}23692370private _resolveColor(color: string | ThemeColor, theme: IColorTheme): string {2371if (typeof color === 'string') {2372return color;2373}2374const c = color ? theme.getColor(color.id) : null;2375if (!c) {2376return '';2377}2378return c.toString();2379}2380}23812382export class ModelDecorationGlyphMarginOptions {2383readonly position: model.GlyphMarginLane;2384readonly persistLane: boolean | undefined;23852386constructor(options: model.IModelDecorationGlyphMarginOptions | null | undefined) {2387this.position = options?.position ?? model.GlyphMarginLane.Center;2388this.persistLane = options?.persistLane;2389}2390}23912392export class ModelDecorationMinimapOptions extends DecorationOptions {2393readonly position: model.MinimapPosition;2394readonly sectionHeaderStyle: model.MinimapSectionHeaderStyle | null;2395readonly sectionHeaderText: string | null;2396private _resolvedColor: Color | undefined;23972398constructor(options: model.IModelDecorationMinimapOptions) {2399super(options);2400this.position = options.position;2401this.sectionHeaderStyle = options.sectionHeaderStyle ?? null;2402this.sectionHeaderText = options.sectionHeaderText ?? null;2403}24042405public getColor(theme: IColorTheme): Color | undefined {2406if (!this._resolvedColor) {2407if (isDark(theme.type) && this.darkColor) {2408this._resolvedColor = this._resolveColor(this.darkColor, theme);2409} else {2410this._resolvedColor = this._resolveColor(this.color, theme);2411}2412}24132414return this._resolvedColor;2415}24162417public invalidateCachedColor(): void {2418this._resolvedColor = undefined;2419}24202421private _resolveColor(color: string | ThemeColor, theme: IColorTheme): Color | undefined {2422if (typeof color === 'string') {2423return Color.fromHex(color);2424}2425return theme.getColor(color.id);2426}2427}24282429export class ModelDecorationInjectedTextOptions implements model.InjectedTextOptions {2430public static from(options: model.InjectedTextOptions): ModelDecorationInjectedTextOptions {2431if (options instanceof ModelDecorationInjectedTextOptions) {2432return options;2433}2434return new ModelDecorationInjectedTextOptions(options);2435}24362437public readonly content: string;2438public readonly tokens: TokenArray | null;2439readonly inlineClassName: string | null;2440readonly inlineClassNameAffectsLetterSpacing: boolean;2441readonly attachedData: unknown | null;2442readonly cursorStops: model.InjectedTextCursorStops | null;24432444private constructor(options: model.InjectedTextOptions) {2445this.content = options.content || '';2446this.tokens = options.tokens ?? null;2447this.inlineClassName = options.inlineClassName || null;2448this.inlineClassNameAffectsLetterSpacing = options.inlineClassNameAffectsLetterSpacing || false;2449this.attachedData = options.attachedData || null;2450this.cursorStops = options.cursorStops || null;2451}2452}24532454export class ModelDecorationOptions implements model.IModelDecorationOptions {24552456public static EMPTY: ModelDecorationOptions;24572458public static register(options: model.IModelDecorationOptions): ModelDecorationOptions {2459return new ModelDecorationOptions(options);2460}24612462public static createDynamic(options: model.IModelDecorationOptions): ModelDecorationOptions {2463return new ModelDecorationOptions(options);2464}2465readonly description: string;2466readonly blockClassName: string | null;2467readonly blockIsAfterEnd: boolean | null;2468readonly blockDoesNotCollapse?: boolean | null;2469readonly blockPadding: [top: number, right: number, bottom: number, left: number] | null;2470readonly stickiness: model.TrackedRangeStickiness;2471readonly zIndex: number;2472readonly className: string | null;2473readonly shouldFillLineOnLineBreak: boolean | null;2474readonly hoverMessage: IMarkdownString | IMarkdownString[] | null;2475readonly glyphMarginHoverMessage: IMarkdownString | IMarkdownString[] | null;2476readonly isWholeLine: boolean;2477readonly lineHeight: number | null;2478readonly fontSize: string | null;2479readonly showIfCollapsed: boolean;2480readonly collapseOnReplaceEdit: boolean;2481readonly overviewRuler: ModelDecorationOverviewRulerOptions | null;2482readonly minimap: ModelDecorationMinimapOptions | null;2483readonly glyphMargin?: model.IModelDecorationGlyphMarginOptions | null | undefined;2484readonly glyphMarginClassName: string | null;2485readonly linesDecorationsClassName: string | null;2486readonly lineNumberClassName: string | null;2487readonly lineNumberHoverMessage: IMarkdownString | IMarkdownString[] | null;2488readonly linesDecorationsTooltip: string | null;2489readonly firstLineDecorationClassName: string | null;2490readonly marginClassName: string | null;2491readonly inlineClassName: string | null;2492readonly inlineClassNameAffectsLetterSpacing: boolean;2493readonly beforeContentClassName: string | null;2494readonly afterContentClassName: string | null;2495readonly after: ModelDecorationInjectedTextOptions | null;2496readonly before: ModelDecorationInjectedTextOptions | null;2497readonly hideInCommentTokens: boolean | null;2498readonly hideInStringTokens: boolean | null;2499readonly affectsFont: boolean | null;2500readonly textDirection?: model.TextDirection | null | undefined;25012502private constructor(options: model.IModelDecorationOptions) {2503this.description = options.description;2504this.blockClassName = options.blockClassName ? cleanClassName(options.blockClassName) : null;2505this.blockDoesNotCollapse = options.blockDoesNotCollapse ?? null;2506this.blockIsAfterEnd = options.blockIsAfterEnd ?? null;2507this.blockPadding = options.blockPadding ?? null;2508this.stickiness = options.stickiness || model.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges;2509this.zIndex = options.zIndex || 0;2510this.className = options.className ? cleanClassName(options.className) : null;2511this.shouldFillLineOnLineBreak = options.shouldFillLineOnLineBreak ?? null;2512this.hoverMessage = options.hoverMessage || null;2513this.glyphMarginHoverMessage = options.glyphMarginHoverMessage || null;2514this.lineNumberHoverMessage = options.lineNumberHoverMessage || null;2515this.isWholeLine = options.isWholeLine || false;2516this.lineHeight = options.lineHeight ? Math.min(options.lineHeight, LINE_HEIGHT_CEILING) : null;2517this.fontSize = options.fontSize || null;2518this.affectsFont = !!options.fontSize || !!options.fontFamily || !!options.fontWeight || !!options.fontStyle;2519this.showIfCollapsed = options.showIfCollapsed || false;2520this.collapseOnReplaceEdit = options.collapseOnReplaceEdit || false;2521this.overviewRuler = options.overviewRuler ? new ModelDecorationOverviewRulerOptions(options.overviewRuler) : null;2522this.minimap = options.minimap ? new ModelDecorationMinimapOptions(options.minimap) : null;2523this.glyphMargin = options.glyphMarginClassName ? new ModelDecorationGlyphMarginOptions(options.glyphMargin) : null;2524this.glyphMarginClassName = options.glyphMarginClassName ? cleanClassName(options.glyphMarginClassName) : null;2525this.linesDecorationsClassName = options.linesDecorationsClassName ? cleanClassName(options.linesDecorationsClassName) : null;2526this.lineNumberClassName = options.lineNumberClassName ? cleanClassName(options.lineNumberClassName) : null;2527this.linesDecorationsTooltip = options.linesDecorationsTooltip ? strings.htmlAttributeEncodeValue(options.linesDecorationsTooltip) : null;2528this.firstLineDecorationClassName = options.firstLineDecorationClassName ? cleanClassName(options.firstLineDecorationClassName) : null;2529this.marginClassName = options.marginClassName ? cleanClassName(options.marginClassName) : null;2530this.inlineClassName = options.inlineClassName ? cleanClassName(options.inlineClassName) : null;2531this.inlineClassNameAffectsLetterSpacing = options.inlineClassNameAffectsLetterSpacing || false;2532this.beforeContentClassName = options.beforeContentClassName ? cleanClassName(options.beforeContentClassName) : null;2533this.afterContentClassName = options.afterContentClassName ? cleanClassName(options.afterContentClassName) : null;2534this.after = options.after ? ModelDecorationInjectedTextOptions.from(options.after) : null;2535this.before = options.before ? ModelDecorationInjectedTextOptions.from(options.before) : null;2536this.hideInCommentTokens = options.hideInCommentTokens ?? false;2537this.hideInStringTokens = options.hideInStringTokens ?? false;2538this.textDirection = options.textDirection ?? null;2539}2540}2541ModelDecorationOptions.EMPTY = ModelDecorationOptions.register({ description: 'empty' });25422543/**2544* The order carefully matches the values of the enum.2545*/2546const TRACKED_RANGE_OPTIONS = [2547ModelDecorationOptions.register({ description: 'tracked-range-always-grows-when-typing-at-edges', stickiness: model.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges }),2548ModelDecorationOptions.register({ description: 'tracked-range-never-grows-when-typing-at-edges', stickiness: model.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges }),2549ModelDecorationOptions.register({ description: 'tracked-range-grows-only-when-typing-before', stickiness: model.TrackedRangeStickiness.GrowsOnlyWhenTypingBefore }),2550ModelDecorationOptions.register({ description: 'tracked-range-grows-only-when-typing-after', stickiness: model.TrackedRangeStickiness.GrowsOnlyWhenTypingAfter }),2551];25522553function _normalizeOptions(options: model.IModelDecorationOptions): ModelDecorationOptions {2554if (options instanceof ModelDecorationOptions) {2555return options;2556}2557return ModelDecorationOptions.createDynamic(options);2558}255925602561class DidChangeDecorationsEmitter extends Disposable {25622563private readonly _actual: Emitter<IModelDecorationsChangedEvent> = this._register(new Emitter<IModelDecorationsChangedEvent>());2564public readonly event: Event<IModelDecorationsChangedEvent> = this._actual.event;25652566private _deferredCnt: number;2567private _shouldFireDeferred: boolean;2568private _affectsMinimap: boolean;2569private _affectsOverviewRuler: boolean;2570private _affectedInjectedTextLines: Set<number> | null = null;2571private _affectedLineHeights: SetWithKey<LineHeightChangingDecoration> | null = null;2572private _affectedFontLines: SetWithKey<LineFontChangingDecoration> | null = null;2573private _affectsGlyphMargin: boolean;2574private _affectsLineNumber: boolean;25752576constructor(private readonly handleBeforeFire: (affectedInjectedTextLines: Set<number> | null, affectedLineHeights: SetWithKey<LineHeightChangingDecoration> | null, affectedFontLines: SetWithKey<LineFontChangingDecoration> | null) => void) {2577super();2578this._deferredCnt = 0;2579this._shouldFireDeferred = false;2580this._affectsMinimap = false;2581this._affectsOverviewRuler = false;2582this._affectsGlyphMargin = false;2583this._affectsLineNumber = false;2584}25852586hasListeners(): boolean {2587return this._actual.hasListeners();2588}25892590public beginDeferredEmit(): void {2591this._deferredCnt++;2592}25932594public endDeferredEmit(): void {2595this._deferredCnt--;2596if (this._deferredCnt === 0) {2597if (this._shouldFireDeferred) {2598this.doFire();2599}26002601this._affectedInjectedTextLines?.clear();2602this._affectedInjectedTextLines = null;2603this._affectedLineHeights?.clear();2604this._affectedLineHeights = null;2605this._affectedFontLines?.clear();2606this._affectedFontLines = null;2607}2608}26092610public recordLineAffectedByInjectedText(lineNumber: number): void {2611if (!this._affectedInjectedTextLines) {2612this._affectedInjectedTextLines = new Set();2613}2614this._affectedInjectedTextLines.add(lineNumber);2615}26162617public recordLineAffectedByLineHeightChange(ownerId: number, decorationId: string, lineNumber: number, lineHeight: number | null): void {2618if (!this._affectedLineHeights) {2619this._affectedLineHeights = new SetWithKey<LineHeightChangingDecoration>([], LineHeightChangingDecoration.toKey);2620}2621this._affectedLineHeights.add(new LineHeightChangingDecoration(ownerId, decorationId, lineNumber, lineHeight));2622}26232624public recordLineAffectedByFontChange(ownerId: number, decorationId: string, lineNumber: number): void {2625if (!this._affectedFontLines) {2626this._affectedFontLines = new SetWithKey<LineFontChangingDecoration>([], LineFontChangingDecoration.toKey);2627}2628this._affectedFontLines.add(new LineFontChangingDecoration(ownerId, decorationId, lineNumber));2629}26302631public checkAffectedAndFire(options: ModelDecorationOptions): void {2632this._affectsMinimap ||= !!options.minimap?.position;2633this._affectsOverviewRuler ||= !!options.overviewRuler?.color;2634this._affectsGlyphMargin ||= !!options.glyphMarginClassName;2635this._affectsLineNumber ||= !!options.lineNumberClassName;2636this.tryFire();2637}26382639public fire(): void {2640this._affectsMinimap = true;2641this._affectsOverviewRuler = true;2642this._affectsGlyphMargin = true;2643this.tryFire();2644}26452646private tryFire() {2647if (this._deferredCnt === 0) {2648this.doFire();2649} else {2650this._shouldFireDeferred = true;2651}2652}26532654private doFire() {2655this.handleBeforeFire(this._affectedInjectedTextLines, this._affectedLineHeights, this._affectedFontLines);26562657const event: IModelDecorationsChangedEvent = {2658affectsMinimap: this._affectsMinimap,2659affectsOverviewRuler: this._affectsOverviewRuler,2660affectsGlyphMargin: this._affectsGlyphMargin,2661affectsLineNumber: this._affectsLineNumber,2662};2663this._shouldFireDeferred = false;2664this._affectsMinimap = false;2665this._affectsOverviewRuler = false;2666this._affectsGlyphMargin = false;2667this._actual.fire(event);2668}2669}26702671//#endregion26722673class DidChangeContentEmitter extends Disposable {26742675private readonly _emitter: Emitter<InternalModelContentChangeEvent> = this._register(new Emitter<InternalModelContentChangeEvent>());2676public readonly event: Event<InternalModelContentChangeEvent> = this._emitter.event;26772678private _deferredCnt: number;2679private _deferredEvent: InternalModelContentChangeEvent | null;26802681constructor() {2682super();2683this._deferredCnt = 0;2684this._deferredEvent = null;2685}26862687public hasListeners(): boolean {2688return this._emitter.hasListeners();2689}26902691public beginDeferredEmit(): void {2692this._deferredCnt++;2693}26942695public endDeferredEmit(resultingSelection: Selection[] | null = null): void {2696this._deferredCnt--;2697if (this._deferredCnt === 0) {2698if (this._deferredEvent !== null) {2699this._deferredEvent.rawContentChangedEvent.resultingSelection = resultingSelection;2700const e = this._deferredEvent;2701this._deferredEvent = null;2702this._emitter.fire(e);2703}2704}2705}27062707public fire(e: InternalModelContentChangeEvent): void {2708if (this._deferredCnt > 0) {2709if (this._deferredEvent) {2710this._deferredEvent = this._deferredEvent.merge(e);2711} else {2712this._deferredEvent = e;2713}2714return;2715}2716this._emitter.fire(e);2717}2718}271927202721