提交 c26325fb 编写于 作者: M Matt Bierner

Support excluding subsets of code actions for codeActionsOnSave

Fixes #84602
上级 1f3642a0
...@@ -66,7 +66,7 @@ export function getCodeActions( ...@@ -66,7 +66,7 @@ export function getCodeActions(
const filter = trigger.filter || {}; const filter = trigger.filter || {};
const codeActionContext: CodeActionContext = { const codeActionContext: CodeActionContext = {
only: filter.kind?.value, only: filter.include?.value,
trigger: trigger.type === 'manual' ? CodeActionTriggerKind.Manual : CodeActionTriggerKind.Automatic trigger: trigger.type === 'manual' ? CodeActionTriggerKind.Manual : CodeActionTriggerKind.Automatic
}; };
...@@ -146,7 +146,7 @@ registerLanguageCommand('_executeCodeActionProvider', async function (accessor, ...@@ -146,7 +146,7 @@ registerLanguageCommand('_executeCodeActionProvider', async function (accessor,
const codeActionSet = await getCodeActions( const codeActionSet = await getCodeActions(
model, model,
validatedRangeOrSelection, validatedRangeOrSelection,
{ type: 'manual', filter: { includeSourceActions: true, kind: kind && kind.value ? new CodeActionKind(kind.value) : undefined } }, { type: 'manual', filter: { includeSourceActions: true, include: kind && kind.value ? new CodeActionKind(kind.value) : undefined } },
CancellationToken.None); CancellationToken.None);
setTimeout(() => codeActionSet.dispose(), 100); setTimeout(() => codeActionSet.dispose(), 100);
......
...@@ -240,7 +240,7 @@ export class CodeActionCommand extends EditorCommand { ...@@ -240,7 +240,7 @@ export class CodeActionCommand extends EditorCommand {
? nls.localize('editor.action.codeAction.noneMessage.preferred', "No preferred code actions available") ? nls.localize('editor.action.codeAction.noneMessage.preferred', "No preferred code actions available")
: nls.localize('editor.action.codeAction.noneMessage', "No code actions available"), : nls.localize('editor.action.codeAction.noneMessage', "No code actions available"),
{ {
kind: args.kind, include: args.kind,
includeSourceActions: true, includeSourceActions: true,
onlyIncludePreferredActions: args.preferred, onlyIncludePreferredActions: args.preferred,
}, },
...@@ -293,7 +293,7 @@ export class RefactorAction extends EditorAction { ...@@ -293,7 +293,7 @@ export class RefactorAction extends EditorAction {
? nls.localize('editor.action.refactor.noneMessage.preferred', "No preferred refactorings available") ? nls.localize('editor.action.refactor.noneMessage.preferred', "No preferred refactorings available")
: nls.localize('editor.action.refactor.noneMessage', "No refactorings available"), : nls.localize('editor.action.refactor.noneMessage', "No refactorings available"),
{ {
kind: CodeActionKind.Refactor.contains(args.kind) ? args.kind : CodeActionKind.None, include: CodeActionKind.Refactor.contains(args.kind) ? args.kind : CodeActionKind.None,
onlyIncludePreferredActions: args.preferred, onlyIncludePreferredActions: args.preferred,
}, },
args.apply); args.apply);
...@@ -336,7 +336,7 @@ export class SourceAction extends EditorAction { ...@@ -336,7 +336,7 @@ export class SourceAction extends EditorAction {
? nls.localize('editor.action.source.noneMessage.preferred', "No preferred source actions available") ? nls.localize('editor.action.source.noneMessage.preferred', "No preferred source actions available")
: nls.localize('editor.action.source.noneMessage', "No source actions available"), : nls.localize('editor.action.source.noneMessage', "No source actions available"),
{ {
kind: CodeActionKind.Source.contains(args.kind) ? args.kind : CodeActionKind.None, include: CodeActionKind.Source.contains(args.kind) ? args.kind : CodeActionKind.None,
includeSourceActions: true, includeSourceActions: true,
onlyIncludePreferredActions: args.preferred, onlyIncludePreferredActions: args.preferred,
}, },
...@@ -365,7 +365,7 @@ export class OrganizeImportsAction extends EditorAction { ...@@ -365,7 +365,7 @@ export class OrganizeImportsAction extends EditorAction {
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
return triggerCodeActionsForEditorSelection(editor, return triggerCodeActionsForEditorSelection(editor,
nls.localize('editor.action.organize.noneMessage', "No organize imports action available"), nls.localize('editor.action.organize.noneMessage', "No organize imports action available"),
{ kind: CodeActionKind.SourceOrganizeImports, includeSourceActions: true }, { include: CodeActionKind.SourceOrganizeImports, includeSourceActions: true },
CodeActionAutoApply.IfSingle); CodeActionAutoApply.IfSingle);
} }
} }
...@@ -386,7 +386,7 @@ export class FixAllAction extends EditorAction { ...@@ -386,7 +386,7 @@ export class FixAllAction extends EditorAction {
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
return triggerCodeActionsForEditorSelection(editor, return triggerCodeActionsForEditorSelection(editor,
nls.localize('fixAll.noneMessage', "No fix all action available"), nls.localize('fixAll.noneMessage', "No fix all action available"),
{ kind: CodeActionKind.SourceFixAll, includeSourceActions: true }, { include: CodeActionKind.SourceFixAll, includeSourceActions: true },
CodeActionAutoApply.IfSingle); CodeActionAutoApply.IfSingle);
} }
} }
...@@ -418,7 +418,7 @@ export class AutoFixAction extends EditorAction { ...@@ -418,7 +418,7 @@ export class AutoFixAction extends EditorAction {
return triggerCodeActionsForEditorSelection(editor, return triggerCodeActionsForEditorSelection(editor,
nls.localize('editor.action.autoFix.noneMessage', "No auto fixes available"), nls.localize('editor.action.autoFix.noneMessage', "No auto fixes available"),
{ {
kind: CodeActionKind.QuickFix, include: CodeActionKind.QuickFix,
onlyIncludePreferredActions: true onlyIncludePreferredActions: true
}, },
CodeActionAutoApply.IfSingle); CodeActionAutoApply.IfSingle);
......
...@@ -75,7 +75,7 @@ export class CodeActionUi extends Disposable { ...@@ -75,7 +75,7 @@ export class CodeActionUi extends Disposable {
} }
if (newState.trigger.type === 'manual') { if (newState.trigger.type === 'manual') {
if (newState.trigger.filter && newState.trigger.filter.kind) { if (newState.trigger.filter && newState.trigger.filter.include) {
// Triggered for specific scope // Triggered for specific scope
if (actions.actions.length > 0) { if (actions.actions.length > 0) {
// Apply if we only have one action or requested autoApply // Apply if we only have one action or requested autoApply
......
...@@ -140,20 +140,20 @@ suite('CodeAction', () => { ...@@ -140,20 +140,20 @@ suite('CodeAction', () => {
disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider));
{ {
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None); const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: new CodeActionKind('a') } }, CancellationToken.None);
assert.equal(actions.length, 2); assert.equal(actions.length, 2);
assert.strictEqual(actions[0].title, 'a'); assert.strictEqual(actions[0].title, 'a');
assert.strictEqual(actions[1].title, 'a.b'); assert.strictEqual(actions[1].title, 'a.b');
} }
{ {
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b') } }, CancellationToken.None); const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: new CodeActionKind('a.b') } }, CancellationToken.None);
assert.equal(actions.length, 1); assert.equal(actions.length, 1);
assert.strictEqual(actions[0].title, 'a.b'); assert.strictEqual(actions[0].title, 'a.b');
} }
{ {
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b.c') } }, CancellationToken.None); const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: new CodeActionKind('a.b.c') } }, CancellationToken.None);
assert.equal(actions.length, 0); assert.equal(actions.length, 0);
} }
}); });
...@@ -172,7 +172,7 @@ suite('CodeAction', () => { ...@@ -172,7 +172,7 @@ suite('CodeAction', () => {
disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider));
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None); const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: new CodeActionKind('a') } }, CancellationToken.None);
assert.equal(actions.length, 1); assert.equal(actions.length, 1);
assert.strictEqual(actions[0].title, 'a'); assert.strictEqual(actions[0].title, 'a');
}); });
...@@ -192,12 +192,34 @@ suite('CodeAction', () => { ...@@ -192,12 +192,34 @@ suite('CodeAction', () => {
} }
{ {
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: CodeActionKind.Source, includeSourceActions: true } }, CancellationToken.None); const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: CodeActionKind.Source, includeSourceActions: true } }, CancellationToken.None);
assert.equal(actions.length, 1); assert.equal(actions.length, 1);
assert.strictEqual(actions[0].title, 'a'); assert.strictEqual(actions[0].title, 'a');
} }
}); });
test('getCodeActions should support filtering out some requested source code actions #84602', async function () {
const provider = staticCodeActionProvider(
{ title: 'a', kind: CodeActionKind.Source.value },
{ title: 'b', kind: CodeActionKind.Source.append('test').value },
{ title: 'c', kind: 'c' }
);
disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider));
{
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), {
type: 'auto', filter: {
include: CodeActionKind.Source.append('test'),
excludes: [CodeActionKind.Source],
includeSourceActions: true,
}
}, CancellationToken.None);
assert.equal(actions.length, 1);
assert.strictEqual(actions[0].title, 'b');
}
});
test('getCodeActions should not invoke code action providers filtered out by providedCodeActionKinds', async function () { test('getCodeActions should not invoke code action providers filtered out by providedCodeActionKinds', async function () {
let wasInvoked = false; let wasInvoked = false;
const provider = new class implements modes.CodeActionProvider { const provider = new class implements modes.CodeActionProvider {
...@@ -214,7 +236,7 @@ suite('CodeAction', () => { ...@@ -214,7 +236,7 @@ suite('CodeAction', () => {
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), {
type: 'auto', type: 'auto',
filter: { filter: {
kind: CodeActionKind.QuickFix include: CodeActionKind.QuickFix
} }
}, CancellationToken.None); }, CancellationToken.None);
assert.strictEqual(actions.length, 0); assert.strictEqual(actions.length, 0);
......
...@@ -46,19 +46,20 @@ export const enum CodeActionAutoApply { ...@@ -46,19 +46,20 @@ export const enum CodeActionAutoApply {
} }
export interface CodeActionFilter { export interface CodeActionFilter {
readonly kind?: CodeActionKind; readonly include?: CodeActionKind;
readonly excludes?: readonly CodeActionKind[];
readonly includeSourceActions?: boolean; readonly includeSourceActions?: boolean;
readonly onlyIncludePreferredActions?: boolean; readonly onlyIncludePreferredActions?: boolean;
} }
export function mayIncludeActionsOfKind(filter: CodeActionFilter, providedKind: CodeActionKind): boolean { export function mayIncludeActionsOfKind(filter: CodeActionFilter, providedKind: CodeActionKind): boolean {
// A provided kind may be a subset or superset of our filtered kind. // A provided kind may be a subset or superset of our filtered kind.
if (filter.kind && !filter.kind.intersects(providedKind)) { if (filter.include && !filter.include.intersects(providedKind)) {
return false; return false;
} }
// Don't return source actions unless they are explicitly requested // Don't return source actions unless they are explicitly requested
if (CodeActionKind.Source.contains(providedKind) && !filter.includeSourceActions) { if (!filter.includeSourceActions && CodeActionKind.Source.contains(providedKind)) {
return false; return false;
} }
...@@ -69,8 +70,17 @@ export function filtersAction(filter: CodeActionFilter, action: CodeAction): boo ...@@ -69,8 +70,17 @@ export function filtersAction(filter: CodeActionFilter, action: CodeAction): boo
const actionKind = action.kind ? new CodeActionKind(action.kind) : undefined; const actionKind = action.kind ? new CodeActionKind(action.kind) : undefined;
// Filter out actions by kind // Filter out actions by kind
if (filter.kind) { if (filter.include) {
if (!actionKind || !filter.kind.contains(actionKind)) { if (!actionKind || !filter.include.contains(actionKind)) {
return false;
}
}
if (filter.excludes) {
if (actionKind && filter.excludes.some(exclude => {
// Excludes are overwritten by includes
return exclude.contains(actionKind) && (!filter.include || !filter.include.contains(actionKind));
})) {
return false; return false;
} }
} }
......
...@@ -586,7 +586,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { ...@@ -586,7 +586,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
return getCodeActions( return getCodeActions(
this._editor.getModel()!, this._editor.getModel()!,
new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn), new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn),
{ type: 'manual', filter: { kind: CodeActionKind.QuickFix } }, { type: 'manual', filter: { include: CodeActionKind.QuickFix } },
cancellationToken); cancellationToken);
}); });
} }
......
...@@ -282,6 +282,10 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { ...@@ -282,6 +282,10 @@ class CodeActionOnSaveParticipant implements ISaveParticipant {
return undefined; return undefined;
} }
const excludedActions = Object.keys(setting)
.filter(x => setting[x] === false)
.map(x => new CodeActionKind(x));
const tokenSource = new CancellationTokenSource(); const tokenSource = new CancellationTokenSource();
const timeout = this._configurationService.getValue<number>('editor.codeActionsOnSaveTimeout', settingsOverrides); const timeout = this._configurationService.getValue<number>('editor.codeActionsOnSaveTimeout', settingsOverrides);
...@@ -292,15 +296,15 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { ...@@ -292,15 +296,15 @@ class CodeActionOnSaveParticipant implements ISaveParticipant {
tokenSource.cancel(); tokenSource.cancel();
reject(localize('codeActionsOnSave.didTimeout', "Aborted codeActionsOnSave after {0}ms", timeout)); reject(localize('codeActionsOnSave.didTimeout', "Aborted codeActionsOnSave after {0}ms", timeout));
}, timeout)), }, timeout)),
this.applyOnSaveActions(model, codeActionsOnSave, tokenSource.token) this.applyOnSaveActions(model, codeActionsOnSave, excludedActions, tokenSource.token)
]).finally(() => { ]).finally(() => {
tokenSource.cancel(); tokenSource.cancel();
}); });
} }
private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: CodeActionKind[], token: CancellationToken): Promise<void> { private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: readonly CodeActionKind[], excludes: readonly CodeActionKind[], token: CancellationToken): Promise<void> {
for (const codeActionKind of codeActionsOnSave) { for (const codeActionKind of codeActionsOnSave) {
const actionsToRun = await this.getActionsToRun(model, codeActionKind, token); const actionsToRun = await this.getActionsToRun(model, codeActionKind, excludes, token);
try { try {
await this.applyCodeActions(actionsToRun.actions); await this.applyCodeActions(actionsToRun.actions);
} catch { } catch {
...@@ -317,10 +321,10 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { ...@@ -317,10 +321,10 @@ class CodeActionOnSaveParticipant implements ISaveParticipant {
} }
} }
private getActionsToRun(model: ITextModel, codeActionKind: CodeActionKind, token: CancellationToken) { private getActionsToRun(model: ITextModel, codeActionKind: CodeActionKind, excludes: readonly CodeActionKind[], token: CancellationToken) {
return getCodeActions(model, model.getFullModelRange(), { return getCodeActions(model, model.getFullModelRange(), {
type: 'auto', type: 'auto',
filter: { kind: codeActionKind, includeSourceActions: true }, filter: { include: codeActionKind, excludes: excludes, includeSourceActions: true },
}, token); }, token);
} }
} }
......
...@@ -551,7 +551,7 @@ export class MarkerViewModel extends Disposable { ...@@ -551,7 +551,7 @@ export class MarkerViewModel extends Disposable {
if (model) { if (model) {
if (!this.codeActionsPromise) { if (!this.codeActionsPromise) {
this.codeActionsPromise = createCancelablePromise(cancellationToken => { this.codeActionsPromise = createCancelablePromise(cancellationToken => {
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 => { 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 => {
return this._register(actions); return this._register(actions);
}); });
}); });
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册