ec225322988508816da12212e0a9622f765fcc4b8145255d657ad8d32364b62adfe522214a80d4f9f9bc26e2b72fd7bfbe411b7c092ee46c8ed937ca974c18 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  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. import { $, addDisposableListener, EventType, isHTMLElement } from '../../../base/browser/dom.js';
  6. import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js';
  7. import { Menu } from '../../../base/browser/ui/menu/menu.js';
  8. import { ActionRunner } from '../../../base/common/actions.js';
  9. import { isCancellationError } from '../../../base/common/errors.js';
  10. import { combinedDisposable, DisposableStore } from '../../../base/common/lifecycle.js';
  11. import { attachMenuStyler } from '../../theme/common/styler.js';
  12. export class ContextMenuHandler {
  13. constructor(contextViewService, telemetryService, notificationService, keybindingService, themeService) {
  14. this.contextViewService = contextViewService;
  15. this.telemetryService = telemetryService;
  16. this.notificationService = notificationService;
  17. this.keybindingService = keybindingService;
  18. this.themeService = themeService;
  19. this.focusToReturn = null;
  20. this.block = null;
  21. this.options = { blockMouse: true };
  22. }
  23. configure(options) {
  24. this.options = options;
  25. }
  26. showContextMenu(delegate) {
  27. const actions = delegate.getActions();
  28. if (!actions.length) {
  29. return; // Don't render an empty context menu
  30. }
  31. this.focusToReturn = document.activeElement;
  32. let menu;
  33. const shadowRootElement = isHTMLElement(delegate.domForShadowRoot) ? delegate.domForShadowRoot : undefined;
  34. this.contextViewService.showContextView({
  35. getAnchor: () => delegate.getAnchor(),
  36. canRelayout: false,
  37. anchorAlignment: delegate.anchorAlignment,
  38. anchorAxisAlignment: delegate.anchorAxisAlignment,
  39. render: (container) => {
  40. const className = delegate.getMenuClassName ? delegate.getMenuClassName() : '';
  41. if (className) {
  42. container.className += ' ' + className;
  43. }
  44. // Render invisible div to block mouse interaction in the rest of the UI
  45. if (this.options.blockMouse) {
  46. this.block = container.appendChild($('.context-view-block'));
  47. this.block.style.position = 'fixed';
  48. this.block.style.cursor = 'initial';
  49. this.block.style.left = '0';
  50. this.block.style.top = '0';
  51. this.block.style.width = '100%';
  52. this.block.style.height = '100%';
  53. this.block.style.zIndex = '-1';
  54. // TODO@Steven: this is never getting disposed
  55. addDisposableListener(this.block, EventType.MOUSE_DOWN, e => e.stopPropagation());
  56. }
  57. const menuDisposables = new DisposableStore();
  58. const actionRunner = delegate.actionRunner || new ActionRunner();
  59. actionRunner.onBeforeRun(this.onActionRun, this, menuDisposables);
  60. actionRunner.onDidRun(this.onDidActionRun, this, menuDisposables);
  61. menu = new Menu(container, actions, {
  62. actionViewItemProvider: delegate.getActionViewItem,
  63. context: delegate.getActionsContext ? delegate.getActionsContext() : null,
  64. actionRunner,
  65. getKeyBinding: delegate.getKeyBinding ? delegate.getKeyBinding : action => this.keybindingService.lookupKeybinding(action.id)
  66. });
  67. menuDisposables.add(attachMenuStyler(menu, this.themeService));
  68. menu.onDidCancel(() => this.contextViewService.hideContextView(true), null, menuDisposables);
  69. menu.onDidBlur(() => this.contextViewService.hideContextView(true), null, menuDisposables);
  70. menuDisposables.add(addDisposableListener(window, EventType.BLUR, () => this.contextViewService.hideContextView(true)));
  71. menuDisposables.add(addDisposableListener(window, EventType.MOUSE_DOWN, (e) => {
  72. if (e.defaultPrevented) {
  73. return;
  74. }
  75. const event = new StandardMouseEvent(e);
  76. let element = event.target;
  77. // Don't do anything as we are likely creating a context menu
  78. if (event.rightButton) {
  79. return;
  80. }
  81. while (element) {
  82. if (element === container) {
  83. return;
  84. }
  85. element = element.parentElement;
  86. }
  87. this.contextViewService.hideContextView(true);
  88. }));
  89. return combinedDisposable(menuDisposables, menu);
  90. },
  91. focus: () => {
  92. menu === null || menu === void 0 ? void 0 : menu.focus(!!delegate.autoSelectFirstItem);
  93. },
  94. onHide: (didCancel) => {
  95. var _a;
  96. (_a = delegate.onHide) === null || _a === void 0 ? void 0 : _a.call(delegate, !!didCancel);
  97. if (this.block) {
  98. this.block.remove();
  99. this.block = null;
  100. }
  101. if (this.focusToReturn) {
  102. this.focusToReturn.focus();
  103. }
  104. }
  105. }, shadowRootElement, !!shadowRootElement);
  106. }
  107. onActionRun(e) {
  108. this.telemetryService.publicLog2('workbenchActionExecuted', { id: e.action.id, from: 'contextMenu' });
  109. this.contextViewService.hideContextView(false);
  110. // Restore focus here
  111. if (this.focusToReturn) {
  112. this.focusToReturn.focus();
  113. }
  114. }
  115. onDidActionRun(e) {
  116. if (e.error && !isCancellationError(e.error)) {
  117. this.notificationService.error(e.error);
  118. }
  119. }
  120. }