Skip to content

Commit 166f925

Browse files
committed
Code action intellisense in the keybindings JSON editor
Fixes microsoft#84033 Enables intellisene in the keybindings json editor for code actions and refactorings. Uses the new contribution point from microsoft#82718
1 parent c8d64b1 commit 166f925

10 files changed

Lines changed: 173 additions & 81 deletions

File tree

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

Lines changed: 31 additions & 40 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 { IJSONSchema } from 'vs/base/common/jsonSchema';
78
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
89
import { Lazy } from 'vs/base/common/lazy';
910
import { Disposable } from 'vs/base/common/lifecycle';
@@ -37,6 +38,33 @@ function contextKeyForSupportedActions(kind: CodeActionKind) {
3738
new RegExp('(\\s|^)' + escapeRegExpCharacters(kind.value) + '\\b'));
3839
}
3940

41+
const argsSchema: IJSONSchema = {
42+
type: 'object',
43+
required: ['kind'],
44+
defaultSnippets: [{ body: { kind: '' } }],
45+
properties: {
46+
'kind': {
47+
type: 'string',
48+
description: nls.localize('args.schema.kind', "Kind of the code action to run."),
49+
},
50+
'apply': {
51+
type: 'string',
52+
description: nls.localize('args.schema.apply', "Controls when the returned actions are applied."),
53+
default: CodeActionAutoApply.IfSingle,
54+
enum: [CodeActionAutoApply.First, CodeActionAutoApply.IfSingle, CodeActionAutoApply.Never],
55+
enumDescriptions: [
56+
nls.localize('args.schema.apply.first', "Always apply the first returned code action."),
57+
nls.localize('args.schema.apply.ifSingle', "Apply the first returned code action if it is the only one."),
58+
nls.localize('args.schema.apply.never', "Do not apply the returned code actions."),
59+
]
60+
},
61+
'preferred': {
62+
type: 'boolean',
63+
default: false,
64+
description: nls.localize('args.schema.preferred', "Controls if only preferred code actions should be returned."),
65+
}
66+
}
67+
};
4068

4169
export class QuickFixController extends Disposable implements IEditorContribution {
4270

@@ -237,20 +265,7 @@ export class CodeActionCommand extends EditorCommand {
237265
description: `Trigger a code action`,
238266
args: [{
239267
name: 'args',
240-
schema: {
241-
'type': 'object',
242-
'required': ['kind'],
243-
'properties': {
244-
'kind': {
245-
'type': 'string'
246-
},
247-
'apply': {
248-
'type': 'string',
249-
'default': 'ifSingle',
250-
'enum': ['first', 'ifSingle', 'never']
251-
}
252-
}
253-
}
268+
schema: argsSchema,
254269
}]
255270
}
256271
});
@@ -301,19 +316,7 @@ export class RefactorAction extends EditorAction {
301316
description: 'Refactor...',
302317
args: [{
303318
name: 'args',
304-
schema: {
305-
'type': 'object',
306-
'properties': {
307-
'kind': {
308-
'type': 'string'
309-
},
310-
'apply': {
311-
'type': 'string',
312-
'default': 'never',
313-
'enum': ['first', 'ifSingle', 'never']
314-
}
315-
}
316-
}
319+
schema: argsSchema
317320
}]
318321
}
319322
});
@@ -356,19 +359,7 @@ export class SourceAction extends EditorAction {
356359
description: 'Source Action...',
357360
args: [{
358361
name: 'args',
359-
schema: {
360-
'type': 'object',
361-
'properties': {
362-
'kind': {
363-
'type': 'string'
364-
},
365-
'apply': {
366-
'type': 'string',
367-
'default': 'never',
368-
'enum': ['first', 'ifSingle', 'never']
369-
}
370-
}
371-
}
362+
schema: argsSchema
372363
}]
373364
}
374365
});

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class CodeActionKind {
2626
}
2727

2828
public contains(other: CodeActionKind): boolean {
29-
return this.equals(other) || startsWith(other.value, this.value + CodeActionKind.sep);
29+
return this.equals(other) || this.value === '' || startsWith(other.value, this.value + CodeActionKind.sep);
3030
}
3131

3232
public intersects(other: CodeActionKind): boolean {
@@ -35,9 +35,9 @@ export class CodeActionKind {
3535
}
3636

3737
export const enum CodeActionAutoApply {
38-
IfSingle,
39-
First,
40-
Never,
38+
IfSingle = 'ifSingle',
39+
First = 'first',
40+
Never = 'never',
4141
}
4242

4343
export interface CodeActionFilter {
@@ -95,4 +95,4 @@ export interface CodeActionTrigger {
9595
readonly notAvailableMessage: string;
9696
readonly position: Position;
9797
};
98-
}
98+
}

src/vs/editor/standalone/browser/simpleServices.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo
3131
import { IConfirmation, IConfirmationResult, IDialogOptions, IDialogService, IShowResult } from 'vs/platform/dialogs/common/dialogs';
3232
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
3333
import { AbstractKeybindingService } from 'vs/platform/keybinding/common/abstractKeybindingService';
34-
import { IKeybindingEvent, IKeyboardEvent, KeybindingSource } from 'vs/platform/keybinding/common/keybinding';
34+
import { IKeybindingEvent, IKeyboardEvent, KeybindingSource, KeybindingsSchemaContribution } from 'vs/platform/keybinding/common/keybinding';
3535
import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver';
3636
import { IKeybindingItem, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
3737
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
@@ -409,6 +409,10 @@ export class StandaloneKeybindingService extends AbstractKeybindingService {
409409
public _dumpDebugInfoJSON(): string {
410410
return '';
411411
}
412+
413+
public registerSchemaContribution(contribution: KeybindingsSchemaContribution): void {
414+
// noop
415+
}
412416
}
413417

414418
function isConfigurationOverrides(thing: any): thing is IConfigurationOverrides {

src/vs/platform/keybinding/common/abstractKeybindingService.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { KeyCode, Keybinding, ResolvedKeybinding } from 'vs/base/common/keyCodes
1111
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
1212
import { ICommandService } from 'vs/platform/commands/common/commands';
1313
import { IContextKeyService, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey';
14-
import { IKeybindingEvent, IKeybindingService, IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding';
14+
import { IKeybindingEvent, IKeybindingService, IKeyboardEvent, KeybindingsSchemaContribution } from 'vs/platform/keybinding/common/keybinding';
1515
import { IResolveResult, KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver';
1616
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
1717
import { INotificationService } from 'vs/platform/notification/common/notification';
@@ -57,6 +57,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK
5757
public abstract resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding[];
5858
public abstract resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding;
5959
public abstract resolveUserBinding(userBinding: string): ResolvedKeybinding[];
60+
public abstract registerSchemaContribution(contribution: KeybindingsSchemaContribution): void;
6061
public abstract _dumpDebugInfo(): string;
6162
public abstract _dumpDebugInfoJSON(): string;
6263

src/vs/platform/keybinding/common/keybinding.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { Event } from 'vs/base/common/event';
7-
import { KeyCode, Keybinding, ResolvedKeybinding } from 'vs/base/common/keyCodes';
7+
import { IJSONSchema } from 'vs/base/common/jsonSchema';
8+
import { Keybinding, KeyCode, ResolvedKeybinding } from 'vs/base/common/keyCodes';
89
import { IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey';
910
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
1011
import { IResolveResult } from 'vs/platform/keybinding/common/keybindingResolver';
@@ -38,6 +39,12 @@ export interface IKeyboardEvent {
3839
readonly code: string;
3940
}
4041

42+
export interface KeybindingsSchemaContribution {
43+
readonly onDidChange?: Event<void>;
44+
45+
getSchemaAdditions(): IJSONSchema[];
46+
}
47+
4148
export const IKeybindingService = createDecorator<IKeybindingService>('keybindingService');
4249

4350
export interface IKeybindingService {
@@ -92,6 +99,8 @@ export interface IKeybindingService {
9299
*/
93100
mightProducePrintableCharacter(event: IKeyboardEvent): boolean;
94101

102+
registerSchemaContribution(contribution: KeybindingsSchemaContribution): void;
103+
95104
_dumpDebugInfo(): string;
96105
_dumpDebugInfoJSON(): string;
97106
}

src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ suite('AbstractKeybindingService', () => {
8787
public _dumpDebugInfoJSON(): string {
8888
return '';
8989
}
90+
91+
public registerSchemaContribution() {
92+
// noop
93+
}
9094
}
9195

9296
let createTestKeybindingService: (items: ResolvedKeybindingItem[], contextValue?: any) => TestKeybindingService = null!;

src/vs/platform/keybinding/test/common/mockKeybindingService.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,8 @@ export class MockKeybindingService implements IKeybindingService {
141141
public _dumpDebugInfoJSON(): string {
142142
return '';
143143
}
144+
145+
public registerSchemaContribution() {
146+
// noop
147+
}
144148
}

src/vs/workbench/contrib/codeActions/common/codeActions.contribution.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,28 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
7+
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
68
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
79
import { Registry } from 'vs/platform/registry/common/platform';
810
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
9-
import { CodeActionConfigurationManager, editorConfiguration } from 'vs/workbench/contrib/codeActions/common/configuration';
11+
import { CodeActionWorkbenchContribution, editorConfiguration } from 'vs/workbench/contrib/codeActions/common/configuration';
1012
import { CodeActionsExtensionPoint, codeActionsExtensionPointDescriptor } from 'vs/workbench/contrib/codeActions/common/extensionPoint';
1113
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
12-
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
1314

1415
const codeActionsExtensionPoint = ExtensionsRegistry.registerExtensionPoint<CodeActionsExtensionPoint[]>(codeActionsExtensionPointDescriptor);
1516

1617
Registry.as<IConfigurationRegistry>(Extensions.Configuration)
1718
.registerConfiguration(editorConfiguration);
1819

20+
class WorkbenchContribution {
21+
constructor(
22+
@IKeybindingService keybindingsService: IKeybindingService,
23+
) {
24+
// tslint:disable-next-line: no-unused-expression
25+
new CodeActionWorkbenchContribution(codeActionsExtensionPoint, keybindingsService);
26+
}
27+
}
28+
1929
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
20-
.registerWorkbenchContribution(
21-
class {
22-
constructor() {
23-
// tslint:disable-next-line: no-unused-expression
24-
new CodeActionConfigurationManager(codeActionsExtensionPoint);
25-
}
26-
},
27-
LifecyclePhase.Eventually);
30+
.registerWorkbenchContribution(WorkbenchContribution, LifecyclePhase.Eventually);

src/vs/workbench/contrib/codeActions/common/configuration.ts

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

66
import { flatten } from 'vs/base/common/arrays';
7+
import { Emitter } from 'vs/base/common/event';
78
import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
9+
import { Disposable } from 'vs/base/common/lifecycle';
10+
import { CodeActionCommand, RefactorAction, SourceAction } from 'vs/editor/contrib/codeAction/codeActionCommands';
811
import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger';
912
import * as nls from 'vs/nls';
1013
import { Extensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
14+
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
1115
import { Registry } from 'vs/platform/registry/common/platform';
1216
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
1317
import { CodeActionExtensionPointFields, CodeActionsExtensionPoint } from 'vs/workbench/contrib/codeActions/common/extensionPoint';
14-
import { IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
18+
import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry';
1519

1620
const codeActionsOnSaveDefaultProperties = Object.freeze<IJSONSchemaMap>({
1721
'source.fixAll': {
@@ -46,31 +50,46 @@ export const editorConfiguration = Object.freeze<IConfigurationNode>({
4650
}
4751
});
4852

49-
export class CodeActionConfigurationManager implements IWorkbenchContribution {
53+
export class CodeActionWorkbenchContribution extends Disposable implements IWorkbenchContribution {
54+
55+
private _contributedCodeActions: CodeActionsExtensionPoint[] = [];
56+
57+
private readonly _onDidChangeContributions = this._register(new Emitter<void>());
58+
5059
constructor(
51-
codeActionsExtensionPoint: IExtensionPoint<CodeActionsExtensionPoint[]>
60+
codeActionsExtensionPoint: IExtensionPoint<CodeActionsExtensionPoint[]>,
61+
keybindingService: IKeybindingService,
5262
) {
63+
super();
64+
5365
codeActionsExtensionPoint.setHandler(extensionPoints => {
54-
const newProperties: IJSONSchemaMap = { ...codeActionsOnSaveDefaultProperties };
55-
for (const [sourceAction, props] of this.getSourceActions(extensionPoints)) {
56-
newProperties[sourceAction] = {
57-
type: 'boolean',
58-
description: nls.localize(
59-
'codeActionsOnSave.generic',
60-
"Controls whether '{0}' actions should be run on file save.",
61-
props.title)
62-
};
63-
}
64-
codeActionsOnSaveSchema.properties = newProperties;
66+
this._contributedCodeActions = flatten(extensionPoints.map(x => x.value));
67+
this.updateConfigurationSchema(this._contributedCodeActions);
68+
this._onDidChangeContributions.fire();
69+
});
6570

66-
Registry.as<IConfigurationRegistry>(Extensions.Configuration)
67-
.notifyConfigurationSchemaUpdated(editorConfiguration);
71+
keybindingService.registerSchemaContribution({
72+
getSchemaAdditions: () => this.getSchemaAdditions(),
73+
onDidChange: this._onDidChangeContributions.event,
6874
});
6975
}
7076

71-
private getSourceActions(extensionPoints: readonly IExtensionPointUser<CodeActionsExtensionPoint[]>[]) {
77+
private updateConfigurationSchema(codeActionContributions: readonly CodeActionsExtensionPoint[]) {
78+
const newProperties: IJSONSchemaMap = { ...codeActionsOnSaveDefaultProperties };
79+
for (const [sourceAction, props] of this.getSourceActions(codeActionContributions)) {
80+
newProperties[sourceAction] = {
81+
type: 'boolean',
82+
description: nls.localize('codeActionsOnSave.generic', "Controls whether '{0}' actions should be run on file save.", props.title)
83+
};
84+
}
85+
codeActionsOnSaveSchema.properties = newProperties;
86+
Registry.as<IConfigurationRegistry>(Extensions.Configuration)
87+
.notifyConfigurationSchemaUpdated(editorConfiguration);
88+
}
89+
90+
private getSourceActions(contributions: readonly CodeActionsExtensionPoint[]) {
7291
const sourceActions = new Map<string, { readonly title: string }>();
73-
for (const contribution of flatten(extensionPoints.map(x => x.value))) {
92+
for (const contribution of contributions) {
7493
const kind = new CodeActionKind(contribution[CodeActionExtensionPointFields.kind]);
7594
const defaultKinds = Object.keys(codeActionsOnSaveDefaultProperties).map(value => new CodeActionKind(value));
7695
if (CodeActionKind.Source.contains(kind)
@@ -82,4 +101,44 @@ export class CodeActionConfigurationManager implements IWorkbenchContribution {
82101
}
83102
return sourceActions;
84103
}
104+
105+
private getSchemaAdditions(): IJSONSchema[] {
106+
const conditionalSchema = (command: string, values: string[]): IJSONSchema => {
107+
return {
108+
if: {
109+
properties: {
110+
'command': { const: command }
111+
}
112+
},
113+
then: {
114+
required: ['args'],
115+
properties: {
116+
'args': {
117+
required: ['kind'],
118+
properties: {
119+
'kind': {
120+
anyOf: [
121+
{ enum: values },
122+
{ type: 'string' },
123+
]
124+
}
125+
}
126+
}
127+
}
128+
}
129+
};
130+
};
131+
132+
const getActions = (ofKind: CodeActionKind): string[] => {
133+
return this._contributedCodeActions
134+
.map(desc => desc[CodeActionExtensionPointFields.kind])
135+
.filter(kind => ofKind.contains(new CodeActionKind(kind)));
136+
};
137+
138+
return [
139+
conditionalSchema(CodeActionCommand.Id, getActions(CodeActionKind.Empty)),
140+
conditionalSchema(RefactorAction.Id, getActions(CodeActionKind.Refactor)),
141+
conditionalSchema(SourceAction.Id, getActions(CodeActionKind.Source)),
142+
];
143+
}
85144
}

0 commit comments

Comments
 (0)