editorCommands.ts 29.3 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, IEditorInputWithOptions, EditorOptions } 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
import { getActiveTextEditorOptions } from 'vs/workbench/browser/parts/editor/editor';
27

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

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

41 42 43 44 45
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 已提交
46
export const NAVIGATE_ALL_EDITORS_GROUP_PREFIX = 'edt ';
B
Benjamin Pasero 已提交
47
export const NAVIGATE_IN_ACTIVE_GROUP_PREFIX = 'edt active ';
48

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

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

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

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

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

	return true;
};

75
function registerActiveEditorMoveCommand(): void {
A
Alex Dima 已提交
76
	KeybindingsRegistry.registerCommandAndKeybindingRule({
77
		id: MOVE_ACTIVE_EDITOR_COMMAND_ID,
S
Sandeep Somavarapu 已提交
78
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
79
		when: EditorContextKeys.editorTextFocus,
S
Sandeep Somavarapu 已提交
80
		primary: null,
81
		handler: (accessor, args: any) => moveActiveEditor(args, accessor),
S
Sandeep Somavarapu 已提交
82
		description: {
83
			description: nls.localize('editorCommand.activeEditorMove.description', "Move the active editor by tabs or groups"),
S
Sandeep Somavarapu 已提交
84 85 86
			args: [
				{
					name: nls.localize('editorCommand.activeEditorMove.arg.name', "Active editor move argument"),
87
					description: nls.localize('editorCommand.activeEditorMove.arg.description', "Argument Properties:\n\t* 'to': String value providing where to move.\n\t* 'by': String value providing the unit for move (by tab or by group).\n\t* 'value': Number value providing how many positions or an absolute position to move."),
S
Sandeep Somavarapu 已提交
88 89 90 91 92 93 94
					constraint: isActiveEditorMoveArg
				}
			]
		}
	});
}

95 96 97 98
function moveActiveEditor(args: ActiveEditorMoveArguments = Object.create(null), accessor: ServicesAccessor): void {
	args.to = args.to || 'right';
	args.by = args.by || 'tab';
	args.value = typeof args.value === 'number' ? args.value : 1;
S
Sandeep Somavarapu 已提交
99

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

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

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

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

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

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

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

197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
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);

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
	// Remember which editor was in which group with associated options
	let groups = editorGroupService.groups;
	const originalFirstGroupEditors = groups[0].editors;
	const mapGroupToEditor: Map<number, IEditorInputWithOptions[]> = new Map();
	groups.forEach((group, index) => {
		const editors: IEditorInputWithOptions[] = [];
		group.editors.forEach((editor, editorIndex) => {
			let options: EditorOptions;
			if (group.isActive(editor)) {
				options = getActiveTextEditorOptions(group);
			} else {
				options = new EditorOptions();
			}

			options.index = editorIndex;
			options.pinned = group.previewEditor !== editor;
			options.inactive = group.activeEditor !== editor;
			options.preserveFocus = true;

			editors.push({ editor, options });
		});

		mapGroupToEditor.set(index, editors);
	});

	// Reduce to one editor group to start building the layout
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 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
	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);
			}
		});
	}

285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
	buildLayout([groups[0]], args.groups, editorGroupService.orientation === GroupOrientation.HORIZONTAL ? GroupDirection.RIGHT : GroupDirection.DOWN);

	// Restore editors as much as possible
	groups = editorGroupService.groups;
	const firstGroup = groups[0];
	const firstGroupEditorsToClose: IEditorInput[] = [];
	groups.forEach((group, index) => {
		if (group === firstGroup) {
			return; // nothing to do here, this group already contains all editors
		}

		const previousEditors = mapGroupToEditor.get(index);
		if (previousEditors) {

			// Restore in group
			group.openEditors(previousEditors);

			// Mark to be deleted in first group unless previously opened
			previousEditors.forEach(({ editor }) => {
				if (originalFirstGroupEditors.indexOf(editor) === -1) {
					firstGroupEditorsToClose.push(editor);
				}
			});
		}
	});

	// Close those editors that were never opened in the first group
	firstGroup.closeEditors(firstGroupEditorsToClose);

	// Restore focus
	editorGroupService.activeGroup.focus();
316 317 318 319 320 321 322 323 324
}

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

325 326 327 328
function registerDiffEditorCommands(): void {
	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: 'workbench.action.compareEditor.nextChange',
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
329
		when: TextCompareEditorVisibleContext,
330 331 332 333 334 335 336
		primary: null,
		handler: accessor => navigateInDiffEditor(accessor, true)
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: 'workbench.action.compareEditor.previousChange',
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
337
		when: TextCompareEditorVisibleContext,
338 339 340 341 342
		primary: null,
		handler: accessor => navigateInDiffEditor(accessor, false)
	});

	function navigateInDiffEditor(accessor: ServicesAccessor, next: boolean): void {
343
		const editorService = accessor.get(IEditorService);
344
		const candidates = [editorService.activeControl, ...editorService.visibleControls].filter(e => e instanceof TextDiffEditor);
345 346 347 348 349 350 351

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

	KeybindingsRegistry.registerCommandAndKeybindingRule({
352 353
		id: TOGGLE_DIFF_INLINE_MODE,
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
354
		when: void 0,
355
		primary: void 0,
356
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
357
			const editorGroupService = accessor.get(IEditorGroupsService);
358

359
			const { control } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
360 361 362 363
			if (control instanceof TextDiffEditor) {
				const widget = control.getControl();
				const isInlineMode = !widget.renderSideBySide;
				widget.updateOptions(<IDiffEditorOptions>{
364 365 366 367
					renderSideBySide: isInlineMode
				});
			}
		}
368
	});
369 370
}

371 372 373 374 375 376 377 378
function getCommandsContext(resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext): IEditorCommandsContext {
	if (URI.isUri(resourceOrContext)) {
		return context;
	}

	return resourceOrContext;
}

379 380 381 382 383 384 385 386 387 388 389 390 391 392
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 => {
393
				const editorService = accessor.get(IEditorService);
394

395 396
				const activeControl = editorService.activeControl;
				if (activeControl) {
397
					const editor = activeControl.group.getEditor(editorIndex);
398
					if (editor) {
B
Benjamin Pasero 已提交
399
						return editorService.openEditor(editor).then(() => void 0);
400 401
					}
				}
402 403

				return void 0;
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
			}
		});
	}

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

		return void 0;
423
	}
424 425
}

B
Benjamin Pasero 已提交
426 427 428 429 430 431 432 433 434 435 436 437
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 => {
438
				const editorGroupService = accessor.get(IEditorGroupsService);
B
Benjamin Pasero 已提交
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
				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 已提交
457
				const newGroup = editorGroupService.addGroup(lastGroup, direction);
B
Benjamin Pasero 已提交
458 459 460

				// 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 已提交
461
				if (lastGroup.activeEditor && (lastGroup.activeEditor as EditorInput).supportsSplitEditor()) {
B
Benjamin Pasero 已提交
462 463
					lastGroup.copyEditor(lastGroup.activeEditor, newGroup);
				}
B
Benjamin Pasero 已提交
464 465 466

				// Focus
				newGroup.focus();
B
Benjamin Pasero 已提交
467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499
			}
		});
	}

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

500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533
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 }) => {
534 535
		CommandsRegistry.registerCommand(id, function (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) {
			splitEditor(accessor.get(IEditorGroupsService), direction, getCommandsContext(resourceOrContext, context));
536 537 538 539 540
		});
	});
}

function registerCloseEditorCommands() {
541

I
isidor 已提交
542
	KeybindingsRegistry.registerCommandAndKeybindingRule({
543
		id: CLOSE_SAVED_EDITORS_COMMAND_ID,
I
isidor 已提交
544
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
545
		when: void 0,
I
isidor 已提交
546
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_U),
547
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
548
			const editorGroupService = accessor.get(IEditorGroupsService);
549
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
I
isidor 已提交
550
			if (contexts.length === 0 && editorGroupService.activeGroup) {
551
				contexts.push({ groupId: editorGroupService.activeGroup.id }); // If command is triggered from the command palette use the active group
I
isidor 已提交
552
			}
553

I
isidor 已提交
554 555 556
			return TPromise.join(distinct(contexts.map(c => c.groupId)).map(groupId =>
				editorGroupService.getGroup(groupId).closeEditors({ savedOnly: true })
			));
557 558 559
		}
	});

I
isidor 已提交
560
	KeybindingsRegistry.registerCommandAndKeybindingRule({
561
		id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID,
I
isidor 已提交
562
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
563
		when: void 0,
I
isidor 已提交
564
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W),
565
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
566
			const editorGroupService = accessor.get(IEditorGroupsService);
567
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
568
			const distinctGroupIds = distinct(contexts.map(c => c.groupId));
569

I
isidor 已提交
570 571
			if (distinctGroupIds.length === 0) {
				distinctGroupIds.push(editorGroupService.activeGroup.id);
572 573
			}

I
isidor 已提交
574 575 576
			return TPromise.join(distinctGroupIds.map(groupId =>
				editorGroupService.getGroup(groupId).closeAllEditors()
			));
577 578 579
		}
	});

I
isidor 已提交
580
	KeybindingsRegistry.registerCommandAndKeybindingRule({
I
isidor 已提交
581
		id: CLOSE_EDITOR_COMMAND_ID,
I
isidor 已提交
582
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
583
		when: void 0,
I
isidor 已提交
584 585
		primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
		win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
586
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
587
			const editorGroupService = accessor.get(IEditorGroupsService);
588
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
I
isidor 已提交
589 590 591
			const activeGroup = editorGroupService.activeGroup;
			if (contexts.length === 0 && activeGroup && activeGroup.activeEditor) {
				contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) });
592
			}
593

I
isidor 已提交
594 595 596 597 598 599
			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);
			}));
600
		}
601 602 603 604 605 606 607 608
	});

	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] },
609
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
610
			const editorGroupService = accessor.get(IEditorGroupsService);
611
			const commandsContext = getCommandsContext(resourceOrContext, context);
612

613
			let group: IEditorGroup;
614 615
			if (commandsContext && typeof commandsContext.groupId === 'number') {
				group = editorGroupService.getGroup(commandsContext.groupId);
616 617 618
			} else {
				group = editorGroupService.activeGroup;
			}
619

620
			editorGroupService.removeGroup(group);
621
		}
622 623
	});

I
isidor 已提交
624
	KeybindingsRegistry.registerCommandAndKeybindingRule({
I
isidor 已提交
625
		id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID,
I
isidor 已提交
626
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
627 628
		when: void 0,
		primary: void 0,
I
isidor 已提交
629
		mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T },
630
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
631
			const editorGroupService = accessor.get(IEditorGroupsService);
632
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
633

634 635
			if (contexts.length === 0) {
				// Cover the case when run from command palette
I
isidor 已提交
636 637 638
				const activeGroup = editorGroupService.activeGroup;
				if (activeGroup && activeGroup.activeEditor) {
					contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) });
639 640 641 642
				}
			}

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

I
isidor 已提交
644 645 646 647
			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);
648

I
isidor 已提交
649 650
				return group.closeEditors(editorsToClose);
			}));
651 652 653 654 655 656 657 658
		}
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID,
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
		when: void 0,
		primary: void 0,
659
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
660
			const editorGroupService = accessor.get(IEditorGroupsService);
661

662
			const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
663 664
			if (group && editor) {
				return group.closeEditors({ direction: CloseDirection.RIGHT, except: editor });
665 666
			}

667 668 669 670 671 672 673 674 675
			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),
676
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
677
			const editorGroupService = accessor.get(IEditorGroupsService);
678

679
			const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
680 681
			if (group && editor) {
				return group.pinEditor(editor);
682 683
			}

I
isidor 已提交
684
			return TPromise.as(false);
685 686
		}
	});
B
Benjamin Pasero 已提交
687 688 689 690 691 692

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

B
Benjamin Pasero 已提交
697
			if (editorGroupService.count <= 1) {
B
Benjamin Pasero 已提交
698 699 700
				return quickOpenService.show(NAVIGATE_ALL_EDITORS_GROUP_PREFIX);
			}

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

B
Benjamin Pasero 已提交
706
			return quickOpenService.show(NAVIGATE_IN_ACTIVE_GROUP_PREFIX);
B
Benjamin Pasero 已提交
707 708
		}
	});
709
}
710

711
function resolveCommandsContext(editorGroupService: IEditorGroupsService, context?: IEditorCommandsContext): { group: IEditorGroup, editor: IEditorInput, control: IEditor } {
712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727

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

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

B
Benjamin Pasero 已提交
730 731
	// First check for a focused list to return the selected items from
	const list = listService.lastFocusedList;
I
isidor 已提交
732
	if (list instanceof List && list.isDOMFocused()) {
B
Benjamin Pasero 已提交
733 734 735 736 737 738 739
		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 已提交
740

B
Benjamin Pasero 已提交
741 742 743 744
		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 }
745 746

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

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

754
			return [focus];
I
isidor 已提交
755 756 757
		}
	}

B
Benjamin Pasero 已提交
758
	// Otherwise go with passed in context
I
isidor 已提交
759 760
	return !!editorContext ? [editorContext] : [];
}
761

B
Benjamin Pasero 已提交
762 763 764 765 766 767 768 769 770 771 772 773
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';
}

774 775
export function setup(): void {
	registerActiveEditorMoveCommand();
776
	registerEditorGroupsLayoutCommand();
777 778
	registerDiffEditorCommands();
	registerOpenEditorAtIndexCommands();
779
	registerCloseEditorCommands();
B
Benjamin Pasero 已提交
780
	registerFocusEditorGroupAtIndexCommands();
781
	registerSplitEditorCommands();
782
}