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