Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/model/textModel.ts
5221 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { ArrayQueue, pushMany } from '../../../base/common/arrays.js';
7
import { VSBuffer, VSBufferReadableStream } from '../../../base/common/buffer.js';
8
import { CharCode } from '../../../base/common/charCode.js';
9
import { SetWithKey } from '../../../base/common/collections.js';
10
import { Color } from '../../../base/common/color.js';
11
import { BugIndicatingError, illegalArgument, onUnexpectedError } from '../../../base/common/errors.js';
12
import { Emitter, Event } from '../../../base/common/event.js';
13
import { IMarkdownString } from '../../../base/common/htmlContent.js';
14
import { Disposable, IDisposable, MutableDisposable } from '../../../base/common/lifecycle.js';
15
import { listenStream } from '../../../base/common/stream.js';
16
import * as strings from '../../../base/common/strings.js';
17
import { ThemeColor } from '../../../base/common/themables.js';
18
import { Constants } from '../../../base/common/uint.js';
19
import { URI } from '../../../base/common/uri.js';
20
import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';
21
import { isDark } from '../../../platform/theme/common/theme.js';
22
import { IColorTheme } from '../../../platform/theme/common/themeService.js';
23
import { IUndoRedoService, ResourceEditStackSnapshot, UndoRedoGroup } from '../../../platform/undoRedo/common/undoRedo.js';
24
import { ISingleEditOperation } from '../core/editOperation.js';
25
import { TextEdit } from '../core/edits/textEdit.js';
26
import { countEOL } from '../core/misc/eolCounter.js';
27
import { normalizeIndentation } from '../core/misc/indentation.js';
28
import { EDITOR_MODEL_DEFAULTS } from '../core/misc/textModelDefaults.js';
29
import { IPosition, Position } from '../core/position.js';
30
import { IRange, Range } from '../core/range.js';
31
import { Selection } from '../core/selection.js';
32
import { TextChange } from '../core/textChange.js';
33
import { IWordAtPosition } from '../core/wordHelper.js';
34
import { FormattingOptions } from '../languages.js';
35
import { ILanguageSelection, ILanguageService } from '../languages/language.js';
36
import { ILanguageConfigurationService } from '../languages/languageConfigurationRegistry.js';
37
import * as model from '../model.js';
38
import { IBracketPairsTextModelPart } from '../textModelBracketPairs.js';
39
import { EditSources, TextModelEditSource } from '../textModelEditSource.js';
40
import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelOptionsChangedEvent, InternalModelContentChangeEvent, LineInjectedText, ModelFontChanged, ModelFontChangedEvent, ModelInjectedTextChangedEvent, ModelLineHeightChanged, ModelLineHeightChangedEvent, ModelRawChange, ModelRawContentChangedEvent, ModelRawEOLChanged, ModelRawFlush, ModelRawLineChanged, ModelRawLinesDeleted, ModelRawLinesInserted } from '../textModelEvents.js';
41
import { IGuidesTextModelPart } from '../textModelGuides.js';
42
import { ITokenizationTextModelPart } from '../tokenizationTextModelPart.js';
43
import { TokenArray } from '../tokens/lineTokens.js';
44
import { BracketPairsTextModelPart } from './bracketPairsTextModelPart/bracketPairsImpl.js';
45
import { ColorizedBracketPairsDecorationProvider } from './bracketPairsTextModelPart/colorizedBracketPairsDecorationProvider.js';
46
import { EditStack } from './editStack.js';
47
import { GuidesTextModelPart } from './guidesTextModelPart.js';
48
import { guessIndentation } from './indentationGuesser.js';
49
import { IntervalNode, IntervalTree, recomputeMaxEnd } from './intervalTree.js';
50
import { PieceTreeTextBuffer } from './pieceTreeTextBuffer/pieceTreeTextBuffer.js';
51
import { PieceTreeTextBufferBuilder } from './pieceTreeTextBuffer/pieceTreeTextBufferBuilder.js';
52
import { SearchParams, TextModelSearch } from './textModelSearch.js';
53
import { AttachedViews } from './tokens/abstractSyntaxTokenBackend.js';
54
import { TokenizationFontDecorationProvider } from './tokens/tokenizationFontDecorationsProvider.js';
55
import { LineFontChangingDecoration, LineHeightChangingDecoration } from './decorationProvider.js';
56
import { TokenizationTextModelPart } from './tokens/tokenizationTextModelPart.js';
57
import { IViewModel } from '../viewModel.js';
58
59
export function createTextBufferFactory(text: string): model.ITextBufferFactory {
60
const builder = new PieceTreeTextBufferBuilder();
61
builder.acceptChunk(text);
62
return builder.finish();
63
}
64
65
interface ITextStream {
66
on(event: 'data', callback: (data: string) => void): void;
67
on(event: 'error', callback: (err: Error) => void): void;
68
on(event: 'end', callback: () => void): void;
69
on(event: string, callback: (...args: unknown[]) => void): void;
70
}
71
72
export function createTextBufferFactoryFromStream(stream: ITextStream): Promise<model.ITextBufferFactory>;
73
export function createTextBufferFactoryFromStream(stream: VSBufferReadableStream): Promise<model.ITextBufferFactory>;
74
export function createTextBufferFactoryFromStream(stream: ITextStream | VSBufferReadableStream): Promise<model.ITextBufferFactory> {
75
return new Promise<model.ITextBufferFactory>((resolve, reject) => {
76
const builder = new PieceTreeTextBufferBuilder();
77
78
let done = false;
79
80
listenStream<string | VSBuffer>(stream, {
81
onData: chunk => {
82
builder.acceptChunk((typeof chunk === 'string') ? chunk : chunk.toString());
83
},
84
onError: error => {
85
if (!done) {
86
done = true;
87
reject(error);
88
}
89
},
90
onEnd: () => {
91
if (!done) {
92
done = true;
93
resolve(builder.finish());
94
}
95
}
96
});
97
});
98
}
99
100
export function createTextBufferFactoryFromSnapshot(snapshot: model.ITextSnapshot): model.ITextBufferFactory {
101
const builder = new PieceTreeTextBufferBuilder();
102
103
let chunk: string | null;
104
while (typeof (chunk = snapshot.read()) === 'string') {
105
builder.acceptChunk(chunk);
106
}
107
108
return builder.finish();
109
}
110
111
export function createTextBuffer(value: string | model.ITextBufferFactory | model.ITextSnapshot, defaultEOL: model.DefaultEndOfLine): { textBuffer: model.ITextBuffer; disposable: IDisposable } {
112
let factory: model.ITextBufferFactory;
113
if (typeof value === 'string') {
114
factory = createTextBufferFactory(value);
115
} else if (model.isITextSnapshot(value)) {
116
factory = createTextBufferFactoryFromSnapshot(value);
117
} else {
118
factory = value;
119
}
120
return factory.create(defaultEOL);
121
}
122
123
let MODEL_ID = 0;
124
125
const LIMIT_FIND_COUNT = 999;
126
const LONG_LINE_BOUNDARY = 10000;
127
const LINE_HEIGHT_CEILING = 300;
128
129
class TextModelSnapshot implements model.ITextSnapshot {
130
131
private readonly _source: model.ITextSnapshot;
132
private _eos: boolean;
133
134
constructor(source: model.ITextSnapshot) {
135
this._source = source;
136
this._eos = false;
137
}
138
139
public read(): string | null {
140
if (this._eos) {
141
return null;
142
}
143
144
const result: string[] = [];
145
let resultCnt = 0;
146
let resultLength = 0;
147
148
do {
149
const tmp = this._source.read();
150
151
if (tmp === null) {
152
// end-of-stream
153
this._eos = true;
154
if (resultCnt === 0) {
155
return null;
156
} else {
157
return result.join('');
158
}
159
}
160
161
if (tmp.length > 0) {
162
result[resultCnt++] = tmp;
163
resultLength += tmp.length;
164
}
165
166
if (resultLength >= 64 * 1024) {
167
return result.join('');
168
}
169
} while (true);
170
}
171
}
172
173
const invalidFunc = () => { throw new Error(`Invalid change accessor`); };
174
175
const enum StringOffsetValidationType {
176
/**
177
* Even allowed in surrogate pairs
178
*/
179
Relaxed = 0,
180
/**
181
* Not allowed in surrogate pairs
182
*/
183
SurrogatePairs = 1,
184
}
185
186
export class TextModel extends Disposable implements model.ITextModel, IDecorationsTreesHost {
187
188
static _MODEL_SYNC_LIMIT = 50 * 1024 * 1024; // 50 MB, // used in tests
189
private static readonly LARGE_FILE_SIZE_THRESHOLD = 20 * 1024 * 1024; // 20 MB;
190
private static readonly LARGE_FILE_LINE_COUNT_THRESHOLD = 300 * 1000; // 300K lines
191
private static readonly LARGE_FILE_HEAP_OPERATION_THRESHOLD = 256 * 1024 * 1024; // 256M characters, usually ~> 512MB memory usage
192
193
public static DEFAULT_CREATION_OPTIONS: model.ITextModelCreationOptions = {
194
isForSimpleWidget: false,
195
tabSize: EDITOR_MODEL_DEFAULTS.tabSize,
196
indentSize: EDITOR_MODEL_DEFAULTS.indentSize,
197
insertSpaces: EDITOR_MODEL_DEFAULTS.insertSpaces,
198
detectIndentation: false,
199
defaultEOL: model.DefaultEndOfLine.LF,
200
trimAutoWhitespace: EDITOR_MODEL_DEFAULTS.trimAutoWhitespace,
201
largeFileOptimizations: EDITOR_MODEL_DEFAULTS.largeFileOptimizations,
202
bracketPairColorizationOptions: EDITOR_MODEL_DEFAULTS.bracketPairColorizationOptions,
203
};
204
205
public static resolveOptions(textBuffer: model.ITextBuffer, options: model.ITextModelCreationOptions): model.TextModelResolvedOptions {
206
if (options.detectIndentation) {
207
const guessedIndentation = guessIndentation(textBuffer, options.tabSize, options.insertSpaces);
208
return new model.TextModelResolvedOptions({
209
tabSize: guessedIndentation.tabSize,
210
indentSize: 'tabSize', // TODO@Alex: guess indentSize independent of tabSize
211
insertSpaces: guessedIndentation.insertSpaces,
212
trimAutoWhitespace: options.trimAutoWhitespace,
213
defaultEOL: options.defaultEOL,
214
bracketPairColorizationOptions: options.bracketPairColorizationOptions,
215
});
216
}
217
218
return new model.TextModelResolvedOptions(options);
219
}
220
221
//#region Events
222
private readonly _onWillDispose: Emitter<void> = this._register(new Emitter<void>());
223
public readonly onWillDispose: Event<void> = this._onWillDispose.event;
224
225
private readonly _onDidChangeDecorations: DidChangeDecorationsEmitter = this._register(new DidChangeDecorationsEmitter((affectedInjectedTextLines, affectedLineHeights, affectedFontLines) => this.handleBeforeFireDecorationsChangedEvent(affectedInjectedTextLines, affectedLineHeights, affectedFontLines)));
226
public readonly onDidChangeDecorations: Event<IModelDecorationsChangedEvent> = this._onDidChangeDecorations.event;
227
228
public get onDidChangeLanguage() { return this._tokenizationTextModelPart.onDidChangeLanguage; }
229
public get onDidChangeLanguageConfiguration() { return this._tokenizationTextModelPart.onDidChangeLanguageConfiguration; }
230
public get onDidChangeTokens() { return this._tokenizationTextModelPart.onDidChangeTokens; }
231
232
private readonly _onDidChangeOptions: Emitter<IModelOptionsChangedEvent> = this._register(new Emitter<IModelOptionsChangedEvent>());
233
public get onDidChangeOptions(): Event<IModelOptionsChangedEvent> { return this._onDidChangeOptions.event; }
234
235
private readonly _onDidChangeAttached: Emitter<void> = this._register(new Emitter<void>());
236
public get onDidChangeAttached(): Event<void> { return this._onDidChangeAttached.event; }
237
238
private readonly _onDidChangeLineHeight: Emitter<ModelLineHeightChangedEvent> = this._register(new Emitter<ModelLineHeightChangedEvent>());
239
public get onDidChangeLineHeight(): Event<ModelLineHeightChangedEvent> { return this._onDidChangeLineHeight.event; }
240
241
private readonly _onDidChangeFont: Emitter<ModelFontChangedEvent> = this._register(new Emitter<ModelFontChangedEvent>());
242
public get onDidChangeFont(): Event<ModelFontChangedEvent> { return this._onDidChangeFont.event; }
243
244
private readonly _eventEmitter: DidChangeContentEmitter = this._register(new DidChangeContentEmitter());
245
public onDidChangeContent(listener: (e: IModelContentChangedEvent) => void): IDisposable {
246
return this._eventEmitter.event((e: InternalModelContentChangeEvent) => listener(e.contentChangedEvent));
247
}
248
//#endregion
249
250
public readonly id: string;
251
public readonly isForSimpleWidget: boolean;
252
private readonly _associatedResource: URI;
253
private _attachedEditorCount: number;
254
private _buffer: model.ITextBuffer;
255
private _bufferDisposable: IDisposable;
256
private _options: model.TextModelResolvedOptions;
257
private readonly _languageSelectionListener = this._register(new MutableDisposable<IDisposable>());
258
259
private _isDisposed: boolean;
260
private __isDisposing: boolean;
261
public _isDisposing(): boolean { return this.__isDisposing; }
262
private _versionId: number;
263
/**
264
* Unlike, versionId, this can go down (via undo) or go to previous values (via redo)
265
*/
266
private _alternativeVersionId: number;
267
private _initialUndoRedoSnapshot: ResourceEditStackSnapshot | null;
268
private readonly _isTooLargeForSyncing: boolean;
269
private readonly _isTooLargeForTokenization: boolean;
270
private readonly _isTooLargeForHeapOperation: boolean;
271
272
//#region Editing
273
private readonly _commandManager: EditStack;
274
private _isUndoing: boolean;
275
private _isRedoing: boolean;
276
private _trimAutoWhitespaceLines: number[] | null;
277
//#endregion
278
279
//#region Decorations
280
/**
281
* Used to workaround broken clients that might attempt using a decoration id generated by a different model.
282
* It is not globally unique in order to limit it to one character.
283
*/
284
private readonly _instanceId: string;
285
private _deltaDecorationCallCnt: number = 0;
286
private _lastDecorationId: number;
287
private _decorations: { [decorationId: string]: IntervalNode };
288
private _decorationsTree: DecorationsTrees;
289
private readonly _decorationProvider: ColorizedBracketPairsDecorationProvider;
290
private readonly _fontTokenDecorationsProvider: TokenizationFontDecorationProvider;
291
//#endregion
292
293
private readonly _tokenizationTextModelPart: TokenizationTextModelPart;
294
public get tokenization(): ITokenizationTextModelPart { return this._tokenizationTextModelPart; }
295
296
private readonly _bracketPairs: BracketPairsTextModelPart;
297
public get bracketPairs(): IBracketPairsTextModelPart { return this._bracketPairs; }
298
299
private readonly _guidesTextModelPart: GuidesTextModelPart;
300
public get guides(): IGuidesTextModelPart { return this._guidesTextModelPart; }
301
302
private readonly _attachedViews = new AttachedViews();
303
private readonly _viewModels = new Set<IViewModel>();
304
305
constructor(
306
source: string | model.ITextBufferFactory,
307
languageIdOrSelection: string | ILanguageSelection,
308
creationOptions: model.ITextModelCreationOptions,
309
associatedResource: URI | null = null,
310
@IUndoRedoService private readonly _undoRedoService: IUndoRedoService,
311
@ILanguageService private readonly _languageService: ILanguageService,
312
@ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService,
313
@IInstantiationService private readonly instantiationService: IInstantiationService
314
) {
315
super();
316
317
// Generate a new unique model id
318
MODEL_ID++;
319
this.id = '$model' + MODEL_ID;
320
this.isForSimpleWidget = creationOptions.isForSimpleWidget;
321
if (typeof associatedResource === 'undefined' || associatedResource === null) {
322
this._associatedResource = URI.parse('inmemory://model/' + MODEL_ID);
323
} else {
324
this._associatedResource = associatedResource;
325
}
326
this._attachedEditorCount = 0;
327
328
const { textBuffer, disposable } = createTextBuffer(source, creationOptions.defaultEOL);
329
this._buffer = textBuffer;
330
this._bufferDisposable = disposable;
331
332
const bufferLineCount = this._buffer.getLineCount();
333
const bufferTextLength = this._buffer.getValueLengthInRange(new Range(1, 1, bufferLineCount, this._buffer.getLineLength(bufferLineCount) + 1), model.EndOfLinePreference.TextDefined);
334
335
// !!! Make a decision in the ctor and permanently respect this decision !!!
336
// If a model is too large at construction time, it will never get tokenized,
337
// under no circumstances.
338
if (creationOptions.largeFileOptimizations) {
339
this._isTooLargeForTokenization = (
340
(bufferTextLength > TextModel.LARGE_FILE_SIZE_THRESHOLD)
341
|| (bufferLineCount > TextModel.LARGE_FILE_LINE_COUNT_THRESHOLD)
342
);
343
344
this._isTooLargeForHeapOperation = bufferTextLength > TextModel.LARGE_FILE_HEAP_OPERATION_THRESHOLD;
345
} else {
346
this._isTooLargeForTokenization = false;
347
this._isTooLargeForHeapOperation = false;
348
}
349
350
this._options = TextModel.resolveOptions(this._buffer, creationOptions);
351
352
const languageId = (typeof languageIdOrSelection === 'string' ? languageIdOrSelection : languageIdOrSelection.languageId);
353
if (typeof languageIdOrSelection !== 'string') {
354
this._languageSelectionListener.value = languageIdOrSelection.onDidChange(() => this._setLanguage(languageIdOrSelection.languageId));
355
}
356
357
this._bracketPairs = this._register(new BracketPairsTextModelPart(this, this._languageConfigurationService));
358
this._guidesTextModelPart = this._register(new GuidesTextModelPart(this, this._languageConfigurationService));
359
this._decorationProvider = this._register(new ColorizedBracketPairsDecorationProvider(this));
360
this._tokenizationTextModelPart = this.instantiationService.createInstance(TokenizationTextModelPart,
361
this,
362
this._bracketPairs,
363
languageId,
364
this._attachedViews
365
);
366
this._fontTokenDecorationsProvider = this._register(new TokenizationFontDecorationProvider(this, this._tokenizationTextModelPart));
367
368
this._isTooLargeForSyncing = (bufferTextLength > TextModel._MODEL_SYNC_LIMIT);
369
370
this._versionId = 1;
371
this._alternativeVersionId = 1;
372
this._initialUndoRedoSnapshot = null;
373
374
this._isDisposed = false;
375
this.__isDisposing = false;
376
377
this._instanceId = strings.singleLetterHash(MODEL_ID);
378
this._lastDecorationId = 0;
379
this._decorations = Object.create(null);
380
this._decorationsTree = new DecorationsTrees();
381
382
this._commandManager = new EditStack(this, this._undoRedoService);
383
this._isUndoing = false;
384
this._isRedoing = false;
385
this._trimAutoWhitespaceLines = null;
386
387
388
this._register(this._decorationProvider.onDidChange(() => {
389
this._onDidChangeDecorations.beginDeferredEmit();
390
this._onDidChangeDecorations.fire();
391
this._onDidChangeDecorations.endDeferredEmit();
392
}));
393
this._register(this._fontTokenDecorationsProvider.onDidChangeLineHeight((affectedLineHeights) => {
394
this._onDidChangeDecorations.beginDeferredEmit();
395
this._onDidChangeDecorations.fire();
396
this._fireOnDidChangeLineHeight(affectedLineHeights);
397
this._onDidChangeDecorations.endDeferredEmit();
398
}));
399
this._register(this._fontTokenDecorationsProvider.onDidChangeFont((affectedFontLines) => {
400
this._onDidChangeDecorations.beginDeferredEmit();
401
this._onDidChangeDecorations.fire();
402
this._fireOnDidChangeFont(affectedFontLines);
403
this._onDidChangeDecorations.endDeferredEmit();
404
}));
405
406
this._languageService.requestRichLanguageFeatures(languageId);
407
408
this._register(this._languageConfigurationService.onDidChange(e => {
409
this._bracketPairs.handleLanguageConfigurationServiceChange(e);
410
this._tokenizationTextModelPart.handleLanguageConfigurationServiceChange(e);
411
}));
412
}
413
414
public override dispose(): void {
415
this.__isDisposing = true;
416
this._onWillDispose.fire();
417
this._tokenizationTextModelPart.dispose();
418
this._isDisposed = true;
419
super.dispose();
420
this._bufferDisposable.dispose();
421
this.__isDisposing = false;
422
// Manually release reference to previous text buffer to avoid large leaks
423
// in case someone leaks a TextModel reference
424
const emptyDisposedTextBuffer = new PieceTreeTextBuffer([], '', '\n', false, false, true, true);
425
emptyDisposedTextBuffer.dispose();
426
this._buffer = emptyDisposedTextBuffer;
427
this._bufferDisposable = Disposable.None;
428
}
429
430
_hasListeners(): boolean {
431
return (
432
this._onWillDispose.hasListeners()
433
|| this._onDidChangeDecorations.hasListeners()
434
|| this._tokenizationTextModelPart._hasListeners()
435
|| this._onDidChangeOptions.hasListeners()
436
|| this._onDidChangeAttached.hasListeners()
437
|| this._onDidChangeLineHeight.hasListeners()
438
|| this._onDidChangeFont.hasListeners()
439
|| this._eventEmitter.hasListeners()
440
);
441
}
442
443
private _assertNotDisposed(): void {
444
if (this._isDisposed) {
445
throw new BugIndicatingError('Model is disposed!');
446
}
447
}
448
449
public registerViewModel(viewModel: IViewModel): void {
450
this._viewModels.add(viewModel);
451
}
452
453
public unregisterViewModel(viewModel: IViewModel): void {
454
this._viewModels.delete(viewModel);
455
}
456
457
public equalsTextBuffer(other: model.ITextBuffer): boolean {
458
this._assertNotDisposed();
459
return this._buffer.equals(other);
460
}
461
462
public getTextBuffer(): model.ITextBuffer {
463
this._assertNotDisposed();
464
return this._buffer;
465
}
466
467
private _emitContentChangedEvent(rawChange: ModelRawContentChangedEvent, change: IModelContentChangedEvent, resultingSelection: Selection[] | null = null): void {
468
if (this.__isDisposing) {
469
// Do not confuse listeners by emitting any event after disposing
470
return;
471
}
472
this._tokenizationTextModelPart.handleDidChangeContent(change);
473
this._bracketPairs.handleDidChangeContent(change);
474
this._fontTokenDecorationsProvider.handleDidChangeContent(change);
475
const contentChangeEvent = new InternalModelContentChangeEvent(rawChange, change);
476
// Set resultingSelection early so viewModels can use it for cursor positioning
477
if (resultingSelection) {
478
contentChangeEvent.rawContentChangedEvent.resultingSelection = resultingSelection;
479
}
480
this._onDidChangeContentOrInjectedText(contentChangeEvent);
481
this._eventEmitter.fire(contentChangeEvent);
482
}
483
484
public setValue(value: string | model.ITextSnapshot, reason = EditSources.setValue()): void {
485
this._assertNotDisposed();
486
487
if (value === null || value === undefined) {
488
throw illegalArgument();
489
}
490
491
const { textBuffer, disposable } = createTextBuffer(value, this._options.defaultEOL);
492
this._setValueFromTextBuffer(textBuffer, disposable, reason);
493
}
494
495
private _createContentChanged2(range: Range, rangeOffset: number, rangeLength: number, rangeEndPosition: Position, text: string, isUndoing: boolean, isRedoing: boolean, isFlush: boolean, isEolChange: boolean, reason: TextModelEditSource): IModelContentChangedEvent {
496
return {
497
changes: [{
498
range: range,
499
rangeOffset: rangeOffset,
500
rangeLength: rangeLength,
501
text: text,
502
}],
503
eol: this._buffer.getEOL(),
504
isEolChange: isEolChange,
505
versionId: this.getVersionId(),
506
isUndoing: isUndoing,
507
isRedoing: isRedoing,
508
isFlush: isFlush,
509
detailedReasons: [reason],
510
detailedReasonsChangeLengths: [1],
511
};
512
}
513
514
private _setValueFromTextBuffer(textBuffer: model.ITextBuffer, textBufferDisposable: IDisposable, reason: TextModelEditSource): void {
515
this._assertNotDisposed();
516
const oldFullModelRange = this.getFullModelRange();
517
const oldModelValueLength = this.getValueLengthInRange(oldFullModelRange);
518
const endLineNumber = this.getLineCount();
519
const endColumn = this.getLineMaxColumn(endLineNumber);
520
521
this._buffer = textBuffer;
522
this._bufferDisposable.dispose();
523
this._bufferDisposable = textBufferDisposable;
524
this._increaseVersionId();
525
526
// Destroy all my decorations
527
this._decorations = Object.create(null);
528
this._decorationsTree = new DecorationsTrees();
529
530
// Destroy my edit history and settings
531
this._commandManager.clear();
532
this._trimAutoWhitespaceLines = null;
533
534
this._emitContentChangedEvent(
535
new ModelRawContentChangedEvent(
536
[
537
new ModelRawFlush()
538
],
539
this._versionId,
540
false,
541
false
542
),
543
this._createContentChanged2(new Range(1, 1, endLineNumber, endColumn), 0, oldModelValueLength, new Position(endLineNumber, endColumn), this.getValue(), false, false, true, false, reason)
544
);
545
}
546
547
public setEOL(eol: model.EndOfLineSequence): void {
548
this._assertNotDisposed();
549
const newEOL = (eol === model.EndOfLineSequence.CRLF ? '\r\n' : '\n');
550
if (this._buffer.getEOL() === newEOL) {
551
// Nothing to do
552
return;
553
}
554
555
const oldFullModelRange = this.getFullModelRange();
556
const oldModelValueLength = this.getValueLengthInRange(oldFullModelRange);
557
const endLineNumber = this.getLineCount();
558
const endColumn = this.getLineMaxColumn(endLineNumber);
559
560
this._onBeforeEOLChange();
561
this._buffer.setEOL(newEOL);
562
this._increaseVersionId();
563
this._onAfterEOLChange();
564
565
this._emitContentChangedEvent(
566
new ModelRawContentChangedEvent(
567
[
568
new ModelRawEOLChanged()
569
],
570
this._versionId,
571
false,
572
false
573
),
574
this._createContentChanged2(new Range(1, 1, endLineNumber, endColumn), 0, oldModelValueLength, new Position(endLineNumber, endColumn), this.getValue(), false, false, false, true, EditSources.eolChange())
575
);
576
}
577
578
private _onBeforeEOLChange(): void {
579
// Ensure all decorations get their `range` set.
580
this._decorationsTree.ensureAllNodesHaveRanges(this);
581
}
582
583
private _onAfterEOLChange(): void {
584
// Transform back `range` to offsets
585
const versionId = this.getVersionId();
586
const allDecorations = this._decorationsTree.collectNodesPostOrder();
587
for (let i = 0, len = allDecorations.length; i < len; i++) {
588
const node = allDecorations[i];
589
const range = node.range!; // the range is defined due to `_onBeforeEOLChange`
590
591
const delta = node.cachedAbsoluteStart - node.start;
592
593
const startOffset = this._buffer.getOffsetAt(range.startLineNumber, range.startColumn);
594
const endOffset = this._buffer.getOffsetAt(range.endLineNumber, range.endColumn);
595
596
node.cachedAbsoluteStart = startOffset;
597
node.cachedAbsoluteEnd = endOffset;
598
node.cachedVersionId = versionId;
599
600
node.start = startOffset - delta;
601
node.end = endOffset - delta;
602
603
recomputeMaxEnd(node);
604
}
605
}
606
607
public onBeforeAttached(): model.IAttachedView {
608
this._attachedEditorCount++;
609
if (this._attachedEditorCount === 1) {
610
this._tokenizationTextModelPart.handleDidChangeAttached();
611
this._onDidChangeAttached.fire(undefined);
612
}
613
return this._attachedViews.attachView();
614
}
615
616
public onBeforeDetached(view: model.IAttachedView): void {
617
this._attachedEditorCount--;
618
if (this._attachedEditorCount === 0) {
619
this._tokenizationTextModelPart.handleDidChangeAttached();
620
this._onDidChangeAttached.fire(undefined);
621
}
622
this._attachedViews.detachView(view);
623
}
624
625
public isAttachedToEditor(): boolean {
626
return this._attachedEditorCount > 0;
627
}
628
629
public getAttachedEditorCount(): number {
630
return this._attachedEditorCount;
631
}
632
633
public isTooLargeForSyncing(): boolean {
634
return this._isTooLargeForSyncing;
635
}
636
637
public isTooLargeForTokenization(): boolean {
638
return this._isTooLargeForTokenization;
639
}
640
641
public isTooLargeForHeapOperation(): boolean {
642
return this._isTooLargeForHeapOperation;
643
}
644
645
public isDisposed(): boolean {
646
return this._isDisposed;
647
}
648
649
public isDominatedByLongLines(): boolean {
650
this._assertNotDisposed();
651
if (this.isTooLargeForTokenization()) {
652
// Cannot word wrap huge files anyways, so it doesn't really matter
653
return false;
654
}
655
let smallLineCharCount = 0;
656
let longLineCharCount = 0;
657
658
const lineCount = this._buffer.getLineCount();
659
for (let lineNumber = 1; lineNumber <= lineCount; lineNumber++) {
660
const lineLength = this._buffer.getLineLength(lineNumber);
661
if (lineLength >= LONG_LINE_BOUNDARY) {
662
longLineCharCount += lineLength;
663
} else {
664
smallLineCharCount += lineLength;
665
}
666
}
667
668
return (longLineCharCount > smallLineCharCount);
669
}
670
671
public get uri(): URI {
672
return this._associatedResource;
673
}
674
675
//#region Options
676
677
public getOptions(): model.TextModelResolvedOptions {
678
this._assertNotDisposed();
679
return this._options;
680
}
681
682
public getFormattingOptions(): FormattingOptions {
683
return {
684
tabSize: this._options.indentSize,
685
insertSpaces: this._options.insertSpaces
686
};
687
}
688
689
public updateOptions(_newOpts: model.ITextModelUpdateOptions): void {
690
this._assertNotDisposed();
691
const tabSize = (typeof _newOpts.tabSize !== 'undefined') ? _newOpts.tabSize : this._options.tabSize;
692
const indentSize = (typeof _newOpts.indentSize !== 'undefined') ? _newOpts.indentSize : this._options.originalIndentSize;
693
const insertSpaces = (typeof _newOpts.insertSpaces !== 'undefined') ? _newOpts.insertSpaces : this._options.insertSpaces;
694
const trimAutoWhitespace = (typeof _newOpts.trimAutoWhitespace !== 'undefined') ? _newOpts.trimAutoWhitespace : this._options.trimAutoWhitespace;
695
const bracketPairColorizationOptions = (typeof _newOpts.bracketColorizationOptions !== 'undefined') ? _newOpts.bracketColorizationOptions : this._options.bracketPairColorizationOptions;
696
697
const newOpts = new model.TextModelResolvedOptions({
698
tabSize: tabSize,
699
indentSize: indentSize,
700
insertSpaces: insertSpaces,
701
defaultEOL: this._options.defaultEOL,
702
trimAutoWhitespace: trimAutoWhitespace,
703
bracketPairColorizationOptions,
704
});
705
706
if (this._options.equals(newOpts)) {
707
return;
708
}
709
710
const e = this._options.createChangeEvent(newOpts);
711
this._options = newOpts;
712
713
this._bracketPairs.handleDidChangeOptions(e);
714
this._decorationProvider.handleDidChangeOptions(e);
715
this._onDidChangeOptions.fire(e);
716
}
717
718
public detectIndentation(defaultInsertSpaces: boolean, defaultTabSize: number): void {
719
this._assertNotDisposed();
720
const guessedIndentation = guessIndentation(this._buffer, defaultTabSize, defaultInsertSpaces);
721
this.updateOptions({
722
insertSpaces: guessedIndentation.insertSpaces,
723
tabSize: guessedIndentation.tabSize,
724
indentSize: guessedIndentation.tabSize, // TODO@Alex: guess indentSize independent of tabSize
725
});
726
}
727
728
public normalizeIndentation(str: string): string {
729
this._assertNotDisposed();
730
return normalizeIndentation(str, this._options.indentSize, this._options.insertSpaces);
731
}
732
733
//#endregion
734
735
//#region Reading
736
737
public getVersionId(): number {
738
this._assertNotDisposed();
739
return this._versionId;
740
}
741
742
public mightContainRTL(): boolean {
743
return this._buffer.mightContainRTL();
744
}
745
746
public mightContainUnusualLineTerminators(): boolean {
747
return this._buffer.mightContainUnusualLineTerminators();
748
}
749
750
public removeUnusualLineTerminators(selections: Selection[] | null = null): void {
751
const matches = this.findMatches(strings.UNUSUAL_LINE_TERMINATORS.source, false, true, false, null, false, Constants.MAX_SAFE_SMALL_INTEGER);
752
this._buffer.resetMightContainUnusualLineTerminators();
753
this.pushEditOperations(selections, matches.map(m => ({ range: m.range, text: null })), () => null);
754
}
755
756
public mightContainNonBasicASCII(): boolean {
757
return this._buffer.mightContainNonBasicASCII();
758
}
759
760
public getAlternativeVersionId(): number {
761
this._assertNotDisposed();
762
return this._alternativeVersionId;
763
}
764
765
public getInitialUndoRedoSnapshot(): ResourceEditStackSnapshot | null {
766
this._assertNotDisposed();
767
return this._initialUndoRedoSnapshot;
768
}
769
770
public getOffsetAt(rawPosition: IPosition): number {
771
this._assertNotDisposed();
772
const position = this._validatePosition(rawPosition.lineNumber, rawPosition.column, StringOffsetValidationType.Relaxed);
773
return this._buffer.getOffsetAt(position.lineNumber, position.column);
774
}
775
776
public getPositionAt(rawOffset: number): Position {
777
this._assertNotDisposed();
778
const offset = (Math.min(this._buffer.getLength(), Math.max(0, rawOffset)));
779
return this._buffer.getPositionAt(offset);
780
}
781
782
private _increaseVersionId(): void {
783
this._versionId = this._versionId + 1;
784
this._alternativeVersionId = this._versionId;
785
}
786
787
public _overwriteVersionId(versionId: number): void {
788
this._versionId = versionId;
789
}
790
791
public _overwriteAlternativeVersionId(newAlternativeVersionId: number): void {
792
this._alternativeVersionId = newAlternativeVersionId;
793
}
794
795
public _overwriteInitialUndoRedoSnapshot(newInitialUndoRedoSnapshot: ResourceEditStackSnapshot | null): void {
796
this._initialUndoRedoSnapshot = newInitialUndoRedoSnapshot;
797
}
798
799
public getValue(eol?: model.EndOfLinePreference, preserveBOM: boolean = false): string {
800
this._assertNotDisposed();
801
if (this.isTooLargeForHeapOperation()) {
802
throw new BugIndicatingError('Operation would exceed heap memory limits');
803
}
804
805
const fullModelRange = this.getFullModelRange();
806
const fullModelValue = this.getValueInRange(fullModelRange, eol);
807
808
if (preserveBOM) {
809
return this._buffer.getBOM() + fullModelValue;
810
}
811
812
return fullModelValue;
813
}
814
815
public createSnapshot(preserveBOM: boolean = false): model.ITextSnapshot {
816
return new TextModelSnapshot(this._buffer.createSnapshot(preserveBOM));
817
}
818
819
public getValueLength(eol?: model.EndOfLinePreference, preserveBOM: boolean = false): number {
820
this._assertNotDisposed();
821
const fullModelRange = this.getFullModelRange();
822
const fullModelValue = this.getValueLengthInRange(fullModelRange, eol);
823
824
if (preserveBOM) {
825
return this._buffer.getBOM().length + fullModelValue;
826
}
827
828
return fullModelValue;
829
}
830
831
public getValueInRange(rawRange: IRange, eol: model.EndOfLinePreference = model.EndOfLinePreference.TextDefined): string {
832
this._assertNotDisposed();
833
return this._buffer.getValueInRange(this.validateRange(rawRange), eol);
834
}
835
836
public getValueLengthInRange(rawRange: IRange, eol: model.EndOfLinePreference = model.EndOfLinePreference.TextDefined): number {
837
this._assertNotDisposed();
838
return this._buffer.getValueLengthInRange(this.validateRange(rawRange), eol);
839
}
840
841
public getCharacterCountInRange(rawRange: IRange, eol: model.EndOfLinePreference = model.EndOfLinePreference.TextDefined): number {
842
this._assertNotDisposed();
843
return this._buffer.getCharacterCountInRange(this.validateRange(rawRange), eol);
844
}
845
846
public getLineCount(): number {
847
this._assertNotDisposed();
848
return this._buffer.getLineCount();
849
}
850
851
public getLineContent(lineNumber: number): string {
852
this._assertNotDisposed();
853
if (lineNumber < 1 || lineNumber > this.getLineCount()) {
854
throw new BugIndicatingError('Illegal value for lineNumber');
855
}
856
857
return this._buffer.getLineContent(lineNumber);
858
}
859
860
public getLineLength(lineNumber: number): number {
861
this._assertNotDisposed();
862
if (lineNumber < 1 || lineNumber > this.getLineCount()) {
863
throw new BugIndicatingError('Illegal value for lineNumber');
864
}
865
866
return this._buffer.getLineLength(lineNumber);
867
}
868
869
public getLinesContent(): string[] {
870
this._assertNotDisposed();
871
if (this.isTooLargeForHeapOperation()) {
872
throw new BugIndicatingError('Operation would exceed heap memory limits');
873
}
874
875
return this._buffer.getLinesContent();
876
}
877
878
public getEOL(): string {
879
this._assertNotDisposed();
880
return this._buffer.getEOL();
881
}
882
883
public getEndOfLineSequence(): model.EndOfLineSequence {
884
this._assertNotDisposed();
885
return (
886
this._buffer.getEOL() === '\n'
887
? model.EndOfLineSequence.LF
888
: model.EndOfLineSequence.CRLF
889
);
890
}
891
892
public getLineMinColumn(lineNumber: number): number {
893
this._assertNotDisposed();
894
return 1;
895
}
896
897
public getLineMaxColumn(lineNumber: number): number {
898
this._assertNotDisposed();
899
if (lineNumber < 1 || lineNumber > this.getLineCount()) {
900
throw new BugIndicatingError('Illegal value for lineNumber');
901
}
902
return this._buffer.getLineLength(lineNumber) + 1;
903
}
904
905
public getLineFirstNonWhitespaceColumn(lineNumber: number): number {
906
this._assertNotDisposed();
907
if (lineNumber < 1 || lineNumber > this.getLineCount()) {
908
throw new BugIndicatingError('Illegal value for lineNumber');
909
}
910
return this._buffer.getLineFirstNonWhitespaceColumn(lineNumber);
911
}
912
913
public getLineLastNonWhitespaceColumn(lineNumber: number): number {
914
this._assertNotDisposed();
915
if (lineNumber < 1 || lineNumber > this.getLineCount()) {
916
throw new BugIndicatingError('Illegal value for lineNumber');
917
}
918
return this._buffer.getLineLastNonWhitespaceColumn(lineNumber);
919
}
920
921
/**
922
* Validates `range` is within buffer bounds, but allows it to sit in between surrogate pairs, etc.
923
* Will try to not allocate if possible.
924
*/
925
public _validateRangeRelaxedNoAllocations(range: IRange): Range {
926
const linesCount = this._buffer.getLineCount();
927
928
const initialStartLineNumber = range.startLineNumber;
929
const initialStartColumn = range.startColumn;
930
let startLineNumber = Math.floor((typeof initialStartLineNumber === 'number' && !isNaN(initialStartLineNumber)) ? initialStartLineNumber : 1);
931
let startColumn = Math.floor((typeof initialStartColumn === 'number' && !isNaN(initialStartColumn)) ? initialStartColumn : 1);
932
933
if (startLineNumber < 1) {
934
startLineNumber = 1;
935
startColumn = 1;
936
} else if (startLineNumber > linesCount) {
937
startLineNumber = linesCount;
938
startColumn = this.getLineMaxColumn(startLineNumber);
939
} else {
940
if (startColumn <= 1) {
941
startColumn = 1;
942
} else {
943
const maxColumn = this.getLineMaxColumn(startLineNumber);
944
if (startColumn >= maxColumn) {
945
startColumn = maxColumn;
946
}
947
}
948
}
949
950
const initialEndLineNumber = range.endLineNumber;
951
const initialEndColumn = range.endColumn;
952
let endLineNumber = Math.floor((typeof initialEndLineNumber === 'number' && !isNaN(initialEndLineNumber)) ? initialEndLineNumber : 1);
953
let endColumn = Math.floor((typeof initialEndColumn === 'number' && !isNaN(initialEndColumn)) ? initialEndColumn : 1);
954
955
if (endLineNumber < 1) {
956
endLineNumber = 1;
957
endColumn = 1;
958
} else if (endLineNumber > linesCount) {
959
endLineNumber = linesCount;
960
endColumn = this.getLineMaxColumn(endLineNumber);
961
} else {
962
if (endColumn <= 1) {
963
endColumn = 1;
964
} else {
965
const maxColumn = this.getLineMaxColumn(endLineNumber);
966
if (endColumn >= maxColumn) {
967
endColumn = maxColumn;
968
}
969
}
970
}
971
972
if (
973
initialStartLineNumber === startLineNumber
974
&& initialStartColumn === startColumn
975
&& initialEndLineNumber === endLineNumber
976
&& initialEndColumn === endColumn
977
&& range instanceof Range
978
&& !(range instanceof Selection)
979
) {
980
return range;
981
}
982
983
return new Range(startLineNumber, startColumn, endLineNumber, endColumn);
984
}
985
986
private _isValidPosition(lineNumber: number, column: number, validationType: StringOffsetValidationType): boolean {
987
if (typeof lineNumber !== 'number' || typeof column !== 'number') {
988
return false;
989
}
990
991
if (isNaN(lineNumber) || isNaN(column)) {
992
return false;
993
}
994
995
if (lineNumber < 1 || column < 1) {
996
return false;
997
}
998
999
if ((lineNumber | 0) !== lineNumber || (column | 0) !== column) {
1000
return false;
1001
}
1002
1003
const lineCount = this._buffer.getLineCount();
1004
if (lineNumber > lineCount) {
1005
return false;
1006
}
1007
1008
if (column === 1) {
1009
return true;
1010
}
1011
1012
const maxColumn = this.getLineMaxColumn(lineNumber);
1013
if (column > maxColumn) {
1014
return false;
1015
}
1016
1017
if (validationType === StringOffsetValidationType.SurrogatePairs) {
1018
// !!At this point, column > 1
1019
const charCodeBefore = this._buffer.getLineCharCode(lineNumber, column - 2);
1020
if (strings.isHighSurrogate(charCodeBefore)) {
1021
return false;
1022
}
1023
}
1024
1025
return true;
1026
}
1027
1028
private _validatePosition(_lineNumber: number, _column: number, validationType: StringOffsetValidationType): Position {
1029
const lineNumber = Math.floor((typeof _lineNumber === 'number' && !isNaN(_lineNumber)) ? _lineNumber : 1);
1030
const column = Math.floor((typeof _column === 'number' && !isNaN(_column)) ? _column : 1);
1031
const lineCount = this._buffer.getLineCount();
1032
1033
if (lineNumber < 1) {
1034
return new Position(1, 1);
1035
}
1036
1037
if (lineNumber > lineCount) {
1038
return new Position(lineCount, this.getLineMaxColumn(lineCount));
1039
}
1040
1041
if (column <= 1) {
1042
return new Position(lineNumber, 1);
1043
}
1044
1045
const maxColumn = this.getLineMaxColumn(lineNumber);
1046
if (column >= maxColumn) {
1047
return new Position(lineNumber, maxColumn);
1048
}
1049
1050
if (validationType === StringOffsetValidationType.SurrogatePairs) {
1051
// If the position would end up in the middle of a high-low surrogate pair,
1052
// we move it to before the pair
1053
// !!At this point, column > 1
1054
const charCodeBefore = this._buffer.getLineCharCode(lineNumber, column - 2);
1055
if (strings.isHighSurrogate(charCodeBefore)) {
1056
return new Position(lineNumber, column - 1);
1057
}
1058
}
1059
1060
return new Position(lineNumber, column);
1061
}
1062
1063
public validatePosition(position: IPosition): Position {
1064
const validationType = StringOffsetValidationType.SurrogatePairs;
1065
this._assertNotDisposed();
1066
1067
// Avoid object allocation and cover most likely case
1068
if (position instanceof Position) {
1069
if (this._isValidPosition(position.lineNumber, position.column, validationType)) {
1070
return position;
1071
}
1072
}
1073
1074
return this._validatePosition(position.lineNumber, position.column, validationType);
1075
}
1076
1077
public isValidRange(range: Range): boolean {
1078
return this._isValidRange(range, StringOffsetValidationType.SurrogatePairs);
1079
}
1080
1081
private _isValidRange(range: Range, validationType: StringOffsetValidationType): boolean {
1082
const startLineNumber = range.startLineNumber;
1083
const startColumn = range.startColumn;
1084
const endLineNumber = range.endLineNumber;
1085
const endColumn = range.endColumn;
1086
1087
if (!this._isValidPosition(startLineNumber, startColumn, StringOffsetValidationType.Relaxed)) {
1088
return false;
1089
}
1090
if (!this._isValidPosition(endLineNumber, endColumn, StringOffsetValidationType.Relaxed)) {
1091
return false;
1092
}
1093
1094
if (validationType === StringOffsetValidationType.SurrogatePairs) {
1095
const charCodeBeforeStart = (startColumn > 1 ? this._buffer.getLineCharCode(startLineNumber, startColumn - 2) : 0);
1096
const charCodeBeforeEnd = (endColumn > 1 && endColumn <= this._buffer.getLineLength(endLineNumber) ? this._buffer.getLineCharCode(endLineNumber, endColumn - 2) : 0);
1097
1098
const startInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeStart);
1099
const endInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeEnd);
1100
1101
if (!startInsideSurrogatePair && !endInsideSurrogatePair) {
1102
return true;
1103
}
1104
return false;
1105
}
1106
1107
return true;
1108
}
1109
1110
public validateRange(_range: IRange): Range {
1111
const validationType = StringOffsetValidationType.SurrogatePairs;
1112
this._assertNotDisposed();
1113
1114
// Avoid object allocation and cover most likely case
1115
if ((_range instanceof Range) && !(_range instanceof Selection)) {
1116
if (this._isValidRange(_range, validationType)) {
1117
return _range;
1118
}
1119
}
1120
1121
const start = this._validatePosition(_range.startLineNumber, _range.startColumn, StringOffsetValidationType.Relaxed);
1122
const end = this._validatePosition(_range.endLineNumber, _range.endColumn, StringOffsetValidationType.Relaxed);
1123
1124
const startLineNumber = start.lineNumber;
1125
const startColumn = start.column;
1126
const endLineNumber = end.lineNumber;
1127
const endColumn = end.column;
1128
1129
if (validationType === StringOffsetValidationType.SurrogatePairs) {
1130
const charCodeBeforeStart = (startColumn > 1 ? this._buffer.getLineCharCode(startLineNumber, startColumn - 2) : 0);
1131
const charCodeBeforeEnd = (endColumn > 1 && endColumn <= this._buffer.getLineLength(endLineNumber) ? this._buffer.getLineCharCode(endLineNumber, endColumn - 2) : 0);
1132
1133
const startInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeStart);
1134
const endInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeEnd);
1135
1136
if (!startInsideSurrogatePair && !endInsideSurrogatePair) {
1137
return new Range(startLineNumber, startColumn, endLineNumber, endColumn);
1138
}
1139
1140
if (startLineNumber === endLineNumber && startColumn === endColumn) {
1141
// do not expand a collapsed range, simply move it to a valid location
1142
return new Range(startLineNumber, startColumn - 1, endLineNumber, endColumn - 1);
1143
}
1144
1145
if (startInsideSurrogatePair && endInsideSurrogatePair) {
1146
// expand range at both ends
1147
return new Range(startLineNumber, startColumn - 1, endLineNumber, endColumn + 1);
1148
}
1149
1150
if (startInsideSurrogatePair) {
1151
// only expand range at the start
1152
return new Range(startLineNumber, startColumn - 1, endLineNumber, endColumn);
1153
}
1154
1155
// only expand range at the end
1156
return new Range(startLineNumber, startColumn, endLineNumber, endColumn + 1);
1157
}
1158
1159
return new Range(startLineNumber, startColumn, endLineNumber, endColumn);
1160
}
1161
1162
public modifyPosition(rawPosition: IPosition, offset: number): Position {
1163
this._assertNotDisposed();
1164
const candidate = this.getOffsetAt(rawPosition) + offset;
1165
return this.getPositionAt(Math.min(this._buffer.getLength(), Math.max(0, candidate)));
1166
}
1167
1168
public getFullModelRange(): Range {
1169
this._assertNotDisposed();
1170
const lineCount = this.getLineCount();
1171
return new Range(1, 1, lineCount, this.getLineMaxColumn(lineCount));
1172
}
1173
1174
private findMatchesLineByLine(searchRange: Range, searchData: model.SearchData, captureMatches: boolean, limitResultCount: number): model.FindMatch[] {
1175
return this._buffer.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount);
1176
}
1177
1178
public findMatches(searchString: string, rawSearchScope: boolean | IRange | IRange[] | null, isRegex: boolean, matchCase: boolean, wordSeparators: string | null, captureMatches: boolean, limitResultCount: number = LIMIT_FIND_COUNT): model.FindMatch[] {
1179
this._assertNotDisposed();
1180
1181
let searchRanges: Range[] | null = null;
1182
1183
if (rawSearchScope !== null && typeof rawSearchScope !== 'boolean') {
1184
if (!Array.isArray(rawSearchScope)) {
1185
rawSearchScope = [rawSearchScope];
1186
}
1187
1188
if (rawSearchScope.every((searchScope: IRange) => Range.isIRange(searchScope))) {
1189
searchRanges = rawSearchScope.map((searchScope: IRange) => this.validateRange(searchScope));
1190
}
1191
}
1192
1193
if (searchRanges === null) {
1194
searchRanges = [this.getFullModelRange()];
1195
}
1196
1197
searchRanges = searchRanges.sort((d1, d2) => d1.startLineNumber - d2.startLineNumber || d1.startColumn - d2.startColumn);
1198
1199
const uniqueSearchRanges: Range[] = [];
1200
uniqueSearchRanges.push(searchRanges.reduce((prev, curr) => {
1201
if (Range.areIntersecting(prev, curr)) {
1202
return prev.plusRange(curr);
1203
}
1204
1205
uniqueSearchRanges.push(prev);
1206
return curr;
1207
}));
1208
1209
let matchMapper: (value: Range, index: number, array: Range[]) => model.FindMatch[];
1210
if (!isRegex && searchString.indexOf('\n') < 0) {
1211
// not regex, not multi line
1212
const searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators);
1213
const searchData = searchParams.parseSearchRequest();
1214
1215
if (!searchData) {
1216
return [];
1217
}
1218
1219
matchMapper = (searchRange: Range) => this.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount);
1220
} else {
1221
matchMapper = (searchRange: Range) => TextModelSearch.findMatches(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchRange, captureMatches, limitResultCount);
1222
}
1223
1224
return uniqueSearchRanges.map(matchMapper).reduce((arr, matches: model.FindMatch[]) => arr.concat(matches), []);
1225
}
1226
1227
public findNextMatch(searchString: string, rawSearchStart: IPosition, isRegex: boolean, matchCase: boolean, wordSeparators: string, captureMatches: boolean): model.FindMatch | null {
1228
this._assertNotDisposed();
1229
const searchStart = this.validatePosition(rawSearchStart);
1230
1231
if (!isRegex && searchString.indexOf('\n') < 0) {
1232
const searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators);
1233
const searchData = searchParams.parseSearchRequest();
1234
if (!searchData) {
1235
return null;
1236
}
1237
1238
const lineCount = this.getLineCount();
1239
let searchRange = new Range(searchStart.lineNumber, searchStart.column, lineCount, this.getLineMaxColumn(lineCount));
1240
let ret = this.findMatchesLineByLine(searchRange, searchData, captureMatches, 1);
1241
TextModelSearch.findNextMatch(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchStart, captureMatches);
1242
if (ret.length > 0) {
1243
return ret[0];
1244
}
1245
1246
searchRange = new Range(1, 1, searchStart.lineNumber, this.getLineMaxColumn(searchStart.lineNumber));
1247
ret = this.findMatchesLineByLine(searchRange, searchData, captureMatches, 1);
1248
1249
if (ret.length > 0) {
1250
return ret[0];
1251
}
1252
1253
return null;
1254
}
1255
1256
return TextModelSearch.findNextMatch(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchStart, captureMatches);
1257
}
1258
1259
public findPreviousMatch(searchString: string, rawSearchStart: IPosition, isRegex: boolean, matchCase: boolean, wordSeparators: string, captureMatches: boolean): model.FindMatch | null {
1260
this._assertNotDisposed();
1261
const searchStart = this.validatePosition(rawSearchStart);
1262
return TextModelSearch.findPreviousMatch(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchStart, captureMatches);
1263
}
1264
1265
//#endregion
1266
1267
//#region Editing
1268
1269
public pushStackElement(): void {
1270
this._commandManager.pushStackElement();
1271
}
1272
1273
public popStackElement(): void {
1274
this._commandManager.popStackElement();
1275
}
1276
1277
public pushEOL(eol: model.EndOfLineSequence): void {
1278
const currentEOL = (this.getEOL() === '\n' ? model.EndOfLineSequence.LF : model.EndOfLineSequence.CRLF);
1279
if (currentEOL === eol) {
1280
return;
1281
}
1282
try {
1283
this._onDidChangeDecorations.beginDeferredEmit();
1284
this._eventEmitter.beginDeferredEmit();
1285
if (this._initialUndoRedoSnapshot === null) {
1286
this._initialUndoRedoSnapshot = this._undoRedoService.createSnapshot(this.uri);
1287
}
1288
this._commandManager.pushEOL(eol);
1289
} finally {
1290
this._eventEmitter.endDeferredEmit();
1291
this._onDidChangeDecorations.endDeferredEmit();
1292
}
1293
}
1294
1295
private _validateEditOperation(rawOperation: model.IIdentifiedSingleEditOperation): model.ValidAnnotatedEditOperation {
1296
if (rawOperation instanceof model.ValidAnnotatedEditOperation) {
1297
return rawOperation;
1298
}
1299
1300
const validatedRange = this.validateRange(rawOperation.range);
1301
1302
// Normalize edit when replacement text ends with lone CR
1303
// and the range ends right before a CRLF in the buffer.
1304
// We strip the trailing CR from the replacement text.
1305
let opText = rawOperation.text;
1306
if (opText) {
1307
const endsWithLoneCR = (
1308
opText.length > 0 && opText.charCodeAt(opText.length - 1) === CharCode.CarriageReturn
1309
);
1310
const removeTrailingCR = (
1311
this.getEOL() === '\r\n' && endsWithLoneCR && validatedRange.endColumn === this.getLineMaxColumn(validatedRange.endLineNumber)
1312
);
1313
if (removeTrailingCR) {
1314
opText = opText.substring(0, opText.length - 1);
1315
}
1316
}
1317
1318
return new model.ValidAnnotatedEditOperation(
1319
rawOperation.identifier || null,
1320
validatedRange,
1321
opText,
1322
rawOperation.forceMoveMarkers || false,
1323
rawOperation.isAutoWhitespaceEdit || false,
1324
rawOperation._isTracked || false
1325
);
1326
}
1327
1328
private _validateEditOperations(rawOperations: readonly model.IIdentifiedSingleEditOperation[]): model.ValidAnnotatedEditOperation[] {
1329
const result: model.ValidAnnotatedEditOperation[] = [];
1330
for (let i = 0, len = rawOperations.length; i < len; i++) {
1331
result[i] = this._validateEditOperation(rawOperations[i]);
1332
}
1333
return result;
1334
}
1335
1336
public edit(edit: TextEdit, options?: { reason?: TextModelEditSource }): void {
1337
this.pushEditOperations(null, edit.replacements.map(r => ({ range: r.range, text: r.text })), null);
1338
}
1339
1340
public pushEditOperations(beforeCursorState: Selection[] | null, editOperations: model.IIdentifiedSingleEditOperation[], cursorStateComputer: model.ICursorStateComputer | null, group?: UndoRedoGroup, reason?: TextModelEditSource): Selection[] | null {
1341
try {
1342
this._onDidChangeDecorations.beginDeferredEmit();
1343
this._eventEmitter.beginDeferredEmit();
1344
return this._pushEditOperations(beforeCursorState, this._validateEditOperations(editOperations), cursorStateComputer, group, reason);
1345
} finally {
1346
this._eventEmitter.endDeferredEmit();
1347
this._onDidChangeDecorations.endDeferredEmit();
1348
}
1349
}
1350
1351
private _pushEditOperations(beforeCursorState: Selection[] | null, editOperations: model.ValidAnnotatedEditOperation[], cursorStateComputer: model.ICursorStateComputer | null, group?: UndoRedoGroup, reason?: TextModelEditSource): Selection[] | null {
1352
if (this._options.trimAutoWhitespace && this._trimAutoWhitespaceLines) {
1353
// Go through each saved line number and insert a trim whitespace edit
1354
// if it is safe to do so (no conflicts with other edits).
1355
1356
const incomingEdits = editOperations.map((op) => {
1357
return {
1358
range: this.validateRange(op.range),
1359
text: op.text
1360
};
1361
});
1362
1363
// Sometimes, auto-formatters change ranges automatically which can cause undesired auto whitespace trimming near the cursor
1364
// We'll use the following heuristic: if the edits occur near the cursor, then it's ok to trim auto whitespace
1365
let editsAreNearCursors = true;
1366
if (beforeCursorState) {
1367
for (let i = 0, len = beforeCursorState.length; i < len; i++) {
1368
const sel = beforeCursorState[i];
1369
let foundEditNearSel = false;
1370
for (let j = 0, lenJ = incomingEdits.length; j < lenJ; j++) {
1371
const editRange = incomingEdits[j].range;
1372
const selIsAbove = editRange.startLineNumber > sel.endLineNumber;
1373
const selIsBelow = sel.startLineNumber > editRange.endLineNumber;
1374
if (!selIsAbove && !selIsBelow) {
1375
foundEditNearSel = true;
1376
break;
1377
}
1378
}
1379
if (!foundEditNearSel) {
1380
editsAreNearCursors = false;
1381
break;
1382
}
1383
}
1384
}
1385
1386
if (editsAreNearCursors) {
1387
for (let i = 0, len = this._trimAutoWhitespaceLines.length; i < len; i++) {
1388
const trimLineNumber = this._trimAutoWhitespaceLines[i];
1389
const maxLineColumn = this.getLineMaxColumn(trimLineNumber);
1390
1391
let allowTrimLine = true;
1392
for (let j = 0, lenJ = incomingEdits.length; j < lenJ; j++) {
1393
const editRange = incomingEdits[j].range;
1394
const editText = incomingEdits[j].text;
1395
1396
if (trimLineNumber < editRange.startLineNumber || trimLineNumber > editRange.endLineNumber) {
1397
// `trimLine` is completely outside this edit
1398
continue;
1399
}
1400
1401
// At this point:
1402
// editRange.startLineNumber <= trimLine <= editRange.endLineNumber
1403
1404
if (
1405
trimLineNumber === editRange.startLineNumber && editRange.startColumn === maxLineColumn
1406
&& editRange.isEmpty() && editText && editText.length > 0 && editText.charAt(0) === '\n'
1407
) {
1408
// This edit inserts a new line (and maybe other text) after `trimLine`
1409
continue;
1410
}
1411
1412
if (
1413
trimLineNumber === editRange.startLineNumber && editRange.startColumn === 1
1414
&& editRange.isEmpty() && editText && editText.length > 0 && editText.charAt(editText.length - 1) === '\n'
1415
) {
1416
// This edit inserts a new line (and maybe other text) before `trimLine`
1417
continue;
1418
}
1419
1420
// Looks like we can't trim this line as it would interfere with an incoming edit
1421
allowTrimLine = false;
1422
break;
1423
}
1424
1425
if (allowTrimLine) {
1426
const trimRange = new Range(trimLineNumber, 1, trimLineNumber, maxLineColumn);
1427
editOperations.push(new model.ValidAnnotatedEditOperation(null, trimRange, null, false, false, false));
1428
}
1429
1430
}
1431
}
1432
1433
this._trimAutoWhitespaceLines = null;
1434
}
1435
if (this._initialUndoRedoSnapshot === null) {
1436
this._initialUndoRedoSnapshot = this._undoRedoService.createSnapshot(this.uri);
1437
}
1438
return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer, group, reason);
1439
}
1440
1441
_applyUndo(changes: TextChange[], eol: model.EndOfLineSequence, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void {
1442
const edits = changes.map<ISingleEditOperation>((change) => {
1443
const rangeStart = this.getPositionAt(change.newPosition);
1444
const rangeEnd = this.getPositionAt(change.newEnd);
1445
return {
1446
range: new Range(rangeStart.lineNumber, rangeStart.column, rangeEnd.lineNumber, rangeEnd.column),
1447
text: change.oldText
1448
};
1449
});
1450
this._applyUndoRedoEdits(edits, eol, true, false, resultingAlternativeVersionId, resultingSelection);
1451
}
1452
1453
_applyRedo(changes: TextChange[], eol: model.EndOfLineSequence, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void {
1454
const edits = changes.map<ISingleEditOperation>((change) => {
1455
const rangeStart = this.getPositionAt(change.oldPosition);
1456
const rangeEnd = this.getPositionAt(change.oldEnd);
1457
return {
1458
range: new Range(rangeStart.lineNumber, rangeStart.column, rangeEnd.lineNumber, rangeEnd.column),
1459
text: change.newText
1460
};
1461
});
1462
this._applyUndoRedoEdits(edits, eol, false, true, resultingAlternativeVersionId, resultingSelection);
1463
}
1464
1465
private _applyUndoRedoEdits(edits: ISingleEditOperation[], eol: model.EndOfLineSequence, isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void {
1466
try {
1467
this._onDidChangeDecorations.beginDeferredEmit();
1468
this._eventEmitter.beginDeferredEmit();
1469
this._isUndoing = isUndoing;
1470
this._isRedoing = isRedoing;
1471
const operations = this._validateEditOperations(edits);
1472
this._doApplyEdits(operations, false, EditSources.applyEdits(), resultingSelection);
1473
this.setEOL(eol);
1474
this._overwriteAlternativeVersionId(resultingAlternativeVersionId);
1475
} finally {
1476
this._isUndoing = false;
1477
this._isRedoing = false;
1478
this._eventEmitter.endDeferredEmit(resultingSelection);
1479
this._onDidChangeDecorations.endDeferredEmit();
1480
}
1481
}
1482
1483
public applyEdits(operations: readonly model.IIdentifiedSingleEditOperation[]): void;
1484
public applyEdits(operations: readonly model.IIdentifiedSingleEditOperation[], computeUndoEdits: false): void;
1485
public applyEdits(operations: readonly model.IIdentifiedSingleEditOperation[], computeUndoEdits: true): model.IValidEditOperation[];
1486
/** @internal */
1487
public applyEdits(operations: readonly model.IIdentifiedSingleEditOperation[], computeUndoEdits: false, reason: TextModelEditSource): void;
1488
/** @internal */
1489
public applyEdits(operations: readonly model.IIdentifiedSingleEditOperation[], computeUndoEdits: true, reason: TextModelEditSource): model.IValidEditOperation[];
1490
public applyEdits(rawOperations: readonly model.IIdentifiedSingleEditOperation[], computeUndoEdits?: boolean, reason?: TextModelEditSource): void | model.IValidEditOperation[] {
1491
try {
1492
this._onDidChangeDecorations.beginDeferredEmit();
1493
this._eventEmitter.beginDeferredEmit();
1494
const operations = this._validateEditOperations(rawOperations);
1495
1496
return this._doApplyEdits(operations, computeUndoEdits ?? false, reason ?? EditSources.applyEdits());
1497
} finally {
1498
this._eventEmitter.endDeferredEmit();
1499
this._onDidChangeDecorations.endDeferredEmit();
1500
}
1501
}
1502
1503
private _doApplyEdits(rawOperations: model.ValidAnnotatedEditOperation[], computeUndoEdits: boolean, reason: TextModelEditSource, resultingSelection: Selection[] | null = null): void | model.IValidEditOperation[] {
1504
1505
const oldLineCount = this._buffer.getLineCount();
1506
const result = this._buffer.applyEdits(rawOperations, this._options.trimAutoWhitespace, computeUndoEdits);
1507
const newLineCount = this._buffer.getLineCount();
1508
1509
const contentChanges = result.changes;
1510
this._trimAutoWhitespaceLines = result.trimAutoWhitespaceLineNumbers;
1511
1512
if (contentChanges.length !== 0) {
1513
// We do a first pass to update decorations
1514
// because we want to read decorations in the second pass
1515
// where we will emit content change events
1516
// and we want to read the final decorations
1517
for (let i = 0, len = contentChanges.length; i < len; i++) {
1518
const change = contentChanges[i];
1519
this._decorationsTree.acceptReplace(change.rangeOffset, change.rangeLength, change.text.length, change.forceMoveMarkers);
1520
}
1521
1522
const rawContentChanges: ModelRawChange[] = [];
1523
1524
this._increaseVersionId();
1525
1526
let lineCount = oldLineCount;
1527
for (let i = 0, len = contentChanges.length; i < len; i++) {
1528
const change = contentChanges[i];
1529
const [eolCount] = countEOL(change.text);
1530
this._onDidChangeDecorations.fire();
1531
1532
const startLineNumber = change.range.startLineNumber;
1533
const endLineNumber = change.range.endLineNumber;
1534
1535
const deletingLinesCnt = endLineNumber - startLineNumber;
1536
const insertingLinesCnt = eolCount;
1537
const editingLinesCnt = Math.min(deletingLinesCnt, insertingLinesCnt);
1538
1539
const changeLineCountDelta = (insertingLinesCnt - deletingLinesCnt);
1540
1541
const currentEditStartLineNumber = newLineCount - lineCount - changeLineCountDelta + startLineNumber;
1542
const firstEditLineNumber = currentEditStartLineNumber;
1543
const lastInsertedLineNumber = currentEditStartLineNumber + insertingLinesCnt;
1544
1545
const decorationsWithInjectedTextInEditedRange = this._decorationsTree.getInjectedTextInInterval(
1546
this,
1547
this.getOffsetAt(new Position(firstEditLineNumber, 1)),
1548
this.getOffsetAt(new Position(lastInsertedLineNumber, this.getLineMaxColumn(lastInsertedLineNumber))),
1549
0
1550
);
1551
1552
1553
const injectedTextInEditedRange = LineInjectedText.fromDecorations(decorationsWithInjectedTextInEditedRange);
1554
const injectedTextInEditedRangeQueue = new ArrayQueue(injectedTextInEditedRange);
1555
1556
for (let j = editingLinesCnt; j >= 0; j--) {
1557
const editLineNumber = startLineNumber + j;
1558
const currentEditLineNumber = currentEditStartLineNumber + j;
1559
1560
injectedTextInEditedRangeQueue.takeFromEndWhile(r => r.lineNumber > currentEditLineNumber);
1561
const decorationsInCurrentLine = injectedTextInEditedRangeQueue.takeFromEndWhile(r => r.lineNumber === currentEditLineNumber);
1562
1563
rawContentChanges.push(
1564
new ModelRawLineChanged(
1565
editLineNumber,
1566
this.getLineContent(currentEditLineNumber),
1567
decorationsInCurrentLine
1568
));
1569
}
1570
1571
if (editingLinesCnt < deletingLinesCnt) {
1572
// Must delete some lines
1573
const spliceStartLineNumber = startLineNumber + editingLinesCnt;
1574
rawContentChanges.push(new ModelRawLinesDeleted(spliceStartLineNumber + 1, endLineNumber));
1575
}
1576
1577
if (editingLinesCnt < insertingLinesCnt) {
1578
const injectedTextInEditedRangeQueue = new ArrayQueue(injectedTextInEditedRange);
1579
// Must insert some lines
1580
const spliceLineNumber = startLineNumber + editingLinesCnt;
1581
const cnt = insertingLinesCnt - editingLinesCnt;
1582
const fromLineNumber = newLineCount - lineCount - cnt + spliceLineNumber + 1;
1583
const injectedTexts: (LineInjectedText[] | null)[] = [];
1584
const newLines: string[] = [];
1585
for (let i = 0; i < cnt; i++) {
1586
const lineNumber = fromLineNumber + i;
1587
newLines[i] = this.getLineContent(lineNumber);
1588
1589
injectedTextInEditedRangeQueue.takeWhile(r => r.lineNumber < lineNumber);
1590
injectedTexts[i] = injectedTextInEditedRangeQueue.takeWhile(r => r.lineNumber === lineNumber);
1591
}
1592
1593
rawContentChanges.push(
1594
new ModelRawLinesInserted(
1595
spliceLineNumber + 1,
1596
startLineNumber + insertingLinesCnt,
1597
newLines,
1598
injectedTexts
1599
)
1600
);
1601
}
1602
1603
lineCount += changeLineCountDelta;
1604
}
1605
1606
this._emitContentChangedEvent(
1607
new ModelRawContentChangedEvent(
1608
rawContentChanges,
1609
this.getVersionId(),
1610
this._isUndoing,
1611
this._isRedoing
1612
),
1613
{
1614
changes: contentChanges,
1615
eol: this._buffer.getEOL(),
1616
isEolChange: false,
1617
versionId: this.getVersionId(),
1618
isUndoing: this._isUndoing,
1619
isRedoing: this._isRedoing,
1620
isFlush: false,
1621
detailedReasons: [reason],
1622
detailedReasonsChangeLengths: [contentChanges.length],
1623
},
1624
resultingSelection
1625
);
1626
}
1627
1628
return (result.reverseEdits === null ? undefined : result.reverseEdits);
1629
}
1630
1631
public undo(): void | Promise<void> {
1632
return this._undoRedoService.undo(this.uri);
1633
}
1634
1635
public canUndo(): boolean {
1636
return this._undoRedoService.canUndo(this.uri);
1637
}
1638
1639
public redo(): void | Promise<void> {
1640
return this._undoRedoService.redo(this.uri);
1641
}
1642
1643
public canRedo(): boolean {
1644
return this._undoRedoService.canRedo(this.uri);
1645
}
1646
1647
//#endregion
1648
1649
//#region Decorations
1650
1651
private handleBeforeFireDecorationsChangedEvent(affectedInjectedTextLines: Set<number> | null, affectedLineHeights: Set<LineHeightChangingDecoration> | null, affectedFontLines: Set<LineFontChangingDecoration> | null): void {
1652
// This is called before the decoration changed event is fired.
1653
1654
if (affectedInjectedTextLines && affectedInjectedTextLines.size > 0) {
1655
const affectedLines = Array.from(affectedInjectedTextLines);
1656
const lineChangeEvents = affectedLines.map(lineNumber => new ModelRawLineChanged(lineNumber, this.getLineContent(lineNumber), this._getInjectedTextInLine(lineNumber)));
1657
this._onDidChangeContentOrInjectedText(new ModelInjectedTextChangedEvent(lineChangeEvents));
1658
}
1659
this._fireOnDidChangeLineHeight(affectedLineHeights);
1660
this._fireOnDidChangeFont(affectedFontLines);
1661
}
1662
1663
private _fireOnDidChangeLineHeight(affectedLineHeights: Set<LineHeightChangingDecoration> | null): void {
1664
if (affectedLineHeights && affectedLineHeights.size > 0) {
1665
const affectedLines = Array.from(affectedLineHeights);
1666
const lineHeightChangeEvent = affectedLines.map(specialLineHeightChange => new ModelLineHeightChanged(specialLineHeightChange.ownerId, specialLineHeightChange.decorationId, specialLineHeightChange.lineNumber, specialLineHeightChange.lineHeight));
1667
this._onDidChangeLineHeight.fire(new ModelLineHeightChangedEvent(lineHeightChangeEvent));
1668
}
1669
}
1670
1671
private _fireOnDidChangeFont(affectedFontLines: Set<LineFontChangingDecoration> | null): void {
1672
if (affectedFontLines && affectedFontLines.size > 0) {
1673
const affectedLines = Array.from(affectedFontLines);
1674
const fontChangeEvent = affectedLines.map(fontChange => new ModelFontChanged(fontChange.ownerId, fontChange.lineNumber));
1675
this._onDidChangeFont.fire(new ModelFontChangedEvent(fontChangeEvent));
1676
}
1677
}
1678
1679
private _onDidChangeContentOrInjectedText(e: InternalModelContentChangeEvent | ModelInjectedTextChangedEvent): void {
1680
for (const viewModel of this._viewModels) {
1681
viewModel.onDidChangeContentOrInjectedText(e);
1682
}
1683
for (const viewModel of this._viewModels) {
1684
viewModel.emitContentChangeEvent(e);
1685
}
1686
}
1687
1688
public changeDecorations<T>(callback: (changeAccessor: model.IModelDecorationsChangeAccessor) => T, ownerId: number = 0): T | null {
1689
this._assertNotDisposed();
1690
1691
try {
1692
this._onDidChangeDecorations.beginDeferredEmit();
1693
return this._changeDecorations(ownerId, callback);
1694
} finally {
1695
this._onDidChangeDecorations.endDeferredEmit();
1696
}
1697
}
1698
1699
private _changeDecorations<T>(ownerId: number, callback: (changeAccessor: model.IModelDecorationsChangeAccessor) => T): T | null {
1700
const changeAccessor: model.IModelDecorationsChangeAccessor = {
1701
addDecoration: (range: IRange, options: model.IModelDecorationOptions): string => {
1702
return this._deltaDecorationsImpl(ownerId, [], [{ range: range, options: options }])[0];
1703
},
1704
changeDecoration: (id: string, newRange: IRange): void => {
1705
this._changeDecorationImpl(ownerId, id, newRange);
1706
},
1707
changeDecorationOptions: (id: string, options: model.IModelDecorationOptions) => {
1708
this._changeDecorationOptionsImpl(ownerId, id, _normalizeOptions(options));
1709
},
1710
removeDecoration: (id: string): void => {
1711
this._deltaDecorationsImpl(ownerId, [id], []);
1712
},
1713
deltaDecorations: (oldDecorations: string[], newDecorations: model.IModelDeltaDecoration[]): string[] => {
1714
if (oldDecorations.length === 0 && newDecorations.length === 0) {
1715
// nothing to do
1716
return [];
1717
}
1718
return this._deltaDecorationsImpl(ownerId, oldDecorations, newDecorations);
1719
}
1720
};
1721
let result: T | null = null;
1722
try {
1723
result = callback(changeAccessor);
1724
} catch (e) {
1725
onUnexpectedError(e);
1726
}
1727
// Invalidate change accessor
1728
changeAccessor.addDecoration = invalidFunc;
1729
changeAccessor.changeDecoration = invalidFunc;
1730
changeAccessor.changeDecorationOptions = invalidFunc;
1731
changeAccessor.removeDecoration = invalidFunc;
1732
changeAccessor.deltaDecorations = invalidFunc;
1733
return result;
1734
}
1735
1736
public deltaDecorations(oldDecorations: string[], newDecorations: model.IModelDeltaDecoration[], ownerId: number = 0): string[] {
1737
this._assertNotDisposed();
1738
if (!oldDecorations) {
1739
oldDecorations = [];
1740
}
1741
if (oldDecorations.length === 0 && newDecorations.length === 0) {
1742
// nothing to do
1743
return [];
1744
}
1745
1746
try {
1747
this._deltaDecorationCallCnt++;
1748
if (this._deltaDecorationCallCnt > 1) {
1749
console.warn(`Invoking deltaDecorations recursively could lead to leaking decorations.`);
1750
onUnexpectedError(new Error(`Invoking deltaDecorations recursively could lead to leaking decorations.`));
1751
}
1752
this._onDidChangeDecorations.beginDeferredEmit();
1753
return this._deltaDecorationsImpl(ownerId, oldDecorations, newDecorations);
1754
} finally {
1755
this._onDidChangeDecorations.endDeferredEmit();
1756
this._deltaDecorationCallCnt--;
1757
}
1758
}
1759
1760
_getTrackedRange(id: string): Range | null {
1761
return this.getDecorationRange(id);
1762
}
1763
1764
_setTrackedRange(id: string | null, newRange: null, newStickiness: model.TrackedRangeStickiness): null;
1765
_setTrackedRange(id: string | null, newRange: Range, newStickiness: model.TrackedRangeStickiness): string;
1766
_setTrackedRange(id: string | null, newRange: Range | null, newStickiness: model.TrackedRangeStickiness): string | null {
1767
const node = (id ? this._decorations[id] : null);
1768
1769
if (!node) {
1770
if (!newRange) {
1771
// node doesn't exist, the request is to delete => nothing to do
1772
return null;
1773
}
1774
// node doesn't exist, the request is to set => add the tracked range
1775
return this._deltaDecorationsImpl(0, [], [{ range: newRange, options: TRACKED_RANGE_OPTIONS[newStickiness] }], true)[0];
1776
}
1777
1778
if (!newRange) {
1779
// node exists, the request is to delete => delete node
1780
this._decorationsTree.delete(node);
1781
delete this._decorations[node.id];
1782
return null;
1783
}
1784
1785
// node exists, the request is to set => change the tracked range and its options
1786
const range = this._validateRangeRelaxedNoAllocations(newRange);
1787
const startOffset = this._buffer.getOffsetAt(range.startLineNumber, range.startColumn);
1788
const endOffset = this._buffer.getOffsetAt(range.endLineNumber, range.endColumn);
1789
this._decorationsTree.delete(node);
1790
node.reset(this.getVersionId(), startOffset, endOffset, range);
1791
node.setOptions(TRACKED_RANGE_OPTIONS[newStickiness]);
1792
this._decorationsTree.insert(node);
1793
return node.id;
1794
}
1795
1796
public removeAllDecorationsWithOwnerId(ownerId: number): void {
1797
if (this._isDisposed) {
1798
return;
1799
}
1800
const nodes = this._decorationsTree.collectNodesFromOwner(ownerId);
1801
for (let i = 0, len = nodes.length; i < len; i++) {
1802
const node = nodes[i];
1803
1804
this._decorationsTree.delete(node);
1805
delete this._decorations[node.id];
1806
}
1807
}
1808
1809
public getDecorationOptions(decorationId: string): model.IModelDecorationOptions | null {
1810
const node = this._decorations[decorationId];
1811
if (!node) {
1812
return null;
1813
}
1814
return node.options;
1815
}
1816
1817
public getDecorationRange(decorationId: string): Range | null {
1818
const node = this._decorations[decorationId];
1819
if (!node) {
1820
return null;
1821
}
1822
return this._decorationsTree.getNodeRange(this, node);
1823
}
1824
1825
public getLineDecorations(lineNumber: number, ownerId: number = 0, filterOutValidation: boolean = false, filterFontDecorations: boolean = false): model.IModelDecoration[] {
1826
if (lineNumber < 1 || lineNumber > this.getLineCount()) {
1827
return [];
1828
}
1829
return this.getLinesDecorations(lineNumber, lineNumber, ownerId, filterOutValidation, filterFontDecorations);
1830
}
1831
1832
public getLinesDecorations(_startLineNumber: number, _endLineNumber: number, ownerId: number = 0, filterOutValidation: boolean = false, filterFontDecorations: boolean = false, onlyMarginDecorations: boolean = false): model.IModelDecoration[] {
1833
const lineCount = this.getLineCount();
1834
const startLineNumber = Math.min(lineCount, Math.max(1, _startLineNumber));
1835
const endLineNumber = Math.min(lineCount, Math.max(1, _endLineNumber));
1836
const endColumn = this.getLineMaxColumn(endLineNumber);
1837
const range = new Range(startLineNumber, 1, endLineNumber, endColumn);
1838
1839
const decorations = this._getDecorationsInRange(range, ownerId, filterOutValidation, filterFontDecorations, onlyMarginDecorations);
1840
pushMany(decorations, this._decorationProvider.getDecorationsInRange(range, ownerId, filterOutValidation, filterFontDecorations));
1841
pushMany(decorations, this._fontTokenDecorationsProvider.getDecorationsInRange(range, ownerId, filterOutValidation, filterFontDecorations));
1842
return decorations;
1843
}
1844
1845
public getDecorationsInRange(range: IRange, ownerId: number = 0, filterOutValidation: boolean = false, filterFontDecorations: boolean = false, onlyMinimapDecorations: boolean = false, onlyMarginDecorations: boolean = false): model.IModelDecoration[] {
1846
const validatedRange = this.validateRange(range);
1847
1848
const decorations = this._getDecorationsInRange(validatedRange, ownerId, filterOutValidation, filterFontDecorations, onlyMarginDecorations);
1849
pushMany(decorations, this._decorationProvider.getDecorationsInRange(validatedRange, ownerId, filterOutValidation, filterFontDecorations, onlyMinimapDecorations));
1850
pushMany(decorations, this._fontTokenDecorationsProvider.getDecorationsInRange(validatedRange, ownerId, filterOutValidation, filterFontDecorations, onlyMinimapDecorations));
1851
return decorations;
1852
}
1853
1854
public getOverviewRulerDecorations(ownerId: number = 0, filterOutValidation: boolean = false, filterFontDecorations: boolean = false): model.IModelDecoration[] {
1855
return this._decorationsTree.getAll(this, ownerId, filterOutValidation, filterFontDecorations, true, false);
1856
}
1857
1858
public getInjectedTextDecorations(ownerId: number = 0): model.IModelDecoration[] {
1859
return this._decorationsTree.getAllInjectedText(this, ownerId);
1860
}
1861
1862
public getCustomLineHeightsDecorations(ownerId: number = 0): model.IModelDecoration[] {
1863
const decs = this._decorationsTree.getAllCustomLineHeights(this, ownerId);
1864
pushMany(decs, this._fontTokenDecorationsProvider.getAllDecorations(ownerId));
1865
return decs;
1866
}
1867
1868
private _getInjectedTextInLine(lineNumber: number): LineInjectedText[] {
1869
const startOffset = this._buffer.getOffsetAt(lineNumber, 1);
1870
const endOffset = startOffset + this._buffer.getLineLength(lineNumber);
1871
1872
const result = this._decorationsTree.getInjectedTextInInterval(this, startOffset, endOffset, 0);
1873
return LineInjectedText.fromDecorations(result).filter(t => t.lineNumber === lineNumber);
1874
}
1875
1876
public getFontDecorationsInRange(range: IRange, ownerId: number = 0): model.IModelDecoration[] {
1877
const startOffset = this._buffer.getOffsetAt(range.startLineNumber, range.startColumn);
1878
const endOffset = this._buffer.getOffsetAt(range.endLineNumber, range.endColumn);
1879
return this._decorationsTree.getFontDecorationsInInterval(this, startOffset, endOffset, ownerId);
1880
}
1881
1882
public getAllDecorations(ownerId: number = 0, filterOutValidation: boolean = false, filterFontDecorations: boolean = false): model.IModelDecoration[] {
1883
let result = this._decorationsTree.getAll(this, ownerId, filterOutValidation, filterFontDecorations, false, false);
1884
result = result.concat(this._decorationProvider.getAllDecorations(ownerId, filterOutValidation));
1885
result = result.concat(this._fontTokenDecorationsProvider.getAllDecorations(ownerId, filterOutValidation));
1886
return result;
1887
}
1888
1889
public getAllMarginDecorations(ownerId: number = 0): model.IModelDecoration[] {
1890
return this._decorationsTree.getAll(this, ownerId, false, false, false, true);
1891
}
1892
1893
private _getDecorationsInRange(filterRange: Range, filterOwnerId: number, filterOutValidation: boolean, filterFontDecorations: boolean, onlyMarginDecorations: boolean): model.IModelDecoration[] {
1894
const startOffset = this._buffer.getOffsetAt(filterRange.startLineNumber, filterRange.startColumn);
1895
const endOffset = this._buffer.getOffsetAt(filterRange.endLineNumber, filterRange.endColumn);
1896
return this._decorationsTree.getAllInInterval(this, startOffset, endOffset, filterOwnerId, filterOutValidation, filterFontDecorations, onlyMarginDecorations);
1897
}
1898
1899
public getRangeAt(start: number, end: number): Range {
1900
return this._buffer.getRangeAt(start, end - start);
1901
}
1902
1903
private _changeDecorationImpl(ownerId: number, decorationId: string, _range: IRange): void {
1904
const node = this._decorations[decorationId];
1905
if (!node) {
1906
return;
1907
}
1908
1909
if (node.options.after) {
1910
const oldRange = this.getDecorationRange(decorationId);
1911
this._onDidChangeDecorations.recordLineAffectedByInjectedText(oldRange!.endLineNumber);
1912
}
1913
if (node.options.before) {
1914
const oldRange = this.getDecorationRange(decorationId);
1915
this._onDidChangeDecorations.recordLineAffectedByInjectedText(oldRange!.startLineNumber);
1916
}
1917
if (node.options.lineHeight !== null) {
1918
const oldRange = this.getDecorationRange(decorationId);
1919
this._onDidChangeDecorations.recordLineAffectedByLineHeightChange(ownerId, decorationId, oldRange!.startLineNumber, null);
1920
}
1921
if (node.options.affectsFont) {
1922
const oldRange = this.getDecorationRange(decorationId);
1923
this._onDidChangeDecorations.recordLineAffectedByFontChange(ownerId, node.id, oldRange!.startLineNumber);
1924
}
1925
1926
const range = this._validateRangeRelaxedNoAllocations(_range);
1927
const startOffset = this._buffer.getOffsetAt(range.startLineNumber, range.startColumn);
1928
const endOffset = this._buffer.getOffsetAt(range.endLineNumber, range.endColumn);
1929
1930
this._decorationsTree.delete(node);
1931
node.reset(this.getVersionId(), startOffset, endOffset, range);
1932
this._decorationsTree.insert(node);
1933
this._onDidChangeDecorations.checkAffectedAndFire(node.options);
1934
1935
if (node.options.after) {
1936
this._onDidChangeDecorations.recordLineAffectedByInjectedText(range.endLineNumber);
1937
}
1938
if (node.options.before) {
1939
this._onDidChangeDecorations.recordLineAffectedByInjectedText(range.startLineNumber);
1940
}
1941
if (node.options.lineHeight !== null) {
1942
this._onDidChangeDecorations.recordLineAffectedByLineHeightChange(ownerId, decorationId, range.startLineNumber, node.options.lineHeight);
1943
}
1944
if (node.options.affectsFont) {
1945
this._onDidChangeDecorations.recordLineAffectedByFontChange(ownerId, node.id, range.startLineNumber);
1946
}
1947
}
1948
1949
private _changeDecorationOptionsImpl(ownerId: number, decorationId: string, options: ModelDecorationOptions): void {
1950
const node = this._decorations[decorationId];
1951
if (!node) {
1952
return;
1953
}
1954
1955
const nodeWasInOverviewRuler = (node.options.overviewRuler && node.options.overviewRuler.color ? true : false);
1956
const nodeIsInOverviewRuler = (options.overviewRuler && options.overviewRuler.color ? true : false);
1957
1958
this._onDidChangeDecorations.checkAffectedAndFire(node.options);
1959
this._onDidChangeDecorations.checkAffectedAndFire(options);
1960
1961
if (node.options.after || options.after) {
1962
const nodeRange = this._decorationsTree.getNodeRange(this, node);
1963
this._onDidChangeDecorations.recordLineAffectedByInjectedText(nodeRange.endLineNumber);
1964
}
1965
if (node.options.before || options.before) {
1966
const nodeRange = this._decorationsTree.getNodeRange(this, node);
1967
this._onDidChangeDecorations.recordLineAffectedByInjectedText(nodeRange.startLineNumber);
1968
}
1969
if (node.options.lineHeight !== null || options.lineHeight !== null) {
1970
const nodeRange = this._decorationsTree.getNodeRange(this, node);
1971
this._onDidChangeDecorations.recordLineAffectedByLineHeightChange(ownerId, decorationId, nodeRange.startLineNumber, options.lineHeight);
1972
}
1973
if (node.options.affectsFont || options.affectsFont) {
1974
const nodeRange = this._decorationsTree.getNodeRange(this, node);
1975
this._onDidChangeDecorations.recordLineAffectedByFontChange(ownerId, decorationId, nodeRange.startLineNumber);
1976
}
1977
1978
const movedInOverviewRuler = nodeWasInOverviewRuler !== nodeIsInOverviewRuler;
1979
const changedWhetherInjectedText = isOptionsInjectedText(options) !== isNodeInjectedText(node);
1980
if (movedInOverviewRuler || changedWhetherInjectedText) {
1981
this._decorationsTree.delete(node);
1982
node.setOptions(options);
1983
this._decorationsTree.insert(node);
1984
} else {
1985
node.setOptions(options);
1986
}
1987
}
1988
1989
private _deltaDecorationsImpl(ownerId: number, oldDecorationsIds: string[], newDecorations: model.IModelDeltaDecoration[], suppressEvents: boolean = false): string[] {
1990
const versionId = this.getVersionId();
1991
1992
const oldDecorationsLen = oldDecorationsIds.length;
1993
let oldDecorationIndex = 0;
1994
1995
const newDecorationsLen = newDecorations.length;
1996
let newDecorationIndex = 0;
1997
1998
this._onDidChangeDecorations.beginDeferredEmit();
1999
try {
2000
const result = new Array<string>(newDecorationsLen);
2001
while (oldDecorationIndex < oldDecorationsLen || newDecorationIndex < newDecorationsLen) {
2002
2003
let node: IntervalNode | null = null;
2004
2005
if (oldDecorationIndex < oldDecorationsLen) {
2006
// (1) get ourselves an old node
2007
let decorationId: string;
2008
do {
2009
decorationId = oldDecorationsIds[oldDecorationIndex++];
2010
node = this._decorations[decorationId];
2011
} while (!node && oldDecorationIndex < oldDecorationsLen);
2012
2013
// (2) remove the node from the tree (if it exists)
2014
if (node) {
2015
if (node.options.after) {
2016
const nodeRange = this._decorationsTree.getNodeRange(this, node);
2017
this._onDidChangeDecorations.recordLineAffectedByInjectedText(nodeRange.endLineNumber);
2018
}
2019
if (node.options.before) {
2020
const nodeRange = this._decorationsTree.getNodeRange(this, node);
2021
this._onDidChangeDecorations.recordLineAffectedByInjectedText(nodeRange.startLineNumber);
2022
}
2023
if (node.options.lineHeight !== null) {
2024
const nodeRange = this._decorationsTree.getNodeRange(this, node);
2025
this._onDidChangeDecorations.recordLineAffectedByLineHeightChange(ownerId, decorationId, nodeRange.startLineNumber, null);
2026
}
2027
if (node.options.affectsFont) {
2028
const nodeRange = this._decorationsTree.getNodeRange(this, node);
2029
this._onDidChangeDecorations.recordLineAffectedByFontChange(ownerId, decorationId, nodeRange.startLineNumber);
2030
}
2031
this._decorationsTree.delete(node);
2032
2033
if (!suppressEvents) {
2034
this._onDidChangeDecorations.checkAffectedAndFire(node.options);
2035
}
2036
}
2037
}
2038
2039
if (newDecorationIndex < newDecorationsLen) {
2040
// (3) create a new node if necessary
2041
if (!node) {
2042
const internalDecorationId = (++this._lastDecorationId);
2043
const decorationId = `${this._instanceId};${internalDecorationId}`;
2044
node = new IntervalNode(decorationId, 0, 0);
2045
this._decorations[decorationId] = node;
2046
}
2047
2048
// (4) initialize node
2049
const newDecoration = newDecorations[newDecorationIndex];
2050
const range = this._validateRangeRelaxedNoAllocations(newDecoration.range);
2051
const options = _normalizeOptions(newDecoration.options);
2052
const startOffset = this._buffer.getOffsetAt(range.startLineNumber, range.startColumn);
2053
const endOffset = this._buffer.getOffsetAt(range.endLineNumber, range.endColumn);
2054
2055
node.ownerId = ownerId;
2056
node.reset(versionId, startOffset, endOffset, range);
2057
node.setOptions(options);
2058
2059
if (node.options.after) {
2060
this._onDidChangeDecorations.recordLineAffectedByInjectedText(range.endLineNumber);
2061
}
2062
if (node.options.before) {
2063
this._onDidChangeDecorations.recordLineAffectedByInjectedText(range.startLineNumber);
2064
}
2065
if (node.options.lineHeight !== null) {
2066
this._onDidChangeDecorations.recordLineAffectedByLineHeightChange(ownerId, node.id, range.startLineNumber, node.options.lineHeight);
2067
}
2068
if (node.options.affectsFont) {
2069
this._onDidChangeDecorations.recordLineAffectedByFontChange(ownerId, node.id, range.startLineNumber);
2070
}
2071
if (!suppressEvents) {
2072
this._onDidChangeDecorations.checkAffectedAndFire(options);
2073
}
2074
2075
this._decorationsTree.insert(node);
2076
2077
result[newDecorationIndex] = node.id;
2078
2079
newDecorationIndex++;
2080
} else {
2081
if (node) {
2082
delete this._decorations[node.id];
2083
}
2084
}
2085
}
2086
2087
return result;
2088
} finally {
2089
this._onDidChangeDecorations.endDeferredEmit();
2090
}
2091
}
2092
2093
//#endregion
2094
2095
//#region Tokenization
2096
2097
// TODO move them to the tokenization part.
2098
public getLanguageId(): string {
2099
return this.tokenization.getLanguageId();
2100
}
2101
2102
public setLanguage(languageIdOrSelection: string | ILanguageSelection, source?: string): void {
2103
if (typeof languageIdOrSelection === 'string') {
2104
this._languageSelectionListener.clear();
2105
this._setLanguage(languageIdOrSelection, source);
2106
} else {
2107
this._languageSelectionListener.value = languageIdOrSelection.onDidChange(() => this._setLanguage(languageIdOrSelection.languageId, source));
2108
this._setLanguage(languageIdOrSelection.languageId, source);
2109
}
2110
}
2111
2112
private _setLanguage(languageId: string, source?: string): void {
2113
this.tokenization.setLanguageId(languageId, source);
2114
this._languageService.requestRichLanguageFeatures(languageId);
2115
}
2116
2117
public getLanguageIdAtPosition(lineNumber: number, column: number): string {
2118
return this.tokenization.getLanguageIdAtPosition(lineNumber, column);
2119
}
2120
2121
public getWordAtPosition(position: IPosition): IWordAtPosition | null {
2122
return this._tokenizationTextModelPart.getWordAtPosition(position);
2123
}
2124
2125
public getWordUntilPosition(position: IPosition): IWordAtPosition {
2126
return this._tokenizationTextModelPart.getWordUntilPosition(position);
2127
}
2128
2129
//#endregion
2130
normalizePosition(position: Position, affinity: model.PositionAffinity): Position {
2131
return position;
2132
}
2133
2134
/**
2135
* Gets the column at which indentation stops at a given line.
2136
* @internal
2137
*/
2138
public getLineIndentColumn(lineNumber: number): number {
2139
// Columns start with 1.
2140
return indentOfLine(this.getLineContent(lineNumber)) + 1;
2141
}
2142
2143
public override toString(): string {
2144
return `TextModel(${this.uri.toString()})`;
2145
}
2146
}
2147
2148
export function indentOfLine(line: string): number {
2149
let indent = 0;
2150
for (const c of line) {
2151
if (c === ' ' || c === '\t') {
2152
indent++;
2153
} else {
2154
break;
2155
}
2156
}
2157
return indent;
2158
}
2159
2160
//#region Decorations
2161
2162
function isNodeInOverviewRuler(node: IntervalNode): boolean {
2163
return (node.options.overviewRuler && node.options.overviewRuler.color ? true : false);
2164
}
2165
2166
function isOptionsInjectedText(options: ModelDecorationOptions): boolean {
2167
return !!options.after || !!options.before;
2168
}
2169
2170
function isNodeInjectedText(node: IntervalNode): boolean {
2171
return !!node.options.after || !!node.options.before;
2172
}
2173
2174
export interface IDecorationsTreesHost {
2175
getVersionId(): number;
2176
getRangeAt(start: number, end: number): Range;
2177
}
2178
2179
class DecorationsTrees {
2180
2181
/**
2182
* This tree holds decorations that do not show up in the overview ruler.
2183
*/
2184
private readonly _decorationsTree0: IntervalTree;
2185
2186
/**
2187
* This tree holds decorations that show up in the overview ruler.
2188
*/
2189
private readonly _decorationsTree1: IntervalTree;
2190
2191
/**
2192
* This tree holds decorations that contain injected text.
2193
*/
2194
private readonly _injectedTextDecorationsTree: IntervalTree;
2195
2196
constructor() {
2197
this._decorationsTree0 = new IntervalTree();
2198
this._decorationsTree1 = new IntervalTree();
2199
this._injectedTextDecorationsTree = new IntervalTree();
2200
}
2201
2202
public ensureAllNodesHaveRanges(host: IDecorationsTreesHost): void {
2203
this.getAll(host, 0, false, false, false, false);
2204
}
2205
2206
private _ensureNodesHaveRanges(host: IDecorationsTreesHost, nodes: IntervalNode[]): model.IModelDecoration[] {
2207
for (const node of nodes) {
2208
if (node.range === null) {
2209
node.range = host.getRangeAt(node.cachedAbsoluteStart, node.cachedAbsoluteEnd);
2210
}
2211
}
2212
return <model.IModelDecoration[]>nodes;
2213
}
2214
2215
public getAllInInterval(host: IDecorationsTreesHost, start: number, end: number, filterOwnerId: number, filterOutValidation: boolean, filterFontDecorations: boolean, onlyMarginDecorations: boolean): model.IModelDecoration[] {
2216
const versionId = host.getVersionId();
2217
const result = this._intervalSearch(start, end, filterOwnerId, filterOutValidation, filterFontDecorations, versionId, onlyMarginDecorations);
2218
return this._ensureNodesHaveRanges(host, result);
2219
}
2220
2221
private _intervalSearch(start: number, end: number, filterOwnerId: number, filterOutValidation: boolean, filterFontDecorations: boolean, cachedVersionId: number, onlyMarginDecorations: boolean): IntervalNode[] {
2222
const r0 = this._decorationsTree0.intervalSearch(start, end, filterOwnerId, filterOutValidation, filterFontDecorations, cachedVersionId, onlyMarginDecorations);
2223
const r1 = this._decorationsTree1.intervalSearch(start, end, filterOwnerId, filterOutValidation, filterFontDecorations, cachedVersionId, onlyMarginDecorations);
2224
const r2 = this._injectedTextDecorationsTree.intervalSearch(start, end, filterOwnerId, filterOutValidation, filterFontDecorations, cachedVersionId, onlyMarginDecorations);
2225
return r0.concat(r1).concat(r2);
2226
}
2227
2228
public getInjectedTextInInterval(host: IDecorationsTreesHost, start: number, end: number, filterOwnerId: number): model.IModelDecoration[] {
2229
const versionId = host.getVersionId();
2230
const result = this._injectedTextDecorationsTree.intervalSearch(start, end, filterOwnerId, false, false, versionId, false);
2231
return this._ensureNodesHaveRanges(host, result).filter((i) => i.options.showIfCollapsed || !i.range.isEmpty());
2232
}
2233
2234
public getFontDecorationsInInterval(host: IDecorationsTreesHost, start: number, end: number, filterOwnerId: number): model.IModelDecoration[] {
2235
const versionId = host.getVersionId();
2236
const decorations = this._decorationsTree0.intervalSearch(start, end, filterOwnerId, false, false, versionId, false);
2237
return this._ensureNodesHaveRanges(host, decorations).filter((i) => i.options.affectsFont);
2238
}
2239
2240
public getAllInjectedText(host: IDecorationsTreesHost, filterOwnerId: number): model.IModelDecoration[] {
2241
const versionId = host.getVersionId();
2242
const result = this._injectedTextDecorationsTree.search(filterOwnerId, false, false, versionId, false);
2243
return this._ensureNodesHaveRanges(host, result).filter((i) => i.options.showIfCollapsed || !i.range.isEmpty());
2244
}
2245
2246
public getAllCustomLineHeights(host: IDecorationsTreesHost, filterOwnerId: number): model.IModelDecoration[] {
2247
const versionId = host.getVersionId();
2248
const result = this._search(filterOwnerId, false, false, false, versionId, false);
2249
return this._ensureNodesHaveRanges(host, result).filter((i) => typeof i.options.lineHeight === 'number');
2250
}
2251
2252
public getAll(host: IDecorationsTreesHost, filterOwnerId: number, filterOutValidation: boolean, filterFontDecorations: boolean, overviewRulerOnly: boolean, onlyMarginDecorations: boolean): model.IModelDecoration[] {
2253
const versionId = host.getVersionId();
2254
const result = this._search(filterOwnerId, filterOutValidation, filterFontDecorations, overviewRulerOnly, versionId, onlyMarginDecorations);
2255
return this._ensureNodesHaveRanges(host, result);
2256
}
2257
2258
private _search(filterOwnerId: number, filterOutValidation: boolean, filterFontDecorations: boolean, overviewRulerOnly: boolean, cachedVersionId: number, onlyMarginDecorations: boolean): IntervalNode[] {
2259
if (overviewRulerOnly) {
2260
return this._decorationsTree1.search(filterOwnerId, filterOutValidation, filterFontDecorations, cachedVersionId, onlyMarginDecorations);
2261
} else {
2262
const r0 = this._decorationsTree0.search(filterOwnerId, filterOutValidation, filterFontDecorations, cachedVersionId, onlyMarginDecorations);
2263
const r1 = this._decorationsTree1.search(filterOwnerId, filterOutValidation, filterFontDecorations, cachedVersionId, onlyMarginDecorations);
2264
const r2 = this._injectedTextDecorationsTree.search(filterOwnerId, filterOutValidation, filterFontDecorations, cachedVersionId, onlyMarginDecorations);
2265
return r0.concat(r1).concat(r2);
2266
}
2267
}
2268
2269
public collectNodesFromOwner(ownerId: number): IntervalNode[] {
2270
const r0 = this._decorationsTree0.collectNodesFromOwner(ownerId);
2271
const r1 = this._decorationsTree1.collectNodesFromOwner(ownerId);
2272
const r2 = this._injectedTextDecorationsTree.collectNodesFromOwner(ownerId);
2273
return r0.concat(r1).concat(r2);
2274
}
2275
2276
public collectNodesPostOrder(): IntervalNode[] {
2277
const r0 = this._decorationsTree0.collectNodesPostOrder();
2278
const r1 = this._decorationsTree1.collectNodesPostOrder();
2279
const r2 = this._injectedTextDecorationsTree.collectNodesPostOrder();
2280
return r0.concat(r1).concat(r2);
2281
}
2282
2283
public insert(node: IntervalNode): void {
2284
if (isNodeInjectedText(node)) {
2285
this._injectedTextDecorationsTree.insert(node);
2286
} else if (isNodeInOverviewRuler(node)) {
2287
this._decorationsTree1.insert(node);
2288
} else {
2289
this._decorationsTree0.insert(node);
2290
}
2291
}
2292
2293
public delete(node: IntervalNode): void {
2294
if (isNodeInjectedText(node)) {
2295
this._injectedTextDecorationsTree.delete(node);
2296
} else if (isNodeInOverviewRuler(node)) {
2297
this._decorationsTree1.delete(node);
2298
} else {
2299
this._decorationsTree0.delete(node);
2300
}
2301
}
2302
2303
public getNodeRange(host: IDecorationsTreesHost, node: IntervalNode): Range {
2304
const versionId = host.getVersionId();
2305
if (node.cachedVersionId !== versionId) {
2306
this._resolveNode(node, versionId);
2307
}
2308
if (node.range === null) {
2309
node.range = host.getRangeAt(node.cachedAbsoluteStart, node.cachedAbsoluteEnd);
2310
}
2311
return node.range;
2312
}
2313
2314
private _resolveNode(node: IntervalNode, cachedVersionId: number): void {
2315
if (isNodeInjectedText(node)) {
2316
this._injectedTextDecorationsTree.resolveNode(node, cachedVersionId);
2317
} else if (isNodeInOverviewRuler(node)) {
2318
this._decorationsTree1.resolveNode(node, cachedVersionId);
2319
} else {
2320
this._decorationsTree0.resolveNode(node, cachedVersionId);
2321
}
2322
}
2323
2324
public acceptReplace(offset: number, length: number, textLength: number, forceMoveMarkers: boolean): void {
2325
this._decorationsTree0.acceptReplace(offset, length, textLength, forceMoveMarkers);
2326
this._decorationsTree1.acceptReplace(offset, length, textLength, forceMoveMarkers);
2327
this._injectedTextDecorationsTree.acceptReplace(offset, length, textLength, forceMoveMarkers);
2328
}
2329
}
2330
2331
function cleanClassName(className: string): string {
2332
return className.replace(/[^a-z0-9\-_]/gi, ' ');
2333
}
2334
2335
class DecorationOptions implements model.IDecorationOptions {
2336
readonly color: string | ThemeColor;
2337
readonly darkColor: string | ThemeColor;
2338
2339
constructor(options: model.IDecorationOptions) {
2340
this.color = options.color || '';
2341
this.darkColor = options.darkColor || '';
2342
2343
}
2344
}
2345
2346
export class ModelDecorationOverviewRulerOptions extends DecorationOptions {
2347
readonly position: model.OverviewRulerLane;
2348
private _resolvedColor: string | null;
2349
2350
constructor(options: model.IModelDecorationOverviewRulerOptions) {
2351
super(options);
2352
this._resolvedColor = null;
2353
this.position = (typeof options.position === 'number' ? options.position : model.OverviewRulerLane.Center);
2354
}
2355
2356
public getColor(theme: IColorTheme): string {
2357
if (!this._resolvedColor) {
2358
if (isDark(theme.type) && this.darkColor) {
2359
this._resolvedColor = this._resolveColor(this.darkColor, theme);
2360
} else {
2361
this._resolvedColor = this._resolveColor(this.color, theme);
2362
}
2363
}
2364
return this._resolvedColor;
2365
}
2366
2367
public invalidateCachedColor(): void {
2368
this._resolvedColor = null;
2369
}
2370
2371
private _resolveColor(color: string | ThemeColor, theme: IColorTheme): string {
2372
if (typeof color === 'string') {
2373
return color;
2374
}
2375
const c = color ? theme.getColor(color.id) : null;
2376
if (!c) {
2377
return '';
2378
}
2379
return c.toString();
2380
}
2381
}
2382
2383
export class ModelDecorationGlyphMarginOptions {
2384
readonly position: model.GlyphMarginLane;
2385
readonly persistLane: boolean | undefined;
2386
2387
constructor(options: model.IModelDecorationGlyphMarginOptions | null | undefined) {
2388
this.position = options?.position ?? model.GlyphMarginLane.Center;
2389
this.persistLane = options?.persistLane;
2390
}
2391
}
2392
2393
export class ModelDecorationMinimapOptions extends DecorationOptions {
2394
readonly position: model.MinimapPosition;
2395
readonly sectionHeaderStyle: model.MinimapSectionHeaderStyle | null;
2396
readonly sectionHeaderText: string | null;
2397
private _resolvedColor: Color | undefined;
2398
2399
constructor(options: model.IModelDecorationMinimapOptions) {
2400
super(options);
2401
this.position = options.position;
2402
this.sectionHeaderStyle = options.sectionHeaderStyle ?? null;
2403
this.sectionHeaderText = options.sectionHeaderText ?? null;
2404
}
2405
2406
public getColor(theme: IColorTheme): Color | undefined {
2407
if (!this._resolvedColor) {
2408
if (isDark(theme.type) && this.darkColor) {
2409
this._resolvedColor = this._resolveColor(this.darkColor, theme);
2410
} else {
2411
this._resolvedColor = this._resolveColor(this.color, theme);
2412
}
2413
}
2414
2415
return this._resolvedColor;
2416
}
2417
2418
public invalidateCachedColor(): void {
2419
this._resolvedColor = undefined;
2420
}
2421
2422
private _resolveColor(color: string | ThemeColor, theme: IColorTheme): Color | undefined {
2423
if (typeof color === 'string') {
2424
return Color.fromHex(color);
2425
}
2426
return theme.getColor(color.id);
2427
}
2428
}
2429
2430
export class ModelDecorationInjectedTextOptions implements model.InjectedTextOptions {
2431
public static from(options: model.InjectedTextOptions): ModelDecorationInjectedTextOptions {
2432
if (options instanceof ModelDecorationInjectedTextOptions) {
2433
return options;
2434
}
2435
return new ModelDecorationInjectedTextOptions(options);
2436
}
2437
2438
public readonly content: string;
2439
public readonly tokens: TokenArray | null;
2440
readonly inlineClassName: string | null;
2441
readonly inlineClassNameAffectsLetterSpacing: boolean;
2442
readonly attachedData: unknown | null;
2443
readonly cursorStops: model.InjectedTextCursorStops | null;
2444
2445
private constructor(options: model.InjectedTextOptions) {
2446
this.content = options.content || '';
2447
this.tokens = options.tokens ?? null;
2448
this.inlineClassName = options.inlineClassName || null;
2449
this.inlineClassNameAffectsLetterSpacing = options.inlineClassNameAffectsLetterSpacing || false;
2450
this.attachedData = options.attachedData || null;
2451
this.cursorStops = options.cursorStops || null;
2452
}
2453
}
2454
2455
export class ModelDecorationOptions implements model.IModelDecorationOptions {
2456
2457
public static EMPTY: ModelDecorationOptions;
2458
2459
public static register(options: model.IModelDecorationOptions): ModelDecorationOptions {
2460
return new ModelDecorationOptions(options);
2461
}
2462
2463
public static createDynamic(options: model.IModelDecorationOptions): ModelDecorationOptions {
2464
return new ModelDecorationOptions(options);
2465
}
2466
readonly description: string;
2467
readonly blockClassName: string | null;
2468
readonly blockIsAfterEnd: boolean | null;
2469
readonly blockDoesNotCollapse?: boolean | null;
2470
readonly blockPadding: [top: number, right: number, bottom: number, left: number] | null;
2471
readonly stickiness: model.TrackedRangeStickiness;
2472
readonly zIndex: number;
2473
readonly className: string | null;
2474
readonly shouldFillLineOnLineBreak: boolean | null;
2475
readonly hoverMessage: IMarkdownString | IMarkdownString[] | null;
2476
readonly glyphMarginHoverMessage: IMarkdownString | IMarkdownString[] | null;
2477
readonly isWholeLine: boolean;
2478
readonly lineHeight: number | null;
2479
readonly fontSize: string | null;
2480
readonly showIfCollapsed: boolean;
2481
readonly collapseOnReplaceEdit: boolean;
2482
readonly overviewRuler: ModelDecorationOverviewRulerOptions | null;
2483
readonly minimap: ModelDecorationMinimapOptions | null;
2484
readonly glyphMargin?: model.IModelDecorationGlyphMarginOptions | null | undefined;
2485
readonly glyphMarginClassName: string | null;
2486
readonly linesDecorationsClassName: string | null;
2487
readonly lineNumberClassName: string | null;
2488
readonly lineNumberHoverMessage: IMarkdownString | IMarkdownString[] | null;
2489
readonly linesDecorationsTooltip: string | null;
2490
readonly firstLineDecorationClassName: string | null;
2491
readonly marginClassName: string | null;
2492
readonly inlineClassName: string | null;
2493
readonly inlineClassNameAffectsLetterSpacing: boolean;
2494
readonly beforeContentClassName: string | null;
2495
readonly afterContentClassName: string | null;
2496
readonly after: ModelDecorationInjectedTextOptions | null;
2497
readonly before: ModelDecorationInjectedTextOptions | null;
2498
readonly hideInCommentTokens: boolean | null;
2499
readonly hideInStringTokens: boolean | null;
2500
readonly affectsFont: boolean | null;
2501
readonly textDirection?: model.TextDirection | null | undefined;
2502
2503
private constructor(options: model.IModelDecorationOptions) {
2504
this.description = options.description;
2505
this.blockClassName = options.blockClassName ? cleanClassName(options.blockClassName) : null;
2506
this.blockDoesNotCollapse = options.blockDoesNotCollapse ?? null;
2507
this.blockIsAfterEnd = options.blockIsAfterEnd ?? null;
2508
this.blockPadding = options.blockPadding ?? null;
2509
this.stickiness = options.stickiness || model.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges;
2510
this.zIndex = options.zIndex || 0;
2511
this.className = options.className ? cleanClassName(options.className) : null;
2512
this.shouldFillLineOnLineBreak = options.shouldFillLineOnLineBreak ?? null;
2513
this.hoverMessage = options.hoverMessage || null;
2514
this.glyphMarginHoverMessage = options.glyphMarginHoverMessage || null;
2515
this.lineNumberHoverMessage = options.lineNumberHoverMessage || null;
2516
this.isWholeLine = options.isWholeLine || false;
2517
this.lineHeight = options.lineHeight ? Math.min(options.lineHeight, LINE_HEIGHT_CEILING) : null;
2518
this.fontSize = options.fontSize || null;
2519
this.affectsFont = !!options.fontSize || !!options.fontFamily || !!options.fontWeight || !!options.fontStyle;
2520
this.showIfCollapsed = options.showIfCollapsed || false;
2521
this.collapseOnReplaceEdit = options.collapseOnReplaceEdit || false;
2522
this.overviewRuler = options.overviewRuler ? new ModelDecorationOverviewRulerOptions(options.overviewRuler) : null;
2523
this.minimap = options.minimap ? new ModelDecorationMinimapOptions(options.minimap) : null;
2524
this.glyphMargin = options.glyphMarginClassName ? new ModelDecorationGlyphMarginOptions(options.glyphMargin) : null;
2525
this.glyphMarginClassName = options.glyphMarginClassName ? cleanClassName(options.glyphMarginClassName) : null;
2526
this.linesDecorationsClassName = options.linesDecorationsClassName ? cleanClassName(options.linesDecorationsClassName) : null;
2527
this.lineNumberClassName = options.lineNumberClassName ? cleanClassName(options.lineNumberClassName) : null;
2528
this.linesDecorationsTooltip = options.linesDecorationsTooltip ? strings.htmlAttributeEncodeValue(options.linesDecorationsTooltip) : null;
2529
this.firstLineDecorationClassName = options.firstLineDecorationClassName ? cleanClassName(options.firstLineDecorationClassName) : null;
2530
this.marginClassName = options.marginClassName ? cleanClassName(options.marginClassName) : null;
2531
this.inlineClassName = options.inlineClassName ? cleanClassName(options.inlineClassName) : null;
2532
this.inlineClassNameAffectsLetterSpacing = options.inlineClassNameAffectsLetterSpacing || false;
2533
this.beforeContentClassName = options.beforeContentClassName ? cleanClassName(options.beforeContentClassName) : null;
2534
this.afterContentClassName = options.afterContentClassName ? cleanClassName(options.afterContentClassName) : null;
2535
this.after = options.after ? ModelDecorationInjectedTextOptions.from(options.after) : null;
2536
this.before = options.before ? ModelDecorationInjectedTextOptions.from(options.before) : null;
2537
this.hideInCommentTokens = options.hideInCommentTokens ?? false;
2538
this.hideInStringTokens = options.hideInStringTokens ?? false;
2539
this.textDirection = options.textDirection ?? null;
2540
}
2541
}
2542
ModelDecorationOptions.EMPTY = ModelDecorationOptions.register({ description: 'empty' });
2543
2544
/**
2545
* The order carefully matches the values of the enum.
2546
*/
2547
const TRACKED_RANGE_OPTIONS = [
2548
ModelDecorationOptions.register({ description: 'tracked-range-always-grows-when-typing-at-edges', stickiness: model.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges }),
2549
ModelDecorationOptions.register({ description: 'tracked-range-never-grows-when-typing-at-edges', stickiness: model.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges }),
2550
ModelDecorationOptions.register({ description: 'tracked-range-grows-only-when-typing-before', stickiness: model.TrackedRangeStickiness.GrowsOnlyWhenTypingBefore }),
2551
ModelDecorationOptions.register({ description: 'tracked-range-grows-only-when-typing-after', stickiness: model.TrackedRangeStickiness.GrowsOnlyWhenTypingAfter }),
2552
];
2553
2554
function _normalizeOptions(options: model.IModelDecorationOptions): ModelDecorationOptions {
2555
if (options instanceof ModelDecorationOptions) {
2556
return options;
2557
}
2558
return ModelDecorationOptions.createDynamic(options);
2559
}
2560
2561
2562
class DidChangeDecorationsEmitter extends Disposable {
2563
2564
private readonly _actual: Emitter<IModelDecorationsChangedEvent> = this._register(new Emitter<IModelDecorationsChangedEvent>());
2565
public readonly event: Event<IModelDecorationsChangedEvent> = this._actual.event;
2566
2567
private _deferredCnt: number;
2568
private _shouldFireDeferred: boolean;
2569
private _affectsMinimap: boolean;
2570
private _affectsOverviewRuler: boolean;
2571
private _affectedInjectedTextLines: Set<number> | null = null;
2572
private _affectedLineHeights: SetWithKey<LineHeightChangingDecoration> | null = null;
2573
private _affectedFontLines: SetWithKey<LineFontChangingDecoration> | null = null;
2574
private _affectsGlyphMargin: boolean;
2575
private _affectsLineNumber: boolean;
2576
2577
constructor(private readonly handleBeforeFire: (affectedInjectedTextLines: Set<number> | null, affectedLineHeights: SetWithKey<LineHeightChangingDecoration> | null, affectedFontLines: SetWithKey<LineFontChangingDecoration> | null) => void) {
2578
super();
2579
this._deferredCnt = 0;
2580
this._shouldFireDeferred = false;
2581
this._affectsMinimap = false;
2582
this._affectsOverviewRuler = false;
2583
this._affectsGlyphMargin = false;
2584
this._affectsLineNumber = false;
2585
}
2586
2587
hasListeners(): boolean {
2588
return this._actual.hasListeners();
2589
}
2590
2591
public beginDeferredEmit(): void {
2592
this._deferredCnt++;
2593
}
2594
2595
public endDeferredEmit(): void {
2596
this._deferredCnt--;
2597
if (this._deferredCnt === 0) {
2598
if (this._shouldFireDeferred) {
2599
this.doFire();
2600
}
2601
2602
this._affectedInjectedTextLines?.clear();
2603
this._affectedInjectedTextLines = null;
2604
this._affectedLineHeights?.clear();
2605
this._affectedLineHeights = null;
2606
this._affectedFontLines?.clear();
2607
this._affectedFontLines = null;
2608
}
2609
}
2610
2611
public recordLineAffectedByInjectedText(lineNumber: number): void {
2612
if (!this._affectedInjectedTextLines) {
2613
this._affectedInjectedTextLines = new Set();
2614
}
2615
this._affectedInjectedTextLines.add(lineNumber);
2616
}
2617
2618
public recordLineAffectedByLineHeightChange(ownerId: number, decorationId: string, lineNumber: number, lineHeight: number | null): void {
2619
if (!this._affectedLineHeights) {
2620
this._affectedLineHeights = new SetWithKey<LineHeightChangingDecoration>([], LineHeightChangingDecoration.toKey);
2621
}
2622
this._affectedLineHeights.add(new LineHeightChangingDecoration(ownerId, decorationId, lineNumber, lineHeight));
2623
}
2624
2625
public recordLineAffectedByFontChange(ownerId: number, decorationId: string, lineNumber: number): void {
2626
if (!this._affectedFontLines) {
2627
this._affectedFontLines = new SetWithKey<LineFontChangingDecoration>([], LineFontChangingDecoration.toKey);
2628
}
2629
this._affectedFontLines.add(new LineFontChangingDecoration(ownerId, decorationId, lineNumber));
2630
}
2631
2632
public checkAffectedAndFire(options: ModelDecorationOptions): void {
2633
this._affectsMinimap ||= !!options.minimap?.position;
2634
this._affectsOverviewRuler ||= !!options.overviewRuler?.color;
2635
this._affectsGlyphMargin ||= !!options.glyphMarginClassName;
2636
this._affectsLineNumber ||= !!options.lineNumberClassName;
2637
this.tryFire();
2638
}
2639
2640
public fire(): void {
2641
this._affectsMinimap = true;
2642
this._affectsOverviewRuler = true;
2643
this._affectsGlyphMargin = true;
2644
this.tryFire();
2645
}
2646
2647
private tryFire() {
2648
if (this._deferredCnt === 0) {
2649
this.doFire();
2650
} else {
2651
this._shouldFireDeferred = true;
2652
}
2653
}
2654
2655
private doFire() {
2656
this.handleBeforeFire(this._affectedInjectedTextLines, this._affectedLineHeights, this._affectedFontLines);
2657
2658
const event: IModelDecorationsChangedEvent = {
2659
affectsMinimap: this._affectsMinimap,
2660
affectsOverviewRuler: this._affectsOverviewRuler,
2661
affectsGlyphMargin: this._affectsGlyphMargin,
2662
affectsLineNumber: this._affectsLineNumber,
2663
};
2664
this._shouldFireDeferred = false;
2665
this._affectsMinimap = false;
2666
this._affectsOverviewRuler = false;
2667
this._affectsGlyphMargin = false;
2668
this._actual.fire(event);
2669
}
2670
}
2671
2672
//#endregion
2673
2674
class DidChangeContentEmitter extends Disposable {
2675
2676
private readonly _emitter: Emitter<InternalModelContentChangeEvent> = this._register(new Emitter<InternalModelContentChangeEvent>());
2677
public readonly event: Event<InternalModelContentChangeEvent> = this._emitter.event;
2678
2679
private _deferredCnt: number;
2680
private _deferredEvent: InternalModelContentChangeEvent | null;
2681
2682
constructor() {
2683
super();
2684
this._deferredCnt = 0;
2685
this._deferredEvent = null;
2686
}
2687
2688
public hasListeners(): boolean {
2689
return this._emitter.hasListeners();
2690
}
2691
2692
public beginDeferredEmit(): void {
2693
this._deferredCnt++;
2694
}
2695
2696
public endDeferredEmit(resultingSelection: Selection[] | null = null): void {
2697
this._deferredCnt--;
2698
if (this._deferredCnt === 0) {
2699
if (this._deferredEvent !== null) {
2700
this._deferredEvent.rawContentChangedEvent.resultingSelection = resultingSelection;
2701
const e = this._deferredEvent;
2702
this._deferredEvent = null;
2703
this._emitter.fire(e);
2704
}
2705
}
2706
}
2707
2708
public fire(e: InternalModelContentChangeEvent): void {
2709
if (this._deferredCnt > 0) {
2710
if (this._deferredEvent) {
2711
this._deferredEvent = this._deferredEvent.merge(e);
2712
} else {
2713
this._deferredEvent = e;
2714
}
2715
return;
2716
}
2717
this._emitter.fire(e);
2718
}
2719
}
2720
2721