editorCommands.ts 29.1 KB
Newer Older
S
Sandeep Somavarapu 已提交
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import * as nls from 'vs/nls';
import * as types from 'vs/base/common/types';
J
Johannes Rieken 已提交
8
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
9
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
10
import { TextCompareEditorVisibleContext, EditorInput, IEditorIdentifier, IEditorCommandsContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, CloseDirection, IEditor, IEditorInput } from 'vs/workbench/common/editor';
11
import { IEditorService, IVisibleEditor } from 'vs/workbench/services/editor/common/editorService';
12
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
13
import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor';
I
isidor 已提交
14
import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes';
15
import { URI } from 'vs/base/common/uri';
B
Benjamin Pasero 已提交
16
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
I
isidor 已提交
17 18
import { IListService } from 'vs/platform/list/browser/listService';
import { List } from 'vs/base/browser/ui/list/listWidget';
19
import { distinct, coalesce } from 'vs/base/common/arrays';
20
import { IEditorGroupsService, IEditorGroup, GroupDirection, GroupLocation, GroupsOrder, preferredSideBySideGroupDirection, EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService';
21
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
22
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
H
Hao Hu 已提交
23
import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands';
24
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
25

26
export const CLOSE_SAVED_EDITORS_COMMAND_ID = 'workbench.action.closeUnmodifiedEditors';
I
isidor 已提交
27
export const CLOSE_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeEditorsInGroup';
28
export const CLOSE_EDITORS_AND_GROUP_COMMAND_ID = 'workbench.action.closeEditorsAndGroup';
29
export const CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID = 'workbench.action.closeEditorsToTheRight';
I
isidor 已提交
30
export const CLOSE_EDITOR_COMMAND_ID = 'workbench.action.closeActiveEditor';
B
Benjamin Pasero 已提交
31
export const CLOSE_EDITOR_GROUP_COMMAND_ID = 'workbench.action.closeGroup';
I
isidor 已提交
32
export const CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeOtherEditors';
33 34

export const MOVE_ACTIVE_EDITOR_COMMAND_ID = 'moveActiveEditor';
35
export const LAYOUT_EDITOR_GROUPS_COMMAND_ID = 'layoutEditorGroups';
36
export const KEEP_EDITOR_COMMAND_ID = 'workbench.action.keepEditor';
B
Benjamin Pasero 已提交
37
export const SHOW_EDITORS_IN_GROUP = 'workbench.action.showEditorsInGroup';
38

B
Benjamin Pasero 已提交
39
export const TOGGLE_DIFF_SIDE_BY_SIDE = 'toggle.diff.renderSideBySide';
40 41
export const GOTO_NEXT_CHANGE = 'workbench.action.compareEditor.nextChange';
export const GOTO_PREVIOUS_CHANGE = 'workbench.action.compareEditor.previousChange';
42
export const TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE = 'toggle.diff.ignoreTrimWhitespace';
B
Benjamin Pasero 已提交
43

B
Benjamin Pasero 已提交
44 45 46 47
export const SPLIT_EDITOR_UP = 'workbench.action.splitEditorUp';
export const SPLIT_EDITOR_DOWN = 'workbench.action.splitEditorDown';
export const SPLIT_EDITOR_LEFT = 'workbench.action.splitEditorLeft';
export const SPLIT_EDITOR_RIGHT = 'workbench.action.splitEditorRight';
48

B
Benjamin Pasero 已提交
49
export const NAVIGATE_ALL_EDITORS_GROUP_PREFIX = 'edt ';
B
Benjamin Pasero 已提交
50
export const NAVIGATE_IN_ACTIVE_GROUP_PREFIX = 'edt active ';
51

H
Hao Hu 已提交
52 53
export const OPEN_EDITOR_AT_INDEX_COMMAND_ID = 'workbench.action.openEditorAtIndex';

54
export interface ActiveEditorMoveArguments {
55 56 57
	to: 'first' | 'last' | 'left' | 'right' | 'up' | 'down' | 'center' | 'position' | 'previous' | 'next';
	by: 'tab' | 'group';
	value: number;
S
Sandeep Somavarapu 已提交
58 59
}

B
Benjamin Pasero 已提交
60
const isActiveEditorMoveArg = function (arg: ActiveEditorMoveArguments): boolean {
S
Sandeep Somavarapu 已提交
61 62 63 64
	if (!types.isObject(arg)) {
		return false;
	}

65
	if (!types.isString(arg.to)) {
S
Sandeep Somavarapu 已提交
66 67 68
		return false;
	}

69
	if (!types.isUndefined(arg.by) && !types.isString(arg.by)) {
S
Sandeep Somavarapu 已提交
70 71 72
		return false;
	}

73
	if (!types.isUndefined(arg.value) && !types.isNumber(arg.value)) {
S
Sandeep Somavarapu 已提交
74 75 76 77 78 79
		return false;
	}

	return true;
};

80
function registerActiveEditorMoveCommand(): void {
A
Alex Dima 已提交
81
	KeybindingsRegistry.registerCommandAndKeybindingRule({
82
		id: MOVE_ACTIVE_EDITOR_COMMAND_ID,
83
		weight: KeybindingWeight.WorkbenchContrib,
84
		when: EditorContextKeys.editorTextFocus,
A
Alex Dima 已提交
85
		primary: 0,
86
		handler: (accessor, args: any) => moveActiveEditor(args, accessor),
S
Sandeep Somavarapu 已提交
87
		description: {
88
			description: nls.localize('editorCommand.activeEditorMove.description', "Move the active editor by tabs or groups"),
S
Sandeep Somavarapu 已提交
89 90 91
			args: [
				{
					name: nls.localize('editorCommand.activeEditorMove.arg.name', "Active editor move argument"),
92
					description: nls.localize('editorCommand.activeEditorMove.arg.description', "Argument Properties:\n\t* 'to': String value providing where to move.\n\t* 'by': String value providing the unit for move (by tab or by group).\n\t* 'value': Number value providing how many positions or an absolute position to move."),
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
					constraint: isActiveEditorMoveArg,
					schema: {
						'type': 'object',
						'required': ['to'],
						'properties': {
							'to': {
								'type': 'string',
								'enum': ['left', 'right']
							},
							'by': {
								'type': 'string',
								'enum': ['tab', 'group']
							},
							'value': {
								'type': 'number'
							}
						},
					}
S
Sandeep Somavarapu 已提交
111 112 113 114 115 116
				}
			]
		}
	});
}

117 118 119 120
function moveActiveEditor(args: ActiveEditorMoveArguments = Object.create(null), accessor: ServicesAccessor): void {
	args.to = args.to || 'right';
	args.by = args.by || 'tab';
	args.value = typeof args.value === 'number' ? args.value : 1;
S
Sandeep Somavarapu 已提交
121

122
	const activeControl = accessor.get(IEditorService).activeControl;
123
	if (activeControl) {
124
		switch (args.by) {
125 126 127 128
			case 'tab':
				return moveActiveTab(args, activeControl, accessor);
			case 'group':
				return moveActiveEditorToGroup(args, activeControl, accessor);
129
		}
S
Sandeep Somavarapu 已提交
130 131 132
	}
}

133
function moveActiveTab(args: ActiveEditorMoveArguments, control: IVisibleEditor, accessor: ServicesAccessor): void {
134 135
	const group = control.group;
	let index = group.getIndexOfEditor(control.input);
S
Sandeep Somavarapu 已提交
136
	switch (args.to) {
137
		case 'first':
S
Sandeep Somavarapu 已提交
138 139
			index = 0;
			break;
140
		case 'last':
141
			index = group.count - 1;
S
Sandeep Somavarapu 已提交
142
			break;
143
		case 'left':
144
			index = index - args.value;
S
Sandeep Somavarapu 已提交
145
			break;
146
		case 'right':
147
			index = index + args.value;
S
Sandeep Somavarapu 已提交
148
			break;
149
		case 'center':
150
			index = Math.round(group.count / 2) - 1;
S
Sandeep Somavarapu 已提交
151
			break;
152
		case 'position':
153
			index = args.value - 1;
S
Sandeep Somavarapu 已提交
154 155
			break;
	}
156

157
	index = index < 0 ? 0 : index >= group.count ? group.count - 1 : index;
158
	group.moveEditor(control.input, group, { index });
S
Sandeep Somavarapu 已提交
159 160
}

161
function moveActiveEditorToGroup(args: ActiveEditorMoveArguments, control: IVisibleEditor, accessor: ServicesAccessor): void {
162
	const editorGroupService = accessor.get(IEditorGroupsService);
B
Benjamin Pasero 已提交
163
	const configurationService = accessor.get(IConfigurationService);
164 165

	const sourceGroup = control.group;
166
	let targetGroup: IEditorGroup | undefined;
167

S
Sandeep Somavarapu 已提交
168
	switch (args.to) {
169
		case 'left':
170 171
			targetGroup = editorGroupService.findGroup({ direction: GroupDirection.LEFT }, sourceGroup);
			if (!targetGroup) {
B
Benjamin Pasero 已提交
172
				targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.LEFT);
173
			}
174 175
			break;
		case 'right':
176 177
			targetGroup = editorGroupService.findGroup({ direction: GroupDirection.RIGHT }, sourceGroup);
			if (!targetGroup) {
B
Benjamin Pasero 已提交
178
				targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.RIGHT);
179
			}
S
Sandeep Somavarapu 已提交
180
			break;
181
		case 'up':
182 183
			targetGroup = editorGroupService.findGroup({ direction: GroupDirection.UP }, sourceGroup);
			if (!targetGroup) {
B
Benjamin Pasero 已提交
184
				targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.UP);
185
			}
S
Sandeep Somavarapu 已提交
186
			break;
187
		case 'down':
188 189
			targetGroup = editorGroupService.findGroup({ direction: GroupDirection.DOWN }, sourceGroup);
			if (!targetGroup) {
B
Benjamin Pasero 已提交
190
				targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.DOWN);
191
			}
S
Sandeep Somavarapu 已提交
192
			break;
193
		case 'first':
194
			targetGroup = editorGroupService.findGroup({ location: GroupLocation.FIRST }, sourceGroup);
S
Sandeep Somavarapu 已提交
195
			break;
196
		case 'last':
197 198 199 200 201 202 203
			targetGroup = editorGroupService.findGroup({ location: GroupLocation.LAST }, sourceGroup);
			break;
		case 'previous':
			targetGroup = editorGroupService.findGroup({ location: GroupLocation.PREVIOUS }, sourceGroup);
			break;
		case 'next':
			targetGroup = editorGroupService.findGroup({ location: GroupLocation.NEXT }, sourceGroup);
B
Benjamin Pasero 已提交
204 205 206
			if (!targetGroup) {
				targetGroup = editorGroupService.addGroup(sourceGroup, preferredSideBySideGroupDirection(configurationService));
			}
S
Sandeep Somavarapu 已提交
207
			break;
208
		case 'center':
B
Benjamin Pasero 已提交
209
			targetGroup = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)[(editorGroupService.count / 2) - 1];
210 211
			break;
		case 'position':
B
Benjamin Pasero 已提交
212
			targetGroup = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)[args.value - 1];
S
Sandeep Somavarapu 已提交
213 214
			break;
	}
215

216 217
	if (targetGroup) {
		sourceGroup.moveEditor(control.input, targetGroup);
B
Benjamin Pasero 已提交
218
		targetGroup.focus();
219
	}
220 221
}

222
function registerEditorGroupsLayoutCommand(): void {
223 224 225 226
	CommandsRegistry.registerCommand(LAYOUT_EDITOR_GROUPS_COMMAND_ID, (accessor: ServicesAccessor, args: EditorGroupLayout) => {
		if (!args || typeof args !== 'object') {
			return;
		}
227

228 229 230
		const editorGroupService = accessor.get(IEditorGroupsService);
		editorGroupService.applyLayout(args);
	});
231 232
}

233 234
export function mergeAllGroups(editorGroupService: IEditorGroupsService): void {
	const target = editorGroupService.activeGroup;
235
	editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).forEach(group => {
236 237 238 239 240 241
		if (group === target) {
			return; // keep target
		}

		editorGroupService.mergeGroup(group, target);
	});
242 243
}

244 245
function registerDiffEditorCommands(): void {
	KeybindingsRegistry.registerCommandAndKeybindingRule({
246
		id: GOTO_NEXT_CHANGE,
247
		weight: KeybindingWeight.WorkbenchContrib,
248
		when: TextCompareEditorVisibleContext,
249
		primary: KeyMod.Alt | KeyCode.F5,
250 251 252 253
		handler: accessor => navigateInDiffEditor(accessor, true)
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
254
		id: GOTO_PREVIOUS_CHANGE,
255
		weight: KeybindingWeight.WorkbenchContrib,
256
		when: TextCompareEditorVisibleContext,
257
		primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F5,
258 259 260 261
		handler: accessor => navigateInDiffEditor(accessor, false)
	});

	function navigateInDiffEditor(accessor: ServicesAccessor, next: boolean): void {
262
		const editorService = accessor.get(IEditorService);
263
		const candidates = [editorService.activeControl, ...editorService.visibleControls].filter(e => e instanceof TextDiffEditor);
264 265 266 267 268 269

		if (candidates.length > 0) {
			next ? (<TextDiffEditor>candidates[0]).getDiffNavigator().next() : (<TextDiffEditor>candidates[0]).getDiffNavigator().previous();
		}
	}

B
Benjamin Pasero 已提交
270 271 272 273 274 275 276
	function toggleDiffSideBySide(accessor: ServicesAccessor): void {
		const configurationService = accessor.get(IConfigurationService);

		const newValue = !configurationService.getValue<boolean>('diffEditor.renderSideBySide');
		configurationService.updateValue('diffEditor.renderSideBySide', newValue, ConfigurationTarget.USER);
	}

277 278 279 280 281 282 283
	function toggleDiffIgnoreTrimWhitespace(accessor: ServicesAccessor): void {
		const configurationService = accessor.get(IConfigurationService);

		const newValue = !configurationService.getValue<boolean>('diffEditor.ignoreTrimWhitespace');
		configurationService.updateValue('diffEditor.ignoreTrimWhitespace', newValue, ConfigurationTarget.USER);
	}

284
	KeybindingsRegistry.registerCommandAndKeybindingRule({
B
Benjamin Pasero 已提交
285
		id: TOGGLE_DIFF_SIDE_BY_SIDE,
286
		weight: KeybindingWeight.WorkbenchContrib,
R
Rob Lourens 已提交
287 288
		when: undefined,
		primary: undefined,
B
Benjamin Pasero 已提交
289 290
		handler: accessor => toggleDiffSideBySide(accessor)
	});
291

292 293
	MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
		command: {
B
Benjamin Pasero 已提交
294
			id: TOGGLE_DIFF_SIDE_BY_SIDE,
B
Benjamin Pasero 已提交
295 296 297 298 299
			title: {
				value: nls.localize('toggleInlineView', "Toggle Inline View"),
				original: 'Compare: Toggle Inline View'
			},
			category: nls.localize('compare', "Compare")
300 301 302
		},
		when: ContextKeyExpr.has('textCompareEditorActive')
	});
303 304 305 306

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE,
		weight: KeybindingWeight.WorkbenchContrib,
R
Rob Lourens 已提交
307 308
		when: undefined,
		primary: undefined,
309 310
		handler: accessor => toggleDiffIgnoreTrimWhitespace(accessor)
	});
311 312 313
}

function registerOpenEditorAtIndexCommands(): void {
H
Hao Hu 已提交
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
	const openEditorAtIndex: ICommandHandler = (accessor: ServicesAccessor, editorIndex: number): void => {
		const editorService = accessor.get(IEditorService);
		const activeControl = editorService.activeControl;
		if (activeControl) {
			const editor = activeControl.group.getEditor(editorIndex);
			if (editor) {
				editorService.openEditor(editor);
			}
		}
	};

	// This command takes in the editor index number to open as an argument
	CommandsRegistry.registerCommand({
		id: OPEN_EDITOR_AT_INDEX_COMMAND_ID,
		handler: openEditorAtIndex
	});
330 331 332 333 334 335 336

	// Keybindings to focus a specific index in the tab folder if tabs are enabled
	for (let i = 0; i < 9; i++) {
		const editorIndex = i;
		const visibleIndex = i + 1;

		KeybindingsRegistry.registerCommandAndKeybindingRule({
H
Hao Hu 已提交
337
			id: OPEN_EDITOR_AT_INDEX_COMMAND_ID + visibleIndex,
338
			weight: KeybindingWeight.WorkbenchContrib,
R
Rob Lourens 已提交
339
			when: undefined,
340 341
			primary: KeyMod.Alt | toKeyCode(visibleIndex),
			mac: { primary: KeyMod.WinCtrl | toKeyCode(visibleIndex) },
H
Hao Hu 已提交
342
			handler: accessor => openEditorAtIndex(accessor, editorIndex)
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
		});
	}

	function toKeyCode(index: number): KeyCode {
		switch (index) {
			case 0: return KeyCode.KEY_0;
			case 1: return KeyCode.KEY_1;
			case 2: return KeyCode.KEY_2;
			case 3: return KeyCode.KEY_3;
			case 4: return KeyCode.KEY_4;
			case 5: return KeyCode.KEY_5;
			case 6: return KeyCode.KEY_6;
			case 7: return KeyCode.KEY_7;
			case 8: return KeyCode.KEY_8;
			case 9: return KeyCode.KEY_9;
		}
359

360
		throw new Error('invalid index');
361
	}
362 363
}

B
Benjamin Pasero 已提交
364 365 366
function registerFocusEditorGroupAtIndexCommands(): void {

	// Keybindings to focus a specific group (2-8) in the editor area
367
	for (let groupIndex = 1; groupIndex < 8; groupIndex++) {
B
Benjamin Pasero 已提交
368 369
		KeybindingsRegistry.registerCommandAndKeybindingRule({
			id: toCommandId(groupIndex),
370
			weight: KeybindingWeight.WorkbenchContrib,
R
Rob Lourens 已提交
371
			when: undefined,
B
Benjamin Pasero 已提交
372 373
			primary: KeyMod.CtrlCmd | toKeyCode(groupIndex),
			handler: accessor => {
374
				const editorGroupService = accessor.get(IEditorGroupsService);
B
Benjamin Pasero 已提交
375 376 377 378 379 380 381 382 383 384
				const configurationService = accessor.get(IConfigurationService);

				// To keep backwards compatibility (pre-grid), allow to focus a group
				// that does not exist as long as it is the next group after the last
				// opened group. Otherwise we return.
				if (groupIndex > editorGroupService.count) {
					return;
				}

				// Group exists: just focus
B
Benjamin Pasero 已提交
385
				const groups = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE);
B
Benjamin Pasero 已提交
386 387 388 389 390
				if (groups[groupIndex]) {
					return groups[groupIndex].focus();
				}

				// Group does not exist: create new by splitting the active one of the last group
391
				const direction = preferredSideBySideGroupDirection(configurationService);
B
Benjamin Pasero 已提交
392
				const lastGroup = editorGroupService.findGroup({ location: GroupLocation.LAST });
B
Benjamin Pasero 已提交
393
				const newGroup = editorGroupService.addGroup(lastGroup, direction);
B
Benjamin Pasero 已提交
394

B
Benjamin Pasero 已提交
395 396
				// Focus
				newGroup.focus();
B
Benjamin Pasero 已提交
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
			}
		});
	}

	function toCommandId(index: number): string {
		switch (index) {
			case 1: return 'workbench.action.focusSecondEditorGroup';
			case 2: return 'workbench.action.focusThirdEditorGroup';
			case 3: return 'workbench.action.focusFourthEditorGroup';
			case 4: return 'workbench.action.focusFifthEditorGroup';
			case 5: return 'workbench.action.focusSixthEditorGroup';
			case 6: return 'workbench.action.focusSeventhEditorGroup';
			case 7: return 'workbench.action.focusEighthEditorGroup';
		}

412
		throw new Error('Invalid index');
B
Benjamin Pasero 已提交
413 414 415 416 417 418 419 420 421 422 423 424 425
	}

	function toKeyCode(index: number): KeyCode {
		switch (index) {
			case 1: return KeyCode.KEY_2;
			case 2: return KeyCode.KEY_3;
			case 3: return KeyCode.KEY_4;
			case 4: return KeyCode.KEY_5;
			case 5: return KeyCode.KEY_6;
			case 6: return KeyCode.KEY_7;
			case 7: return KeyCode.KEY_8;
		}

426
		throw new Error('Invalid index');
B
Benjamin Pasero 已提交
427 428 429
	}
}

430
export function splitEditor(editorGroupService: IEditorGroupsService, direction: GroupDirection, context?: IEditorCommandsContext): void {
B
Benjamin Pasero 已提交
431
	let sourceGroup: IEditorGroup | undefined;
432 433 434 435 436 437
	if (context && typeof context.groupId === 'number') {
		sourceGroup = editorGroupService.getGroup(context.groupId);
	} else {
		sourceGroup = editorGroupService.activeGroup;
	}

B
Benjamin Pasero 已提交
438 439 440 441
	if (!sourceGroup) {
		return;
	}

442 443 444 445
	// Add group
	const newGroup = editorGroupService.addGroup(sourceGroup, direction);

	// Split editor (if it can be split)
446
	let editorToCopy: IEditorInput | null;
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
	if (context && typeof context.editorIndex === 'number') {
		editorToCopy = sourceGroup.getEditor(context.editorIndex);
	} else {
		editorToCopy = sourceGroup.activeEditor;
	}

	if (editorToCopy && (editorToCopy as EditorInput).supportsSplitEditor()) {
		sourceGroup.copyEditor(editorToCopy, newGroup);
	}

	// Focus
	newGroup.focus();
}

function registerSplitEditorCommands() {
	[
		{ id: SPLIT_EDITOR_UP, direction: GroupDirection.UP },
		{ id: SPLIT_EDITOR_DOWN, direction: GroupDirection.DOWN },
		{ id: SPLIT_EDITOR_LEFT, direction: GroupDirection.LEFT },
		{ id: SPLIT_EDITOR_RIGHT, direction: GroupDirection.RIGHT }
	].forEach(({ id, direction }) => {
468 469
		CommandsRegistry.registerCommand(id, function (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) {
			splitEditor(accessor.get(IEditorGroupsService), direction, getCommandsContext(resourceOrContext, context));
470 471 472 473 474
		});
	});
}

function registerCloseEditorCommands() {
475

I
isidor 已提交
476
	KeybindingsRegistry.registerCommandAndKeybindingRule({
477
		id: CLOSE_SAVED_EDITORS_COMMAND_ID,
478
		weight: KeybindingWeight.WorkbenchContrib,
R
Rob Lourens 已提交
479
		when: undefined,
I
isidor 已提交
480
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_U),
481
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
482
			const editorGroupService = accessor.get(IEditorGroupsService);
483
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
B
Benjamin Pasero 已提交
484 485 486 487

			const activeGroup = editorGroupService.activeGroup;
			if (contexts.length === 0) {
				contexts.push({ groupId: activeGroup.id }); // active group as fallback
I
isidor 已提交
488
			}
489

B
Benjamin Pasero 已提交
490 491 492 493 494 495 496 497
			return Promise.all(distinct(contexts.map(c => c.groupId)).map(groupId => {
				const group = editorGroupService.getGroup(groupId);
				if (group) {
					return group.closeEditors({ savedOnly: true });
				}

				return Promise.resolve();
			}));
498 499 500
		}
	});

I
isidor 已提交
501
	KeybindingsRegistry.registerCommandAndKeybindingRule({
502
		id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID,
503
		weight: KeybindingWeight.WorkbenchContrib,
R
Rob Lourens 已提交
504
		when: undefined,
I
isidor 已提交
505
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W),
506
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
507
			const editorGroupService = accessor.get(IEditorGroupsService);
508
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
509
			const distinctGroupIds = distinct(contexts.map(c => c.groupId));
510

I
isidor 已提交
511 512
			if (distinctGroupIds.length === 0) {
				distinctGroupIds.push(editorGroupService.activeGroup.id);
513 514
			}

B
Benjamin Pasero 已提交
515 516 517 518 519 520 521 522
			return Promise.all(distinctGroupIds.map(groupId => {
				const group = editorGroupService.getGroup(groupId);
				if (group) {
					return group.closeAllEditors();
				}

				return Promise.resolve();
			}));
523 524 525
		}
	});

I
isidor 已提交
526
	KeybindingsRegistry.registerCommandAndKeybindingRule({
I
isidor 已提交
527
		id: CLOSE_EDITOR_COMMAND_ID,
528
		weight: KeybindingWeight.WorkbenchContrib,
R
Rob Lourens 已提交
529
		when: undefined,
I
isidor 已提交
530 531
		primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
		win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
532
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
533
			const editorGroupService = accessor.get(IEditorGroupsService);
534
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
B
Benjamin Pasero 已提交
535

I
isidor 已提交
536
			const activeGroup = editorGroupService.activeGroup;
B
Benjamin Pasero 已提交
537 538
			if (contexts.length === 0 && activeGroup.activeEditor) {
				contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) });  // active editor as fallback
539
			}
540

I
isidor 已提交
541
			const groupIds = distinct(contexts.map(context => context.groupId));
B
Benjamin Pasero 已提交
542

I
isidor 已提交
543
			return Promise.all(groupIds.map(groupId => {
I
isidor 已提交
544
				const group = editorGroupService.getGroup(groupId);
B
Benjamin Pasero 已提交
545 546 547 548
				if (group) {
					const editors = coalesce(contexts
						.filter(context => context.groupId === groupId)
						.map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor));
B
Benjamin Pasero 已提交
549

B
Benjamin Pasero 已提交
550 551 552 553
					return group.closeEditors(editors);
				}

				return Promise.resolve();
I
isidor 已提交
554
			}));
555
		}
556 557 558 559
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: CLOSE_EDITOR_GROUP_COMMAND_ID,
560
		weight: KeybindingWeight.WorkbenchContrib,
561 562 563
		when: ContextKeyExpr.and(ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext),
		primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
		win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
564
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
565
			const editorGroupService = accessor.get(IEditorGroupsService);
566
			const commandsContext = getCommandsContext(resourceOrContext, context);
567

B
Benjamin Pasero 已提交
568
			let group: IEditorGroup | undefined;
569 570
			if (commandsContext && typeof commandsContext.groupId === 'number') {
				group = editorGroupService.getGroup(commandsContext.groupId);
571 572 573
			} else {
				group = editorGroupService.activeGroup;
			}
574

B
Benjamin Pasero 已提交
575 576 577
			if (group) {
				editorGroupService.removeGroup(group);
			}
578
		}
579 580
	});

I
isidor 已提交
581
	KeybindingsRegistry.registerCommandAndKeybindingRule({
I
isidor 已提交
582
		id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID,
583
		weight: KeybindingWeight.WorkbenchContrib,
R
Rob Lourens 已提交
584 585
		when: undefined,
		primary: undefined,
I
isidor 已提交
586
		mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T },
587
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
588
			const editorGroupService = accessor.get(IEditorGroupsService);
589
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
590

B
Benjamin Pasero 已提交
591 592 593
			const activeGroup = editorGroupService.activeGroup;
			if (contexts.length === 0 && activeGroup.activeEditor) {
				contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) });  // active editor as fallback
594 595 596
			}

			const groupIds = distinct(contexts.map(context => context.groupId));
597

I
isidor 已提交
598
			return Promise.all(groupIds.map(groupId => {
I
isidor 已提交
599
				const group = editorGroupService.getGroup(groupId);
B
Benjamin Pasero 已提交
600 601 602 603 604 605 606 607
				if (group) {
					const editors = contexts
						.filter(context => context.groupId === groupId)
						.map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor);
					const editorsToClose = group.editors.filter(e => editors.indexOf(e) === -1);

					return group.closeEditors(editorsToClose);
				}
608

B
Benjamin Pasero 已提交
609
				return Promise.resolve();
I
isidor 已提交
610
			}));
611 612 613 614 615
		}
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID,
616
		weight: KeybindingWeight.WorkbenchContrib,
R
Rob Lourens 已提交
617 618
		when: undefined,
		primary: undefined,
619
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
620
			const editorGroupService = accessor.get(IEditorGroupsService);
621

622
			const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
623 624
			if (group && editor) {
				return group.closeEditors({ direction: CloseDirection.RIGHT, except: editor });
625 626
			}

B
Benjamin Pasero 已提交
627
			return Promise.resolve(false);
628 629 630 631 632
		}
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: KEEP_EDITOR_COMMAND_ID,
633
		weight: KeybindingWeight.WorkbenchContrib,
R
Rob Lourens 已提交
634
		when: undefined,
635
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter),
636
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
637
			const editorGroupService = accessor.get(IEditorGroupsService);
638

639
			const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
640 641
			if (group && editor) {
				return group.pinEditor(editor);
642 643
			}

B
Benjamin Pasero 已提交
644
			return Promise.resolve(false);
645 646
		}
	});
B
Benjamin Pasero 已提交
647 648 649

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: SHOW_EDITORS_IN_GROUP,
650
		weight: KeybindingWeight.WorkbenchContrib,
R
Rob Lourens 已提交
651 652
		when: undefined,
		primary: undefined,
653
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
654
			const editorGroupService = accessor.get(IEditorGroupsService);
B
Benjamin Pasero 已提交
655 656
			const quickOpenService = accessor.get(IQuickOpenService);

B
Benjamin Pasero 已提交
657
			if (editorGroupService.count <= 1) {
B
Benjamin Pasero 已提交
658 659 660
				return quickOpenService.show(NAVIGATE_ALL_EDITORS_GROUP_PREFIX);
			}

661 662
			const commandsContext = getCommandsContext(resourceOrContext, context);
			if (commandsContext && typeof commandsContext.groupId === 'number') {
B
Benjamin Pasero 已提交
663 664 665 666
				const group = editorGroupService.getGroup(commandsContext.groupId);
				if (group) {
					editorGroupService.activateGroup(group); // we need the group to be active
				}
B
Benjamin Pasero 已提交
667
			}
B
Benjamin Pasero 已提交
668

B
Benjamin Pasero 已提交
669
			return quickOpenService.show(NAVIGATE_IN_ACTIVE_GROUP_PREFIX);
B
Benjamin Pasero 已提交
670 671
		}
	});
672

673
	CommandsRegistry.registerCommand(CLOSE_EDITORS_AND_GROUP_COMMAND_ID, async (accessor: ServicesAccessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
674 675 676 677
		const editorGroupService = accessor.get(IEditorGroupsService);

		const { group } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
		if (group) {
678
			await group.closeAllEditors();
679

680 681 682 683
			if (group.count === 0 && editorGroupService.getGroup(group.id) /* could be gone by now */) {
				editorGroupService.removeGroup(group); // only remove group if it is now empty
			}
		}
684
	});
685
}
686

M
Matt Bierner 已提交
687
function getCommandsContext(resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext): IEditorCommandsContext | undefined {
688 689 690 691 692 693 694 695
	if (URI.isUri(resourceOrContext)) {
		return context;
	}

	if (resourceOrContext && typeof resourceOrContext.groupId === 'number') {
		return resourceOrContext;
	}

I
polish  
isidor 已提交
696 697 698 699
	if (context && typeof context.groupId === 'number') {
		return context;
	}

R
Rob Lourens 已提交
700
	return undefined;
701 702
}

703
function resolveCommandsContext(editorGroupService: IEditorGroupsService, context?: IEditorCommandsContext): { group: IEditorGroup, editor?: IEditorInput, control?: IEditor } {
704 705 706

	// Resolve from context
	let group = context && typeof context.groupId === 'number' ? editorGroupService.getGroup(context.groupId) : undefined;
M
Matt Bierner 已提交
707
	let editor = group && context && typeof context.editorIndex === 'number' ? types.withNullAsUndefined(group.getEditor(context.editorIndex)) : undefined;
708 709 710 711 712 713 714 715 716
	let control = group ? group.activeControl : undefined;

	// Fallback to active group as needed
	if (!group) {
		group = editorGroupService.activeGroup;
		editor = <EditorInput>group.activeEditor;
		control = group.activeControl;
	}

M
Matt Bierner 已提交
717
	return { group, editor, control };
718 719
}

M
Matt Bierner 已提交
720
export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsContext | undefined, listService: IListService, editorGroupService: IEditorGroupsService): IEditorCommandsContext[] {
B
Benjamin Pasero 已提交
721

B
Benjamin Pasero 已提交
722 723
	// First check for a focused list to return the selected items from
	const list = listService.lastFocusedList;
J
Joao Moreno 已提交
724
	if (list instanceof List && list.getHTMLElement() === document.activeElement) {
B
Benjamin Pasero 已提交
725 726
		const elementToContext = (element: IEditorIdentifier | IEditorGroup) => {
			if (isEditorGroup(element)) {
R
Rob Lourens 已提交
727
				return { groupId: element.id, editorIndex: undefined };
B
Benjamin Pasero 已提交
728 729
			}

B
Benjamin Pasero 已提交
730 731 732
			const group = editorGroupService.getGroup(element.groupId);

			return { groupId: element.groupId, editorIndex: group ? group.getIndexOfEditor(element.editor) : -1 };
B
Benjamin Pasero 已提交
733
		};
I
isidor 已提交
734

B
Benjamin Pasero 已提交
735 736
		const onlyEditorGroupAndEditor = (e: IEditorIdentifier | IEditorGroup) => isEditorGroup(e) || isEditorIdentifier(e);

737
		const focusedElements: Array<IEditorIdentifier | IEditorGroup> = list.getFocusedElements().filter(onlyEditorGroupAndEditor);
R
Rob Lourens 已提交
738
		const focus = editorContext ? editorContext : focusedElements.length ? focusedElements.map(elementToContext)[0] : undefined; // need to take into account when editor context is { group: group }
739 740

		if (focus) {
741
			const selection: Array<IEditorIdentifier | IEditorGroup> = list.getSelectedElements().filter(onlyEditorGroupAndEditor);
B
Benjamin Pasero 已提交
742

743
			// Only respect selection if it contains focused element
B
Benjamin Pasero 已提交
744 745 746 747 748 749 750 751
			if (selection && selection.some(s => {
				if (isEditorGroup(s)) {
					return s.id === focus.groupId;
				}

				const group = editorGroupService.getGroup(s.groupId);
				return s.groupId === focus.groupId && (group ? group.getIndexOfEditor(s.editor) : -1) === focus.editorIndex;
			})) {
752 753
				return selection.map(elementToContext);
			}
I
isidor 已提交
754

755
			return [focus];
I
isidor 已提交
756 757 758
		}
	}

B
Benjamin Pasero 已提交
759
	// Otherwise go with passed in context
I
isidor 已提交
760 761
	return !!editorContext ? [editorContext] : [];
}
762

763
function isEditorGroup(thing: unknown): thing is IEditorGroup {
B
Benjamin Pasero 已提交
764 765 766 767 768
	const group = thing as IEditorGroup;

	return group && typeof group.id === 'number' && Array.isArray(group.editors);
}

769
function isEditorIdentifier(thing: unknown): thing is IEditorIdentifier {
B
Benjamin Pasero 已提交
770 771 772 773 774
	const identifier = thing as IEditorIdentifier;

	return identifier && typeof identifier.groupId === 'number';
}

775 776
export function setup(): void {
	registerActiveEditorMoveCommand();
777
	registerEditorGroupsLayoutCommand();
778 779
	registerDiffEditorCommands();
	registerOpenEditorAtIndexCommands();
780
	registerCloseEditorCommands();
B
Benjamin Pasero 已提交
781
	registerFocusEditorGroupAtIndexCommands();
782
	registerSplitEditorCommands();
783
}