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';
15
import { IWorkspaceIdentifier, IWorkspaceFolderCreationData } 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
	public addFolders(foldersToAdd: IWorkspaceFolderCreationData[]): TPromise<void> {
55 56 57 58 59
		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) {
60 61 62 63
			const newWorkspaceFolders: IWorkspaceFolderCreationData[] = distinct([
				...this.contextService.getWorkspace().folders.map(folder => ({ uri: folder.uri } as IWorkspaceFolderCreationData)),
				...foldersToAdd
			] as IWorkspaceFolderCreationData[], folder => isLinux ? folder.uri.toString() : folder.uri.toString().toLowerCase());
64 65 66 67 68 69 70 71 72 73

			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 已提交
74 75 76
			.then(() => null, error => this.handleWorkspaceConfigurationEditingError(error));
	}

77 78 79 80 81 82
	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 已提交
83
			if (foldersToRemove.some(folder => isEqual(folder, workspaceFolder.uri, !isLinux))) {
84 85 86 87 88 89
				return this.windowService.closeWorkspace();
			}
		}

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

93 94
	public createAndEnterWorkspace(folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<void> {
		return this.doEnterWorkspace(() => this.windowService.createAndEnterWorkspace(folders, path));
95 96
	}

97 98
	public saveAndEnterWorkspace(path: string): TPromise<void> {
		return this.doEnterWorkspace(() => this.windowService.saveAndEnterWorkspace(path));
99 100
	}

S
Sandeep Somavarapu 已提交
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
	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;
				}
			});
	}

133
	private doEnterWorkspace(mainSidePromise: () => TPromise<IEnterWorkspaceResult>): TPromise<void> {
134

135
		// Stop the extension host first to give extensions most time to shutdown
136 137
		this.extensionService.stopExtensionHost();

138 139 140 141
		const startExtensionHost = () => {
			this.extensionService.startExtensionHost();
		};

142
		return mainSidePromise().then(result => {
143

144 145 146
			// Migrate storage and settings if we are to enter a workspace
			if (result) {
				return this.migrate(result.workspace).then(() => {
147

148 149 150
					// Show message to user (once)
					this.informUserOnce(); // TODO@Ben remove me after a couple of releases

151 152 153 154 155
					// Reinitialize backup service
					const backupFileService = this.backupFileService as BackupFileService; // TODO@Ben ugly cast
					backupFileService.initialize(result.backupPath);

					// Reinitialize configuration service
156
					const workspaceImpl = this.contextService as WorkspaceService; // TODO@Ben TODO@Sandeep ugly cast
157
					return workspaceImpl.initialize(result.workspace);
158 159 160
				});
			}

161
			return TPromise.as(void 0);
B
Benjamin Pasero 已提交
162 163 164 165 166
		}).then(startExtensionHost, error => {
			startExtensionHost(); // in any case start the extension host again!

			return TPromise.wrapError(error);
		});
167 168
	}

169
	private informUserOnce(): void {
B
Benjamin Pasero 已提交
170 171 172
		if (product.quality !== 'stable') {
			return; // only for stable
		}
173

B
Benjamin Pasero 已提交
174 175 176
		if (this.storageService.getBoolean(WorkspaceEditingService.INFO_MESSAGE_KEY)) {
			return; // user does not want to see it again
		}
177

B
Benjamin Pasero 已提交
178 179 180
		const closeAction = new Action(
			'enterWorkspace.close',
			nls.localize('enterWorkspace.close', "Close"),
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
			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, {
B
Benjamin Pasero 已提交
211 212
			message: nls.localize('enterWorkspace.prompt', "Learn more about working with multiple folders in VS Code."),
			actions: [moreInfoAction, dontShowAgainAction, closeAction]
213 214 215
		});
	}

B
Benjamin Pasero 已提交
216
	private migrate(toWorkspace: IWorkspaceIdentifier): TPromise<void> {
217 218

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

221 222 223 224 225 226
		// 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);
227 228
	}

B
Benjamin Pasero 已提交
229
	private migrateStorage(toWorkspace: IWorkspaceIdentifier): void {
230 231 232

		// 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 已提交
233 234
		const newWorkspaceId = migrateStorageToMultiRootWorkspace(storageImpl.workspaceId, toWorkspace, storageImpl.workspaceStorage);
		storageImpl.setWorkspaceId(newWorkspaceId);
235 236
	}

237
	public copyWorkspaceSettings(toWorkspace: IWorkspaceIdentifier): TPromise<void> {
238 239 240
		const configurationProperties = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();
		const targetWorkspaceConfiguration = {};
		for (const key of this.workspaceConfigurationService.keys().workspace) {
S
Sandeep Somavarapu 已提交
241
			if (configurationProperties[key] && !configurationProperties[key].isFromExtensions && configurationProperties[key].scope === ConfigurationScope.WINDOW) {
242
				targetWorkspaceConfiguration[key] = this.workspaceConfigurationService.inspect(key).workspace;
243 244 245
			}
		}

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