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

B
Benjamin Pasero 已提交
6
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
7
import { URI } from 'vs/base/common/uri';
S
Sandeep Somavarapu 已提交
8
import * as nls from 'vs/nls';
9
import { TPromise } from 'vs/base/common/winjs.base';
10
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
S
Sandeep Somavarapu 已提交
11
import { IWindowService, IEnterWorkspaceResult } from 'vs/platform/windows/common/windows';
S
Sandeep Somavarapu 已提交
12
import { IJSONEditingService, JSONEditingError, JSONEditingErrorCode } from 'vs/workbench/services/configuration/common/jsonEditing';
13
import { IWorkspaceIdentifier, IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces';
14
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
15
import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService';
16 17
import { IStorageService } from 'vs/platform/storage/common/storage';
import { DelegatingStorageService } from 'vs/platform/storage/node/storageService';
B
Benjamin Pasero 已提交
18
import { ConfigurationScope, IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
19
import { Registry } from 'vs/platform/registry/common/platform';
20
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
21 22
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { BackupFileService } from 'vs/workbench/services/backup/node/backupFileService';
S
Sandeep Somavarapu 已提交
23
import { ICommandService } from 'vs/platform/commands/common/commands';
24 25
import { distinct } from 'vs/base/common/arrays';
import { isLinux } from 'vs/base/common/platform';
26
import { isEqual } from 'vs/base/common/resources';
27
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
B
Benjamin Pasero 已提交
28 29 30
import { join } from 'path';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { mkdirp } from 'vs/base/node/pfs';
31 32 33

export class WorkspaceEditingService implements IWorkspaceEditingService {

B
Benjamin Pasero 已提交
34
	_serviceBrand: any;
35 36

	constructor(
37
		@IJSONEditingService private jsonEditingService: IJSONEditingService,
38
		@IWorkspaceContextService private contextService: WorkspaceService,
39
		@IWindowService private windowService: IWindowService,
40
		@IWorkspaceConfigurationService private workspaceConfigurationService: IWorkspaceConfigurationService,
41
		@IStorageService private storageService: IStorageService,
42
		@IExtensionService private extensionService: IExtensionService,
S
Sandeep Somavarapu 已提交
43
		@IBackupFileService private backupFileService: IBackupFileService,
44
		@INotificationService private notificationService: INotificationService,
B
Benjamin Pasero 已提交
45 46
		@ICommandService private commandService: ICommandService,
		@IEnvironmentService private environmentService: IEnvironmentService
47 48 49
	) {
	}

B
Benjamin Pasero 已提交
50
	updateFolders(index: number, deleteCount?: number, foldersToAdd?: IWorkspaceFolderCreationData[], donotNotifyError?: boolean): TPromise<void> {
B
Benjamin Pasero 已提交
51 52
		const folders = this.contextService.getWorkspace().folders;

53 54 55
		let foldersToDelete: URI[] = [];
		if (typeof deleteCount === 'number') {
			foldersToDelete = folders.slice(index, index + deleteCount).map(f => f.uri);
B
Benjamin Pasero 已提交
56 57
		}

58 59 60 61 62 63
		const wantsToDelete = foldersToDelete.length > 0;
		const wantsToAdd = Array.isArray(foldersToAdd) && foldersToAdd.length > 0;

		if (!wantsToAdd && !wantsToDelete) {
			return TPromise.as(void 0); // return early if there is nothing to do
		}
B
Benjamin Pasero 已提交
64 65 66 67 68 69 70 71

		// Add Folders
		if (wantsToAdd && !wantsToDelete) {
			return this.doAddFolders(foldersToAdd, index, donotNotifyError);
		}

		// Delete Folders
		if (wantsToDelete && !wantsToAdd) {
72
			return this.removeFolders(foldersToDelete);
B
Benjamin Pasero 已提交
73 74
		}

75
		// Add & Delete Folders
76 77
		else {

78 79 80
			// if we are in single-folder state and the folder is replaced with
			// other folders, we handle this specially and just enter workspace
			// mode with the folders that are being added.
81 82 83 84 85 86 87 88 89 90 91
			if (this.includesSingleFolderWorkspace(foldersToDelete)) {
				return this.createAndEnterWorkspace(foldersToAdd);
			}

			// if we are not in workspace-state, we just add the folders
			if (this.contextService.getWorkbenchState() !== WorkbenchState.WORKSPACE) {
				return this.doAddFolders(foldersToAdd, index, donotNotifyError);
			}

			// finally, update folders within the workspace
			return this.doUpdateFolders(foldersToAdd, foldersToDelete, index, donotNotifyError);
92
		}
93
	}
94

95 96 97
	private doUpdateFolders(foldersToAdd: IWorkspaceFolderCreationData[], foldersToDelete: URI[], index?: number, donotNotifyError: boolean = false): TPromise<void> {
		return this.contextService.updateFolders(foldersToAdd, foldersToDelete, index)
			.then(() => null, error => donotNotifyError ? TPromise.wrapError(error) : this.handleWorkspaceConfigurationEditingError(error));
B
Benjamin Pasero 已提交
98 99
	}

B
Benjamin Pasero 已提交
100
	addFolders(foldersToAdd: IWorkspaceFolderCreationData[], donotNotifyError: boolean = false): TPromise<void> {
B
Benjamin Pasero 已提交
101 102 103 104
		return this.doAddFolders(foldersToAdd, void 0, donotNotifyError);
	}

	private doAddFolders(foldersToAdd: IWorkspaceFolderCreationData[], index?: number, donotNotifyError: boolean = false): TPromise<void> {
105 106 107 108 109
		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) {
110 111 112
			let newWorkspaceFolders = this.contextService.getWorkspace().folders.map(folder => ({ uri: folder.uri } as IWorkspaceFolderCreationData));
			newWorkspaceFolders.splice(typeof index === 'number' ? index : newWorkspaceFolders.length, 0, ...foldersToAdd);
			newWorkspaceFolders = distinct(newWorkspaceFolders, folder => isLinux ? folder.uri.toString() : folder.uri.toString().toLowerCase());
113 114 115 116 117 118 119 120 121

			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
B
Benjamin Pasero 已提交
122
		return this.contextService.addFolders(foldersToAdd, index)
123
			.then(() => null, error => donotNotifyError ? TPromise.wrapError(error) : this.handleWorkspaceConfigurationEditingError(error));
S
Sandeep Somavarapu 已提交
124 125
	}

B
Benjamin Pasero 已提交
126
	removeFolders(foldersToRemove: URI[], donotNotifyError: boolean = false): TPromise<void> {
127 128

		// If we are in single-folder state and the opened folder is to be removed,
129 130 131
		// we create an empty workspace and enter it.
		if (this.includesSingleFolderWorkspace(foldersToRemove)) {
			return this.createAndEnterWorkspace([]);
132 133 134 135
		}

		// Delegate removal of folders to workspace service otherwise
		return this.contextService.removeFolders(foldersToRemove)
136
			.then(() => null, error => donotNotifyError ? TPromise.wrapError(error) : this.handleWorkspaceConfigurationEditingError(error));
S
Sandeep Somavarapu 已提交
137 138
	}

139 140 141
	private includesSingleFolderWorkspace(folders: URI[]): boolean {
		if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
			const workspaceFolder = this.contextService.getWorkspace().folders[0];
142
			return (folders.some(folder => isEqual(folder, workspaceFolder.uri)));
143 144 145 146 147
		}

		return false;
	}

148 149 150 151
	enterWorkspace(path: string): TPromise<void> {
		return this.doEnterWorkspace(() => this.windowService.enterWorkspace(path));
	}

B
Benjamin Pasero 已提交
152
	createAndEnterWorkspace(folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<void> {
153
		return this.doEnterWorkspace(() => this.windowService.createAndEnterWorkspace(folders, path));
154 155
	}

B
Benjamin Pasero 已提交
156
	saveAndEnterWorkspace(path: string): TPromise<void> {
157
		return this.doEnterWorkspace(() => this.windowService.saveAndEnterWorkspace(path));
158 159
	}

S
Sandeep Somavarapu 已提交
160 161 162
	private handleWorkspaceConfigurationEditingError(error: JSONEditingError): TPromise<void> {
		switch (error.code) {
			case JSONEditingErrorCode.ERROR_INVALID_FILE:
163 164
				this.onInvalidWorkspaceConfigurationFileError();
				return TPromise.as(void 0);
S
Sandeep Somavarapu 已提交
165
			case JSONEditingErrorCode.ERROR_FILE_DIRTY:
166 167
				this.onWorkspaceConfigurationFileDirtyError();
				return TPromise.as(void 0);
S
Sandeep Somavarapu 已提交
168
		}
169
		this.notificationService.error(error.message);
S
Sandeep Somavarapu 已提交
170 171 172
		return TPromise.as(void 0);
	}

173
	private onInvalidWorkspaceConfigurationFileError(): void {
S
Sandeep Somavarapu 已提交
174
		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.");
175
		this.askToOpenWorkspaceConfigurationFile(message);
S
Sandeep Somavarapu 已提交
176 177
	}

178
	private onWorkspaceConfigurationFileDirtyError(): void {
S
Sandeep Somavarapu 已提交
179
		const message = nls.localize('errorWorkspaceConfigurationFileDirty', "Unable to write into workspace configuration file because the file is dirty. Please save it and try again.");
180
		this.askToOpenWorkspaceConfigurationFile(message);
S
Sandeep Somavarapu 已提交
181 182
	}

183 184 185 186 187 188 189
	private askToOpenWorkspaceConfigurationFile(message: string): void {
		this.notificationService.prompt(Severity.Error, message,
			[{
				label: nls.localize('openWorkspaceConfigurationFile', "Open Workspace Configuration"),
				run: () => this.commandService.executeCommand('workbench.action.openWorkspaceConfigFile')
			}]
		);
S
Sandeep Somavarapu 已提交
190 191
	}

192
	private doEnterWorkspace(mainSidePromise: () => TPromise<IEnterWorkspaceResult>): TPromise<void> {
193

194
		// Stop the extension host first to give extensions most time to shutdown
195
		this.extensionService.stopExtensionHost();
S
Sandeep Somavarapu 已提交
196
		let extensionHostStarted: boolean = false;
197

198 199
		const startExtensionHost = () => {
			this.extensionService.startExtensionHost();
S
Sandeep Somavarapu 已提交
200
			extensionHostStarted = true;
201 202
		};

203
		return mainSidePromise().then(result => {
204

205 206 207
			// Migrate storage and settings if we are to enter a workspace
			if (result) {
				return this.migrate(result.workspace).then(() => {
208

209
					// Reinitialize backup service
B
Benjamin Pasero 已提交
210 211 212
					if (this.backupFileService instanceof BackupFileService) {
						this.backupFileService.initialize(result.backupPath);
					}
213 214

					// Reinitialize configuration service
215
					const workspaceImpl = this.contextService as WorkspaceService;
S
Sandeep Somavarapu 已提交
216
					return workspaceImpl.initialize(result.workspace, startExtensionHost);
217 218 219
				});
			}

220
			return TPromise.as(void 0);
S
Sandeep Somavarapu 已提交
221 222 223 224
		}).then(null, error => {
			if (!extensionHostStarted) {
				startExtensionHost(); // start the extension host if not started
			}
B
Benjamin Pasero 已提交
225 226
			return TPromise.wrapError(error);
		});
227 228
	}

B
Benjamin Pasero 已提交
229
	private migrate(toWorkspace: IWorkspaceIdentifier): TPromise<void> {
230

231 232
		// Storage migration
		return this.migrateStorage(toWorkspace).then(() => {
233

234 235 236 237
			// Settings migration (only if we come from a folder workspace)
			if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
				return this.migrateWorkspaceSettings(toWorkspace);
			}
238

239 240
			return void 0;
		});
241 242
	}

243
	private migrateStorage(toWorkspace: IWorkspaceIdentifier): TPromise<void> {
B
Benjamin Pasero 已提交
244
		const newWorkspaceStorageHome = join(this.environmentService.workspaceStorageHome, toWorkspace.id);
245

B
Benjamin Pasero 已提交
246 247 248 249 250
		return mkdirp(newWorkspaceStorageHome).then(() => {
			const storageImpl = this.storageService as DelegatingStorageService;

			return storageImpl.storage.migrate(newWorkspaceStorageHome);
		});
251 252
	}

B
Benjamin Pasero 已提交
253
	private migrateWorkspaceSettings(toWorkspace: IWorkspaceIdentifier): TPromise<void> {
254
		return this.doCopyWorkspaceSettings(toWorkspace, setting => setting.scope === ConfigurationScope.WINDOW);
B
Benjamin Pasero 已提交
255 256
	}

B
Benjamin Pasero 已提交
257
	copyWorkspaceSettings(toWorkspace: IWorkspaceIdentifier): TPromise<void> {
B
Benjamin Pasero 已提交
258 259 260 261
		return this.doCopyWorkspaceSettings(toWorkspace);
	}

	private doCopyWorkspaceSettings(toWorkspace: IWorkspaceIdentifier, filter?: (config: IConfigurationPropertySchema) => boolean): TPromise<void> {
262 263 264
		const configurationProperties = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();
		const targetWorkspaceConfiguration = {};
		for (const key of this.workspaceConfigurationService.keys().workspace) {
B
Benjamin Pasero 已提交
265 266 267 268 269
			if (configurationProperties[key]) {
				if (filter && !filter(configurationProperties[key])) {
					continue;
				}

270
				targetWorkspaceConfiguration[key] = this.workspaceConfigurationService.inspect(key).workspace;
271 272 273
			}
		}

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