Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/files/browser/fileCommands.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 * as nls from '../../../../nls.js';
7
import { URI } from '../../../../base/common/uri.js';
8
import { EditorResourceAccessor, IEditorCommandsContext, SideBySideEditor, IEditorIdentifier, SaveReason, EditorsOrder, EditorInputCapabilities } from '../../../common/editor.js';
9
import { SideBySideEditorInput } from '../../../common/editor/sideBySideEditorInput.js';
10
import { IWindowOpenable, IOpenWindowOptions, isWorkspaceToOpen, IOpenEmptyWindowOptions } from '../../../../platform/window/common/window.js';
11
import { IHostService } from '../../../services/host/browser/host.js';
12
import { ServicesAccessor, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
13
import { IWorkspaceContextService, UNTITLED_WORKSPACE_NAME } from '../../../../platform/workspace/common/workspace.js';
14
import { ExplorerFocusCondition, TextFileContentProvider, VIEWLET_ID, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, FilesExplorerFocusCondition, ExplorerFolderContext, VIEW_ID } from '../common/files.js';
15
import { ExplorerViewPaneContainer } from './explorerViewlet.js';
16
import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
17
import { toErrorMessage } from '../../../../base/common/errorMessage.js';
18
import { CommandsRegistry, ICommandHandler, ICommandService } from '../../../../platform/commands/common/commands.js';
19
import { IContextKey, IContextKeyService, ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
20
import { IFileService } from '../../../../platform/files/common/files.js';
21
import { KeybindingsRegistry, KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
22
import { KeyMod, KeyCode, KeyChord } from '../../../../base/common/keyCodes.js';
23
import { isWeb, isWindows } from '../../../../base/common/platform.js';
24
import { ITextModelService } from '../../../../editor/common/services/resolverService.js';
25
import { getResourceForCommand, getMultiSelectedResources, getOpenEditorsViewMultiSelection, IExplorerService } from './files.js';
26
import { IWorkspaceEditingService } from '../../../services/workspaces/common/workspaceEditing.js';
27
import { resolveCommandsContext } from '../../../browser/parts/editor/editorCommandsContext.js';
28
import { Schemas } from '../../../../base/common/network.js';
29
import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js';
30
import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js';
31
import { IEditorService, SIDE_GROUP, ISaveEditorsOptions } from '../../../services/editor/common/editorService.js';
32
import { IEditorGroupsService, GroupsOrder, IEditorGroup } from '../../../services/editor/common/editorGroupsService.js';
33
import { ILabelService } from '../../../../platform/label/common/label.js';
34
import { basename, joinPath, isEqual } from '../../../../base/common/resources.js';
35
import { IDisposable, dispose } from '../../../../base/common/lifecycle.js';
36
import { IEnvironmentService } from '../../../../platform/environment/common/environment.js';
37
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
38
import { EmbeddedCodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/embeddedCodeEditorWidget.js';
39
import { ITextFileService } from '../../../services/textfile/common/textfiles.js';
40
import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';
41
import { isCancellationError } from '../../../../base/common/errors.js';
42
import { IAction, toAction } from '../../../../base/common/actions.js';
43
import { EditorOpenSource, EditorResolution } from '../../../../platform/editor/common/editor.js';
44
import { hash } from '../../../../base/common/hash.js';
45
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
46
import { IPaneCompositePartService } from '../../../services/panecomposite/browser/panecomposite.js';
47
import { ViewContainerLocation } from '../../../common/views.js';
48
import { IViewsService } from '../../../services/views/common/viewsService.js';
49
import { OPEN_TO_SIDE_COMMAND_ID, COMPARE_WITH_SAVED_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, COMPARE_SELECTED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, COPY_PATH_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_WITH_EXPLORER_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_AS_COMMAND_ID, SAVE_ALL_COMMAND_ID, SAVE_ALL_IN_GROUP_COMMAND_ID, SAVE_FILES_COMMAND_ID, REVERT_FILE_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, PREVIOUS_COMPRESSED_FOLDER, NEXT_COMPRESSED_FOLDER, FIRST_COMPRESSED_FOLDER, LAST_COMPRESSED_FOLDER, NEW_UNTITLED_FILE_COMMAND_ID, NEW_UNTITLED_FILE_LABEL, NEW_FILE_COMMAND_ID } from './fileConstants.js';
50
import { IFileDialogService } from '../../../../platform/dialogs/common/dialogs.js';
51
import { RemoveRootFolderAction } from '../../../browser/actions/workspaceActions.js';
52
import { OpenEditorsView } from './views/openEditorsView.js';
53
import { ExplorerView } from './views/explorerView.js';
54
import { IListService } from '../../../../platform/list/browser/listService.js';
55
56
export const openWindowCommand = (accessor: ServicesAccessor, toOpen: IWindowOpenable[], options?: IOpenWindowOptions) => {
57
if (Array.isArray(toOpen)) {
58
const hostService = accessor.get(IHostService);
59
const environmentService = accessor.get(IEnvironmentService);
60
61
// rewrite untitled: workspace URIs to the absolute path on disk
62
toOpen = toOpen.map(openable => {
63
if (isWorkspaceToOpen(openable) && openable.workspaceUri.scheme === Schemas.untitled) {
64
return {
65
workspaceUri: joinPath(environmentService.untitledWorkspacesHome, openable.workspaceUri.path, UNTITLED_WORKSPACE_NAME)
66
};
67
}
68
69
return openable;
70
});
71
72
hostService.openWindow(toOpen, options);
73
}
74
};
75
76
export const newWindowCommand = (accessor: ServicesAccessor, options?: IOpenEmptyWindowOptions) => {
77
const hostService = accessor.get(IHostService);
78
hostService.openWindow(options);
79
};
80
81
// Command registration
82
83
KeybindingsRegistry.registerCommandAndKeybindingRule({
84
weight: KeybindingWeight.WorkbenchContrib,
85
when: ExplorerFocusCondition,
86
primary: KeyMod.CtrlCmd | KeyCode.Enter,
87
mac: {
88
primary: KeyMod.WinCtrl | KeyCode.Enter
89
},
90
id: OPEN_TO_SIDE_COMMAND_ID, handler: async (accessor, resource: URI | object) => {
91
const editorService = accessor.get(IEditorService);
92
const fileService = accessor.get(IFileService);
93
const explorerService = accessor.get(IExplorerService);
94
const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService, accessor.get(IEditorGroupsService), explorerService);
95
96
// Set side input
97
if (resources.length) {
98
const untitledResources = resources.filter(resource => resource.scheme === Schemas.untitled);
99
const fileResources = resources.filter(resource => resource.scheme !== Schemas.untitled);
100
101
const items = await Promise.all(fileResources.map(async resource => {
102
const item = explorerService.findClosest(resource);
103
if (item) {
104
// Explorer already resolved the item, no need to go to the file service #109780
105
return item;
106
}
107
108
return await fileService.stat(resource);
109
}));
110
const files = items.filter(i => !i.isDirectory);
111
const editors = files.map(f => ({
112
resource: f.resource,
113
options: { pinned: true }
114
})).concat(...untitledResources.map(untitledResource => ({ resource: untitledResource, options: { pinned: true } })));
115
116
await editorService.openEditors(editors, SIDE_GROUP);
117
}
118
}
119
});
120
121
KeybindingsRegistry.registerCommandAndKeybindingRule({
122
weight: KeybindingWeight.WorkbenchContrib + 10,
123
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerFolderContext.toNegated()),
124
primary: KeyCode.Enter,
125
mac: {
126
primary: KeyMod.CtrlCmd | KeyCode.DownArrow
127
},
128
id: 'explorer.openAndPassFocus', handler: async (accessor, _resource: URI | object) => {
129
const editorService = accessor.get(IEditorService);
130
const explorerService = accessor.get(IExplorerService);
131
const resources = explorerService.getContext(true);
132
133
if (resources.length) {
134
await editorService.openEditors(resources.map(r => ({ resource: r.resource, options: { preserveFocus: false, pinned: true } })));
135
}
136
}
137
});
138
139
const COMPARE_WITH_SAVED_SCHEMA = 'showModifications';
140
let providerDisposables: IDisposable[] = [];
141
KeybindingsRegistry.registerCommandAndKeybindingRule({
142
id: COMPARE_WITH_SAVED_COMMAND_ID,
143
when: undefined,
144
weight: KeybindingWeight.WorkbenchContrib,
145
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyD),
146
handler: async (accessor, resource: URI | object) => {
147
const instantiationService = accessor.get(IInstantiationService);
148
const textModelService = accessor.get(ITextModelService);
149
const editorService = accessor.get(IEditorService);
150
const fileService = accessor.get(IFileService);
151
const listService = accessor.get(IListService);
152
153
// Register provider at first as needed
154
let registerEditorListener = false;
155
if (providerDisposables.length === 0) {
156
registerEditorListener = true;
157
158
const provider = instantiationService.createInstance(TextFileContentProvider);
159
providerDisposables.push(provider);
160
providerDisposables.push(textModelService.registerTextModelContentProvider(COMPARE_WITH_SAVED_SCHEMA, provider));
161
}
162
163
// Open editor (only resources that can be handled by file service are supported)
164
const uri = getResourceForCommand(resource, editorService, listService);
165
if (uri && fileService.hasProvider(uri)) {
166
const name = basename(uri);
167
const editorLabel = nls.localize('modifiedLabel', "{0} (in file) ↔ {1}", name, name);
168
169
try {
170
await TextFileContentProvider.open(uri, COMPARE_WITH_SAVED_SCHEMA, editorLabel, editorService, { pinned: true });
171
// Dispose once no more diff editor is opened with the scheme
172
if (registerEditorListener) {
173
providerDisposables.push(editorService.onDidVisibleEditorsChange(() => {
174
if (!editorService.editors.some(editor => !!EditorResourceAccessor.getCanonicalUri(editor, { supportSideBySide: SideBySideEditor.SECONDARY, filterByScheme: COMPARE_WITH_SAVED_SCHEMA }))) {
175
providerDisposables = dispose(providerDisposables);
176
}
177
}));
178
}
179
} catch {
180
providerDisposables = dispose(providerDisposables);
181
}
182
}
183
}
184
});
185
186
let globalResourceToCompare: URI | undefined;
187
let resourceSelectedForCompareContext: IContextKey<boolean>;
188
CommandsRegistry.registerCommand({
189
id: SELECT_FOR_COMPARE_COMMAND_ID,
190
handler: (accessor, resource: URI | object) => {
191
globalResourceToCompare = getResourceForCommand(resource, accessor.get(IEditorService), accessor.get(IListService));
192
if (!resourceSelectedForCompareContext) {
193
resourceSelectedForCompareContext = ResourceSelectedForCompareContext.bindTo(accessor.get(IContextKeyService));
194
}
195
resourceSelectedForCompareContext.set(true);
196
}
197
});
198
199
CommandsRegistry.registerCommand({
200
id: COMPARE_SELECTED_COMMAND_ID,
201
handler: async (accessor, resource: URI | object) => {
202
const editorService = accessor.get(IEditorService);
203
const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService, accessor.get(IEditorGroupsService), accessor.get(IExplorerService));
204
205
if (resources.length === 2) {
206
return editorService.openEditor({
207
original: { resource: resources[0] },
208
modified: { resource: resources[1] },
209
options: { pinned: true }
210
});
211
}
212
213
return true;
214
}
215
});
216
217
CommandsRegistry.registerCommand({
218
id: COMPARE_RESOURCE_COMMAND_ID,
219
handler: (accessor, resource: URI | object) => {
220
const editorService = accessor.get(IEditorService);
221
const rightResource = getResourceForCommand(resource, editorService, accessor.get(IListService));
222
if (globalResourceToCompare && rightResource) {
223
editorService.openEditor({
224
original: { resource: globalResourceToCompare },
225
modified: { resource: rightResource },
226
options: { pinned: true }
227
});
228
}
229
}
230
});
231
232
async function resourcesToClipboard(resources: URI[], relative: boolean, clipboardService: IClipboardService, labelService: ILabelService, configurationService: IConfigurationService): Promise<void> {
233
if (resources.length) {
234
const lineDelimiter = isWindows ? '\r\n' : '\n';
235
236
let separator: '/' | '\\' | undefined = undefined;
237
const copyRelativeOrFullPathSeparatorSection = relative ? 'explorer.copyRelativePathSeparator' : 'explorer.copyPathSeparator';
238
const copyRelativeOrFullPathSeparator: '/' | '\\' | undefined = configurationService.getValue(copyRelativeOrFullPathSeparatorSection);
239
if (copyRelativeOrFullPathSeparator === '/' || copyRelativeOrFullPathSeparator === '\\') {
240
separator = copyRelativeOrFullPathSeparator;
241
}
242
243
const text = resources.map(resource => labelService.getUriLabel(resource, { relative, noPrefix: true, separator })).join(lineDelimiter);
244
await clipboardService.writeText(text);
245
}
246
}
247
248
const copyPathCommandHandler: ICommandHandler = async (accessor, resource: URI | object) => {
249
const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IExplorerService));
250
await resourcesToClipboard(resources, false, accessor.get(IClipboardService), accessor.get(ILabelService), accessor.get(IConfigurationService));
251
};
252
253
KeybindingsRegistry.registerCommandAndKeybindingRule({
254
weight: KeybindingWeight.WorkbenchContrib,
255
when: EditorContextKeys.focus.toNegated(),
256
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyC,
257
win: {
258
primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KeyC
259
},
260
id: COPY_PATH_COMMAND_ID,
261
handler: copyPathCommandHandler
262
});
263
264
KeybindingsRegistry.registerCommandAndKeybindingRule({
265
weight: KeybindingWeight.WorkbenchContrib,
266
when: EditorContextKeys.focus,
267
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyC),
268
win: {
269
primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KeyC
270
},
271
id: COPY_PATH_COMMAND_ID,
272
handler: copyPathCommandHandler
273
});
274
275
const copyRelativePathCommandHandler: ICommandHandler = async (accessor, resource: URI | object) => {
276
const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IExplorerService));
277
await resourcesToClipboard(resources, true, accessor.get(IClipboardService), accessor.get(ILabelService), accessor.get(IConfigurationService));
278
};
279
280
KeybindingsRegistry.registerCommandAndKeybindingRule({
281
weight: KeybindingWeight.WorkbenchContrib,
282
when: EditorContextKeys.focus.toNegated(),
283
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.KeyC,
284
win: {
285
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyC)
286
},
287
id: COPY_RELATIVE_PATH_COMMAND_ID,
288
handler: copyRelativePathCommandHandler
289
});
290
291
KeybindingsRegistry.registerCommandAndKeybindingRule({
292
weight: KeybindingWeight.WorkbenchContrib,
293
when: EditorContextKeys.focus,
294
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.KeyC),
295
win: {
296
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyC)
297
},
298
id: COPY_RELATIVE_PATH_COMMAND_ID,
299
handler: copyRelativePathCommandHandler
300
});
301
302
KeybindingsRegistry.registerCommandAndKeybindingRule({
303
weight: KeybindingWeight.WorkbenchContrib,
304
when: undefined,
305
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyP),
306
id: 'workbench.action.files.copyPathOfActiveFile',
307
handler: async accessor => {
308
const editorService = accessor.get(IEditorService);
309
const activeInput = editorService.activeEditor;
310
const resource = EditorResourceAccessor.getOriginalUri(activeInput, { supportSideBySide: SideBySideEditor.PRIMARY });
311
const resources = resource ? [resource] : [];
312
await resourcesToClipboard(resources, false, accessor.get(IClipboardService), accessor.get(ILabelService), accessor.get(IConfigurationService));
313
}
314
});
315
316
CommandsRegistry.registerCommand({
317
id: REVEAL_IN_EXPLORER_COMMAND_ID,
318
handler: async (accessor, resource: URI | object) => {
319
const viewService = accessor.get(IViewsService);
320
const contextService = accessor.get(IWorkspaceContextService);
321
const explorerService = accessor.get(IExplorerService);
322
const editorService = accessor.get(IEditorService);
323
const listService = accessor.get(IListService);
324
const uri = getResourceForCommand(resource, editorService, listService);
325
326
if (uri && contextService.isInsideWorkspace(uri)) {
327
const explorerView = await viewService.openView<ExplorerView>(VIEW_ID, false);
328
if (explorerView) {
329
const oldAutoReveal = explorerView.autoReveal;
330
// Disable autoreveal before revealing the explorer to prevent a race betwene auto reveal + selection
331
// Fixes #197268
332
explorerView.autoReveal = false;
333
explorerView.setExpanded(true);
334
await explorerService.select(uri, 'force');
335
explorerView.focus();
336
explorerView.autoReveal = oldAutoReveal;
337
}
338
} else {
339
// Do not reveal the open editors view if it's hidden explicitly
340
// See https://github.com/microsoft/vscode/issues/227378
341
const openEditorsView = viewService.getViewWithId(OpenEditorsView.ID);
342
if (openEditorsView) {
343
openEditorsView.setExpanded(true);
344
openEditorsView.focus();
345
}
346
}
347
}
348
});
349
350
CommandsRegistry.registerCommand({
351
id: OPEN_WITH_EXPLORER_COMMAND_ID,
352
handler: async (accessor, resource: URI | object) => {
353
const editorService = accessor.get(IEditorService);
354
const listService = accessor.get(IListService);
355
const uri = getResourceForCommand(resource, editorService, listService);
356
if (uri) {
357
return editorService.openEditor({ resource: uri, options: { override: EditorResolution.PICK, source: EditorOpenSource.USER } });
358
}
359
360
return undefined;
361
}
362
});
363
364
// Save / Save As / Save All / Revert
365
366
async function saveSelectedEditors(accessor: ServicesAccessor, options?: ISaveEditorsOptions): Promise<void> {
367
const editorGroupService = accessor.get(IEditorGroupsService);
368
const codeEditorService = accessor.get(ICodeEditorService);
369
const textFileService = accessor.get(ITextFileService);
370
371
// Retrieve selected or active editor
372
let editors = getOpenEditorsViewMultiSelection(accessor);
373
if (!editors) {
374
const activeGroup = editorGroupService.activeGroup;
375
if (activeGroup.activeEditor) {
376
editors = [];
377
378
// Special treatment for side by side editors: if the active editor
379
// has 2 sides, we consider both, to support saving both sides.
380
// We only allow this when saving, not for "Save As" and not if any
381
// editor is untitled which would bring up a "Save As" dialog too.
382
// In addition, we require the secondary side to be modified to not
383
// trigger a touch operation unexpectedly.
384
//
385
// See also https://github.com/microsoft/vscode/issues/4180
386
// See also https://github.com/microsoft/vscode/issues/106330
387
// See also https://github.com/microsoft/vscode/issues/190210
388
if (
389
activeGroup.activeEditor instanceof SideBySideEditorInput &&
390
!options?.saveAs && !(activeGroup.activeEditor.primary.hasCapability(EditorInputCapabilities.Untitled) || activeGroup.activeEditor.secondary.hasCapability(EditorInputCapabilities.Untitled)) &&
391
activeGroup.activeEditor.secondary.isModified()
392
) {
393
editors.push({ groupId: activeGroup.id, editor: activeGroup.activeEditor.primary });
394
editors.push({ groupId: activeGroup.id, editor: activeGroup.activeEditor.secondary });
395
} else {
396
editors.push({ groupId: activeGroup.id, editor: activeGroup.activeEditor });
397
}
398
}
399
}
400
401
if (!editors || editors.length === 0) {
402
return; // nothing to save
403
}
404
405
// Save editors
406
await doSaveEditors(accessor, editors, options);
407
408
// Special treatment for embedded editors: if we detect that focus is
409
// inside an embedded code editor, we save that model as well if we
410
// find it in our text file models. Currently, only textual editors
411
// support embedded editors.
412
const focusedCodeEditor = codeEditorService.getFocusedCodeEditor();
413
if (focusedCodeEditor instanceof EmbeddedCodeEditorWidget && !focusedCodeEditor.isSimpleWidget) {
414
const resource = focusedCodeEditor.getModel()?.uri;
415
416
// Check that the resource of the model was not saved already
417
if (resource && !editors.some(({ editor }) => isEqual(EditorResourceAccessor.getCanonicalUri(editor, { supportSideBySide: SideBySideEditor.PRIMARY }), resource))) {
418
const model = textFileService.files.get(resource);
419
if (!model?.isReadonly()) {
420
await textFileService.save(resource, options);
421
}
422
}
423
}
424
}
425
426
function saveDirtyEditorsOfGroups(accessor: ServicesAccessor, groups: readonly IEditorGroup[], options?: ISaveEditorsOptions): Promise<void> {
427
const dirtyEditors: IEditorIdentifier[] = [];
428
for (const group of groups) {
429
for (const editor of group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) {
430
if (editor.isDirty()) {
431
dirtyEditors.push({ groupId: group.id, editor });
432
}
433
}
434
}
435
436
return doSaveEditors(accessor, dirtyEditors, options);
437
}
438
439
async function doSaveEditors(accessor: ServicesAccessor, editors: IEditorIdentifier[], options?: ISaveEditorsOptions): Promise<void> {
440
const editorService = accessor.get(IEditorService);
441
const notificationService = accessor.get(INotificationService);
442
const instantiationService = accessor.get(IInstantiationService);
443
444
try {
445
await editorService.save(editors, options);
446
} catch (error) {
447
if (!isCancellationError(error)) {
448
const actions: IAction[] = [toAction({ id: 'workbench.action.files.saveEditors', label: nls.localize('retry', "Retry"), run: () => instantiationService.invokeFunction(accessor => doSaveEditors(accessor, editors, options)) })];
449
const editorsToRevert = editors.filter(({ editor }) => !editor.hasCapability(EditorInputCapabilities.Untitled) /* all except untitled to prevent unexpected data-loss */);
450
if (editorsToRevert.length > 0) {
451
actions.push(toAction({ id: 'workbench.action.files.revertEditors', label: editorsToRevert.length > 1 ? nls.localize('revertAll', "Revert All") : nls.localize('revert', "Revert"), run: () => editorService.revert(editorsToRevert) }));
452
}
453
454
notificationService.notify({
455
id: editors.map(({ editor }) => hash(editor.resource?.toString())).join(), // ensure unique notification ID per set of editor
456
severity: Severity.Error,
457
message: nls.localize({ key: 'genericSaveError', comment: ['{0} is the resource that failed to save and {1} the error message'] }, "Failed to save '{0}': {1}", editors.map(({ editor }) => editor.getName()).join(', '), toErrorMessage(error, false)),
458
actions: { primary: actions }
459
});
460
}
461
}
462
}
463
464
KeybindingsRegistry.registerCommandAndKeybindingRule({
465
when: undefined,
466
weight: KeybindingWeight.WorkbenchContrib,
467
primary: KeyMod.CtrlCmd | KeyCode.KeyS,
468
id: SAVE_FILE_COMMAND_ID,
469
handler: accessor => {
470
return saveSelectedEditors(accessor, { reason: SaveReason.EXPLICIT, force: true /* force save even when non-dirty */ });
471
}
472
});
473
474
KeybindingsRegistry.registerCommandAndKeybindingRule({
475
when: undefined,
476
weight: KeybindingWeight.WorkbenchContrib,
477
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyS),
478
win: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyS) },
479
id: SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID,
480
handler: accessor => {
481
return saveSelectedEditors(accessor, { reason: SaveReason.EXPLICIT, force: true /* force save even when non-dirty */, skipSaveParticipants: true });
482
}
483
});
484
485
KeybindingsRegistry.registerCommandAndKeybindingRule({
486
id: SAVE_FILE_AS_COMMAND_ID,
487
weight: KeybindingWeight.WorkbenchContrib,
488
when: undefined,
489
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyS,
490
handler: accessor => {
491
return saveSelectedEditors(accessor, { reason: SaveReason.EXPLICIT, saveAs: true });
492
}
493
});
494
495
KeybindingsRegistry.registerCommandAndKeybindingRule({
496
when: undefined,
497
weight: KeybindingWeight.WorkbenchContrib,
498
primary: undefined,
499
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyS },
500
win: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyS) },
501
id: SAVE_ALL_COMMAND_ID,
502
handler: accessor => {
503
return saveDirtyEditorsOfGroups(accessor, accessor.get(IEditorGroupsService).getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE), { reason: SaveReason.EXPLICIT });
504
}
505
});
506
507
CommandsRegistry.registerCommand({
508
id: SAVE_ALL_IN_GROUP_COMMAND_ID,
509
handler: (accessor, _: URI | object, editorContext: IEditorCommandsContext) => {
510
const editorGroupsService = accessor.get(IEditorGroupsService);
511
512
const resolvedContext = resolveCommandsContext([editorContext], accessor.get(IEditorService), editorGroupsService, accessor.get(IListService));
513
514
let groups: readonly IEditorGroup[] | undefined = undefined;
515
if (!resolvedContext.groupedEditors.length) {
516
groups = editorGroupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE);
517
} else {
518
groups = resolvedContext.groupedEditors.map(({ group }) => group);
519
}
520
521
return saveDirtyEditorsOfGroups(accessor, groups, { reason: SaveReason.EXPLICIT });
522
}
523
});
524
525
CommandsRegistry.registerCommand({
526
id: SAVE_FILES_COMMAND_ID,
527
handler: async accessor => {
528
const editorService = accessor.get(IEditorService);
529
530
const res = await editorService.saveAll({ includeUntitled: false, reason: SaveReason.EXPLICIT });
531
return res.success;
532
}
533
});
534
535
CommandsRegistry.registerCommand({
536
id: REVERT_FILE_COMMAND_ID,
537
handler: async accessor => {
538
const editorGroupService = accessor.get(IEditorGroupsService);
539
const editorService = accessor.get(IEditorService);
540
541
// Retrieve selected or active editor
542
let editors = getOpenEditorsViewMultiSelection(accessor);
543
if (!editors) {
544
const activeGroup = editorGroupService.activeGroup;
545
if (activeGroup.activeEditor) {
546
editors = [{ groupId: activeGroup.id, editor: activeGroup.activeEditor }];
547
}
548
}
549
550
if (!editors || editors.length === 0) {
551
return; // nothing to revert
552
}
553
554
try {
555
await editorService.revert(editors.filter(({ editor }) => !editor.hasCapability(EditorInputCapabilities.Untitled) /* all except untitled */), { force: true });
556
} catch (error) {
557
const notificationService = accessor.get(INotificationService);
558
notificationService.error(nls.localize('genericRevertError', "Failed to revert '{0}': {1}", editors.map(({ editor }) => editor.getName()).join(', '), toErrorMessage(error, false)));
559
}
560
}
561
});
562
563
CommandsRegistry.registerCommand({
564
id: REMOVE_ROOT_FOLDER_COMMAND_ID,
565
handler: (accessor, resource: URI | object) => {
566
const contextService = accessor.get(IWorkspaceContextService);
567
const uriIdentityService = accessor.get(IUriIdentityService);
568
const workspace = contextService.getWorkspace();
569
const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IExplorerService)).filter(resource =>
570
workspace.folders.some(folder => uriIdentityService.extUri.isEqual(folder.uri, resource)) // Need to verify resources are workspaces since multi selection can trigger this command on some non workspace resources
571
);
572
573
if (resources.length === 0) {
574
const commandService = accessor.get(ICommandService);
575
// Show a picker for the user to choose which folder to remove
576
return commandService.executeCommand(RemoveRootFolderAction.ID);
577
}
578
579
const workspaceEditingService = accessor.get(IWorkspaceEditingService);
580
return workspaceEditingService.removeFolders(resources);
581
}
582
});
583
584
// Compressed item navigation
585
586
KeybindingsRegistry.registerCommandAndKeybindingRule({
587
weight: KeybindingWeight.WorkbenchContrib + 10,
588
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext.negate()),
589
primary: KeyCode.LeftArrow,
590
id: PREVIOUS_COMPRESSED_FOLDER,
591
handler: accessor => {
592
const paneCompositeService = accessor.get(IPaneCompositePartService);
593
const viewlet = paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar);
594
595
if (viewlet?.getId() !== VIEWLET_ID) {
596
return;
597
}
598
599
const explorer = viewlet.getViewPaneContainer() as ExplorerViewPaneContainer;
600
const view = explorer.getExplorerView();
601
view.previousCompressedStat();
602
}
603
});
604
605
KeybindingsRegistry.registerCommandAndKeybindingRule({
606
weight: KeybindingWeight.WorkbenchContrib + 10,
607
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedLastFocusContext.negate()),
608
primary: KeyCode.RightArrow,
609
id: NEXT_COMPRESSED_FOLDER,
610
handler: accessor => {
611
const paneCompositeService = accessor.get(IPaneCompositePartService);
612
const viewlet = paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar);
613
614
if (viewlet?.getId() !== VIEWLET_ID) {
615
return;
616
}
617
618
const explorer = viewlet.getViewPaneContainer() as ExplorerViewPaneContainer;
619
const view = explorer.getExplorerView();
620
view.nextCompressedStat();
621
}
622
});
623
624
KeybindingsRegistry.registerCommandAndKeybindingRule({
625
weight: KeybindingWeight.WorkbenchContrib + 10,
626
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext.negate()),
627
primary: KeyCode.Home,
628
id: FIRST_COMPRESSED_FOLDER,
629
handler: accessor => {
630
const paneCompositeService = accessor.get(IPaneCompositePartService);
631
const viewlet = paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar);
632
633
if (viewlet?.getId() !== VIEWLET_ID) {
634
return;
635
}
636
637
const explorer = viewlet.getViewPaneContainer() as ExplorerViewPaneContainer;
638
const view = explorer.getExplorerView();
639
view.firstCompressedStat();
640
}
641
});
642
643
KeybindingsRegistry.registerCommandAndKeybindingRule({
644
weight: KeybindingWeight.WorkbenchContrib + 10,
645
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedLastFocusContext.negate()),
646
primary: KeyCode.End,
647
id: LAST_COMPRESSED_FOLDER,
648
handler: accessor => {
649
const paneCompositeService = accessor.get(IPaneCompositePartService);
650
const viewlet = paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar);
651
652
if (viewlet?.getId() !== VIEWLET_ID) {
653
return;
654
}
655
656
const explorer = viewlet.getViewPaneContainer() as ExplorerViewPaneContainer;
657
const view = explorer.getExplorerView();
658
view.lastCompressedStat();
659
}
660
});
661
662
KeybindingsRegistry.registerCommandAndKeybindingRule({
663
weight: KeybindingWeight.WorkbenchContrib,
664
when: null,
665
primary: isWeb ? (isWindows ? KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyN) : KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyN) : KeyMod.CtrlCmd | KeyCode.KeyN,
666
secondary: isWeb ? [KeyMod.CtrlCmd | KeyCode.KeyN] : undefined,
667
id: NEW_UNTITLED_FILE_COMMAND_ID,
668
metadata: {
669
description: NEW_UNTITLED_FILE_LABEL,
670
args: [
671
{
672
isOptional: true,
673
name: 'New Untitled Text File arguments',
674
description: 'The editor view type or language ID if known',
675
schema: {
676
'type': 'object',
677
'properties': {
678
'viewType': {
679
'type': 'string'
680
},
681
'languageId': {
682
'type': 'string'
683
}
684
}
685
}
686
}
687
]
688
},
689
handler: async (accessor, args?: { languageId?: string; viewType?: string }) => {
690
const editorService = accessor.get(IEditorService);
691
692
await editorService.openEditor({
693
resource: undefined,
694
options: {
695
override: args?.viewType,
696
pinned: true
697
},
698
languageId: args?.languageId,
699
});
700
}
701
});
702
703
CommandsRegistry.registerCommand({
704
id: NEW_FILE_COMMAND_ID,
705
handler: async (accessor, args?: { languageId?: string; viewType?: string; fileName?: string }) => {
706
const editorService = accessor.get(IEditorService);
707
const dialogService = accessor.get(IFileDialogService);
708
const fileService = accessor.get(IFileService);
709
710
const createFileLocalized = nls.localize('newFileCommand.saveLabel', "Create File");
711
const defaultFileUri = joinPath(await dialogService.defaultFilePath(), args?.fileName ?? 'Untitled.txt');
712
713
const saveUri = await dialogService.showSaveDialog({ saveLabel: createFileLocalized, title: createFileLocalized, defaultUri: defaultFileUri });
714
715
if (!saveUri) {
716
return;
717
}
718
719
await fileService.createFile(saveUri, undefined, { overwrite: true });
720
721
await editorService.openEditor({
722
resource: saveUri,
723
options: {
724
override: args?.viewType,
725
pinned: true
726
},
727
languageId: args?.languageId,
728
});
729
}
730
});
731
732