introduce and adopt workspace save event

This commit is contained in:
Benjamin Pasero 2017-07-17 08:12:57 +02:00
parent fa40b32bde
commit 0d6a935cd9
9 changed files with 146 additions and 46 deletions

View file

@ -24,7 +24,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { KeyboardLayoutMonitor } from 'vs/code/electron-main/keyboard';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { ICodeWindow } from "vs/platform/windows/electron-main/windows";
import { IWorkspaceIdentifier } from "vs/platform/workspaces/common/workspaces";
import { IWorkspaceIdentifier, IWorkspacesMainService, IWorkspaceSavedEvent } from "vs/platform/workspaces/common/workspaces";
export interface IWindowState {
width?: number;
@ -96,7 +96,8 @@ export class CodeWindow implements ICodeWindow {
@ILogService private logService: ILogService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IConfigurationService private configurationService: IConfigurationService,
@IStorageService private storageService: IStorageService
@IStorageService private storageService: IStorageService,
@IWorkspacesMainService private workspaceService: IWorkspacesMainService
) {
this.options = config;
this._lastFocusTime = -1;
@ -403,6 +404,18 @@ export class CodeWindow implements ICodeWindow {
// Handle configuration changes
this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated()));
// Handle Workspace saves
this.toDispose.push(this.workspaceService.onWorkspaceSaved(e => this.onWorkspaceSaved(e)));
}
private onWorkspaceSaved(e: IWorkspaceSavedEvent): void {
// Make sure to update our workspace config if we detect that it
// was saved to a new config location
if (this.openedWorkspace && this.openedWorkspace.id === e.workspace.id && this.openedWorkspace.configPath !== e.workspace.configPath) {
this.config.workspace.configPath = e.workspace.configPath;
}
}
private onConfigurationUpdated(): void {

View file

@ -29,7 +29,8 @@ import { IWindowsMainService, IOpenConfiguration } from "vs/platform/windows/ele
import { IHistoryMainService } from "vs/platform/history/common/history";
import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { TPromise } from "vs/base/common/winjs.base";
import { IWorkspacesMainService, IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from "vs/platform/workspaces/common/workspaces";
import { IWorkspacesMainService, IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceSavedEvent } from "vs/platform/workspaces/common/workspaces";
import { IInstantiationService } from "vs/platform/instantiation/common/instantiation";
enum WindowError {
UNRESPONSIVE,
@ -130,7 +131,8 @@ export class WindowsManager implements IWindowsMainService {
@ITelemetryService private telemetryService: ITelemetryService,
@IConfigurationService private configurationService: IConfigurationService,
@IHistoryMainService private historyService: IHistoryMainService,
@IWorkspacesMainService private workspacesService: IWorkspacesMainService
@IWorkspacesMainService private workspacesService: IWorkspacesMainService,
@IInstantiationService private instantiationService: IInstantiationService
) {
this.windowsState = this.storageService.getItem<IWindowsState>(WindowsManager.windowsStateStorageKey) || { openedWindows: [] };
this.fileDialog = new FileDialog(environmentService, telemetryService, storageService, this);
@ -197,6 +199,21 @@ export class WindowsManager implements IWindowsMainService {
// Update our windows state before quitting and before closing windows
this.lifecycleService.onBeforeWindowClose(win => this.onBeforeWindowClose(win as CodeWindow));
this.lifecycleService.onBeforeQuit(() => this.onBeforeQuit());
// Handle workspace save event
this.workspacesService.onWorkspaceSaved(e => this.onWorkspaceSaved(e));
}
private onWorkspaceSaved(e: IWorkspaceSavedEvent): void {
// A workspace was saved to a different config location. Make sure to update our
// window states with this new location.
const states = [this.windowsState.lastActiveWindow, this.windowsState.lastPluginDevelopmentHostWindow, ...this.windowsState.openedWindows];
states.forEach(state => {
if (state && state.workspace && state.workspace.id === e.workspace.id && state.workspace.configPath !== e.workspace.configPath) {
state.workspace.configPath = e.workspace.configPath;
}
});
}
// Note that onBeforeQuit() and onBeforeWindowClose() are fired in different order depending on the OS:
@ -435,24 +452,6 @@ export class WindowsManager implements IWindowsMainService {
const allWorkspacesToOpen = arrays.distinct([...workspacesToOpen, ...workspacesToRestore], workspace => workspace.id); // prevent duplicates
if (allWorkspacesToOpen.length > 0) {
// Check for existing instances that have same workspace ID but different configuration path
// For now we reload that window with the new configuration so that the configuration path change
// can travel properly.
// TODO@Ben multi root revisit this once we can better transition between workspaces of the same id
allWorkspacesToOpen.forEach(workspaceToOpen => {
const existingWindow = findWindowOnWorkspace(WindowsManager.WINDOWS, workspaceToOpen);
if (existingWindow && existingWindow.openedWorkspace.configPath !== workspaceToOpen.configPath) {
usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { workspace: workspaceToOpen }, false, filesToOpen, filesToCreate, filesToDiff, existingWindow));
// Reset these because we handled them
filesToOpen = [];
filesToCreate = [];
filesToDiff = [];
openFolderInNewWindow = true; // any other folders to open must open in new window then
}
});
// Check for existing instances
const windowsOnWorkspace = arrays.coalesce(allWorkspacesToOpen.map(workspaceToOpen => findWindowOnWorkspace(WindowsManager.WINDOWS, workspaceToOpen)));
if (windowsOnWorkspace.length > 0) {
@ -916,16 +915,11 @@ export class WindowsManager implements IWindowsMainService {
state.mode = WindowMode.Normal;
}
codeWindow = new CodeWindow({
codeWindow = this.instantiationService.createInstance(CodeWindow, {
state,
extensionDevelopmentPath: configuration.extensionDevelopmentPath,
isExtensionTestHost: !!configuration.extensionTestsPath
},
this.logService,
this.environmentService,
this.configurationService,
this.storageService
);
});
WindowsManager.WINDOWS.push(codeWindow);

View file

@ -14,7 +14,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IFilesConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files';
import { ILogService } from "vs/platform/log/common/log";
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from "vs/platform/workspaces/common/workspaces";
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspacesMainService, IWorkspaceSavedEvent } from "vs/platform/workspaces/common/workspaces";
export class BackupMainService implements IBackupMainService {
@ -28,12 +28,35 @@ export class BackupMainService implements IBackupMainService {
constructor(
@IEnvironmentService environmentService: IEnvironmentService,
@IConfigurationService private configurationService: IConfigurationService,
@ILogService private logService: ILogService
@ILogService private logService: ILogService,
@IWorkspacesMainService private workspacesService: IWorkspacesMainService
) {
this.backupHome = environmentService.backupHome;
this.workspacesJsonPath = environmentService.backupWorkspacesPath;
this.loadSync();
this.registerListeners();
}
private registerListeners(): void {
this.workspacesService.onWorkspaceSaved(e => this.onWorkspaceSaved(e));
}
private onWorkspaceSaved(e: IWorkspaceSavedEvent): void {
// A workspace was saved to a new configuration location. Make sure to update
// our backup state with this new location.
let needsUpdate = false;
this.backups.rootWorkspaces.forEach(workspace => {
if (workspace.id === e.workspace.id && workspace.configPath !== e.workspace.configPath) {
workspace.configPath = e.workspace.configPath;
needsUpdate = true;
}
});
if (needsUpdate) {
this.saveSync();
}
}
public getWorkspaceBackups(): IWorkspaceIdentifier[] {
@ -42,6 +65,7 @@ export class BackupMainService implements IBackupMainService {
// hot exit is configured as onExitAndWindowClose.
return [];
}
return this.backups.rootWorkspaces.slice(0); // return a copy
}
@ -51,6 +75,7 @@ export class BackupMainService implements IBackupMainService {
// hot exit is configured as onExitAndWindowClose.
return [];
}
return this.backups.folderWorkspaces.slice(0); // return a copy
}
@ -99,10 +124,12 @@ export class BackupMainService implements IBackupMainService {
if (!target) {
return;
}
const index = this.indexOf(workspaceIdentifier, target);
if (index === -1) {
return;
}
target.splice(index, 1);
this.saveSync();
}
@ -261,6 +288,7 @@ export class BackupMainService implements IBackupMainService {
if (!fs.existsSync(this.backupHome)) {
fs.mkdirSync(this.backupHome);
}
fs.writeFileSync(this.workspacesJsonPath, JSON.stringify(this.backups));
} catch (ex) {
this.logService.error(`Backup: Could not save workspaces.json: ${ex.toString()}`);

View file

@ -22,11 +22,14 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/
import { LogMainService } from "vs/platform/log/common/log";
import { IWorkspaceIdentifier } from "vs/platform/workspaces/common/workspaces";
import { createHash } from "crypto";
import { WorkspacesMainService } from "vs/platform/workspaces/electron-main/workspacesMainService";
const environmentService = new EnvironmentService(parseArgs(process.argv), process.execPath);
class TestBackupMainService extends BackupMainService {
constructor(backupHome: string, backupWorkspacesPath: string, configService: TestConfigurationService) {
super(new EnvironmentService(parseArgs(process.argv), process.execPath), configService, new LogMainService(new EnvironmentService(parseArgs(process.argv), process.execPath)));
super(environmentService, configService, new LogMainService(environmentService), new WorkspacesMainService(environmentService));
this.backupHome = backupHome;
this.workspacesJsonPath = backupWorkspacesPath;

View file

@ -16,7 +16,7 @@ import { getPathLabel } from 'vs/base/common/labels';
import { IPath } from 'vs/platform/windows/common/windows';
import CommonEvent, { Emitter } from 'vs/base/common/event';
import { isWindows, isMacintosh, isLinux } from 'vs/base/common/platform';
import { IWorkspaceIdentifier, IWorkspacesMainService, getWorkspaceLabel, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from "vs/platform/workspaces/common/workspaces";
import { IWorkspaceIdentifier, IWorkspacesMainService, getWorkspaceLabel, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IWorkspaceSavedEvent } from "vs/platform/workspaces/common/workspaces";
import { IHistoryMainService, IRecentlyOpened } from "vs/platform/history/common/history";
import { IEnvironmentService } from "vs/platform/environment/common/environment";
@ -39,8 +39,36 @@ export class HistoryMainService implements IHistoryMainService {
@IStorageService private storageService: IStorageService,
@ILogService private logService: ILogService,
@IWorkspacesMainService private workspacesService: IWorkspacesMainService,
@IEnvironmentService private environmentService: IEnvironmentService
@IEnvironmentService private environmentService: IEnvironmentService,
) {
this.registerListeners();
}
private registerListeners(): void {
this.workspacesService.onWorkspaceSaved(e => this.onWorkspaceSaved(e));
}
private onWorkspaceSaved(e: IWorkspaceSavedEvent): void {
// A workspace was saved to a new config location. Make sure to
// update our recently opened workspaces with this new location.
let changed = false;
const mru = this.getRecentlyOpened();
mru.workspaces.forEach(workspace => {
if (isSingleFolderWorkspaceIdentifier(workspace)) {
return;
}
if (workspace.id === e.workspace.id && workspace.configPath !== e.workspace.configPath) {
workspace.configPath = e.workspace.configPath;
changed = true;
}
});
if (changed) {
this.saveRecentlyOpened(mru);
this._onRecentlyOpenedChange.fire();
}
}
public addRecentlyOpened(workspaces: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier)[], files: string[]): void {
@ -74,7 +102,7 @@ export class HistoryMainService implements IHistoryMainService {
mru.workspaces = mru.workspaces.slice(0, HistoryMainService.MAX_TOTAL_RECENT_ENTRIES);
mru.files = mru.files.slice(0, HistoryMainService.MAX_TOTAL_RECENT_ENTRIES);
this.storageService.setItem(HistoryMainService.recentlyOpenedStorageKey, mru);
this.saveRecentlyOpened(mru);
this._onRecentlyOpenedChange.fire();
}
}
@ -101,13 +129,13 @@ export class HistoryMainService implements IHistoryMainService {
}));
if (update) {
this.storageService.setItem(HistoryMainService.recentlyOpenedStorageKey, mru);
this.saveRecentlyOpened(mru);
this._onRecentlyOpenedChange.fire();
}
}
public clearRecentlyOpened(): void {
this.storageService.setItem(HistoryMainService.recentlyOpenedStorageKey, <IRecentlyOpened>{ workspaces: [], folders: [], files: [] });
this.saveRecentlyOpened({ workspaces: [], files: [] });
app.clearRecentDocuments();
// Event
@ -145,6 +173,10 @@ export class HistoryMainService implements IHistoryMainService {
return { workspaces, files };
}
private saveRecentlyOpened(recent: IRecentlyOpened): void {
this.storageService.setItem(HistoryMainService.recentlyOpenedStorageKey, recent);
}
public updateWindowsJumpList(): void {
if (!isWindows) {
return; // only on windows

View file

@ -12,6 +12,7 @@ import { localize } from "vs/nls";
import { basename } from "vs/base/common/paths";
import { isLinux } from "vs/base/common/platform";
import { IEnvironmentService } from "vs/platform/environment/common/environment";
import Event from 'vs/base/common/event';
export const IWorkspacesMainService = createDecorator<IWorkspacesMainService>('workspacesMainService');
export const IWorkspacesService = createDecorator<IWorkspacesService>('workspacesService');
@ -33,9 +34,16 @@ export interface IStoredWorkspace {
folders: string[];
}
export interface IWorkspaceSavedEvent {
workspace: IWorkspaceIdentifier;
oldConfigPath: string;
}
export interface IWorkspacesMainService extends IWorkspacesService {
_serviceBrand: any;
onWorkspaceSaved: Event<IWorkspaceSavedEvent>;
resolveWorkspaceSync(path: string): IWorkspaceIdentifier;
isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean;
}

View file

@ -5,7 +5,7 @@
'use strict';
import { IWorkspacesMainService, IWorkspaceIdentifier, IStoredWorkspace, WORKSPACE_EXTENSION } from "vs/platform/workspaces/common/workspaces";
import { IWorkspacesMainService, IWorkspaceIdentifier, IStoredWorkspace, WORKSPACE_EXTENSION, IWorkspaceSavedEvent } from "vs/platform/workspaces/common/workspaces";
import { TPromise } from "vs/base/common/winjs.base";
import { isParent } from "vs/platform/files/common/files";
import { IEnvironmentService } from "vs/platform/environment/common/environment";
@ -16,6 +16,7 @@ import { isLinux } from "vs/base/common/platform";
import { copy } from "vs/base/node/extfs";
import { nfcall } from "vs/base/common/async";
import { localize } from "vs/nls";
import Event, { Emitter } from "vs/base/common/event";
export class WorkspacesMainService implements IWorkspacesMainService {
@ -23,8 +24,15 @@ export class WorkspacesMainService implements IWorkspacesMainService {
protected workspacesHome: string;
private _onWorkspaceSaved: Emitter<IWorkspaceSavedEvent>;
constructor( @IEnvironmentService private environmentService: IEnvironmentService) {
this.workspacesHome = environmentService.workspacesHome;
this._onWorkspaceSaved = new Emitter<IWorkspaceSavedEvent>();
}
public get onWorkspaceSaved(): Event<IWorkspaceSavedEvent> {
return this._onWorkspaceSaved.event;
}
public resolveWorkspaceSync(path: string): IWorkspaceIdentifier {
@ -89,7 +97,11 @@ export class WorkspacesMainService implements IWorkspacesMainService {
}
return nfcall(copy, workspace.configPath, target).then(() => {
return this.resolveWorkspaceSync(target);
const savedWorkspace = this.resolveWorkspaceSync(target);
this._onWorkspaceSaved.fire({ workspace: savedWorkspace, oldConfigPath: workspace.configPath });
return savedWorkspace;
});
});
}

View file

@ -14,7 +14,7 @@ import pfs = require('vs/base/node/pfs');
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { parseArgs } from 'vs/platform/environment/node/argv';
import { WorkspacesMainService } from "vs/platform/workspaces/electron-main/workspacesMainService";
import { IStoredWorkspace, WORKSPACE_EXTENSION } from "vs/platform/workspaces/common/workspaces";
import { IStoredWorkspace, WORKSPACE_EXTENSION, IWorkspaceSavedEvent } from "vs/platform/workspaces/common/workspaces";
class TestWorkspacesMainService extends WorkspacesMainService {
constructor(workspacesHome: string) {
@ -87,6 +87,11 @@ suite('WorkspacesMainService', () => {
});
test('saveWorkspace', done => {
let event: IWorkspaceSavedEvent;
const dispose = service.onWorkspaceSaved(e => {
event = e;
});
return service.createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => {
const workspaceConfigPath = path.join(os.tmpdir(), `myworkspace.${WORKSPACE_EXTENSION}`);
@ -100,6 +105,11 @@ suite('WorkspacesMainService', () => {
assert.equal(ws.folders[0], process.cwd());
assert.equal(ws.folders[1], os.tmpdir());
dispose.dispose();
assert.equal(savedWorkspace, event.workspace);
assert.equal(workspace.configPath, event.oldConfigPath);
extfs.delSync(workspaceConfigPath);
done();

View file

@ -46,6 +46,8 @@ import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import fs = require('fs');
gracefulFs.gracefulify(fs); // enable gracefulFs
const currentWindowId = remote.getCurrentWindow().id;
export function startup(configuration: IWindowConfiguration): TPromise<void> {
// Ensure others can listen to zoom level changes
@ -104,10 +106,8 @@ function toInputs(paths: IPath[], isUntitledFile?: boolean): IResourceInput[] {
}
function openWorkbench(configuration: IWindowConfiguration, options: IOptions): TPromise<void> {
const currentWindow = remote.getCurrentWindow();
const mainProcessClient = new ElectronIPCClient(String(`window${currentWindow.id}`));
const mainServices = createMainProcessServices(mainProcessClient, currentWindow);
const mainProcessClient = new ElectronIPCClient(String(`window${currentWindowId}`));
const mainServices = createMainProcessServices(mainProcessClient);
const environmentService = new EnvironmentService(configuration, configuration.execPath);
@ -214,7 +214,7 @@ function createStorageService(configuration: IWindowConfiguration, workspaceServ
return new StorageService(storage, storage, workspaceId, secondaryWorkspaceId);
}
function createMainProcessServices(mainProcessClient: ElectronIPCClient, currentWindow: Electron.BrowserWindow): ServiceCollection {
function createMainProcessServices(mainProcessClient: ElectronIPCClient): ServiceCollection {
const serviceCollection = new ServiceCollection();
const windowsChannel = mainProcessClient.getChannel('windows');
@ -224,7 +224,7 @@ function createMainProcessServices(mainProcessClient: ElectronIPCClient, current
serviceCollection.set(IUpdateService, new SyncDescriptor(UpdateChannelClient, updateChannel));
const urlChannel = mainProcessClient.getChannel('url');
serviceCollection.set(IURLService, new SyncDescriptor(URLChannelClient, urlChannel, currentWindow.id));
serviceCollection.set(IURLService, new SyncDescriptor(URLChannelClient, urlChannel, currentWindowId));
const workspacesChannel = mainProcessClient.getChannel('workspaces');
serviceCollection.set(IWorkspacesService, new WorkspacesChannelClient(workspacesChannel));