33 * Licensed under the MIT License. See License.txt in the project root for license information.
44 *--------------------------------------------------------------------------------------------*/
55
6- import * as nls from 'vs/nls' ;
76import { Emitter , Event } from 'vs/base/common/event' ;
87import { Disposable , IDisposable , DisposableStore , dispose } from 'vs/base/common/lifecycle' ;
98import * as platform from 'vs/base/common/platform' ;
@@ -28,13 +27,9 @@ import { ILogService } from 'vs/platform/log/common/log';
2827import { IUndoRedoService , IUndoRedoElement , IPastFutureElements } from 'vs/platform/undoRedo/common/undoRedo' ;
2928import { StringSHA1 } from 'vs/base/common/hash' ;
3029import { SingleModelEditStackElement , MultiModelEditStackElement , EditStackElement } from 'vs/editor/common/model/editStack' ;
31- import { IDialogService } from 'vs/platform/dialogs/common/dialogs' ;
3230import { Schemas } from 'vs/base/common/network' ;
33- import Severity from 'vs/base/common/severity' ;
3431import { SemanticTokensProviderStyling , toMultilineTokens2 } from 'vs/editor/common/services/semanticTokensProviderStyling' ;
3532
36- export const MAINTAIN_UNDO_REDO_STACK = true ;
37-
3833export interface IEditorSemanticHighlightingOptions {
3934 enabled ?: boolean ;
4035}
@@ -143,6 +138,8 @@ function isEditStackElements(elements: IUndoRedoElement[]): elements is EditStac
143138class DisposedModelInfo {
144139 constructor (
145140 public readonly uri : URI ,
141+ public readonly time : number ,
142+ public readonly heapSize : number ,
146143 public readonly sha1 : string ,
147144 public readonly versionId : number ,
148145 public readonly alternativeVersionId : number ,
@@ -151,8 +148,6 @@ class DisposedModelInfo {
151148
152149export class ModelServiceImpl extends Disposable implements IModelService {
153150
154- private static _PROMPT_UNDO_REDO_SIZE_LIMIT = 10 * 1024 * 1024 ; // 10MB
155-
156151 public _serviceBrand : undefined ;
157152
158153 private readonly _onModelAdded : Emitter < ITextModel > = this . _register ( new Emitter < ITextModel > ( ) ) ;
@@ -171,6 +166,7 @@ export class ModelServiceImpl extends Disposable implements IModelService {
171166 */
172167 private readonly _models : { [ modelId : string ] : ModelData ; } ;
173168 private readonly _disposedModels : Map < string , DisposedModelInfo > ;
169+ private _disposedModelsHeapSize : number ;
174170 private readonly _semanticStyling : SemanticStyling ;
175171
176172 constructor (
@@ -179,12 +175,12 @@ export class ModelServiceImpl extends Disposable implements IModelService {
179175 @IThemeService private readonly _themeService : IThemeService ,
180176 @ILogService private readonly _logService : ILogService ,
181177 @IUndoRedoService private readonly _undoRedoService : IUndoRedoService ,
182- @IDialogService private readonly _dialogService : IDialogService ,
183178 ) {
184179 super ( ) ;
185180 this . _modelCreationOptionsByLanguageAndResource = Object . create ( null ) ;
186181 this . _models = { } ;
187182 this . _disposedModels = new Map < string , DisposedModelInfo > ( ) ;
183+ this . _disposedModelsHeapSize = 0 ;
188184 this . _semanticStyling = this . _register ( new SemanticStyling ( this . _themeService , this . _logService ) ) ;
189185
190186 this . _register ( this . _configurationService . onDidChangeConfiguration ( ( ) => this . _updateModelOptions ( ) ) ) ;
@@ -267,6 +263,14 @@ export class ModelServiceImpl extends Disposable implements IModelService {
267263 return platform . OS === platform . OperatingSystem . Linux || platform . OS === platform . OperatingSystem . Macintosh ? '\n' : '\r\n' ;
268264 }
269265
266+ private _getMaxMemoryForClosedFilesUndoStack ( ) : number {
267+ const result = this . _configurationService . getValue < number > ( 'files.maxMemoryForClosedFilesUndoStackMB' ) ;
268+ if ( typeof result === 'number' ) {
269+ return result * 1024 * 1024 ;
270+ }
271+ return 20 * 1024 * 1024 ;
272+ }
273+
270274 public getCreationOptions ( language : string , resource : URI | undefined , isForSimpleWidget : boolean ) : ITextModelCreationOptions {
271275 let creationOptions = this . _modelCreationOptionsByLanguageAndResource [ language + resource ] ;
272276 if ( ! creationOptions ) {
@@ -328,13 +332,40 @@ export class ModelServiceImpl extends Disposable implements IModelService {
328332
329333 // --- begin IModelService
330334
335+ private _insertDisposedModel ( disposedModelData : DisposedModelInfo ) : void {
336+ this . _disposedModels . set ( MODEL_ID ( disposedModelData . uri ) , disposedModelData ) ;
337+ this . _disposedModelsHeapSize += disposedModelData . heapSize ;
338+ }
339+
340+ private _removeDisposedModel ( resource : URI ) : DisposedModelInfo | undefined {
341+ const disposedModelData = this . _disposedModels . get ( MODEL_ID ( resource ) ) ;
342+ if ( disposedModelData ) {
343+ this . _disposedModelsHeapSize -= disposedModelData . heapSize ;
344+ }
345+ this . _disposedModels . delete ( MODEL_ID ( resource ) ) ;
346+ return disposedModelData ;
347+ }
348+
349+ private _ensureDisposedModelsHeapSize ( maxModelsHeapSize : number ) : void {
350+ if ( this . _disposedModelsHeapSize > maxModelsHeapSize ) {
351+ // we must remove some old undo stack elements to free up some memory
352+ const disposedModels : DisposedModelInfo [ ] = [ ] ;
353+ this . _disposedModels . forEach ( entry => disposedModels . push ( entry ) ) ;
354+ disposedModels . sort ( ( a , b ) => a . time - b . time ) ;
355+ while ( disposedModels . length > 0 && this . _disposedModelsHeapSize > maxModelsHeapSize ) {
356+ const disposedModel = disposedModels . shift ( ) ! ;
357+ this . _removeDisposedModel ( disposedModel . uri ) ;
358+ this . _undoRedoService . removeElements ( disposedModel . uri ) ;
359+ }
360+ }
361+ }
362+
331363 private _createModelData ( value : string | ITextBufferFactory , languageIdentifier : LanguageIdentifier , resource : URI | undefined , isForSimpleWidget : boolean ) : ModelData {
332364 // create & save the model
333365 const options = this . getCreationOptions ( languageIdentifier . language , resource , isForSimpleWidget ) ;
334366 const model : TextModel = new TextModel ( value , options , languageIdentifier , resource , this . _undoRedoService ) ;
335367 if ( resource && this . _disposedModels . has ( MODEL_ID ( resource ) ) ) {
336- const disposedModelData = this . _disposedModels . get ( MODEL_ID ( resource ) ) ! ;
337- this . _disposedModels . delete ( MODEL_ID ( resource ) ) ;
368+ const disposedModelData = this . _removeDisposedModel ( resource ) ! ;
338369 const elements = this . _undoRedoService . getElements ( resource ) ;
339370 if ( computeModelSha1 ( model ) === disposedModelData . sha1 && isEditStackPastFutureElements ( elements ) ) {
340371 for ( const element of elements . past ) {
@@ -473,7 +504,7 @@ export class ModelServiceImpl extends Disposable implements IModelService {
473504 const model = modelData . model ;
474505 let maintainUndoRedoStack = false ;
475506 let heapSize = 0 ;
476- if ( MAINTAIN_UNDO_REDO_STACK && ( resource . scheme === Schemas . file || resource . scheme === Schemas . vscodeRemote || resource . scheme === Schemas . userData ) ) {
507+ if ( resource . scheme === Schemas . file || resource . scheme === Schemas . vscodeRemote || resource . scheme === Schemas . userData ) {
477508 const elements = this . _undoRedoService . getElements ( resource ) ;
478509 if ( ( elements . past . length > 0 || elements . future . length > 0 ) && isEditStackPastFutureElements ( elements ) ) {
479510 maintainUndoRedoStack = true ;
@@ -490,37 +521,27 @@ export class ModelServiceImpl extends Disposable implements IModelService {
490521 }
491522 }
492523
493- if ( maintainUndoRedoStack ) {
494- // We only invalidate the elements, but they remain in the undo-redo service.
495- this . _undoRedoService . setElementsIsValid ( resource , false ) ;
496- this . _disposedModels . set ( MODEL_ID ( resource ) , new DisposedModelInfo ( resource , computeModelSha1 ( model ) , model . getVersionId ( ) , model . getAlternativeVersionId ( ) ) ) ;
497- } else {
524+ if ( ! maintainUndoRedoStack ) {
498525 this . _undoRedoService . removeElements ( resource ) ;
526+ modelData . model . dispose ( ) ;
527+ return ;
499528 }
500529
501- modelData . model . dispose ( ) ;
502-
503- // After disposing the model, prompt and ask if we should keep the undo-redo stack
504- if ( maintainUndoRedoStack && heapSize > ModelServiceImpl . _PROMPT_UNDO_REDO_SIZE_LIMIT ) {
505- const mbSize = ( heapSize / 1024 / 1024 ) . toFixed ( 1 ) ;
506- this . _dialogService . show (
507- Severity . Info ,
508- nls . localize ( 'undoRedoConfirm' , "Keep the undo-redo stack for {0} in memory ({1} MB)?" , ( resource . scheme === Schemas . file ? resource . fsPath : resource . path ) , mbSize ) ,
509- [
510- nls . localize ( 'nok' , "Discard" ) ,
511- nls . localize ( 'ok' , "Keep" ) ,
512- ] ,
513- {
514- cancelId : 2
515- }
516- ) . then ( ( result ) => {
517- const discard = ( result . choice === 2 || result . choice === 0 ) ;
518- if ( discard ) {
519- this . _disposedModels . delete ( MODEL_ID ( resource ) ) ;
520- this . _undoRedoService . removeElements ( resource ) ;
521- }
522- } ) ;
530+ const maxMemory = this . _getMaxMemoryForClosedFilesUndoStack ( ) ;
531+ if ( heapSize > maxMemory ) {
532+ // the undo stack for this file would never fit in the configured memory, so don't bother with it.
533+ this . _undoRedoService . removeElements ( resource ) ;
534+ modelData . model . dispose ( ) ;
535+ return ;
523536 }
537+
538+ this . _ensureDisposedModelsHeapSize ( maxMemory - heapSize ) ;
539+
540+ // We only invalidate the elements, but they remain in the undo-redo service.
541+ this . _undoRedoService . setElementsIsValid ( resource , false ) ;
542+ this . _insertDisposedModel ( new DisposedModelInfo ( resource , Date . now ( ) , heapSize , computeModelSha1 ( model ) , model . getVersionId ( ) , model . getAlternativeVersionId ( ) ) ) ;
543+
544+ modelData . model . dispose ( ) ;
524545 }
525546
526547 public getModels ( ) : ITextModel [ ] {
0 commit comments