contextmenu.ts 8.5 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

A
Alex Dima 已提交
6
import * as nls from 'vs/nls';
A
Alex Dima 已提交
7
import * as dom from 'vs/base/browser/dom';
J
Johannes Rieken 已提交
8
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
9
import { ActionViewItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
A
Alex Dima 已提交
10 11 12
import { IAnchor } from 'vs/base/browser/ui/contextview/contextview';
import { IAction } from 'vs/base/common/actions';
import { KeyCode, KeyMod, ResolvedKeybinding } from 'vs/base/common/keyCodes';
13
import { DisposableStore } from 'vs/base/common/lifecycle';
A
Alex Dima 已提交
14 15
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
16
import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon';
17
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
A
Alex Dima 已提交
18 19 20 21
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
22
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
A
Alex Dima 已提交
23
import { ITextModel } from 'vs/editor/common/model';
24
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
E
Erich Gamma 已提交
25

26
export class ContextMenuController implements IEditorContribution {
E
Erich Gamma 已提交
27

28
	private static readonly ID = 'editor.contrib.contextmenu';
29

30
	public static get(editor: ICodeEditor): ContextMenuController {
A
Alex Dima 已提交
31
		return editor.getContribution<ContextMenuController>(ContextMenuController.ID);
32
	}
E
Erich Gamma 已提交
33

34
	private readonly _toDispose = new DisposableStore();
35
	private _contextMenuIsBeingShownCount: number = 0;
36
	private readonly _editor: ICodeEditor;
37 38 39

	constructor(
		editor: ICodeEditor,
40 41 42 43 44
		@IContextMenuService private readonly _contextMenuService: IContextMenuService,
		@IContextViewService private readonly _contextViewService: IContextViewService,
		@IContextKeyService private readonly _contextKeyService: IContextKeyService,
		@IKeybindingService private readonly _keybindingService: IKeybindingService,
		@IMenuService private readonly _menuService: IMenuService
45
	) {
E
Erich Gamma 已提交
46 47
		this._editor = editor;

48 49
		this._toDispose.add(this._editor.onContextMenu((e: IEditorMouseEvent) => this._onContextMenu(e)));
		this._toDispose.add(this._editor.onMouseWheel((e: IMouseWheelEvent) => {
50
			if (this._contextMenuIsBeingShownCount > 0) {
51 52 53
				this._contextViewService.hideContextView();
			}
		}));
54
		this._toDispose.add(this._editor.onKeyDown((e: IKeyboardEvent) => {
E
Erich Gamma 已提交
55 56 57 58 59 60 61 62 63
			if (e.keyCode === KeyCode.ContextMenu) {
				// Chrome is funny like that
				e.preventDefault();
				e.stopPropagation();
				this.showContextMenu();
			}
		}));
	}

64
	private _onContextMenu(e: IEditorMouseEvent): void {
A
Alex Dima 已提交
65 66 67 68
		if (!this._editor.hasModel()) {
			return;
		}

A
Alex Dima 已提交
69
		if (!this._editor.getConfiguration().contribInfo.contextmenu) {
E
Erich Gamma 已提交
70 71 72 73 74 75 76 77
			this._editor.focus();
			// Ensure the cursor is at the position of the mouse click
			if (e.target.position && !this._editor.getSelection().containsPosition(e.target.position)) {
				this._editor.setPosition(e.target.position);
			}
			return; // Context menu is turned off through configuration
		}

A
Alex Dima 已提交
78
		if (e.target.type === MouseTargetType.OVERLAY_WIDGET) {
E
Erich Gamma 已提交
79 80 81 82 83
			return; // allow native menu on widgets to support right click on input field for example in find
		}

		e.event.preventDefault();

A
Alex Dima 已提交
84
		if (e.target.type !== MouseTargetType.CONTENT_TEXT && e.target.type !== MouseTargetType.CONTENT_EMPTY && e.target.type !== MouseTargetType.TEXTAREA) {
E
Erich Gamma 已提交
85 86 87 88 89 90 91 92 93 94 95 96
			return; // only support mouse click into text or native context menu key for now
		}

		// Ensure the editor gets focus if it hasn't, so the right events are being sent to other contributions
		this._editor.focus();

		// Ensure the cursor is at the position of the mouse click
		if (e.target.position && !this._editor.getSelection().containsPosition(e.target.position)) {
			this._editor.setPosition(e.target.position);
		}

		// Unless the user triggerd the context menu through Shift+F10, use the mouse position as menu position
A
Alex Dima 已提交
97
		let anchor: IAnchor | null = null;
A
Alex Dima 已提交
98
		if (e.target.type !== MouseTargetType.TEXTAREA) {
99
			anchor = { x: e.event.posx - 1, width: 2, y: e.event.posy - 1, height: 2 };
E
Erich Gamma 已提交
100 101 102
		}

		// Show the context menu
103
		this.showContextMenu(anchor);
E
Erich Gamma 已提交
104 105
	}

A
Alex Dima 已提交
106
	public showContextMenu(anchor?: IAnchor | null): void {
A
Alex Dima 已提交
107
		if (!this._editor.getConfiguration().contribInfo.contextmenu) {
E
Erich Gamma 已提交
108 109
			return; // Context menu is turned off through configuration
		}
A
Alex Dima 已提交
110 111 112
		if (!this._editor.hasModel()) {
			return;
		}
E
Erich Gamma 已提交
113

114
		if (!this._contextMenuService) {
E
Erich Gamma 已提交
115 116 117 118 119
			this._editor.focus();
			return;	// We need the context menu service to function
		}

		// Find actions available for menu
A
Alex Dima 已提交
120
		const menuActions = this._getMenuActions(this._editor.getModel());
E
Erich Gamma 已提交
121 122 123

		// Show menu if we have actions to show
		if (menuActions.length > 0) {
124
			this._doShowContextMenu(menuActions, anchor);
E
Erich Gamma 已提交
125 126 127
		}
	}

128
	private _getMenuActions(model: ITextModel): ReadonlyArray<IAction> {
129
		const result: IAction[] = [];
J
Johannes Rieken 已提交
130 131

		let contextMenu = this._menuService.createMenu(MenuId.EditorContext, this._contextKeyService);
A
Alex Dima 已提交
132
		const groups = contextMenu.getActions({ arg: model.uri });
133
		contextMenu.dispose();
J
Johannes Rieken 已提交
134 135 136 137 138 139 140

		for (let group of groups) {
			const [, actions] = group;
			result.push(...actions);
			result.push(new Separator());
		}
		result.pop(); // remove last separator
141
		return result;
E
Erich Gamma 已提交
142 143
	}

144
	private _doShowContextMenu(actions: ReadonlyArray<IAction>, anchor: IAnchor | null = null): void {
A
Alex Dima 已提交
145 146 147
		if (!this._editor.hasModel()) {
			return;
		}
E
Erich Gamma 已提交
148 149

		// Disable hover
A
Alex Dima 已提交
150
		const oldHoverSetting = this._editor.getConfiguration().contribInfo.hover;
E
Erich Gamma 已提交
151
		this._editor.updateOptions({
A
Alex Dima 已提交
152 153 154
			hover: {
				enabled: false
			}
E
Erich Gamma 已提交
155 156
		});

J
Joao Moreno 已提交
157
		if (!anchor) {
158
			// Ensure selection is visible
159
			this._editor.revealPosition(this._editor.getPosition(), ScrollType.Immediate);
E
Erich Gamma 已提交
160

161
			this._editor.render();
A
Alex Dima 已提交
162
			const cursorCoords = this._editor.getScrolledVisiblePosition(this._editor.getPosition());
E
Erich Gamma 已提交
163 164

			// Translate to absolute editor position
A
Alex Dima 已提交
165 166 167
			const editorCoords = dom.getDomNodePagePosition(this._editor.getDomNode());
			const posx = editorCoords.left + cursorCoords.left;
			const posy = editorCoords.top + cursorCoords.top + cursorCoords.height;
E
Erich Gamma 已提交
168

J
Joao Moreno 已提交
169
			anchor = { x: posx, y: posy };
E
Erich Gamma 已提交
170 171 172
		}

		// Show menu
173
		this._contextMenuIsBeingShownCount++;
174
		this._contextMenuService.showContextMenu({
A
Alex Dima 已提交
175
			getAnchor: () => anchor!,
E
Erich Gamma 已提交
176

177
			getActions: () => actions,
E
Erich Gamma 已提交
178

179
			getActionViewItem: (action) => {
A
Alex Dima 已提交
180
				const keybinding = this._keybindingFor(action);
E
Erich Gamma 已提交
181
				if (keybinding) {
182
					return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel(), isMenu: true });
E
Erich Gamma 已提交
183 184
				}

185 186 187
				const customActionViewItem = <any>action;
				if (typeof customActionViewItem.getActionViewItem === 'function') {
					return customActionViewItem.getActionViewItem();
E
Erich Gamma 已提交
188 189
				}

190
				return new ActionViewItem(action, action, { icon: true, label: true, isMenu: true });
E
Erich Gamma 已提交
191 192
			},

M
Matt Bierner 已提交
193
			getKeyBinding: (action): ResolvedKeybinding | undefined => {
M
Matt Bierner 已提交
194
				return this._keybindingFor(action);
E
Erich Gamma 已提交
195 196
			},

197
			onHide: (wasCancelled: boolean) => {
E
Erich Gamma 已提交
198 199 200 201 202 203 204 205 206
				this._contextMenuIsBeingShownCount--;
				this._editor.focus();
				this._editor.updateOptions({
					hover: oldHoverSetting
				});
			}
		});
	}

M
Matt Bierner 已提交
207
	private _keybindingFor(action: IAction): ResolvedKeybinding | undefined {
208
		return this._keybindingService.lookupKeybinding(action.id);
E
Erich Gamma 已提交
209 210 211 212 213 214 215 216
	}

	public getId(): string {
		return ContextMenuController.ID;
	}

	public dispose(): void {
		if (this._contextMenuIsBeingShownCount > 0) {
217
			this._contextViewService.hideContextView();
E
Erich Gamma 已提交
218 219
		}

220
		this._toDispose.dispose();
E
Erich Gamma 已提交
221 222 223
	}
}

A
Alex Dima 已提交
224
class ShowContextMenu extends EditorAction {
E
Erich Gamma 已提交
225

A
Alex Dima 已提交
226
	constructor() {
227 228 229 230
		super({
			id: 'editor.action.showContextMenu',
			label: nls.localize('action.showContextMenu.label', "Show Editor Context Menu"),
			alias: 'Show Editor Context Menu',
231
			precondition: undefined,
232
			kbOpts: {
233
				kbExpr: EditorContextKeys.textInputFocus,
A
Alex Dima 已提交
234
				primary: KeyMod.Shift | KeyCode.F10,
235
				weight: KeybindingWeight.EditorContrib
236 237
			}
		});
E
Erich Gamma 已提交
238 239
	}

240
	public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
241
		let contribution = ContextMenuController.get(editor);
E
Erich Gamma 已提交
242 243 244
		contribution.showContextMenu();
	}
}
245

246
registerEditorContribution(ContextMenuController);
247
registerEditorAction(ShowContextMenu);