Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/sessions/contrib/workingSet/browser/workingSet.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { mainWindow } from '../../../../base/browser/window.js';
7
import { Sequencer } from '../../../../base/common/async.js';
8
import { Disposable } from '../../../../base/common/lifecycle.js';
9
import { ResourceMap } from '../../../../base/common/map.js';
10
import { autorun, derivedObservableWithCache, IObservable, observableFromEvent, runOnChange } from '../../../../base/common/observable.js';
11
import { isEqual } from '../../../../base/common/resources.js';
12
import { URI } from '../../../../base/common/uri.js';
13
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
14
import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js';
15
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
16
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
17
import { IWorkbenchContribution } from '../../../../workbench/common/contributions.js';
18
import { IEditorGroupsService, IEditorWorkingSet } from '../../../../workbench/services/editor/common/editorGroupsService.js';
19
import { IEditorService } from '../../../../workbench/services/editor/common/editorService.js';
20
import { IWorkbenchLayoutService, Parts } from '../../../../workbench/services/layout/browser/layoutService.js';
21
import { SessionStatus } from '../../../services/sessions/common/session.js';
22
import { IActiveSession, ISessionsManagementService } from '../../../services/sessions/common/sessionsManagement.js';
23
24
type ISessionSerializedWorkingSet = {
25
readonly sessionResource: string;
26
readonly editorWorkingSet: IEditorWorkingSet;
27
};
28
29
export class SessionWorkingSetController extends Disposable implements IWorkbenchContribution {
30
31
static readonly ID = 'workbench.contrib.sessionsWorkingSetController';
32
private static readonly STORAGE_KEY = 'sessions.workingSets';
33
34
private readonly _useModalConfigObs: IObservable<'off' | 'some' | 'all'>;
35
private readonly _workingSets: ResourceMap<IEditorWorkingSet>;
36
private readonly _workingSetSequencer = new Sequencer();
37
38
constructor(
39
@IConfigurationService private readonly _configurationService: IConfigurationService,
40
@ISessionsManagementService private readonly _sessionManagementService: ISessionsManagementService,
41
@IEditorService private readonly _editorService: IEditorService,
42
@IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService,
43
@IStorageService private readonly _storageService: IStorageService,
44
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
45
@IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService,
46
) {
47
super();
48
49
this._workingSets = this._loadWorkingSets();
50
51
this._useModalConfigObs = observableConfigValue<'off' | 'some' | 'all'>('workbench.editor.useModal', 'all', this._configurationService);
52
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;
79
});
80
81
this._register(autorun(reader => {
82
const _useModalConfig = this._useModalConfigObs.read(reader);
83
if (_useModalConfig === 'all') {
84
return;
85
}
86
87
// Session changed (save, apply)
88
reader.store.add(runOnChange(activeSession, (session, previousSession) => {
89
// Save working set for previous session (skip for untitled sessions)
90
if (previousSession && previousSession.status.read(undefined) !== SessionStatus.Untitled) {
91
this._saveWorkingSet(previousSession.resource);
92
}
93
94
// Apply working set for current session
95
void this._applyWorkingSet(session?.resource);
96
}));
97
98
// Session state changed (archive, delete)
99
reader.store.add(this._sessionManagementService.onDidChangeSessions(e => {
100
const archivedSessions = e.changed.filter(session => session.isArchived.read(undefined));
101
for (const session of [...e.removed, ...archivedSessions]) {
102
this._deleteWorkingSet(session.resource);
103
}
104
}));
105
106
// Save working sets to storage
107
reader.store.add(this._storageService.onWillSaveState(() => {
108
const activeSession = this._sessionManagementService.activeSession.read(undefined);
109
110
// Save working set for previous session (skip for untitled sessions)
111
if (activeSession && activeSession.status.read(undefined) !== SessionStatus.Untitled) {
112
this._saveWorkingSet(activeSession.resource);
113
}
114
115
this._storeWorkingSets();
116
}));
117
}));
118
}
119
120
private _loadWorkingSets(): ResourceMap<IEditorWorkingSet> {
121
const workingSets = new ResourceMap<IEditorWorkingSet>();
122
const workingSetsRaw = this._storageService.get(SessionWorkingSetController.STORAGE_KEY, StorageScope.WORKSPACE);
123
if (!workingSetsRaw) {
124
return workingSets;
125
}
126
127
for (const serializedWorkingSet of JSON.parse(workingSetsRaw) as ISessionSerializedWorkingSet[]) {
128
const sessionResource = URI.parse(serializedWorkingSet.sessionResource);
129
workingSets.set(sessionResource, serializedWorkingSet.editorWorkingSet);
130
}
131
132
return workingSets;
133
}
134
135
private _storeWorkingSets(): void {
136
if (this._workingSets.size === 0) {
137
this._storageService.remove(SessionWorkingSetController.STORAGE_KEY, StorageScope.WORKSPACE);
138
return;
139
}
140
141
const serializedWorkingSets: ISessionSerializedWorkingSet[] = [];
142
for (const [sessionResource, editorWorkingSet] of this._workingSets) {
143
serializedWorkingSets.push({ sessionResource: sessionResource.toString(), editorWorkingSet });
144
}
145
146
this._storageService.store(SessionWorkingSetController.STORAGE_KEY, JSON.stringify(serializedWorkingSets), StorageScope.WORKSPACE, StorageTarget.MACHINE);
147
}
148
149
private async _applyWorkingSet(sessionResource: URI | undefined): Promise<void> {
150
const preserveFocus = this._layoutService.hasFocus(Parts.PANEL_PART);
151
const workingSet: IEditorWorkingSet | 'empty' = sessionResource
152
? (this._workingSets.get(sessionResource) ?? 'empty')
153
: 'empty';
154
155
return this._workingSetSequencer.queue(async () => {
156
if (workingSet === 'empty') {
157
// Applying an empty working set closes all editors, and we already have an
158
// event listener that listens to the editor close event to hide the editor
159
// part if there are no visible editors
160
await this._editorGroupsService.applyWorkingSet(workingSet, { preserveFocus });
161
return;
162
}
163
164
if (!this._layoutService.isVisible(Parts.EDITOR_PART, mainWindow)) {
165
// Applying the working set requires the editor part to be visible
166
this._layoutService.setPartHidden(false, Parts.EDITOR_PART);
167
}
168
169
// Applying the working set closes all editors which triggers the event listener
170
// to close the editor part. After we apply the working set we need to show the
171
// editor part
172
const result = await this._editorGroupsService.applyWorkingSet(workingSet, { preserveFocus });
173
if (result && !this._layoutService.isVisible(Parts.EDITOR_PART, mainWindow)) {
174
this._layoutService.setPartHidden(false, Parts.EDITOR_PART);
175
}
176
});
177
}
178
179
private _saveWorkingSet(sessionResource: URI): void {
180
// Delete existing working set
181
this._deleteWorkingSet(sessionResource);
182
183
// Add new working set
184
if (this._editorService.visibleEditors.length > 0) {
185
const workingSetName = `session-working-set:${sessionResource.toString()}`;
186
const workingSet = this._editorGroupsService.saveWorkingSet(workingSetName);
187
this._workingSets.set(sessionResource, workingSet);
188
}
189
}
190
191
private _deleteWorkingSet(sessionResource: URI): void {
192
const existingWorkingSet = this._workingSets.get(sessionResource);
193
if (!existingWorkingSet) {
194
return;
195
}
196
197
this._editorGroupsService.deleteWorkingSet(existingWorkingSet);
198
this._workingSets.delete(sessionResource);
199
}
200
}
201
202