mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-13 15:35:20 +01:00
CodeActionScope (#41782)
* Add CodeActionScope * Replace matches with contains, try using in ts extension * Move filtering to getCodeActions * Basic test * Docs * Fix tests * Hooking up requested scope * Add basic test for requestedScope * Added auto apply logic * Gate refactor provider to only compute refactorings when requested * Making suggested renames * Clean up code action trigger impl to use single Trrigger info object * Rename codeActionScope file and internal CodeActionScope class * Add quick fix base type * Make keybinding API more similar to insertSnippet Take args as an object instead of as an array of values * Clean up docs * scope -> kind * Fixing examples to match Refactor kind
This commit is contained in:
@@ -108,13 +108,17 @@ export default class TypeScriptRefactorProvider implements vscode.CodeActionProv
|
||||
public async provideCodeActions(
|
||||
document: vscode.TextDocument,
|
||||
_range: vscode.Range,
|
||||
_context: vscode.CodeActionContext,
|
||||
context: vscode.CodeActionContext,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<vscode.CodeAction[]> {
|
||||
if (!this.client.apiVersion.has240Features()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (context.only && !vscode.CodeActionKind.Refactor.contains(context.only)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!vscode.window.activeTextEditor) {
|
||||
return [];
|
||||
}
|
||||
@@ -146,7 +150,8 @@ export default class TypeScriptRefactorProvider implements vscode.CodeActionProv
|
||||
title: info.description,
|
||||
command: SelectRefactorCommand.ID,
|
||||
arguments: [document, file, info, range]
|
||||
}
|
||||
},
|
||||
kind: vscode.CodeActionKind.Refactor
|
||||
});
|
||||
} else {
|
||||
for (const action of info.actions) {
|
||||
@@ -156,7 +161,8 @@ export default class TypeScriptRefactorProvider implements vscode.CodeActionProv
|
||||
title: action.description,
|
||||
command: ApplyRefactoringCommand.ID,
|
||||
arguments: [document, file, info.name, action.name, range]
|
||||
}
|
||||
},
|
||||
kind: TypeScriptRefactorProvider.getKind(action)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -166,4 +172,11 @@ export default class TypeScriptRefactorProvider implements vscode.CodeActionProv
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private static getKind(refactor: Proto.RefactorActionInfo) {
|
||||
if (refactor.name.startsWith('function_')) {
|
||||
return vscode.CodeActionKind.RefactorExtract.append('function');
|
||||
}
|
||||
return vscode.CodeActionKind.Refactor;
|
||||
}
|
||||
}
|
||||
@@ -343,6 +343,14 @@ export interface CodeAction {
|
||||
command?: Command;
|
||||
edit?: WorkspaceEdit;
|
||||
diagnostics?: IMarkerData[];
|
||||
kind?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface CodeActionContext {
|
||||
only?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -354,7 +362,7 @@ export interface CodeActionProvider {
|
||||
/**
|
||||
* Provide commands for the given document and range.
|
||||
*/
|
||||
provideCodeActions(model: model.ITextModel, range: Range, token: CancellationToken): CodeAction[] | Thenable<CodeAction[]>;
|
||||
provideCodeActions(model: model.ITextModel, range: Range, context: CodeActionContext, token: CancellationToken): CodeAction[] | Thenable<CodeAction[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
|
||||
export class CodeActionKind {
|
||||
private static readonly sep = '.';
|
||||
|
||||
public static readonly Empty = new CodeActionKind('');
|
||||
|
||||
constructor(
|
||||
public readonly value: string
|
||||
) { }
|
||||
|
||||
public contains(other: string): boolean {
|
||||
return this.value === other || startsWith(other, this.value + CodeActionKind.sep);
|
||||
}
|
||||
}
|
||||
|
||||
export enum CodeActionAutoApply {
|
||||
IfSingle = 1,
|
||||
First = 2,
|
||||
Never = 3
|
||||
}
|
||||
|
||||
export interface CodeActionTrigger {
|
||||
type: 'auto' | 'manual';
|
||||
kind?: CodeActionKind;
|
||||
autoApply?: CodeActionAutoApply;
|
||||
}
|
||||
@@ -14,16 +14,19 @@ import { onUnexpectedExternalError, illegalArgument } from 'vs/base/common/error
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
|
||||
import { CodeActionKind } from './codeActionTrigger';
|
||||
|
||||
export function getCodeActions(model: ITextModel, range: Range): TPromise<CodeAction[]> {
|
||||
export function getCodeActions(model: ITextModel, range: Range, scope?: CodeActionKind): TPromise<CodeAction[]> {
|
||||
|
||||
const allResults: CodeAction[] = [];
|
||||
const promises = CodeActionProviderRegistry.all(model).map(support => {
|
||||
return asWinJsPromise(token => support.provideCodeActions(model, range, token)).then(result => {
|
||||
return asWinJsPromise(token => support.provideCodeActions(model, range, { only: scope ? scope.value : undefined }, token)).then(result => {
|
||||
if (Array.isArray(result)) {
|
||||
for (const quickFix of result) {
|
||||
if (quickFix) {
|
||||
allResults.push(quickFix);
|
||||
if (!scope || (quickFix.kind && scope.contains(quickFix.kind))) {
|
||||
allResults.push(quickFix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,12 @@ import { optional } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IMarkerService } from 'vs/platform/markers/common/markers';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction } from 'vs/editor/browser/editorExtensions';
|
||||
import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { QuickFixContextMenu } from './quickFixWidget';
|
||||
import { LightBulbWidget } from './lightBulbWidget';
|
||||
import { QuickFixModel, QuickFixComputeEvent } from './quickFixModel';
|
||||
import { CodeActionKind, CodeActionAutoApply } from './codeActionTrigger';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { CodeAction } from 'vs/editor/common/modes';
|
||||
import { createBulkEdit } from 'vs/editor/browser/services/bulkEdit';
|
||||
@@ -57,7 +58,7 @@ export class QuickFixController implements IEditorContribution {
|
||||
this._updateLightBulbTitle();
|
||||
|
||||
this._disposables.push(
|
||||
this._quickFixContextMenu.onDidExecuteCodeAction(_ => this._model.trigger('auto')),
|
||||
this._quickFixContextMenu.onDidExecuteCodeAction(_ => this._model.trigger({ type: 'auto' })),
|
||||
this._lightBulbWidget.onClick(this._handleLightBulbSelect, this),
|
||||
this._model.onDidChangeFixes(e => this._onQuickFixEvent(e)),
|
||||
this._keybindingService.onDidUpdateKeybindings(this._updateLightBulbTitle, this)
|
||||
@@ -70,9 +71,21 @@ export class QuickFixController implements IEditorContribution {
|
||||
}
|
||||
|
||||
private _onQuickFixEvent(e: QuickFixComputeEvent): void {
|
||||
if (e && e.type === 'manual') {
|
||||
this._quickFixContextMenu.show(e.fixes, e.position);
|
||||
if (e && e.trigger.kind) {
|
||||
// Triggered for specific scope
|
||||
// Apply if we only have one action or requested autoApply, otherwise show menu
|
||||
e.fixes.then(fixes => {
|
||||
if (e.trigger.autoApply === CodeActionAutoApply.First || (e.trigger.autoApply === CodeActionAutoApply.IfSingle && fixes.length === 1)) {
|
||||
this._onApplyCodeAction(fixes[0]);
|
||||
} else {
|
||||
this._quickFixContextMenu.show(e.fixes, e.position);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (e && e.trigger.type === 'manual') {
|
||||
this._quickFixContextMenu.show(e.fixes, e.position);
|
||||
} else if (e && e.fixes) {
|
||||
// auto magically triggered
|
||||
// * update an existing list of code actions
|
||||
@@ -96,7 +109,11 @@ export class QuickFixController implements IEditorContribution {
|
||||
}
|
||||
|
||||
public triggerFromEditorSelection(): void {
|
||||
this._model.trigger('manual');
|
||||
this._model.trigger({ type: 'manual' });
|
||||
}
|
||||
|
||||
public triggerCodeActionFromEditorSelection(kind?: CodeActionKind, autoApply?: CodeActionAutoApply): void {
|
||||
this._model.trigger({ type: 'manual', kind, autoApply });
|
||||
}
|
||||
|
||||
private _updateLightBulbTitle(): void {
|
||||
@@ -148,5 +165,63 @@ export class QuickFixAction extends EditorAction {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CodeActionCommandArgs {
|
||||
public static fromUser(arg: any): CodeActionCommandArgs {
|
||||
if (!arg || typeof arg !== 'object') {
|
||||
return new CodeActionCommandArgs(CodeActionKind.Empty, CodeActionAutoApply.IfSingle);
|
||||
}
|
||||
return new CodeActionCommandArgs(
|
||||
CodeActionCommandArgs.getKindFromUser(arg),
|
||||
CodeActionCommandArgs.getApplyFromUser(arg));
|
||||
}
|
||||
|
||||
private static getApplyFromUser(arg: any) {
|
||||
switch (typeof arg.apply === 'string' ? arg.apply.toLowerCase() : '') {
|
||||
case 'first':
|
||||
return CodeActionAutoApply.First;
|
||||
|
||||
case 'never':
|
||||
return CodeActionAutoApply.Never;
|
||||
|
||||
case 'ifsingle':
|
||||
default:
|
||||
return CodeActionAutoApply.IfSingle;
|
||||
}
|
||||
}
|
||||
|
||||
private static getKindFromUser(arg: any) {
|
||||
return typeof arg.kind === 'string'
|
||||
? new CodeActionKind(arg.kind)
|
||||
: CodeActionKind.Empty;
|
||||
}
|
||||
|
||||
private constructor(
|
||||
public readonly kind: CodeActionKind,
|
||||
public readonly apply: CodeActionAutoApply
|
||||
) { }
|
||||
}
|
||||
|
||||
export class CodeActionCommand extends EditorCommand {
|
||||
|
||||
static readonly Id = 'editor.action.codeAction';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: CodeActionCommand.Id,
|
||||
precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider)
|
||||
});
|
||||
}
|
||||
|
||||
public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, userArg: any) {
|
||||
const controller = QuickFixController.get(editor);
|
||||
if (controller) {
|
||||
const args = CodeActionCommandArgs.fromUser(userArg);
|
||||
controller.triggerCodeActionFromEditorSelection(args.kind, args.apply);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorContribution(QuickFixController);
|
||||
registerEditorAction(QuickFixAction);
|
||||
registerEditorCommand(new CodeActionCommand());
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { CodeActionProviderRegistry, CodeAction } from 'vs/editor/common/modes';
|
||||
import { getCodeActions } from './quickFix';
|
||||
import { CodeActionTrigger } from './codeActionTrigger';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
|
||||
@@ -36,26 +37,26 @@ export class QuickFixOracle {
|
||||
this._disposables = dispose(this._disposables);
|
||||
}
|
||||
|
||||
trigger(type: 'manual' | 'auto'): void {
|
||||
trigger(trigger: CodeActionTrigger): void {
|
||||
let rangeOrSelection = this._getRangeOfMarker() || this._getRangeOfSelectionUnlessWhitespaceEnclosed();
|
||||
if (!rangeOrSelection && type === 'manual') {
|
||||
if (!rangeOrSelection && trigger.type === 'manual') {
|
||||
rangeOrSelection = this._editor.getSelection();
|
||||
}
|
||||
this._createEventAndSignalChange(type, rangeOrSelection);
|
||||
this._createEventAndSignalChange(trigger, rangeOrSelection);
|
||||
}
|
||||
|
||||
private _onMarkerChanges(resources: URI[]): void {
|
||||
const { uri } = this._editor.getModel();
|
||||
for (const resource of resources) {
|
||||
if (resource.toString() === uri.toString()) {
|
||||
this.trigger('auto');
|
||||
this.trigger({ type: 'auto' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _onCursorChange(): void {
|
||||
this.trigger('auto');
|
||||
this.trigger({ type: 'auto' });
|
||||
}
|
||||
|
||||
private _getRangeOfMarker(): Range {
|
||||
@@ -98,24 +99,24 @@ export class QuickFixOracle {
|
||||
return selection;
|
||||
}
|
||||
|
||||
private _createEventAndSignalChange(type: 'auto' | 'manual', rangeOrSelection: Range | Selection): void {
|
||||
private _createEventAndSignalChange(trigger: CodeActionTrigger, rangeOrSelection: Range | Selection): void {
|
||||
if (!rangeOrSelection) {
|
||||
// cancel
|
||||
this._signalChange({
|
||||
type,
|
||||
trigger,
|
||||
range: undefined,
|
||||
position: undefined,
|
||||
fixes: undefined
|
||||
fixes: undefined,
|
||||
});
|
||||
} else {
|
||||
// actual
|
||||
const model = this._editor.getModel();
|
||||
const range = model.validateRange(rangeOrSelection);
|
||||
const position = rangeOrSelection instanceof Selection ? rangeOrSelection.getPosition() : rangeOrSelection.getStartPosition();
|
||||
const fixes = getCodeActions(model, range);
|
||||
const fixes = getCodeActions(model, range, trigger && trigger.kind);
|
||||
|
||||
this._signalChange({
|
||||
type,
|
||||
trigger,
|
||||
range,
|
||||
position,
|
||||
fixes
|
||||
@@ -125,7 +126,7 @@ export class QuickFixOracle {
|
||||
}
|
||||
|
||||
export interface QuickFixComputeEvent {
|
||||
type: 'auto' | 'manual';
|
||||
trigger: CodeActionTrigger;
|
||||
range: Range;
|
||||
position: Position;
|
||||
fixes: TPromise<CodeAction[]>;
|
||||
@@ -172,13 +173,13 @@ export class QuickFixModel {
|
||||
&& !this._editor.getConfiguration().readOnly) {
|
||||
|
||||
this._quickFixOracle = new QuickFixOracle(this._editor, this._markerService, p => this._onDidChangeFixes.fire(p));
|
||||
this._quickFixOracle.trigger('auto');
|
||||
this._quickFixOracle.trigger({ type: 'auto' });
|
||||
}
|
||||
}
|
||||
|
||||
trigger(type: 'auto' | 'manual'): void {
|
||||
trigger(trigger: CodeActionTrigger): void {
|
||||
if (this._quickFixOracle) {
|
||||
this._quickFixOracle.trigger(type);
|
||||
this._quickFixOracle.trigger(trigger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,11 @@ import * as assert from 'assert';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { CodeActionProviderRegistry, LanguageIdentifier, CodeActionProvider, Command, WorkspaceEdit, IResourceEdit } from 'vs/editor/common/modes';
|
||||
import { CodeActionProviderRegistry, LanguageIdentifier, CodeActionProvider, Command, WorkspaceEdit, IResourceEdit, CodeAction, CodeActionContext } from 'vs/editor/common/modes';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { getCodeActions } from 'vs/editor/contrib/quickFix/quickFix';
|
||||
import { CodeActionKind } from 'vs/editor/contrib/quickFix/codeActionTrigger';
|
||||
|
||||
suite('QuickFix', () => {
|
||||
|
||||
@@ -120,4 +121,52 @@ suite('QuickFix', () => {
|
||||
assert.equal(actions.length, 6);
|
||||
assert.deepEqual(actions, expected);
|
||||
});
|
||||
|
||||
test('getCodeActions should filter by scope', async function () {
|
||||
const provider = new class implements CodeActionProvider {
|
||||
provideCodeActions(): CodeAction[] {
|
||||
return [
|
||||
{ title: 'a', kind: 'a' },
|
||||
{ title: 'b', kind: 'b' },
|
||||
{ title: 'a.b', kind: 'a.b' }
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
disposables.push(CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
{
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), new CodeActionKind('a'));
|
||||
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), new CodeActionKind('a.b'));
|
||||
assert.equal(actions.length, 1);
|
||||
assert.strictEqual(actions[0].title, 'a.b');
|
||||
}
|
||||
|
||||
{
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), new CodeActionKind('a.b.c'));
|
||||
assert.equal(actions.length, 0);
|
||||
}
|
||||
});
|
||||
|
||||
test('getCodeActions should forward requested scope to providers', async function () {
|
||||
const provider = new class implements CodeActionProvider {
|
||||
provideCodeActions(_model: any, _range: Range, context: CodeActionContext, _token: any): CodeAction[] {
|
||||
return [
|
||||
{ title: context.only, kind: context.only }
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
disposables.push(CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), new CodeActionKind('a'));
|
||||
assert.equal(actions.length, 1);
|
||||
assert.strictEqual(actions[0].title, 'a');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,7 +47,7 @@ suite('QuickFix', () => {
|
||||
test('Orcale -> marker added', done => {
|
||||
|
||||
const oracle = new QuickFixOracle(editor, markerService, e => {
|
||||
assert.equal(e.type, 'auto');
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
assert.ok(e.fixes);
|
||||
|
||||
e.fixes.then(fixes => {
|
||||
@@ -83,7 +83,7 @@ suite('QuickFix', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
const oracle = new QuickFixOracle(editor, markerService, e => {
|
||||
assert.equal(e.type, 'auto');
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
assert.ok(e.fixes);
|
||||
e.fixes.then(fixes => {
|
||||
oracle.dispose();
|
||||
@@ -160,7 +160,7 @@ suite('QuickFix', () => {
|
||||
await new Promise(resolve => {
|
||||
|
||||
let oracle = new QuickFixOracle(editor, markerService, e => {
|
||||
assert.equal(e.type, 'auto');
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
assert.deepEqual(e.range, { startLineNumber: 3, startColumn: 1, endLineNumber: 3, endColumn: 4 });
|
||||
assert.deepEqual(e.position, { lineNumber: 3, column: 1 });
|
||||
|
||||
|
||||
@@ -329,11 +329,11 @@ export function registerCodeLensProvider(languageId: string, provider: modes.Cod
|
||||
*/
|
||||
export function registerCodeActionProvider(languageId: string, provider: CodeActionProvider): IDisposable {
|
||||
return modes.CodeActionProviderRegistry.register(languageId, {
|
||||
provideCodeActions: (model: model.ITextModel, range: Range, token: CancellationToken): (modes.Command | modes.CodeAction)[] | Thenable<(modes.Command | modes.CodeAction)[]> => {
|
||||
provideCodeActions: (model: model.ITextModel, range: Range, context: modes.CodeActionContext, token: CancellationToken): (modes.Command | modes.CodeAction)[] | Thenable<(modes.Command | modes.CodeAction)[]> => {
|
||||
let markers = StaticServices.markerService.get().read({ resource: model.uri }).filter(m => {
|
||||
return Range.areIntersectingOrTouching(m, range);
|
||||
});
|
||||
return provider.provideCodeActions(model, range, { markers }, token);
|
||||
return provider.provideCodeActions(model, range, { markers, only: context.only }, token);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -401,6 +401,11 @@ export interface CodeActionContext {
|
||||
* @readonly
|
||||
*/
|
||||
readonly markers: IMarkerData[];
|
||||
|
||||
/**
|
||||
* Requested kind of actions to return.
|
||||
*/
|
||||
readonly only?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Vendored
+5
@@ -4043,6 +4043,10 @@ declare module monaco.languages {
|
||||
* @readonly
|
||||
*/
|
||||
readonly markers: editor.IMarkerData[];
|
||||
/**
|
||||
* Requested kind of actions to return.
|
||||
*/
|
||||
readonly only?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4495,6 +4499,7 @@ declare module monaco.languages {
|
||||
command?: Command;
|
||||
edit?: WorkspaceEdit;
|
||||
diagnostics?: editor.IMarkerData[];
|
||||
kind?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Vendored
+74
@@ -1812,6 +1812,66 @@ declare module 'vscode' {
|
||||
*/
|
||||
export type ProviderResult<T> = T | undefined | null | Thenable<T | undefined | null>;
|
||||
|
||||
/**
|
||||
* Kind of a code action.
|
||||
*
|
||||
* Kinds are a hierarchical list of identifiers separated by `.`, e.g. `"refactor.extract.function"`.
|
||||
*/
|
||||
export class CodeActionKind {
|
||||
/**
|
||||
* Empty kind.
|
||||
*/
|
||||
static readonly Empty: CodeActionKind;
|
||||
|
||||
/**
|
||||
* Base kind for quickfix actions.
|
||||
*/
|
||||
static readonly QuickFix: CodeActionKind;
|
||||
|
||||
/**
|
||||
* Base kind for refactoring actions.
|
||||
*/
|
||||
static readonly Refactor: CodeActionKind;
|
||||
|
||||
/**
|
||||
* Base kind for refactoring extraction actions.
|
||||
*/
|
||||
static readonly RefactorExtract: CodeActionKind;
|
||||
|
||||
/**
|
||||
* Base kind for refactoring inline actions.
|
||||
*/
|
||||
static readonly RefactorInline: CodeActionKind;
|
||||
|
||||
/**
|
||||
* Base kind for refactoring rewite actions.
|
||||
*/
|
||||
static readonly RefactorRewrite: CodeActionKind;
|
||||
|
||||
private constructor(value: string);
|
||||
|
||||
/**
|
||||
* String value of the kind, e.g. `"refactor.extract.function"`.
|
||||
*/
|
||||
readonly value?: string;
|
||||
|
||||
/**
|
||||
* Create a new kind by appending a more specific selector to the current kind.
|
||||
*
|
||||
* Does not modify the current kind.
|
||||
*/
|
||||
append(parts: string): CodeActionKind;
|
||||
|
||||
/**
|
||||
* Does this kind contain `other`?
|
||||
*
|
||||
* The kind `"refactor"` for example contains `"refactor.extract"` and ``"refactor.extract.function"`, but not `"unicorn.refactor.extract"` or `"refactory.extract"`
|
||||
*
|
||||
* @param other Kind to check.
|
||||
*/
|
||||
contains(other: CodeActionKind): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains additional diagnostic information about the context in which
|
||||
* a [code action](#CodeActionProvider.provideCodeActions) is run.
|
||||
@@ -1821,6 +1881,13 @@ declare module 'vscode' {
|
||||
* An array of diagnostics.
|
||||
*/
|
||||
readonly diagnostics: Diagnostic[];
|
||||
|
||||
/**
|
||||
* Requested kind of actions to return.
|
||||
*
|
||||
* Actions not of this kind are filtered out before being shown by the lightbulb.
|
||||
*/
|
||||
readonly only?: CodeActionKind;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1853,6 +1920,13 @@ declare module 'vscode' {
|
||||
*/
|
||||
command?: Command;
|
||||
|
||||
/**
|
||||
* Kind of the code action.
|
||||
*
|
||||
* Used to filter code actions.
|
||||
*/
|
||||
readonly kind?: CodeActionKind;
|
||||
|
||||
/**
|
||||
* Creates a new code action.
|
||||
*
|
||||
|
||||
@@ -206,8 +206,8 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
|
||||
$registerQuickFixSupport(handle: number, selector: vscode.DocumentSelector): void {
|
||||
this._registrations[handle] = modes.CodeActionProviderRegistry.register(toLanguageSelector(selector), <modes.CodeActionProvider>{
|
||||
provideCodeActions: (model: ITextModel, range: EditorRange, token: CancellationToken): Thenable<modes.CodeAction[]> => {
|
||||
return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$provideCodeActions(handle, model.uri, range))).then(MainThreadLanguageFeatures._reviveCodeActionDto);
|
||||
provideCodeActions: (model: ITextModel, range: EditorRange, context: modes.CodeActionContext, token: CancellationToken): Thenable<modes.CodeAction[]> => {
|
||||
return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$provideCodeActions(handle, model.uri, range, context))).then(MainThreadLanguageFeatures._reviveCodeActionDto);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -556,6 +556,7 @@ export function createApiFactory(
|
||||
Breakpoint: extHostTypes.Breakpoint,
|
||||
CancellationTokenSource: CancellationTokenSource,
|
||||
CodeAction: extHostTypes.CodeAction,
|
||||
CodeActionKind: extHostTypes.CodeActionKind,
|
||||
CodeLens: extHostTypes.CodeLens,
|
||||
Color: extHostTypes.Color,
|
||||
ColorPresentation: extHostTypes.ColorPresentation,
|
||||
|
||||
@@ -639,6 +639,7 @@ export interface CodeActionDto {
|
||||
edit?: WorkspaceEditDto;
|
||||
diagnostics?: IMarkerData[];
|
||||
command?: modes.Command;
|
||||
scope?: string;
|
||||
}
|
||||
|
||||
export interface ExtHostLanguageFeaturesShape {
|
||||
@@ -651,7 +652,7 @@ export interface ExtHostLanguageFeaturesShape {
|
||||
$provideHover(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.Hover>;
|
||||
$provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.DocumentHighlight[]>;
|
||||
$provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext): TPromise<LocationDto[]>;
|
||||
$provideCodeActions(handle: number, resource: UriComponents, range: IRange): TPromise<CodeActionDto[]>;
|
||||
$provideCodeActions(handle: number, resource: UriComponents, range: IRange, context: modes.CodeActionContext): TPromise<CodeActionDto[]>;
|
||||
$provideDocumentFormattingEdits(handle: number, resource: UriComponents, options: modes.FormattingOptions): TPromise<ISingleEditOperation[]>;
|
||||
$provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange, options: modes.FormattingOptions): TPromise<ISingleEditOperation[]>;
|
||||
$provideOnTypeFormattingEdits(handle: number, resource: UriComponents, position: IPosition, ch: string, options: modes.FormattingOptions): TPromise<ISingleEditOperation[]>;
|
||||
|
||||
@@ -419,6 +419,9 @@ export class ExtHostApiCommands {
|
||||
codeAction.title,
|
||||
typeConverters.WorkspaceEdit.to(codeAction.edit)
|
||||
);
|
||||
if (codeAction.kind) {
|
||||
ret.scope = new types.CodeActionKind(codeAction.kind);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import * as vscode from 'vscode';
|
||||
import * as TypeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { Range, Disposable, CompletionList, SnippetString, Color } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { Range, Disposable, CompletionList, SnippetString, Color, CodeActionKind } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { ISingleEditOperation } from 'vs/editor/common/model';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { ExtHostHeapService } from 'vs/workbench/api/node/extHostHeapService';
|
||||
@@ -273,7 +273,7 @@ class CodeActionAdapter {
|
||||
this._provider = provider;
|
||||
}
|
||||
|
||||
provideCodeActions(resource: URI, range: IRange): TPromise<modes.CodeAction[]> {
|
||||
provideCodeActions(resource: URI, range: IRange, context: modes.CodeActionContext): TPromise<modes.CodeAction[]> {
|
||||
|
||||
const doc = this._documents.getDocumentData(resource).document;
|
||||
const ran = <vscode.Range>TypeConverters.toRange(range);
|
||||
@@ -289,8 +289,12 @@ class CodeActionAdapter {
|
||||
}
|
||||
});
|
||||
|
||||
const codeActionContext: vscode.CodeActionContext = {
|
||||
diagnostics: allDiagnostics,
|
||||
only: context.only ? new CodeActionKind(context.only) : undefined
|
||||
};
|
||||
return asWinJsPromise(token =>
|
||||
this._provider.provideCodeActions(doc, ran, { diagnostics: allDiagnostics }, token)
|
||||
this._provider.provideCodeActions(doc, ran, codeActionContext, token)
|
||||
).then(commandsOrActions => {
|
||||
if (isFalsyOrEmpty(commandsOrActions)) {
|
||||
return undefined;
|
||||
@@ -314,6 +318,7 @@ class CodeActionAdapter {
|
||||
command: candidate.command && this._commands.toInternal(candidate.command),
|
||||
diagnostics: candidate.diagnostics && candidate.diagnostics.map(DiagnosticCollection.toMarkerData),
|
||||
edit: candidate.edit && TypeConverters.WorkspaceEdit.from(candidate.edit),
|
||||
kind: candidate.kind && candidate.kind.value
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -943,8 +948,8 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
||||
return this._createDisposable(handle);
|
||||
}
|
||||
|
||||
$provideCodeActions(handle: number, resource: UriComponents, range: IRange): TPromise<modes.CodeAction[]> {
|
||||
return this._withAdapter(handle, CodeActionAdapter, adapter => adapter.provideCodeActions(URI.revive(resource), range));
|
||||
$provideCodeActions(handle: number, resource: UriComponents, range: IRange, context: modes.CodeActionContext): TPromise<modes.CodeAction[]> {
|
||||
return this._withAdapter(handle, CodeActionAdapter, adapter => adapter.provideCodeActions(URI.revive(resource), range, context));
|
||||
}
|
||||
|
||||
// --- formatting
|
||||
|
||||
@@ -12,6 +12,7 @@ import * as vscode from 'vscode';
|
||||
import { isMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { IRelativePattern } from 'vs/base/common/glob';
|
||||
import { relative } from 'path';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
|
||||
export class Disposable {
|
||||
|
||||
@@ -818,12 +819,39 @@ export class CodeAction {
|
||||
|
||||
dianostics?: Diagnostic[];
|
||||
|
||||
scope?: CodeActionKind;
|
||||
|
||||
constructor(title: string, edit?: WorkspaceEdit) {
|
||||
this.title = title;
|
||||
this.edit = edit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class CodeActionKind {
|
||||
private static readonly sep = '.';
|
||||
|
||||
public static readonly Empty = new CodeActionKind('');
|
||||
public static readonly QuickFix = new CodeActionKind('quickfix');
|
||||
public static readonly Refactor = new CodeActionKind('refactor');
|
||||
public static readonly RefactorExtract = CodeActionKind.Refactor.append('extract');
|
||||
public static readonly RefactorInline = CodeActionKind.Refactor.append('inline');
|
||||
public static readonly RefactorRewrite = CodeActionKind.Refactor.append('rewrite');
|
||||
|
||||
constructor(
|
||||
public readonly value: string
|
||||
) { }
|
||||
|
||||
public append(parts: string): CodeActionKind {
|
||||
return new CodeActionKind(this.value ? this.value + CodeActionKind.sep + parts : parts);
|
||||
}
|
||||
|
||||
public contains(other: CodeActionKind): boolean {
|
||||
return this.value === other.value || startsWith(other.value, this.value + CodeActionKind.sep);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class CodeLens {
|
||||
|
||||
range: Range;
|
||||
|
||||
@@ -641,7 +641,7 @@ suite('ExtHostLanguageFeatures', function () {
|
||||
|
||||
// --- quick fix
|
||||
|
||||
test('Quick Fix, data conversion', function () {
|
||||
test('Quick Fix, command data conversion', function () {
|
||||
|
||||
disposables.push(extHost.registerCodeActionProvider(defaultSelector, {
|
||||
provideCodeActions(): vscode.Command[] {
|
||||
@@ -665,6 +665,34 @@ suite('ExtHostLanguageFeatures', function () {
|
||||
});
|
||||
});
|
||||
|
||||
test('Quick Fix, code action data conversion', function () {
|
||||
|
||||
disposables.push(extHost.registerCodeActionProvider(defaultSelector, {
|
||||
provideCodeActions(): vscode.CodeAction[] {
|
||||
return [
|
||||
{
|
||||
title: 'Testing1',
|
||||
command: { title: 'Testing1Command', command: 'test1' },
|
||||
kind: types.CodeActionKind.Empty.append('test.scope')
|
||||
}
|
||||
];
|
||||
}
|
||||
}));
|
||||
|
||||
return rpcProtocol.sync().then(() => {
|
||||
return getCodeActions(model, model.getFullModelRange()).then(value => {
|
||||
assert.equal(value.length, 1);
|
||||
|
||||
const [first] = value;
|
||||
assert.equal(first.title, 'Testing1');
|
||||
assert.equal(first.command.title, 'Testing1Command');
|
||||
assert.equal(first.command.id, 'test1');
|
||||
assert.equal(first.kind, 'test.scope');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test('Cannot read property \'id\' of undefined, #29469', function () {
|
||||
|
||||
disposables.push(extHost.registerCodeActionProvider(defaultSelector, <vscode.CodeActionProvider>{
|
||||
|
||||
Reference in New Issue
Block a user