33 * Licensed under the MIT License. See License.txt in the project root for license information.
44 *--------------------------------------------------------------------------------------------*/
55
6+ import { mainWindow } from '../../../../base/browser/window.js' ;
67import { Sequencer } from '../../../../base/common/async.js' ;
78import { Disposable } from '../../../../base/common/lifecycle.js' ;
89import { ResourceMap } from '../../../../base/common/map.js' ;
9- import { autorun , derivedOpts , IObservable , runOnChange } from '../../../../base/common/observable.js' ;
10+ import { autorun , derivedObservableWithCache , IObservable , observableFromEvent , runOnChange } from '../../../../base/common/observable.js' ;
1011import { isEqual } from '../../../../base/common/resources.js' ;
1112import { URI } from '../../../../base/common/uri.js' ;
1213import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js' ;
1314import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js' ;
15+ import { IStorageService , StorageScope , StorageTarget } from '../../../../platform/storage/common/storage.js' ;
1416import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js' ;
1517import { IWorkbenchContribution } from '../../../../workbench/common/contributions.js' ;
1618import { IEditorGroupsService , IEditorWorkingSet } from '../../../../workbench/services/editor/common/editorGroupsService.js' ;
@@ -19,30 +21,61 @@ import { IWorkbenchLayoutService, Parts } from '../../../../workbench/services/l
1921import { SessionStatus } from '../../../services/sessions/common/session.js' ;
2022import { IActiveSession , ISessionsManagementService } from '../../../services/sessions/common/sessionsManagement.js' ;
2123
24+ type ISessionSerializedWorkingSet = {
25+ readonly sessionResource : string ;
26+ readonly editorWorkingSet : IEditorWorkingSet ;
27+ } ;
28+
2229export class SessionWorkingSetController extends Disposable implements IWorkbenchContribution {
2330
2431 static readonly ID = 'workbench.contrib.sessionsWorkingSetController' ;
32+ private static readonly STORAGE_KEY = 'sessions.workingSets' ;
2533
2634 private readonly _useModalConfigObs : IObservable < 'off' | 'some' | 'all' > ;
27- private readonly _workingSets = new ResourceMap < IEditorWorkingSet > ( ) ;
35+ private readonly _workingSets : ResourceMap < IEditorWorkingSet > ;
2836 private readonly _workingSetSequencer = new Sequencer ( ) ;
2937
3038 constructor (
3139 @IConfigurationService private readonly _configurationService : IConfigurationService ,
3240 @ISessionsManagementService private readonly _sessionManagementService : ISessionsManagementService ,
33- @IEditorGroupsService private readonly _editorGroupsService : IEditorGroupsService ,
3441 @IEditorService private readonly _editorService : IEditorService ,
42+ @IEditorGroupsService private readonly _editorGroupsService : IEditorGroupsService ,
43+ @IStorageService private readonly _storageService : IStorageService ,
3544 @IWorkspaceContextService private readonly _workspaceContextService : IWorkspaceContextService ,
3645 @IWorkbenchLayoutService private readonly _layoutService : IWorkbenchLayoutService ,
3746 ) {
3847 super ( ) ;
3948
49+ this . _workingSets = this . _loadWorkingSets ( ) ;
50+
4051 this . _useModalConfigObs = observableConfigValue < 'off' | 'some' | 'all' > ( 'workbench.editor.useModal' , 'all' , this . _configurationService ) ;
4152
42- const activeSession = derivedOpts < IActiveSession | undefined > ( {
43- equalsFn : ( ( a , b ) => isEqual ( a ?. resource , b ?. resource ) )
44- } , reader => {
45- return this . _sessionManagementService . activeSession . read ( reader ) ;
53+ // Workspace folders
54+ const workspaceFoldersObs = observableFromEvent (
55+ this . _workspaceContextService . onDidChangeWorkspaceFolders ,
56+ ( ) => this . _workspaceContextService . getWorkspace ( ) . folders ) ;
57+
58+ const activeSession = derivedObservableWithCache < IActiveSession | undefined > ( this , ( reader , lastValue ) => {
59+ const workspaceFolders = workspaceFoldersObs . read ( reader ) ;
60+ const activeSession = this . _sessionManagementService . activeSession . read ( reader ) ;
61+ const activeSessionWorkspace = activeSession ?. workspace . read ( reader ) ?. repositories [ 0 ] ;
62+ const activeSessionWorkspaceUri = activeSessionWorkspace ?. workingDirectory ?? activeSessionWorkspace ?. uri ;
63+
64+ // The active session is updated before the workspace folders are updated. We
65+ // need to wait until the workspace folders are updated before considering the
66+ // active session.
67+ if (
68+ activeSessionWorkspaceUri &&
69+ ! workspaceFolders . some ( folder => isEqual ( folder . uri , activeSessionWorkspaceUri ) )
70+ ) {
71+ return lastValue ;
72+ }
73+
74+ if ( isEqual ( activeSession ?. resource , lastValue ?. resource ) ) {
75+ return lastValue ;
76+ }
77+
78+ return activeSession ;
4679 } ) ;
4780
4881 this . _register ( autorun ( reader => {
@@ -51,54 +84,122 @@ export class SessionWorkingSetController extends Disposable implements IWorkbenc
5184 return ;
5285 }
5386
54- // Session changed (save)
55- reader . store . add ( runOnChange ( activeSession , ( _ , previousSession ) => {
56- if ( ! previousSession || previousSession . status . read ( undefined ) === SessionStatus . Untitled ) {
87+ // Session changed (save, apply )
88+ reader . store . add ( runOnChange ( activeSession , ( session , previousSession ) => {
89+ if ( ! session || ! previousSession ) {
5790 return ;
5891 }
5992
60- this . _saveWorkingSet ( previousSession . resource ) ;
93+ // Save working set for previous session (skip for untitled sessions)
94+ if ( previousSession . status . read ( undefined ) !== SessionStatus . Untitled ) {
95+ this . _saveWorkingSet ( previousSession . resource ) ;
96+ }
97+
98+ // Apply working set for current session
99+ void this . _applyWorkingSet ( session . resource ) ;
61100 } ) ) ;
62101
63- // Workspace folders changes (apply)
64- reader . store . add ( this . _workspaceContextService . onDidChangeWorkspaceFolders ( ( ) => {
65- const activeSessionResource = activeSession . read ( undefined ) ?. resource ;
66- if ( ! activeSessionResource ) {
67- return ;
102+ // Session state changed (archive, delete)
103+ reader . store . add ( this . _sessionManagementService . onDidChangeSessions ( e => {
104+ const archivedSessions = e . changed . filter ( session => session . isArchived . read ( undefined ) ) ;
105+ for ( const session of [ ...e . removed , ...archivedSessions ] ) {
106+ this . _deleteWorkingSet ( session . resource ) ;
107+ }
108+ } ) ) ;
109+
110+ // Save working sets to storage
111+ reader . store . add ( this . _storageService . onWillSaveState ( ( ) => {
112+ const activeSession = this . _sessionManagementService . activeSession . read ( undefined ) ;
113+
114+ // Save working set for previous session (skip for untitled sessions)
115+ if ( activeSession && activeSession . status . read ( undefined ) !== SessionStatus . Untitled ) {
116+ this . _saveWorkingSet ( activeSession . resource ) ;
68117 }
69118
70- void this . _applyWorkingSet ( activeSessionResource ) ;
119+ this . _storeWorkingSets ( ) ;
71120 } ) ) ;
72121 } ) ) ;
73122 }
74123
75- private _saveWorkingSet ( sessionResource : URI ) : void {
76- const existingWorkingSet = this . _workingSets . get ( sessionResource ) ;
77- if ( existingWorkingSet ) {
78- this . _editorGroupsService . deleteWorkingSet ( existingWorkingSet ) ;
124+ private _loadWorkingSets ( ) : ResourceMap < IEditorWorkingSet > {
125+ const workingSets = new ResourceMap < IEditorWorkingSet > ( ) ;
126+ const workingSetsRaw = this . _storageService . get ( SessionWorkingSetController . STORAGE_KEY , StorageScope . WORKSPACE ) ;
127+ if ( ! workingSetsRaw ) {
128+ return workingSets ;
79129 }
80130
81- const workingSet = this . _editorGroupsService . saveWorkingSet ( `session-working-set:${ sessionResource . toString ( ) } ` ) ;
82- this . _workingSets . set ( sessionResource , workingSet ) ;
131+ for ( const serializedWorkingSet of JSON . parse ( workingSetsRaw ) as ISessionSerializedWorkingSet [ ] ) {
132+ const sessionResource = URI . parse ( serializedWorkingSet . sessionResource ) ;
133+ workingSets . set ( sessionResource , serializedWorkingSet . editorWorkingSet ) ;
134+ }
135+
136+ return workingSets ;
137+ }
138+
139+ private _storeWorkingSets ( ) : void {
140+ if ( this . _workingSets . size === 0 ) {
141+ this . _storageService . remove ( SessionWorkingSetController . STORAGE_KEY , StorageScope . WORKSPACE ) ;
142+ return ;
143+ }
144+
145+ const serializedWorkingSets : ISessionSerializedWorkingSet [ ] = [ ] ;
146+ for ( const [ sessionResource , editorWorkingSet ] of this . _workingSets ) {
147+ serializedWorkingSets . push ( { sessionResource : sessionResource . toString ( ) , editorWorkingSet } ) ;
148+ }
149+
150+ this . _storageService . store ( SessionWorkingSetController . STORAGE_KEY , JSON . stringify ( serializedWorkingSets ) , StorageScope . WORKSPACE , StorageTarget . MACHINE ) ;
83151 }
84152
85153 private async _applyWorkingSet ( sessionResource : URI ) : Promise < void > {
86154 const workingSet : IEditorWorkingSet | 'empty' = this . _workingSets . get ( sessionResource ) ?? 'empty' ;
87155 const preserveFocus = this . _layoutService . hasFocus ( Parts . PANEL_PART ) ;
88156
89157 return this . _workingSetSequencer . queue ( async ( ) => {
90- const applied = await this . _editorGroupsService . applyWorkingSet ( workingSet , { preserveFocus } ) ;
91- if ( applied && this . _editorService . visibleEditors . length > 0 ) {
158+ if ( workingSet === 'empty' ) {
159+ // Applying an empty working set closes all editors, and we already have an
160+ // event listener that listens to the editor close event to hide the editor
161+ // part if there are no visible editors
162+ await this . _editorGroupsService . applyWorkingSet ( workingSet , { preserveFocus } ) ;
163+ return ;
164+ }
165+
166+ if ( ! this . _layoutService . isVisible ( Parts . EDITOR_PART , mainWindow ) ) {
167+ // Applying the working set requires the editor part to be visible
168+ this . _layoutService . setPartHidden ( false , Parts . EDITOR_PART ) ;
169+ }
170+
171+ // Applying the working set closes all editors which triggers the event listener
172+ // to close the editor part. After we apply the working set we need to show the
173+ // editor part
174+ const result = await this . _editorGroupsService . applyWorkingSet ( workingSet , { preserveFocus } ) ;
175+ if ( result && ! this . _layoutService . isVisible ( Parts . EDITOR_PART , mainWindow ) ) {
92176 this . _layoutService . setPartHidden ( false , Parts . EDITOR_PART ) ;
93177 }
94178 } ) ;
95179 }
96180
97- override dispose ( ) : void {
98- for ( const [ , workingSet ] of this . _workingSets ) {
99- this . _editorGroupsService . deleteWorkingSet ( workingSet ) ;
181+ private _saveWorkingSet ( sessionResource : URI ) : void {
182+ // Delete existing working set for session if any
183+ const existingWorkingSet = this . _workingSets . get ( sessionResource ) ;
184+ if ( existingWorkingSet ) {
185+ this . _workingSets . delete ( sessionResource ) ;
186+ this . _editorGroupsService . deleteWorkingSet ( existingWorkingSet ) ;
187+ }
188+
189+ // Create new working set for session
190+ if ( this . _editorService . visibleEditors . length > 0 ) {
191+ const workingSet = this . _editorGroupsService . saveWorkingSet ( `session-working-set:${ sessionResource . toString ( ) } ` ) ;
192+ this . _workingSets . set ( sessionResource , workingSet ) ;
193+ }
194+ }
195+
196+ private _deleteWorkingSet ( sessionResource : URI ) : void {
197+ const existingWorkingSet = this . _workingSets . get ( sessionResource ) ;
198+ if ( ! existingWorkingSet ) {
199+ return ;
100200 }
101201
102- super . dispose ( ) ;
202+ this . _editorGroupsService . deleteWorkingSet ( existingWorkingSet ) ;
203+ this . _workingSets . delete ( sessionResource ) ;
103204 }
104205}
0 commit comments