fileCommands.ts 25.5 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.
 *--------------------------------------------------------------------------------------------*/

6
import * as nls from 'vs/nls';
7
import { URI } from 'vs/base/common/uri';
B
Benjamin Pasero 已提交
8
import { toResource, IEditorCommandsContext, SideBySideEditor, IEditorIdentifier, SaveReason, SideBySideEditorInput, EditorsOrder } from 'vs/workbench/common/editor';
9
import { IWindowOpenable, IOpenWindowOptions, isWorkspaceToOpen, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows';
10
import { IHostService } from 'vs/workbench/services/host/browser/host';
I
isidor 已提交
11
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
12 13
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
14
import { ExplorerFocusCondition, TextFileContentProvider, VIEWLET_ID, IExplorerService, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, FilesExplorerFocusCondition } from 'vs/workbench/contrib/files/common/files';
S
SteVen Batten 已提交
15
import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet';
16
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
I
isidor 已提交
17 18
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { IListService } from 'vs/platform/list/browser/listService';
I
ups  
isidor 已提交
19
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
20
import { RawContextKey, IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
21
import { IFileService } from 'vs/platform/files/common/files';
22
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
23
import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes';
24
import { isWindows } from 'vs/base/common/platform';
I
isidor 已提交
25
import { ITextModelService } from 'vs/editor/common/services/resolverService';
26
import { getResourceForCommand, getMultiSelectedResources, getOpenEditorsViewMultiSelection } from 'vs/workbench/contrib/files/browser/files';
B
Benjamin Pasero 已提交
27
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
I
isidor 已提交
28
import { getMultiSelectedEditorContexts } from 'vs/workbench/browser/parts/editor/editorCommands';
29
import { Schemas } from 'vs/base/common/network';
30
import { INotificationService } from 'vs/platform/notification/common/notification';
31
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
32
import { IEditorService, SIDE_GROUP, ISaveEditorsOptions } from 'vs/workbench/services/editor/common/editorService';
B
Benjamin Pasero 已提交
33
import { IEditorGroupsService, GroupsOrder, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
I
isidor 已提交
34
import { ILabelService } from 'vs/platform/label/common/label';
35
import { basename, joinPath, isEqual } from 'vs/base/common/resources';
36
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
37 38
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { UNTITLED_WORKSPACE_NAME } from 'vs/platform/workspaces/common/workspaces';
B
Benjamin Pasero 已提交
39
import { coalesce } from 'vs/base/common/arrays';
40 41 42
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
43 44 45
import { openEditorWith } from 'vs/workbench/contrib/files/common/openWith';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
46 47 48

// Commands

I
isidor 已提交
49
export const REVEAL_IN_EXPLORER_COMMAND_ID = 'revealInExplorer';
50
export const REVERT_FILE_COMMAND_ID = 'workbench.action.files.revert';
51
export const OPEN_TO_SIDE_COMMAND_ID = 'explorer.openToSide';
52
export const OPEN_WITH_EXPLORER_COMMAND_ID = 'explorer.openWith';
I
isidor 已提交
53
export const SELECT_FOR_COMPARE_COMMAND_ID = 'selectForCompare';
I
isidor 已提交
54 55

export const COMPARE_SELECTED_COMMAND_ID = 'compareSelected';
I
isidor 已提交
56
export const COMPARE_RESOURCE_COMMAND_ID = 'compareFiles';
57
export const COMPARE_WITH_SAVED_COMMAND_ID = 'workbench.files.action.compareWithSaved';
58
export const COPY_PATH_COMMAND_ID = 'copyFilePath';
59
export const COPY_RELATIVE_PATH_COMMAND_ID = 'copyRelativeFilePath';
60

61
export const SAVE_FILE_AS_COMMAND_ID = 'workbench.action.files.saveAs';
62
export const SAVE_FILE_AS_LABEL = nls.localize('saveAs', "Save As...");
63
export const SAVE_FILE_COMMAND_ID = 'workbench.action.files.save';
64
export const SAVE_FILE_LABEL = nls.localize('save', "Save");
B
Benjamin Pasero 已提交
65 66
export const SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID = 'workbench.action.files.saveWithoutFormatting';
export const SAVE_FILE_WITHOUT_FORMATTING_LABEL = nls.localize('saveWithoutFormatting', "Save without Formatting");
67

I
isidor 已提交
68
export const SAVE_ALL_COMMAND_ID = 'saveAll';
I
isidor 已提交
69 70
export const SAVE_ALL_LABEL = nls.localize('saveAll', "Save All");

71
export const SAVE_ALL_IN_GROUP_COMMAND_ID = 'workbench.files.action.saveAllInGroup';
I
isidor 已提交
72

I
isidor 已提交
73
export const SAVE_FILES_COMMAND_ID = 'workbench.action.files.saveFiles';
I
isidor 已提交
74

B
Benjamin Pasero 已提交
75
export const OpenEditorsGroupContext = new RawContextKey<boolean>('groupFocusedInOpenEditors', false);
76
export const DirtyEditorContext = new RawContextKey<boolean>('dirtyEditor', false);
77
export const ReadonlyEditorContext = new RawContextKey<boolean>('readonlyEditor', false);
I
isidor 已提交
78
export const ResourceSelectedForCompareContext = new RawContextKey<boolean>('resourceSelectedForCompare', false);
79

I
isidor 已提交
80
export const REMOVE_ROOT_FOLDER_COMMAND_ID = 'removeRootFolder';
81 82
export const REMOVE_ROOT_FOLDER_LABEL = nls.localize('removeFolderFromWorkspace', "Remove Folder from Workspace");

83 84
export const PREVIOUS_COMPRESSED_FOLDER = 'previousCompressedFolder';
export const NEXT_COMPRESSED_FOLDER = 'nextCompressedFolder';
85 86
export const FIRST_COMPRESSED_FOLDER = 'firstCompressedFolder';
export const LAST_COMPRESSED_FOLDER = 'lastCompressedFolder';
87

88
export const openWindowCommand = (accessor: ServicesAccessor, toOpen: IWindowOpenable[], options?: IOpenWindowOptions) => {
89 90
	if (Array.isArray(toOpen)) {
		const hostService = accessor.get(IHostService);
91 92 93
		const environmentService = accessor.get(IEnvironmentService);

		// rewrite untitled: workspace URIs to the absolute path on disk
94 95
		toOpen = toOpen.map(openable => {
			if (isWorkspaceToOpen(openable) && openable.workspaceUri.scheme === Schemas.untitled) {
96
				return {
97
					workspaceUri: joinPath(environmentService.untitledWorkspacesHome, openable.workspaceUri.path, UNTITLED_WORKSPACE_NAME)
98 99 100
				};
			}

101
			return openable;
102 103
		});

104
		hostService.openWindow(toOpen, options);
M
Martin Aeschlimann 已提交
105
	}
106 107
};

108
export const newWindowCommand = (accessor: ServicesAccessor, options?: IOpenEmptyWindowOptions) => {
109
	const hostService = accessor.get(IHostService);
110
	hostService.openWindow(options);
111 112
};

113
// Command registration
I
isidor 已提交
114

115
KeybindingsRegistry.registerCommandAndKeybindingRule({
116
	weight: KeybindingWeight.WorkbenchContrib,
117 118 119 120 121
	when: ExplorerFocusCondition,
	primary: KeyMod.CtrlCmd | KeyCode.Enter,
	mac: {
		primary: KeyMod.WinCtrl | KeyCode.Enter
	},
122
	id: OPEN_TO_SIDE_COMMAND_ID, handler: async (accessor, resource: URI | object) => {
123
		const editorService = accessor.get(IEditorService);
124
		const listService = accessor.get(IListService);
I
isidor 已提交
125
		const fileService = accessor.get(IFileService);
I
isidor 已提交
126 127
		const explorerService = accessor.get(IExplorerService);
		const resources = getMultiSelectedResources(resource, listService, editorService, explorerService);
B
Benjamin Pasero 已提交
128

129
		// Set side input
I
isidor 已提交
130
		if (resources.length) {
131 132 133 134
			const untitledResources = resources.filter(resource => resource.scheme === Schemas.untitled);
			const fileResources = resources.filter(resource => resource.scheme !== Schemas.untitled);

			const resolved = await fileService.resolveAll(fileResources.map(resource => ({ resource })));
135 136
			const editors = resolved.filter(r => r.stat && r.success && !r.stat.isDirectory).map(r => ({
				resource: r.stat!.resource
137
			})).concat(...untitledResources.map(untitledResource => ({ resource: untitledResource })));
I
isidor 已提交
138

139
			await editorService.openEditors(editors, SIDE_GROUP);
B
Benjamin Pasero 已提交
140
		}
141 142 143
	}
});

I
isidor 已提交
144
const COMPARE_WITH_SAVED_SCHEMA = 'showModifications';
145
let providerDisposables: IDisposable[] = [];
146 147 148
KeybindingsRegistry.registerCommandAndKeybindingRule({
	id: COMPARE_WITH_SAVED_COMMAND_ID,
	when: undefined,
149
	weight: KeybindingWeight.WorkbenchContrib,
150
	primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_D),
I
isidor 已提交
151
	handler: async (accessor, resource: URI | object) => {
152 153 154
		const instantiationService = accessor.get(IInstantiationService);
		const textModelService = accessor.get(ITextModelService);
		const editorService = accessor.get(IEditorService);
155
		const fileService = accessor.get(IFileService);
156 157 158 159 160 161

		// Register provider at first as needed
		let registerEditorListener = false;
		if (providerDisposables.length === 0) {
			registerEditorListener = true;

162
			const provider = instantiationService.createInstance(TextFileContentProvider);
163 164
			providerDisposables.push(provider);
			providerDisposables.push(textModelService.registerTextModelContentProvider(COMPARE_WITH_SAVED_SCHEMA, provider));
I
isidor 已提交
165 166
		}

167
		// Open editor (only resources that can be handled by file service are supported)
I
isidor 已提交
168
		const uri = getResourceForCommand(resource, accessor.get(IListService), editorService);
169
		if (uri && fileService.canHandleResource(uri)) {
B
Benjamin Pasero 已提交
170
			const name = basename(uri);
171
			const editorLabel = nls.localize('modifiedLabel', "{0} (in file) ↔ {1}", name, name);
I
isidor 已提交
172

I
isidor 已提交
173 174
			try {
				await TextFileContentProvider.open(uri, COMPARE_WITH_SAVED_SCHEMA, editorLabel, editorService);
175 176 177
				// Dispose once no more diff editor is opened with the scheme
				if (registerEditorListener) {
					providerDisposables.push(editorService.onDidVisibleEditorsChange(() => {
178
						if (!editorService.editors.some(editor => !!toResource(editor, { supportSideBySide: SideBySideEditor.DETAILS, filterByScheme: COMPARE_WITH_SAVED_SCHEMA }))) {
179 180 181 182
							providerDisposables = dispose(providerDisposables);
						}
					}));
				}
I
isidor 已提交
183
			} catch {
184
				providerDisposables = dispose(providerDisposables);
I
isidor 已提交
185
			}
I
isidor 已提交
186
		}
187 188 189
	}
});

190
let globalResourceToCompare: URI | undefined;
I
isidor 已提交
191
let resourceSelectedForCompareContext: IContextKey<boolean>;
192 193
CommandsRegistry.registerCommand({
	id: SELECT_FOR_COMPARE_COMMAND_ID,
I
isidor 已提交
194
	handler: (accessor, resource: URI | object) => {
195
		const listService = accessor.get(IListService);
I
isidor 已提交
196

197
		globalResourceToCompare = getResourceForCommand(resource, listService, accessor.get(IEditorService));
I
isidor 已提交
198 199 200 201
		if (!resourceSelectedForCompareContext) {
			resourceSelectedForCompareContext = ResourceSelectedForCompareContext.bindTo(accessor.get(IContextKeyService));
		}
		resourceSelectedForCompareContext.set(true);
202 203 204
	}
});

I
isidor 已提交
205 206
CommandsRegistry.registerCommand({
	id: COMPARE_SELECTED_COMMAND_ID,
207
	handler: async (accessor, resource: URI | object) => {
208
		const editorService = accessor.get(IEditorService);
I
isidor 已提交
209 210
		const explorerService = accessor.get(IExplorerService);
		const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService, explorerService);
I
isidor 已提交
211

I
isidor 已提交
212 213 214 215 216 217 218
		if (resources.length === 2) {
			return editorService.openEditor({
				leftResource: resources[0],
				rightResource: resources[1]
			});
		}

219
		return true;
I
isidor 已提交
220 221 222
	}
});

223 224
CommandsRegistry.registerCommand({
	id: COMPARE_RESOURCE_COMMAND_ID,
I
isidor 已提交
225
	handler: (accessor, resource: URI | object) => {
226
		const editorService = accessor.get(IEditorService);
227
		const listService = accessor.get(IListService);
I
isidor 已提交
228

I
isidor 已提交
229 230 231 232 233
		const rightResource = getResourceForCommand(resource, listService, editorService);
		if (globalResourceToCompare && rightResource) {
			editorService.openEditor({
				leftResource: globalResourceToCompare,
				rightResource
234
			});
I
isidor 已提交
235
		}
236 237 238
	}
});

239
async function resourcesToClipboard(resources: URI[], relative: boolean, clipboardService: IClipboardService, notificationService: INotificationService, labelService: ILabelService): Promise<void> {
240
	if (resources.length) {
I
isidor 已提交
241
		const lineDelimiter = isWindows ? '\r\n' : '\n';
242

243
		const text = resources.map(resource => labelService.getUriLabel(resource, { relative, noPrefix: true }))
I
isidor 已提交
244
			.join(lineDelimiter);
245
		await clipboardService.writeText(text);
246
	} else {
247
		notificationService.info(nls.localize('openFileToCopy', "Open a file first to copy its path"));
248
	}
I
isidor 已提交
249
}
250

251
KeybindingsRegistry.registerCommandAndKeybindingRule({
252
	weight: KeybindingWeight.WorkbenchContrib,
P
Peng Lyu 已提交
253
	when: EditorContextKeys.focus.toNegated(),
254 255 256 257 258
	primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_C,
	win: {
		primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_C
	},
	id: COPY_PATH_COMMAND_ID,
259
	handler: async (accessor, resource: URI | object) => {
I
isidor 已提交
260
		const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IExplorerService));
261
		await resourcesToClipboard(resources, false, accessor.get(IClipboardService), accessor.get(INotificationService), accessor.get(ILabelService));
I
isidor 已提交
262
	}
263 264
});

265
KeybindingsRegistry.registerCommandAndKeybindingRule({
266
	weight: KeybindingWeight.WorkbenchContrib,
267 268
	when: EditorContextKeys.focus.toNegated(),
	primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_C,
269
	win: {
270
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C)
271
	},
272
	id: COPY_RELATIVE_PATH_COMMAND_ID,
273
	handler: async (accessor, resource: URI | object) => {
I
isidor 已提交
274
		const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IExplorerService));
275
		await resourcesToClipboard(resources, true, accessor.get(IClipboardService), accessor.get(INotificationService), accessor.get(ILabelService));
276 277 278
	}
});

279
KeybindingsRegistry.registerCommandAndKeybindingRule({
280
	weight: KeybindingWeight.WorkbenchContrib,
281 282
	when: undefined,
	primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_P),
283
	id: 'workbench.action.files.copyPathOfActiveFile',
284
	handler: async (accessor) => {
285
		const editorService = accessor.get(IEditorService);
286
		const activeInput = editorService.activeEditor;
287
		const resource = activeInput ? activeInput.resource : null;
M
Matt Bierner 已提交
288
		const resources = resource ? [resource] : [];
289
		await resourcesToClipboard(resources, false, accessor.get(IClipboardService), accessor.get(INotificationService), accessor.get(ILabelService));
I
isidor 已提交
290
	}
291 292 293 294
});

CommandsRegistry.registerCommand({
	id: REVEAL_IN_EXPLORER_COMMAND_ID,
295
	handler: async (accessor, resource: URI | object) => {
296 297
		const viewletService = accessor.get(IViewletService);
		const contextService = accessor.get(IWorkspaceContextService);
I
isidor 已提交
298
		const explorerService = accessor.get(IExplorerService);
299
		const uri = getResourceForCommand(resource, accessor.get(IListService), accessor.get(IEditorService));
300

S
SteVen Batten 已提交
301
		const viewlet = (await viewletService.openViewlet(VIEWLET_ID, false))?.getViewPaneContainer() as ExplorerViewPaneContainer;
302 303 304 305 306 307 308

		if (uri && contextService.isInsideWorkspace(uri)) {
			const explorerView = viewlet.getExplorerView();
			if (explorerView) {
				explorerView.setExpanded(true);
				await explorerService.select(uri, true);
				explorerView.focus();
I
isidor 已提交
309
			}
310 311 312 313 314 315 316
		} else {
			const openEditorsView = viewlet.getOpenEditorsView();
			if (openEditorsView) {
				openEditorsView.setExpanded(true);
				openEditorsView.focus();
			}
		}
317 318
	}
});
I
isidor 已提交
319

320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
CommandsRegistry.registerCommand({
	id: OPEN_WITH_EXPLORER_COMMAND_ID,
	handler: async (accessor, resource: URI | object) => {
		const editorService = accessor.get(IEditorService);
		const editorGroupsService = accessor.get(IEditorGroupsService);
		const configurationService = accessor.get(IConfigurationService);
		const quickInputService = accessor.get(IQuickInputService);

		const uri = getResourceForCommand(resource, accessor.get(IListService), accessor.get(IEditorService));
		if (uri) {
			const input = editorService.createEditorInput({ resource: uri });
			openEditorWith(input, undefined, undefined, editorGroupsService.activeGroup, editorService, configurationService, quickInputService);
		}
	}
});

336 337
// Save / Save As / Save All / Revert

338
async function saveSelectedEditors(accessor: ServicesAccessor, options?: ISaveEditorsOptions): Promise<void> {
339
	const listService = accessor.get(IListService);
340
	const editorGroupService = accessor.get(IEditorGroupsService);
341 342 343
	const codeEditorService = accessor.get(ICodeEditorService);
	const textFileService = accessor.get(ITextFileService);

344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
	// Retrieve selected or active editor
	let editors = getOpenEditorsViewMultiSelection(listService, editorGroupService);
	if (!editors) {
		const activeGroup = editorGroupService.activeGroup;
		if (activeGroup.activeEditor) {
			editors = [];

			// Special treatment for side by side editors: if the active editor
			// has 2 sides, we consider both, to support saving both sides.
			// We only allow this when saving, not for "Save As".
			// See also https://github.com/microsoft/vscode/issues/4180
			if (activeGroup.activeEditor instanceof SideBySideEditorInput && !options?.saveAs) {
				editors.push({ groupId: activeGroup.id, editor: activeGroup.activeEditor.master });
				editors.push({ groupId: activeGroup.id, editor: activeGroup.activeEditor.details });
			} else {
				editors.push({ groupId: activeGroup.id, editor: activeGroup.activeEditor });
			}
		}
	}

	if (!editors || editors.length === 0) {
		return; // nothing to save
	}

368 369 370 371 372 373 374 375 376 377 378 379 380
	// Save editors
	await doSaveEditors(accessor, editors, options);

	// Special treatment for embedded editors: if we detect that focus is
	// inside an embedded code editor, we save that model as well if we
	// find it in our text file models. Currently, only textual editors
	// support embedded editors.
	const focusedCodeEditor = codeEditorService.getFocusedCodeEditor();
	if (focusedCodeEditor instanceof EmbeddedCodeEditorWidget) {
		const resource = focusedCodeEditor.getModel()?.uri;

		// Check that the resource of the model was not saved already
		if (resource && !editors.some(({ editor }) => isEqual(toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }), resource))) {
381
			const model = textFileService.files.get(resource);
382 383 384 385 386
			if (!model?.isReadonly()) {
				await textFileService.save(resource, options);
			}
		}
	}
387 388
}

B
Benjamin Pasero 已提交
389
function saveDirtyEditorsOfGroups(accessor: ServicesAccessor, groups: ReadonlyArray<IEditorGroup>, options?: ISaveEditorsOptions): Promise<void> {
390
	const dirtyEditors: IEditorIdentifier[] = [];
391 392
	for (const group of groups) {
		for (const editor of group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) {
B
Benjamin Pasero 已提交
393
			if (editor.isDirty()) {
394
				dirtyEditors.push({ groupId: group.id, editor });
395
			}
396
		}
397 398
	}

399
	return doSaveEditors(accessor, dirtyEditors, options);
400
}
401

402 403 404 405 406 407 408 409
async function doSaveEditors(accessor: ServicesAccessor, editors: IEditorIdentifier[], options?: ISaveEditorsOptions): Promise<void> {
	const editorService = accessor.get(IEditorService);
	const notificationService = accessor.get(INotificationService);

	try {
		await editorService.save(editors, options);
	} catch (error) {
		notificationService.error(nls.localize('genericSaveError', "Failed to save '{0}': {1}", editors.map(({ editor }) => editor.getName()).join(', '), toErrorMessage(error, false)));
410
	}
411
}
412 413 414

KeybindingsRegistry.registerCommandAndKeybindingRule({
	when: undefined,
415
	weight: KeybindingWeight.WorkbenchContrib,
416 417
	primary: KeyMod.CtrlCmd | KeyCode.KEY_S,
	id: SAVE_FILE_COMMAND_ID,
418
	handler: accessor => {
B
Benjamin Pasero 已提交
419
		return saveSelectedEditors(accessor, { reason: SaveReason.EXPLICIT, force: true /* force save even when non-dirty */ });
420 421
	}
});
I
isidor 已提交
422

B
Benjamin Pasero 已提交
423 424 425 426
KeybindingsRegistry.registerCommandAndKeybindingRule({
	when: undefined,
	weight: KeybindingWeight.WorkbenchContrib,
	primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_S),
B
Benjamin Pasero 已提交
427
	win: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_S) },
B
Benjamin Pasero 已提交
428 429
	id: SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID,
	handler: accessor => {
B
Benjamin Pasero 已提交
430
		return saveSelectedEditors(accessor, { reason: SaveReason.EXPLICIT, force: true /* force save even when non-dirty */, skipSaveParticipants: true });
431 432
	}
});
B
Benjamin Pasero 已提交
433

434 435 436 437 438 439
KeybindingsRegistry.registerCommandAndKeybindingRule({
	id: SAVE_FILE_AS_COMMAND_ID,
	weight: KeybindingWeight.WorkbenchContrib,
	when: undefined,
	primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_S,
	handler: accessor => {
B
Benjamin Pasero 已提交
440
		return saveSelectedEditors(accessor, { reason: SaveReason.EXPLICIT, saveAs: true });
B
Benjamin Pasero 已提交
441 442 443
	}
});

444 445
CommandsRegistry.registerCommand({
	id: SAVE_ALL_COMMAND_ID,
446
	handler: (accessor) => {
B
Benjamin Pasero 已提交
447
		return saveDirtyEditorsOfGroups(accessor, accessor.get(IEditorGroupsService).getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE), { reason: SaveReason.EXPLICIT });
448 449 450 451 452
	}
});

CommandsRegistry.registerCommand({
	id: SAVE_ALL_IN_GROUP_COMMAND_ID,
I
isidor 已提交
453
	handler: (accessor, _: URI | object, editorContext: IEditorCommandsContext) => {
454
		const editorGroupService = accessor.get(IEditorGroupsService);
455 456 457

		const contexts = getMultiSelectedEditorContexts(editorContext, accessor.get(IListService), accessor.get(IEditorGroupsService));

B
Benjamin Pasero 已提交
458
		let groups: ReadonlyArray<IEditorGroup> | undefined = undefined;
I
isidor 已提交
459
		if (!contexts.length) {
B
Benjamin Pasero 已提交
460
			groups = editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE);
461
		} else {
B
Benjamin Pasero 已提交
462
			groups = coalesce(contexts.map(context => editorGroupService.getGroup(context.groupId)));
463 464
		}

B
Benjamin Pasero 已提交
465
		return saveDirtyEditorsOfGroups(accessor, groups, { reason: SaveReason.EXPLICIT });
466 467
	}
});
I
isidor 已提交
468

469 470
CommandsRegistry.registerCommand({
	id: SAVE_FILES_COMMAND_ID,
471 472 473
	handler: accessor => {
		const editorService = accessor.get(IEditorService);

B
Benjamin Pasero 已提交
474
		return editorService.saveAll({ includeUntitled: false, reason: SaveReason.EXPLICIT });
475 476 477 478 479 480 481 482
	}
});

CommandsRegistry.registerCommand({
	id: REVERT_FILE_COMMAND_ID,
	handler: async accessor => {
		const notificationService = accessor.get(INotificationService);
		const listService = accessor.get(IListService);
483
		const editorGroupService = accessor.get(IEditorGroupsService);
B
Benjamin Pasero 已提交
484
		const editorService = accessor.get(IEditorService);
485

486 487 488 489 490 491 492 493 494 495 496 497 498
		// Retrieve selected or active editor
		let editors = getOpenEditorsViewMultiSelection(listService, editorGroupService);
		if (!editors) {
			const activeGroup = editorGroupService.activeGroup;
			if (activeGroup.activeEditor) {
				editors = [{ groupId: activeGroup.id, editor: activeGroup.activeEditor }];
			}
		}

		if (!editors || editors.length === 0) {
			return; // nothing to revert
		}

B
Benjamin Pasero 已提交
499
		try {
500
			await editorService.revert(editors.filter(({ editor }) => !editor.isUntitled() /* all except untitled */), { force: true });
B
Benjamin Pasero 已提交
501 502
		} catch (error) {
			notificationService.error(nls.localize('genericRevertError', "Failed to revert '{0}': {1}", editors.map(({ editor }) => editor.getName()).join(', '), toErrorMessage(error, false)));
503
		}
504 505
	}
});
506 507 508

CommandsRegistry.registerCommand({
	id: REMOVE_ROOT_FOLDER_COMMAND_ID,
I
isidor 已提交
509
	handler: (accessor, resource: URI | object) => {
510 511 512
		const workspaceEditingService = accessor.get(IWorkspaceEditingService);
		const contextService = accessor.get(IWorkspaceContextService);
		const workspace = contextService.getWorkspace();
I
isidor 已提交
513
		const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IExplorerService)).filter(r =>
514
			// Need to verify resources are workspaces since multi selection can trigger this command on some non workspace resources
515
			workspace.folders.some(f => isEqual(f.uri, r))
516 517 518 519 520
		);

		return workspaceEditingService.removeFolders(resources);
	}
});
521 522 523 524

// Compressed item navigation

KeybindingsRegistry.registerCommandAndKeybindingRule({
525 526
	weight: KeybindingWeight.WorkbenchContrib + 10,
	when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext.negate()),
527 528 529 530 531 532 533 534 535 536
	primary: KeyCode.LeftArrow,
	id: PREVIOUS_COMPRESSED_FOLDER,
	handler: (accessor) => {
		const viewletService = accessor.get(IViewletService);
		const viewlet = viewletService.getActiveViewlet();

		if (viewlet?.getId() !== VIEWLET_ID) {
			return;
		}

S
SteVen Batten 已提交
537
		const explorer = viewlet.getViewPaneContainer() as ExplorerViewPaneContainer;
538 539 540 541 542 543
		const view = explorer.getExplorerView();
		view.previousCompressedStat();
	}
});

KeybindingsRegistry.registerCommandAndKeybindingRule({
544 545
	weight: KeybindingWeight.WorkbenchContrib + 10,
	when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedLastFocusContext.negate()),
546 547 548 549 550 551 552 553 554 555
	primary: KeyCode.RightArrow,
	id: NEXT_COMPRESSED_FOLDER,
	handler: (accessor) => {
		const viewletService = accessor.get(IViewletService);
		const viewlet = viewletService.getActiveViewlet();

		if (viewlet?.getId() !== VIEWLET_ID) {
			return;
		}

S
SteVen Batten 已提交
556
		const explorer = viewlet.getViewPaneContainer() as ExplorerViewPaneContainer;
557 558 559 560
		const view = explorer.getExplorerView();
		view.nextCompressedStat();
	}
});
561 562 563 564 565 566 567 568 569 570 571 572 573 574

KeybindingsRegistry.registerCommandAndKeybindingRule({
	weight: KeybindingWeight.WorkbenchContrib + 10,
	when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext.negate()),
	primary: KeyCode.Home,
	id: FIRST_COMPRESSED_FOLDER,
	handler: (accessor) => {
		const viewletService = accessor.get(IViewletService);
		const viewlet = viewletService.getActiveViewlet();

		if (viewlet?.getId() !== VIEWLET_ID) {
			return;
		}

S
SteVen Batten 已提交
575
		const explorer = viewlet.getViewPaneContainer() as ExplorerViewPaneContainer;
576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
		const view = explorer.getExplorerView();
		view.firstCompressedStat();
	}
});

KeybindingsRegistry.registerCommandAndKeybindingRule({
	weight: KeybindingWeight.WorkbenchContrib + 10,
	when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedLastFocusContext.negate()),
	primary: KeyCode.End,
	id: LAST_COMPRESSED_FOLDER,
	handler: (accessor) => {
		const viewletService = accessor.get(IViewletService);
		const viewlet = viewletService.getActiveViewlet();

		if (viewlet?.getId() !== VIEWLET_ID) {
			return;
		}

S
SteVen Batten 已提交
594
		const explorer = viewlet.getViewPaneContainer() as ExplorerViewPaneContainer;
595 596 597 598
		const view = explorer.getExplorerView();
		view.lastCompressedStat();
	}
});