editorCommands.ts 27.5 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';
H
Hao Hu 已提交
25
import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands';
26
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
27

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

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

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

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

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

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

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

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

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

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

	return true;
};

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

98 99 100 101
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 已提交
102

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

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

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

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

	const sourceGroup = control.group;
147
	let targetGroup: IEditorGroup;
148

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

197 198
	if (targetGroup) {
		sourceGroup.moveEditor(control.input, targetGroup);
B
Benjamin Pasero 已提交
199
		targetGroup.focus();
200
	}
201 202
}

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

209 210 211
		const editorGroupService = accessor.get(IEditorGroupsService);
		editorGroupService.applyLayout(args);
	});
212 213
}

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

		editorGroupService.mergeGroup(group, target);
	});
223 224
}

225 226 227
function registerDiffEditorCommands(): void {
	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: 'workbench.action.compareEditor.nextChange',
228
		weight: KeybindingWeight.WorkbenchContrib,
229
		when: TextCompareEditorVisibleContext,
230
		primary: KeyMod.Alt | KeyCode.F5,
231 232 233 234 235
		handler: accessor => navigateInDiffEditor(accessor, true)
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: 'workbench.action.compareEditor.previousChange',
236
		weight: KeybindingWeight.WorkbenchContrib,
237
		when: TextCompareEditorVisibleContext,
238
		primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F5,
239 240 241 242
		handler: accessor => navigateInDiffEditor(accessor, false)
	});

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

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

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

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

	MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
		command: {
			id: TOGGLE_DIFF_INLINE_MODE,
B
Benjamin Pasero 已提交
273 274 275 276 277
			title: {
				value: nls.localize('toggleInlineView', "Toggle Inline View"),
				original: 'Compare: Toggle Inline View'
			},
			category: nls.localize('compare', "Compare")
278 279 280
		},
		when: ContextKeyExpr.has('textCompareEditorActive')
	});
281 282 283
}

function registerOpenEditorAtIndexCommands(): void {
H
Hao Hu 已提交
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
	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
	});
300 301 302 303 304 305 306

	// 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 已提交
307
			id: OPEN_EDITOR_AT_INDEX_COMMAND_ID + visibleIndex,
308
			weight: KeybindingWeight.WorkbenchContrib,
309 310 311
			when: void 0,
			primary: KeyMod.Alt | toKeyCode(visibleIndex),
			mac: { primary: KeyMod.WinCtrl | toKeyCode(visibleIndex) },
H
Hao Hu 已提交
312
			handler: accessor => openEditorAtIndex(accessor, editorIndex)
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
		});
	}

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

		return void 0;
331
	}
332 333
}

B
Benjamin Pasero 已提交
334 335 336
function registerFocusEditorGroupAtIndexCommands(): void {

	// Keybindings to focus a specific group (2-8) in the editor area
337
	for (let groupIndex = 1; groupIndex < 8; groupIndex++) {
B
Benjamin Pasero 已提交
338 339
		KeybindingsRegistry.registerCommandAndKeybindingRule({
			id: toCommandId(groupIndex),
340
			weight: KeybindingWeight.WorkbenchContrib,
B
Benjamin Pasero 已提交
341 342 343
			when: void 0,
			primary: KeyMod.CtrlCmd | toKeyCode(groupIndex),
			handler: accessor => {
344
				const editorGroupService = accessor.get(IEditorGroupsService);
B
Benjamin Pasero 已提交
345 346 347 348 349 350 351 352 353 354
				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 已提交
355
				const groups = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE);
B
Benjamin Pasero 已提交
356 357 358 359 360
				if (groups[groupIndex]) {
					return groups[groupIndex].focus();
				}

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

B
Benjamin Pasero 已提交
365 366
				// Focus
				newGroup.focus();
B
Benjamin Pasero 已提交
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
			}
		});
	}

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

400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
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 }) => {
434 435
		CommandsRegistry.registerCommand(id, function (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) {
			splitEditor(accessor.get(IEditorGroupsService), direction, getCommandsContext(resourceOrContext, context));
436 437 438 439 440
		});
	});
}

function registerCloseEditorCommands() {
441

I
isidor 已提交
442
	KeybindingsRegistry.registerCommandAndKeybindingRule({
443
		id: CLOSE_SAVED_EDITORS_COMMAND_ID,
444
		weight: KeybindingWeight.WorkbenchContrib,
445
		when: void 0,
I
isidor 已提交
446
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_U),
447
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
448
			const editorGroupService = accessor.get(IEditorGroupsService);
449
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
B
Benjamin Pasero 已提交
450 451 452 453

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

I
isidor 已提交
456 457 458
			return TPromise.join(distinct(contexts.map(c => c.groupId)).map(groupId =>
				editorGroupService.getGroup(groupId).closeEditors({ savedOnly: true })
			));
459 460 461
		}
	});

I
isidor 已提交
462
	KeybindingsRegistry.registerCommandAndKeybindingRule({
463
		id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID,
464
		weight: KeybindingWeight.WorkbenchContrib,
465
		when: void 0,
I
isidor 已提交
466
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W),
467
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
468
			const editorGroupService = accessor.get(IEditorGroupsService);
469
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
470
			const distinctGroupIds = distinct(contexts.map(c => c.groupId));
471

I
isidor 已提交
472 473
			if (distinctGroupIds.length === 0) {
				distinctGroupIds.push(editorGroupService.activeGroup.id);
474 475
			}

I
isidor 已提交
476 477 478
			return TPromise.join(distinctGroupIds.map(groupId =>
				editorGroupService.getGroup(groupId).closeAllEditors()
			));
479 480 481
		}
	});

I
isidor 已提交
482
	KeybindingsRegistry.registerCommandAndKeybindingRule({
I
isidor 已提交
483
		id: CLOSE_EDITOR_COMMAND_ID,
484
		weight: KeybindingWeight.WorkbenchContrib,
485
		when: void 0,
I
isidor 已提交
486 487
		primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
		win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
488
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
489
			const editorGroupService = accessor.get(IEditorGroupsService);
490
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
B
Benjamin Pasero 已提交
491

I
isidor 已提交
492
			const activeGroup = editorGroupService.activeGroup;
B
Benjamin Pasero 已提交
493 494
			if (contexts.length === 0 && activeGroup.activeEditor) {
				contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) });  // active editor as fallback
495
			}
496

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

I
isidor 已提交
499 500
			return TPromise.join(groupIds.map(groupId => {
				const group = editorGroupService.getGroup(groupId);
B
Benjamin Pasero 已提交
501 502 503 504
				const editors = contexts
					.filter(context => context.groupId === groupId)
					.map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor);

I
isidor 已提交
505 506
				return group.closeEditors(editors);
			}));
507
		}
508 509 510 511
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: CLOSE_EDITOR_GROUP_COMMAND_ID,
512
		weight: KeybindingWeight.WorkbenchContrib,
513 514 515
		when: ContextKeyExpr.and(ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext),
		primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
		win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
516
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
517
			const editorGroupService = accessor.get(IEditorGroupsService);
518
			const commandsContext = getCommandsContext(resourceOrContext, context);
519

520
			let group: IEditorGroup;
521 522
			if (commandsContext && typeof commandsContext.groupId === 'number') {
				group = editorGroupService.getGroup(commandsContext.groupId);
523 524 525
			} else {
				group = editorGroupService.activeGroup;
			}
526

527
			editorGroupService.removeGroup(group);
528
		}
529 530
	});

I
isidor 已提交
531
	KeybindingsRegistry.registerCommandAndKeybindingRule({
I
isidor 已提交
532
		id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID,
533
		weight: KeybindingWeight.WorkbenchContrib,
534 535
		when: void 0,
		primary: void 0,
I
isidor 已提交
536
		mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T },
537
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
538
			const editorGroupService = accessor.get(IEditorGroupsService);
539
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
540

B
Benjamin Pasero 已提交
541 542 543
			const activeGroup = editorGroupService.activeGroup;
			if (contexts.length === 0 && activeGroup.activeEditor) {
				contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) });  // active editor as fallback
544 545 546
			}

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

I
isidor 已提交
548 549
			return TPromise.join(groupIds.map(groupId => {
				const group = editorGroupService.getGroup(groupId);
B
Benjamin Pasero 已提交
550 551 552
				const editors = contexts
					.filter(context => context.groupId === groupId)
					.map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor);
I
isidor 已提交
553
				const editorsToClose = group.editors.filter(e => editors.indexOf(e) === -1);
554

I
isidor 已提交
555 556
				return group.closeEditors(editorsToClose);
			}));
557 558 559 560 561
		}
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID,
562
		weight: KeybindingWeight.WorkbenchContrib,
563 564
		when: void 0,
		primary: void 0,
565
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
566
			const editorGroupService = accessor.get(IEditorGroupsService);
567

568
			const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
569 570
			if (group && editor) {
				return group.closeEditors({ direction: CloseDirection.RIGHT, except: editor });
571 572
			}

573 574 575 576 577 578
			return TPromise.as(false);
		}
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: KEEP_EDITOR_COMMAND_ID,
579
		weight: KeybindingWeight.WorkbenchContrib,
580 581
		when: void 0,
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter),
582
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
583
			const editorGroupService = accessor.get(IEditorGroupsService);
584

585
			const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
586 587
			if (group && editor) {
				return group.pinEditor(editor);
588 589
			}

I
isidor 已提交
590
			return TPromise.as(false);
591 592
		}
	});
B
Benjamin Pasero 已提交
593 594 595

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: SHOW_EDITORS_IN_GROUP,
596
		weight: KeybindingWeight.WorkbenchContrib,
B
Benjamin Pasero 已提交
597 598
		when: void 0,
		primary: void 0,
599
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
600
			const editorGroupService = accessor.get(IEditorGroupsService);
B
Benjamin Pasero 已提交
601 602
			const quickOpenService = accessor.get(IQuickOpenService);

B
Benjamin Pasero 已提交
603
			if (editorGroupService.count <= 1) {
B
Benjamin Pasero 已提交
604 605 606
				return quickOpenService.show(NAVIGATE_ALL_EDITORS_GROUP_PREFIX);
			}

607 608 609
			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 已提交
610
			}
B
Benjamin Pasero 已提交
611

B
Benjamin Pasero 已提交
612
			return quickOpenService.show(NAVIGATE_IN_ACTIVE_GROUP_PREFIX);
B
Benjamin Pasero 已提交
613 614
		}
	});
615 616 617 618 619 620 621 622 623 624 625 626 627 628 629

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

632 633 634 635 636 637 638 639 640
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 已提交
641 642 643 644 645
	if (context && typeof context.groupId === 'number') {
		return context;
	}

	return void 0;
646 647
}

648
function resolveCommandsContext(editorGroupService: IEditorGroupsService, context?: IEditorCommandsContext): { group: IEditorGroup, editor: IEditorInput, control: IEditor } {
649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664

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

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

B
Benjamin Pasero 已提交
667 668
	// First check for a focused list to return the selected items from
	const list = listService.lastFocusedList;
I
isidor 已提交
669
	if (list instanceof List && list.isDOMFocused()) {
B
Benjamin Pasero 已提交
670 671 672 673 674 675 676
		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 已提交
677

B
Benjamin Pasero 已提交
678 679 680 681
		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 }
682 683

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

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

691
			return [focus];
I
isidor 已提交
692 693 694
		}
	}

B
Benjamin Pasero 已提交
695
	// Otherwise go with passed in context
I
isidor 已提交
696 697
	return !!editorContext ? [editorContext] : [];
}
698

B
Benjamin Pasero 已提交
699 700 701 702 703 704 705 706 707 708 709 710
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';
}

711 712
export function setup(): void {
	registerActiveEditorMoveCommand();
713
	registerEditorGroupsLayoutCommand();
714 715
	registerDiffEditorCommands();
	registerOpenEditorAtIndexCommands();
716
	registerCloseEditorCommands();
B
Benjamin Pasero 已提交
717
	registerFocusEditorGroupAtIndexCommands();
718
	registerSplitEditorCommands();
719
}