71bb221af0532e0b733aaeff0af4ce7bacaa46352761982ee0507cc0a116721e5cedf3fa47a0455b90995cf5d693d333ee1cd298eb6c1f5f3a5983e825a5a6 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  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. import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
  27. let ContextMenuController = class ContextMenuController {
  28. constructor(editor, _contextMenuService, _contextViewService, _contextKeyService, _keybindingService, _menuService, _configurationService) {
  29. this._contextMenuService = _contextMenuService;
  30. this._contextViewService = _contextViewService;
  31. this._contextKeyService = _contextKeyService;
  32. this._keybindingService = _keybindingService;
  33. this._menuService = _menuService;
  34. this._configurationService = _configurationService;
  35. this._toDispose = new DisposableStore();
  36. this._contextMenuIsBeingShownCount = 0;
  37. this._editor = editor;
  38. this._toDispose.add(this._editor.onContextMenu((e) => this._onContextMenu(e)));
  39. this._toDispose.add(this._editor.onMouseWheel((e) => {
  40. if (this._contextMenuIsBeingShownCount > 0) {
  41. const view = this._contextViewService.getContextViewElement();
  42. const target = e.srcElement;
  43. // Event triggers on shadow root host first
  44. // Check if the context view is under this host before hiding it #103169
  45. if (!(target.shadowRoot && dom.getShadowRoot(view) === target.shadowRoot)) {
  46. this._contextViewService.hideContextView();
  47. }
  48. }
  49. }));
  50. this._toDispose.add(this._editor.onKeyDown((e) => {
  51. if (!this._editor.getOption(20 /* EditorOption.contextmenu */)) {
  52. return; // Context menu is turned off through configuration
  53. }
  54. if (e.keyCode === 58 /* KeyCode.ContextMenu */) {
  55. // Chrome is funny like that
  56. e.preventDefault();
  57. e.stopPropagation();
  58. this.showContextMenu();
  59. }
  60. }));
  61. }
  62. static get(editor) {
  63. return editor.getContribution(ContextMenuController.ID);
  64. }
  65. _onContextMenu(e) {
  66. if (!this._editor.hasModel()) {
  67. return;
  68. }
  69. if (!this._editor.getOption(20 /* EditorOption.contextmenu */)) {
  70. this._editor.focus();
  71. // Ensure the cursor is at the position of the mouse click
  72. if (e.target.position && !this._editor.getSelection().containsPosition(e.target.position)) {
  73. this._editor.setPosition(e.target.position);
  74. }
  75. return; // Context menu is turned off through configuration
  76. }
  77. if (e.target.type === 12 /* MouseTargetType.OVERLAY_WIDGET */) {
  78. return; // allow native menu on widgets to support right click on input field for example in find
  79. }
  80. if (e.target.type === 6 /* MouseTargetType.CONTENT_TEXT */ && e.target.detail.injectedText) {
  81. return; // allow native menu on injected text
  82. }
  83. e.event.preventDefault();
  84. e.event.stopPropagation();
  85. if (e.target.type === 11 /* MouseTargetType.SCROLLBAR */) {
  86. return this._showScrollbarContextMenu({ x: e.event.posx - 1, width: 2, y: e.event.posy - 1, height: 2 });
  87. }
  88. if (e.target.type !== 6 /* MouseTargetType.CONTENT_TEXT */ && e.target.type !== 7 /* MouseTargetType.CONTENT_EMPTY */ && e.target.type !== 1 /* MouseTargetType.TEXTAREA */) {
  89. return; // only support mouse click into text or native context menu key for now
  90. }
  91. // Ensure the editor gets focus if it hasn't, so the right events are being sent to other contributions
  92. this._editor.focus();
  93. // Ensure the cursor is at the position of the mouse click
  94. if (e.target.position) {
  95. let hasSelectionAtPosition = false;
  96. for (const selection of this._editor.getSelections()) {
  97. if (selection.containsPosition(e.target.position)) {
  98. hasSelectionAtPosition = true;
  99. break;
  100. }
  101. }
  102. if (!hasSelectionAtPosition) {
  103. this._editor.setPosition(e.target.position);
  104. }
  105. }
  106. // Unless the user triggerd the context menu through Shift+F10, use the mouse position as menu position
  107. let anchor = null;
  108. if (e.target.type !== 1 /* MouseTargetType.TEXTAREA */) {
  109. anchor = { x: e.event.posx - 1, width: 2, y: e.event.posy - 1, height: 2 };
  110. }
  111. // Show the context menu
  112. this.showContextMenu(anchor);
  113. }
  114. showContextMenu(anchor) {
  115. if (!this._editor.getOption(20 /* EditorOption.contextmenu */)) {
  116. return; // Context menu is turned off through configuration
  117. }
  118. if (!this._editor.hasModel()) {
  119. return;
  120. }
  121. // Find actions available for menu
  122. const menuActions = this._getMenuActions(this._editor.getModel(), this._editor.isSimpleWidget ? MenuId.SimpleEditorContext : MenuId.EditorContext);
  123. // Show menu if we have actions to show
  124. if (menuActions.length > 0) {
  125. this._doShowContextMenu(menuActions, anchor);
  126. }
  127. }
  128. _getMenuActions(model, menuId) {
  129. const result = [];
  130. // get menu groups
  131. const menu = this._menuService.createMenu(menuId, this._contextKeyService);
  132. const groups = menu.getActions({ arg: model.uri });
  133. menu.dispose();
  134. // translate them into other actions
  135. for (const group of groups) {
  136. const [, actions] = group;
  137. let addedItems = 0;
  138. for (const action of actions) {
  139. if (action instanceof SubmenuItemAction) {
  140. const subActions = this._getMenuActions(model, action.item.submenu);
  141. if (subActions.length > 0) {
  142. result.push(new SubmenuAction(action.id, action.label, subActions));
  143. addedItems++;
  144. }
  145. }
  146. else {
  147. result.push(action);
  148. addedItems++;
  149. }
  150. }
  151. if (addedItems) {
  152. result.push(new Separator());
  153. }
  154. }
  155. if (result.length) {
  156. result.pop(); // remove last separator
  157. }
  158. return result;
  159. }
  160. _doShowContextMenu(actions, anchor = null) {
  161. if (!this._editor.hasModel()) {
  162. return;
  163. }
  164. // Disable hover
  165. const oldHoverSetting = this._editor.getOption(55 /* EditorOption.hover */);
  166. this._editor.updateOptions({
  167. hover: {
  168. enabled: false
  169. }
  170. });
  171. if (!anchor) {
  172. // Ensure selection is visible
  173. this._editor.revealPosition(this._editor.getPosition(), 1 /* ScrollType.Immediate */);
  174. this._editor.render();
  175. const cursorCoords = this._editor.getScrolledVisiblePosition(this._editor.getPosition());
  176. // Translate to absolute editor position
  177. const editorCoords = dom.getDomNodePagePosition(this._editor.getDomNode());
  178. const posx = editorCoords.left + cursorCoords.left;
  179. const posy = editorCoords.top + cursorCoords.top + cursorCoords.height;
  180. anchor = { x: posx, y: posy };
  181. }
  182. const useShadowDOM = this._editor.getOption(117 /* EditorOption.useShadowDOM */) && !isIOS; // Do not use shadow dom on IOS #122035
  183. // Show menu
  184. this._contextMenuIsBeingShownCount++;
  185. this._contextMenuService.showContextMenu({
  186. domForShadowRoot: useShadowDOM ? this._editor.getDomNode() : undefined,
  187. getAnchor: () => anchor,
  188. getActions: () => actions,
  189. getActionViewItem: (action) => {
  190. const keybinding = this._keybindingFor(action);
  191. if (keybinding) {
  192. return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel(), isMenu: true });
  193. }
  194. const customActionViewItem = action;
  195. if (typeof customActionViewItem.getActionViewItem === 'function') {
  196. return customActionViewItem.getActionViewItem();
  197. }
  198. return new ActionViewItem(action, action, { icon: true, label: true, isMenu: true });
  199. },
  200. getKeyBinding: (action) => {
  201. return this._keybindingFor(action);
  202. },
  203. onHide: (wasCancelled) => {
  204. this._contextMenuIsBeingShownCount--;
  205. this._editor.focus();
  206. this._editor.updateOptions({
  207. hover: oldHoverSetting
  208. });
  209. }
  210. });
  211. }
  212. _showScrollbarContextMenu(anchor) {
  213. if (!this._editor.hasModel()) {
  214. return;
  215. }
  216. const minimapOptions = this._editor.getOption(67 /* EditorOption.minimap */);
  217. let lastId = 0;
  218. const createAction = (opts) => {
  219. return {
  220. id: `menu-action-${++lastId}`,
  221. label: opts.label,
  222. tooltip: '',
  223. class: undefined,
  224. enabled: (typeof opts.enabled === 'undefined' ? true : opts.enabled),
  225. checked: opts.checked,
  226. run: opts.run,
  227. dispose: () => null
  228. };
  229. };
  230. const createSubmenuAction = (label, actions) => {
  231. return new SubmenuAction(`menu-action-${++lastId}`, label, actions, undefined);
  232. };
  233. const createEnumAction = (label, enabled, configName, configuredValue, options) => {
  234. if (!enabled) {
  235. return createAction({ label, enabled, run: () => { } });
  236. }
  237. const createRunner = (value) => {
  238. return () => {
  239. this._configurationService.updateValue(configName, value);
  240. };
  241. };
  242. const actions = [];
  243. for (const option of options) {
  244. actions.push(createAction({
  245. label: option.label,
  246. checked: configuredValue === option.value,
  247. run: createRunner(option.value)
  248. }));
  249. }
  250. return createSubmenuAction(label, actions);
  251. };
  252. const actions = [];
  253. actions.push(createAction({
  254. label: nls.localize('context.minimap.minimap', "Minimap"),
  255. checked: minimapOptions.enabled,
  256. run: () => {
  257. this._configurationService.updateValue(`editor.minimap.enabled`, !minimapOptions.enabled);
  258. }
  259. }));
  260. actions.push(new Separator());
  261. actions.push(createAction({
  262. label: nls.localize('context.minimap.renderCharacters', "Render Characters"),
  263. enabled: minimapOptions.enabled,
  264. checked: minimapOptions.renderCharacters,
  265. run: () => {
  266. this._configurationService.updateValue(`editor.minimap.renderCharacters`, !minimapOptions.renderCharacters);
  267. }
  268. }));
  269. actions.push(createEnumAction(nls.localize('context.minimap.size', "Vertical size"), minimapOptions.enabled, 'editor.minimap.size', minimapOptions.size, [{
  270. label: nls.localize('context.minimap.size.proportional', "Proportional"),
  271. value: 'proportional'
  272. }, {
  273. label: nls.localize('context.minimap.size.fill', "Fill"),
  274. value: 'fill'
  275. }, {
  276. label: nls.localize('context.minimap.size.fit', "Fit"),
  277. value: 'fit'
  278. }]));
  279. actions.push(createEnumAction(nls.localize('context.minimap.slider', "Slider"), minimapOptions.enabled, 'editor.minimap.showSlider', minimapOptions.showSlider, [{
  280. label: nls.localize('context.minimap.slider.mouseover', "Mouse Over"),
  281. value: 'mouseover'
  282. }, {
  283. label: nls.localize('context.minimap.slider.always', "Always"),
  284. value: 'always'
  285. }]));
  286. const useShadowDOM = this._editor.getOption(117 /* EditorOption.useShadowDOM */) && !isIOS; // Do not use shadow dom on IOS #122035
  287. this._contextMenuIsBeingShownCount++;
  288. this._contextMenuService.showContextMenu({
  289. domForShadowRoot: useShadowDOM ? this._editor.getDomNode() : undefined,
  290. getAnchor: () => anchor,
  291. getActions: () => actions,
  292. onHide: (wasCancelled) => {
  293. this._contextMenuIsBeingShownCount--;
  294. this._editor.focus();
  295. }
  296. });
  297. }
  298. _keybindingFor(action) {
  299. return this._keybindingService.lookupKeybinding(action.id);
  300. }
  301. dispose() {
  302. if (this._contextMenuIsBeingShownCount > 0) {
  303. this._contextViewService.hideContextView();
  304. }
  305. this._toDispose.dispose();
  306. }
  307. };
  308. ContextMenuController.ID = 'editor.contrib.contextmenu';
  309. ContextMenuController = __decorate([
  310. __param(1, IContextMenuService),
  311. __param(2, IContextViewService),
  312. __param(3, IContextKeyService),
  313. __param(4, IKeybindingService),
  314. __param(5, IMenuService),
  315. __param(6, IConfigurationService)
  316. ], ContextMenuController);
  317. export { ContextMenuController };
  318. class ShowContextMenu extends EditorAction {
  319. constructor() {
  320. super({
  321. id: 'editor.action.showContextMenu',
  322. label: nls.localize('action.showContextMenu.label', "Show Editor Context Menu"),
  323. alias: 'Show Editor Context Menu',
  324. precondition: undefined,
  325. kbOpts: {
  326. kbExpr: EditorContextKeys.textInputFocus,
  327. primary: 1024 /* KeyMod.Shift */ | 68 /* KeyCode.F10 */,
  328. weight: 100 /* KeybindingWeight.EditorContrib */
  329. }
  330. });
  331. }
  332. run(accessor, editor) {
  333. var _a;
  334. (_a = ContextMenuController.get(editor)) === null || _a === void 0 ? void 0 : _a.showContextMenu();
  335. }
  336. }
  337. registerEditorContribution(ContextMenuController.ID, ContextMenuController);
  338. registerEditorAction(ShowContextMenu);