66import { URI , UriComponents } from 'vs/base/common/uri' ;
77import { IEditor } from 'vs/editor/common/editorCommon' ;
88import { ITextEditorOptions , IResourceEditorInput , TextEditorSelectionRevealType , IEditorOptions } from 'vs/platform/editor/common/editor' ;
9- import { IEditorInput , IEditorPane , Extensions as EditorExtensions , EditorInput , IEditorCloseEvent , IEditorInputFactoryRegistry , toResource , IEditorIdentifier , GroupIdentifier , EditorsOrder } from 'vs/workbench/common/editor' ;
9+ import { IEditorInput , IEditorPane , Extensions as EditorExtensions , EditorInput , IEditorCloseEvent , IEditorInputFactoryRegistry , toResource , IEditorIdentifier , GroupIdentifier , EditorsOrder , SideBySideEditor } from 'vs/workbench/common/editor' ;
1010import { IEditorService } from 'vs/workbench/services/editor/common/editorService' ;
1111import { IHistoryService } from 'vs/workbench/services/history/common/history' ;
1212import { FileChangesEvent , IFileService , FileChangeType } from 'vs/platform/files/common/files' ;
@@ -17,21 +17,20 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag
1717import { Registry } from 'vs/platform/registry/common/platform' ;
1818import { Event } from 'vs/base/common/event' ;
1919import { IConfigurationService } from 'vs/platform/configuration/common/configuration' ;
20- import { IEditorGroupsService , IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService' ;
20+ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService' ;
2121import { getCodeEditor , ICodeEditor } from 'vs/editor/browser/editorBrowser' ;
2222import { createResourceExcludeMatcher } from 'vs/workbench/services/search/common/search' ;
2323import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents' ;
2424import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation' ;
2525import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor' ;
2626import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService' ;
2727import { IContextKeyService , RawContextKey } from 'vs/platform/contextkey/common/contextkey' ;
28- import { coalesce } from 'vs/base/common/arrays' ;
28+ import { coalesce , remove } from 'vs/base/common/arrays' ;
2929import { registerSingleton } from 'vs/platform/instantiation/common/extensions' ;
3030import { withNullAsUndefined } from 'vs/base/common/types' ;
3131import { addDisposableListener , EventType , EventHelper } from 'vs/base/browser/dom' ;
3232import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces' ;
3333import { Schemas } from 'vs/base/common/network' ;
34- import { isEqual } from 'vs/base/common/resources' ;
3534import { onUnexpectedError } from 'vs/base/common/errors' ;
3635
3736/**
@@ -85,8 +84,10 @@ interface IStackEntry {
8584 selection ?: Selection ;
8685}
8786
88- interface IRecentlyClosedFile {
89- resource : URI ;
87+ interface IRecentlyClosedEditor {
88+ resource : URI | undefined ;
89+ associatedResources : URI [ ] ;
90+ serialized : { typeId : string , value : string } ;
9091 index : number ;
9192 sticky : boolean ;
9293}
@@ -101,6 +102,8 @@ export class HistoryService extends Disposable implements IHistoryService {
101102 private readonly editorHistoryListeners = new Map ( ) ;
102103 private readonly editorStackListeners = new Map ( ) ;
103104
105+ private readonly editorInputFactory = Registry . as < IEditorInputFactoryRegistry > ( EditorExtensions . EditorInputFactories ) ;
106+
104107 constructor (
105108 @IEditorService private readonly editorService : EditorServiceImpl ,
106109 @IEditorGroupsService private readonly editorGroupService : IEditorGroupsService ,
@@ -260,7 +263,7 @@ export class HistoryService extends Disposable implements IHistoryService {
260263 remove ( arg1 : IEditorInput | IResourceEditorInput | FileChangesEvent ) : void {
261264 this . removeFromHistory ( arg1 ) ;
262265 this . removeFromNavigationStack ( arg1 ) ;
263- this . removeFromRecentlyClosedFiles ( arg1 ) ;
266+ this . removeFromRecentlyClosedEditors ( arg1 ) ;
264267 this . removeFromRecentlyOpened ( arg1 ) ;
265268 }
266269
@@ -286,8 +289,8 @@ export class HistoryService extends Disposable implements IHistoryService {
286289 this . editorStackListeners . forEach ( listeners => dispose ( listeners ) ) ;
287290 this . editorStackListeners . clear ( ) ;
288291
289- // Closed files
290- this . recentlyClosedFiles = [ ] ;
292+ // Recently closed editors
293+ this . recentlyClosedEditors = [ ] ;
291294
292295 // Context Keys
293296 this . updateContextKeys ( ) ;
@@ -602,88 +605,120 @@ export class HistoryService extends Disposable implements IHistoryService {
602605
603606 //#endregion
604607
605- //#region Recently Closed Files
608+ //#region Recently Closed Editors
606609
607610 private static readonly MAX_RECENTLY_CLOSED_EDITORS = 20 ;
608611
609- private recentlyClosedFiles : IRecentlyClosedFile [ ] = [ ] ;
612+ private recentlyClosedEditors : IRecentlyClosedEditor [ ] = [ ] ;
610613
611614 private onEditorClosed ( event : IEditorCloseEvent ) : void {
615+ const { editor, replaced } = event ;
616+ if ( replaced ) {
617+ return ; // ignore if editor was replaced
618+ }
619+
620+ const factory = this . editorInputFactory . getEditorInputFactory ( editor . getTypeId ( ) ) ;
621+ if ( ! factory || ! factory . canSerialize ( editor ) ) {
622+ return ; // we need a factory from this point that can serialize this editor
623+ }
612624
613- // Track closing of editor to support to reopen closed editors (unless editor was replaced)
614- if ( ! event . replaced ) {
615- const resource = event . editor ? event . editor . resource : undefined ;
616- const supportsReopen = resource && this . fileService . canHandleResource ( resource ) ; // we only support file'ish things to reopen
617- if ( resource && supportsReopen ) {
625+ const serialized = factory . serialize ( editor ) ;
626+ if ( typeof serialized !== 'string' ) {
627+ return ; // we need something to deserialize from
628+ }
618629
619- // Remove all inputs matching and add as last recently closed
620- this . removeFromRecentlyClosedFiles ( event . editor ) ;
621- this . recentlyClosedFiles . push ( { resource, index : event . index , sticky : event . sticky } ) ;
630+ const associatedResources : URI [ ] = [ ] ;
631+ const editorResource = toResource ( editor , { supportSideBySide : SideBySideEditor . BOTH } ) ;
632+ if ( URI . isUri ( editorResource ) ) {
633+ associatedResources . push ( editorResource ) ;
634+ } else if ( editorResource ) {
635+ associatedResources . push ( ...coalesce ( [ editorResource . master , editorResource . detail ] ) ) ;
636+ }
622637
623- // Bounding
624- if ( this . recentlyClosedFiles . length > HistoryService . MAX_RECENTLY_CLOSED_EDITORS ) {
625- this . recentlyClosedFiles . shift ( ) ;
626- }
638+ // Remove from list of recently closed before...
639+ this . removeFromRecentlyClosedEditors ( editor ) ;
627640
628- // Context
629- this . canReopenClosedEditorContextKey . set ( true ) ;
630- }
641+ // ...adding it as last recently closed
642+ this . recentlyClosedEditors . push ( {
643+ resource : editor . resource ,
644+ associatedResources,
645+ serialized : { typeId : editor . getTypeId ( ) , value : serialized } ,
646+ index : event . index ,
647+ sticky : event . sticky
648+ } ) ;
649+
650+ // Bounding
651+ if ( this . recentlyClosedEditors . length > HistoryService . MAX_RECENTLY_CLOSED_EDITORS ) {
652+ this . recentlyClosedEditors . shift ( ) ;
631653 }
654+
655+ // Context
656+ this . canReopenClosedEditorContextKey . set ( true ) ;
632657 }
633658
634659 reopenLastClosedEditor ( ) : void {
635- let lastClosedFile = this . recentlyClosedFiles . pop ( ) ;
636- while ( lastClosedFile && this . containsRecentlyClosedFile ( this . editorGroupService . activeGroup , lastClosedFile ) ) {
637- lastClosedFile = this . recentlyClosedFiles . pop ( ) ; // pop until we find a file that is not opened
638- }
639-
640- if ( lastClosedFile ) {
641- ( async ( ) => {
642- let options : IEditorOptions ;
643- if ( lastClosedFile . sticky ) {
644- // Sticky: in case the target index is outside of the range of
645- // sticky editors, we make sure to not provide the index as
646- // option. Otherwise the index will cause the sticky flag to
647- // be ignored.
648- if ( ! this . editorGroupService . activeGroup . isSticky ( lastClosedFile . index ) ) {
649- options = { pinned : true , sticky : true } ;
650- } else {
651- options = { pinned : true , sticky : true , index : lastClosedFile . index } ;
652- }
653- } else {
654- options = { pinned : true , index : lastClosedFile . index } ;
655- }
656-
657- const editor = await this . editorService . openEditor ( { resource : lastClosedFile . resource , options } ) ;
658660
659- // Fix for https://github.com/Microsoft/vscode/issues/67882
660- // If opening of the editor fails, make sure to try the next one
661- // but make sure to remove this one from the list to prevent
662- // endless loops.
663- if ( ! editor ) {
664- this . recentlyClosedFiles . pop ( ) ;
665- this . reopenLastClosedEditor ( ) ;
666- }
667- } ) ( ) ;
661+ // Open editor if we have one
662+ const lastClosedEditor = this . recentlyClosedEditors . pop ( ) ;
663+ if ( lastClosedEditor ) {
664+ this . doReopenLastClosedEditor ( lastClosedEditor ) ;
668665 }
669666
670- // Context
671- this . canReopenClosedEditorContextKey . set ( this . recentlyClosedFiles . length > 0 ) ;
667+ // Update context
668+ this . canReopenClosedEditorContextKey . set ( this . recentlyClosedEditors . length > 0 ) ;
672669 }
673670
674- private containsRecentlyClosedFile ( group : IEditorGroup , recentlyClosedEditor : IRecentlyClosedFile ) : boolean {
675- for ( const editor of group . editors ) {
676- if ( isEqual ( editor . resource , recentlyClosedEditor . resource ) ) {
677- return true ;
671+ private async doReopenLastClosedEditor ( lastClosedEditor : IRecentlyClosedEditor ) : Promise < void > {
672+
673+ // Determine editor options
674+ let options : IEditorOptions ;
675+ if ( lastClosedEditor . sticky ) {
676+ // Sticky: in case the target index is outside of the range of
677+ // sticky editors, we make sure to not provide the index as
678+ // option. Otherwise the index will cause the sticky flag to
679+ // be ignored.
680+ if ( ! this . editorGroupService . activeGroup . isSticky ( lastClosedEditor . index ) ) {
681+ options = { pinned : true , sticky : true , ignoreError : true } ;
682+ } else {
683+ options = { pinned : true , sticky : true , index : lastClosedEditor . index , ignoreError : true } ;
678684 }
685+ } else {
686+ options = { pinned : true , index : lastClosedEditor . index , ignoreError : true } ;
679687 }
680688
681- return false ;
689+ // Deserialize and open editor unless already opened
690+ const restoredEditor = this . editorInputFactory . getEditorInputFactory ( lastClosedEditor . serialized . typeId ) ?. deserialize ( this . instantiationService , lastClosedEditor . serialized . value ) ;
691+ let editorPane : IEditorPane | undefined = undefined ;
692+ if ( restoredEditor && ! this . editorGroupService . activeGroup . isOpened ( restoredEditor ) ) {
693+ editorPane = await this . editorService . openEditor ( restoredEditor , options ) ;
694+ }
695+
696+ // If no editor was opened, try with the next one
697+ if ( ! editorPane ) {
698+ // Fix for https://github.com/Microsoft/vscode/issues/67882
699+ // If opening of the editor fails, make sure to try the next one
700+ // but make sure to remove this one from the list to prevent
701+ // endless loops.
702+ remove ( this . recentlyClosedEditors , lastClosedEditor ) ;
703+ this . reopenLastClosedEditor ( ) ;
704+ }
682705 }
683706
684- private removeFromRecentlyClosedFiles ( arg1 : IEditorInput | IResourceEditorInput | FileChangesEvent ) : void {
685- this . recentlyClosedFiles = this . recentlyClosedFiles . filter ( e => ! this . matchesFile ( e . resource , arg1 ) ) ;
686- this . canReopenClosedEditorContextKey . set ( this . recentlyClosedFiles . length > 0 ) ;
707+ private removeFromRecentlyClosedEditors ( arg1 : IEditorInput | IResourceEditorInput | FileChangesEvent ) : void {
708+ this . recentlyClosedEditors = this . recentlyClosedEditors . filter ( recentlyClosedEditor => {
709+ if ( recentlyClosedEditor . resource && this . matchesFile ( recentlyClosedEditor . resource , arg1 ) ) {
710+ return false ; // editor matches directly
711+ }
712+
713+ if ( recentlyClosedEditor . associatedResources . some ( associatedResource => this . matchesFile ( associatedResource , arg1 ) ) ) {
714+ return false ; // an associated resource matches
715+ }
716+
717+ return true ;
718+ } ) ;
719+
720+ // Update context
721+ this . canReopenClosedEditorContextKey . set ( this . recentlyClosedEditors . length > 0 ) ;
687722 }
688723
689724 //#endregion
@@ -721,7 +756,7 @@ export class HistoryService extends Disposable implements IHistoryService {
721756 this . canNavigateBackContextKey . set ( this . navigationStack . length > 0 && this . navigationStackIndex > 0 ) ;
722757 this . canNavigateForwardContextKey . set ( this . navigationStack . length > 0 && this . navigationStackIndex < this . navigationStack . length - 1 ) ;
723758 this . canNavigateToLastEditLocationContextKey . set ( ! ! this . lastEditLocation ) ;
724- this . canReopenClosedEditorContextKey . set ( this . recentlyClosedFiles . length > 0 ) ;
759+ this . canReopenClosedEditorContextKey . set ( this . recentlyClosedEditors . length > 0 ) ;
725760 }
726761
727762 //#endregion
@@ -833,18 +868,16 @@ export class HistoryService extends Disposable implements IHistoryService {
833868 }
834869 }
835870
836- const registry = Registry . as < IEditorInputFactoryRegistry > ( EditorExtensions . EditorInputFactories ) ;
837-
838871 return coalesce ( entries . map ( entry => {
839872 try {
840- return this . safeLoadHistoryEntry ( registry , entry ) ;
873+ return this . safeLoadHistoryEntry ( entry ) ;
841874 } catch ( error ) {
842875 return undefined ; // https://github.com/Microsoft/vscode/issues/60960
843876 }
844877 } ) ) ;
845878 }
846879
847- private safeLoadHistoryEntry ( registry : IEditorInputFactoryRegistry , entry : ISerializedEditorHistoryEntry ) : IEditorInput | IResourceEditorInput | undefined {
880+ private safeLoadHistoryEntry ( entry : ISerializedEditorHistoryEntry ) : IEditorInput | IResourceEditorInput | undefined {
848881 const serializedEditorHistoryEntry = entry ;
849882
850883 // File resource: via URI.revive()
@@ -855,7 +888,7 @@ export class HistoryService extends Disposable implements IHistoryService {
855888 // Editor input: via factory
856889 const { editorInputJSON } = serializedEditorHistoryEntry ;
857890 if ( editorInputJSON ?. deserialized ) {
858- const factory = registry . getEditorInputFactory ( editorInputJSON . typeId ) ;
891+ const factory = this . editorInputFactory . getEditorInputFactory ( editorInputJSON . typeId ) ;
859892 if ( factory ) {
860893 const input = factory . deserialize ( this . instantiationService , editorInputJSON . deserialized ) ;
861894 if ( input ) {
@@ -874,13 +907,11 @@ export class HistoryService extends Disposable implements IHistoryService {
874907 return ; // nothing to save because history was not used
875908 }
876909
877- const registry = Registry . as < IEditorInputFactoryRegistry > ( EditorExtensions . EditorInputFactories ) ;
878-
879910 const entries : ISerializedEditorHistoryEntry [ ] = coalesce ( this . history . map ( ( input ) : ISerializedEditorHistoryEntry | undefined => {
880911
881912 // Editor input: try via factory
882913 if ( input instanceof EditorInput ) {
883- const factory = registry . getEditorInputFactory ( input . getTypeId ( ) ) ;
914+ const factory = this . editorInputFactory . getEditorInputFactory ( input . getTypeId ( ) ) ;
884915 if ( factory ) {
885916 const deserialized = factory . serialize ( input ) ;
886917 if ( deserialized ) {
0 commit comments