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:
Matt Bierner
2018-01-22 11:45:22 -08:00
committed by GitHub
parent 8f58711d82
commit eccf728e64
18 changed files with 372 additions and 41 deletions
@@ -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;
}
}
+9 -1
View File
@@ -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;
}
+6 -3
View File
@@ -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());
+15 -14
View File
@@ -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;
}
/**
+5
View File
@@ -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;
}
/**
+74
View File
@@ -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
+28
View File
@@ -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>{