@@ -13,7 +13,7 @@ import { IsMacNativeContext, IsDevelopmentContext, IsWebContext, IsIOSContext }
1313import { Categories } from '../../../platform/action/common/actionCommonCategories.js' ;
1414import { KeybindingsRegistry , KeybindingWeight } from '../../../platform/keybinding/common/keybindingsRegistry.js' ;
1515import { IQuickInputButton , IQuickInputService , IQuickPickSeparator , IKeyMods , IQuickPickItem } from '../../../platform/quickinput/common/quickInput.js' ;
16- import { IWorkspaceContextService , IWorkspaceIdentifier } from '../../../platform/workspace/common/workspace.js' ;
16+ import { IWorkspaceContextService , IWorkspaceIdentifier , isWorkspaceIdentifier , isSingleFolderWorkspaceIdentifier } from '../../../platform/workspace/common/workspace.js' ;
1717import { ILabelService , Verbosity } from '../../../platform/label/common/label.js' ;
1818import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js' ;
1919import { IModelService } from '../../../editor/common/services/model.js' ;
@@ -62,6 +62,17 @@ abstract class BaseOpenRecentAction extends Action2 {
6262 tooltip : localize ( 'dirtyRecentlyOpenedWorkspace' , "Workspace With Unsaved Files" ) ,
6363 } ;
6464
65+ private readonly windowOpenedRecentlyOpenedFolder : IQuickInputButton = {
66+ iconClass : 'opened-workspace ' + ThemeIcon . asClassName ( Codicon . window ) ,
67+ tooltip : localize ( 'openedRecentlyOpenedFolder' , "Folder Opened in a Window" ) ,
68+ alwaysVisible : true
69+ } ;
70+
71+ private readonly windowOpenedRecentlyOpenedWorkspace : IQuickInputButton = {
72+ ...this . windowOpenedRecentlyOpenedFolder ,
73+ tooltip : localize ( 'openedRecentlyOpenedWorkspace' , "Workspace Opened in a Window" ) ,
74+ } ;
75+
6576 protected abstract isQuickNavigate ( ) : boolean ;
6677
6778 override async run ( accessor : ServicesAccessor ) : Promise < void > {
@@ -75,8 +86,11 @@ abstract class BaseOpenRecentAction extends Action2 {
7586 const hostService = accessor . get ( IHostService ) ;
7687 const dialogService = accessor . get ( IDialogService ) ;
7788
78- const recentlyOpened = await workspacesService . getRecentlyOpened ( ) ;
79- const dirtyWorkspacesAndFolders = await workspacesService . getDirtyWorkspaces ( ) ;
89+ const [ mainWindows , recentlyOpened , dirtyWorkspacesAndFolders ] = await Promise . all ( [
90+ hostService . getWindows ( { includeAuxiliaryWindows : false } ) ,
91+ workspacesService . getRecentlyOpened ( ) ,
92+ workspacesService . getDirtyWorkspaces ( )
93+ ] ) ;
8094
8195 let hasWorkspaces = false ;
8296
@@ -92,6 +106,16 @@ abstract class BaseOpenRecentAction extends Action2 {
92106 }
93107 }
94108
109+ // Identify all folders and workspaces opened in main windows
110+ const openedInWindows = new ResourceMap < boolean > ( ) ;
111+ for ( const window of mainWindows ) {
112+ if ( isSingleFolderWorkspaceIdentifier ( window . workspace ) ) {
113+ openedInWindows . set ( window . workspace . uri , true ) ;
114+ } else if ( isWorkspaceIdentifier ( window . workspace ) ) {
115+ openedInWindows . set ( window . workspace . configPath , true ) ;
116+ }
117+ }
118+
95119 // Identify all recently opened folders and workspaces
96120 const recentFolders = new ResourceMap < boolean > ( ) ;
97121 const recentWorkspaces = new ResourceMap < IWorkspaceIdentifier > ( ) ;
@@ -108,20 +132,21 @@ abstract class BaseOpenRecentAction extends Action2 {
108132 const workspacePicks : IRecentlyOpenedPick [ ] = [ ] ;
109133 for ( const recent of recentlyOpened . workspaces ) {
110134 const isDirty = isRecentFolder ( recent ) ? dirtyFolders . has ( recent . folderUri ) : dirtyWorkspaces . has ( recent . workspace . configPath ) ;
135+ const isOpenedInWindow = isRecentFolder ( recent ) ? openedInWindows . has ( recent . folderUri ) : openedInWindows . has ( recent . workspace . configPath ) ;
111136
112- workspacePicks . push ( this . toQuickPick ( modelService , languageService , labelService , recent , isDirty ) ) ;
137+ workspacePicks . push ( this . toQuickPick ( modelService , languageService , labelService , recent , { isDirty, isOpenedInWindow } ) ) ;
113138 }
114139
115140 // Fill any backup workspace that is not yet shown at the end
116141 for ( const dirtyWorkspaceOrFolder of dirtyWorkspacesAndFolders ) {
117142 if ( isFolderBackupInfo ( dirtyWorkspaceOrFolder ) && ! recentFolders . has ( dirtyWorkspaceOrFolder . folderUri ) ) {
118- workspacePicks . push ( this . toQuickPick ( modelService , languageService , labelService , dirtyWorkspaceOrFolder , true ) ) ;
143+ workspacePicks . push ( this . toQuickPick ( modelService , languageService , labelService , dirtyWorkspaceOrFolder , { isDirty : true , isOpenedInWindow : false } ) ) ;
119144 } else if ( isWorkspaceBackupInfo ( dirtyWorkspaceOrFolder ) && ! recentWorkspaces . has ( dirtyWorkspaceOrFolder . workspace . configPath ) ) {
120- workspacePicks . push ( this . toQuickPick ( modelService , languageService , labelService , dirtyWorkspaceOrFolder , true ) ) ;
145+ workspacePicks . push ( this . toQuickPick ( modelService , languageService , labelService , dirtyWorkspaceOrFolder , { isDirty : true , isOpenedInWindow : false } ) ) ;
121146 }
122147 }
123148
124- const filePicks = recentlyOpened . files . map ( p => this . toQuickPick ( modelService , languageService , labelService , p , false ) ) ;
149+ const filePicks = recentlyOpened . files . map ( p => this . toQuickPick ( modelService , languageService , labelService , p , { isDirty : false , isOpenedInWindow : false } ) ) ;
125150
126151 // focus second entry if the first recent workspace is the current workspace
127152 const firstEntry = recentlyOpened . workspaces [ 0 ] ;
@@ -179,7 +204,7 @@ abstract class BaseOpenRecentAction extends Action2 {
179204 }
180205 }
181206
182- private toQuickPick ( modelService : IModelService , languageService : ILanguageService , labelService : ILabelService , recent : IRecent , isDirty : boolean ) : IRecentlyOpenedPick {
207+ private toQuickPick ( modelService : IModelService , languageService : ILanguageService , labelService : ILabelService , recent : IRecent , kind : { isDirty : boolean ; isOpenedInWindow : boolean } ) : IRecentlyOpenedPick {
183208 let openable : IWindowOpenable | undefined ;
184209 let iconClasses : string [ ] ;
185210 let fullLabel : string | undefined ;
@@ -213,12 +238,21 @@ abstract class BaseOpenRecentAction extends Action2 {
213238
214239 const { name, parentPath } = splitRecentLabel ( fullLabel ) ;
215240
241+ const buttons : IQuickInputButton [ ] = [ ] ;
242+ if ( kind . isDirty ) {
243+ buttons . push ( isWorkspace ? this . dirtyRecentlyOpenedWorkspace : this . dirtyRecentlyOpenedFolder ) ;
244+ } else if ( kind . isOpenedInWindow ) {
245+ buttons . push ( isWorkspace ? this . windowOpenedRecentlyOpenedWorkspace : this . windowOpenedRecentlyOpenedFolder ) ;
246+ } else {
247+ buttons . push ( this . removeFromRecentlyOpened ) ;
248+ }
249+
216250 return {
217251 iconClasses,
218252 label : name ,
219- ariaLabel : isDirty ? isWorkspace ? localize ( 'recentDirtyWorkspaceAriaLabel' , "{0}, workspace with unsaved changes" , name ) : localize ( 'recentDirtyFolderAriaLabel' , "{0}, folder with unsaved changes" , name ) : name ,
253+ ariaLabel : kind . isDirty ? isWorkspace ? localize ( 'recentDirtyWorkspaceAriaLabel' , "{0}, workspace with unsaved changes" , name ) : localize ( 'recentDirtyFolderAriaLabel' , "{0}, folder with unsaved changes" , name ) : name ,
220254 description : parentPath ,
221- buttons : isDirty ? [ isWorkspace ? this . dirtyRecentlyOpenedWorkspace : this . dirtyRecentlyOpenedFolder ] : [ this . removeFromRecentlyOpened ] ,
255+ buttons,
222256 openable,
223257 resource,
224258 remoteAuthority : recent . remoteAuthority
0 commit comments