editorCommands.ts 28.6 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, IActiveEditor } 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: IActiveEditor, 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: IActiveEditor, 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 431 432 433 434 435 436 437 438 439 440 441
export function splitEditor(editorGroupService: IEditorGroupsService, direction: GroupDirection, context?: IEditorCommandsContext): void {
	let sourceGroup: IEditorGroup;
	if (context && typeof context.groupId === 'number') {
		sourceGroup = editorGroupService.getGroup(context.groupId);
	} else {
		sourceGroup = editorGroupService.activeGroup;
	}

	// Add group
	const newGroup = editorGroupService.addGroup(sourceGroup, direction);

	// Split editor (if it can be split)
442
	let editorToCopy: IEditorInput | null;
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
	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 }) => {
464 465
		CommandsRegistry.registerCommand(id, function (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) {
			splitEditor(accessor.get(IEditorGroupsService), direction, getCommandsContext(resourceOrContext, context));
466 467 468 469 470
		});
	});
}

function registerCloseEditorCommands() {
471

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

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

I
isidor 已提交
486
			return Promise.all(distinct(contexts.map(c => c.groupId)).map(groupId =>
I
isidor 已提交
487 488
				editorGroupService.getGroup(groupId).closeEditors({ savedOnly: true })
			));
489 490 491
		}
	});

I
isidor 已提交
492
	KeybindingsRegistry.registerCommandAndKeybindingRule({
493
		id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID,
494
		weight: KeybindingWeight.WorkbenchContrib,
R
Rob Lourens 已提交
495
		when: undefined,
I
isidor 已提交
496
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W),
497
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
498
			const editorGroupService = accessor.get(IEditorGroupsService);
499
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
500
			const distinctGroupIds = distinct(contexts.map(c => c.groupId));
501

I
isidor 已提交
502 503
			if (distinctGroupIds.length === 0) {
				distinctGroupIds.push(editorGroupService.activeGroup.id);
504 505
			}

I
isidor 已提交
506
			return Promise.all(distinctGroupIds.map(groupId =>
I
isidor 已提交
507 508
				editorGroupService.getGroup(groupId).closeAllEditors()
			));
509 510 511
		}
	});

I
isidor 已提交
512
	KeybindingsRegistry.registerCommandAndKeybindingRule({
I
isidor 已提交
513
		id: CLOSE_EDITOR_COMMAND_ID,
514
		weight: KeybindingWeight.WorkbenchContrib,
R
Rob Lourens 已提交
515
		when: undefined,
I
isidor 已提交
516 517
		primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
		win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
518
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
519
			const editorGroupService = accessor.get(IEditorGroupsService);
520
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
B
Benjamin Pasero 已提交
521

I
isidor 已提交
522
			const activeGroup = editorGroupService.activeGroup;
B
Benjamin Pasero 已提交
523 524
			if (contexts.length === 0 && activeGroup.activeEditor) {
				contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) });  // active editor as fallback
525
			}
526

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

I
isidor 已提交
529
			return Promise.all(groupIds.map(groupId => {
I
isidor 已提交
530
				const group = editorGroupService.getGroup(groupId);
531
				const editors = coalesce(contexts
B
Benjamin Pasero 已提交
532
					.filter(context => context.groupId === groupId)
533
					.map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor));
B
Benjamin Pasero 已提交
534

I
isidor 已提交
535 536
				return group.closeEditors(editors);
			}));
537
		}
538 539 540 541
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: CLOSE_EDITOR_GROUP_COMMAND_ID,
542
		weight: KeybindingWeight.WorkbenchContrib,
543 544 545
		when: ContextKeyExpr.and(ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext),
		primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
		win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
546
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
547
			const editorGroupService = accessor.get(IEditorGroupsService);
548
			const commandsContext = getCommandsContext(resourceOrContext, context);
549

550
			let group: IEditorGroup;
551 552
			if (commandsContext && typeof commandsContext.groupId === 'number') {
				group = editorGroupService.getGroup(commandsContext.groupId);
553 554 555
			} else {
				group = editorGroupService.activeGroup;
			}
556

557
			editorGroupService.removeGroup(group);
558
		}
559 560
	});

I
isidor 已提交
561
	KeybindingsRegistry.registerCommandAndKeybindingRule({
I
isidor 已提交
562
		id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID,
563
		weight: KeybindingWeight.WorkbenchContrib,
R
Rob Lourens 已提交
564 565
		when: undefined,
		primary: undefined,
I
isidor 已提交
566
		mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T },
567
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
568
			const editorGroupService = accessor.get(IEditorGroupsService);
569
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
570

B
Benjamin Pasero 已提交
571 572 573
			const activeGroup = editorGroupService.activeGroup;
			if (contexts.length === 0 && activeGroup.activeEditor) {
				contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) });  // active editor as fallback
574 575 576
			}

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

I
isidor 已提交
578
			return Promise.all(groupIds.map(groupId => {
I
isidor 已提交
579
				const group = editorGroupService.getGroup(groupId);
B
Benjamin Pasero 已提交
580 581 582
				const editors = contexts
					.filter(context => context.groupId === groupId)
					.map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor);
I
isidor 已提交
583
				const editorsToClose = group.editors.filter(e => editors.indexOf(e) === -1);
584

I
isidor 已提交
585 586
				return group.closeEditors(editorsToClose);
			}));
587 588 589 590 591
		}
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID,
592
		weight: KeybindingWeight.WorkbenchContrib,
R
Rob Lourens 已提交
593 594
		when: undefined,
		primary: undefined,
595
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
596
			const editorGroupService = accessor.get(IEditorGroupsService);
597

598
			const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
599 600
			if (group && editor) {
				return group.closeEditors({ direction: CloseDirection.RIGHT, except: editor });
601 602
			}

B
Benjamin Pasero 已提交
603
			return Promise.resolve(false);
604 605 606 607 608
		}
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: KEEP_EDITOR_COMMAND_ID,
609
		weight: KeybindingWeight.WorkbenchContrib,
R
Rob Lourens 已提交
610
		when: undefined,
611
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter),
612
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
613
			const editorGroupService = accessor.get(IEditorGroupsService);
614

615
			const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
616 617
			if (group && editor) {
				return group.pinEditor(editor);
618 619
			}

B
Benjamin Pasero 已提交
620
			return Promise.resolve(false);
621 622
		}
	});
B
Benjamin Pasero 已提交
623 624 625

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: SHOW_EDITORS_IN_GROUP,
626
		weight: KeybindingWeight.WorkbenchContrib,
R
Rob Lourens 已提交
627 628
		when: undefined,
		primary: undefined,
629
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
630
			const editorGroupService = accessor.get(IEditorGroupsService);
B
Benjamin Pasero 已提交
631 632
			const quickOpenService = accessor.get(IQuickOpenService);

B
Benjamin Pasero 已提交
633
			if (editorGroupService.count <= 1) {
B
Benjamin Pasero 已提交
634 635 636
				return quickOpenService.show(NAVIGATE_ALL_EDITORS_GROUP_PREFIX);
			}

637 638 639
			const commandsContext = getCommandsContext(resourceOrContext, context);
			if (commandsContext && typeof commandsContext.groupId === 'number') {
				editorGroupService.activateGroup(editorGroupService.getGroup(commandsContext.groupId)); // we need the group to be active
B
Benjamin Pasero 已提交
640
			}
B
Benjamin Pasero 已提交
641

B
Benjamin Pasero 已提交
642
			return quickOpenService.show(NAVIGATE_IN_ACTIVE_GROUP_PREFIX);
B
Benjamin Pasero 已提交
643 644
		}
	});
645 646 647 648 649 650 651 652 653 654 655 656 657

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

		const { group } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
		if (group) {
			return group.closeAllEditors().then(() => {
				if (group.count === 0 && editorGroupService.getGroup(group.id) /* could be gone by now */) {
					editorGroupService.removeGroup(group); // only remove group if it is now empty
				}
			});
		}

R
Rob Lourens 已提交
658
		return undefined;
659
	});
660
}
661

M
Matt Bierner 已提交
662
function getCommandsContext(resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext): IEditorCommandsContext | undefined {
663 664 665 666 667 668 669 670
	if (URI.isUri(resourceOrContext)) {
		return context;
	}

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

I
polish  
isidor 已提交
671 672 673 674
	if (context && typeof context.groupId === 'number') {
		return context;
	}

R
Rob Lourens 已提交
675
	return undefined;
676 677
}

678
function resolveCommandsContext(editorGroupService: IEditorGroupsService, context?: IEditorCommandsContext): { group: IEditorGroup, editor?: IEditorInput, control?: IEditor } {
679 680 681

	// Resolve from context
	let group = context && typeof context.groupId === 'number' ? editorGroupService.getGroup(context.groupId) : undefined;
M
Matt Bierner 已提交
682
	let editor = group && context && typeof context.editorIndex === 'number' ? types.withNullAsUndefined(group.getEditor(context.editorIndex)) : undefined;
683 684 685 686 687 688 689 690 691
	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 已提交
692
	return { group, editor, control };
693 694
}

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

B
Benjamin Pasero 已提交
697 698
	// First check for a focused list to return the selected items from
	const list = listService.lastFocusedList;
J
Joao Moreno 已提交
699
	if (list instanceof List && list.getHTMLElement() === document.activeElement) {
B
Benjamin Pasero 已提交
700 701
		const elementToContext = (element: IEditorIdentifier | IEditorGroup) => {
			if (isEditorGroup(element)) {
R
Rob Lourens 已提交
702
				return { groupId: element.id, editorIndex: undefined };
B
Benjamin Pasero 已提交
703 704 705 706
			}

			return { groupId: element.groupId, editorIndex: editorGroupService.getGroup(element.groupId).getIndexOfEditor(element.editor) };
		};
I
isidor 已提交
707

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

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

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

716
			// Only respect selection if it contains focused element
B
Benjamin Pasero 已提交
717
			if (selection && selection.some(s => isEditorGroup(s) ? s.id === focus.groupId : s.groupId === focus.groupId && editorGroupService.getGroup(s.groupId).getIndexOfEditor(s.editor) === focus.editorIndex)) {
718 719
				return selection.map(elementToContext);
			}
I
isidor 已提交
720

721
			return [focus];
I
isidor 已提交
722 723 724
		}
	}

B
Benjamin Pasero 已提交
725
	// Otherwise go with passed in context
I
isidor 已提交
726 727
	return !!editorContext ? [editorContext] : [];
}
728

B
Benjamin Pasero 已提交
729 730 731 732 733 734 735 736 737 738 739 740
function isEditorGroup(thing: any): thing is IEditorGroup {
	const group = thing as IEditorGroup;

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

function isEditorIdentifier(thing: any): thing is IEditorIdentifier {
	const identifier = thing as IEditorIdentifier;

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

741 742
export function setup(): void {
	registerActiveEditorMoveCommand();
743
	registerEditorGroupsLayoutCommand();
744 745
	registerDiffEditorCommands();
	registerOpenEditorAtIndexCommands();
746
	registerCloseEditorCommands();
B
Benjamin Pasero 已提交
747
	registerFocusEditorGroupAtIndexCommands();
748
	registerSplitEditorCommands();
749
}