Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/browser/actions/workspaceCommands.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { localize, localize2 } from '../../../nls.js';
7
import { hasWorkspaceFileExtension, IWorkspaceContextService } from '../../../platform/workspace/common/workspace.js';
8
import { IWorkspaceEditingService } from '../../services/workspaces/common/workspaceEditing.js';
9
import { dirname } from '../../../base/common/resources.js';
10
import { CancellationToken } from '../../../base/common/cancellation.js';
11
import { mnemonicButtonLabel } from '../../../base/common/labels.js';
12
import { CommandsRegistry, ICommandService } from '../../../platform/commands/common/commands.js';
13
import { FileKind } from '../../../platform/files/common/files.js';
14
import { ServicesAccessor } from '../../../platform/instantiation/common/instantiation.js';
15
import { ILabelService } from '../../../platform/label/common/label.js';
16
import { IQuickInputService, IPickOptions, IQuickPickItem } from '../../../platform/quickinput/common/quickInput.js';
17
import { getIconClasses } from '../../../editor/common/services/getIconClasses.js';
18
import { IModelService } from '../../../editor/common/services/model.js';
19
import { ILanguageService } from '../../../editor/common/languages/language.js';
20
import { IFileDialogService, IPickAndOpenOptions } from '../../../platform/dialogs/common/dialogs.js';
21
import { URI, UriComponents } from '../../../base/common/uri.js';
22
import { Schemas } from '../../../base/common/network.js';
23
import { IFileToOpen, IFolderToOpen, IOpenEmptyWindowOptions, IOpenWindowOptions, IWorkspaceToOpen } from '../../../platform/window/common/window.js';
24
import { IRecent, IWorkspacesService } from '../../../platform/workspaces/common/workspaces.js';
25
import { IPathService } from '../../services/path/common/pathService.js';
26
import { ILocalizedString } from '../../../platform/action/common/action.js';
27
28
export const ADD_ROOT_FOLDER_COMMAND_ID = 'addRootFolder';
29
export const ADD_ROOT_FOLDER_LABEL: ILocalizedString = localize2('addFolderToWorkspace', 'Add Folder to Workspace...');
30
31
export const SET_ROOT_FOLDER_COMMAND_ID = 'setRootFolder';
32
33
export const PICK_WORKSPACE_FOLDER_COMMAND_ID = '_workbench.pickWorkspaceFolder';
34
35
// Command registration
36
37
CommandsRegistry.registerCommand({
38
id: 'workbench.action.files.openFileFolderInNewWindow',
39
handler: (accessor: ServicesAccessor) => accessor.get(IFileDialogService).pickFileFolderAndOpen({ forceNewWindow: true })
40
});
41
42
CommandsRegistry.registerCommand({
43
id: '_files.pickFolderAndOpen',
44
handler: (accessor: ServicesAccessor, options: { forceNewWindow: boolean }) => accessor.get(IFileDialogService).pickFolderAndOpen(options)
45
});
46
47
CommandsRegistry.registerCommand({
48
id: 'workbench.action.files.openFolderInNewWindow',
49
handler: (accessor: ServicesAccessor) => accessor.get(IFileDialogService).pickFolderAndOpen({ forceNewWindow: true })
50
});
51
52
CommandsRegistry.registerCommand({
53
id: 'workbench.action.files.openFileInNewWindow',
54
handler: (accessor: ServicesAccessor) => accessor.get(IFileDialogService).pickFileAndOpen({ forceNewWindow: true })
55
});
56
57
CommandsRegistry.registerCommand({
58
id: 'workbench.action.openWorkspaceInNewWindow',
59
handler: (accessor: ServicesAccessor) => accessor.get(IFileDialogService).pickWorkspaceAndOpen({ forceNewWindow: true })
60
});
61
62
CommandsRegistry.registerCommand({
63
id: ADD_ROOT_FOLDER_COMMAND_ID,
64
handler: async (accessor) => {
65
const workspaceEditingService = accessor.get(IWorkspaceEditingService);
66
67
const folders = await selectWorkspaceFolders(accessor);
68
if (!folders || !folders.length) {
69
return;
70
}
71
72
await workspaceEditingService.addFolders(folders.map(folder => ({ uri: folder })));
73
}
74
});
75
76
CommandsRegistry.registerCommand({
77
id: SET_ROOT_FOLDER_COMMAND_ID,
78
handler: async (accessor) => {
79
const workspaceEditingService = accessor.get(IWorkspaceEditingService);
80
const contextService = accessor.get(IWorkspaceContextService);
81
82
const folders = await selectWorkspaceFolders(accessor);
83
if (!folders || !folders.length) {
84
return;
85
}
86
87
await workspaceEditingService.updateFolders(0, contextService.getWorkspace().folders.length, folders.map(folder => ({ uri: folder })));
88
}
89
});
90
91
async function selectWorkspaceFolders(accessor: ServicesAccessor): Promise<URI[] | undefined> {
92
const dialogsService = accessor.get(IFileDialogService);
93
const pathService = accessor.get(IPathService);
94
95
const folders = await dialogsService.showOpenDialog({
96
openLabel: mnemonicButtonLabel(localize({ key: 'add', comment: ['&& denotes a mnemonic'] }, "&&Add")),
97
title: localize('addFolderToWorkspaceTitle', "Add Folder to Workspace"),
98
canSelectFolders: true,
99
canSelectMany: true,
100
defaultUri: await dialogsService.defaultFolderPath(),
101
availableFileSystems: [pathService.defaultUriScheme]
102
});
103
104
return folders;
105
}
106
107
CommandsRegistry.registerCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, async function (accessor, args?: [IPickOptions<IQuickPickItem>, CancellationToken]) {
108
const quickInputService = accessor.get(IQuickInputService);
109
const labelService = accessor.get(ILabelService);
110
const contextService = accessor.get(IWorkspaceContextService);
111
const modelService = accessor.get(IModelService);
112
const languageService = accessor.get(ILanguageService);
113
114
const folders = contextService.getWorkspace().folders;
115
if (!folders.length) {
116
return;
117
}
118
119
const folderPicks: IQuickPickItem[] = folders.map(folder => {
120
const label = folder.name;
121
const description = labelService.getUriLabel(dirname(folder.uri), { relative: true });
122
123
return {
124
label,
125
description: description !== label ? description : undefined, // https://github.com/microsoft/vscode/issues/183418
126
folder,
127
iconClasses: getIconClasses(modelService, languageService, folder.uri, FileKind.ROOT_FOLDER)
128
};
129
});
130
131
const options: IPickOptions<IQuickPickItem> = (args ? args[0] : undefined) || Object.create(null);
132
133
if (!options.activeItem) {
134
options.activeItem = folderPicks[0];
135
}
136
137
if (!options.placeHolder) {
138
options.placeHolder = localize('workspaceFolderPickerPlaceholder', "Select workspace folder");
139
}
140
141
if (typeof options.matchOnDescription !== 'boolean') {
142
options.matchOnDescription = true;
143
}
144
145
const token: CancellationToken = (args ? args[1] : undefined) || CancellationToken.None;
146
const pick = await quickInputService.pick(folderPicks, options, token);
147
if (pick) {
148
return folders[folderPicks.indexOf(pick)];
149
}
150
151
return;
152
});
153
154
// API Command registration
155
156
interface IOpenFolderAPICommandOptions {
157
forceNewWindow?: boolean;
158
forceReuseWindow?: boolean;
159
noRecentEntry?: boolean;
160
forceLocalWindow?: boolean;
161
forceProfile?: string;
162
forceTempProfile?: boolean;
163
filesToOpen?: UriComponents[];
164
}
165
166
CommandsRegistry.registerCommand({
167
id: 'vscode.openFolder',
168
handler: (accessor: ServicesAccessor, uriComponents?: UriComponents, arg?: boolean | IOpenFolderAPICommandOptions) => {
169
const commandService = accessor.get(ICommandService);
170
171
// Be compatible to previous args by converting to options
172
if (typeof arg === 'boolean') {
173
arg = { forceNewWindow: arg };
174
}
175
176
// Without URI, ask to pick a folder or workspace to open
177
if (!uriComponents) {
178
const options: IPickAndOpenOptions = {
179
forceNewWindow: arg?.forceNewWindow
180
};
181
182
if (arg?.forceLocalWindow) {
183
options.remoteAuthority = null;
184
options.availableFileSystems = ['file'];
185
}
186
187
return commandService.executeCommand('_files.pickFolderAndOpen', options);
188
}
189
190
const uri = URI.from(uriComponents, true);
191
192
const options: IOpenWindowOptions = {
193
forceNewWindow: arg?.forceNewWindow,
194
forceReuseWindow: arg?.forceReuseWindow,
195
noRecentEntry: arg?.noRecentEntry,
196
remoteAuthority: arg?.forceLocalWindow ? null : undefined,
197
forceProfile: arg?.forceProfile,
198
forceTempProfile: arg?.forceTempProfile,
199
};
200
201
const workspaceToOpen: IWorkspaceToOpen | IFolderToOpen = (hasWorkspaceFileExtension(uri) || uri.scheme === Schemas.untitled) ? { workspaceUri: uri } : { folderUri: uri };
202
const filesToOpen: IFileToOpen[] = arg?.filesToOpen?.map(file => ({ fileUri: URI.from(file, true) })) ?? [];
203
return commandService.executeCommand('_files.windowOpen', [workspaceToOpen, ...filesToOpen], options);
204
},
205
metadata: {
206
description: 'Open a folder or workspace in the current window or new window depending on the newWindow argument. Note that opening in the same window will shutdown the current extension host process and start a new one on the given folder/workspace unless the newWindow parameter is set to true.',
207
args: [
208
{
209
name: 'uri', description: '(optional) Uri of the folder or workspace file to open. If not provided, a native dialog will ask the user for the folder',
210
constraint: (value: any) => value === undefined || value === null || value instanceof URI
211
},
212
{
213
name: 'options',
214
description: '(optional) Options. Object with the following properties: ' +
215
'`forceNewWindow`: Whether to open the folder/workspace in a new window or the same. Defaults to opening in the same window. ' +
216
'`forceReuseWindow`: Whether to force opening the folder/workspace in the same window. Defaults to false. ' +
217
'`noRecentEntry`: Whether the opened URI will appear in the \'Open Recent\' list. Defaults to false. ' +
218
'`forceLocalWindow`: Whether to force opening the folder/workspace in a local window. Defaults to false. ' +
219
'`forceProfile`: The profile to use when opening the folder/workspace. Defaults to the current profile. ' +
220
'`forceTempProfile`: Whether to use a temporary profile when opening the folder/workspace. Defaults to false. ' +
221
'`filesToOpen`: An array of files to open in the new window. Defaults to an empty array. ' +
222
'Note, for backward compatibility, options can also be of type boolean, representing the `forceNewWindow` setting.',
223
constraint: (value: any) => value === undefined || typeof value === 'object' || typeof value === 'boolean'
224
}
225
]
226
}
227
});
228
229
interface INewWindowAPICommandOptions {
230
reuseWindow?: boolean;
231
/**
232
* If set, defines the remoteAuthority of the new window. `null` will open a local window.
233
* If not set, defaults to remoteAuthority of the current window.
234
*/
235
remoteAuthority?: string | null;
236
}
237
238
CommandsRegistry.registerCommand({
239
id: 'vscode.newWindow',
240
handler: (accessor: ServicesAccessor, options?: INewWindowAPICommandOptions) => {
241
const commandService = accessor.get(ICommandService);
242
243
const commandOptions: IOpenEmptyWindowOptions = {
244
forceReuseWindow: options && options.reuseWindow,
245
remoteAuthority: options && options.remoteAuthority
246
};
247
248
return commandService.executeCommand('_files.newWindow', commandOptions);
249
},
250
metadata: {
251
description: 'Opens an new window depending on the newWindow argument.',
252
args: [
253
{
254
name: 'options',
255
description: '(optional) Options. Object with the following properties: ' +
256
'`reuseWindow`: Whether to open a new window or the same. Defaults to opening in a new window. ',
257
constraint: (value: any) => value === undefined || typeof value === 'object'
258
}
259
]
260
}
261
});
262
263
// recent history commands
264
265
CommandsRegistry.registerCommand('_workbench.removeFromRecentlyOpened', function (accessor: ServicesAccessor, uri: URI) {
266
const workspacesService = accessor.get(IWorkspacesService);
267
return workspacesService.removeRecentlyOpened([uri]);
268
});
269
270
CommandsRegistry.registerCommand({
271
id: 'vscode.removeFromRecentlyOpened',
272
handler: (accessor: ServicesAccessor, path: string | URI): Promise<void> => {
273
const workspacesService = accessor.get(IWorkspacesService);
274
275
if (typeof path === 'string') {
276
path = path.match(/^[^:/?#]+:\/\//) ? URI.parse(path) : URI.file(path);
277
} else {
278
path = URI.revive(path); // called from extension host
279
}
280
281
return workspacesService.removeRecentlyOpened([path]);
282
},
283
metadata: {
284
description: 'Removes an entry with the given path from the recently opened list.',
285
args: [
286
{ name: 'path', description: 'URI or URI string to remove from recently opened.', constraint: (value: any) => typeof value === 'string' || value instanceof URI }
287
]
288
}
289
});
290
291
interface RecentEntry {
292
uri: URI;
293
type: 'workspace' | 'folder' | 'file';
294
label?: string;
295
remoteAuthority?: string;
296
}
297
298
CommandsRegistry.registerCommand('_workbench.addToRecentlyOpened', async function (accessor: ServicesAccessor, recentEntry: RecentEntry) {
299
const workspacesService = accessor.get(IWorkspacesService);
300
const uri = recentEntry.uri;
301
const label = recentEntry.label;
302
const remoteAuthority = recentEntry.remoteAuthority;
303
304
let recent: IRecent | undefined = undefined;
305
if (recentEntry.type === 'workspace') {
306
const workspace = await workspacesService.getWorkspaceIdentifier(uri);
307
recent = { workspace, label, remoteAuthority };
308
} else if (recentEntry.type === 'folder') {
309
recent = { folderUri: uri, label, remoteAuthority };
310
} else {
311
recent = { fileUri: uri, label, remoteAuthority };
312
}
313
314
return workspacesService.addRecentlyOpened([recent]);
315
});
316
317
CommandsRegistry.registerCommand('_workbench.getRecentlyOpened', async function (accessor: ServicesAccessor) {
318
const workspacesService = accessor.get(IWorkspacesService);
319
320
return workspacesService.getRecentlyOpened();
321
});
322
323