From 5ff2ccfc2876251d6dec420e6897490abd19c46a Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 12 Apr 2018 11:50:21 -0700 Subject: [PATCH] CodeActionProvider.providedKinds (#47702) Adds a new optional `CodeActionProviderMetadata`. This is passed in`registerCodeActionProvider` and contains a list of`CodeActionKinds` that the provider may return. The list is used for deciding when to show the `refactor` and `source action` context menus. It is not used for filtering the returned code actions Possibly helps address #45383 --- .../src/features/organizeImports.ts | 7 ++- .../src/features/refactorProvider.ts | 4 ++ .../src/languageProvider.ts | 8 +++- src/vs/editor/common/modes.ts | 5 +++ .../contrib/quickFix/quickFixCommands.ts | 11 +++-- .../editor/contrib/quickFix/quickFixModel.ts | 43 ++++++++++++++++--- src/vs/vscode.d.ts | 17 +++++++- .../mainThreadLanguageFeatures.ts | 5 ++- src/vs/workbench/api/node/extHost.api.impl.ts | 4 +- src/vs/workbench/api/node/extHost.protocol.ts | 2 +- .../api/node/extHostLanguageFeatures.ts | 4 +- 11 files changed, 87 insertions(+), 23 deletions(-) diff --git a/extensions/typescript-language-features/src/features/organizeImports.ts b/extensions/typescript-language-features/src/features/organizeImports.ts index 8dd28828c34..96bf276d3cb 100644 --- a/extensions/typescript-language-features/src/features/organizeImports.ts +++ b/extensions/typescript-language-features/src/features/organizeImports.ts @@ -91,11 +91,16 @@ export class OrganizeImportsContextManager { export class OrganizeImportsCodeActionProvider implements vscode.CodeActionProvider { + private static readonly organizeImportsKind = vscode.CodeActionKind.Source.append('organizeImports'); public constructor( private readonly client: ITypeScriptServiceClient ) { } + public readonly metadata: vscode.CodeActionProviderMetadata = { + providedCodeActionKinds: [OrganizeImportsCodeActionProvider.organizeImportsKind] + }; + public provideCodeActions( document: vscode.TextDocument, _range: vscode.Range, @@ -110,7 +115,7 @@ export class OrganizeImportsCodeActionProvider implements vscode.CodeActionProvi return []; } - const action = new vscode.CodeAction(localize('oraganizeImportsAction.title', "Organize Imports"), vscode.CodeActionKind.Source.append('organizeImports')); + const action = new vscode.CodeAction(localize('oraganizeImportsAction.title', "Organize Imports"), OrganizeImportsCodeActionProvider.organizeImportsKind); action.command = { title: '', command: OrganizeImportsCommand.Ids[0] }; return [action]; } diff --git a/extensions/typescript-language-features/src/features/refactorProvider.ts b/extensions/typescript-language-features/src/features/refactorProvider.ts index 17cb2d66f2f..352108ed58c 100644 --- a/extensions/typescript-language-features/src/features/refactorProvider.ts +++ b/extensions/typescript-language-features/src/features/refactorProvider.ts @@ -96,6 +96,10 @@ export default class TypeScriptRefactorProvider implements vscode.CodeActionProv commandManager.register(new SelectRefactorCommand(doRefactoringCommand)); } + public readonly metadata: vscode.CodeActionProviderMetadata = { + providedCodeActionKinds: [vscode.CodeActionKind.Refactor] + }; + public async provideCodeActions( document: vscode.TextDocument, _range: vscode.Range, diff --git a/extensions/typescript-language-features/src/languageProvider.ts b/extensions/typescript-language-features/src/languageProvider.ts index 8198daf9e74..5efb11d5a63 100644 --- a/extensions/typescript-language-features/src/languageProvider.ts +++ b/extensions/typescript-language-features/src/languageProvider.ts @@ -119,8 +119,12 @@ export default class LanguageProvider { this.disposables.push(languages.registerSignatureHelpProvider(selector, new (await import('./features/signatureHelpProvider')).default(client), '(', ',')); this.disposables.push(languages.registerRenameProvider(selector, new (await import('./features/renameProvider')).default(client))); this.disposables.push(languages.registerCodeActionsProvider(selector, new (await import('./features/quickFixProvider')).default(client, this.formattingOptionsManager, commandManager, this.diagnosticsManager, this.bufferSyncSupport))); - this.disposables.push(languages.registerCodeActionsProvider(selector, new (await import('./features/refactorProvider')).default(client, this.formattingOptionsManager, commandManager))); - this.disposables.push(languages.registerCodeActionsProvider(selector, new (await import('./features/organizeImports')).OrganizeImportsCodeActionProvider(client))); + + const refactorProvider = new (await import('./features/refactorProvider')).default(client, this.formattingOptionsManager, commandManager); + this.disposables.push(languages.registerCodeActionsProvider(selector, refactorProvider, refactorProvider.metadata)); + + const organizeImportsProvider = new (await import('./features/organizeImports')).OrganizeImportsCodeActionProvider(client); + this.disposables.push(languages.registerCodeActionsProvider(selector, organizeImportsProvider, organizeImportsProvider.metadata)); await this.initFoldingProvider(); this.disposables.push(workspace.onDidChangeConfiguration(c => { diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 64bac6dd228..7d9701574ec 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -364,6 +364,11 @@ export interface CodeActionProvider { * Provide commands for the given document and range. */ provideCodeActions(model: model.ITextModel, range: Range, context: CodeActionContext, token: CancellationToken): CodeAction[] | Thenable; + + /** + * Optional list of of CodeActionKinds that this provider returns. + */ + providedCodeActionKinds?: string[]; } /** diff --git a/src/vs/editor/contrib/quickFix/quickFixCommands.ts b/src/vs/editor/contrib/quickFix/quickFixCommands.ts index 8f85eb069c6..606015d8fab 100644 --- a/src/vs/editor/contrib/quickFix/quickFixCommands.ts +++ b/src/vs/editor/contrib/quickFix/quickFixCommands.ts @@ -25,7 +25,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IMarkerService } from 'vs/platform/markers/common/markers'; import { CodeActionAutoApply, CodeActionFilter, CodeActionKind } from './codeActionTrigger'; import { LightBulbWidget } from './lightBulbWidget'; -import { QuickFixComputeEvent, QuickFixModel } from './quickFixModel'; +import { QuickFixComputeEvent, QuickFixModel, HAS_REFACTOR_PROVIDER, HAS_SOURCE_ACTION_PROVIDER } from './quickFixModel'; import { QuickFixContextMenu } from './quickFixWidget'; export class QuickFixController implements IEditorContribution { @@ -52,7 +52,7 @@ export class QuickFixController implements IEditorContribution { @optional(IFileService) private _fileService: IFileService ) { this._editor = editor; - this._model = new QuickFixModel(this._editor, markerService); + this._model = new QuickFixModel(this._editor, markerService, contextKeyService); this._quickFixContextMenu = new QuickFixContextMenu(editor, contextMenuService, action => this._onApplyCodeAction(action)); this._lightBulbWidget = new LightBulbWidget(editor); @@ -247,7 +247,8 @@ export class RefactorAction extends EditorAction { }, menuOpts: { group: '1_modification', - order: 2 + order: 2, + when: ContextKeyExpr.and(EditorContextKeys.writable, HAS_REFACTOR_PROVIDER), } }); } @@ -273,7 +274,9 @@ export class SourceAction extends EditorAction { precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider), menuOpts: { group: '1_modification', - order: 2.1 + order: 2.1, + when: ContextKeyExpr.and(EditorContextKeys.writable, HAS_SOURCE_ACTION_PROVIDER), + } }); } diff --git a/src/vs/editor/contrib/quickFix/quickFixModel.ts b/src/vs/editor/contrib/quickFix/quickFixModel.ts index 5b6dc111745..ac037d009c0 100644 --- a/src/vs/editor/contrib/quickFix/quickFixModel.ts +++ b/src/vs/editor/contrib/quickFix/quickFixModel.ts @@ -4,18 +4,22 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { Event, Emitter, debounceEvent } from 'vs/base/common/event'; +import { Emitter, Event, debounceEvent } from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IMarkerService } from 'vs/platform/markers/common/markers'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import { CodeActionProviderRegistry, CodeAction } from 'vs/editor/common/modes'; +import { CodeAction, CodeActionProviderRegistry } from 'vs/editor/common/modes'; +import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IMarkerService } from 'vs/platform/markers/common/markers'; +import { CodeActionKind, CodeActionTrigger } from './codeActionTrigger'; import { getCodeActions } from './quickFix'; -import { CodeActionTrigger } from './codeActionTrigger'; -import { Position } from 'vs/editor/common/core/position'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; + +export const HAS_REFACTOR_PROVIDER = new RawContextKey('hasRefactorProvider', false); +export const HAS_SOURCE_ACTION_PROVIDER = new RawContextKey('hasSourceActionProvider', false); export class QuickFixOracle { @@ -141,11 +145,16 @@ export class QuickFixModel { private _quickFixOracle: QuickFixOracle; private _onDidChangeFixes = new Emitter(); private _disposables: IDisposable[] = []; + private readonly _hasRefactorProvider: IContextKey; + private readonly _hasSourceProvider: IContextKey; - constructor(editor: ICodeEditor, markerService: IMarkerService) { + constructor(editor: ICodeEditor, markerService: IMarkerService, contextKeyService: IContextKeyService) { this._editor = editor; this._markerService = markerService; + this._hasRefactorProvider = HAS_REFACTOR_PROVIDER.bindTo(contextKeyService); + this._hasSourceProvider = HAS_SOURCE_ACTION_PROVIDER.bindTo(contextKeyService); + this._disposables.push(this._editor.onDidChangeModel(() => this._update())); this._disposables.push(this._editor.onDidChangeModelLanguage(() => this._update())); this._disposables.push(CodeActionProviderRegistry.onDidChange(this._update, this)); @@ -174,8 +183,28 @@ export class QuickFixModel { && CodeActionProviderRegistry.has(this._editor.getModel()) && !this._editor.getConfiguration().readOnly) { + let hasRefactorProvider = false; + let hasSourceProvider = false; + outer: for (const provider of CodeActionProviderRegistry.all(this._editor.getModel())) { + if (!provider.providedCodeActionKinds) { + continue; + } + for (const providedKind of provider.providedCodeActionKinds) { + hasRefactorProvider = hasRefactorProvider || CodeActionKind.Refactor.contains(providedKind); + hasSourceProvider = hasSourceProvider || CodeActionKind.Source.contains(providedKind); + if (hasRefactorProvider && hasSourceProvider) { + break outer; + } + } + } + + this._hasRefactorProvider.set(hasRefactorProvider); + this._hasSourceProvider.set(hasSourceProvider); + this._quickFixOracle = new QuickFixOracle(this._editor, this._markerService, p => this._onDidChangeFixes.fire(p)); this._quickFixOracle.trigger({ type: 'auto' }); + } else { + this._hasRefactorProvider.reset(); } } diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 4d1af2090d5..5f416533b57 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -2034,7 +2034,6 @@ declare module 'vscode' { * A code action can be any command that is [known](#commands.getCommands) to the system. */ export interface CodeActionProvider { - /** * Provide commands for the given document and range. * @@ -2048,6 +2047,19 @@ declare module 'vscode' { provideCodeActions(document: TextDocument, range: Range, context: CodeActionContext, token: CancellationToken): ProviderResult<(Command | CodeAction)[]>; } + /** + * Metadata about the type of code actions that a [CodeActionProvider](#CodeActionProvider) providers + */ + export interface CodeActionProviderMetadata { + /** + * [CodeActionKinds](#CodeActionKind) that this provider may return. + * + * The list of kinds may be generic, such as `CodeActionKind.Refactor`, or the provider + * may list our every specific kind they provide, such as `CodeActionKind.Refactor.Extract.append('function`)` + */ + readonly providedCodeActionKinds?: CodeActionKind[]; + } + /** * A code lens represents a [command](#Command) that should be shown along with * source text, like the number of references, a way to run tests, etc. @@ -6123,9 +6135,10 @@ declare module 'vscode' { * * @param selector A selector that defines the documents this provider is applicable to. * @param provider A code action provider. + * @param metadata Metadata about the kind of code actions the provider providers. * @return A [disposable](#Disposable) that unregisters this provider when being disposed. */ - export function registerCodeActionsProvider(selector: DocumentSelector, provider: CodeActionProvider): Disposable; + export function registerCodeActionsProvider(selector: DocumentSelector, provider: CodeActionProvider, metadata?: CodeActionProviderMetadata): Disposable; /** * Register a code lens provider. diff --git a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts index 35563ae6dbc..d9057794eab 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts @@ -191,11 +191,12 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- quick fix - $registerQuickFixSupport(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerQuickFixSupport(handle: number, selector: ISerializedDocumentFilter[], providedCodeActionKinds?: string[]): void { this._registrations[handle] = modes.CodeActionProviderRegistry.register(toLanguageSelector(selector), { provideCodeActions: (model: ITextModel, range: EditorRange, context: modes.CodeActionContext, token: CancellationToken): Thenable => { return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$provideCodeActions(handle, model.uri, range, context))).then(MainThreadLanguageFeatures._reviveCodeActionDto); - } + }, + providedCodeActionKinds }); } diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index ba6e88e2150..d40a312f3ef 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -271,8 +271,8 @@ export function createApiFactory( match(selector: vscode.DocumentSelector, document: vscode.TextDocument): number { return score(toLanguageSelector(selector), document.uri, document.languageId, true); }, - registerCodeActionsProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider): vscode.Disposable { - return extHostLanguageFeatures.registerCodeActionProvider(checkSelector(selector), provider); + registerCodeActionsProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider, metadata?: vscode.CodeActionProviderMetadata): vscode.Disposable { + return extHostLanguageFeatures.registerCodeActionProvider(checkSelector(selector), provider, metadata); }, registerCodeLensProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable { return extHostLanguageFeatures.registerCodeLensProvider(checkSelector(selector), provider); diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index f5081461723..c1196c4dc2b 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -273,7 +273,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerHoverProvider(handle: number, selector: ISerializedDocumentFilter[]): void; $registerDocumentHighlightProvider(handle: number, selector: ISerializedDocumentFilter[]): void; $registerReferenceSupport(handle: number, selector: ISerializedDocumentFilter[]): void; - $registerQuickFixSupport(handle: number, selector: ISerializedDocumentFilter[]): void; + $registerQuickFixSupport(handle: number, selector: ISerializedDocumentFilter[], supportedKinds?: string[]): void; $registerDocumentFormattingSupport(handle: number, selector: ISerializedDocumentFilter[]): void; $registerRangeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[]): void; $registerOnTypeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], autoFormatTriggerCharacters: string[]): void; diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index 55d6a6e3d8a..0d4825c77bd 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -1013,9 +1013,9 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { // --- quick fix - registerCodeActionProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider): vscode.Disposable { + registerCodeActionProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider, metadata?: vscode.CodeActionProviderMetadata): vscode.Disposable { const handle = this._addNewAdapter(new CodeActionAdapter(this._documents, this._commands.converter, this._diagnostics, provider)); - this._proxy.$registerQuickFixSupport(handle, this._transformDocumentSelector(selector)); + this._proxy.$registerQuickFixSupport(handle, this._transformDocumentSelector(selector), metadata && metadata.providedCodeActionKinds ? metadata.providedCodeActionKinds.map(kind => kind.value) : undefined); return this._createDisposable(handle); } -- GitLab