codeActionMenu.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  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. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  15. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  16. return new (P || (P = Promise))(function (resolve, reject) {
  17. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  18. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  19. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  20. step((generator = generator.apply(thisArg, _arguments || [])).next());
  21. });
  22. };
  23. import { getDomNodePagePosition } from '../../../base/browser/dom.js';
  24. import { Action, Separator } from '../../../base/common/actions.js';
  25. import { canceled } from '../../../base/common/errors.js';
  26. import { Lazy } from '../../../base/common/lazy.js';
  27. import { Disposable, MutableDisposable } from '../../../base/common/lifecycle.js';
  28. import { Position } from '../../common/core/position.js';
  29. import { CodeActionProviderRegistry } from '../../common/modes.js';
  30. import { codeActionCommandId, CodeActionItem, fixAllCommandId, organizeImportsCommandId, refactorCommandId, sourceActionCommandId } from './codeAction.js';
  31. import { CodeActionCommandArgs, CodeActionKind } from './types.js';
  32. import { IContextMenuService } from '../../../platform/contextview/browser/contextView.js';
  33. import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js';
  34. class CodeActionAction extends Action {
  35. constructor(action, callback) {
  36. super(action.command ? action.command.id : action.title, stripNewlines(action.title), undefined, !action.disabled, callback);
  37. this.action = action;
  38. }
  39. }
  40. function stripNewlines(str) {
  41. return str.replace(/\r\n|\r|\n/g, ' ');
  42. }
  43. let CodeActionMenu = class CodeActionMenu extends Disposable {
  44. constructor(_editor, _delegate, _contextMenuService, keybindingService) {
  45. super();
  46. this._editor = _editor;
  47. this._delegate = _delegate;
  48. this._contextMenuService = _contextMenuService;
  49. this._visible = false;
  50. this._showingActions = this._register(new MutableDisposable());
  51. this._keybindingResolver = new CodeActionKeybindingResolver({
  52. getKeybindings: () => keybindingService.getKeybindings()
  53. });
  54. }
  55. get isVisible() {
  56. return this._visible;
  57. }
  58. show(trigger, codeActions, at, options) {
  59. return __awaiter(this, void 0, void 0, function* () {
  60. const actionsToShow = options.includeDisabledActions ? codeActions.allActions : codeActions.validActions;
  61. if (!actionsToShow.length) {
  62. this._visible = false;
  63. return;
  64. }
  65. if (!this._editor.getDomNode()) {
  66. // cancel when editor went off-dom
  67. this._visible = false;
  68. throw canceled();
  69. }
  70. this._visible = true;
  71. this._showingActions.value = codeActions;
  72. const menuActions = this.getMenuActions(trigger, actionsToShow, codeActions.documentation);
  73. const anchor = Position.isIPosition(at) ? this._toCoords(at) : at || { x: 0, y: 0 };
  74. const resolver = this._keybindingResolver.getResolver();
  75. const useShadowDOM = this._editor.getOption(114 /* useShadowDOM */);
  76. this._contextMenuService.showContextMenu({
  77. domForShadowRoot: useShadowDOM ? this._editor.getDomNode() : undefined,
  78. getAnchor: () => anchor,
  79. getActions: () => menuActions,
  80. onHide: () => {
  81. this._visible = false;
  82. this._editor.focus();
  83. },
  84. autoSelectFirstItem: true,
  85. getKeyBinding: action => action instanceof CodeActionAction ? resolver(action.action) : undefined,
  86. });
  87. });
  88. }
  89. getMenuActions(trigger, actionsToShow, documentation) {
  90. var _a, _b;
  91. const toCodeActionAction = (item) => new CodeActionAction(item.action, () => this._delegate.onSelectCodeAction(item));
  92. const result = actionsToShow
  93. .map(toCodeActionAction);
  94. const allDocumentation = [...documentation];
  95. const model = this._editor.getModel();
  96. if (model && result.length) {
  97. for (const provider of CodeActionProviderRegistry.all(model)) {
  98. if (provider._getAdditionalMenuItems) {
  99. allDocumentation.push(...provider._getAdditionalMenuItems({ trigger: trigger.type, only: (_b = (_a = trigger.filter) === null || _a === void 0 ? void 0 : _a.include) === null || _b === void 0 ? void 0 : _b.value }, actionsToShow.map(item => item.action)));
  100. }
  101. }
  102. }
  103. if (allDocumentation.length) {
  104. result.push(new Separator(), ...allDocumentation.map(command => toCodeActionAction(new CodeActionItem({
  105. title: command.title,
  106. command: command,
  107. }, undefined))));
  108. }
  109. return result;
  110. }
  111. _toCoords(position) {
  112. if (!this._editor.hasModel()) {
  113. return { x: 0, y: 0 };
  114. }
  115. this._editor.revealPosition(position, 1 /* Immediate */);
  116. this._editor.render();
  117. // Translate to absolute editor position
  118. const cursorCoords = this._editor.getScrolledVisiblePosition(position);
  119. const editorCoords = getDomNodePagePosition(this._editor.getDomNode());
  120. const x = editorCoords.left + cursorCoords.left;
  121. const y = editorCoords.top + cursorCoords.top + cursorCoords.height;
  122. return { x, y };
  123. }
  124. };
  125. CodeActionMenu = __decorate([
  126. __param(2, IContextMenuService),
  127. __param(3, IKeybindingService)
  128. ], CodeActionMenu);
  129. export { CodeActionMenu };
  130. export class CodeActionKeybindingResolver {
  131. constructor(_keybindingProvider) {
  132. this._keybindingProvider = _keybindingProvider;
  133. }
  134. getResolver() {
  135. // Lazy since we may not actually ever read the value
  136. const allCodeActionBindings = new Lazy(() => this._keybindingProvider.getKeybindings()
  137. .filter(item => CodeActionKeybindingResolver.codeActionCommands.indexOf(item.command) >= 0)
  138. .filter(item => item.resolvedKeybinding)
  139. .map((item) => {
  140. // Special case these commands since they come built-in with VS Code and don't use 'commandArgs'
  141. let commandArgs = item.commandArgs;
  142. if (item.command === organizeImportsCommandId) {
  143. commandArgs = { kind: CodeActionKind.SourceOrganizeImports.value };
  144. }
  145. else if (item.command === fixAllCommandId) {
  146. commandArgs = { kind: CodeActionKind.SourceFixAll.value };
  147. }
  148. return Object.assign({ resolvedKeybinding: item.resolvedKeybinding }, CodeActionCommandArgs.fromUser(commandArgs, {
  149. kind: CodeActionKind.None,
  150. apply: "never" /* Never */
  151. }));
  152. }));
  153. return (action) => {
  154. if (action.kind) {
  155. const binding = this.bestKeybindingForCodeAction(action, allCodeActionBindings.getValue());
  156. return binding === null || binding === void 0 ? void 0 : binding.resolvedKeybinding;
  157. }
  158. return undefined;
  159. };
  160. }
  161. bestKeybindingForCodeAction(action, candidates) {
  162. if (!action.kind) {
  163. return undefined;
  164. }
  165. const kind = new CodeActionKind(action.kind);
  166. return candidates
  167. .filter(candidate => candidate.kind.contains(kind))
  168. .filter(candidate => {
  169. if (candidate.preferred) {
  170. // If the candidate keybinding only applies to preferred actions, the this action must also be preferred
  171. return action.isPreferred;
  172. }
  173. return true;
  174. })
  175. .reduceRight((currentBest, candidate) => {
  176. if (!currentBest) {
  177. return candidate;
  178. }
  179. // Select the more specific binding
  180. return currentBest.kind.contains(candidate.kind) ? candidate : currentBest;
  181. }, undefined);
  182. }
  183. }
  184. CodeActionKeybindingResolver.codeActionCommands = [
  185. refactorCommandId,
  186. codeActionCommandId,
  187. sourceActionCommandId,
  188. organizeImportsCommandId,
  189. fixAllCommandId
  190. ];