Skip to content

Commit

Permalink
Code action intellisense in the keybindings JSON editor
Browse files Browse the repository at this point in the history
Fixes #84033

Enables intellisene in the keybindings json editor for code actions and refactorings. Uses the new contribution point from #82718
  • Loading branch information
mjbvz committed Nov 6, 2019
1 parent c8d64b1 commit 166f925
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 81 deletions.
71 changes: 31 additions & 40 deletions src/vs/editor/contrib/codeAction/codeActionCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { IAnchor } from 'vs/base/browser/ui/contextview/contextview';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Lazy } from 'vs/base/common/lazy';
import { Disposable } from 'vs/base/common/lifecycle';
Expand Down Expand Up @@ -37,6 +38,33 @@ function contextKeyForSupportedActions(kind: CodeActionKind) {
new RegExp('(\\s|^)' + escapeRegExpCharacters(kind.value) + '\\b'));
}

const argsSchema: IJSONSchema = {
type: 'object',
required: ['kind'],
defaultSnippets: [{ body: { kind: '' } }],
properties: {
'kind': {
type: 'string',
description: nls.localize('args.schema.kind', "Kind of the code action to run."),
},
'apply': {
type: 'string',
description: nls.localize('args.schema.apply', "Controls when the returned actions are applied."),
default: CodeActionAutoApply.IfSingle,
enum: [CodeActionAutoApply.First, CodeActionAutoApply.IfSingle, CodeActionAutoApply.Never],
enumDescriptions: [
nls.localize('args.schema.apply.first', "Always apply the first returned code action."),
nls.localize('args.schema.apply.ifSingle', "Apply the first returned code action if it is the only one."),
nls.localize('args.schema.apply.never', "Do not apply the returned code actions."),
]
},
'preferred': {
type: 'boolean',
default: false,
description: nls.localize('args.schema.preferred', "Controls if only preferred code actions should be returned."),
}
}
};

export class QuickFixController extends Disposable implements IEditorContribution {

Expand Down Expand Up @@ -237,20 +265,7 @@ export class CodeActionCommand extends EditorCommand {
description: `Trigger a code action`,
args: [{
name: 'args',
schema: {
'type': 'object',
'required': ['kind'],
'properties': {
'kind': {
'type': 'string'
},
'apply': {
'type': 'string',
'default': 'ifSingle',
'enum': ['first', 'ifSingle', 'never']
}
}
}
schema: argsSchema,
}]
}
});
Expand Down Expand Up @@ -301,19 +316,7 @@ export class RefactorAction extends EditorAction {
description: 'Refactor...',
args: [{
name: 'args',
schema: {
'type': 'object',
'properties': {
'kind': {
'type': 'string'
},
'apply': {
'type': 'string',
'default': 'never',
'enum': ['first', 'ifSingle', 'never']
}
}
}
schema: argsSchema
}]
}
});
Expand Down Expand Up @@ -356,19 +359,7 @@ export class SourceAction extends EditorAction {
description: 'Source Action...',
args: [{
name: 'args',
schema: {
'type': 'object',
'properties': {
'kind': {
'type': 'string'
},
'apply': {
'type': 'string',
'default': 'never',
'enum': ['first', 'ifSingle', 'never']
}
}
}
schema: argsSchema
}]
}
});
Expand Down
10 changes: 5 additions & 5 deletions src/vs/editor/contrib/codeAction/codeActionTrigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class CodeActionKind {
}

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

public intersects(other: CodeActionKind): boolean {
Expand All @@ -35,9 +35,9 @@ export class CodeActionKind {
}

export const enum CodeActionAutoApply {
IfSingle,
First,
Never,
IfSingle = 'ifSingle',
First = 'first',
Never = 'never',
}

export interface CodeActionFilter {
Expand Down Expand Up @@ -95,4 +95,4 @@ export interface CodeActionTrigger {
readonly notAvailableMessage: string;
readonly position: Position;
};
}
}
6 changes: 5 additions & 1 deletion src/vs/editor/standalone/browser/simpleServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo
import { IConfirmation, IConfirmationResult, IDialogOptions, IDialogService, IShowResult } from 'vs/platform/dialogs/common/dialogs';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { AbstractKeybindingService } from 'vs/platform/keybinding/common/abstractKeybindingService';
import { IKeybindingEvent, IKeyboardEvent, KeybindingSource } from 'vs/platform/keybinding/common/keybinding';
import { IKeybindingEvent, IKeyboardEvent, KeybindingSource, KeybindingsSchemaContribution } from 'vs/platform/keybinding/common/keybinding';
import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver';
import { IKeybindingItem, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
Expand Down Expand Up @@ -409,6 +409,10 @@ export class StandaloneKeybindingService extends AbstractKeybindingService {
public _dumpDebugInfoJSON(): string {
return '';
}

public registerSchemaContribution(contribution: KeybindingsSchemaContribution): void {
// noop
}
}

function isConfigurationOverrides(thing: any): thing is IConfigurationOverrides {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { KeyCode, Keybinding, ResolvedKeybinding } from 'vs/base/common/keyCodes
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IContextKeyService, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey';
import { IKeybindingEvent, IKeybindingService, IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding';
import { IKeybindingEvent, IKeybindingService, IKeyboardEvent, KeybindingsSchemaContribution } from 'vs/platform/keybinding/common/keybinding';
import { IResolveResult, KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver';
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
import { INotificationService } from 'vs/platform/notification/common/notification';
Expand Down Expand Up @@ -57,6 +57,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK
public abstract resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding[];
public abstract resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding;
public abstract resolveUserBinding(userBinding: string): ResolvedKeybinding[];
public abstract registerSchemaContribution(contribution: KeybindingsSchemaContribution): void;
public abstract _dumpDebugInfo(): string;
public abstract _dumpDebugInfoJSON(): string;

Expand Down
11 changes: 10 additions & 1 deletion src/vs/platform/keybinding/common/keybinding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
*--------------------------------------------------------------------------------------------*/

import { Event } from 'vs/base/common/event';
import { KeyCode, Keybinding, ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { Keybinding, KeyCode, ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IResolveResult } from 'vs/platform/keybinding/common/keybindingResolver';
Expand Down Expand Up @@ -38,6 +39,12 @@ export interface IKeyboardEvent {
readonly code: string;
}

export interface KeybindingsSchemaContribution {
readonly onDidChange?: Event<void>;

getSchemaAdditions(): IJSONSchema[];
}

export const IKeybindingService = createDecorator<IKeybindingService>('keybindingService');

export interface IKeybindingService {
Expand Down Expand Up @@ -92,6 +99,8 @@ export interface IKeybindingService {
*/
mightProducePrintableCharacter(event: IKeyboardEvent): boolean;

registerSchemaContribution(contribution: KeybindingsSchemaContribution): void;

_dumpDebugInfo(): string;
_dumpDebugInfoJSON(): string;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ suite('AbstractKeybindingService', () => {
public _dumpDebugInfoJSON(): string {
return '';
}

public registerSchemaContribution() {
// noop
}
}

let createTestKeybindingService: (items: ResolvedKeybindingItem[], contextValue?: any) => TestKeybindingService = null!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,8 @@ export class MockKeybindingService implements IKeybindingService {
public _dumpDebugInfoJSON(): string {
return '';
}

public registerSchemaContribution() {
// noop
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,28 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import { CodeActionConfigurationManager, editorConfiguration } from 'vs/workbench/contrib/codeActions/common/configuration';
import { CodeActionWorkbenchContribution, editorConfiguration } from 'vs/workbench/contrib/codeActions/common/configuration';
import { CodeActionsExtensionPoint, codeActionsExtensionPointDescriptor } from 'vs/workbench/contrib/codeActions/common/extensionPoint';
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';

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

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

class WorkbenchContribution {
constructor(
@IKeybindingService keybindingsService: IKeybindingService,
) {
// tslint:disable-next-line: no-unused-expression
new CodeActionWorkbenchContribution(codeActionsExtensionPoint, keybindingsService);
}
}

Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
.registerWorkbenchContribution(
class {
constructor() {
// tslint:disable-next-line: no-unused-expression
new CodeActionConfigurationManager(codeActionsExtensionPoint);
}
},
LifecyclePhase.Eventually);
.registerWorkbenchContribution(WorkbenchContribution, LifecyclePhase.Eventually);
95 changes: 77 additions & 18 deletions src/vs/workbench/contrib/codeActions/common/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@
*--------------------------------------------------------------------------------------------*/

import { flatten } from 'vs/base/common/arrays';
import { Emitter } from 'vs/base/common/event';
import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
import { Disposable } from 'vs/base/common/lifecycle';
import { CodeActionCommand, RefactorAction, SourceAction } from 'vs/editor/contrib/codeAction/codeActionCommands';
import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger';
import * as nls from 'vs/nls';
import { Extensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { CodeActionExtensionPointFields, CodeActionsExtensionPoint } from 'vs/workbench/contrib/codeActions/common/extensionPoint';
import { IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry';

const codeActionsOnSaveDefaultProperties = Object.freeze<IJSONSchemaMap>({
'source.fixAll': {
Expand Down Expand Up @@ -46,31 +50,46 @@ export const editorConfiguration = Object.freeze<IConfigurationNode>({
}
});

export class CodeActionConfigurationManager implements IWorkbenchContribution {
export class CodeActionWorkbenchContribution extends Disposable implements IWorkbenchContribution {

private _contributedCodeActions: CodeActionsExtensionPoint[] = [];

private readonly _onDidChangeContributions = this._register(new Emitter<void>());

constructor(
codeActionsExtensionPoint: IExtensionPoint<CodeActionsExtensionPoint[]>
codeActionsExtensionPoint: IExtensionPoint<CodeActionsExtensionPoint[]>,
keybindingService: IKeybindingService,
) {
super();

codeActionsExtensionPoint.setHandler(extensionPoints => {
const newProperties: IJSONSchemaMap = { ...codeActionsOnSaveDefaultProperties };
for (const [sourceAction, props] of this.getSourceActions(extensionPoints)) {
newProperties[sourceAction] = {
type: 'boolean',
description: nls.localize(
'codeActionsOnSave.generic',
"Controls whether '{0}' actions should be run on file save.",
props.title)
};
}
codeActionsOnSaveSchema.properties = newProperties;
this._contributedCodeActions = flatten(extensionPoints.map(x => x.value));
this.updateConfigurationSchema(this._contributedCodeActions);
this._onDidChangeContributions.fire();
});

Registry.as<IConfigurationRegistry>(Extensions.Configuration)
.notifyConfigurationSchemaUpdated(editorConfiguration);
keybindingService.registerSchemaContribution({
getSchemaAdditions: () => this.getSchemaAdditions(),
onDidChange: this._onDidChangeContributions.event,
});
}

private getSourceActions(extensionPoints: readonly IExtensionPointUser<CodeActionsExtensionPoint[]>[]) {
private updateConfigurationSchema(codeActionContributions: readonly CodeActionsExtensionPoint[]) {
const newProperties: IJSONSchemaMap = { ...codeActionsOnSaveDefaultProperties };
for (const [sourceAction, props] of this.getSourceActions(codeActionContributions)) {
newProperties[sourceAction] = {
type: 'boolean',
description: nls.localize('codeActionsOnSave.generic', "Controls whether '{0}' actions should be run on file save.", props.title)
};
}
codeActionsOnSaveSchema.properties = newProperties;
Registry.as<IConfigurationRegistry>(Extensions.Configuration)
.notifyConfigurationSchemaUpdated(editorConfiguration);
}

private getSourceActions(contributions: readonly CodeActionsExtensionPoint[]) {
const sourceActions = new Map<string, { readonly title: string }>();
for (const contribution of flatten(extensionPoints.map(x => x.value))) {
for (const contribution of contributions) {
const kind = new CodeActionKind(contribution[CodeActionExtensionPointFields.kind]);
const defaultKinds = Object.keys(codeActionsOnSaveDefaultProperties).map(value => new CodeActionKind(value));
if (CodeActionKind.Source.contains(kind)
Expand All @@ -82,4 +101,44 @@ export class CodeActionConfigurationManager implements IWorkbenchContribution {
}
return sourceActions;
}

private getSchemaAdditions(): IJSONSchema[] {
const conditionalSchema = (command: string, values: string[]): IJSONSchema => {
return {
if: {
properties: {
'command': { const: command }
}
},
then: {
required: ['args'],
properties: {
'args': {
required: ['kind'],
properties: {
'kind': {
anyOf: [
{ enum: values },
{ type: 'string' },
]
}
}
}
}
}
};
};

const getActions = (ofKind: CodeActionKind): string[] => {
return this._contributedCodeActions
.map(desc => desc[CodeActionExtensionPointFields.kind])
.filter(kind => ofKind.contains(new CodeActionKind(kind)));
};

return [
conditionalSchema(CodeActionCommand.Id, getActions(CodeActionKind.Empty)),
conditionalSchema(RefactorAction.Id, getActions(CodeActionKind.Refactor)),
conditionalSchema(SourceAction.Id, getActions(CodeActionKind.Source)),
];
}
}
Loading

0 comments on commit 166f925

Please sign in to comment.