Skip to content

Commit 9813ffd

Browse files
committed
Add resolveCodeAction to internal API, introduce CodeActionItem which knows a CodeAction and the provider, adopt CodeActitemItem in codeAction-land
1 parent 70953ac commit 9813ffd

9 files changed

Lines changed: 96 additions & 66 deletions

File tree

src/vs/editor/common/modes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,11 @@ export interface CodeActionProvider {
654654
*/
655655
provideCodeActions(model: model.ITextModel, range: Range | Selection, context: CodeActionContext, token: CancellationToken): ProviderResult<CodeActionList>;
656656

657+
/**
658+
* Given a code action fill in the edit or command. Will only invoked when missing.
659+
*/
660+
resolveCodeAction?(codeAction: CodeAction, token: CancellationToken): ProviderResult<CodeAction>;
661+
657662
/**
658663
* Optional list of CodeActionKinds that this provider returns.
659664
*/

src/vs/editor/contrib/codeAction/codeAction.ts

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,37 @@ export const sourceActionCommandId = 'editor.action.sourceAction';
2424
export const organizeImportsCommandId = 'editor.action.organizeImports';
2525
export const fixAllCommandId = 'editor.action.fixAll';
2626

27+
export class CodeActionItem {
28+
29+
constructor(
30+
readonly action: modes.CodeAction,
31+
readonly provider: modes.CodeActionProvider | undefined,
32+
) { }
33+
34+
async resolve(token: CancellationToken): Promise<this> {
35+
// TODO@jrieken when is an item resolved already?
36+
if (this.provider?.resolveCodeAction && !this.action.edit && !this.action.command) {
37+
try {
38+
this.provider.resolveCodeAction(this.action, token);
39+
} catch (err) {
40+
onUnexpectedExternalError(err);
41+
}
42+
}
43+
return this;
44+
}
45+
}
46+
2747
export interface CodeActionSet extends IDisposable {
28-
readonly validActions: readonly modes.CodeAction[];
29-
readonly allActions: readonly modes.CodeAction[];
48+
readonly validActions: readonly CodeActionItem[];
49+
readonly allActions: readonly CodeActionItem[];
3050
readonly hasAutoFix: boolean;
3151

3252
readonly documentation: readonly modes.Command[];
3353
}
3454

3555
class ManagedCodeActionSet extends Disposable implements CodeActionSet {
3656

37-
private static codeActionsComparator(a: modes.CodeAction, b: modes.CodeAction): number {
57+
private static codeActionsComparator({ action: a }: CodeActionItem, { action: b }: CodeActionItem): number {
3858
if (a.isPreferred && !b.isPreferred) {
3959
return -1;
4060
} else if (!a.isPreferred && b.isPreferred) {
@@ -54,27 +74,27 @@ class ManagedCodeActionSet extends Disposable implements CodeActionSet {
5474
}
5575
}
5676

57-
public readonly validActions: readonly modes.CodeAction[];
58-
public readonly allActions: readonly modes.CodeAction[];
77+
public readonly validActions: readonly CodeActionItem[];
78+
public readonly allActions: readonly CodeActionItem[];
5979

6080
public constructor(
61-
actions: readonly modes.CodeAction[],
81+
actions: readonly CodeActionItem[],
6282
public readonly documentation: readonly modes.Command[],
6383
disposables: DisposableStore,
6484
) {
6585
super();
6686
this._register(disposables);
6787
this.allActions = mergeSort([...actions], ManagedCodeActionSet.codeActionsComparator);
68-
this.validActions = this.allActions.filter(action => !action.disabled);
88+
this.validActions = this.allActions.filter(({ action }) => !action.disabled);
6989
}
7090

7191
public get hasAutoFix() {
72-
return this.validActions.some(fix => !!fix.kind && CodeActionKind.QuickFix.contains(new CodeActionKind(fix.kind)) && !!fix.isPreferred);
92+
return this.validActions.some(({ action: fix }) => !!fix.kind && CodeActionKind.QuickFix.contains(new CodeActionKind(fix.kind)) && !!fix.isPreferred);
7393
}
7494
}
7595

7696

77-
const emptyCodeActionsResponse = { actions: [] as modes.CodeAction[], documentation: undefined };
97+
const emptyCodeActionsResponse = { actions: [] as CodeActionItem[], documentation: undefined };
7898

7999
export function getCodeActions(
80100
model: ITextModel,
@@ -108,7 +128,10 @@ export function getCodeActions(
108128

109129
const filteredActions = (providedCodeActions?.actions || []).filter(action => action && filtersAction(filter, action));
110130
const documentation = getDocumentation(provider, filteredActions, filter.include);
111-
return { actions: filteredActions, documentation };
131+
return {
132+
actions: filteredActions.map(action => new CodeActionItem(action, provider)),
133+
documentation
134+
};
112135
} catch (err) {
113136
if (isPromiseCanceledError(err)) {
114137
throw err;
@@ -226,5 +249,5 @@ registerLanguageCommand('_executeCodeActionProvider', async function (accessor,
226249
CancellationToken.None);
227250

228251
setTimeout(() => codeActionSet.dispose(), 100);
229-
return codeActionSet.validActions;
252+
return codeActionSet.validActions.map(item => item.action);
230253
});

src/vs/editor/contrib/codeAction/codeActionCommands.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { IAnchor } from 'vs/base/browser/ui/contextview/contextview';
7+
import { CancellationToken } from 'vs/base/common/cancellation';
78
import { IJSONSchema } from 'vs/base/common/jsonSchema';
89
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
910
import { Lazy } from 'vs/base/common/lazy';
@@ -15,8 +16,8 @@ import { IBulkEditService, ResourceEdit } from 'vs/editor/browser/services/bulkE
1516
import { IPosition } from 'vs/editor/common/core/position';
1617
import { IEditorContribution } from 'vs/editor/common/editorCommon';
1718
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
18-
import { CodeAction, CodeActionTriggerType } from 'vs/editor/common/modes';
19-
import { codeActionCommandId, CodeActionSet, fixAllCommandId, organizeImportsCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/codeAction';
19+
import { CodeActionTriggerType } from 'vs/editor/common/modes';
20+
import { codeActionCommandId, CodeActionItem, CodeActionSet, fixAllCommandId, organizeImportsCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/codeAction';
2021
import { CodeActionUi } from 'vs/editor/contrib/codeAction/codeActionUi';
2122
import { MessageController } from 'vs/editor/contrib/message/messageController';
2223
import * as nls from 'vs/nls';
@@ -130,14 +131,14 @@ export class QuickFixController extends Disposable implements IEditorContributio
130131
return this._model.trigger(trigger);
131132
}
132133

133-
private _applyCodeAction(action: CodeAction): Promise<void> {
134+
private _applyCodeAction(action: CodeActionItem): Promise<void> {
134135
return this._instantiationService.invokeFunction(applyCodeAction, action, this._editor);
135136
}
136137
}
137138

138139
export async function applyCodeAction(
139140
accessor: ServicesAccessor,
140-
action: CodeAction,
141+
item: CodeActionItem,
141142
editor?: ICodeEditor,
142143
): Promise<void> {
143144
const bulkEditService = accessor.get(IBulkEditService);
@@ -157,18 +158,20 @@ export async function applyCodeAction(
157158
};
158159

159160
telemetryService.publicLog2<ApplyCodeActionEvent, ApplyCodeEventClassification>('codeAction.applyCodeAction', {
160-
codeActionTitle: action.title,
161-
codeActionKind: action.kind,
162-
codeActionIsPreferred: !!action.isPreferred,
161+
codeActionTitle: item.action.title,
162+
codeActionKind: item.action.kind,
163+
codeActionIsPreferred: !!item.action.isPreferred,
163164
});
164165

165-
if (action.edit) {
166-
await bulkEditService.apply(ResourceEdit.convert(action.edit), { editor, label: action.title });
166+
await item.resolve(CancellationToken.None);
167+
168+
if (item.action.edit) {
169+
await bulkEditService.apply(ResourceEdit.convert(item.action.edit), { editor, label: item.action.title });
167170
}
168171

169-
if (action.command) {
172+
if (item.action.command) {
170173
try {
171-
await commandService.executeCommand(action.command.id, ...(action.command.arguments || []));
174+
await commandService.executeCommand(item.action.command.id, ...(item.action.command.arguments || []));
172175
} catch (err) {
173176
const message = asMessage(err);
174177
notificationService.error(

src/vs/editor/contrib/codeAction/codeActionMenu.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
1414
import { IPosition, Position } from 'vs/editor/common/core/position';
1515
import { ScrollType } from 'vs/editor/common/editorCommon';
1616
import { CodeAction, CodeActionProviderRegistry, Command } from 'vs/editor/common/modes';
17-
import { codeActionCommandId, CodeActionSet, fixAllCommandId, organizeImportsCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/codeAction';
17+
import { codeActionCommandId, CodeActionItem, CodeActionSet, fixAllCommandId, organizeImportsCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/codeAction';
1818
import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionTrigger, CodeActionKind } from 'vs/editor/contrib/codeAction/types';
1919
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
2020
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
2121
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
2222

2323
interface CodeActionWidgetDelegate {
24-
onSelectCodeAction: (action: CodeAction) => Promise<any>;
24+
onSelectCodeAction: (action: CodeActionItem) => Promise<any>;
2525
}
2626

2727
interface ResolveCodeActionKeybinding {
@@ -103,10 +103,10 @@ export class CodeActionMenu extends Disposable {
103103

104104
private getMenuActions(
105105
trigger: CodeActionTrigger,
106-
actionsToShow: readonly CodeAction[],
106+
actionsToShow: readonly CodeActionItem[],
107107
documentation: readonly Command[]
108108
): IAction[] {
109-
const toCodeActionAction = (action: CodeAction): CodeActionAction => new CodeActionAction(action, () => this._delegate.onSelectCodeAction(action));
109+
const toCodeActionAction = (item: CodeActionItem): CodeActionAction => new CodeActionAction(item.action, () => this._delegate.onSelectCodeAction(item));
110110

111111
const result: IAction[] = actionsToShow
112112
.map(toCodeActionAction);
@@ -117,16 +117,16 @@ export class CodeActionMenu extends Disposable {
117117
if (model && result.length) {
118118
for (const provider of CodeActionProviderRegistry.all(model)) {
119119
if (provider._getAdditionalMenuItems) {
120-
allDocumentation.push(...provider._getAdditionalMenuItems({ trigger: trigger.type, only: trigger.filter?.include?.value }, actionsToShow));
120+
allDocumentation.push(...provider._getAdditionalMenuItems({ trigger: trigger.type, only: trigger.filter?.include?.value }, actionsToShow.map(item => item.action)));
121121
}
122122
}
123123
}
124124

125125
if (allDocumentation.length) {
126-
result.push(new Separator(), ...allDocumentation.map(command => toCodeActionAction({
126+
result.push(new Separator(), ...allDocumentation.map(command => toCodeActionAction(new CodeActionItem({
127127
title: command.title,
128128
command: command,
129-
})));
129+
}, undefined))));
130130
}
131131

132132
return result;

src/vs/editor/contrib/codeAction/codeActionUi.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import { Lazy } from 'vs/base/common/lazy';
99
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
1010
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
1111
import { IPosition } from 'vs/editor/common/core/position';
12-
import { CodeAction, CodeActionTriggerType } from 'vs/editor/common/modes';
13-
import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction';
12+
import { CodeActionTriggerType } from 'vs/editor/common/modes';
13+
import { CodeActionItem, CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction';
1414
import { MessageController } from 'vs/editor/contrib/message/messageController';
1515
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
1616
import { CodeActionMenu, CodeActionShowOptions } from './codeActionMenu';
@@ -29,7 +29,7 @@ export class CodeActionUi extends Disposable {
2929
quickFixActionId: string,
3030
preferredFixActionId: string,
3131
private readonly delegate: {
32-
applyCodeAction: (action: CodeAction, regtriggerAfterApply: boolean) => Promise<void>
32+
applyCodeAction: (action: CodeActionItem, regtriggerAfterApply: boolean) => Promise<void>
3333
},
3434
@IInstantiationService instantiationService: IInstantiationService,
3535
) {
@@ -83,8 +83,8 @@ export class CodeActionUi extends Disposable {
8383
// Check to see if there is an action that we would have applied were it not invalid
8484
if (newState.trigger.context) {
8585
const invalidAction = this.getInvalidActionThatWouldHaveBeenApplied(newState.trigger, actions);
86-
if (invalidAction && invalidAction.disabled) {
87-
MessageController.get(this._editor).showMessage(invalidAction.disabled, newState.trigger.context.position);
86+
if (invalidAction && invalidAction.action.disabled) {
87+
MessageController.get(this._editor).showMessage(invalidAction.action.disabled, newState.trigger.context.position);
8888
actions.dispose();
8989
return;
9090
}
@@ -114,21 +114,21 @@ export class CodeActionUi extends Disposable {
114114
}
115115
}
116116

117-
private getInvalidActionThatWouldHaveBeenApplied(trigger: CodeActionTrigger, actions: CodeActionSet): CodeAction | undefined {
117+
private getInvalidActionThatWouldHaveBeenApplied(trigger: CodeActionTrigger, actions: CodeActionSet): CodeActionItem | undefined {
118118
if (!actions.allActions.length) {
119119
return undefined;
120120
}
121121

122122
if ((trigger.autoApply === CodeActionAutoApply.First && actions.validActions.length === 0)
123123
|| (trigger.autoApply === CodeActionAutoApply.IfSingle && actions.allActions.length === 1)
124124
) {
125-
return actions.allActions.find(action => action.disabled);
125+
return actions.allActions.find(({ action }) => action.disabled);
126126
}
127127

128128
return undefined;
129129
}
130130

131-
private tryGetValidActionToApply(trigger: CodeActionTrigger, actions: CodeActionSet): CodeAction | undefined {
131+
private tryGetValidActionToApply(trigger: CodeActionTrigger, actions: CodeActionSet): CodeActionItem | undefined {
132132
if (!actions.validActions.length) {
133133
return undefined;
134134
}

src/vs/editor/contrib/codeAction/test/codeAction.test.ts

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri';
88
import { Range } from 'vs/editor/common/core/range';
99
import { TextModel } from 'vs/editor/common/model/textModel';
1010
import * as modes from 'vs/editor/common/modes';
11-
import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction';
11+
import { CodeActionItem, getCodeActions } from 'vs/editor/contrib/codeAction/codeAction';
1212
import { CodeActionKind } from 'vs/editor/contrib/codeAction/types';
1313
import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers';
1414
import { CancellationToken } from 'vs/base/common/cancellation';
@@ -117,14 +117,14 @@ suite('CodeAction', () => {
117117

118118
const expected = [
119119
// CodeActions with a diagnostics array are shown first ordered by diagnostics.message
120-
testData.diagnostics.abc,
121-
testData.diagnostics.bcd,
120+
new CodeActionItem(testData.diagnostics.abc, provider),
121+
new CodeActionItem(testData.diagnostics.bcd, provider),
122122

123123
// CodeActions without diagnostics are shown in the given order without any further sorting
124-
testData.command.abc,
125-
testData.spelling.bcd, // empty diagnostics array
126-
testData.tsLint.bcd,
127-
testData.tsLint.abc
124+
new CodeActionItem(testData.command.abc, provider),
125+
new CodeActionItem(testData.spelling.bcd, provider), // empty diagnostics array
126+
new CodeActionItem(testData.tsLint.bcd, provider),
127+
new CodeActionItem(testData.tsLint.abc, provider)
128128
];
129129

130130
const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Manual }, Progress.None, CancellationToken.None);
@@ -144,14 +144,14 @@ suite('CodeAction', () => {
144144
{
145145
const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a') } }, Progress.None, CancellationToken.None);
146146
assert.equal(actions.length, 2);
147-
assert.strictEqual(actions[0].title, 'a');
148-
assert.strictEqual(actions[1].title, 'a.b');
147+
assert.strictEqual(actions[0].action.title, 'a');
148+
assert.strictEqual(actions[1].action.title, 'a.b');
149149
}
150150

151151
{
152152
const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a.b') } }, Progress.None, CancellationToken.None);
153153
assert.equal(actions.length, 1);
154-
assert.strictEqual(actions[0].title, 'a.b');
154+
assert.strictEqual(actions[0].action.title, 'a.b');
155155
}
156156

157157
{
@@ -176,7 +176,7 @@ suite('CodeAction', () => {
176176

177177
const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a') } }, Progress.None, CancellationToken.None);
178178
assert.equal(actions.length, 1);
179-
assert.strictEqual(actions[0].title, 'a');
179+
assert.strictEqual(actions[0].action.title, 'a');
180180
});
181181

182182
test('getCodeActions should not return source code action by default', async function () {
@@ -190,13 +190,13 @@ suite('CodeAction', () => {
190190
{
191191
const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto }, Progress.None, CancellationToken.None);
192192
assert.equal(actions.length, 1);
193-
assert.strictEqual(actions[0].title, 'b');
193+
assert.strictEqual(actions[0].action.title, 'b');
194194
}
195195

196196
{
197197
const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: CodeActionKind.Source, includeSourceActions: true } }, Progress.None, CancellationToken.None);
198198
assert.equal(actions.length, 1);
199-
assert.strictEqual(actions[0].title, 'a');
199+
assert.strictEqual(actions[0].action.title, 'a');
200200
}
201201
});
202202

@@ -218,7 +218,7 @@ suite('CodeAction', () => {
218218
}
219219
}, Progress.None, CancellationToken.None);
220220
assert.equal(actions.length, 1);
221-
assert.strictEqual(actions[0].title, 'b');
221+
assert.strictEqual(actions[0].action.title, 'b');
222222
}
223223
});
224224

@@ -255,7 +255,7 @@ suite('CodeAction', () => {
255255
}, Progress.None, CancellationToken.None);
256256
assert.strictEqual(didInvoke, false);
257257
assert.equal(actions.length, 1);
258-
assert.strictEqual(actions[0].title, 'a');
258+
assert.strictEqual(actions[0].action.title, 'a');
259259
}
260260
});
261261

@@ -282,4 +282,3 @@ suite('CodeAction', () => {
282282
assert.strictEqual(wasInvoked, false);
283283
});
284284
});
285-

src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant {
344344
const actionsToRun = await this.getActionsToRun(model, codeActionKind, excludes, getActionProgress, token);
345345
try {
346346
for (const action of actionsToRun.validActions) {
347-
progress.report({ message: localize('codeAction.apply', "Applying code action '{0}'.", action.title) });
347+
progress.report({ message: localize('codeAction.apply', "Applying code action '{0}'.", action.action.title) });
348348
await this.instantiationService.invokeFunction(applyCodeAction, action);
349349
}
350350
} catch {

src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -691,14 +691,14 @@ export class MarkerViewModel extends Disposable {
691691
}
692692

693693
private toActions(codeActions: CodeActionSet): IAction[] {
694-
return codeActions.validActions.map(codeAction => new Action(
695-
codeAction.command ? codeAction.command.id : codeAction.title,
696-
codeAction.title,
694+
return codeActions.validActions.map(item => new Action(
695+
item.action.command ? item.action.command.id : item.action.title,
696+
item.action.title,
697697
undefined,
698698
true,
699699
() => {
700700
return this.openFileAtMarker(this.marker)
701-
.then(() => this.instantiationService.invokeFunction(applyCodeAction, codeAction));
701+
.then(() => this.instantiationService.invokeFunction(applyCodeAction, item));
702702
}));
703703
}
704704

0 commit comments

Comments
 (0)