editorCommands.ts 28.0 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 { 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';
I
isidor 已提交
19
import { distinct } from 'vs/base/common/arrays';
20
import { IEditorGroupsService, IEditorGroup, GroupDirection, GroupLocation, GroupsOrder, preferredSideBySideGroupDirection, EditorGroupLayout } from 'vs/workbench/services/group/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
	to?: 'first' | 'last' | 'left' | 'right' | 'up' | 'down' | 'center' | 'position' | 'previous' | 'next';
56 57
	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."),
S
Sandeep Somavarapu 已提交
93 94 95 96 97 98 99
					constraint: isActiveEditorMoveArg
				}
			]
		}
	});
}

100 101 102 103
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 已提交
104

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

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

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

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

	const sourceGroup = control.group;
149
	let targetGroup: IEditorGroup;
150

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

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

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

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

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

		editorGroupService.mergeGroup(group, target);
	});
225 226
}

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

	KeybindingsRegistry.registerCommandAndKeybindingRule({
237
		id: GOTO_PREVIOUS_CHANGE,
238
		weight: KeybindingWeight.WorkbenchContrib,
239
		when: TextCompareEditorVisibleContext,
240
		primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F5,
241 242 243 244
		handler: accessor => navigateInDiffEditor(accessor, false)
	});

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

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

B
Benjamin Pasero 已提交
253 254 255 256 257 258 259
	function toggleDiffSideBySide(accessor: ServicesAccessor): void {
		const configurationService = accessor.get(IConfigurationService);

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

260 261 262 263 264 265 266
	function toggleDiffIgnoreTrimWhitespace(accessor: ServicesAccessor): void {
		const configurationService = accessor.get(IConfigurationService);

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

267
	KeybindingsRegistry.registerCommandAndKeybindingRule({
B
Benjamin Pasero 已提交
268
		id: TOGGLE_DIFF_SIDE_BY_SIDE,
269
		weight: KeybindingWeight.WorkbenchContrib,
270
		when: void 0,
271
		primary: void 0,
B
Benjamin Pasero 已提交
272 273
		handler: accessor => toggleDiffSideBySide(accessor)
	});
274

275 276
	MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
		command: {
B
Benjamin Pasero 已提交
277
			id: TOGGLE_DIFF_SIDE_BY_SIDE,
B
Benjamin Pasero 已提交
278 279 280 281 282
			title: {
				value: nls.localize('toggleInlineView', "Toggle Inline View"),
				original: 'Compare: Toggle Inline View'
			},
			category: nls.localize('compare', "Compare")
283 284 285
		},
		when: ContextKeyExpr.has('textCompareEditorActive')
	});
286 287 288 289 290 291 292 293

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE,
		weight: KeybindingWeight.WorkbenchContrib,
		when: void 0,
		primary: void 0,
		handler: accessor => toggleDiffIgnoreTrimWhitespace(accessor)
	});
294 295 296
}

function registerOpenEditorAtIndexCommands(): void {
H
Hao Hu 已提交
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
	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
	});
313 314 315 316 317 318 319

	// 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 已提交
320
			id: OPEN_EDITOR_AT_INDEX_COMMAND_ID + visibleIndex,
321
			weight: KeybindingWeight.WorkbenchContrib,
322 323 324
			when: void 0,
			primary: KeyMod.Alt | toKeyCode(visibleIndex),
			mac: { primary: KeyMod.WinCtrl | toKeyCode(visibleIndex) },
H
Hao Hu 已提交
325
			handler: accessor => openEditorAtIndex(accessor, editorIndex)
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
		});
	}

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

		return void 0;
344
	}
345 346
}

B
Benjamin Pasero 已提交
347 348 349
function registerFocusEditorGroupAtIndexCommands(): void {

	// Keybindings to focus a specific group (2-8) in the editor area
350
	for (let groupIndex = 1; groupIndex < 8; groupIndex++) {
B
Benjamin Pasero 已提交
351 352
		KeybindingsRegistry.registerCommandAndKeybindingRule({
			id: toCommandId(groupIndex),
353
			weight: KeybindingWeight.WorkbenchContrib,
B
Benjamin Pasero 已提交
354 355 356
			when: void 0,
			primary: KeyMod.CtrlCmd | toKeyCode(groupIndex),
			handler: accessor => {
357
				const editorGroupService = accessor.get(IEditorGroupsService);
B
Benjamin Pasero 已提交
358 359 360 361 362 363 364 365 366 367
				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 已提交
368
				const groups = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE);
B
Benjamin Pasero 已提交
369 370 371 372 373
				if (groups[groupIndex]) {
					return groups[groupIndex].focus();
				}

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

B
Benjamin Pasero 已提交
378 379
				// Focus
				newGroup.focus();
B
Benjamin Pasero 已提交
380 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
			}
		});
	}

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

413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
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 }) => {
447 448
		CommandsRegistry.registerCommand(id, function (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) {
			splitEditor(accessor.get(IEditorGroupsService), direction, getCommandsContext(resourceOrContext, context));
449 450 451 452 453
		});
	});
}

function registerCloseEditorCommands() {
454

I
isidor 已提交
455
	KeybindingsRegistry.registerCommandAndKeybindingRule({
456
		id: CLOSE_SAVED_EDITORS_COMMAND_ID,
457
		weight: KeybindingWeight.WorkbenchContrib,
458
		when: void 0,
I
isidor 已提交
459
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_U),
460
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
461
			const editorGroupService = accessor.get(IEditorGroupsService);
462
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
B
Benjamin Pasero 已提交
463 464 465 466

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

I
isidor 已提交
469
			return Promise.all(distinct(contexts.map(c => c.groupId)).map(groupId =>
I
isidor 已提交
470 471
				editorGroupService.getGroup(groupId).closeEditors({ savedOnly: true })
			));
472 473 474
		}
	});

I
isidor 已提交
475
	KeybindingsRegistry.registerCommandAndKeybindingRule({
476
		id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID,
477
		weight: KeybindingWeight.WorkbenchContrib,
478
		when: void 0,
I
isidor 已提交
479
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W),
480
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
481
			const editorGroupService = accessor.get(IEditorGroupsService);
482
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
483
			const distinctGroupIds = distinct(contexts.map(c => c.groupId));
484

I
isidor 已提交
485 486
			if (distinctGroupIds.length === 0) {
				distinctGroupIds.push(editorGroupService.activeGroup.id);
487 488
			}

I
isidor 已提交
489
			return Promise.all(distinctGroupIds.map(groupId =>
I
isidor 已提交
490 491
				editorGroupService.getGroup(groupId).closeAllEditors()
			));
492 493 494
		}
	});

I
isidor 已提交
495
	KeybindingsRegistry.registerCommandAndKeybindingRule({
I
isidor 已提交
496
		id: CLOSE_EDITOR_COMMAND_ID,
497
		weight: KeybindingWeight.WorkbenchContrib,
498
		when: void 0,
I
isidor 已提交
499 500
		primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
		win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
501
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
502
			const editorGroupService = accessor.get(IEditorGroupsService);
503
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
B
Benjamin Pasero 已提交
504

I
isidor 已提交
505
			const activeGroup = editorGroupService.activeGroup;
B
Benjamin Pasero 已提交
506 507
			if (contexts.length === 0 && activeGroup.activeEditor) {
				contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) });  // active editor as fallback
508
			}
509

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

I
isidor 已提交
512
			return Promise.all(groupIds.map(groupId => {
I
isidor 已提交
513
				const group = editorGroupService.getGroup(groupId);
B
Benjamin Pasero 已提交
514 515 516 517
				const editors = contexts
					.filter(context => context.groupId === groupId)
					.map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor);

I
isidor 已提交
518 519
				return group.closeEditors(editors);
			}));
520
		}
521 522 523 524
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: CLOSE_EDITOR_GROUP_COMMAND_ID,
525
		weight: KeybindingWeight.WorkbenchContrib,
526 527 528
		when: ContextKeyExpr.and(ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext),
		primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
		win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
529
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
530
			const editorGroupService = accessor.get(IEditorGroupsService);
531
			const commandsContext = getCommandsContext(resourceOrContext, context);
532

533
			let group: IEditorGroup;
534 535
			if (commandsContext && typeof commandsContext.groupId === 'number') {
				group = editorGroupService.getGroup(commandsContext.groupId);
536 537 538
			} else {
				group = editorGroupService.activeGroup;
			}
539

540
			editorGroupService.removeGroup(group);
541
		}
542 543
	});

I
isidor 已提交
544
	KeybindingsRegistry.registerCommandAndKeybindingRule({
I
isidor 已提交
545
		id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID,
546
		weight: KeybindingWeight.WorkbenchContrib,
547 548
		when: void 0,
		primary: void 0,
I
isidor 已提交
549
		mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T },
550
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
551
			const editorGroupService = accessor.get(IEditorGroupsService);
552
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
553

B
Benjamin Pasero 已提交
554 555 556
			const activeGroup = editorGroupService.activeGroup;
			if (contexts.length === 0 && activeGroup.activeEditor) {
				contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) });  // active editor as fallback
557 558 559
			}

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

I
isidor 已提交
561
			return Promise.all(groupIds.map(groupId => {
I
isidor 已提交
562
				const group = editorGroupService.getGroup(groupId);
B
Benjamin Pasero 已提交
563 564 565
				const editors = contexts
					.filter(context => context.groupId === groupId)
					.map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor);
I
isidor 已提交
566
				const editorsToClose = group.editors.filter(e => editors.indexOf(e) === -1);
567

I
isidor 已提交
568 569
				return group.closeEditors(editorsToClose);
			}));
570 571 572 573 574
		}
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID,
575
		weight: KeybindingWeight.WorkbenchContrib,
576 577
		when: void 0,
		primary: void 0,
578
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
579
			const editorGroupService = accessor.get(IEditorGroupsService);
580

581
			const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
582 583
			if (group && editor) {
				return group.closeEditors({ direction: CloseDirection.RIGHT, except: editor });
584 585
			}

B
Benjamin Pasero 已提交
586
			return Promise.resolve(false);
587 588 589 590 591
		}
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: KEEP_EDITOR_COMMAND_ID,
592
		weight: KeybindingWeight.WorkbenchContrib,
593 594
		when: void 0,
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter),
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.pinEditor(editor);
601 602
			}

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

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: SHOW_EDITORS_IN_GROUP,
609
		weight: KeybindingWeight.WorkbenchContrib,
B
Benjamin Pasero 已提交
610 611
		when: void 0,
		primary: void 0,
612
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
613
			const editorGroupService = accessor.get(IEditorGroupsService);
B
Benjamin Pasero 已提交
614 615
			const quickOpenService = accessor.get(IQuickOpenService);

B
Benjamin Pasero 已提交
616
			if (editorGroupService.count <= 1) {
B
Benjamin Pasero 已提交
617 618 619
				return quickOpenService.show(NAVIGATE_ALL_EDITORS_GROUP_PREFIX);
			}

620 621 622
			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 已提交
623
			}
B
Benjamin Pasero 已提交
624

B
Benjamin Pasero 已提交
625
			return quickOpenService.show(NAVIGATE_IN_ACTIVE_GROUP_PREFIX);
B
Benjamin Pasero 已提交
626 627
		}
	});
628 629 630 631 632 633 634 635 636 637 638 639 640 641 642

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

645 646 647 648 649 650 651 652 653
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 已提交
654 655 656 657 658
	if (context && typeof context.groupId === 'number') {
		return context;
	}

	return void 0;
659 660
}

661
function resolveCommandsContext(editorGroupService: IEditorGroupsService, context?: IEditorCommandsContext): { group: IEditorGroup, editor: IEditorInput, control: IEditor } {
662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677

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

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

B
Benjamin Pasero 已提交
680 681
	// First check for a focused list to return the selected items from
	const list = listService.lastFocusedList;
J
Joao Moreno 已提交
682
	if (list instanceof List && list.getHTMLElement() === document.activeElement) {
B
Benjamin Pasero 已提交
683 684 685 686 687 688 689
		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 已提交
690

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

693
		const focusedElements: Array<IEditorIdentifier | IEditorGroup> = list.getFocusedElements().filter(onlyEditorGroupAndEditor);
B
Benjamin Pasero 已提交
694
		const focus = editorContext ? editorContext : focusedElements.length ? focusedElements.map(elementToContext)[0] : void 0; // need to take into account when editor context is { group: group }
695 696

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

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

704
			return [focus];
I
isidor 已提交
705 706 707
		}
	}

B
Benjamin Pasero 已提交
708
	// Otherwise go with passed in context
I
isidor 已提交
709 710
	return !!editorContext ? [editorContext] : [];
}
711

B
Benjamin Pasero 已提交
712 713 714 715 716 717 718 719 720 721 722 723
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';
}

724 725
export function setup(): void {
	registerActiveEditorMoveCommand();
726
	registerEditorGroupsLayoutCommand();
727 728
	registerDiffEditorCommands();
	registerOpenEditorAtIndexCommands();
729
	registerCloseEditorCommands();
B
Benjamin Pasero 已提交
730
	registerFocusEditorGroupAtIndexCommands();
731
	registerSplitEditorCommands();
732
}