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