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 9
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingsRegistry } 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, preferredGroupDirection, GroupOrientation } from 'vs/workbench/services/group/common/editorGroupsService';
23
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
B
Benjamin Pasero 已提交
24
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
25
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
26

27
export const CLOSE_SAVED_EDITORS_COMMAND_ID = 'workbench.action.closeUnmodifiedEditors';
I
isidor 已提交
28
export const CLOSE_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeEditorsInGroup';
29
export const CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID = 'workbench.action.closeEditorsToTheRight';
I
isidor 已提交
30
export const CLOSE_EDITOR_COMMAND_ID = 'workbench.action.closeActiveEditor';
31
export const CLOSE_EDITOR_GROUP_COMMAND_ID = 'workbench.action.closeEditorGroup';
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
export const TOGGLE_DIFF_INLINE_MODE = 'toggle.diff.editorMode';
B
Benjamin Pasero 已提交
39

40 41 42 43 44
export const SPLIT_EDITOR_UP = 'splitEditor.up';
export const SPLIT_EDITOR_DOWN = 'splitEditor.down';
export const SPLIT_EDITOR_LEFT = 'splitEditor.left';
export const SPLIT_EDITOR_RIGHT = 'splitEditor.right';

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

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

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

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

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

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

	return true;
};

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

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

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

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

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

138
function moveActiveEditorToGroup(args: ActiveEditorMoveArguments, control: IEditor, accessor: ServicesAccessor): void {
139
	const editorGroupService = accessor.get(IEditorGroupsService);
140 141 142

	const groups = editorGroupService.groups;
	const sourceGroup = control.group;
143
	let targetGroup: IEditorGroup;
144

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

190 191
	if (targetGroup) {
		sourceGroup.moveEditor(control.input, targetGroup);
B
Benjamin Pasero 已提交
192
		targetGroup.focus();
193
	}
194 195
}

196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
function registerEditorGroupsLayoutCommand(): void {
	CommandsRegistry.registerCommand(LAYOUT_EDITOR_GROUPS_COMMAND_ID, applyEditorGroupLayout);
}

export interface GroupLayoutArgument {
	size?: number;
	groups?: Array<GroupLayoutArgument>;
}

export interface EditorGroupLayout {
	orientation: GroupOrientation;
	groups: GroupLayoutArgument[];
}

function applyEditorGroupLayout(accessor: ServicesAccessor, args: EditorGroupLayout): void {
	if (!args || typeof args !== 'object') {
		return;
	}

	const editorGroupService = accessor.get(IEditorGroupsService);

	// Reduce to one editor group
	mergeAllGroups(editorGroupService);

	// Apply orientation
	if (typeof args.orientation === 'number') {
		editorGroupService.setGroupOrientation(args.orientation);
	}

	// Build layout
	function buildLayout(groups: IEditorGroup[], descriptions: GroupLayoutArgument[], direction: GroupDirection): void {
		if (descriptions.length === 0) {
			return; // we need at least one group to layout
		}

		// Add a group for each item in the description
		let totalProportions = 0;
		descriptions.forEach((description, index) => {
			if (index > 0) {
				groups.push(editorGroupService.addGroup(groups[index - 1], direction));
			}

			if (typeof description.size === 'number') {
				totalProportions += description.size;
			}
		});

		// Apply proportions if they are valid (sum() === 1)
		if (totalProportions === 1) {
			const totalSize = groups.map(group => editorGroupService.getSize(group)).reduce(((prev, cur) => prev + cur));
			descriptions.forEach((description, index) => {
				editorGroupService.setSize(groups[index], totalSize * description.size);
			});
		}

		// Continue building layout if description.groups is array-type
		descriptions.forEach((description, index) => {
			if (Array.isArray(description.groups)) {
				buildLayout([groups[index]], description.groups, direction === GroupDirection.RIGHT ? GroupDirection.DOWN : GroupDirection.RIGHT);
			}
		});
	}

	buildLayout([editorGroupService.groups[0]], args.groups, editorGroupService.orientation === GroupOrientation.HORIZONTAL ? GroupDirection.RIGHT : GroupDirection.DOWN);
}

export function mergeAllGroups(editorGroupService: IEditorGroupsService): void {
	const firstGroup = editorGroupService.groups[0];
	while (editorGroupService.count > 1) {
		editorGroupService.mergeGroup(editorGroupService.findGroup({ location: GroupLocation.NEXT }, firstGroup), firstGroup);
	}
}

269 270 271 272
function registerDiffEditorCommands(): void {
	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: 'workbench.action.compareEditor.nextChange',
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
273
		when: TextCompareEditorVisibleContext,
274 275 276 277 278 279 280
		primary: null,
		handler: accessor => navigateInDiffEditor(accessor, true)
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: 'workbench.action.compareEditor.previousChange',
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
281
		when: TextCompareEditorVisibleContext,
282 283 284 285 286
		primary: null,
		handler: accessor => navigateInDiffEditor(accessor, false)
	});

	function navigateInDiffEditor(accessor: ServicesAccessor, next: boolean): void {
287
		const editorService = accessor.get(IEditorService);
288
		const candidates = [editorService.activeControl, ...editorService.visibleControls].filter(e => e instanceof TextDiffEditor);
289 290 291 292 293 294 295

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

	KeybindingsRegistry.registerCommandAndKeybindingRule({
296 297
		id: TOGGLE_DIFF_INLINE_MODE,
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
298
		when: void 0,
299
		primary: void 0,
300
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
301
			const editorGroupService = accessor.get(IEditorGroupsService);
302

303
			const { control } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
304 305 306 307
			if (control instanceof TextDiffEditor) {
				const widget = control.getControl();
				const isInlineMode = !widget.renderSideBySide;
				widget.updateOptions(<IDiffEditorOptions>{
308 309 310 311
					renderSideBySide: isInlineMode
				});
			}
		}
312
	});
313 314
}

315 316 317 318 319 320 321 322
function getCommandsContext(resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext): IEditorCommandsContext {
	if (URI.isUri(resourceOrContext)) {
		return context;
	}

	return resourceOrContext;
}

323 324 325 326 327 328 329 330 331 332 333 334 335 336
function registerOpenEditorAtIndexCommands(): void {

	// Keybindings to focus a specific index in the tab folder if tabs are enabled
	for (let i = 0; i < 9; i++) {
		const editorIndex = i;
		const visibleIndex = i + 1;

		KeybindingsRegistry.registerCommandAndKeybindingRule({
			id: 'workbench.action.openEditorAtIndex' + visibleIndex,
			weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
			when: void 0,
			primary: KeyMod.Alt | toKeyCode(visibleIndex),
			mac: { primary: KeyMod.WinCtrl | toKeyCode(visibleIndex) },
			handler: accessor => {
337
				const editorService = accessor.get(IEditorService);
338

339 340
				const activeControl = editorService.activeControl;
				if (activeControl) {
341
					const editor = activeControl.group.getEditor(editorIndex);
342
					if (editor) {
B
Benjamin Pasero 已提交
343
						return editorService.openEditor(editor).then(() => void 0);
344 345
					}
				}
346 347

				return void 0;
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
			}
		});
	}

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

		return void 0;
367
	}
368 369
}

B
Benjamin Pasero 已提交
370 371 372 373 374 375 376 377 378 379 380 381
function registerFocusEditorGroupAtIndexCommands(): void {

	// Keybindings to focus a specific group (2-8) in the editor area
	for (let i = 1; i < 8; i++) {
		const groupIndex = i;

		KeybindingsRegistry.registerCommandAndKeybindingRule({
			id: toCommandId(groupIndex),
			weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
			when: void 0,
			primary: KeyMod.CtrlCmd | toKeyCode(groupIndex),
			handler: accessor => {
382
				const editorGroupService = accessor.get(IEditorGroupsService);
B
Benjamin Pasero 已提交
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
				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
				const groups = editorGroupService.getGroups(GroupsOrder.CREATION_TIME);
				if (groups[groupIndex]) {
					return groups[groupIndex].focus();
				}

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

				// To keep backwards compatibility (pre-grid) we automatically copy the active editor
				// of the last group over to the new group as long as it supports to be split.
B
Benjamin Pasero 已提交
405
				if (lastGroup.activeEditor && (lastGroup.activeEditor as EditorInput).supportsSplitEditor()) {
B
Benjamin Pasero 已提交
406 407
					lastGroup.copyEditor(lastGroup.activeEditor, newGroup);
				}
B
Benjamin Pasero 已提交
408 409 410

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

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

444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477
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 }) => {
478 479
		CommandsRegistry.registerCommand(id, function (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) {
			splitEditor(accessor.get(IEditorGroupsService), direction, getCommandsContext(resourceOrContext, context));
480 481 482 483 484
		});
	});
}

function registerCloseEditorCommands() {
485

I
isidor 已提交
486
	KeybindingsRegistry.registerCommandAndKeybindingRule({
487
		id: CLOSE_SAVED_EDITORS_COMMAND_ID,
I
isidor 已提交
488
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
489
		when: void 0,
I
isidor 已提交
490
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_U),
491
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
492
			const editorGroupService = accessor.get(IEditorGroupsService);
493
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
I
isidor 已提交
494
			if (contexts.length === 0 && editorGroupService.activeGroup) {
495
				contexts.push({ groupId: editorGroupService.activeGroup.id }); // If command is triggered from the command palette use the active group
I
isidor 已提交
496
			}
497

I
isidor 已提交
498 499 500
			return TPromise.join(distinct(contexts.map(c => c.groupId)).map(groupId =>
				editorGroupService.getGroup(groupId).closeEditors({ savedOnly: true })
			));
501 502 503
		}
	});

I
isidor 已提交
504
	KeybindingsRegistry.registerCommandAndKeybindingRule({
505
		id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID,
I
isidor 已提交
506
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
507
		when: void 0,
I
isidor 已提交
508
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, 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);
512
			const distinctGroupIds = distinct(contexts.map(c => c.groupId));
513

I
isidor 已提交
514 515
			if (distinctGroupIds.length === 0) {
				distinctGroupIds.push(editorGroupService.activeGroup.id);
516 517
			}

I
isidor 已提交
518 519 520
			return TPromise.join(distinctGroupIds.map(groupId =>
				editorGroupService.getGroup(groupId).closeAllEditors()
			));
521 522 523
		}
	});

I
isidor 已提交
524
	KeybindingsRegistry.registerCommandAndKeybindingRule({
I
isidor 已提交
525
		id: CLOSE_EDITOR_COMMAND_ID,
I
isidor 已提交
526
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
527
		when: void 0,
I
isidor 已提交
528 529
		primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
		win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
530
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
531
			const editorGroupService = accessor.get(IEditorGroupsService);
532
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
I
isidor 已提交
533 534 535
			const activeGroup = editorGroupService.activeGroup;
			if (contexts.length === 0 && activeGroup && activeGroup.activeEditor) {
				contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) });
536
			}
537

I
isidor 已提交
538 539 540 541 542 543
			const groupIds = distinct(contexts.map(context => context.groupId));
			return TPromise.join(groupIds.map(groupId => {
				const group = editorGroupService.getGroup(groupId);
				const editors = contexts.filter(c => c.groupId === groupId).map(c => group.getEditor(c.editorIndex));
				return group.closeEditors(editors);
			}));
544
		}
545 546 547 548 549 550 551 552
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: CLOSE_EDITOR_GROUP_COMMAND_ID,
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
		when: ContextKeyExpr.and(ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext),
		primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
		win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
553
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
554
			const editorGroupService = accessor.get(IEditorGroupsService);
555
			const commandsContext = getCommandsContext(resourceOrContext, context);
556

557
			let group: IEditorGroup;
558 559
			if (commandsContext && typeof commandsContext.groupId === 'number') {
				group = editorGroupService.getGroup(commandsContext.groupId);
560 561 562
			} else {
				group = editorGroupService.activeGroup;
			}
563

564
			editorGroupService.removeGroup(group);
565
		}
566 567
	});

I
isidor 已提交
568
	KeybindingsRegistry.registerCommandAndKeybindingRule({
I
isidor 已提交
569
		id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID,
I
isidor 已提交
570
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
571 572
		when: void 0,
		primary: void 0,
I
isidor 已提交
573
		mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T },
574
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
575
			const editorGroupService = accessor.get(IEditorGroupsService);
576
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
577

578 579
			if (contexts.length === 0) {
				// Cover the case when run from command palette
I
isidor 已提交
580 581 582
				const activeGroup = editorGroupService.activeGroup;
				if (activeGroup && activeGroup.activeEditor) {
					contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) });
583 584 585 586
				}
			}

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

I
isidor 已提交
588 589 590 591
			return TPromise.join(groupIds.map(groupId => {
				const group = editorGroupService.getGroup(groupId);
				const editors = contexts.filter(c => c.groupId === groupId).map(c => group.getEditor(c.editorIndex));
				const editorsToClose = group.editors.filter(e => editors.indexOf(e) === -1);
592

I
isidor 已提交
593 594
				return group.closeEditors(editorsToClose);
			}));
595 596 597 598 599 600 601 602
		}
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID,
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
		when: void 0,
		primary: void 0,
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.closeEditors({ direction: CloseDirection.RIGHT, except: editor });
609 610
			}

611 612 613 614 615 616 617 618 619
			return TPromise.as(false);
		}
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: KEEP_EDITOR_COMMAND_ID,
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
		when: void 0,
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter),
620
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
621
			const editorGroupService = accessor.get(IEditorGroupsService);
622

623
			const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
624 625
			if (group && editor) {
				return group.pinEditor(editor);
626 627
			}

I
isidor 已提交
628
			return TPromise.as(false);
629 630
		}
	});
B
Benjamin Pasero 已提交
631 632 633 634 635 636

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: SHOW_EDITORS_IN_GROUP,
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
		when: void 0,
		primary: void 0,
637
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
638
			const editorGroupService = accessor.get(IEditorGroupsService);
B
Benjamin Pasero 已提交
639 640
			const quickOpenService = accessor.get(IQuickOpenService);

B
Benjamin Pasero 已提交
641
			if (editorGroupService.count <= 1) {
B
Benjamin Pasero 已提交
642 643 644
				return quickOpenService.show(NAVIGATE_ALL_EDITORS_GROUP_PREFIX);
			}

645 646 647
			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 已提交
648
			}
B
Benjamin Pasero 已提交
649

B
Benjamin Pasero 已提交
650
			return quickOpenService.show(NAVIGATE_IN_ACTIVE_GROUP_PREFIX);
B
Benjamin Pasero 已提交
651 652
		}
	});
653
}
654

655
function resolveCommandsContext(editorGroupService: IEditorGroupsService, context?: IEditorCommandsContext): { group: IEditorGroup, editor: IEditorInput, control: IEditor } {
656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671

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

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

B
Benjamin Pasero 已提交
674 675
	// First check for a focused list to return the selected items from
	const list = listService.lastFocusedList;
I
isidor 已提交
676
	if (list instanceof List && list.isDOMFocused()) {
B
Benjamin Pasero 已提交
677 678 679 680 681 682 683
		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 已提交
684

B
Benjamin Pasero 已提交
685 686 687 688
		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 }
689 690

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

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

698
			return [focus];
I
isidor 已提交
699 700 701
		}
	}

B
Benjamin Pasero 已提交
702
	// Otherwise go with passed in context
I
isidor 已提交
703 704
	return !!editorContext ? [editorContext] : [];
}
705

B
Benjamin Pasero 已提交
706 707 708 709 710 711 712 713 714 715 716 717
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';
}

718 719
export function setup(): void {
	registerActiveEditorMoveCommand();
720
	registerEditorGroupsLayoutCommand();
721 722
	registerDiffEditorCommands();
	registerOpenEditorAtIndexCommands();
723
	registerCloseEditorCommands();
B
Benjamin Pasero 已提交
724
	registerFocusEditorGroupAtIndexCommands();
725
	registerSplitEditorCommands();
726
}