@@ -20,11 +20,11 @@ import { DisposableCollection, Disposable } from '@theia/core/lib/common/disposa
2020import { Emitter , Event } from '@theia/core/lib/common/event' ;
2121import { CancellationTokenSource , CancellationToken } from '@theia/core/lib/common/cancellation' ;
2222import { Resource , ResourceError , ResourceVersion } from '@theia/core/lib/common/resource' ;
23- import { Saveable , SaveOptions } from '@theia/core/lib/browser/saveable' ;
23+ import { Saveable , SaveOptions , SaveReason } from '@theia/core/lib/browser/saveable' ;
2424import { MonacoToProtocolConverter } from './monaco-to-protocol-converter' ;
2525import { ProtocolToMonacoConverter } from './protocol-to-monaco-converter' ;
2626import { ILogger , Loggable , Log } from '@theia/core/lib/common/logger' ;
27- import { IIdentifiedSingleEditOperation , ITextBufferFactory , ITextModel , ITextSnapshot } from '@theia/monaco-editor-core/esm/vs/editor/common/model' ;
27+ import { ITextBufferFactory , ITextModel , ITextSnapshot } from '@theia/monaco-editor-core/esm/vs/editor/common/model' ;
2828import { IResolvedTextEditorModel } from '@theia/monaco-editor-core/esm/vs/editor/common/services/resolverService' ;
2929import * as monaco from '@theia/monaco-editor-core' ;
3030import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices' ;
@@ -39,12 +39,7 @@ export {
3939 TextDocumentSaveReason
4040} ;
4141
42- export interface WillSaveMonacoModelEvent {
43- readonly model : MonacoEditorModel ;
44- readonly reason : TextDocumentSaveReason ;
45- readonly options ?: SaveOptions ;
46- waitUntil ( thenable : Thenable < IIdentifiedSingleEditOperation [ ] > ) : void ;
47- }
42+ export type WillSaveMonacoModelListener = ( model : MonacoEditorModel , token : CancellationToken , options ?: SaveOptions ) => Promise < void > ;
4843
4944export interface MonacoModelContentChangedEvent {
5045 readonly model : MonacoEditorModel ;
@@ -83,9 +78,6 @@ export class MonacoEditorModel implements IResolvedTextEditorModel, TextEditorDo
8378 protected readonly onDidSaveModelEmitter = new Emitter < ITextModel > ( ) ;
8479 readonly onDidSaveModel = this . onDidSaveModelEmitter . event ;
8580
86- protected readonly onWillSaveModelEmitter = new Emitter < WillSaveMonacoModelEvent > ( ) ;
87- readonly onWillSaveModel = this . onWillSaveModelEmitter . event ;
88-
8981 protected readonly onDidChangeValidEmitter = new Emitter < void > ( ) ;
9082 readonly onDidChangeValid = this . onDidChangeValidEmitter . event ;
9183
@@ -99,6 +91,8 @@ export class MonacoEditorModel implements IResolvedTextEditorModel, TextEditorDo
9991
10092 protected resourceVersion : ResourceVersion | undefined ;
10193
94+ protected readonly willSaveModelListeners : WillSaveMonacoModelListener [ ] = [ ] ;
95+
10296 constructor (
10397 protected readonly resource : Resource ,
10498 protected readonly m2p : MonacoToProtocolConverter ,
@@ -110,7 +104,6 @@ export class MonacoEditorModel implements IResolvedTextEditorModel, TextEditorDo
110104 this . toDispose . push ( this . toDisposeOnAutoSave ) ;
111105 this . toDispose . push ( this . onDidChangeContentEmitter ) ;
112106 this . toDispose . push ( this . onDidSaveModelEmitter ) ;
113- this . toDispose . push ( this . onWillSaveModelEmitter ) ;
114107 this . toDispose . push ( this . onDirtyChangedEmitter ) ;
115108 this . toDispose . push ( this . onDidChangeValidEmitter ) ;
116109 this . toDispose . push ( Disposable . create ( ( ) => this . cancelSave ( ) ) ) ;
@@ -154,7 +147,7 @@ export class MonacoEditorModel implements IResolvedTextEditorModel, TextEditorDo
154147 if ( mode === EncodingMode . Decode ) {
155148 return this . sync ( ) ;
156149 }
157- return this . scheduleSave ( TextDocumentSaveReason . Manual , this . cancelSave ( ) , true ) ;
150+ return this . scheduleSave ( this . cancelSave ( ) , true , { saveReason : SaveReason . Manual } ) ;
158151 }
159152
160153 getEncoding ( ) : string | undefined {
@@ -386,7 +379,10 @@ export class MonacoEditorModel implements IResolvedTextEditorModel, TextEditorDo
386379 }
387380
388381 save ( options ?: SaveOptions ) : Promise < void > {
389- return this . scheduleSave ( options ?. saveReason ?? TextDocumentSaveReason . Manual , undefined , undefined , options ) ;
382+ return this . scheduleSave ( undefined , undefined , {
383+ saveReason : TextDocumentSaveReason . Manual ,
384+ ...options
385+ } ) ;
390386 }
391387
392388 protected pendingOperation = Promise . resolve ( ) ;
@@ -485,8 +481,8 @@ export class MonacoEditorModel implements IResolvedTextEditorModel, TextEditorDo
485481 return this . saveCancellationTokenSource . token ;
486482 }
487483
488- protected scheduleSave ( reason : TextDocumentSaveReason , token : CancellationToken = this . cancelSave ( ) , overwriteEncoding ?: boolean , options ?: SaveOptions ) : Promise < void > {
489- return this . run ( ( ) => this . doSave ( reason , token , overwriteEncoding , options ) ) ;
484+ protected scheduleSave ( token : CancellationToken = this . cancelSave ( ) , overwriteEncoding ?: boolean , options ?: SaveOptions ) : Promise < void > {
485+ return this . run ( ( ) => this . doSave ( token , overwriteEncoding , options ) ) ;
490486 }
491487
492488 protected ignoreContentChanges = false ;
@@ -546,18 +542,18 @@ export class MonacoEditorModel implements IResolvedTextEditorModel, TextEditorDo
546542 }
547543 }
548544
549- protected async doSave ( reason : TextDocumentSaveReason , token : CancellationToken , overwriteEncoding ?: boolean , options ?: SaveOptions ) : Promise < void > {
545+ protected async doSave ( token : CancellationToken , overwriteEncoding ?: boolean , options ?: SaveOptions ) : Promise < void > {
550546 if ( token . isCancellationRequested || ! this . resource . saveContents ) {
551547 return ;
552548 }
553549
554- await this . fireWillSaveModel ( reason , token , options ) ;
550+ await this . fireWillSaveModel ( token , options ) ;
555551 if ( token . isCancellationRequested ) {
556552 return ;
557553 }
558554
559555 const changes = [ ...this . contentChanges ] ;
560- if ( ( changes . length === 0 && ! this . resource . initiallyDirty ) && ! overwriteEncoding && reason !== TextDocumentSaveReason . Manual ) {
556+ if ( ( changes . length === 0 && ! this . resource . initiallyDirty ) && ! overwriteEncoding && options ?. saveReason !== TextDocumentSaveReason . Manual ) {
561557 return ;
562558 }
563559
@@ -586,64 +582,19 @@ export class MonacoEditorModel implements IResolvedTextEditorModel, TextEditorDo
586582 }
587583 }
588584
589- protected async fireWillSaveModel ( reason : TextDocumentSaveReason , token : CancellationToken , options ?: SaveOptions ) : Promise < void > {
590- type EditContributor = Thenable < monaco . editor . IIdentifiedSingleEditOperation [ ] > ;
591-
592- const firing = this . onWillSaveModelEmitter . sequence ( async listener => {
593- if ( token . isCancellationRequested ) {
594- return false ;
595- }
596- const waitables : EditContributor [ ] = [ ] ;
597- const { version } = this ;
598-
599- const event = {
600- model : this , reason, options,
601- waitUntil : ( thenable : EditContributor ) => {
602- if ( Object . isFrozen ( waitables ) ) {
603- throw new Error ( 'waitUntil cannot be called asynchronously.' ) ;
604- }
605- waitables . push ( thenable ) ;
606- }
607- } ;
608-
609- // Fire.
610- try {
611- listener ( event ) ;
612- } catch ( err ) {
613- console . error ( err ) ;
614- return true ;
615- }
616-
617- // Asynchronous calls to `waitUntil` should fail.
618- Object . freeze ( waitables ) ;
619-
620- // Wait for all promises.
621- const edits = await Promise . all ( waitables ) . then ( allOperations =>
622- ( [ ] as monaco . editor . IIdentifiedSingleEditOperation [ ] ) . concat ( ...allOperations )
623- ) ;
624- if ( token . isCancellationRequested ) {
625- return false ;
626- }
627-
628- // In a perfect world, we should only apply edits if document is clean.
629- if ( version !== this . version ) {
630- console . error ( 'onWillSave listeners should provide edits, not directly alter the document.' ) ;
631- }
632-
633- // Finally apply edits provided by this listener before firing the next.
634- if ( edits && edits . length > 0 ) {
635- this . applyEdits ( edits , {
636- ignoreDirty : true ,
637- } ) ;
585+ registerWillSaveModelListener ( listener : WillSaveMonacoModelListener ) : Disposable {
586+ this . willSaveModelListeners . push ( listener ) ;
587+ return Disposable . create ( ( ) => {
588+ const index = this . willSaveModelListeners . indexOf ( listener ) ;
589+ if ( index >= 0 ) {
590+ this . willSaveModelListeners . splice ( index , 1 ) ;
638591 }
639-
640- return true ;
641592 } ) ;
593+ }
642594
643- try {
644- await firing ;
645- } catch ( e ) {
646- console . error ( e ) ;
595+ protected async fireWillSaveModel ( token : CancellationToken , options ?: SaveOptions ) : Promise < void > {
596+ for ( const listener of this . willSaveModelListeners ) {
597+ await listener ( this , token , options ) ;
647598 }
648599 }
649600
0 commit comments