titleControl.ts 14.1 KB
Newer Older
B
Benjamin Pasero 已提交
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.
 *--------------------------------------------------------------------------------------------*/

'use strict';

8
import 'vs/css!./media/titlecontrol';
9
import { localize } from 'vs/nls';
I
isidor 已提交
10
import { prepareActions } from 'vs/workbench/browser/actions';
I
isidor 已提交
11
import { IAction, Action, IRunEvent } from 'vs/base/common/actions';
12
import { TPromise } from 'vs/base/common/winjs.base';
13
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
14
import * as arrays from 'vs/base/common/arrays';
15
import { toResource, IEditorCommandsContext, IEditorInput } from 'vs/workbench/common/editor';
B
Benjamin Pasero 已提交
16
import { IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
17 18 19 20 21
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
J
Johannes Rieken 已提交
22
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
23
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
24
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
25
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
26
import { CloseOneEditorAction, SplitEditorAction } from 'vs/workbench/browser/parts/editor/editorActions';
27 28
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { createActionItem, fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
29
import { IMenuService, MenuId, IMenu, ExecuteCommandAction } from 'vs/platform/actions/common/actions';
30
import { ResourceContextKey } from 'vs/workbench/common/resources';
31
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
B
Benjamin Pasero 已提交
32
import { Themable } from 'vs/workbench/common/theme';
B
Benjamin Pasero 已提交
33
import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
34
import { INotificationService } from 'vs/platform/notification/common/notification';
B
Benjamin Pasero 已提交
35
import { Dimension, addDisposableListener, EventType } from 'vs/base/browser/dom';
36
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
37 38
import { IEditorGroup } from 'vs/workbench/services/group/common/nextEditorGroupsService';
import { IEditorGroupsAccessor, IEditorPartOptions } from 'vs/workbench/browser/parts/editor/editor';
B
Benjamin Pasero 已提交
39 40 41
import { listActiveSelectionBackground, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry';
import { LocalSelectionTransfer, DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillResourceDataTransfers } from 'vs/workbench/browser/dnd';
import { applyDragImage } from 'vs/base/browser/dnd';
B
Benjamin Pasero 已提交
42 43 44 45 46 47

export interface IToolbarActions {
	primary: IAction[];
	secondary: IAction[];
}

48
export abstract class TitleControl extends Themable {
49

50
	protected closeOneEditorAction: CloseOneEditorAction;
B
Benjamin Pasero 已提交
51

B
Benjamin Pasero 已提交
52 53 54
	protected readonly groupTransfer = LocalSelectionTransfer.getInstance<DraggedEditorGroupIdentifier>();
	protected readonly editorTransfer = LocalSelectionTransfer.getInstance<DraggedEditorIdentifier>();

55 56 57 58
	private currentPrimaryEditorActionIds: string[] = [];
	private currentSecondaryEditorActionIds: string[] = [];
	protected editorActionsToolbar: ToolBar;

59
	private mapEditorToActions: Map<string, IToolbarActions> = new Map();
B
Benjamin Pasero 已提交
60

61
	private resourceContext: ResourceContextKey;
62
	private disposeOnEditorActions: IDisposable[] = [];
63

64
	private splitEditorAction: SplitEditorAction;
65

B
Benjamin Pasero 已提交
66 67
	private contextMenu: IMenu;

B
Benjamin Pasero 已提交
68
	constructor(
69
		parent: HTMLElement,
70 71
		protected accessor: IEditorGroupsAccessor,
		protected group: IEditorGroup,
72
		@IContextMenuService private contextMenuService: IContextMenuService,
B
Benjamin Pasero 已提交
73
		@IInstantiationService protected instantiationService: IInstantiationService,
74 75 76
		@IContextKeyService private contextKeyService: IContextKeyService,
		@IKeybindingService private keybindingService: IKeybindingService,
		@ITelemetryService private telemetryService: ITelemetryService,
77
		@INotificationService private notificationService: INotificationService,
78
		@IMenuService private menuService: IMenuService,
79
		@IQuickOpenService protected quickOpenService: IQuickOpenService,
80
		@IThemeService themeService: IThemeService,
81
		@IExtensionService private extensionService: IExtensionService
B
Benjamin Pasero 已提交
82
	) {
B
Benjamin Pasero 已提交
83
		super(themeService);
84

85
		this.resourceContext = instantiationService.createInstance(ResourceContextKey);
86
		this.contextMenu = this._register(this.menuService.createMenu(MenuId.EditorTitleContext, this.contextKeyService));
B
Benjamin Pasero 已提交
87

88
		this.closeOneEditorAction = this._register(this.instantiationService.createInstance(CloseOneEditorAction, CloseOneEditorAction.ID, CloseOneEditorAction.LABEL));
89
		this.splitEditorAction = this._register(this.instantiationService.createInstance(SplitEditorAction, SplitEditorAction.ID, SplitEditorAction.LABEL));
90

91
		this.create(parent);
92
		this.registerListeners();
93 94
	}

95
	private registerListeners(): void {
96
		this._register(this.extensionService.onDidRegisterExtensions(() => this.updateEditorActionsToolbar()));
B
Benjamin Pasero 已提交
97 98
	}

99
	protected abstract create(parent: HTMLElement): void;
B
Benjamin Pasero 已提交
100

101
	protected createEditorActionsToolBar(container: HTMLElement): void {
102
		this.editorActionsToolbar = this._register(new ToolBar(container, this.contextMenuService, {
103
			actionItemProvider: action => this.actionItemProvider(action as Action),
B
Benjamin Pasero 已提交
104
			orientation: ActionsOrientation.HORIZONTAL,
105
			ariaLabel: localize('araLabelEditorActions', "Editor actions"),
106
			getKeyBinding: action => this.getKeybinding(action)
107
		}));
B
Benjamin Pasero 已提交
108

109 110 111
		// Context
		this.editorActionsToolbar.context = { groupId: this.group.id } as IEditorCommandsContext;

B
Benjamin Pasero 已提交
112
		// Action Run Handling
113
		this._register(this.editorActionsToolbar.actionRunner.onDidRun((e: IRunEvent) => {
B
Benjamin Pasero 已提交
114

115 116
			// Notify for Error
			this.notificationService.error(e.error);
B
Benjamin Pasero 已提交
117 118 119

			// Log in telemetry
			if (this.telemetryService) {
K
kieferrm 已提交
120
				/* __GDPR__
K
kieferrm 已提交
121 122 123 124 125
					"workbenchActionExecuted" : {
						"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
						"from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
					}
				*/
B
Benjamin Pasero 已提交
126 127 128 129 130
				this.telemetryService.publicLog('workbenchActionExecuted', { id: e.action.id, from: 'editorPart' });
			}
		}));
	}

131
	private actionItemProvider(action: Action): IActionItem {
132
		const activeControl = this.group.activeControl;
B
Benjamin Pasero 已提交
133 134

		// Check Active Editor
135
		let actionItem: IActionItem;
136 137
		if (activeControl instanceof BaseEditor) {
			actionItem = activeControl.getActionItem(action);
B
Benjamin Pasero 已提交
138 139
		}

140 141
		// Check extensions
		if (!actionItem) {
142
			actionItem = createActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService);
143 144
		}

B
Benjamin Pasero 已提交
145 146 147
		return actionItem;
	}

148
	private getEditorActions(): IToolbarActions {
B
Benjamin Pasero 已提交
149 150 151
		const primary: IAction[] = [];
		const secondary: IAction[] = [];

152
		// Update the resource context
153
		this.resourceContext.set(toResource(this.group.activeEditor, { supportSideBySide: true }));
154

155
		// Editor actions require the editor control to be there, so we retrieve it via service
156
		const activeControl = this.group.activeControl;
157
		if (activeControl instanceof BaseEditor) {
158 159

			// Editor Control Actions
160
			let editorActions = this.mapEditorToActions.get(activeControl.getId());
B
Benjamin Pasero 已提交
161
			if (!editorActions) {
162
				editorActions = { primary: activeControl.getActions(), secondary: activeControl.getSecondaryActions() };
163
				this.mapEditorToActions.set(activeControl.getId(), editorActions);
B
Benjamin Pasero 已提交
164 165 166 167
			}
			primary.push(...editorActions.primary);
			secondary.push(...editorActions.secondary);

168
			// Contributed Actions
169
			this.disposeOnEditorActions = dispose(this.disposeOnEditorActions);
B
Benjamin Pasero 已提交
170
			const codeEditor = getCodeEditor(activeControl.getControl());
171 172
			const scopedContextKeyService = codeEditor && codeEditor.invokeWithinContext(accessor => accessor.get(IContextKeyService)) || this.contextKeyService;
			const titleBarMenu = this.menuService.createMenu(MenuId.EditorTitle, scopedContextKeyService);
173
			this.disposeOnEditorActions.push(titleBarMenu, titleBarMenu.onDidChange(() => this.updateEditorActionsToolbar()));
174

175
			fillInActions(titleBarMenu, { arg: this.resourceContext.get(), shouldForwardArgs: true }, { primary, secondary }, this.contextMenuService);
B
Benjamin Pasero 已提交
176 177 178 179 180
		}

		return { primary, secondary };
	}

181
	protected updateEditorActionsToolbar(): void {
182
		const isGroupActive = this.accessor.activeGroup === this.group;
183 184 185 186

		// Update Editor Actions Toolbar
		let primaryEditorActions: IAction[] = [];
		let secondaryEditorActions: IAction[] = [];
187

188
		const editorActions = this.getEditorActions();
189 190

		// Primary actions only for the active group
191
		if (isGroupActive) {
192
			primaryEditorActions = prepareActions(editorActions.primary);
193
			primaryEditorActions.push(this.splitEditorAction);
194
		}
195

196 197
		secondaryEditorActions = prepareActions(editorActions.secondary);

198
		const { showTabs } = this.accessor.partOptions;
B
Benjamin Pasero 已提交
199

200
		const primaryEditorActionIds = primaryEditorActions.map(a => a.id);
201
		if (!showTabs) {
202
			primaryEditorActionIds.push(this.closeOneEditorAction.id); // always show "Close" when tabs are disabled
B
Benjamin Pasero 已提交
203 204
		}

205
		const secondaryEditorActionIds = secondaryEditorActions.map(a => a.id);
206 207 208 209 210 211
		if (
			!arrays.equals(primaryEditorActionIds, this.currentPrimaryEditorActionIds) ||
			!arrays.equals(secondaryEditorActionIds, this.currentSecondaryEditorActionIds) ||
			primaryEditorActions.some(action => action instanceof ExecuteCommandAction) || // execute command actions can have the same ID but different arguments
			secondaryEditorActions.some(action => action instanceof ExecuteCommandAction)  // see also https://github.com/Microsoft/vscode/issues/16298
		) {
212 213
			this.editorActionsToolbar.setActions(primaryEditorActions, secondaryEditorActions)();

214
			if (!showTabs) {
215
				this.editorActionsToolbar.addPrimaryAction(this.closeOneEditorAction)();
216 217 218 219 220 221 222 223 224 225 226 227
			}

			this.currentPrimaryEditorActionIds = primaryEditorActionIds;
			this.currentSecondaryEditorActionIds = secondaryEditorActionIds;
		}
	}

	protected clearEditorActionsToolbar(): void {
		this.editorActionsToolbar.setActions([], [])();

		this.currentPrimaryEditorActionIds = [];
		this.currentSecondaryEditorActionIds = [];
B
Benjamin Pasero 已提交
228 229
	}

B
Benjamin Pasero 已提交
230
	protected enableGroupDragging(element: HTMLElement): void {
B
Benjamin Pasero 已提交
231 232 233 234 235 236 237 238 239 240 241

		// Drag start
		this._register(addDisposableListener(element, EventType.DRAG_START, (e: DragEvent) => {
			if (e.target !== element) {
				return; // only if originating from tabs container
			}

			// Set editor group as transfer
			this.groupTransfer.setData([new DraggedEditorGroupIdentifier(this.group.id)], DraggedEditorGroupIdentifier.prototype);
			e.dataTransfer.effectAllowed = 'copyMove';

B
Benjamin Pasero 已提交
242 243
			// If tabs are disabled, treat dragging as if an editor tab was dragged
			if (!this.accessor.partOptions.showTabs) {
244 245 246 247
				const resource = toResource(this.group.activeEditor, { supportSideBySide: true });
				if (resource) {
					this.instantiationService.invokeFunction(fillResourceDataTransfers, [resource], e);
				}
B
Benjamin Pasero 已提交
248 249 250
			}

			// Drag Image
B
Benjamin Pasero 已提交
251 252 253 254 255 256
			let label = this.group.activeEditor.getName();
			if (this.accessor.partOptions.showTabs && this.group.count > 1) {
				label = localize('draggedEditorGroup', "{0} (+{1})", label, this.group.count - 1);
			}

			applyDragImage(e, label, 'monaco-editor-group-drag-image');
B
Benjamin Pasero 已提交
257 258 259 260 261 262 263 264
		}));

		// Drag end
		this._register(addDisposableListener(element, EventType.DRAG_END, () => {
			this.groupTransfer.clearData(DraggedEditorGroupIdentifier.prototype);
		}));
	}

265
	protected onContextMenu(editor: IEditorInput, e: Event, node: HTMLElement): void {
266 267 268

		// Update the resource context
		const currentContext = this.resourceContext.get();
269
		this.resourceContext.set(toResource(editor, { supportSideBySide: true }));
270 271

		// Find target anchor
272 273 274 275 276 277
		let anchor: HTMLElement | { x: number, y: number } = node;
		if (e instanceof MouseEvent) {
			const event = new StandardMouseEvent(e);
			anchor = { x: event.posx, y: event.posy };
		}

278 279
		// Fill in contributed actions
		const actions: IAction[] = [];
I
isidor 已提交
280
		fillInActions(this.contextMenu, { shouldForwardArgs: true, arg: this.resourceContext.get() }, actions, this.contextMenuService);
281 282

		// Show it
283 284
		this.contextMenuService.showContextMenu({
			getAnchor: () => anchor,
285
			getActions: () => TPromise.as(actions),
286
			getActionsContext: () => ({ groupId: this.group.id, editorIndex: this.group.getIndexOfEditor(editor) } as IEditorCommandsContext),
B
Benjamin Pasero 已提交
287
			getKeyBinding: (action) => this.getKeybinding(action),
B
Benjamin Pasero 已提交
288
			onHide: () => {
B
Benjamin Pasero 已提交
289 290 291 292

				// restore previous context
				this.resourceContext.set(currentContext);

293
				// restore focus to active group
294
				this.accessor.activeGroup.focus();
B
Benjamin Pasero 已提交
295
			}
296 297 298
		});
	}

299
	protected getKeybinding(action: IAction): ResolvedKeybinding {
300
		return this.keybindingService.lookupKeybinding(action.id);
B
Benjamin Pasero 已提交
301 302 303 304 305
	}

	protected getKeybindingLabel(action: IAction): string {
		const keybinding = this.getKeybinding(action);

306
		return keybinding ? keybinding.getLabel() : void 0;
B
Benjamin Pasero 已提交
307 308
	}

309
	//#region ITitleAreaControl
310

311
	abstract openEditor(editor: IEditorInput): void;
312

313 314 315 316 317
	abstract closeEditor(editor: IEditorInput): void;

	abstract closeEditors(editors: IEditorInput[]): void;

	abstract closeAllEditors(): void;
318

319
	abstract moveEditor(editor: IEditorInput, fromIndex: number, targetIndex: number): void;
320

321
	abstract pinEditor(editor: IEditorInput): void;
322

323
	abstract setActive(isActive: boolean): void;
324

325 326 327
	abstract updateEditorLabel(editor: IEditorInput): void;

	abstract updateEditorDirty(editor: IEditorInput): void;
328

329
	abstract updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void;
330 331 332

	abstract updateStyles(): void;

333 334 335 336
	layout(dimension: Dimension): void {
		// Optionally implemented in subclasses
	}

337 338 339 340 341 342
	dispose(): void {
		this.disposeOnEditorActions = dispose(this.disposeOnEditorActions);

		super.dispose();
	}

343
	//#endregion
J
Johannes Rieken 已提交
344
}
345 346 347 348

registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {

	// Drag Feedback
B
Benjamin Pasero 已提交
349 350 351 352 353 354 355 356
	const dragImageBackground = theme.getColor(listActiveSelectionBackground);
	const dragImageForeground = theme.getColor(listActiveSelectionForeground);
	collector.addRule(`
		.monaco-editor-group-drag-image {
			background: ${dragImageBackground};
			color: ${dragImageForeground};
		}
	`);
357
});