editorCommands.ts 26.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, preferredSideBySideGroupDirection, EditorGroupLayout } from 'vs/workbench/services/group/common/editorGroupsService';
23
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
B
Benjamin Pasero 已提交
24
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
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_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
export const TOGGLE_DIFF_INLINE_MODE = 'toggle.diff.editorMode';
B
Benjamin Pasero 已提交
40

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

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
function registerEditorGroupsLayoutCommand(): void {
198 199 200 201
	CommandsRegistry.registerCommand(LAYOUT_EDITOR_GROUPS_COMMAND_ID, (accessor: ServicesAccessor, args: EditorGroupLayout) => {
		if (!args || typeof args !== 'object') {
			return;
		}
202

203 204 205
		const editorGroupService = accessor.get(IEditorGroupsService);
		editorGroupService.applyLayout(args);
	});
206 207
}

208 209
export function mergeAllGroups(editorGroupService: IEditorGroupsService): void {
	const target = editorGroupService.activeGroup;
210
	editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).forEach(group => {
211 212 213 214 215 216
		if (group === target) {
			return; // keep target
		}

		editorGroupService.mergeGroup(group, target);
	});
217 218
}

219 220 221 222
function registerDiffEditorCommands(): void {
	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: 'workbench.action.compareEditor.nextChange',
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
223
		when: TextCompareEditorVisibleContext,
224 225 226 227 228 229 230
		primary: null,
		handler: accessor => navigateInDiffEditor(accessor, true)
	});

	KeybindingsRegistry.registerCommandAndKeybindingRule({
		id: 'workbench.action.compareEditor.previousChange',
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
231
		when: TextCompareEditorVisibleContext,
232 233 234 235 236
		primary: null,
		handler: accessor => navigateInDiffEditor(accessor, false)
	});

	function navigateInDiffEditor(accessor: ServicesAccessor, next: boolean): void {
237
		const editorService = accessor.get(IEditorService);
238
		const candidates = [editorService.activeControl, ...editorService.visibleControls].filter(e => e instanceof TextDiffEditor);
239 240 241 242 243 244 245

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

	KeybindingsRegistry.registerCommandAndKeybindingRule({
246 247
		id: TOGGLE_DIFF_INLINE_MODE,
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
248
		when: void 0,
249
		primary: void 0,
250
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
251
			const editorGroupService = accessor.get(IEditorGroupsService);
252

253
			const { control } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
254 255 256 257
			if (control instanceof TextDiffEditor) {
				const widget = control.getControl();
				const isInlineMode = !widget.renderSideBySide;
				widget.updateOptions(<IDiffEditorOptions>{
258 259 260 261
					renderSideBySide: isInlineMode
				});
			}
		}
262
	});
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
}

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 => {
279
				const editorService = accessor.get(IEditorService);
280

281 282
				const activeControl = editorService.activeControl;
				if (activeControl) {
283
					const editor = activeControl.group.getEditor(editorIndex);
284
					if (editor) {
B
Benjamin Pasero 已提交
285
						return editorService.openEditor(editor).then(() => void 0);
286 287
					}
				}
288 289

				return void 0;
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
			}
		});
	}

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

		return void 0;
309
	}
310 311
}

B
Benjamin Pasero 已提交
312 313 314
function registerFocusEditorGroupAtIndexCommands(): void {

	// Keybindings to focus a specific group (2-8) in the editor area
315
	for (let groupIndex = 1; groupIndex < 8; groupIndex++) {
B
Benjamin Pasero 已提交
316 317 318 319 320 321
		KeybindingsRegistry.registerCommandAndKeybindingRule({
			id: toCommandId(groupIndex),
			weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
			when: void 0,
			primary: KeyMod.CtrlCmd | toKeyCode(groupIndex),
			handler: accessor => {
322
				const editorGroupService = accessor.get(IEditorGroupsService);
B
Benjamin Pasero 已提交
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
				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
339
				const direction = preferredSideBySideGroupDirection(configurationService);
B
Benjamin Pasero 已提交
340
				const lastGroup = editorGroupService.findGroup({ location: GroupLocation.LAST });
B
Benjamin Pasero 已提交
341
				const newGroup = editorGroupService.addGroup(lastGroup, direction);
B
Benjamin Pasero 已提交
342

B
Benjamin Pasero 已提交
343 344
				// Focus
				newGroup.focus();
B
Benjamin Pasero 已提交
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
			}
		});
	}

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

378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
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 }) => {
412 413
		CommandsRegistry.registerCommand(id, function (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) {
			splitEditor(accessor.get(IEditorGroupsService), direction, getCommandsContext(resourceOrContext, context));
414 415 416 417 418
		});
	});
}

function registerCloseEditorCommands() {
419

I
isidor 已提交
420
	KeybindingsRegistry.registerCommandAndKeybindingRule({
421
		id: CLOSE_SAVED_EDITORS_COMMAND_ID,
I
isidor 已提交
422
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
423
		when: void 0,
I
isidor 已提交
424
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_U),
425
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
426
			const editorGroupService = accessor.get(IEditorGroupsService);
427
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
B
Benjamin Pasero 已提交
428 429 430 431

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

I
isidor 已提交
434 435 436
			return TPromise.join(distinct(contexts.map(c => c.groupId)).map(groupId =>
				editorGroupService.getGroup(groupId).closeEditors({ savedOnly: true })
			));
437 438 439
		}
	});

I
isidor 已提交
440
	KeybindingsRegistry.registerCommandAndKeybindingRule({
441
		id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID,
I
isidor 已提交
442
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
443
		when: void 0,
I
isidor 已提交
444
		primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W),
445
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
446
			const editorGroupService = accessor.get(IEditorGroupsService);
447
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
448
			const distinctGroupIds = distinct(contexts.map(c => c.groupId));
449

I
isidor 已提交
450 451
			if (distinctGroupIds.length === 0) {
				distinctGroupIds.push(editorGroupService.activeGroup.id);
452 453
			}

I
isidor 已提交
454 455 456
			return TPromise.join(distinctGroupIds.map(groupId =>
				editorGroupService.getGroup(groupId).closeAllEditors()
			));
457 458 459
		}
	});

I
isidor 已提交
460
	KeybindingsRegistry.registerCommandAndKeybindingRule({
I
isidor 已提交
461
		id: CLOSE_EDITOR_COMMAND_ID,
I
isidor 已提交
462
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
463
		when: void 0,
I
isidor 已提交
464 465
		primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
		win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
466
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
467
			const editorGroupService = accessor.get(IEditorGroupsService);
468
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
B
Benjamin Pasero 已提交
469

I
isidor 已提交
470
			const activeGroup = editorGroupService.activeGroup;
B
Benjamin Pasero 已提交
471 472
			if (contexts.length === 0 && activeGroup.activeEditor) {
				contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) });  // active editor as fallback
473
			}
474

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

I
isidor 已提交
477 478
			return TPromise.join(groupIds.map(groupId => {
				const group = editorGroupService.getGroup(groupId);
B
Benjamin Pasero 已提交
479 480 481 482
				const editors = contexts
					.filter(context => context.groupId === groupId)
					.map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor);

I
isidor 已提交
483 484
				return group.closeEditors(editors);
			}));
485
		}
486 487 488 489 490 491 492 493
	});

	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] },
494
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
495
			const editorGroupService = accessor.get(IEditorGroupsService);
496
			const commandsContext = getCommandsContext(resourceOrContext, context);
497

498
			let group: IEditorGroup;
499 500
			if (commandsContext && typeof commandsContext.groupId === 'number') {
				group = editorGroupService.getGroup(commandsContext.groupId);
501 502 503
			} else {
				group = editorGroupService.activeGroup;
			}
504

505
			editorGroupService.removeGroup(group);
506
		}
507 508
	});

I
isidor 已提交
509
	KeybindingsRegistry.registerCommandAndKeybindingRule({
I
isidor 已提交
510
		id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID,
I
isidor 已提交
511
		weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
512 513
		when: void 0,
		primary: void 0,
I
isidor 已提交
514
		mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T },
515
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
516
			const editorGroupService = accessor.get(IEditorGroupsService);
517
			const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
518

B
Benjamin Pasero 已提交
519 520 521
			const activeGroup = editorGroupService.activeGroup;
			if (contexts.length === 0 && activeGroup.activeEditor) {
				contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) });  // active editor as fallback
522 523 524
			}

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

I
isidor 已提交
526 527
			return TPromise.join(groupIds.map(groupId => {
				const group = editorGroupService.getGroup(groupId);
B
Benjamin Pasero 已提交
528 529 530
				const editors = contexts
					.filter(context => context.groupId === groupId)
					.map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor);
I
isidor 已提交
531
				const editorsToClose = group.editors.filter(e => editors.indexOf(e) === -1);
532

I
isidor 已提交
533 534
				return group.closeEditors(editorsToClose);
			}));
535 536 537 538 539 540 541 542
		}
	});

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

546
			const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
547 548
			if (group && editor) {
				return group.closeEditors({ direction: CloseDirection.RIGHT, except: editor });
549 550
			}

551 552 553 554 555 556 557 558 559
			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),
560
		handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
561
			const editorGroupService = accessor.get(IEditorGroupsService);
562

563
			const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
564 565
			if (group && editor) {
				return group.pinEditor(editor);
566 567
			}

I
isidor 已提交
568
			return TPromise.as(false);
569 570
		}
	});
B
Benjamin Pasero 已提交
571 572 573 574 575 576

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

B
Benjamin Pasero 已提交
581
			if (editorGroupService.count <= 1) {
B
Benjamin Pasero 已提交
582 583 584
				return quickOpenService.show(NAVIGATE_ALL_EDITORS_GROUP_PREFIX);
			}

585 586 587
			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 已提交
588
			}
B
Benjamin Pasero 已提交
589

B
Benjamin Pasero 已提交
590
			return quickOpenService.show(NAVIGATE_IN_ACTIVE_GROUP_PREFIX);
B
Benjamin Pasero 已提交
591 592
		}
	});
593 594 595 596 597 598 599 600 601 602 603 604 605 606 607

	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;
	});
608
}
609

610 611 612 613 614 615 616 617 618
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 已提交
619 620 621 622 623
	if (context && typeof context.groupId === 'number') {
		return context;
	}

	return void 0;
624 625
}

626
function resolveCommandsContext(editorGroupService: IEditorGroupsService, context?: IEditorCommandsContext): { group: IEditorGroup, editor: IEditorInput, control: IEditor } {
627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642

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

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

B
Benjamin Pasero 已提交
645 646
	// First check for a focused list to return the selected items from
	const list = listService.lastFocusedList;
I
isidor 已提交
647
	if (list instanceof List && list.isDOMFocused()) {
B
Benjamin Pasero 已提交
648 649 650 651 652 653 654
		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 已提交
655

B
Benjamin Pasero 已提交
656 657 658 659
		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 }
660 661

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

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

669
			return [focus];
I
isidor 已提交
670 671 672
		}
	}

B
Benjamin Pasero 已提交
673
	// Otherwise go with passed in context
I
isidor 已提交
674 675
	return !!editorContext ? [editorContext] : [];
}
676

B
Benjamin Pasero 已提交
677 678 679 680 681 682 683 684 685 686 687 688
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';
}

689 690
export function setup(): void {
	registerActiveEditorMoveCommand();
691
	registerEditorGroupsLayoutCommand();
692 693
	registerDiffEditorCommands();
	registerOpenEditorAtIndexCommands();
694
	registerCloseEditorCommands();
B
Benjamin Pasero 已提交
695
	registerFocusEditorGroupAtIndexCommands();
696
	registerSplitEditorCommands();
697
}