diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts index 14307f031e5..a56fb161ee2 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts @@ -30,6 +30,7 @@ import { IMarkerService } from 'vs/platform/markers/common/markers'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { CodeActionAutoApply, CodeActionFilter, CodeActionItem, CodeActionSet, CodeActionTrigger, CodeActionTriggerSource } from '../common/types'; import { CodeActionModel, CodeActionsState } from './codeActionModel'; +import { CancellationToken } from 'vs/base/common/cancellation'; interface IActionShowOptions { @@ -251,6 +252,13 @@ export class CodeActionController extends Disposable implements IEditorContribut }, onHide: () => { this._editor?.focus(); + }, + onFocus: async (action: CodeActionItem, token: CancellationToken) => { + await action.resolve(token); + if (token.isCancellationRequested) { + return; + } + return { canPreview: !!action.action.edit?.edits.length }; } }; diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 8108d979327..7d15a54b44e 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -44,7 +44,8 @@ export function toMenuItems( item: action, group: uncategorizedCodeActionGroup, disabled: !!action.action.disabled, - label: action.action.disabled || action.action.title + label: action.action.disabled || action.action.title, + canPreview: !!action.action.edit?.edits.length, }; }); } diff --git a/src/vs/platform/actionWidget/browser/actionList.ts b/src/vs/platform/actionWidget/browser/actionList.ts index e4422c393d6..6a4c9402ca2 100644 --- a/src/vs/platform/actionWidget/browser/actionList.ts +++ b/src/vs/platform/actionWidget/browser/actionList.ts @@ -6,6 +6,7 @@ import * as dom from 'vs/base/browser/dom'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { IListEvent, IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -24,6 +25,7 @@ export const previewSelectedActionCommand = 'previewSelectedCodeAction'; export interface IActionListDelegate { onHide(didCancel?: boolean): void; onSelect(action: T, preview?: boolean): void; + onFocus?(action: T, cancellationToken: CancellationToken): Promise<{ canPreview: boolean } | void>; } export interface IActionListItem { @@ -32,8 +34,8 @@ export interface IActionListItem { readonly group?: { kind?: any; icon?: ThemeIcon; title: string }; readonly disabled?: boolean; readonly label?: string; - readonly keybinding?: ResolvedKeybinding; + canPreview?: boolean | undefined; } interface IActionMenuTemplateData { @@ -126,7 +128,7 @@ class ActionItemRenderer implements IListRenderer, IAction if (element.disabled) { data.container.title = element.label; } else if (actionTitle && previewTitle) { - if (this._supportsPreview) { + if (this._supportsPreview && element.canPreview) { data.container.title = localize({ key: 'label-preview', comment: ['placeholders are keybindings, e.g "F2 to apply, Shift+F2 to preview"'] }, "{0} to apply, {1} to preview", actionTitle, previewTitle); } else { data.container.title = localize({ key: 'label', comment: ['placeholder is a keybinding, e.g "F2 to apply"'] }, "{0} to apply", actionTitle); @@ -168,6 +170,8 @@ export class ActionList extends Disposable { private readonly _allMenuItems: readonly IActionListItem[]; + private readonly cts = this._register(new CancellationTokenSource()); + constructor( user: string, preview: boolean, @@ -230,6 +234,7 @@ export class ActionList extends Disposable { hide(didCancel?: boolean): void { this._delegate.onHide(didCancel); + this.cts.cancel(); this._contextViewService.hideContextView(); } @@ -302,7 +307,18 @@ export class ActionList extends Disposable { } } - private onListHover(e: IListMouseEvent>): void { + private async onListHover(e: IListMouseEvent>) { + const element = e.element; + if (element && element.item && this.focusCondition(element)) { + if (this._delegate.onFocus && !element.disabled && element.kind === ActionListItemKind.Action) { + const result = await this._delegate.onFocus(element.item, this.cts.token); + element.canPreview = result ? result.canPreview : undefined; + } + if (e.index) { + this._list.splice(e.index, 1, [element]); + } + } + this._list.setFocus(typeof e.index === 'number' ? [e.index] : []); }