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

Support excluding subsets of code actions for codeActionsOnSave

Fixes #84602
上级 1f3642a0
......@@ -66,7 +66,7 @@ export function getCodeActions(
const filter = trigger.filter || {};
const codeActionContext: CodeActionContext = {
only: filter.kind?.value,
only: filter.include?.value,
trigger: trigger.type === 'manual' ? CodeActionTriggerKind.Manual : CodeActionTriggerKind.Automatic
};
......@@ -146,7 +146,7 @@ registerLanguageCommand('_executeCodeActionProvider', async function (accessor,
const codeActionSet = await getCodeActions(
model,
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);
setTimeout(() => codeActionSet.dispose(), 100);
......
......@@ -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', "No code actions available"),
{
kind: args.kind,
include: args.kind,
includeSourceActions: true,
onlyIncludePreferredActions: args.preferred,
},
......@@ -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', "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,
},
args.apply);
......@@ -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', "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,
onlyIncludePreferredActions: args.preferred,
},
......@@ -365,7 +365,7 @@ export class OrganizeImportsAction extends EditorAction {
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
return triggerCodeActionsForEditorSelection(editor,
nls.localize('editor.action.organize.noneMessage', "No organize imports action available"),
{ kind: CodeActionKind.SourceOrganizeImports, includeSourceActions: true },
{ include: CodeActionKind.SourceOrganizeImports, includeSourceActions: true },
CodeActionAutoApply.IfSingle);
}
}
......@@ -386,7 +386,7 @@ export class FixAllAction extends EditorAction {
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
return triggerCodeActionsForEditorSelection(editor,
nls.localize('fixAll.noneMessage', "No fix all action available"),
{ kind: CodeActionKind.SourceFixAll, includeSourceActions: true },
{ include: CodeActionKind.SourceFixAll, includeSourceActions: true },
CodeActionAutoApply.IfSingle);
}
}
......@@ -418,7 +418,7 @@ export class AutoFixAction extends EditorAction {
return triggerCodeActionsForEditorSelection(editor,
nls.localize('editor.action.autoFix.noneMessage', "No auto fixes available"),
{
kind: CodeActionKind.QuickFix,
include: CodeActionKind.QuickFix,
onlyIncludePreferredActions: true
},
CodeActionAutoApply.IfSingle);
......
......@@ -75,7 +75,7 @@ export class CodeActionUi extends Disposable {
}
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
if (actions.actions.length > 0) {
// Apply if we only have one action or requested autoApply
......
......@@ -140,20 +140,20 @@ suite('CodeAction', () => {
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.strictEqual(actions[0].title, 'a');
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.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);
}
});
......@@ -172,7 +172,7 @@ suite('CodeAction', () => {
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.strictEqual(actions[0].title, 'a');
});
......@@ -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.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 () {
let wasInvoked = false;
const provider = new class implements modes.CodeActionProvider {
......@@ -214,7 +236,7 @@ suite('CodeAction', () => {
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), {
type: 'auto',
filter: {
kind: CodeActionKind.QuickFix
include: CodeActionKind.QuickFix
}
}, CancellationToken.None);
assert.strictEqual(actions.length, 0);
......
......@@ -46,19 +46,20 @@ export const enum CodeActionAutoApply {
}
export interface CodeActionFilter {
readonly kind?: CodeActionKind;
readonly include?: CodeActionKind;
readonly excludes?: readonly CodeActionKind[];
readonly includeSourceActions?: boolean;
readonly onlyIncludePreferredActions?: boolean;
}
export function mayIncludeActionsOfKind(filter: CodeActionFilter, providedKind: CodeActionKind): boolean {
// 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;
}
// 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;
}
......@@ -69,8 +70,17 @@ export function filtersAction(filter: CodeActionFilter, action: CodeAction): boo
const actionKind = action.kind ? new CodeActionKind(action.kind) : undefined;
// Filter out actions by kind
if (filter.kind) {
if (!actionKind || !filter.kind.contains(actionKind)) {
if (filter.include) {
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;
}
}
......
......@@ -586,7 +586,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
return getCodeActions(
this._editor.getModel()!,
new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn),
{ type: 'manual', filter: { kind: CodeActionKind.QuickFix } },
{ type: 'manual', filter: { include: CodeActionKind.QuickFix } },
cancellationToken);
});
}
......
......@@ -282,6 +282,10 @@ class CodeActionOnSaveParticipant implements ISaveParticipant {
return undefined;
}
const excludedActions = Object.keys(setting)
.filter(x => setting[x] === false)
.map(x => new CodeActionKind(x));
const tokenSource = new CancellationTokenSource();
const timeout = this._configurationService.getValue<number>('editor.codeActionsOnSaveTimeout', settingsOverrides);
......@@ -292,15 +296,15 @@ class CodeActionOnSaveParticipant implements ISaveParticipant {
tokenSource.cancel();
reject(localize('codeActionsOnSave.didTimeout', "Aborted codeActionsOnSave after {0}ms", timeout));
}, timeout)),
this.applyOnSaveActions(model, codeActionsOnSave, tokenSource.token)
this.applyOnSaveActions(model, codeActionsOnSave, excludedActions, tokenSource.token)
]).finally(() => {
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) {
const actionsToRun = await this.getActionsToRun(model, codeActionKind, token);
const actionsToRun = await this.getActionsToRun(model, codeActionKind, excludes, token);
try {
await this.applyCodeActions(actionsToRun.actions);
} catch {
......@@ -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(), {
type: 'auto',
filter: { kind: codeActionKind, includeSourceActions: true },
filter: { include: codeActionKind, excludes: excludes, includeSourceActions: true },
}, token);
}
}
......
......@@ -551,7 +551,7 @@ export class MarkerViewModel extends Disposable {
if (model) {
if (!this.codeActionsPromise) {
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);
});
});
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册