diff --git a/src/vs/editor/contrib/codeAction/browser/codeAction.ts b/src/vs/editor/contrib/codeAction/browser/codeAction.ts index 75f7f392e2ee2..72d71d496f0f2 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeAction.ts @@ -22,6 +22,7 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat export const codeActionCommandId = 'editor.action.codeAction'; export const refactorCommandId = 'editor.action.refactor'; +export const refactorPreviewCommandId = 'editor.action.refactor.preview'; export const sourceActionCommandId = 'editor.action.sourceAction'; export const organizeImportsCommandId = 'editor.action.organizeImports'; export const fixAllCommandId = 'editor.action.fixAll'; diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts index 6e4c7a0b42e5f..6043a70dedfde 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts @@ -18,7 +18,7 @@ import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { CodeActionTriggerType } from 'vs/editor/common/languages'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { codeActionCommandId, CodeActionItem, CodeActionSet, fixAllCommandId, organizeImportsCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/browser/codeAction'; +import { codeActionCommandId, CodeActionItem, CodeActionSet, fixAllCommandId, organizeImportsCommandId, refactorCommandId, refactorPreviewCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/browser/codeAction'; import { CodeActionUi } from 'vs/editor/contrib/codeAction/browser/codeActionUi'; import { MessageController } from 'vs/editor/contrib/message/browser/messageController'; import * as nls from 'vs/nls'; @@ -27,8 +27,8 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IMarkerService } from 'vs/platform/markers/common/markers'; -import { INotificationService } from 'vs/platform/notification/common/notification'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; +import { INotificationService } from 'vs/platform/notification/common/notification'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CodeActionModel, CodeActionsState, SUPPORTED_CODE_ACTIONS } from './codeActionModel'; import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionFilter, CodeActionKind, CodeActionTrigger } from './types'; @@ -39,6 +39,26 @@ function contextKeyForSupportedActions(kind: CodeActionKind) { new RegExp('(\\s|^)' + escapeRegExpCharacters(kind.value) + '\\b')); } +function RefactorTrigger(editor: ICodeEditor, userArgs: any, preview: boolean) { + const args = CodeActionCommandArgs.fromUser(userArgs, { + kind: CodeActionKind.Refactor, + apply: CodeActionAutoApply.Never + }); + return triggerCodeActionsForEditorSelection(editor, + typeof userArgs?.kind === 'string' + ? args.preferred + ? nls.localize('editor.action.refactor.noneMessage.preferred.kind', "No preferred refactorings for '{0}' available", userArgs.kind) + : nls.localize('editor.action.refactor.noneMessage.kind', "No refactorings for '{0}' available", userArgs.kind) + : args.preferred + ? nls.localize('editor.action.refactor.noneMessage.preferred', "No preferred refactorings available") + : nls.localize('editor.action.refactor.noneMessage', "No refactorings available"), + { + include: CodeActionKind.Refactor.contains(args.kind) ? args.kind : CodeActionKind.None, + onlyIncludePreferredActions: args.preferred + }, + args.apply, preview); +} + const argsSchema: IJSONSchema = { type: 'object', defaultSnippets: [{ body: { kind: '' } }], @@ -92,11 +112,12 @@ export class QuickFixController extends Disposable implements IEditorContributio this._model = this._register(new CodeActionModel(this._editor, languageFeaturesService.codeActionProvider, markerService, contextKeyService, progressService)); this._register(this._model.onDidChangeState(newState => this.update(newState))); + this._ui = new Lazy(() => this._register(new CodeActionUi(editor, QuickFixAction.Id, AutoFixAction.Id, { - applyCodeAction: async (action, retrigger) => { + applyCodeAction: async (action, retrigger, preview) => { try { - await this._applyCodeAction(action); + await this._applyCodeAction(action, preview); } finally { if (retrigger) { this._trigger({ type: CodeActionTriggerType.Auto, filter: {} }); @@ -118,7 +139,8 @@ export class QuickFixController extends Disposable implements IEditorContributio public manualTriggerAtCurrentPosition( notAvailableMessage: string, filter?: CodeActionFilter, - autoApply?: CodeActionAutoApply + autoApply?: CodeActionAutoApply, + preview?: boolean ): void { if (!this._editor.hasModel()) { return; @@ -126,22 +148,22 @@ export class QuickFixController extends Disposable implements IEditorContributio MessageController.get(this._editor)?.closeMessage(); const triggerPosition = this._editor.getPosition(); - this._trigger({ type: CodeActionTriggerType.Invoke, filter, autoApply, context: { notAvailableMessage, position: triggerPosition } }); + this._trigger({ type: CodeActionTriggerType.Invoke, filter, autoApply, context: { notAvailableMessage, position: triggerPosition }, preview }); } private _trigger(trigger: CodeActionTrigger) { return this._model.trigger(trigger); } - private _applyCodeAction(action: CodeActionItem): Promise { - return this._instantiationService.invokeFunction(applyCodeAction, action, this._editor); + private _applyCodeAction(action: CodeActionItem, preview: boolean): Promise { + return this._instantiationService.invokeFunction(applyCodeAction, action, { preview, editor: this._editor }); } } export async function applyCodeAction( accessor: ServicesAccessor, item: CodeActionItem, - editor?: ICodeEditor, + options?: { preview?: boolean; editor?: ICodeEditor } ): Promise { const bulkEditService = accessor.get(IBulkEditService); const commandService = accessor.get(ICommandService); @@ -171,11 +193,12 @@ export async function applyCodeAction( if (item.action.edit) { await bulkEditService.apply(ResourceEdit.convert(item.action.edit), { - editor, + editor: options?.editor, label: item.action.title, quotableLabel: item.action.title, code: 'undoredo.codeAction', - respectAutoSaveConfig: true + respectAutoSaveConfig: true, + showPreview: options?.preview, }); } @@ -206,12 +229,13 @@ function triggerCodeActionsForEditorSelection( editor: ICodeEditor, notAvailableMessage: string, filter: CodeActionFilter | undefined, - autoApply: CodeActionAutoApply | undefined + autoApply: CodeActionAutoApply | undefined, + preview: boolean = false ): void { if (editor.hasModel()) { const controller = QuickFixController.get(editor); if (controller) { - controller.manualTriggerAtCurrentPosition(notAvailableMessage, filter, autoApply); + controller.manualTriggerAtCurrentPosition(notAvailableMessage, filter, autoApply, preview); } } } @@ -306,23 +330,27 @@ export class RefactorAction extends EditorAction { } public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs: any): void { - const args = CodeActionCommandArgs.fromUser(userArgs, { - kind: CodeActionKind.Refactor, - apply: CodeActionAutoApply.Never + return RefactorTrigger(editor, userArgs, false); + } +} + +export class RefactorPreview extends EditorAction { + + constructor() { + super({ + id: refactorPreviewCommandId, + label: nls.localize('refactor.preview.label', "Refactor with Preview..."), + alias: 'Refactor Preview...', + precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider), + description: { + description: 'Refactor Preview...', + args: [{ name: 'args', schema: argsSchema }] + } }); - return triggerCodeActionsForEditorSelection(editor, - typeof userArgs?.kind === 'string' - ? args.preferred - ? nls.localize('editor.action.refactor.noneMessage.preferred.kind', "No preferred refactorings for '{0}' available", userArgs.kind) - : nls.localize('editor.action.refactor.noneMessage.kind', "No refactorings for '{0}' available", userArgs.kind) - : args.preferred - ? nls.localize('editor.action.refactor.noneMessage.preferred', "No preferred refactorings available") - : nls.localize('editor.action.refactor.noneMessage', "No refactorings available"), - { - include: CodeActionKind.Refactor.contains(args.kind) ? args.kind : CodeActionKind.None, - onlyIncludePreferredActions: args.preferred, - }, - args.apply); + } + + public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs: any): void { + return RefactorTrigger(editor, userArgs, true); } } diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionContributions.ts b/src/vs/editor/contrib/codeAction/browser/codeActionContributions.ts index ddbe57e0e3153..359bbf7941fa7 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionContributions.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionContributions.ts @@ -4,12 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { registerEditorAction, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { AutoFixAction, CodeActionCommand, FixAllAction, OrganizeImportsAction, QuickFixAction, QuickFixController, RefactorAction, SourceAction } from 'vs/editor/contrib/codeAction/browser/codeActionCommands'; +import { AutoFixAction, CodeActionCommand, FixAllAction, OrganizeImportsAction, QuickFixAction, QuickFixController, RefactorAction, RefactorPreview, SourceAction } from 'vs/editor/contrib/codeAction/browser/codeActionCommands'; registerEditorContribution(QuickFixController.ID, QuickFixController); registerEditorAction(QuickFixAction); registerEditorAction(RefactorAction); +registerEditorAction(RefactorPreview); registerEditorAction(SourceAction); registerEditorAction(OrganizeImportsAction); registerEditorAction(AutoFixAction); diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 9978c8a442caa..56da9f50fb306 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -23,7 +23,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; interface CodeActionWidgetDelegate { - onSelectCodeAction: (action: CodeActionItem) => Promise; + onSelectCodeAction: (action: CodeActionItem, trigger: CodeActionTrigger) => Promise; } interface ResolveCodeActionKeybinding { @@ -115,7 +115,7 @@ export class CodeActionMenu extends Disposable { actionsToShow: readonly CodeActionItem[], documentation: readonly Command[] ): IAction[] { - const toCodeActionAction = (item: CodeActionItem): CodeActionAction => new CodeActionAction(item.action, () => this._delegate.onSelectCodeAction(item)); + const toCodeActionAction = (item: CodeActionItem): CodeActionAction => new CodeActionAction(item.action, () => this._delegate.onSelectCodeAction(item, trigger)); const result: IAction[] = actionsToShow .map(toCodeActionAction); diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts index e191d6ba9f52a..09c82ccd2ea85 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts @@ -31,7 +31,7 @@ export class CodeActionUi extends Disposable { quickFixActionId: string, preferredFixActionId: string, private readonly delegate: { - applyCodeAction: (action: CodeActionItem, regtriggerAfterApply: boolean) => Promise; + applyCodeAction: (action: CodeActionItem, regtriggerAfterApply: boolean, preview: boolean) => Promise; }, @IInstantiationService instantiationService: IInstantiationService, ) { @@ -39,8 +39,8 @@ export class CodeActionUi extends Disposable { this._codeActionWidget = new Lazy(() => { return this._register(instantiationService.createInstance(CodeActionMenu, this._editor, { - onSelectCodeAction: async (action) => { - this.delegate.applyCodeAction(action, /* retrigger */ true); + onSelectCodeAction: async (action, trigger) => { + this.delegate.applyCodeAction(action, /* retrigger */ true, Boolean(trigger.preview)); } })); }); @@ -85,7 +85,7 @@ export class CodeActionUi extends Disposable { if (validActionToApply) { try { this._lightBulbWidget.getValue().hide(); - await this.delegate.applyCodeAction(validActionToApply, false); + await this.delegate.applyCodeAction(validActionToApply, false, false); } finally { actions.dispose(); } diff --git a/src/vs/editor/contrib/codeAction/browser/types.ts b/src/vs/editor/contrib/codeAction/browser/types.ts index ae406530c687e..cfc6d34498082 100644 --- a/src/vs/editor/contrib/codeAction/browser/types.ts +++ b/src/vs/editor/contrib/codeAction/browser/types.ts @@ -122,6 +122,7 @@ export interface CodeActionTrigger { readonly notAvailableMessage: string; readonly position: Position; }; + readonly preview?: boolean; } export class CodeActionCommandArgs {