Skip to content

Commit c26325f

Browse files
committed
Support excluding subsets of code actions for codeActionsOnSave
Fixes microsoft#84602
1 parent 1f3642a commit c26325f

8 files changed

Lines changed: 63 additions & 27 deletions

File tree

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export function getCodeActions(
6666
const filter = trigger.filter || {};
6767

6868
const codeActionContext: CodeActionContext = {
69-
only: filter.kind?.value,
69+
only: filter.include?.value,
7070
trigger: trigger.type === 'manual' ? CodeActionTriggerKind.Manual : CodeActionTriggerKind.Automatic
7171
};
7272

@@ -146,7 +146,7 @@ registerLanguageCommand('_executeCodeActionProvider', async function (accessor,
146146
const codeActionSet = await getCodeActions(
147147
model,
148148
validatedRangeOrSelection,
149-
{ type: 'manual', filter: { includeSourceActions: true, kind: kind && kind.value ? new CodeActionKind(kind.value) : undefined } },
149+
{ type: 'manual', filter: { includeSourceActions: true, include: kind && kind.value ? new CodeActionKind(kind.value) : undefined } },
150150
CancellationToken.None);
151151

152152
setTimeout(() => codeActionSet.dispose(), 100);

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ export class CodeActionCommand extends EditorCommand {
240240
? nls.localize('editor.action.codeAction.noneMessage.preferred', "No preferred code actions available")
241241
: nls.localize('editor.action.codeAction.noneMessage', "No code actions available"),
242242
{
243-
kind: args.kind,
243+
include: args.kind,
244244
includeSourceActions: true,
245245
onlyIncludePreferredActions: args.preferred,
246246
},
@@ -293,7 +293,7 @@ export class RefactorAction extends EditorAction {
293293
? nls.localize('editor.action.refactor.noneMessage.preferred', "No preferred refactorings available")
294294
: nls.localize('editor.action.refactor.noneMessage', "No refactorings available"),
295295
{
296-
kind: CodeActionKind.Refactor.contains(args.kind) ? args.kind : CodeActionKind.None,
296+
include: CodeActionKind.Refactor.contains(args.kind) ? args.kind : CodeActionKind.None,
297297
onlyIncludePreferredActions: args.preferred,
298298
},
299299
args.apply);
@@ -336,7 +336,7 @@ export class SourceAction extends EditorAction {
336336
? nls.localize('editor.action.source.noneMessage.preferred', "No preferred source actions available")
337337
: nls.localize('editor.action.source.noneMessage', "No source actions available"),
338338
{
339-
kind: CodeActionKind.Source.contains(args.kind) ? args.kind : CodeActionKind.None,
339+
include: CodeActionKind.Source.contains(args.kind) ? args.kind : CodeActionKind.None,
340340
includeSourceActions: true,
341341
onlyIncludePreferredActions: args.preferred,
342342
},
@@ -365,7 +365,7 @@ export class OrganizeImportsAction extends EditorAction {
365365
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
366366
return triggerCodeActionsForEditorSelection(editor,
367367
nls.localize('editor.action.organize.noneMessage', "No organize imports action available"),
368-
{ kind: CodeActionKind.SourceOrganizeImports, includeSourceActions: true },
368+
{ include: CodeActionKind.SourceOrganizeImports, includeSourceActions: true },
369369
CodeActionAutoApply.IfSingle);
370370
}
371371
}
@@ -386,7 +386,7 @@ export class FixAllAction extends EditorAction {
386386
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
387387
return triggerCodeActionsForEditorSelection(editor,
388388
nls.localize('fixAll.noneMessage', "No fix all action available"),
389-
{ kind: CodeActionKind.SourceFixAll, includeSourceActions: true },
389+
{ include: CodeActionKind.SourceFixAll, includeSourceActions: true },
390390
CodeActionAutoApply.IfSingle);
391391
}
392392
}
@@ -418,7 +418,7 @@ export class AutoFixAction extends EditorAction {
418418
return triggerCodeActionsForEditorSelection(editor,
419419
nls.localize('editor.action.autoFix.noneMessage', "No auto fixes available"),
420420
{
421-
kind: CodeActionKind.QuickFix,
421+
include: CodeActionKind.QuickFix,
422422
onlyIncludePreferredActions: true
423423
},
424424
CodeActionAutoApply.IfSingle);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export class CodeActionUi extends Disposable {
7575
}
7676

7777
if (newState.trigger.type === 'manual') {
78-
if (newState.trigger.filter && newState.trigger.filter.kind) {
78+
if (newState.trigger.filter && newState.trigger.filter.include) {
7979
// Triggered for specific scope
8080
if (actions.actions.length > 0) {
8181
// Apply if we only have one action or requested autoApply

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

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,20 +140,20 @@ suite('CodeAction', () => {
140140
disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider));
141141

142142
{
143-
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None);
143+
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: new CodeActionKind('a') } }, CancellationToken.None);
144144
assert.equal(actions.length, 2);
145145
assert.strictEqual(actions[0].title, 'a');
146146
assert.strictEqual(actions[1].title, 'a.b');
147147
}
148148

149149
{
150-
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b') } }, CancellationToken.None);
150+
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: new CodeActionKind('a.b') } }, CancellationToken.None);
151151
assert.equal(actions.length, 1);
152152
assert.strictEqual(actions[0].title, 'a.b');
153153
}
154154

155155
{
156-
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b.c') } }, CancellationToken.None);
156+
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: new CodeActionKind('a.b.c') } }, CancellationToken.None);
157157
assert.equal(actions.length, 0);
158158
}
159159
});
@@ -172,7 +172,7 @@ suite('CodeAction', () => {
172172

173173
disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider));
174174

175-
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None);
175+
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: new CodeActionKind('a') } }, CancellationToken.None);
176176
assert.equal(actions.length, 1);
177177
assert.strictEqual(actions[0].title, 'a');
178178
});
@@ -192,12 +192,34 @@ suite('CodeAction', () => {
192192
}
193193

194194
{
195-
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: CodeActionKind.Source, includeSourceActions: true } }, CancellationToken.None);
195+
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: CodeActionKind.Source, includeSourceActions: true } }, CancellationToken.None);
196196
assert.equal(actions.length, 1);
197197
assert.strictEqual(actions[0].title, 'a');
198198
}
199199
});
200200

201+
test('getCodeActions should support filtering out some requested source code actions #84602', async function () {
202+
const provider = staticCodeActionProvider(
203+
{ title: 'a', kind: CodeActionKind.Source.value },
204+
{ title: 'b', kind: CodeActionKind.Source.append('test').value },
205+
{ title: 'c', kind: 'c' }
206+
);
207+
208+
disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider));
209+
210+
{
211+
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), {
212+
type: 'auto', filter: {
213+
include: CodeActionKind.Source.append('test'),
214+
excludes: [CodeActionKind.Source],
215+
includeSourceActions: true,
216+
}
217+
}, CancellationToken.None);
218+
assert.equal(actions.length, 1);
219+
assert.strictEqual(actions[0].title, 'b');
220+
}
221+
});
222+
201223
test('getCodeActions should not invoke code action providers filtered out by providedCodeActionKinds', async function () {
202224
let wasInvoked = false;
203225
const provider = new class implements modes.CodeActionProvider {
@@ -214,7 +236,7 @@ suite('CodeAction', () => {
214236
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), {
215237
type: 'auto',
216238
filter: {
217-
kind: CodeActionKind.QuickFix
239+
include: CodeActionKind.QuickFix
218240
}
219241
}, CancellationToken.None);
220242
assert.strictEqual(actions.length, 0);

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,20 @@ export const enum CodeActionAutoApply {
4646
}
4747

4848
export interface CodeActionFilter {
49-
readonly kind?: CodeActionKind;
49+
readonly include?: CodeActionKind;
50+
readonly excludes?: readonly CodeActionKind[];
5051
readonly includeSourceActions?: boolean;
5152
readonly onlyIncludePreferredActions?: boolean;
5253
}
5354

5455
export function mayIncludeActionsOfKind(filter: CodeActionFilter, providedKind: CodeActionKind): boolean {
5556
// A provided kind may be a subset or superset of our filtered kind.
56-
if (filter.kind && !filter.kind.intersects(providedKind)) {
57+
if (filter.include && !filter.include.intersects(providedKind)) {
5758
return false;
5859
}
5960

6061
// Don't return source actions unless they are explicitly requested
61-
if (CodeActionKind.Source.contains(providedKind) && !filter.includeSourceActions) {
62+
if (!filter.includeSourceActions && CodeActionKind.Source.contains(providedKind)) {
6263
return false;
6364
}
6465

@@ -69,8 +70,17 @@ export function filtersAction(filter: CodeActionFilter, action: CodeAction): boo
6970
const actionKind = action.kind ? new CodeActionKind(action.kind) : undefined;
7071

7172
// Filter out actions by kind
72-
if (filter.kind) {
73-
if (!actionKind || !filter.kind.contains(actionKind)) {
73+
if (filter.include) {
74+
if (!actionKind || !filter.include.contains(actionKind)) {
75+
return false;
76+
}
77+
}
78+
79+
if (filter.excludes) {
80+
if (actionKind && filter.excludes.some(exclude => {
81+
// Excludes are overwritten by includes
82+
return exclude.contains(actionKind) && (!filter.include || !filter.include.contains(actionKind));
83+
})) {
7484
return false;
7585
}
7686
}

src/vs/editor/contrib/hover/modesContentHover.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
586586
return getCodeActions(
587587
this._editor.getModel()!,
588588
new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn),
589-
{ type: 'manual', filter: { kind: CodeActionKind.QuickFix } },
589+
{ type: 'manual', filter: { include: CodeActionKind.QuickFix } },
590590
cancellationToken);
591591
});
592592
}

src/vs/workbench/api/browser/mainThreadSaveParticipant.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,10 @@ class CodeActionOnSaveParticipant implements ISaveParticipant {
282282
return undefined;
283283
}
284284

285+
const excludedActions = Object.keys(setting)
286+
.filter(x => setting[x] === false)
287+
.map(x => new CodeActionKind(x));
288+
285289
const tokenSource = new CancellationTokenSource();
286290

287291
const timeout = this._configurationService.getValue<number>('editor.codeActionsOnSaveTimeout', settingsOverrides);
@@ -292,15 +296,15 @@ class CodeActionOnSaveParticipant implements ISaveParticipant {
292296
tokenSource.cancel();
293297
reject(localize('codeActionsOnSave.didTimeout', "Aborted codeActionsOnSave after {0}ms", timeout));
294298
}, timeout)),
295-
this.applyOnSaveActions(model, codeActionsOnSave, tokenSource.token)
299+
this.applyOnSaveActions(model, codeActionsOnSave, excludedActions, tokenSource.token)
296300
]).finally(() => {
297301
tokenSource.cancel();
298302
});
299303
}
300304

301-
private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: CodeActionKind[], token: CancellationToken): Promise<void> {
305+
private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: readonly CodeActionKind[], excludes: readonly CodeActionKind[], token: CancellationToken): Promise<void> {
302306
for (const codeActionKind of codeActionsOnSave) {
303-
const actionsToRun = await this.getActionsToRun(model, codeActionKind, token);
307+
const actionsToRun = await this.getActionsToRun(model, codeActionKind, excludes, token);
304308
try {
305309
await this.applyCodeActions(actionsToRun.actions);
306310
} catch {
@@ -317,10 +321,10 @@ class CodeActionOnSaveParticipant implements ISaveParticipant {
317321
}
318322
}
319323

320-
private getActionsToRun(model: ITextModel, codeActionKind: CodeActionKind, token: CancellationToken) {
324+
private getActionsToRun(model: ITextModel, codeActionKind: CodeActionKind, excludes: readonly CodeActionKind[], token: CancellationToken) {
321325
return getCodeActions(model, model.getFullModelRange(), {
322326
type: 'auto',
323-
filter: { kind: codeActionKind, includeSourceActions: true },
327+
filter: { include: codeActionKind, excludes: excludes, includeSourceActions: true },
324328
}, token);
325329
}
326330
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ export class MarkerViewModel extends Disposable {
551551
if (model) {
552552
if (!this.codeActionsPromise) {
553553
this.codeActionsPromise = createCancelablePromise(cancellationToken => {
554-
return getCodeActions(model, new Range(this.marker.range.startLineNumber, this.marker.range.startColumn, this.marker.range.endLineNumber, this.marker.range.endColumn), { type: 'manual', filter: { kind: CodeActionKind.QuickFix } }, cancellationToken).then(actions => {
554+
return getCodeActions(model, new Range(this.marker.range.startLineNumber, this.marker.range.startColumn, this.marker.range.endLineNumber, this.marker.range.endColumn), { type: 'manual', filter: { include: CodeActionKind.QuickFix } }, cancellationToken).then(actions => {
555555
return this._register(actions);
556556
});
557557
});

0 commit comments

Comments
 (0)