workspaceEditingService.ts 10.7 KB
Newer Older
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

B
Benjamin Pasero 已提交
8 9
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
import URI from 'vs/base/common/uri';
S
Sandeep Somavarapu 已提交
10
import * as nls from 'vs/nls';
11
import { TPromise } from 'vs/base/common/winjs.base';
12
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
S
Sandeep Somavarapu 已提交
13
import { IWindowService, IEnterWorkspaceResult } from 'vs/platform/windows/common/windows';
S
Sandeep Somavarapu 已提交
14
import { IJSONEditingService, JSONEditingError, JSONEditingErrorCode } from 'vs/workbench/services/configuration/common/jsonEditing';
S
Sandeep Somavarapu 已提交
15
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
16
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
17
import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService';
18
import { migrateStorageToMultiRootWorkspace } from 'vs/platform/storage/common/migration';
19
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
20 21 22 23
import { StorageService } from 'vs/platform/storage/common/storageService';
import { ConfigurationScope, IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
24 25
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { BackupFileService } from 'vs/workbench/services/backup/node/backupFileService';
S
Sandeep Somavarapu 已提交
26 27
import { IChoiceService, Severity, IMessageService } from 'vs/platform/message/common/message';
import { ICommandService } from 'vs/platform/commands/common/commands';
28 29 30
import { distinct } from 'vs/base/common/arrays';
import { isLinux } from 'vs/base/common/platform';
import { isEqual } from 'vs/base/common/resources';
31 32
import { Action } from 'vs/base/common/actions';
import product from 'vs/platform/node/product';
33 34 35 36 37

export class WorkspaceEditingService implements IWorkspaceEditingService {

	public _serviceBrand: any;

38 39
	private static INFO_MESSAGE_KEY = 'enterWorkspace.message';

40
	constructor(
41
		@IJSONEditingService private jsonEditingService: IJSONEditingService,
42
		@IWorkspaceContextService private contextService: WorkspaceService,
43
		@IWindowService private windowService: IWindowService,
44 45
		@IWorkspaceConfigurationService private workspaceConfigurationService: IWorkspaceConfigurationService,
		@IStorageService private storageService: IStorageService,
46
		@IExtensionService private extensionService: IExtensionService,
S
Sandeep Somavarapu 已提交
47 48 49 50
		@IBackupFileService private backupFileService: IBackupFileService,
		@IChoiceService private choiceService: IChoiceService,
		@IMessageService private messageService: IMessageService,
		@ICommandService private commandService: ICommandService
51 52 53
	) {
	}

54 55
	public addFolders(foldersToAdd: URI[]): TPromise<void>;
	public addFolders(foldersToAdd: { uri: URI, name?: string }[]): TPromise<void>;
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
	public addFolders(foldersToAdd: any[]): TPromise<void> {
		const state = this.contextService.getWorkbenchState();

		// If we are in no-workspace or single-folder workspace, adding folders has to
		// enter a workspace.
		if (state !== WorkbenchState.WORKSPACE) {
			const newWorkspaceFolders = distinct([
				...this.contextService.getWorkspace().folders.map(folder => folder.uri),
				...foldersToAdd.map(folder => {
					if (URI.isUri(folder)) {
						return folder;
					}

					return folder.uri;
				})
			].map(folder => folder.fsPath), folder => isLinux ? folder : folder.toLowerCase());

			if (state === WorkbenchState.EMPTY && newWorkspaceFolders.length === 0 || state === WorkbenchState.FOLDER && newWorkspaceFolders.length === 1) {
				return TPromise.as(void 0); // return if the operation is a no-op for the current state
			}

			return this.createAndEnterWorkspace(newWorkspaceFolders);
		}

		// Delegate addition of folders to workspace service otherwise
		return this.contextService.addFolders(foldersToAdd)
S
Sandeep Somavarapu 已提交
82 83 84
			.then(() => null, error => this.handleWorkspaceConfigurationEditingError(error));
	}

85 86 87 88 89 90
	public removeFolders(foldersToRemove: URI[]): TPromise<void> {

		// If we are in single-folder state and the opened folder is to be removed,
		// we close the workspace and enter the empty workspace state for the window.
		if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
			const workspaceFolder = this.contextService.getWorkspace().folders[0];
B
Benjamin Pasero 已提交
91
			if (foldersToRemove.some(folder => isEqual(folder, workspaceFolder.uri, !isLinux))) {
92 93 94 95 96 97
				return this.windowService.closeWorkspace();
			}
		}

		// Delegate removal of folders to workspace service otherwise
		return this.contextService.removeFolders(foldersToRemove)
S
Sandeep Somavarapu 已提交
98 99 100
			.then(() => null, error => this.handleWorkspaceConfigurationEditingError(error));
	}

B
Benjamin Pasero 已提交
101 102
	public createAndEnterWorkspace(folderPaths?: string[], path?: string): TPromise<void> {
		return this.doEnterWorkspace(() => this.windowService.createAndEnterWorkspace(folderPaths, path));
103 104
	}

105 106
	public saveAndEnterWorkspace(path: string): TPromise<void> {
		return this.doEnterWorkspace(() => this.windowService.saveAndEnterWorkspace(path));
107 108
	}

S
Sandeep Somavarapu 已提交
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
	private handleWorkspaceConfigurationEditingError(error: JSONEditingError): TPromise<void> {
		switch (error.code) {
			case JSONEditingErrorCode.ERROR_INVALID_FILE:
				return this.onInvalidWorkspaceConfigurationFileError();
			case JSONEditingErrorCode.ERROR_FILE_DIRTY:
				return this.onWorkspaceConfigurationFileDirtyError();
		}
		this.messageService.show(Severity.Error, error.message);
		return TPromise.as(void 0);
	}

	private onInvalidWorkspaceConfigurationFileError(): TPromise<void> {
		const message = nls.localize('errorInvalidTaskConfiguration', "Unable to write into workspace configuration file. Please open the file to correct errors/warnings in it and try again.");
		return this.askToOpenWorkspaceConfigurationFile(message);
	}

	private onWorkspaceConfigurationFileDirtyError(): TPromise<void> {
		const message = nls.localize('errorWorkspaceConfigurationFileDirty', "Unable to write into workspace configuration file because the file is dirty. Please save it and try again.");
		return this.askToOpenWorkspaceConfigurationFile(message);
	}

	private askToOpenWorkspaceConfigurationFile(message: string): TPromise<void> {
		return this.choiceService.choose(Severity.Error, message, [nls.localize('openWorkspaceConfigurationFile', "Open Workspace Configuration File"), nls.localize('close', "Close")], 1)
			.then(option => {
				switch (option) {
					case 0:
						this.commandService.executeCommand('workbench.action.openWorkspaceConfigFile');
						break;
				}
			});
	}

141
	private doEnterWorkspace(mainSidePromise: () => TPromise<IEnterWorkspaceResult>): TPromise<void> {
142

143
		// Stop the extension host first to give extensions most time to shutdown
144 145
		this.extensionService.stopExtensionHost();

146 147 148 149
		const startExtensionHost = () => {
			this.extensionService.startExtensionHost();
		};

150
		return mainSidePromise().then(result => {
151

152 153 154
			// Migrate storage and settings if we are to enter a workspace
			if (result) {
				return this.migrate(result.workspace).then(() => {
155

156 157 158
					// Show message to user (once)
					this.informUserOnce(); // TODO@Ben remove me after a couple of releases

159 160 161 162 163
					// Reinitialize backup service
					const backupFileService = this.backupFileService as BackupFileService; // TODO@Ben ugly cast
					backupFileService.initialize(result.backupPath);

					// Reinitialize configuration service
164
					const workspaceImpl = this.contextService as WorkspaceService; // TODO@Ben TODO@Sandeep ugly cast
165
					return workspaceImpl.initialize(result.workspace);
166 167 168
				});
			}

169
			return TPromise.as(void 0);
B
Benjamin Pasero 已提交
170 171 172 173 174
		}).then(startExtensionHost, error => {
			startExtensionHost(); // in any case start the extension host again!

			return TPromise.wrapError(error);
		});
175 176
	}

177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
	private informUserOnce(): void {
		if (product.quality !== 'stable') {
			return; // only for stable
		}

		if (this.storageService.getBoolean(WorkspaceEditingService.INFO_MESSAGE_KEY)) {
			return; // user does not want to see it again
		}

		const okAction = new Action(
			'enterWorkspace.ok',
			nls.localize('integrity.ok', "OK"),
			null,
			true,
			() => TPromise.as(true)
		);

		const dontShowAgainAction = new Action(
			'enterWorkspace.dontShowAgain',
			nls.localize('enterWorkspace.dontShowAgain', "Don't Show Again"),
			null,
			true,
			() => {
				this.storageService.store(WorkspaceEditingService.INFO_MESSAGE_KEY, true, StorageScope.GLOBAL);

				return TPromise.as(true);
			}
		);
		const moreInfoAction = new Action(
			'enterWorkspace.moreInfo',
			nls.localize('enterWorkspace.moreInfo', "More Information"),
			null,
			true,
			() => {
				const uri = URI.parse(product.documentationUrl);
				window.open(uri.toString(true));

				return TPromise.as(true);
			}
		);

		this.messageService.show(Severity.Info, {
			message: nls.localize('enterWorkspace.prompt', "The opened workspace changed into a multi-root workspace."),
			actions: [okAction, moreInfoAction, dontShowAgainAction]
		});
	}

B
Benjamin Pasero 已提交
224
	private migrate(toWorkspace: IWorkspaceIdentifier): TPromise<void> {
225 226

		// Storage (UI State) migration
B
Benjamin Pasero 已提交
227
		this.migrateStorage(toWorkspace);
228

229 230 231 232 233 234
		// Settings migration (only if we come from a folder workspace)
		if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
			return this.copyWorkspaceSettings(toWorkspace);
		}

		return TPromise.as(void 0);
235 236
	}

B
Benjamin Pasero 已提交
237
	private migrateStorage(toWorkspace: IWorkspaceIdentifier): void {
238 239 240

		// TODO@Ben revisit this when we move away from local storage to a file based approach
		const storageImpl = this.storageService as StorageService;
B
Benjamin Pasero 已提交
241 242
		const newWorkspaceId = migrateStorageToMultiRootWorkspace(storageImpl.workspaceId, toWorkspace, storageImpl.workspaceStorage);
		storageImpl.setWorkspaceId(newWorkspaceId);
243 244
	}

245
	public copyWorkspaceSettings(toWorkspace: IWorkspaceIdentifier): TPromise<void> {
246 247 248
		const configurationProperties = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();
		const targetWorkspaceConfiguration = {};
		for (const key of this.workspaceConfigurationService.keys().workspace) {
S
Sandeep Somavarapu 已提交
249
			if (configurationProperties[key] && !configurationProperties[key].isFromExtensions && configurationProperties[key].scope === ConfigurationScope.WINDOW) {
250
				targetWorkspaceConfiguration[key] = this.workspaceConfigurationService.inspect(key).workspace;
251 252 253
			}
		}

B
Benjamin Pasero 已提交
254
		return this.jsonEditingService.write(URI.file(toWorkspace.configPath), { key: 'settings', value: targetWorkspaceConfiguration }, true);
255
	}
J
Johannes Rieken 已提交
256
}