-
Notifications
You must be signed in to change notification settings - Fork 28.8k
/
workspaceEditingService.ts
321 lines (259 loc) · 14 KB
/
workspaceEditingService.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
import { URI } from 'vs/base/common/uri';
import * as nls from 'vs/nls';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IWindowService, MessageBoxOptions, IWindowsService } from 'vs/platform/windows/common/windows';
import { IJSONEditingService, JSONEditingError, JSONEditingErrorCode } from 'vs/workbench/services/configuration/common/jsonEditing';
import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, isWorkspaceIdentifier, toWorkspaceIdentifier, IWorkspacesService, rewriteWorkspaceFileForNewLocation } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { StorageService } from 'vs/platform/storage/node/storageService';
import { ConfigurationScope, IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { BackupFileService } from 'vs/workbench/services/backup/node/backupFileService';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { distinct } from 'vs/base/common/arrays';
import { isLinux } from 'vs/base/common/platform';
import { isEqual, basename } from 'vs/base/common/resources';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IFileService } from 'vs/platform/files/common/files';
export class WorkspaceEditingService implements IWorkspaceEditingService {
_serviceBrand: any;
constructor(
@IJSONEditingService private readonly jsonEditingService: IJSONEditingService,
@IWorkspaceContextService private readonly contextService: WorkspaceService,
@IWindowService private readonly windowService: IWindowService,
@IWorkspaceConfigurationService private readonly workspaceConfigurationService: IWorkspaceConfigurationService,
@IStorageService private readonly storageService: IStorageService,
@IExtensionService private readonly extensionService: IExtensionService,
@IBackupFileService private readonly backupFileService: IBackupFileService,
@INotificationService private readonly notificationService: INotificationService,
@ICommandService private readonly commandService: ICommandService,
@IFileService private readonly fileSystemService: IFileService,
@IWindowsService private readonly windowsService: IWindowsService,
@IWorkspacesService private readonly workspaceService: IWorkspacesService
) {
}
updateFolders(index: number, deleteCount?: number, foldersToAdd?: IWorkspaceFolderCreationData[], donotNotifyError?: boolean): Promise<void> {
const folders = this.contextService.getWorkspace().folders;
let foldersToDelete: URI[] = [];
if (typeof deleteCount === 'number') {
foldersToDelete = folders.slice(index, index + deleteCount).map(f => f.uri);
}
const wantsToDelete = foldersToDelete.length > 0;
const wantsToAdd = Array.isArray(foldersToAdd) && foldersToAdd.length > 0;
if (!wantsToAdd && !wantsToDelete) {
return Promise.resolve(); // return early if there is nothing to do
}
// Add Folders
if (wantsToAdd && !wantsToDelete && Array.isArray(foldersToAdd)) {
return this.doAddFolders(foldersToAdd, index, donotNotifyError);
}
// Delete Folders
if (wantsToDelete && !wantsToAdd) {
return this.removeFolders(foldersToDelete);
}
// Add & Delete Folders
else {
// 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.
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);
}
}
private doUpdateFolders(foldersToAdd: IWorkspaceFolderCreationData[], foldersToDelete: URI[], index?: number, donotNotifyError: boolean = false): Promise<void> {
return this.contextService.updateFolders(foldersToAdd, foldersToDelete, index)
.then(() => null, error => donotNotifyError ? Promise.reject(error) : this.handleWorkspaceConfigurationEditingError(error));
}
addFolders(foldersToAdd: IWorkspaceFolderCreationData[], donotNotifyError: boolean = false): Promise<void> {
return this.doAddFolders(foldersToAdd, undefined, donotNotifyError);
}
private doAddFolders(foldersToAdd: IWorkspaceFolderCreationData[], index?: number, donotNotifyError: boolean = false): Promise<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) {
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());
if (state === WorkbenchState.EMPTY && newWorkspaceFolders.length === 0 || state === WorkbenchState.FOLDER && newWorkspaceFolders.length === 1) {
return Promise.resolve(); // 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, index)
.then(() => null, error => donotNotifyError ? Promise.reject(error) : this.handleWorkspaceConfigurationEditingError(error));
}
removeFolders(foldersToRemove: URI[], donotNotifyError: boolean = false): Promise<void> {
// If we are in single-folder state and the opened folder is to be removed,
// we create an empty workspace and enter it.
if (this.includesSingleFolderWorkspace(foldersToRemove)) {
return this.createAndEnterWorkspace([]);
}
// Delegate removal of folders to workspace service otherwise
return this.contextService.removeFolders(foldersToRemove)
.then(() => null, error => donotNotifyError ? Promise.reject(error) : this.handleWorkspaceConfigurationEditingError(error));
}
private includesSingleFolderWorkspace(folders: URI[]): boolean {
if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
const workspaceFolder = this.contextService.getWorkspace().folders[0];
return (folders.some(folder => isEqual(folder, workspaceFolder.uri)));
}
return false;
}
async createAndEnterWorkspace(folders: IWorkspaceFolderCreationData[], path?: URI): Promise<void> {
if (path && !this.isValidTargetWorkspacePath(path)) {
return Promise.reject(null);
}
const untitledWorkspace = await this.workspaceService.createUntitledWorkspace(folders);
if (path) {
await this.saveWorkspaceAs(untitledWorkspace, path);
} else {
path = untitledWorkspace.configPath;
}
return this.enterWorkspace(path);
}
async saveAndEnterWorkspace(path: URI): Promise<void> {
if (!this.isValidTargetWorkspacePath(path)) {
return Promise.reject(null);
}
const currentWorkspaceIdentifier = toWorkspaceIdentifier(this.contextService.getWorkspace());
if (!isWorkspaceIdentifier(currentWorkspaceIdentifier)) {
return Promise.reject(null);
}
await this.saveWorkspaceAs(currentWorkspaceIdentifier, path);
return this.enterWorkspace(path);
}
async isValidTargetWorkspacePath(path: URI): Promise<boolean> {
const windows = await this.windowsService.getWindows();
// Prevent overwriting a workspace that is currently opened in another window
if (windows.some(window => !!window.workspace && isEqual(window.workspace.configPath, path))) {
const options: MessageBoxOptions = {
type: 'info',
buttons: [nls.localize('ok', "OK")],
message: nls.localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(path)),
detail: nls.localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again."),
noLink: true
};
return this.windowService.showMessageBox(options).then(() => false);
}
return Promise.resolve(true); // OK
}
private async saveWorkspaceAs(workspace: IWorkspaceIdentifier, targetConfigPathURI: URI): Promise<any> {
const configPathURI = workspace.configPath;
// Return early if target is same as source
if (isEqual(configPathURI, targetConfigPathURI)) {
return Promise.resolve(null);
}
// Read the contents of the workspace file, update it to new location and save it.
const raw = await this.fileSystemService.resolveContent(configPathURI);
const newRawWorkspaceContents = rewriteWorkspaceFileForNewLocation(raw.value, configPathURI, targetConfigPathURI);
await this.fileSystemService.createFile(targetConfigPathURI, newRawWorkspaceContents, { overwrite: true });
}
private handleWorkspaceConfigurationEditingError(error: JSONEditingError): Promise<void> {
switch (error.code) {
case JSONEditingErrorCode.ERROR_INVALID_FILE:
this.onInvalidWorkspaceConfigurationFileError();
return Promise.resolve();
case JSONEditingErrorCode.ERROR_FILE_DIRTY:
this.onWorkspaceConfigurationFileDirtyError();
return Promise.resolve();
}
this.notificationService.error(error.message);
return Promise.resolve();
}
private onInvalidWorkspaceConfigurationFileError(): 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.");
this.askToOpenWorkspaceConfigurationFile(message);
}
private onWorkspaceConfigurationFileDirtyError(): void {
const message = nls.localize('errorWorkspaceConfigurationFileDirty', "Unable to write into workspace configuration file because the file is dirty. Please save it and try again.");
this.askToOpenWorkspaceConfigurationFile(message);
}
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')
}]
);
}
enterWorkspace(path: URI): Promise<void> {
// Stop the extension host first to give extensions most time to shutdown
this.extensionService.stopExtensionHost();
let extensionHostStarted: boolean = false;
const startExtensionHost = () => {
this.extensionService.startExtensionHost();
extensionHostStarted = true;
};
return this.windowService.enterWorkspace(path).then(result => {
// Migrate storage and settings if we are to enter a workspace
if (result) {
return this.migrate(result.workspace).then(() => {
// Reinitialize backup service
if (this.backupFileService instanceof BackupFileService) {
this.backupFileService.initialize(result.backupPath!);
}
// Reinitialize configuration service
const workspaceImpl = this.contextService as WorkspaceService;
return workspaceImpl.initialize(result.workspace, startExtensionHost);
});
}
return Promise.resolve();
}).then(undefined, error => {
if (!extensionHostStarted) {
startExtensionHost(); // start the extension host if not started
}
return Promise.reject(error);
});
}
private migrate(toWorkspace: IWorkspaceIdentifier): Promise<void> {
// Storage migration
return this.migrateStorage(toWorkspace).then(() => {
// Settings migration (only if we come from a folder workspace)
if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
return this.migrateWorkspaceSettings(toWorkspace);
}
return undefined;
});
}
private migrateStorage(toWorkspace: IWorkspaceIdentifier): Promise<void> {
const storageImpl = this.storageService as StorageService;
return storageImpl.migrate(toWorkspace);
}
private migrateWorkspaceSettings(toWorkspace: IWorkspaceIdentifier): Promise<void> {
return this.doCopyWorkspaceSettings(toWorkspace, setting => setting.scope === ConfigurationScope.WINDOW);
}
copyWorkspaceSettings(toWorkspace: IWorkspaceIdentifier): Promise<void> {
return this.doCopyWorkspaceSettings(toWorkspace);
}
private doCopyWorkspaceSettings(toWorkspace: IWorkspaceIdentifier, filter?: (config: IConfigurationPropertySchema) => boolean): Promise<void> {
const configurationProperties = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();
const targetWorkspaceConfiguration = {};
for (const key of this.workspaceConfigurationService.keys().workspace) {
if (configurationProperties[key]) {
if (filter && !filter(configurationProperties[key])) {
continue;
}
targetWorkspaceConfiguration[key] = this.workspaceConfigurationService.inspect(key).workspace;
}
}
return this.jsonEditingService.write(toWorkspace.configPath, { key: 'settings', value: targetWorkspaceConfiguration }, true);
}
}