fd3692147cae5c057d59bb670021f204d931b14ed49af26f13687c7bf50775ede3cc4a73b1dda9f5aa594b189b068176b9ff00eeebba0ef365d44e108fc553 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  6. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  7. return new (P || (P = Promise))(function (resolve, reject) {
  8. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  9. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  10. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  11. step((generator = generator.apply(thisArg, _arguments || [])).next());
  12. });
  13. };
  14. import { coalesce, equals, isNonEmptyArray } from '../../../../base/common/arrays.js';
  15. import { CancellationToken } from '../../../../base/common/cancellation.js';
  16. import { illegalArgument, isCancellationError, onUnexpectedExternalError } from '../../../../base/common/errors.js';
  17. import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
  18. import { URI } from '../../../../base/common/uri.js';
  19. import { TextModelCancellationTokenSource } from '../../editorState/browser/editorState.js';
  20. import { Range } from '../../../common/core/range.js';
  21. import { Selection } from '../../../common/core/selection.js';
  22. import { IModelService } from '../../../common/services/model.js';
  23. import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';
  24. import { Progress } from '../../../../platform/progress/common/progress.js';
  25. import { CodeActionKind, CodeActionTriggerSource, filtersAction, mayIncludeActionsOfKind } from './types.js';
  26. import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
  27. export const codeActionCommandId = 'editor.action.codeAction';
  28. export const refactorCommandId = 'editor.action.refactor';
  29. export const refactorPreviewCommandId = 'editor.action.refactor.preview';
  30. export const sourceActionCommandId = 'editor.action.sourceAction';
  31. export const organizeImportsCommandId = 'editor.action.organizeImports';
  32. export const fixAllCommandId = 'editor.action.fixAll';
  33. export class CodeActionItem {
  34. constructor(action, provider) {
  35. this.action = action;
  36. this.provider = provider;
  37. }
  38. resolve(token) {
  39. var _a;
  40. return __awaiter(this, void 0, void 0, function* () {
  41. if (((_a = this.provider) === null || _a === void 0 ? void 0 : _a.resolveCodeAction) && !this.action.edit) {
  42. let action;
  43. try {
  44. action = yield this.provider.resolveCodeAction(this.action, token);
  45. }
  46. catch (err) {
  47. onUnexpectedExternalError(err);
  48. }
  49. if (action) {
  50. this.action.edit = action.edit;
  51. }
  52. }
  53. return this;
  54. });
  55. }
  56. }
  57. class ManagedCodeActionSet extends Disposable {
  58. constructor(actions, documentation, disposables) {
  59. super();
  60. this.documentation = documentation;
  61. this._register(disposables);
  62. this.allActions = [...actions].sort(ManagedCodeActionSet.codeActionsComparator);
  63. this.validActions = this.allActions.filter(({ action }) => !action.disabled);
  64. }
  65. static codeActionsComparator({ action: a }, { action: b }) {
  66. if (a.isPreferred && !b.isPreferred) {
  67. return -1;
  68. }
  69. else if (!a.isPreferred && b.isPreferred) {
  70. return 1;
  71. }
  72. if (isNonEmptyArray(a.diagnostics)) {
  73. if (isNonEmptyArray(b.diagnostics)) {
  74. return a.diagnostics[0].message.localeCompare(b.diagnostics[0].message);
  75. }
  76. else {
  77. return -1;
  78. }
  79. }
  80. else if (isNonEmptyArray(b.diagnostics)) {
  81. return 1;
  82. }
  83. else {
  84. return 0; // both have no diagnostics
  85. }
  86. }
  87. get hasAutoFix() {
  88. return this.validActions.some(({ action: fix }) => !!fix.kind && CodeActionKind.QuickFix.contains(new CodeActionKind(fix.kind)) && !!fix.isPreferred);
  89. }
  90. }
  91. const emptyCodeActionsResponse = { actions: [], documentation: undefined };
  92. export function getCodeActions(registry, model, rangeOrSelection, trigger, progress, token) {
  93. var _a;
  94. const filter = trigger.filter || {};
  95. const codeActionContext = {
  96. only: (_a = filter.include) === null || _a === void 0 ? void 0 : _a.value,
  97. trigger: trigger.type,
  98. };
  99. const cts = new TextModelCancellationTokenSource(model, token);
  100. const providers = getCodeActionProviders(registry, model, filter);
  101. const disposables = new DisposableStore();
  102. const promises = providers.map((provider) => __awaiter(this, void 0, void 0, function* () {
  103. try {
  104. progress.report(provider);
  105. const providedCodeActions = yield provider.provideCodeActions(model, rangeOrSelection, codeActionContext, cts.token);
  106. if (providedCodeActions) {
  107. disposables.add(providedCodeActions);
  108. }
  109. if (cts.token.isCancellationRequested) {
  110. return emptyCodeActionsResponse;
  111. }
  112. const filteredActions = ((providedCodeActions === null || providedCodeActions === void 0 ? void 0 : providedCodeActions.actions) || []).filter(action => action && filtersAction(filter, action));
  113. const documentation = getDocumentation(provider, filteredActions, filter.include);
  114. return {
  115. actions: filteredActions.map(action => new CodeActionItem(action, provider)),
  116. documentation
  117. };
  118. }
  119. catch (err) {
  120. if (isCancellationError(err)) {
  121. throw err;
  122. }
  123. onUnexpectedExternalError(err);
  124. return emptyCodeActionsResponse;
  125. }
  126. }));
  127. const listener = registry.onDidChange(() => {
  128. const newProviders = registry.all(model);
  129. if (!equals(newProviders, providers)) {
  130. cts.cancel();
  131. }
  132. });
  133. return Promise.all(promises).then(actions => {
  134. const allActions = actions.map(x => x.actions).flat();
  135. const allDocumentation = coalesce(actions.map(x => x.documentation));
  136. return new ManagedCodeActionSet(allActions, allDocumentation, disposables);
  137. })
  138. .finally(() => {
  139. listener.dispose();
  140. cts.dispose();
  141. });
  142. }
  143. function getCodeActionProviders(registry, model, filter) {
  144. return registry.all(model)
  145. // Don't include providers that we know will not return code actions of interest
  146. .filter(provider => {
  147. if (!provider.providedCodeActionKinds) {
  148. // We don't know what type of actions this provider will return.
  149. return true;
  150. }
  151. return provider.providedCodeActionKinds.some(kind => mayIncludeActionsOfKind(filter, new CodeActionKind(kind)));
  152. });
  153. }
  154. function getDocumentation(provider, providedCodeActions, only) {
  155. if (!provider.documentation) {
  156. return undefined;
  157. }
  158. const documentation = provider.documentation.map(entry => ({ kind: new CodeActionKind(entry.kind), command: entry.command }));
  159. if (only) {
  160. let currentBest;
  161. for (const entry of documentation) {
  162. if (entry.kind.contains(only)) {
  163. if (!currentBest) {
  164. currentBest = entry;
  165. }
  166. else {
  167. // Take best match
  168. if (currentBest.kind.contains(entry.kind)) {
  169. currentBest = entry;
  170. }
  171. }
  172. }
  173. }
  174. if (currentBest) {
  175. return currentBest === null || currentBest === void 0 ? void 0 : currentBest.command;
  176. }
  177. }
  178. // Otherwise, check to see if any of the provided actions match.
  179. for (const action of providedCodeActions) {
  180. if (!action.kind) {
  181. continue;
  182. }
  183. for (const entry of documentation) {
  184. if (entry.kind.contains(new CodeActionKind(action.kind))) {
  185. return entry.command;
  186. }
  187. }
  188. }
  189. return undefined;
  190. }
  191. CommandsRegistry.registerCommand('_executeCodeActionProvider', function (accessor, resource, rangeOrSelection, kind, itemResolveCount) {
  192. return __awaiter(this, void 0, void 0, function* () {
  193. if (!(resource instanceof URI)) {
  194. throw illegalArgument();
  195. }
  196. const { codeActionProvider } = accessor.get(ILanguageFeaturesService);
  197. const model = accessor.get(IModelService).getModel(resource);
  198. if (!model) {
  199. throw illegalArgument();
  200. }
  201. const validatedRangeOrSelection = Selection.isISelection(rangeOrSelection)
  202. ? Selection.liftSelection(rangeOrSelection)
  203. : Range.isIRange(rangeOrSelection)
  204. ? model.validateRange(rangeOrSelection)
  205. : undefined;
  206. if (!validatedRangeOrSelection) {
  207. throw illegalArgument();
  208. }
  209. const include = typeof kind === 'string' ? new CodeActionKind(kind) : undefined;
  210. const codeActionSet = yield getCodeActions(codeActionProvider, model, validatedRangeOrSelection, { type: 1 /* languages.CodeActionTriggerType.Invoke */, triggerAction: CodeActionTriggerSource.Default, filter: { includeSourceActions: true, include } }, Progress.None, CancellationToken.None);
  211. const resolving = [];
  212. const resolveCount = Math.min(codeActionSet.validActions.length, typeof itemResolveCount === 'number' ? itemResolveCount : 0);
  213. for (let i = 0; i < resolveCount; i++) {
  214. resolving.push(codeActionSet.validActions[i].resolve(CancellationToken.None));
  215. }
  216. try {
  217. yield Promise.all(resolving);
  218. return codeActionSet.validActions.map(item => item.action);
  219. }
  220. finally {
  221. setTimeout(() => codeActionSet.dispose(), 100);
  222. }
  223. });
  224. });