editorCommands.ts 28.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 { 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';
B
Benjamin Pasero 已提交
25
import { INotificationService } from 'vs/platform/notification/common/notification';
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

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

B
Benjamin Pasero 已提交
45 46 47 48
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';
49

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

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

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

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

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

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

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

	return true;
};

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

B
Benjamin Pasero 已提交
276 277 278 279 280
	// TODO@Ben remove me after a while
	CommandsRegistry.registerCommand('toggle.diff.editorMode', accessor => {
		toggleDiffSideBySide(accessor);

		accessor.get(INotificationService).warn(nls.localize('diffCommandDeprecation', "Command 'toggle.diff.editorMode' has been deprecated. Please use '{0}' instead.", TOGGLE_DIFF_SIDE_BY_SIDE));
281
	});
282 283 284

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

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

function registerOpenEditorAtIndexCommands(): void {
H
Hao Hu 已提交
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
	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
	});
321 322 323 324 325 326 327

	// 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 已提交
328
			id: OPEN_EDITOR_AT_INDEX_COMMAND_ID + visibleIndex,
329
			weight: KeybindingWeight.WorkbenchContrib,
330 331 332
			when: void 0,
			primary: KeyMod.Alt | toKeyCode(visibleIndex),
			mac: { primary: KeyMod.WinCtrl | toKeyCode(visibleIndex) },
H
Hao Hu 已提交
333
			handler: accessor => openEditorAtIndex(accessor, editorIndex)
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
		});
	}

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

		return void 0;
352
	}
353 354
}

B
Benjamin Pasero 已提交
355 356 357
function registerFocusEditorGroupAtIndexCommands(): void {

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

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

B
Benjamin Pasero 已提交
386 387
				// Focus
				newGroup.focus();
B
Benjamin Pasero 已提交
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 415 416 417 418 419 420
			}
		});
	}

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

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 447 448 449 450 451 452 453 454
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 }) => {
455 456
		CommandsRegistry.registerCommand(id, function (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) {
			splitEditor(accessor.get(IEditorGroupsService), direction, getCommandsContext(resourceOrContext, context));
457 458 459 460 461
		});
	});
}

function registerCloseEditorCommands() {
462

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

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

I
isidor 已提交
477
			return Promise.all(distinct(contexts.map(c => c.groupId)).map(groupId =>
I
isidor 已提交
478 479
				editorGroupService.getGroup(groupId).closeEditors({ savedOnly: true })
			));
480 481 482
		}
	});

I
isidor 已提交
483
	KeybindingsRegistry.registerCommandAndKeybindingRule({
484
		id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID,
485
		weight: KeybindingWeight.WorkbenchContrib,
486
		when: void 0,
I
isidor 已提交
487
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, 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);
491
			const distinctGroupIds = distinct(contexts.map(c => c.groupId));
492

I
isidor 已提交
493 494
			if (distinctGroupIds.length === 0) {
				distinctGroupIds.push(editorGroupService.activeGroup.id);
495 496
			}

I
isidor 已提交
497
			return Promise.all(distinctGroupIds.map(groupId =>
I
isidor 已提交
498 499
				editorGroupService.getGroup(groupId).closeAllEditors()
			));
500 501 502
		}
	});

I
isidor 已提交
503
	KeybindingsRegistry.registerCommandAndKeybindingRule({
I
isidor 已提交
504
		id: CLOSE_EDITOR_COMMAND_ID,
505
		weight: KeybindingWeight.WorkbenchContrib,
506
		when: void 0,
I
isidor 已提交
507 508
		primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
		win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
509
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
510
			const editorGroupService = accessor.get(IEditorGroupsService);
511
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
B
Benjamin Pasero 已提交
512

I
isidor 已提交
513
			const activeGroup = editorGroupService.activeGroup;
B
Benjamin Pasero 已提交
514 515
			if (contexts.length === 0 && activeGroup.activeEditor) {
				contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) });  // active editor as fallback
516
			}
517

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

I
isidor 已提交
520
			return Promise.all(groupIds.map(groupId => {
I
isidor 已提交
521
				const group = editorGroupService.getGroup(groupId);
B
Benjamin Pasero 已提交
522 523 524 525
				const editors = contexts
					.filter(context => context.groupId === groupId)
					.map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor);

I
isidor 已提交
526 527
				return group.closeEditors(editors);
			}));
528
		}
529 530 531 532
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: CLOSE_EDITOR_GROUP_COMMAND_ID,
533
		weight: KeybindingWeight.WorkbenchContrib,
534 535 536
		when: ContextKeyExpr.and(ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext),
		primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
		win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
537
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
538
			const editorGroupService = accessor.get(IEditorGroupsService);
539
			const commandsContext = getCommandsContext(resourceOrContext, context);
540

541
			let group: IEditorGroup;
542 543
			if (commandsContext && typeof commandsContext.groupId === 'number') {
				group = editorGroupService.getGroup(commandsContext.groupId);
544 545 546
			} else {
				group = editorGroupService.activeGroup;
			}
547

548
			editorGroupService.removeGroup(group);
549
		}
550 551
	});

I
isidor 已提交
552
	KeybindingsRegistry.registerCommandAndKeybindingRule({
I
isidor 已提交
553
		id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID,
554
		weight: KeybindingWeight.WorkbenchContrib,
555 556
		when: void 0,
		primary: void 0,
I
isidor 已提交
557
		mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T },
558
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
559
			const editorGroupService = accessor.get(IEditorGroupsService);
560
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
561

B
Benjamin Pasero 已提交
562 563 564
			const activeGroup = editorGroupService.activeGroup;
			if (contexts.length === 0 && activeGroup.activeEditor) {
				contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) });  // active editor as fallback
565 566 567
			}

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

I
isidor 已提交
569
			return Promise.all(groupIds.map(groupId => {
I
isidor 已提交
570
				const group = editorGroupService.getGroup(groupId);
B
Benjamin Pasero 已提交
571 572 573
				const editors = contexts
					.filter(context => context.groupId === groupId)
					.map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor);
I
isidor 已提交
574
				const editorsToClose = group.editors.filter(e => editors.indexOf(e) === -1);
575

I
isidor 已提交
576 577
				return group.closeEditors(editorsToClose);
			}));
578 579 580 581 582
		}
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID,
583
		weight: KeybindingWeight.WorkbenchContrib,
584 585
		when: void 0,
		primary: void 0,
586
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
587
			const editorGroupService = accessor.get(IEditorGroupsService);
588

589
			const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
590 591
			if (group && editor) {
				return group.closeEditors({ direction: CloseDirection.RIGHT, except: editor });
592 593
			}

B
Benjamin Pasero 已提交
594
			return Promise.resolve(false);
595 596 597 598 599
		}
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: KEEP_EDITOR_COMMAND_ID,
600
		weight: KeybindingWeight.WorkbenchContrib,
601 602
		when: void 0,
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter),
603
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
604
			const editorGroupService = accessor.get(IEditorGroupsService);
605

606
			const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
607 608
			if (group && editor) {
				return group.pinEditor(editor);
609 610
			}

B
Benjamin Pasero 已提交
611
			return Promise.resolve(false);
612 613
		}
	});
B
Benjamin Pasero 已提交
614 615 616

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: SHOW_EDITORS_IN_GROUP,
617
		weight: KeybindingWeight.WorkbenchContrib,
B
Benjamin Pasero 已提交
618 619
		when: void 0,
		primary: void 0,
620
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
621
			const editorGroupService = accessor.get(IEditorGroupsService);
B
Benjamin Pasero 已提交
622 623
			const quickOpenService = accessor.get(IQuickOpenService);

B
Benjamin Pasero 已提交
624
			if (editorGroupService.count <= 1) {
B
Benjamin Pasero 已提交
625 626 627
				return quickOpenService.show(NAVIGATE_ALL_EDITORS_GROUP_PREFIX);
			}

628 629 630
			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 已提交
631
			}
B
Benjamin Pasero 已提交
632

B
Benjamin Pasero 已提交
633
			return quickOpenService.show(NAVIGATE_IN_ACTIVE_GROUP_PREFIX);
B
Benjamin Pasero 已提交
634 635
		}
	});
636 637 638 639 640 641 642 643 644 645 646 647 648 649 650

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

653 654 655 656 657 658 659 660 661
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 已提交
662 663 664 665 666
	if (context && typeof context.groupId === 'number') {
		return context;
	}

	return void 0;
667 668
}

669
function resolveCommandsContext(editorGroupService: IEditorGroupsService, context?: IEditorCommandsContext): { group: IEditorGroup, editor: IEditorInput, control: IEditor } {
670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685

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

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

B
Benjamin Pasero 已提交
688 689
	// First check for a focused list to return the selected items from
	const list = listService.lastFocusedList;
J
Joao Moreno 已提交
690
	if (list instanceof List && list.getHTMLElement() === document.activeElement) {
B
Benjamin Pasero 已提交
691 692 693 694 695 696 697
		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 已提交
698

B
Benjamin Pasero 已提交
699 700 701 702
		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 }
703 704

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

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

712
			return [focus];
I
isidor 已提交
713 714 715
		}
	}

B
Benjamin Pasero 已提交
716
	// Otherwise go with passed in context
I
isidor 已提交
717 718
	return !!editorContext ? [editorContext] : [];
}
719

B
Benjamin Pasero 已提交
720 721 722 723 724 725 726 727 728 729 730 731
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';
}

732 733
export function setup(): void {
	registerActiveEditorMoveCommand();
734
	registerEditorGroupsLayoutCommand();
735 736
	registerDiffEditorCommands();
	registerOpenEditorAtIndexCommands();
737
	registerCloseEditorCommands();
B
Benjamin Pasero 已提交
738
	registerFocusEditorGroupAtIndexCommands();
739
	registerSplitEditorCommands();
740
}