contextmenu.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  6. var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  7. if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  8. else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  9. return c > 3 && r && Object.defineProperty(target, key, r), r;
  10. };
  11. var __param = (this && this.__param) || function (paramIndex, decorator) {
  12. return function (target, key) { decorator(target, key, paramIndex); }
  13. };
  14. import * as dom from '../../../base/browser/dom.js';
  15. import { ActionViewItem } from '../../../base/browser/ui/actionbar/actionViewItems.js';
  16. import { Separator, SubmenuAction } from '../../../base/common/actions.js';
  17. import { DisposableStore } from '../../../base/common/lifecycle.js';
  18. import { isIOS } from '../../../base/common/platform.js';
  19. import { EditorAction, registerEditorAction, registerEditorContribution } from '../../browser/editorExtensions.js';
  20. import { EditorContextKeys } from '../../common/editorContextKeys.js';
  21. import * as nls from '../../../nls.js';
  22. import { IMenuService, MenuId, SubmenuItemAction } from '../../../platform/actions/common/actions.js';
  23. import { IContextKeyService } from '../../../platform/contextkey/common/contextkey.js';
  24. import { IContextMenuService, IContextViewService } from '../../../platform/contextview/browser/contextView.js';
  25. import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js';
  26. let ContextMenuController = class ContextMenuController {
  27. constructor(editor, _contextMenuService, _contextViewService, _contextKeyService, _keybindingService, _menuService) {
  28. this._contextMenuService = _contextMenuService;
  29. this._contextViewService = _contextViewService;
  30. this._contextKeyService = _contextKeyService;
  31. this._keybindingService = _keybindingService;
  32. this._menuService = _menuService;
  33. this._toDispose = new DisposableStore();
  34. this._contextMenuIsBeingShownCount = 0;
  35. this._editor = editor;
  36. this._toDispose.add(this._editor.onContextMenu((e) => this._onContextMenu(e)));
  37. this._toDispose.add(this._editor.onMouseWheel((e) => {
  38. if (this._contextMenuIsBeingShownCount > 0) {
  39. const view = this._contextViewService.getContextViewElement();
  40. const target = e.srcElement;
  41. // Event triggers on shadow root host first
  42. // Check if the context view is under this host before hiding it #103169
  43. if (!(target.shadowRoot && dom.getShadowRoot(view) === target.shadowRoot)) {
  44. this._contextViewService.hideContextView();
  45. }
  46. }
  47. }));
  48. this._toDispose.add(this._editor.onKeyDown((e) => {
  49. if (e.keyCode === 58 /* ContextMenu */) {
  50. // Chrome is funny like that
  51. e.preventDefault();
  52. e.stopPropagation();
  53. this.showContextMenu();
  54. }
  55. }));
  56. }
  57. static get(editor) {
  58. return editor.getContribution(ContextMenuController.ID);
  59. }
  60. _onContextMenu(e) {
  61. if (!this._editor.hasModel()) {
  62. return;
  63. }
  64. if (!this._editor.getOption(20 /* contextmenu */)) {
  65. this._editor.focus();
  66. // Ensure the cursor is at the position of the mouse click
  67. if (e.target.position && !this._editor.getSelection().containsPosition(e.target.position)) {
  68. this._editor.setPosition(e.target.position);
  69. }
  70. return; // Context menu is turned off through configuration
  71. }
  72. if (e.target.type === 12 /* OVERLAY_WIDGET */) {
  73. return; // allow native menu on widgets to support right click on input field for example in find
  74. }
  75. e.event.preventDefault();
  76. e.event.stopPropagation();
  77. if (e.target.type !== 6 /* CONTENT_TEXT */ && e.target.type !== 7 /* CONTENT_EMPTY */ && e.target.type !== 1 /* TEXTAREA */) {
  78. return; // only support mouse click into text or native context menu key for now
  79. }
  80. // Ensure the editor gets focus if it hasn't, so the right events are being sent to other contributions
  81. this._editor.focus();
  82. // Ensure the cursor is at the position of the mouse click
  83. if (e.target.position) {
  84. let hasSelectionAtPosition = false;
  85. for (const selection of this._editor.getSelections()) {
  86. if (selection.containsPosition(e.target.position)) {
  87. hasSelectionAtPosition = true;
  88. break;
  89. }
  90. }
  91. if (!hasSelectionAtPosition) {
  92. this._editor.setPosition(e.target.position);
  93. }
  94. }
  95. // Unless the user triggerd the context menu through Shift+F10, use the mouse position as menu position
  96. let anchor = null;
  97. if (e.target.type !== 1 /* TEXTAREA */) {
  98. anchor = { x: e.event.posx - 1, width: 2, y: e.event.posy - 1, height: 2 };
  99. }
  100. // Show the context menu
  101. this.showContextMenu(anchor);
  102. }
  103. showContextMenu(anchor) {
  104. if (!this._editor.getOption(20 /* contextmenu */)) {
  105. return; // Context menu is turned off through configuration
  106. }
  107. if (!this._editor.hasModel()) {
  108. return;
  109. }
  110. if (!this._contextMenuService) {
  111. this._editor.focus();
  112. return; // We need the context menu service to function
  113. }
  114. // Find actions available for menu
  115. const menuActions = this._getMenuActions(this._editor.getModel(), this._editor.isSimpleWidget ? MenuId.SimpleEditorContext : MenuId.EditorContext);
  116. // Show menu if we have actions to show
  117. if (menuActions.length > 0) {
  118. this._doShowContextMenu(menuActions, anchor);
  119. }
  120. }
  121. _getMenuActions(model, menuId) {
  122. const result = [];
  123. // get menu groups
  124. const menu = this._menuService.createMenu(menuId, this._contextKeyService);
  125. const groups = menu.getActions({ arg: model.uri });
  126. menu.dispose();
  127. // translate them into other actions
  128. for (let group of groups) {
  129. const [, actions] = group;
  130. let addedItems = 0;
  131. for (const action of actions) {
  132. if (action instanceof SubmenuItemAction) {
  133. const subActions = this._getMenuActions(model, action.item.submenu);
  134. if (subActions.length > 0) {
  135. result.push(new SubmenuAction(action.id, action.label, subActions));
  136. addedItems++;
  137. }
  138. }
  139. else {
  140. result.push(action);
  141. addedItems++;
  142. }
  143. }
  144. if (addedItems) {
  145. result.push(new Separator());
  146. }
  147. }
  148. if (result.length) {
  149. result.pop(); // remove last separator
  150. }
  151. return result;
  152. }
  153. _doShowContextMenu(actions, anchor = null) {
  154. if (!this._editor.hasModel()) {
  155. return;
  156. }
  157. // Disable hover
  158. const oldHoverSetting = this._editor.getOption(52 /* hover */);
  159. this._editor.updateOptions({
  160. hover: {
  161. enabled: false
  162. }
  163. });
  164. if (!anchor) {
  165. // Ensure selection is visible
  166. this._editor.revealPosition(this._editor.getPosition(), 1 /* Immediate */);
  167. this._editor.render();
  168. const cursorCoords = this._editor.getScrolledVisiblePosition(this._editor.getPosition());
  169. // Translate to absolute editor position
  170. const editorCoords = dom.getDomNodePagePosition(this._editor.getDomNode());
  171. const posx = editorCoords.left + cursorCoords.left;
  172. const posy = editorCoords.top + cursorCoords.top + cursorCoords.height;
  173. anchor = { x: posx, y: posy };
  174. }
  175. const useShadowDOM = this._editor.getOption(114 /* useShadowDOM */) && !isIOS; // Do not use shadow dom on IOS #122035
  176. // Show menu
  177. this._contextMenuIsBeingShownCount++;
  178. this._contextMenuService.showContextMenu({
  179. domForShadowRoot: useShadowDOM ? this._editor.getDomNode() : undefined,
  180. getAnchor: () => anchor,
  181. getActions: () => actions,
  182. getActionViewItem: (action) => {
  183. const keybinding = this._keybindingFor(action);
  184. if (keybinding) {
  185. return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel(), isMenu: true });
  186. }
  187. const customActionViewItem = action;
  188. if (typeof customActionViewItem.getActionViewItem === 'function') {
  189. return customActionViewItem.getActionViewItem();
  190. }
  191. return new ActionViewItem(action, action, { icon: true, label: true, isMenu: true });
  192. },
  193. getKeyBinding: (action) => {
  194. return this._keybindingFor(action);
  195. },
  196. onHide: (wasCancelled) => {
  197. this._contextMenuIsBeingShownCount--;
  198. this._editor.focus();
  199. this._editor.updateOptions({
  200. hover: oldHoverSetting
  201. });
  202. }
  203. });
  204. }
  205. _keybindingFor(action) {
  206. return this._keybindingService.lookupKeybinding(action.id);
  207. }
  208. dispose() {
  209. if (this._contextMenuIsBeingShownCount > 0) {
  210. this._contextViewService.hideContextView();
  211. }
  212. this._toDispose.dispose();
  213. }
  214. };
  215. ContextMenuController.ID = 'editor.contrib.contextmenu';
  216. ContextMenuController = __decorate([
  217. __param(1, IContextMenuService),
  218. __param(2, IContextViewService),
  219. __param(3, IContextKeyService),
  220. __param(4, IKeybindingService),
  221. __param(5, IMenuService)
  222. ], ContextMenuController);
  223. export { ContextMenuController };
  224. class ShowContextMenu extends EditorAction {
  225. constructor() {
  226. super({
  227. id: 'editor.action.showContextMenu',
  228. label: nls.localize('action.showContextMenu.label', "Show Editor Context Menu"),
  229. alias: 'Show Editor Context Menu',
  230. precondition: undefined,
  231. kbOpts: {
  232. kbExpr: EditorContextKeys.textInputFocus,
  233. primary: 1024 /* Shift */ | 68 /* F10 */,
  234. weight: 100 /* EditorContrib */
  235. }
  236. });
  237. }
  238. run(accessor, editor) {
  239. let contribution = ContextMenuController.get(editor);
  240. contribution.showContextMenu();
  241. }
  242. }
  243. registerEditorContribution(ContextMenuController.ID, ContextMenuController);
  244. registerEditorAction(ShowContextMenu);