Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/services/modelService.ts
3296 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 { Emitter, Event } from '../../../base/common/event.js';
7
import { Disposable, IDisposable, DisposableStore } from '../../../base/common/lifecycle.js';
8
import * as platform from '../../../base/common/platform.js';
9
import { URI } from '../../../base/common/uri.js';
10
import { EditOperation, ISingleEditOperation } from '../core/editOperation.js';
11
import { Range } from '../core/range.js';
12
import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions } from '../model.js';
13
import { TextModel, createTextBuffer } from '../model/textModel.js';
14
import { EDITOR_MODEL_DEFAULTS } from '../core/misc/textModelDefaults.js';
15
import { IModelLanguageChangedEvent } from '../textModelEvents.js';
16
import { PLAINTEXT_LANGUAGE_ID } from '../languages/modesRegistry.js';
17
import { ILanguageSelection } from '../languages/language.js';
18
import { IModelService } from './model.js';
19
import { ITextResourcePropertiesService } from './textResourceConfiguration.js';
20
import { IConfigurationChangeEvent, IConfigurationService } from '../../../platform/configuration/common/configuration.js';
21
import { IUndoRedoService, ResourceEditStackSnapshot } from '../../../platform/undoRedo/common/undoRedo.js';
22
import { StringSHA1 } from '../../../base/common/hash.js';
23
import { isEditStackElement } from '../model/editStack.js';
24
import { Schemas } from '../../../base/common/network.js';
25
import { equals } from '../../../base/common/objects.js';
26
import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';
27
import { EditSources, TextModelEditSource } from '../textModelEditSource.js';
28
29
function MODEL_ID(resource: URI): string {
30
return resource.toString();
31
}
32
33
class ModelData implements IDisposable {
34
35
private readonly _modelEventListeners = new DisposableStore();
36
37
constructor(
38
public readonly model: TextModel,
39
onWillDispose: (model: ITextModel) => void,
40
onDidChangeLanguage: (model: ITextModel, e: IModelLanguageChangedEvent) => void
41
) {
42
this.model = model;
43
this._modelEventListeners.add(model.onWillDispose(() => onWillDispose(model)));
44
this._modelEventListeners.add(model.onDidChangeLanguage((e) => onDidChangeLanguage(model, e)));
45
}
46
47
public dispose(): void {
48
this._modelEventListeners.dispose();
49
}
50
}
51
52
interface IRawEditorConfig {
53
tabSize?: any;
54
indentSize?: any;
55
insertSpaces?: any;
56
detectIndentation?: any;
57
trimAutoWhitespace?: any;
58
creationOptions?: any;
59
largeFileOptimizations?: any;
60
bracketPairColorization?: any;
61
}
62
63
interface IRawConfig {
64
eol?: any;
65
editor?: IRawEditorConfig;
66
}
67
68
const DEFAULT_EOL = (platform.isLinux || platform.isMacintosh) ? DefaultEndOfLine.LF : DefaultEndOfLine.CRLF;
69
70
class DisposedModelInfo {
71
constructor(
72
public readonly uri: URI,
73
public readonly initialUndoRedoSnapshot: ResourceEditStackSnapshot | null,
74
public readonly time: number,
75
public readonly sharesUndoRedoStack: boolean,
76
public readonly heapSize: number,
77
public readonly sha1: string,
78
public readonly versionId: number,
79
public readonly alternativeVersionId: number,
80
) { }
81
}
82
83
export class ModelService extends Disposable implements IModelService {
84
85
public static MAX_MEMORY_FOR_CLOSED_FILES_UNDO_STACK = 20 * 1024 * 1024;
86
87
public _serviceBrand: undefined;
88
89
private readonly _onModelAdded: Emitter<ITextModel> = this._register(new Emitter<ITextModel>());
90
public readonly onModelAdded: Event<ITextModel> = this._onModelAdded.event;
91
92
private readonly _onModelRemoved: Emitter<ITextModel> = this._register(new Emitter<ITextModel>());
93
public readonly onModelRemoved: Event<ITextModel> = this._onModelRemoved.event;
94
95
private readonly _onModelModeChanged = this._register(new Emitter<{ model: ITextModel; oldLanguageId: string }>());
96
public readonly onModelLanguageChanged = this._onModelModeChanged.event;
97
98
private _modelCreationOptionsByLanguageAndResource: { [languageAndResource: string]: ITextModelCreationOptions };
99
100
/**
101
* All the models known in the system.
102
*/
103
private readonly _models: { [modelId: string]: ModelData };
104
private readonly _disposedModels: Map<string, DisposedModelInfo>;
105
private _disposedModelsHeapSize: number;
106
107
constructor(
108
@IConfigurationService private readonly _configurationService: IConfigurationService,
109
@ITextResourcePropertiesService private readonly _resourcePropertiesService: ITextResourcePropertiesService,
110
@IUndoRedoService private readonly _undoRedoService: IUndoRedoService,
111
@IInstantiationService private readonly _instantiationService: IInstantiationService
112
) {
113
super();
114
this._modelCreationOptionsByLanguageAndResource = Object.create(null);
115
this._models = {};
116
this._disposedModels = new Map<string, DisposedModelInfo>();
117
this._disposedModelsHeapSize = 0;
118
119
this._register(this._configurationService.onDidChangeConfiguration(e => this._updateModelOptions(e)));
120
this._updateModelOptions(undefined);
121
}
122
123
private static _readModelOptions(config: IRawConfig, isForSimpleWidget: boolean): ITextModelCreationOptions {
124
let tabSize = EDITOR_MODEL_DEFAULTS.tabSize;
125
if (config.editor && typeof config.editor.tabSize !== 'undefined') {
126
const parsedTabSize = parseInt(config.editor.tabSize, 10);
127
if (!isNaN(parsedTabSize)) {
128
tabSize = parsedTabSize;
129
}
130
if (tabSize < 1) {
131
tabSize = 1;
132
}
133
}
134
135
let indentSize: number | 'tabSize' = 'tabSize';
136
if (config.editor && typeof config.editor.indentSize !== 'undefined' && config.editor.indentSize !== 'tabSize') {
137
const parsedIndentSize = parseInt(config.editor.indentSize, 10);
138
if (!isNaN(parsedIndentSize)) {
139
indentSize = Math.max(parsedIndentSize, 1);
140
}
141
}
142
143
let insertSpaces = EDITOR_MODEL_DEFAULTS.insertSpaces;
144
if (config.editor && typeof config.editor.insertSpaces !== 'undefined') {
145
insertSpaces = (config.editor.insertSpaces === 'false' ? false : Boolean(config.editor.insertSpaces));
146
}
147
148
let newDefaultEOL = DEFAULT_EOL;
149
const eol = config.eol;
150
if (eol === '\r\n') {
151
newDefaultEOL = DefaultEndOfLine.CRLF;
152
} else if (eol === '\n') {
153
newDefaultEOL = DefaultEndOfLine.LF;
154
}
155
156
let trimAutoWhitespace = EDITOR_MODEL_DEFAULTS.trimAutoWhitespace;
157
if (config.editor && typeof config.editor.trimAutoWhitespace !== 'undefined') {
158
trimAutoWhitespace = (config.editor.trimAutoWhitespace === 'false' ? false : Boolean(config.editor.trimAutoWhitespace));
159
}
160
161
let detectIndentation = EDITOR_MODEL_DEFAULTS.detectIndentation;
162
if (config.editor && typeof config.editor.detectIndentation !== 'undefined') {
163
detectIndentation = (config.editor.detectIndentation === 'false' ? false : Boolean(config.editor.detectIndentation));
164
}
165
166
let largeFileOptimizations = EDITOR_MODEL_DEFAULTS.largeFileOptimizations;
167
if (config.editor && typeof config.editor.largeFileOptimizations !== 'undefined') {
168
largeFileOptimizations = (config.editor.largeFileOptimizations === 'false' ? false : Boolean(config.editor.largeFileOptimizations));
169
}
170
let bracketPairColorizationOptions = EDITOR_MODEL_DEFAULTS.bracketPairColorizationOptions;
171
if (config.editor?.bracketPairColorization && typeof config.editor.bracketPairColorization === 'object') {
172
bracketPairColorizationOptions = {
173
enabled: !!config.editor.bracketPairColorization.enabled,
174
independentColorPoolPerBracketType: !!config.editor.bracketPairColorization.independentColorPoolPerBracketType
175
};
176
}
177
178
return {
179
isForSimpleWidget: isForSimpleWidget,
180
tabSize: tabSize,
181
indentSize: indentSize,
182
insertSpaces: insertSpaces,
183
detectIndentation: detectIndentation,
184
defaultEOL: newDefaultEOL,
185
trimAutoWhitespace: trimAutoWhitespace,
186
largeFileOptimizations: largeFileOptimizations,
187
bracketPairColorizationOptions
188
};
189
}
190
191
private _getEOL(resource: URI | undefined, language: string): string {
192
if (resource) {
193
return this._resourcePropertiesService.getEOL(resource, language);
194
}
195
const eol = this._configurationService.getValue('files.eol', { overrideIdentifier: language });
196
if (eol && typeof eol === 'string' && eol !== 'auto') {
197
return eol;
198
}
199
return platform.OS === platform.OperatingSystem.Linux || platform.OS === platform.OperatingSystem.Macintosh ? '\n' : '\r\n';
200
}
201
202
private _shouldRestoreUndoStack(): boolean {
203
const result = this._configurationService.getValue('files.restoreUndoStack');
204
if (typeof result === 'boolean') {
205
return result;
206
}
207
return true;
208
}
209
210
public getCreationOptions(languageIdOrSelection: string | ILanguageSelection, resource: URI | undefined, isForSimpleWidget: boolean): ITextModelCreationOptions {
211
const language = (typeof languageIdOrSelection === 'string' ? languageIdOrSelection : languageIdOrSelection.languageId);
212
let creationOptions = this._modelCreationOptionsByLanguageAndResource[language + resource];
213
if (!creationOptions) {
214
const editor = this._configurationService.getValue<IRawEditorConfig>('editor', { overrideIdentifier: language, resource });
215
const eol = this._getEOL(resource, language);
216
creationOptions = ModelService._readModelOptions({ editor, eol }, isForSimpleWidget);
217
this._modelCreationOptionsByLanguageAndResource[language + resource] = creationOptions;
218
}
219
return creationOptions;
220
}
221
222
private _updateModelOptions(e: IConfigurationChangeEvent | undefined): void {
223
const oldOptionsByLanguageAndResource = this._modelCreationOptionsByLanguageAndResource;
224
this._modelCreationOptionsByLanguageAndResource = Object.create(null);
225
226
// Update options on all models
227
const keys = Object.keys(this._models);
228
for (let i = 0, len = keys.length; i < len; i++) {
229
const modelId = keys[i];
230
const modelData = this._models[modelId];
231
const language = modelData.model.getLanguageId();
232
const uri = modelData.model.uri;
233
234
if (e && !e.affectsConfiguration('editor', { overrideIdentifier: language, resource: uri }) && !e.affectsConfiguration('files.eol', { overrideIdentifier: language, resource: uri })) {
235
continue; // perf: skip if this model is not affected by configuration change
236
}
237
238
const oldOptions = oldOptionsByLanguageAndResource[language + uri];
239
const newOptions = this.getCreationOptions(language, uri, modelData.model.isForSimpleWidget);
240
ModelService._setModelOptionsForModel(modelData.model, newOptions, oldOptions);
241
}
242
}
243
244
private static _setModelOptionsForModel(model: ITextModel, newOptions: ITextModelCreationOptions, currentOptions: ITextModelCreationOptions): void {
245
if (currentOptions && currentOptions.defaultEOL !== newOptions.defaultEOL && model.getLineCount() === 1) {
246
model.setEOL(newOptions.defaultEOL === DefaultEndOfLine.LF ? EndOfLineSequence.LF : EndOfLineSequence.CRLF);
247
}
248
249
if (currentOptions
250
&& (currentOptions.detectIndentation === newOptions.detectIndentation)
251
&& (currentOptions.insertSpaces === newOptions.insertSpaces)
252
&& (currentOptions.tabSize === newOptions.tabSize)
253
&& (currentOptions.indentSize === newOptions.indentSize)
254
&& (currentOptions.trimAutoWhitespace === newOptions.trimAutoWhitespace)
255
&& equals(currentOptions.bracketPairColorizationOptions, newOptions.bracketPairColorizationOptions)
256
) {
257
// Same indent opts, no need to touch the model
258
return;
259
}
260
261
if (newOptions.detectIndentation) {
262
model.detectIndentation(newOptions.insertSpaces, newOptions.tabSize);
263
model.updateOptions({
264
trimAutoWhitespace: newOptions.trimAutoWhitespace,
265
bracketColorizationOptions: newOptions.bracketPairColorizationOptions
266
});
267
} else {
268
model.updateOptions({
269
insertSpaces: newOptions.insertSpaces,
270
tabSize: newOptions.tabSize,
271
indentSize: newOptions.indentSize,
272
trimAutoWhitespace: newOptions.trimAutoWhitespace,
273
bracketColorizationOptions: newOptions.bracketPairColorizationOptions
274
});
275
}
276
}
277
278
// --- begin IModelService
279
280
private _insertDisposedModel(disposedModelData: DisposedModelInfo): void {
281
this._disposedModels.set(MODEL_ID(disposedModelData.uri), disposedModelData);
282
this._disposedModelsHeapSize += disposedModelData.heapSize;
283
}
284
285
private _removeDisposedModel(resource: URI): DisposedModelInfo | undefined {
286
const disposedModelData = this._disposedModels.get(MODEL_ID(resource));
287
if (disposedModelData) {
288
this._disposedModelsHeapSize -= disposedModelData.heapSize;
289
}
290
this._disposedModels.delete(MODEL_ID(resource));
291
return disposedModelData;
292
}
293
294
private _ensureDisposedModelsHeapSize(maxModelsHeapSize: number): void {
295
if (this._disposedModelsHeapSize > maxModelsHeapSize) {
296
// we must remove some old undo stack elements to free up some memory
297
const disposedModels: DisposedModelInfo[] = [];
298
this._disposedModels.forEach(entry => {
299
if (!entry.sharesUndoRedoStack) {
300
disposedModels.push(entry);
301
}
302
});
303
disposedModels.sort((a, b) => a.time - b.time);
304
while (disposedModels.length > 0 && this._disposedModelsHeapSize > maxModelsHeapSize) {
305
const disposedModel = disposedModels.shift()!;
306
this._removeDisposedModel(disposedModel.uri);
307
if (disposedModel.initialUndoRedoSnapshot !== null) {
308
this._undoRedoService.restoreSnapshot(disposedModel.initialUndoRedoSnapshot);
309
}
310
}
311
}
312
}
313
314
private _createModelData(value: string | ITextBufferFactory, languageIdOrSelection: string | ILanguageSelection, resource: URI | undefined, isForSimpleWidget: boolean): ModelData {
315
// create & save the model
316
const options = this.getCreationOptions(languageIdOrSelection, resource, isForSimpleWidget);
317
const model: TextModel = this._instantiationService.createInstance(TextModel,
318
value,
319
languageIdOrSelection,
320
options,
321
resource
322
);
323
if (resource && this._disposedModels.has(MODEL_ID(resource))) {
324
const disposedModelData = this._removeDisposedModel(resource)!;
325
const elements = this._undoRedoService.getElements(resource);
326
const sha1Computer = this._getSHA1Computer();
327
const sha1IsEqual = (
328
sha1Computer.canComputeSHA1(model)
329
? sha1Computer.computeSHA1(model) === disposedModelData.sha1
330
: false
331
);
332
if (sha1IsEqual || disposedModelData.sharesUndoRedoStack) {
333
for (const element of elements.past) {
334
if (isEditStackElement(element) && element.matchesResource(resource)) {
335
element.setModel(model);
336
}
337
}
338
for (const element of elements.future) {
339
if (isEditStackElement(element) && element.matchesResource(resource)) {
340
element.setModel(model);
341
}
342
}
343
this._undoRedoService.setElementsValidFlag(resource, true, (element) => (isEditStackElement(element) && element.matchesResource(resource)));
344
if (sha1IsEqual) {
345
model._overwriteVersionId(disposedModelData.versionId);
346
model._overwriteAlternativeVersionId(disposedModelData.alternativeVersionId);
347
model._overwriteInitialUndoRedoSnapshot(disposedModelData.initialUndoRedoSnapshot);
348
}
349
} else {
350
if (disposedModelData.initialUndoRedoSnapshot !== null) {
351
this._undoRedoService.restoreSnapshot(disposedModelData.initialUndoRedoSnapshot);
352
}
353
}
354
}
355
const modelId = MODEL_ID(model.uri);
356
357
if (this._models[modelId]) {
358
// There already exists a model with this id => this is a programmer error
359
throw new Error('ModelService: Cannot add model because it already exists!');
360
}
361
362
const modelData = new ModelData(
363
model,
364
(model) => this._onWillDispose(model),
365
(model, e) => this._onDidChangeLanguage(model, e)
366
);
367
this._models[modelId] = modelData;
368
369
return modelData;
370
}
371
372
public updateModel(model: ITextModel, value: string | ITextBufferFactory, reason: TextModelEditSource = EditSources.unknown({ name: 'updateModel' })): void {
373
const options = this.getCreationOptions(model.getLanguageId(), model.uri, model.isForSimpleWidget);
374
const { textBuffer, disposable } = createTextBuffer(value, options.defaultEOL);
375
376
// Return early if the text is already set in that form
377
if (model.equalsTextBuffer(textBuffer)) {
378
disposable.dispose();
379
return;
380
}
381
382
// Otherwise find a diff between the values and update model
383
model.pushStackElement();
384
model.pushEOL(textBuffer.getEOL() === '\r\n' ? EndOfLineSequence.CRLF : EndOfLineSequence.LF);
385
model.pushEditOperations(
386
[],
387
ModelService._computeEdits(model, textBuffer),
388
() => [],
389
undefined,
390
reason
391
);
392
model.pushStackElement();
393
disposable.dispose();
394
}
395
396
private static _commonPrefix(a: ITextModel, aLen: number, aDelta: number, b: ITextBuffer, bLen: number, bDelta: number): number {
397
const maxResult = Math.min(aLen, bLen);
398
399
let result = 0;
400
for (let i = 0; i < maxResult && a.getLineContent(aDelta + i) === b.getLineContent(bDelta + i); i++) {
401
result++;
402
}
403
return result;
404
}
405
406
private static _commonSuffix(a: ITextModel, aLen: number, aDelta: number, b: ITextBuffer, bLen: number, bDelta: number): number {
407
const maxResult = Math.min(aLen, bLen);
408
409
let result = 0;
410
for (let i = 0; i < maxResult && a.getLineContent(aDelta + aLen - i) === b.getLineContent(bDelta + bLen - i); i++) {
411
result++;
412
}
413
return result;
414
}
415
416
/**
417
* Compute edits to bring `model` to the state of `textSource`.
418
*/
419
public static _computeEdits(model: ITextModel, textBuffer: ITextBuffer): ISingleEditOperation[] {
420
const modelLineCount = model.getLineCount();
421
const textBufferLineCount = textBuffer.getLineCount();
422
const commonPrefix = this._commonPrefix(model, modelLineCount, 1, textBuffer, textBufferLineCount, 1);
423
424
if (modelLineCount === textBufferLineCount && commonPrefix === modelLineCount) {
425
// equality case
426
return [];
427
}
428
429
const commonSuffix = this._commonSuffix(model, modelLineCount - commonPrefix, commonPrefix, textBuffer, textBufferLineCount - commonPrefix, commonPrefix);
430
431
let oldRange: Range;
432
let newRange: Range;
433
if (commonSuffix > 0) {
434
oldRange = new Range(commonPrefix + 1, 1, modelLineCount - commonSuffix + 1, 1);
435
newRange = new Range(commonPrefix + 1, 1, textBufferLineCount - commonSuffix + 1, 1);
436
} else if (commonPrefix > 0) {
437
oldRange = new Range(commonPrefix, model.getLineMaxColumn(commonPrefix), modelLineCount, model.getLineMaxColumn(modelLineCount));
438
newRange = new Range(commonPrefix, 1 + textBuffer.getLineLength(commonPrefix), textBufferLineCount, 1 + textBuffer.getLineLength(textBufferLineCount));
439
} else {
440
oldRange = new Range(1, 1, modelLineCount, model.getLineMaxColumn(modelLineCount));
441
newRange = new Range(1, 1, textBufferLineCount, 1 + textBuffer.getLineLength(textBufferLineCount));
442
}
443
444
return [EditOperation.replaceMove(oldRange, textBuffer.getValueInRange(newRange, EndOfLinePreference.TextDefined))];
445
}
446
447
public createModel(value: string | ITextBufferFactory, languageSelection: ILanguageSelection | null, resource?: URI, isForSimpleWidget: boolean = false): ITextModel {
448
let modelData: ModelData;
449
450
if (languageSelection) {
451
modelData = this._createModelData(value, languageSelection, resource, isForSimpleWidget);
452
} else {
453
modelData = this._createModelData(value, PLAINTEXT_LANGUAGE_ID, resource, isForSimpleWidget);
454
}
455
456
this._onModelAdded.fire(modelData.model);
457
458
return modelData.model;
459
}
460
461
public destroyModel(resource: URI): void {
462
// We need to support that not all models get disposed through this service (i.e. model.dispose() should work!)
463
const modelData = this._models[MODEL_ID(resource)];
464
if (!modelData) {
465
return;
466
}
467
modelData.model.dispose();
468
}
469
470
public getModels(): ITextModel[] {
471
const ret: ITextModel[] = [];
472
473
const keys = Object.keys(this._models);
474
for (let i = 0, len = keys.length; i < len; i++) {
475
const modelId = keys[i];
476
ret.push(this._models[modelId].model);
477
}
478
479
return ret;
480
}
481
482
public getModel(resource: URI): ITextModel | null {
483
const modelId = MODEL_ID(resource);
484
const modelData = this._models[modelId];
485
if (!modelData) {
486
return null;
487
}
488
return modelData.model;
489
}
490
491
// --- end IModelService
492
493
protected _schemaShouldMaintainUndoRedoElements(resource: URI) {
494
return (
495
resource.scheme === Schemas.file
496
|| resource.scheme === Schemas.vscodeRemote
497
|| resource.scheme === Schemas.vscodeUserData
498
|| resource.scheme === Schemas.vscodeNotebookCell
499
|| resource.scheme === 'fake-fs' // for tests
500
);
501
}
502
503
private _onWillDispose(model: ITextModel): void {
504
const modelId = MODEL_ID(model.uri);
505
const modelData = this._models[modelId];
506
507
const sharesUndoRedoStack = (this._undoRedoService.getUriComparisonKey(model.uri) !== model.uri.toString());
508
let maintainUndoRedoStack = false;
509
let heapSize = 0;
510
if (sharesUndoRedoStack || (this._shouldRestoreUndoStack() && this._schemaShouldMaintainUndoRedoElements(model.uri))) {
511
const elements = this._undoRedoService.getElements(model.uri);
512
if (elements.past.length > 0 || elements.future.length > 0) {
513
for (const element of elements.past) {
514
if (isEditStackElement(element) && element.matchesResource(model.uri)) {
515
maintainUndoRedoStack = true;
516
heapSize += element.heapSize(model.uri);
517
element.setModel(model.uri); // remove reference from text buffer instance
518
}
519
}
520
for (const element of elements.future) {
521
if (isEditStackElement(element) && element.matchesResource(model.uri)) {
522
maintainUndoRedoStack = true;
523
heapSize += element.heapSize(model.uri);
524
element.setModel(model.uri); // remove reference from text buffer instance
525
}
526
}
527
}
528
}
529
530
const maxMemory = ModelService.MAX_MEMORY_FOR_CLOSED_FILES_UNDO_STACK;
531
const sha1Computer = this._getSHA1Computer();
532
if (!maintainUndoRedoStack) {
533
if (!sharesUndoRedoStack) {
534
const initialUndoRedoSnapshot = modelData.model.getInitialUndoRedoSnapshot();
535
if (initialUndoRedoSnapshot !== null) {
536
this._undoRedoService.restoreSnapshot(initialUndoRedoSnapshot);
537
}
538
}
539
} else if (!sharesUndoRedoStack && (heapSize > maxMemory || !sha1Computer.canComputeSHA1(model))) {
540
// the undo stack for this file would never fit in the configured memory or the file is very large, so don't bother with it.
541
const initialUndoRedoSnapshot = modelData.model.getInitialUndoRedoSnapshot();
542
if (initialUndoRedoSnapshot !== null) {
543
this._undoRedoService.restoreSnapshot(initialUndoRedoSnapshot);
544
}
545
} else {
546
this._ensureDisposedModelsHeapSize(maxMemory - heapSize);
547
// We only invalidate the elements, but they remain in the undo-redo service.
548
this._undoRedoService.setElementsValidFlag(model.uri, false, (element) => (isEditStackElement(element) && element.matchesResource(model.uri)));
549
this._insertDisposedModel(new DisposedModelInfo(model.uri, modelData.model.getInitialUndoRedoSnapshot(), Date.now(), sharesUndoRedoStack, heapSize, sha1Computer.computeSHA1(model), model.getVersionId(), model.getAlternativeVersionId()));
550
}
551
552
delete this._models[modelId];
553
modelData.dispose();
554
555
// clean up cache
556
delete this._modelCreationOptionsByLanguageAndResource[model.getLanguageId() + model.uri];
557
558
this._onModelRemoved.fire(model);
559
}
560
561
private _onDidChangeLanguage(model: ITextModel, e: IModelLanguageChangedEvent): void {
562
const oldLanguageId = e.oldLanguage;
563
const newLanguageId = model.getLanguageId();
564
const oldOptions = this.getCreationOptions(oldLanguageId, model.uri, model.isForSimpleWidget);
565
const newOptions = this.getCreationOptions(newLanguageId, model.uri, model.isForSimpleWidget);
566
ModelService._setModelOptionsForModel(model, newOptions, oldOptions);
567
this._onModelModeChanged.fire({ model, oldLanguageId: oldLanguageId });
568
}
569
570
protected _getSHA1Computer(): ITextModelSHA1Computer {
571
return new DefaultModelSHA1Computer();
572
}
573
}
574
575
export interface ITextModelSHA1Computer {
576
canComputeSHA1(model: ITextModel): boolean;
577
computeSHA1(model: ITextModel): string;
578
}
579
580
export class DefaultModelSHA1Computer implements ITextModelSHA1Computer {
581
582
public static MAX_MODEL_SIZE = 10 * 1024 * 1024; // takes 200ms to compute a sha1 on a 10MB model on a new machine
583
584
canComputeSHA1(model: ITextModel): boolean {
585
return (model.getValueLength() <= DefaultModelSHA1Computer.MAX_MODEL_SIZE);
586
}
587
588
computeSHA1(model: ITextModel): string {
589
// compute the sha1
590
const shaComputer = new StringSHA1();
591
const snapshot = model.createSnapshot();
592
let text: string | null;
593
while ((text = snapshot.read())) {
594
shaComputer.update(text);
595
}
596
return shaComputer.digest();
597
}
598
}
599
600