Path: blob/main/src/vs/sessions/contrib/workingSet/browser/workingSet.ts
13401 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { mainWindow } from '../../../../base/browser/window.js';6import { Sequencer } from '../../../../base/common/async.js';7import { Disposable } from '../../../../base/common/lifecycle.js';8import { ResourceMap } from '../../../../base/common/map.js';9import { autorun, derivedObservableWithCache, IObservable, observableFromEvent, runOnChange } from '../../../../base/common/observable.js';10import { isEqual } from '../../../../base/common/resources.js';11import { URI } from '../../../../base/common/uri.js';12import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';13import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js';14import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';15import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';16import { IWorkbenchContribution } from '../../../../workbench/common/contributions.js';17import { IEditorGroupsService, IEditorWorkingSet } from '../../../../workbench/services/editor/common/editorGroupsService.js';18import { IEditorService } from '../../../../workbench/services/editor/common/editorService.js';19import { IWorkbenchLayoutService, Parts } from '../../../../workbench/services/layout/browser/layoutService.js';20import { SessionStatus } from '../../../services/sessions/common/session.js';21import { IActiveSession, ISessionsManagementService } from '../../../services/sessions/common/sessionsManagement.js';2223type ISessionSerializedWorkingSet = {24readonly sessionResource: string;25readonly editorWorkingSet: IEditorWorkingSet;26};2728export class SessionWorkingSetController extends Disposable implements IWorkbenchContribution {2930static readonly ID = 'workbench.contrib.sessionsWorkingSetController';31private static readonly STORAGE_KEY = 'sessions.workingSets';3233private readonly _useModalConfigObs: IObservable<'off' | 'some' | 'all'>;34private readonly _workingSets: ResourceMap<IEditorWorkingSet>;35private readonly _workingSetSequencer = new Sequencer();3637constructor(38@IConfigurationService private readonly _configurationService: IConfigurationService,39@ISessionsManagementService private readonly _sessionManagementService: ISessionsManagementService,40@IEditorService private readonly _editorService: IEditorService,41@IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService,42@IStorageService private readonly _storageService: IStorageService,43@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,44@IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService,45) {46super();4748this._workingSets = this._loadWorkingSets();4950this._useModalConfigObs = observableConfigValue<'off' | 'some' | 'all'>('workbench.editor.useModal', 'all', this._configurationService);5152// Workspace folders53const workspaceFoldersObs = observableFromEvent(54this._workspaceContextService.onDidChangeWorkspaceFolders,55() => this._workspaceContextService.getWorkspace().folders);5657const activeSession = derivedObservableWithCache<IActiveSession | undefined>(this, (reader, lastValue) => {58const workspaceFolders = workspaceFoldersObs.read(reader);59const activeSession = this._sessionManagementService.activeSession.read(reader);60const activeSessionWorkspace = activeSession?.workspace.read(reader)?.repositories[0];61const activeSessionWorkspaceUri = activeSessionWorkspace?.workingDirectory ?? activeSessionWorkspace?.uri;6263// The active session is updated before the workspace folders are updated. We64// need to wait until the workspace folders are updated before considering the65// active session.66if (67activeSessionWorkspaceUri &&68!workspaceFolders.some(folder => isEqual(folder.uri, activeSessionWorkspaceUri))69) {70return lastValue;71}7273if (isEqual(activeSession?.resource, lastValue?.resource)) {74return lastValue;75}7677return activeSession;78});7980this._register(autorun(reader => {81const _useModalConfig = this._useModalConfigObs.read(reader);82if (_useModalConfig === 'all') {83return;84}8586// Session changed (save, apply)87reader.store.add(runOnChange(activeSession, (session, previousSession) => {88// Save working set for previous session (skip for untitled sessions)89if (previousSession && previousSession.status.read(undefined) !== SessionStatus.Untitled) {90this._saveWorkingSet(previousSession.resource);91}9293// Apply working set for current session94void this._applyWorkingSet(session?.resource);95}));9697// Session state changed (archive, delete)98reader.store.add(this._sessionManagementService.onDidChangeSessions(e => {99const archivedSessions = e.changed.filter(session => session.isArchived.read(undefined));100for (const session of [...e.removed, ...archivedSessions]) {101this._deleteWorkingSet(session.resource);102}103}));104105// Save working sets to storage106reader.store.add(this._storageService.onWillSaveState(() => {107const activeSession = this._sessionManagementService.activeSession.read(undefined);108109// Save working set for previous session (skip for untitled sessions)110if (activeSession && activeSession.status.read(undefined) !== SessionStatus.Untitled) {111this._saveWorkingSet(activeSession.resource);112}113114this._storeWorkingSets();115}));116}));117}118119private _loadWorkingSets(): ResourceMap<IEditorWorkingSet> {120const workingSets = new ResourceMap<IEditorWorkingSet>();121const workingSetsRaw = this._storageService.get(SessionWorkingSetController.STORAGE_KEY, StorageScope.WORKSPACE);122if (!workingSetsRaw) {123return workingSets;124}125126for (const serializedWorkingSet of JSON.parse(workingSetsRaw) as ISessionSerializedWorkingSet[]) {127const sessionResource = URI.parse(serializedWorkingSet.sessionResource);128workingSets.set(sessionResource, serializedWorkingSet.editorWorkingSet);129}130131return workingSets;132}133134private _storeWorkingSets(): void {135if (this._workingSets.size === 0) {136this._storageService.remove(SessionWorkingSetController.STORAGE_KEY, StorageScope.WORKSPACE);137return;138}139140const serializedWorkingSets: ISessionSerializedWorkingSet[] = [];141for (const [sessionResource, editorWorkingSet] of this._workingSets) {142serializedWorkingSets.push({ sessionResource: sessionResource.toString(), editorWorkingSet });143}144145this._storageService.store(SessionWorkingSetController.STORAGE_KEY, JSON.stringify(serializedWorkingSets), StorageScope.WORKSPACE, StorageTarget.MACHINE);146}147148private async _applyWorkingSet(sessionResource: URI | undefined): Promise<void> {149const preserveFocus = this._layoutService.hasFocus(Parts.PANEL_PART);150const workingSet: IEditorWorkingSet | 'empty' = sessionResource151? (this._workingSets.get(sessionResource) ?? 'empty')152: 'empty';153154return this._workingSetSequencer.queue(async () => {155if (workingSet === 'empty') {156// Applying an empty working set closes all editors, and we already have an157// event listener that listens to the editor close event to hide the editor158// part if there are no visible editors159await this._editorGroupsService.applyWorkingSet(workingSet, { preserveFocus });160return;161}162163if (!this._layoutService.isVisible(Parts.EDITOR_PART, mainWindow)) {164// Applying the working set requires the editor part to be visible165this._layoutService.setPartHidden(false, Parts.EDITOR_PART);166}167168// Applying the working set closes all editors which triggers the event listener169// to close the editor part. After we apply the working set we need to show the170// editor part171const result = await this._editorGroupsService.applyWorkingSet(workingSet, { preserveFocus });172if (result && !this._layoutService.isVisible(Parts.EDITOR_PART, mainWindow)) {173this._layoutService.setPartHidden(false, Parts.EDITOR_PART);174}175});176}177178private _saveWorkingSet(sessionResource: URI): void {179// Delete existing working set180this._deleteWorkingSet(sessionResource);181182// Add new working set183if (this._editorService.visibleEditors.length > 0) {184const workingSetName = `session-working-set:${sessionResource.toString()}`;185const workingSet = this._editorGroupsService.saveWorkingSet(workingSetName);186this._workingSets.set(sessionResource, workingSet);187}188}189190private _deleteWorkingSet(sessionResource: URI): void {191const existingWorkingSet = this._workingSets.get(sessionResource);192if (!existingWorkingSet) {193return;194}195196this._editorGroupsService.deleteWorkingSet(existingWorkingSet);197this._workingSets.delete(sessionResource);198}199}200201202