From 827e94edefbf51f48629e23bee14344e5aeb0c37 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 20 Nov 2019 17:08:42 -0800 Subject: [PATCH] Hook up basic alert of why a code action could not be applied For #85160 --- .../src/features/refactor.ts | 17 ++++- .../editor/contrib/codeAction/codeActionUi.ts | 66 +++++++++++++++---- .../contrib/codeAction/codeActionWidget.ts | 4 +- 3 files changed, 71 insertions(+), 16 deletions(-) diff --git a/extensions/typescript-language-features/src/features/refactor.ts b/extensions/typescript-language-features/src/features/refactor.ts index a55236781b6..a6ed687fbc2 100644 --- a/extensions/typescript-language-features/src/features/refactor.ts +++ b/extensions/typescript-language-features/src/features/refactor.ts @@ -238,9 +238,14 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider { return undefined; } - return this.convertApplicableRefactors(response.body, document, rangeOrSelection); + const actions = this.convertApplicableRefactors(response.body, document, rangeOrSelection); + if (!context.only) { + return actions; + } + return this.appendInvalidActions(actions); } + private convertApplicableRefactors( body: Proto.ApplicableRefactorInfo[], document: vscode.TextDocument, @@ -305,6 +310,16 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider { } return false; } + + private appendInvalidActions(actions: vscode.CodeAction[]): vscode.CodeAction[] { + if (!actions.some(action => action.kind && Extract_Constant.kind.contains(action.kind))) { + const disabledAction = new vscode.CodeAction('Extract to constant', Extract_Constant.kind); + disabledAction.disabled = localize('extract.disabled', "The current selection cannot be extracted"); + disabledAction.isPreferred = true; + actions.push(disabledAction); + } + return actions; + } } export function register( diff --git a/src/vs/editor/contrib/codeAction/codeActionUi.ts b/src/vs/editor/contrib/codeAction/codeActionUi.ts index 40c6dd2d04d..57e857cf285 100644 --- a/src/vs/editor/contrib/codeAction/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/codeActionUi.ts @@ -3,21 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; +import { find } from 'vs/base/common/arrays'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { Lazy } from 'vs/base/common/lazy'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IPosition } from 'vs/editor/common/core/position'; import { CodeAction } from 'vs/editor/common/modes'; import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; import { MessageController } from 'vs/editor/contrib/message/messageController'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { CodeActionsState } from './codeActionModel'; -import { CodeActionAutoApply } from './types'; import { CodeActionWidget } from './codeActionWidget'; import { LightBulbWidget } from './lightBulbWidget'; -import { IPosition } from 'vs/editor/common/core/position'; -import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; -import { Lazy } from 'vs/base/common/lazy'; +import { CodeActionAutoApply, CodeActionTrigger } from './types'; export class CodeActionUi extends Disposable { @@ -68,7 +69,7 @@ export class CodeActionUi extends Disposable { this._lightBulbWidget.getValue().update(actions, newState.position); - if (!actions.validActions.length && newState.trigger.context) { + if (!actions.allActions.length && newState.trigger.context) { MessageController.get(this._editor).showMessage(newState.trigger.context.notAvailableMessage, newState.trigger.context.position); this._activeCodeActions.value = actions; return; @@ -76,18 +77,29 @@ export class CodeActionUi extends Disposable { if (newState.trigger.type === 'manual') { if (newState.trigger.filter?.include) { // Triggered for specific scope - if (actions.validActions.length > 0) { - // Apply if we only have one action or requested autoApply - if (newState.trigger.autoApply === CodeActionAutoApply.First || (newState.trigger.autoApply === CodeActionAutoApply.IfSingle && actions.validActions.length === 1)) { - try { - await this.delegate.applyCodeAction(actions.validActions[0], false); - } finally { - actions.dispose(); - } + // Check to see if we want to auto apply. + + const validActionToApply = this.tryGetValidActionToApply(newState.trigger, actions); + if (validActionToApply) { + try { + await this.delegate.applyCodeAction(validActionToApply, false); + } finally { + actions.dispose(); + } + return; + } + + // Check to see if there is an action that we would have applied were it not invalid + if (newState.trigger.context) { + const invalidAction = this.getInvalidActionThatWouldHaveBeenApplied(newState.trigger, actions); + if (invalidAction && invalidAction.disabled) { + MessageController.get(this._editor).showMessage(invalidAction.disabled, newState.trigger.context.position); + actions.dispose(); return; } } } + this._activeCodeActions.value = actions; this._codeActionWidget.getValue().show(actions, newState.position); } else { @@ -101,6 +113,34 @@ export class CodeActionUi extends Disposable { } } + private getInvalidActionThatWouldHaveBeenApplied(trigger: CodeActionTrigger, actions: CodeActionSet): CodeAction | undefined { + if (!actions.allActions.length) { + return undefined; + } + + if ((trigger.autoApply === CodeActionAutoApply.First && actions.validActions.length === 0) + || (trigger.autoApply === CodeActionAutoApply.IfSingle && actions.allActions.length === 1) + ) { + return find(actions.allActions, action => action.disabled); + } + + return undefined; + } + + private tryGetValidActionToApply(trigger: CodeActionTrigger, actions: CodeActionSet): CodeAction | undefined { + if (!actions.validActions.length) { + return undefined; + } + + if ((trigger.autoApply === CodeActionAutoApply.First && actions.validActions.length > 0) + || (trigger.autoApply === CodeActionAutoApply.IfSingle && actions.validActions.length === 1) + ) { + return actions.validActions[0]; + } + + return undefined; + } + public async showCodeActionList(actions: CodeActionSet, at: IAnchor | IPosition): Promise { this._codeActionWidget.getValue().show(actions, at); } diff --git a/src/vs/editor/contrib/codeAction/codeActionWidget.ts b/src/vs/editor/contrib/codeAction/codeActionWidget.ts index b0608b3dbcb..8abde0d0ebe 100644 --- a/src/vs/editor/contrib/codeAction/codeActionWidget.ts +++ b/src/vs/editor/contrib/codeAction/codeActionWidget.ts @@ -64,7 +64,7 @@ export class CodeActionWidget extends Disposable { } public async show(codeActions: CodeActionSet, at: IAnchor | IPosition): Promise { - if (!codeActions.validActions.length) { + if (!codeActions.allActions.length) { this._visible = false; return; } @@ -78,7 +78,7 @@ export class CodeActionWidget extends Disposable { this._visible = true; this._showingActions.value = codeActions; - const actions = codeActions.validActions.map(action => + const actions = codeActions.allActions.map(action => new CodeActionAction(action, () => this._delegate.onSelectCodeAction(action))); const anchor = Position.isIPosition(at) ? this._toCoords(at) : at || { x: 0, y: 0 };