editorCommands.ts 26.7 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 } 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 { TPromise } from 'vs/base/common/winjs.base';
16
import URI from 'vs/base/common/uri';
B
Benjamin Pasero 已提交
17
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
18
import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions';
I
isidor 已提交
19 20
import { IListService } from 'vs/platform/list/browser/listService';
import { List } from 'vs/base/browser/ui/list/listWidget';
I
isidor 已提交
21
import { distinct } from 'vs/base/common/arrays';
22
import { IEditorGroupsService, IEditorGroup, GroupDirection, GroupLocation, GroupsOrder, preferredSideBySideGroupDirection, EditorGroupLayout } from 'vs/workbench/services/group/common/editorGroupsService';
23
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
B
Benjamin Pasero 已提交
24
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
25
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
26

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

export const MOVE_ACTIVE_EDITOR_COMMAND_ID = 'moveActiveEditor';
36
export const LAYOUT_EDITOR_GROUPS_COMMAND_ID = 'layoutEditorGroups';
37
export const KEEP_EDITOR_COMMAND_ID = 'workbench.action.keepEditor';
B
Benjamin Pasero 已提交
38
export const SHOW_EDITORS_IN_GROUP = 'workbench.action.showEditorsInGroup';
39
export const TOGGLE_DIFF_INLINE_MODE = 'toggle.diff.editorMode';
B
Benjamin Pasero 已提交
40

B
Benjamin Pasero 已提交
41 42 43 44
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';
45

B
Benjamin Pasero 已提交
46
export const NAVIGATE_ALL_EDITORS_GROUP_PREFIX = 'edt ';
B
Benjamin Pasero 已提交
47
export const NAVIGATE_IN_ACTIVE_GROUP_PREFIX = 'edt active ';
48

49
export interface ActiveEditorMoveArguments {
50
	to?: 'first' | 'last' | 'left' | 'right' | 'up' | 'down' | 'center' | 'position' | 'previous' | 'next';
51 52
	by?: 'tab' | 'group';
	value?: number;
S
Sandeep Somavarapu 已提交
53 54
}

B
Benjamin Pasero 已提交
55
const isActiveEditorMoveArg = function (arg: ActiveEditorMoveArguments): boolean {
S
Sandeep Somavarapu 已提交
56 57 58 59
	if (!types.isObject(arg)) {
		return false;
	}

60
	if (!types.isString(arg.to)) {
S
Sandeep Somavarapu 已提交
61 62 63
		return false;
	}

64
	if (!types.isUndefined(arg.by) && !types.isString(arg.by)) {
S
Sandeep Somavarapu 已提交
65 66 67
		return false;
	}

68
	if (!types.isUndefined(arg.value) && !types.isNumber(arg.value)) {
S
Sandeep Somavarapu 已提交
69 70 71 72 73 74
		return false;
	}

	return true;
};

75
function registerActiveEditorMoveCommand(): void {
A
Alex Dima 已提交
76
	KeybindingsRegistry.registerCommandAndKeybindingRule({
77
		id: MOVE_ACTIVE_EDITOR_COMMAND_ID,
78
		weight: KeybindingWeight.WorkbenchContrib,
79
		when: EditorContextKeys.editorTextFocus,
S
Sandeep Somavarapu 已提交
80
		primary: null,
81
		handler: (accessor, args: any) => moveActiveEditor(args, accessor),
S
Sandeep Somavarapu 已提交
82
		description: {
83
			description: nls.localize('editorCommand.activeEditorMove.description', "Move the active editor by tabs or groups"),
S
Sandeep Somavarapu 已提交
84 85 86
			args: [
				{
					name: nls.localize('editorCommand.activeEditorMove.arg.name', "Active editor move argument"),
87
					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."),
S
Sandeep Somavarapu 已提交
88 89 90 91 92 93 94
					constraint: isActiveEditorMoveArg
				}
			]
		}
	});
}

95 96 97 98
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 已提交
99

100
	const activeControl = accessor.get(IEditorService).activeControl;
101
	if (activeControl) {
102
		switch (args.by) {
103 104 105 106
			case 'tab':
				return moveActiveTab(args, activeControl, accessor);
			case 'group':
				return moveActiveEditorToGroup(args, activeControl, accessor);
107
		}
S
Sandeep Somavarapu 已提交
108 109 110
	}
}

111 112 113
function moveActiveTab(args: ActiveEditorMoveArguments, control: IEditor, accessor: ServicesAccessor): void {
	const group = control.group;
	let index = group.getIndexOfEditor(control.input);
S
Sandeep Somavarapu 已提交
114
	switch (args.to) {
115
		case 'first':
S
Sandeep Somavarapu 已提交
116 117
			index = 0;
			break;
118
		case 'last':
119
			index = group.count - 1;
S
Sandeep Somavarapu 已提交
120
			break;
121
		case 'left':
122
			index = index - args.value;
S
Sandeep Somavarapu 已提交
123
			break;
124
		case 'right':
125
			index = index + args.value;
S
Sandeep Somavarapu 已提交
126
			break;
127
		case 'center':
128
			index = Math.round(group.count / 2) - 1;
S
Sandeep Somavarapu 已提交
129
			break;
130
		case 'position':
131
			index = args.value - 1;
S
Sandeep Somavarapu 已提交
132 133
			break;
	}
134

135
	index = index < 0 ? 0 : index >= group.count ? group.count - 1 : index;
136
	group.moveEditor(control.input, group, { index });
S
Sandeep Somavarapu 已提交
137 138
}

139
function moveActiveEditorToGroup(args: ActiveEditorMoveArguments, control: IEditor, accessor: ServicesAccessor): void {
140
	const editorGroupService = accessor.get(IEditorGroupsService);
B
Benjamin Pasero 已提交
141
	const configurationService = accessor.get(IConfigurationService);
142 143

	const sourceGroup = control.group;
144
	let targetGroup: IEditorGroup;
145

S
Sandeep Somavarapu 已提交
146
	switch (args.to) {
147
		case 'left':
148 149
			targetGroup = editorGroupService.findGroup({ direction: GroupDirection.LEFT }, sourceGroup);
			if (!targetGroup) {
B
Benjamin Pasero 已提交
150
				targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.LEFT);
151
			}
152 153
			break;
		case 'right':
154 155
			targetGroup = editorGroupService.findGroup({ direction: GroupDirection.RIGHT }, sourceGroup);
			if (!targetGroup) {
B
Benjamin Pasero 已提交
156
				targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.RIGHT);
157
			}
S
Sandeep Somavarapu 已提交
158
			break;
159
		case 'up':
160 161
			targetGroup = editorGroupService.findGroup({ direction: GroupDirection.UP }, sourceGroup);
			if (!targetGroup) {
B
Benjamin Pasero 已提交
162
				targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.UP);
163
			}
S
Sandeep Somavarapu 已提交
164
			break;
165
		case 'down':
166 167
			targetGroup = editorGroupService.findGroup({ direction: GroupDirection.DOWN }, sourceGroup);
			if (!targetGroup) {
B
Benjamin Pasero 已提交
168
				targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.DOWN);
169
			}
S
Sandeep Somavarapu 已提交
170
			break;
171
		case 'first':
172
			targetGroup = editorGroupService.findGroup({ location: GroupLocation.FIRST }, sourceGroup);
S
Sandeep Somavarapu 已提交
173
			break;
174
		case 'last':
175 176 177 178 179 180 181
			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 已提交
182 183 184
			if (!targetGroup) {
				targetGroup = editorGroupService.addGroup(sourceGroup, preferredSideBySideGroupDirection(configurationService));
			}
S
Sandeep Somavarapu 已提交
185
			break;
186
		case 'center':
B
Benjamin Pasero 已提交
187
			targetGroup = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)[(editorGroupService.count / 2) - 1];
188 189
			break;
		case 'position':
B
Benjamin Pasero 已提交
190
			targetGroup = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)[args.value - 1];
S
Sandeep Somavarapu 已提交
191 192
			break;
	}
193

194 195
	if (targetGroup) {
		sourceGroup.moveEditor(control.input, targetGroup);
B
Benjamin Pasero 已提交
196
		targetGroup.focus();
197
	}
198 199
}

200
function registerEditorGroupsLayoutCommand(): void {
201 202 203 204
	CommandsRegistry.registerCommand(LAYOUT_EDITOR_GROUPS_COMMAND_ID, (accessor: ServicesAccessor, args: EditorGroupLayout) => {
		if (!args || typeof args !== 'object') {
			return;
		}
205

206 207 208
		const editorGroupService = accessor.get(IEditorGroupsService);
		editorGroupService.applyLayout(args);
	});
209 210
}

211 212
export function mergeAllGroups(editorGroupService: IEditorGroupsService): void {
	const target = editorGroupService.activeGroup;
213
	editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).forEach(group => {
214 215 216 217 218 219
		if (group === target) {
			return; // keep target
		}

		editorGroupService.mergeGroup(group, target);
	});
220 221
}

222 223 224
function registerDiffEditorCommands(): void {
	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: 'workbench.action.compareEditor.nextChange',
225
		weight: KeybindingWeight.WorkbenchContrib,
226
		when: TextCompareEditorVisibleContext,
227 228 229 230 231 232
		primary: null,
		handler: accessor => navigateInDiffEditor(accessor, true)
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: 'workbench.action.compareEditor.previousChange',
233
		weight: KeybindingWeight.WorkbenchContrib,
234
		when: TextCompareEditorVisibleContext,
235 236 237 238 239
		primary: null,
		handler: accessor => navigateInDiffEditor(accessor, false)
	});

	function navigateInDiffEditor(accessor: ServicesAccessor, next: boolean): void {
240
		const editorService = accessor.get(IEditorService);
241
		const candidates = [editorService.activeControl, ...editorService.visibleControls].filter(e => e instanceof TextDiffEditor);
242 243 244 245 246 247 248

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

	KeybindingsRegistry.registerCommandAndKeybindingRule({
249
		id: TOGGLE_DIFF_INLINE_MODE,
250
		weight: KeybindingWeight.WorkbenchContrib,
251
		when: void 0,
252
		primary: void 0,
253
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
254
			const editorGroupService = accessor.get(IEditorGroupsService);
255

256
			const { control } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
257 258 259 260
			if (control instanceof TextDiffEditor) {
				const widget = control.getControl();
				const isInlineMode = !widget.renderSideBySide;
				widget.updateOptions(<IDiffEditorOptions>{
261 262 263 264
					renderSideBySide: isInlineMode
				});
			}
		}
265
	});
266 267 268 269 270 271 272 273 274 275 276
}

function registerOpenEditorAtIndexCommands(): void {

	// 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({
			id: 'workbench.action.openEditorAtIndex' + visibleIndex,
277
			weight: KeybindingWeight.WorkbenchContrib,
278 279 280 281
			when: void 0,
			primary: KeyMod.Alt | toKeyCode(visibleIndex),
			mac: { primary: KeyMod.WinCtrl | toKeyCode(visibleIndex) },
			handler: accessor => {
282
				const editorService = accessor.get(IEditorService);
283

284 285
				const activeControl = editorService.activeControl;
				if (activeControl) {
286
					const editor = activeControl.group.getEditor(editorIndex);
287
					if (editor) {
B
Benjamin Pasero 已提交
288
						return editorService.openEditor(editor).then(() => void 0);
289 290
					}
				}
291 292

				return void 0;
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
			}
		});
	}

	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;
		}
310 311

		return void 0;
312
	}
313 314
}

B
Benjamin Pasero 已提交
315 316 317
function registerFocusEditorGroupAtIndexCommands(): void {

	// Keybindings to focus a specific group (2-8) in the editor area
318
	for (let groupIndex = 1; groupIndex < 8; groupIndex++) {
B
Benjamin Pasero 已提交
319 320
		KeybindingsRegistry.registerCommandAndKeybindingRule({
			id: toCommandId(groupIndex),
321
			weight: KeybindingWeight.WorkbenchContrib,
B
Benjamin Pasero 已提交
322 323 324
			when: void 0,
			primary: KeyMod.CtrlCmd | toKeyCode(groupIndex),
			handler: accessor => {
325
				const editorGroupService = accessor.get(IEditorGroupsService);
B
Benjamin Pasero 已提交
326 327 328 329 330 331 332 333 334 335
				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 已提交
336
				const groups = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE);
B
Benjamin Pasero 已提交
337 338 339 340 341
				if (groups[groupIndex]) {
					return groups[groupIndex].focus();
				}

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

B
Benjamin Pasero 已提交
346 347
				// Focus
				newGroup.focus();
B
Benjamin Pasero 已提交
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
			}
		});
	}

	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';
		}

		return void 0;
	}

	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;
		}

		return void 0;
	}
}

381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
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)
	let editorToCopy: IEditorInput;
	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 }) => {
415 416
		CommandsRegistry.registerCommand(id, function (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) {
			splitEditor(accessor.get(IEditorGroupsService), direction, getCommandsContext(resourceOrContext, context));
417 418 419 420 421
		});
	});
}

function registerCloseEditorCommands() {
422

I
isidor 已提交
423
	KeybindingsRegistry.registerCommandAndKeybindingRule({
424
		id: CLOSE_SAVED_EDITORS_COMMAND_ID,
425
		weight: KeybindingWeight.WorkbenchContrib,
426
		when: void 0,
I
isidor 已提交
427
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_U),
428
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
429
			const editorGroupService = accessor.get(IEditorGroupsService);
430
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
B
Benjamin Pasero 已提交
431 432 433 434

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

I
isidor 已提交
437 438 439
			return TPromise.join(distinct(contexts.map(c => c.groupId)).map(groupId =>
				editorGroupService.getGroup(groupId).closeEditors({ savedOnly: true })
			));
440 441 442
		}
	});

I
isidor 已提交
443
	KeybindingsRegistry.registerCommandAndKeybindingRule({
444
		id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID,
445
		weight: KeybindingWeight.WorkbenchContrib,
446
		when: void 0,
I
isidor 已提交
447
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W),
448
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
449
			const editorGroupService = accessor.get(IEditorGroupsService);
450
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
451
			const distinctGroupIds = distinct(contexts.map(c => c.groupId));
452

I
isidor 已提交
453 454
			if (distinctGroupIds.length === 0) {
				distinctGroupIds.push(editorGroupService.activeGroup.id);
455 456
			}

I
isidor 已提交
457 458 459
			return TPromise.join(distinctGroupIds.map(groupId =>
				editorGroupService.getGroup(groupId).closeAllEditors()
			));
460 461 462
		}
	});

I
isidor 已提交
463
	KeybindingsRegistry.registerCommandAndKeybindingRule({
I
isidor 已提交
464
		id: CLOSE_EDITOR_COMMAND_ID,
465
		weight: KeybindingWeight.WorkbenchContrib,
466
		when: void 0,
I
isidor 已提交
467 468
		primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
		win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
469
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
470
			const editorGroupService = accessor.get(IEditorGroupsService);
471
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
B
Benjamin Pasero 已提交
472

I
isidor 已提交
473
			const activeGroup = editorGroupService.activeGroup;
B
Benjamin Pasero 已提交
474 475
			if (contexts.length === 0 && activeGroup.activeEditor) {
				contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) });  // active editor as fallback
476
			}
477

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

I
isidor 已提交
480 481
			return TPromise.join(groupIds.map(groupId => {
				const group = editorGroupService.getGroup(groupId);
B
Benjamin Pasero 已提交
482 483 484 485
				const editors = contexts
					.filter(context => context.groupId === groupId)
					.map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor);

I
isidor 已提交
486 487
				return group.closeEditors(editors);
			}));
488
		}
489 490 491 492
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: CLOSE_EDITOR_GROUP_COMMAND_ID,
493
		weight: KeybindingWeight.WorkbenchContrib,
494 495 496
		when: ContextKeyExpr.and(ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext),
		primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
		win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
497
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
498
			const editorGroupService = accessor.get(IEditorGroupsService);
499
			const commandsContext = getCommandsContext(resourceOrContext, context);
500

501
			let group: IEditorGroup;
502 503
			if (commandsContext && typeof commandsContext.groupId === 'number') {
				group = editorGroupService.getGroup(commandsContext.groupId);
504 505 506
			} else {
				group = editorGroupService.activeGroup;
			}
507

508
			editorGroupService.removeGroup(group);
509
		}
510 511
	});

I
isidor 已提交
512
	KeybindingsRegistry.registerCommandAndKeybindingRule({
I
isidor 已提交
513
		id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID,
514
		weight: KeybindingWeight.WorkbenchContrib,
515 516
		when: void 0,
		primary: void 0,
I
isidor 已提交
517
		mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T },
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);
521

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

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

I
isidor 已提交
529 530
			return TPromise.join(groupIds.map(groupId => {
				const group = editorGroupService.getGroup(groupId);
B
Benjamin Pasero 已提交
531 532 533
				const editors = contexts
					.filter(context => context.groupId === groupId)
					.map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor);
I
isidor 已提交
534
				const editorsToClose = group.editors.filter(e => editors.indexOf(e) === -1);
535

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

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID,
543
		weight: KeybindingWeight.WorkbenchContrib,
544 545
		when: void 0,
		primary: void 0,
546
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
547
			const editorGroupService = accessor.get(IEditorGroupsService);
548

549
			const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
550 551
			if (group && editor) {
				return group.closeEditors({ direction: CloseDirection.RIGHT, except: editor });
552 553
			}

554 555 556 557 558 559
			return TPromise.as(false);
		}
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: KEEP_EDITOR_COMMAND_ID,
560
		weight: KeybindingWeight.WorkbenchContrib,
561 562
		when: void 0,
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter),
563
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
564
			const editorGroupService = accessor.get(IEditorGroupsService);
565

566
			const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
567 568
			if (group && editor) {
				return group.pinEditor(editor);
569 570
			}

I
isidor 已提交
571
			return TPromise.as(false);
572 573
		}
	});
B
Benjamin Pasero 已提交
574 575 576

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: SHOW_EDITORS_IN_GROUP,
577
		weight: KeybindingWeight.WorkbenchContrib,
B
Benjamin Pasero 已提交
578 579
		when: void 0,
		primary: void 0,
580
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
581
			const editorGroupService = accessor.get(IEditorGroupsService);
B
Benjamin Pasero 已提交
582 583
			const quickOpenService = accessor.get(IQuickOpenService);

B
Benjamin Pasero 已提交
584
			if (editorGroupService.count <= 1) {
B
Benjamin Pasero 已提交
585 586 587
				return quickOpenService.show(NAVIGATE_ALL_EDITORS_GROUP_PREFIX);
			}

588 589 590
			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 已提交
591
			}
B
Benjamin Pasero 已提交
592

B
Benjamin Pasero 已提交
593
			return quickOpenService.show(NAVIGATE_IN_ACTIVE_GROUP_PREFIX);
B
Benjamin Pasero 已提交
594 595
		}
	});
596 597 598 599 600 601 602 603 604 605 606 607 608 609 610

	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
				}
			});
		}

		return void 0;
	});
611
}
612

613 614 615 616 617 618 619 620 621
function getCommandsContext(resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext): IEditorCommandsContext {
	if (URI.isUri(resourceOrContext)) {
		return context;
	}

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

I
polish  
isidor 已提交
622 623 624 625 626
	if (context && typeof context.groupId === 'number') {
		return context;
	}

	return void 0;
627 628
}

629
function resolveCommandsContext(editorGroupService: IEditorGroupsService, context?: IEditorCommandsContext): { group: IEditorGroup, editor: IEditorInput, control: IEditor } {
630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645

	// Resolve from context
	let group = context && typeof context.groupId === 'number' ? editorGroupService.getGroup(context.groupId) : undefined;
	let editor = group && typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : undefined;
	let control = group ? group.activeControl : undefined;

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

	return { group, editor, control };
}

646
export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsContext, listService: IListService, editorGroupService: IEditorGroupsService): IEditorCommandsContext[] {
B
Benjamin Pasero 已提交
647

B
Benjamin Pasero 已提交
648 649
	// First check for a focused list to return the selected items from
	const list = listService.lastFocusedList;
I
isidor 已提交
650
	if (list instanceof List && list.isDOMFocused()) {
B
Benjamin Pasero 已提交
651 652 653 654 655 656 657
		const elementToContext = (element: IEditorIdentifier | IEditorGroup) => {
			if (isEditorGroup(element)) {
				return { groupId: element.id, editorIndex: void 0 };
			}

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

B
Benjamin Pasero 已提交
659 660 661 662
		const onlyEditorGroupAndEditor = (e: IEditorIdentifier | IEditorGroup) => isEditorGroup(e) || isEditorIdentifier(e);

		const focusedElements: (IEditorIdentifier | IEditorGroup)[] = list.getFocusedElements().filter(onlyEditorGroupAndEditor);
		const focus = editorContext ? editorContext : focusedElements.length ? focusedElements.map(elementToContext)[0] : void 0; // need to take into account when editor context is { group: group }
663 664

		if (focus) {
B
Benjamin Pasero 已提交
665 666
			const selection: (IEditorIdentifier | IEditorGroup)[] = list.getSelectedElements().filter(onlyEditorGroupAndEditor);

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

672
			return [focus];
I
isidor 已提交
673 674 675
		}
	}

B
Benjamin Pasero 已提交
676
	// Otherwise go with passed in context
I
isidor 已提交
677 678
	return !!editorContext ? [editorContext] : [];
}
679

B
Benjamin Pasero 已提交
680 681 682 683 684 685 686 687 688 689 690 691
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';
}

692 693
export function setup(): void {
	registerActiveEditorMoveCommand();
694
	registerEditorGroupsLayoutCommand();
695 696
	registerDiffEditorCommands();
	registerOpenEditorAtIndexCommands();
697
	registerCloseEditorCommands();
B
Benjamin Pasero 已提交
698
	registerFocusEditorGroupAtIndexCommands();
699
	registerSplitEditorCommands();
700
}