mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-20 20:10:11 -04:00
Merge VS Code 1.31.1 (#4283)
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { flatten, mergeSort, isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { flatten, isNonEmptyArray, mergeSort } from 'vs/base/common/arrays';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { illegalArgument, isPromiseCanceledError, onUnexpectedExternalError } from 'vs/base/common/errors';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
@@ -13,73 +13,59 @@ import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { CodeAction, CodeActionContext, CodeActionProviderRegistry, CodeActionTrigger as CodeActionTriggerKind } from 'vs/editor/common/modes';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { CodeActionFilter, CodeActionKind, CodeActionTrigger } from './codeActionTrigger';
|
||||
import { CodeActionKind, CodeActionTrigger, filtersAction, mayIncludeActionsOfKind, CodeActionFilter } from './codeActionTrigger';
|
||||
|
||||
export function getCodeActions(
|
||||
model: ITextModel,
|
||||
rangeOrSelection: Range | Selection,
|
||||
trigger: CodeActionTrigger,
|
||||
token: CancellationToken
|
||||
): Promise<CodeAction[]> {
|
||||
const filter = trigger.filter || {};
|
||||
|
||||
export function getCodeActions(model: ITextModel, rangeOrSelection: Range | Selection, trigger?: CodeActionTrigger, token: CancellationToken = CancellationToken.None): Promise<CodeAction[]> {
|
||||
const codeActionContext: CodeActionContext = {
|
||||
only: trigger && trigger.filter && trigger.filter.kind ? trigger.filter.kind.value : undefined,
|
||||
trigger: trigger && trigger.type === 'manual' ? CodeActionTriggerKind.Manual : CodeActionTriggerKind.Automatic
|
||||
only: filter.kind ? filter.kind.value : undefined,
|
||||
trigger: trigger.type === 'manual' ? CodeActionTriggerKind.Manual : CodeActionTriggerKind.Automatic
|
||||
};
|
||||
|
||||
const promises = CodeActionProviderRegistry.all(model)
|
||||
.filter(provider => {
|
||||
if (!provider.providedCodeActionKinds) {
|
||||
return true;
|
||||
if (filter.kind && CodeActionKind.Source.contains(filter.kind) && rangeOrSelection.isEmpty()) {
|
||||
rangeOrSelection = model.getFullModelRange();
|
||||
}
|
||||
|
||||
const promises = getCodeActionProviders(model, filter).map(provider => {
|
||||
return Promise.resolve(provider.provideCodeActions(model, rangeOrSelection, codeActionContext, token)).then(providedCodeActions => {
|
||||
if (!Array.isArray(providedCodeActions)) {
|
||||
return [];
|
||||
}
|
||||
return providedCodeActions.filter(action => action && filtersAction(filter, action));
|
||||
}, (err): CodeAction[] => {
|
||||
if (isPromiseCanceledError(err)) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Avoid calling providers that we know will not return code actions of interest
|
||||
return provider.providedCodeActionKinds.some(providedKind => {
|
||||
// Filter out actions by kind
|
||||
// The provided kind can be either a subset of a superset of the filtered kind
|
||||
if (trigger && trigger.filter && trigger.filter.kind && !(trigger.filter.kind.contains(providedKind) || new CodeActionKind(providedKind).contains(trigger.filter.kind.value))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't return source actions unless they are explicitly requested
|
||||
if (trigger && CodeActionKind.Source.contains(providedKind) && (!trigger.filter || !trigger.filter.includeSourceActions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
})
|
||||
.map(support => {
|
||||
return Promise.resolve(support.provideCodeActions(model, rangeOrSelection, codeActionContext, token)).then(providedCodeActions => {
|
||||
if (!Array.isArray(providedCodeActions)) {
|
||||
return [];
|
||||
}
|
||||
return providedCodeActions.filter(action => isValidAction(trigger && trigger.filter, action));
|
||||
}, (err): CodeAction[] => {
|
||||
if (isPromiseCanceledError(err)) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
onUnexpectedExternalError(err);
|
||||
return [];
|
||||
});
|
||||
onUnexpectedExternalError(err);
|
||||
return [];
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(flatten)
|
||||
.then(allCodeActions => mergeSort(allCodeActions, codeActionsComparator));
|
||||
}
|
||||
|
||||
function isValidAction(filter: CodeActionFilter | undefined, action: CodeAction): boolean {
|
||||
return action && isValidActionKind(filter, action.kind);
|
||||
}
|
||||
|
||||
function isValidActionKind(filter: CodeActionFilter | undefined, kind: string | undefined): boolean {
|
||||
// Filter out actions by kind
|
||||
if (filter && filter.kind && (!kind || !filter.kind.contains(kind))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't return source actions unless they are explicitly requested
|
||||
if (kind && CodeActionKind.Source.contains(kind) && (!filter || !filter.includeSourceActions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
function getCodeActionProviders(
|
||||
model: ITextModel,
|
||||
filter: CodeActionFilter
|
||||
) {
|
||||
return CodeActionProviderRegistry.all(model)
|
||||
// Don't include providers that we know will not return code actions of interest
|
||||
.filter(provider => {
|
||||
if (!provider.providedCodeActionKinds) {
|
||||
// We don't know what type of actions this provider will return.
|
||||
return true;
|
||||
}
|
||||
return provider.providedCodeActionKinds.some(kind => mayIncludeActionsOfKind(filter, new CodeActionKind(kind)));
|
||||
});
|
||||
}
|
||||
|
||||
function codeActionsComparator(a: CodeAction, b: CodeAction): number {
|
||||
@@ -107,5 +93,9 @@ registerLanguageCommand('_executeCodeActionProvider', function (accessor, args)
|
||||
throw illegalArgument();
|
||||
}
|
||||
|
||||
return getCodeActions(model, model.validateRange(range), { type: 'manual', filter: { includeSourceActions: true } });
|
||||
return getCodeActions(
|
||||
model,
|
||||
model.validateRange(range),
|
||||
{ type: 'manual', filter: { includeSourceActions: true } },
|
||||
CancellationToken.None);
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IMarkerService } from 'vs/platform/markers/common/markers';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { CodeActionModel, CodeActionsComputeEvent, SUPPORTED_CODE_ACTIONS } from './codeActionModel';
|
||||
import { CodeActionModel, SUPPORTED_CODE_ACTIONS, CodeActionsState } from './codeActionModel';
|
||||
import { CodeActionAutoApply, CodeActionFilter, CodeActionKind } from './codeActionTrigger';
|
||||
import { CodeActionContextMenu } from './codeActionWidget';
|
||||
import { LightBulbWidget } from './lightBulbWidget';
|
||||
@@ -69,7 +69,7 @@ export class QuickFixController implements IEditorContribution {
|
||||
this._disposables.push(
|
||||
this._codeActionContextMenu.onDidExecuteCodeAction(_ => this._model.trigger({ type: 'auto', filter: {} })),
|
||||
this._lightBulbWidget.onClick(this._handleLightBulbSelect, this),
|
||||
this._model.onDidChangeFixes(e => this._onCodeActionsEvent(e)),
|
||||
this._model.onDidChangeState(e => this._onDidChangeCodeActionsState(e)),
|
||||
this._keybindingService.onDidUpdateKeybindings(this._updateLightBulbTitle, this)
|
||||
);
|
||||
}
|
||||
@@ -79,39 +79,39 @@ export class QuickFixController implements IEditorContribution {
|
||||
dispose(this._disposables);
|
||||
}
|
||||
|
||||
private _onCodeActionsEvent(e: CodeActionsComputeEvent): void {
|
||||
private _onDidChangeCodeActionsState(newState: CodeActionsState.State): void {
|
||||
if (this._activeRequest) {
|
||||
this._activeRequest.cancel();
|
||||
this._activeRequest = undefined;
|
||||
}
|
||||
|
||||
if (e && e.actions) {
|
||||
this._activeRequest = e.actions;
|
||||
}
|
||||
if (newState.type === CodeActionsState.Type.Triggered) {
|
||||
this._activeRequest = newState.actions;
|
||||
|
||||
if (e && e.actions && e.trigger.filter && e.trigger.filter.kind) {
|
||||
// Triggered for specific scope
|
||||
// Apply if we only have one action or requested autoApply, otherwise show menu
|
||||
e.actions.then(fixes => {
|
||||
if (fixes.length > 0 && e.trigger.autoApply === CodeActionAutoApply.First || (e.trigger.autoApply === CodeActionAutoApply.IfSingle && fixes.length === 1)) {
|
||||
this._onApplyCodeAction(fixes[0]);
|
||||
} else {
|
||||
this._codeActionContextMenu.show(e.actions, e.position);
|
||||
}
|
||||
}).catch(onUnexpectedError);
|
||||
return;
|
||||
}
|
||||
if (newState.trigger.filter && newState.trigger.filter.kind) {
|
||||
// Triggered for specific scope
|
||||
newState.actions.then(fixes => {
|
||||
if (fixes.length > 0) {
|
||||
// Apply if we only have one action or requested autoApply
|
||||
if (newState.trigger.autoApply === CodeActionAutoApply.First || (newState.trigger.autoApply === CodeActionAutoApply.IfSingle && fixes.length === 1)) {
|
||||
this._onApplyCodeAction(fixes[0]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._codeActionContextMenu.show(newState.actions, newState.position);
|
||||
|
||||
if (e && e.trigger.type === 'manual') {
|
||||
this._codeActionContextMenu.show(e.actions, e.position);
|
||||
} else if (e && e.actions) {
|
||||
// auto magically triggered
|
||||
// * update an existing list of code actions
|
||||
// * manage light bulb
|
||||
if (this._codeActionContextMenu.isVisible) {
|
||||
this._codeActionContextMenu.show(e.actions, e.position);
|
||||
}).catch(onUnexpectedError);
|
||||
} else if (newState.trigger.type === 'manual') {
|
||||
this._codeActionContextMenu.show(newState.actions, newState.position);
|
||||
} else {
|
||||
this._lightBulbWidget.model = e;
|
||||
// auto magically triggered
|
||||
// * update an existing list of code actions
|
||||
// * manage light bulb
|
||||
if (this._codeActionContextMenu.isVisible) {
|
||||
this._codeActionContextMenu.show(newState.actions, newState.position);
|
||||
} else {
|
||||
this._lightBulbWidget.tryShow(newState);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._lightBulbWidget.hide();
|
||||
@@ -122,13 +122,11 @@ export class QuickFixController implements IEditorContribution {
|
||||
return QuickFixController.ID;
|
||||
}
|
||||
|
||||
private _handleLightBulbSelect(coords: { x: number, y: number }): void {
|
||||
if (this._lightBulbWidget.model && this._lightBulbWidget.model.actions) {
|
||||
this._codeActionContextMenu.show(this._lightBulbWidget.model.actions, coords);
|
||||
}
|
||||
private _handleLightBulbSelect(e: { x: number, y: number, state: CodeActionsState.Triggered }): void {
|
||||
this._codeActionContextMenu.show(e.state.actions, e);
|
||||
}
|
||||
|
||||
public triggerFromEditorSelection(filter?: CodeActionFilter, autoApply?: CodeActionAutoApply): Thenable<CodeAction[] | undefined> {
|
||||
public triggerFromEditorSelection(filter?: CodeActionFilter, autoApply?: CodeActionAutoApply): Promise<CodeAction[] | undefined> {
|
||||
return this._model.trigger({ type: 'manual', filter, autoApply });
|
||||
}
|
||||
|
||||
@@ -158,7 +156,7 @@ export async function applyCodeAction(
|
||||
await bulkEditService.apply(action.edit, { editor });
|
||||
}
|
||||
if (action.command) {
|
||||
await commandService.executeCommand(action.command.id, ...action.command.arguments);
|
||||
await commandService.executeCommand(action.command.id, ...(action.command.arguments || []));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +166,10 @@ function showCodeActionsForEditorSelection(
|
||||
filter?: CodeActionFilter,
|
||||
autoApply?: CodeActionAutoApply
|
||||
) {
|
||||
if (!editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const controller = QuickFixController.get(editor);
|
||||
if (!controller) {
|
||||
return;
|
||||
@@ -206,38 +208,41 @@ export class QuickFixAction extends EditorAction {
|
||||
|
||||
|
||||
class CodeActionCommandArgs {
|
||||
public static fromUser(arg: any): CodeActionCommandArgs {
|
||||
public static fromUser(arg: any, defaults: { kind: CodeActionKind, apply: CodeActionAutoApply }): CodeActionCommandArgs {
|
||||
if (!arg || typeof arg !== 'object') {
|
||||
return new CodeActionCommandArgs(CodeActionKind.Empty, CodeActionAutoApply.IfSingle);
|
||||
return new CodeActionCommandArgs(defaults.kind, defaults.apply, false);
|
||||
}
|
||||
return new CodeActionCommandArgs(
|
||||
CodeActionCommandArgs.getKindFromUser(arg),
|
||||
CodeActionCommandArgs.getApplyFromUser(arg));
|
||||
CodeActionCommandArgs.getKindFromUser(arg, defaults.kind),
|
||||
CodeActionCommandArgs.getApplyFromUser(arg, defaults.apply),
|
||||
CodeActionCommandArgs.getPreferredUser(arg));
|
||||
}
|
||||
|
||||
private static getApplyFromUser(arg: any) {
|
||||
private static getApplyFromUser(arg: any, defaultAutoApply: CodeActionAutoApply) {
|
||||
switch (typeof arg.apply === 'string' ? arg.apply.toLowerCase() : '') {
|
||||
case 'first':
|
||||
return CodeActionAutoApply.First;
|
||||
|
||||
case 'never':
|
||||
return CodeActionAutoApply.Never;
|
||||
|
||||
case 'ifsingle':
|
||||
default:
|
||||
return CodeActionAutoApply.IfSingle;
|
||||
case 'first': return CodeActionAutoApply.First;
|
||||
case 'never': return CodeActionAutoApply.Never;
|
||||
case 'ifsingle': return CodeActionAutoApply.IfSingle;
|
||||
default: return defaultAutoApply;
|
||||
}
|
||||
}
|
||||
|
||||
private static getKindFromUser(arg: any) {
|
||||
private static getKindFromUser(arg: any, defaultKind: CodeActionKind) {
|
||||
return typeof arg.kind === 'string'
|
||||
? new CodeActionKind(arg.kind)
|
||||
: CodeActionKind.Empty;
|
||||
: defaultKind;
|
||||
}
|
||||
|
||||
private static getPreferredUser(arg: any): boolean {
|
||||
return typeof arg.preferred === 'boolean'
|
||||
? arg.preferred
|
||||
: false;
|
||||
}
|
||||
|
||||
private constructor(
|
||||
public readonly kind: CodeActionKind,
|
||||
public readonly apply: CodeActionAutoApply
|
||||
public readonly apply: CodeActionAutoApply,
|
||||
public readonly preferred: boolean,
|
||||
) { }
|
||||
}
|
||||
|
||||
@@ -253,8 +258,17 @@ export class CodeActionCommand extends EditorCommand {
|
||||
}
|
||||
|
||||
public runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, userArg: any) {
|
||||
const args = CodeActionCommandArgs.fromUser(userArg);
|
||||
return showCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available"), { kind: args.kind, includeSourceActions: true }, args.apply);
|
||||
const args = CodeActionCommandArgs.fromUser(userArg, {
|
||||
kind: CodeActionKind.Empty,
|
||||
apply: CodeActionAutoApply.IfSingle,
|
||||
});
|
||||
return showCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available"),
|
||||
{
|
||||
kind: args.kind,
|
||||
includeSourceActions: true,
|
||||
onlyIncludePreferredActions: args.preferred,
|
||||
},
|
||||
args.apply);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,11 +301,18 @@ export class RefactorAction extends EditorAction {
|
||||
});
|
||||
}
|
||||
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArg: any): void {
|
||||
const args = CodeActionCommandArgs.fromUser(userArg, {
|
||||
kind: CodeActionKind.Refactor,
|
||||
apply: CodeActionAutoApply.Never
|
||||
});
|
||||
return showCodeActionsForEditorSelection(editor,
|
||||
nls.localize('editor.action.refactor.noneMessage', "No refactorings available"),
|
||||
{ kind: CodeActionKind.Refactor },
|
||||
CodeActionAutoApply.Never);
|
||||
{
|
||||
kind: CodeActionKind.Refactor.contains(args.kind) ? args.kind : CodeActionKind.Empty,
|
||||
onlyIncludePreferredActions: args.preferred,
|
||||
},
|
||||
args.apply);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,11 +337,19 @@ export class SourceAction extends EditorAction {
|
||||
});
|
||||
}
|
||||
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArg: any): void {
|
||||
const args = CodeActionCommandArgs.fromUser(userArg, {
|
||||
kind: CodeActionKind.Source,
|
||||
apply: CodeActionAutoApply.Never
|
||||
});
|
||||
return showCodeActionsForEditorSelection(editor,
|
||||
nls.localize('editor.action.source.noneMessage', "No source actions available"),
|
||||
{ kind: CodeActionKind.Source, includeSourceActions: true },
|
||||
CodeActionAutoApply.Never);
|
||||
{
|
||||
kind: CodeActionKind.Source.contains(args.kind) ? args.kind : CodeActionKind.Empty,
|
||||
includeSourceActions: true,
|
||||
onlyIncludePreferredActions: args.preferred,
|
||||
},
|
||||
args.apply);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,4 +379,38 @@ export class OrganizeImportsAction extends EditorAction {
|
||||
{ kind: CodeActionKind.SourceOrganizeImports, includeSourceActions: true },
|
||||
CodeActionAutoApply.IfSingle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class AutoFixAction extends EditorAction {
|
||||
|
||||
static readonly Id = 'editor.action.autoFix';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: AutoFixAction.Id,
|
||||
label: nls.localize('autoFix.label', "Auto Fix..."),
|
||||
alias: 'Auto Fix',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.writable,
|
||||
contextKeyForSupportedActions(CodeActionKind.QuickFix)),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.US_DOT,
|
||||
mac: {
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_DOT
|
||||
},
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
return showCodeActionsForEditorSelection(editor,
|
||||
nls.localize('editor.action.autoFix.noneMessage', "No auto fixes available"),
|
||||
{
|
||||
kind: CodeActionKind.QuickFix,
|
||||
onlyIncludePreferredActions: true
|
||||
},
|
||||
CodeActionAutoApply.IfSingle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { registerEditorAction, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { CodeActionCommand, OrganizeImportsAction, QuickFixAction, QuickFixController, RefactorAction, SourceAction } from 'vs/editor/contrib/codeAction/codeActionCommands';
|
||||
import { CodeActionCommand, OrganizeImportsAction, QuickFixAction, QuickFixController, RefactorAction, SourceAction, AutoFixAction } from 'vs/editor/contrib/codeAction/codeActionCommands';
|
||||
|
||||
|
||||
registerEditorContribution(QuickFixController);
|
||||
@@ -12,4 +12,5 @@ registerEditorAction(QuickFixAction);
|
||||
registerEditorAction(RefactorAction);
|
||||
registerEditorAction(SourceAction);
|
||||
registerEditorAction(OrganizeImportsAction);
|
||||
registerEditorAction(AutoFixAction);
|
||||
registerEditorCommand(new CodeActionCommand());
|
||||
|
||||
@@ -28,7 +28,7 @@ export class CodeActionOracle {
|
||||
constructor(
|
||||
private _editor: ICodeEditor,
|
||||
private readonly _markerService: IMarkerService,
|
||||
private _signalChange: (e: CodeActionsComputeEvent) => any,
|
||||
private _signalChange: (newState: CodeActionsState.State) => void,
|
||||
private readonly _delay: number = 250,
|
||||
private readonly _progressService?: IProgressService,
|
||||
) {
|
||||
@@ -112,26 +112,16 @@ export class CodeActionOracle {
|
||||
return selection ? selection : undefined;
|
||||
}
|
||||
|
||||
private _createEventAndSignalChange(trigger: CodeActionTrigger, selection: Selection | undefined): Thenable<CodeAction[] | undefined> {
|
||||
private _createEventAndSignalChange(trigger: CodeActionTrigger, selection: Selection | undefined): Promise<CodeAction[] | undefined> {
|
||||
if (!selection) {
|
||||
// cancel
|
||||
this._signalChange({
|
||||
trigger,
|
||||
rangeOrSelection: undefined,
|
||||
position: undefined,
|
||||
actions: undefined,
|
||||
});
|
||||
this._signalChange(CodeActionsState.Empty);
|
||||
return Promise.resolve(undefined);
|
||||
} else {
|
||||
const model = this._editor.getModel();
|
||||
if (!model) {
|
||||
// cancel
|
||||
this._signalChange({
|
||||
trigger,
|
||||
rangeOrSelection: undefined,
|
||||
position: undefined,
|
||||
actions: undefined,
|
||||
});
|
||||
this._signalChange(CodeActionsState.Empty);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
@@ -143,22 +133,38 @@ export class CodeActionOracle {
|
||||
this._progressService.showWhile(actions, 250);
|
||||
}
|
||||
|
||||
this._signalChange({
|
||||
this._signalChange(new CodeActionsState.Triggered(
|
||||
trigger,
|
||||
rangeOrSelection: selection,
|
||||
selection,
|
||||
position,
|
||||
actions
|
||||
});
|
||||
));
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface CodeActionsComputeEvent {
|
||||
trigger: CodeActionTrigger;
|
||||
rangeOrSelection: Range | Selection | undefined;
|
||||
position: Position | undefined;
|
||||
actions: CancelablePromise<CodeAction[]> | undefined;
|
||||
export namespace CodeActionsState {
|
||||
|
||||
export const enum Type {
|
||||
Empty,
|
||||
Triggered,
|
||||
}
|
||||
|
||||
export const Empty = new class { readonly type = Type.Empty; };
|
||||
|
||||
export class Triggered {
|
||||
readonly type = Type.Triggered;
|
||||
|
||||
constructor(
|
||||
public readonly trigger: CodeActionTrigger,
|
||||
public readonly rangeOrSelection: Range | Selection,
|
||||
public readonly position: Position,
|
||||
public readonly actions: CancelablePromise<CodeAction[]>,
|
||||
) { }
|
||||
}
|
||||
|
||||
export type State = typeof Empty | Triggered;
|
||||
}
|
||||
|
||||
export class CodeActionModel {
|
||||
@@ -166,7 +172,8 @@ export class CodeActionModel {
|
||||
private _editor: ICodeEditor;
|
||||
private _markerService: IMarkerService;
|
||||
private _codeActionOracle?: CodeActionOracle;
|
||||
private _onDidChangeFixes = new Emitter<CodeActionsComputeEvent>();
|
||||
private _state: CodeActionsState.State = CodeActionsState.Empty;
|
||||
private _onDidChangeState = new Emitter<CodeActionsState.State>();
|
||||
private _disposables: IDisposable[] = [];
|
||||
private readonly _supportedCodeActions: IContextKey<string>;
|
||||
|
||||
@@ -178,7 +185,7 @@ export class CodeActionModel {
|
||||
|
||||
this._disposables.push(this._editor.onDidChangeModel(() => this._update()));
|
||||
this._disposables.push(this._editor.onDidChangeModelLanguage(() => this._update()));
|
||||
this._disposables.push(CodeActionProviderRegistry.onDidChange(this._update, this));
|
||||
this._disposables.push(CodeActionProviderRegistry.onDidChange(() => this._update()));
|
||||
|
||||
this._update();
|
||||
}
|
||||
@@ -188,23 +195,26 @@ export class CodeActionModel {
|
||||
dispose(this._codeActionOracle);
|
||||
}
|
||||
|
||||
get onDidChangeFixes(): Event<CodeActionsComputeEvent> {
|
||||
return this._onDidChangeFixes.event;
|
||||
get onDidChangeState(): Event<CodeActionsState.State> {
|
||||
return this._onDidChangeState.event;
|
||||
}
|
||||
|
||||
private _update(): void {
|
||||
|
||||
if (this._codeActionOracle) {
|
||||
this._codeActionOracle.dispose();
|
||||
this._codeActionOracle = undefined;
|
||||
this._onDidChangeFixes.fire(undefined);
|
||||
}
|
||||
|
||||
if (this._state.type === CodeActionsState.Type.Triggered) {
|
||||
// this._state.actions.cancel();
|
||||
}
|
||||
this.setState(CodeActionsState.Empty);
|
||||
|
||||
const model = this._editor.getModel();
|
||||
if (model
|
||||
&& CodeActionProviderRegistry.has(model)
|
||||
&& !this._editor.getConfiguration().readOnly) {
|
||||
|
||||
&& !this._editor.getConfiguration().readOnly
|
||||
) {
|
||||
const supportedActions: string[] = [];
|
||||
for (const provider of CodeActionProviderRegistry.all(model)) {
|
||||
if (Array.isArray(provider.providedCodeActionKinds)) {
|
||||
@@ -214,17 +224,25 @@ export class CodeActionModel {
|
||||
|
||||
this._supportedCodeActions.set(supportedActions.join(' '));
|
||||
|
||||
this._codeActionOracle = new CodeActionOracle(this._editor, this._markerService, p => this._onDidChangeFixes.fire(p), undefined, this._progressService);
|
||||
this._codeActionOracle = new CodeActionOracle(this._editor, this._markerService, newState => this.setState(newState), undefined, this._progressService);
|
||||
this._codeActionOracle.trigger({ type: 'auto' });
|
||||
} else {
|
||||
this._supportedCodeActions.reset();
|
||||
}
|
||||
}
|
||||
|
||||
trigger(trigger: CodeActionTrigger): Thenable<CodeAction[] | undefined> {
|
||||
public trigger(trigger: CodeActionTrigger): Promise<CodeAction[] | undefined> {
|
||||
if (this._codeActionOracle) {
|
||||
return this._codeActionOracle.trigger(trigger);
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
private setState(newState: CodeActionsState.State) {
|
||||
if (newState === this._state) {
|
||||
return;
|
||||
}
|
||||
this._state = newState;
|
||||
this._onDidChangeState.fire(newState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
import { CodeAction } from 'vs/editor/common/modes';
|
||||
|
||||
export class CodeActionKind {
|
||||
private static readonly sep = '.';
|
||||
@@ -13,25 +14,72 @@ export class CodeActionKind {
|
||||
public static readonly Refactor = new CodeActionKind('refactor');
|
||||
public static readonly Source = new CodeActionKind('source');
|
||||
public static readonly SourceOrganizeImports = new CodeActionKind('source.organizeImports');
|
||||
public static readonly SourceFixAll = new CodeActionKind('source.fixAll');
|
||||
|
||||
constructor(
|
||||
public readonly value: string
|
||||
) { }
|
||||
|
||||
public contains(other: string): boolean {
|
||||
return this.value === other || startsWith(other, this.value + CodeActionKind.sep);
|
||||
public contains(other: CodeActionKind): boolean {
|
||||
return this.value === other.value || startsWith(other.value, this.value + CodeActionKind.sep);
|
||||
}
|
||||
|
||||
public intersects(other: CodeActionKind): boolean {
|
||||
return this.contains(other) || other.contains(this);
|
||||
}
|
||||
}
|
||||
|
||||
export const enum CodeActionAutoApply {
|
||||
IfSingle = 1,
|
||||
First = 2,
|
||||
Never = 3
|
||||
IfSingle,
|
||||
First,
|
||||
Never,
|
||||
}
|
||||
|
||||
export interface CodeActionFilter {
|
||||
readonly kind?: CodeActionKind;
|
||||
readonly includeSourceActions?: boolean;
|
||||
readonly onlyIncludePreferredActions?: boolean;
|
||||
}
|
||||
|
||||
export function mayIncludeActionsOfKind(filter: CodeActionFilter, providedKind: CodeActionKind): boolean {
|
||||
// A provided kind may be a subset or superset of our filtered kind.
|
||||
if (filter.kind && !filter.kind.intersects(providedKind)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't return source actions unless they are explicitly requested
|
||||
if (CodeActionKind.Source.contains(providedKind) && !filter.includeSourceActions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
export function filtersAction(filter: CodeActionFilter, action: CodeAction): boolean {
|
||||
const actionKind = action.kind ? new CodeActionKind(action.kind) : undefined;
|
||||
|
||||
// Filter out actions by kind
|
||||
if (filter.kind) {
|
||||
if (!actionKind || !filter.kind.contains(actionKind)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't return source actions unless they are explicitly requested
|
||||
if (!filter.includeSourceActions) {
|
||||
if (actionKind && CodeActionKind.Source.contains(actionKind)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.onlyIncludePreferredActions) {
|
||||
if (!action.isPreferred) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export interface CodeActionTrigger {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { getDomNodePagePosition } from 'vs/base/browser/dom';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { always } from 'vs/base/common/async';
|
||||
import { canceled } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
@@ -27,48 +26,45 @@ export class CodeActionContextMenu {
|
||||
private readonly _onApplyCodeAction: (action: CodeAction) => Promise<any>
|
||||
) { }
|
||||
|
||||
show(fixes: Thenable<CodeAction[]>, at: { x: number; y: number } | Position) {
|
||||
|
||||
const actionsPromise = fixes ? fixes.then(value => {
|
||||
return value.map(action => {
|
||||
return new Action(action.command ? action.command.id : action.title, action.title, undefined, true, () => {
|
||||
return always(
|
||||
this._onApplyCodeAction(action),
|
||||
() => this._onDidExecuteCodeAction.fire(undefined));
|
||||
});
|
||||
});
|
||||
}).then(actions => {
|
||||
if (!this._editor.getDomNode()) {
|
||||
// cancel when editor went off-dom
|
||||
return Promise.reject(canceled());
|
||||
}
|
||||
return actions;
|
||||
}) : Promise.resolve([] as Action[]);
|
||||
|
||||
actionsPromise.then(actions => {
|
||||
this._contextMenuService.showContextMenu({
|
||||
getAnchor: () => {
|
||||
if (Position.isIPosition(at)) {
|
||||
at = this._toCoords(at);
|
||||
}
|
||||
return at;
|
||||
},
|
||||
getActions: () => actions,
|
||||
onHide: () => {
|
||||
this._visible = false;
|
||||
this._editor.focus();
|
||||
},
|
||||
autoSelectFirstItem: true
|
||||
});
|
||||
async show(actionsToShow: Promise<CodeAction[]>, at?: { x: number; y: number } | Position): Promise<void> {
|
||||
const codeActions = await actionsToShow;
|
||||
if (!this._editor.getDomNode()) {
|
||||
// cancel when editor went off-dom
|
||||
return Promise.reject(canceled());
|
||||
}
|
||||
const actions = codeActions.map(action => this.codeActionToAction(action));
|
||||
this._contextMenuService.showContextMenu({
|
||||
getAnchor: () => {
|
||||
if (Position.isIPosition(at)) {
|
||||
at = this._toCoords(at);
|
||||
}
|
||||
return at || { x: 0, y: 0 };
|
||||
},
|
||||
getActions: () => actions,
|
||||
onHide: () => {
|
||||
this._visible = false;
|
||||
this._editor.focus();
|
||||
},
|
||||
autoSelectFirstItem: true
|
||||
});
|
||||
}
|
||||
|
||||
private codeActionToAction(action: CodeAction): Action {
|
||||
const id = action.command ? action.command.id : action.title;
|
||||
const title = action.isPreferred ? `${action.title} ★` : action.title;
|
||||
return new Action(id, title, undefined, true, () =>
|
||||
this._onApplyCodeAction(action)
|
||||
.finally(() => this._onDidExecuteCodeAction.fire(undefined)));
|
||||
}
|
||||
|
||||
get isVisible(): boolean {
|
||||
return this._visible;
|
||||
}
|
||||
|
||||
private _toCoords(position: Position): { x: number, y: number } {
|
||||
|
||||
if (!this._editor.hasModel()) {
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
this._editor.revealPosition(position, ScrollType.Immediate);
|
||||
this._editor.render();
|
||||
|
||||
|
||||
@@ -6,45 +6,49 @@
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { GlobalMouseMoveMonitor, IStandardMouseMoveEventData, standardMouseMoveMerger } from 'vs/base/browser/globalMouseMoveMonitor';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import 'vs/css!./lightBulbWidget';
|
||||
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { CodeActionsComputeEvent } from './codeActionModel';
|
||||
import { CodeActionsState } from './codeActionModel';
|
||||
|
||||
export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
export class LightBulbWidget extends Disposable implements IContentWidget {
|
||||
|
||||
private static readonly _posPref = [ContentWidgetPositionPreference.EXACT];
|
||||
|
||||
private readonly _domNode: HTMLDivElement;
|
||||
private readonly _editor: ICodeEditor;
|
||||
private readonly _disposables: IDisposable[] = [];
|
||||
private readonly _onClick = new Emitter<{ x: number, y: number }>();
|
||||
|
||||
readonly onClick: Event<{ x: number, y: number }> = this._onClick.event;
|
||||
private readonly _onClick = this._register(new Emitter<{ x: number; y: number; state: CodeActionsState.Triggered }>());
|
||||
public readonly onClick = this._onClick.event;
|
||||
|
||||
private _position: IContentWidgetPosition | null;
|
||||
private _model: CodeActionsComputeEvent | null;
|
||||
private _state: CodeActionsState.State = CodeActionsState.Empty;
|
||||
private _futureFixes = new CancellationTokenSource();
|
||||
|
||||
constructor(editor: ICodeEditor) {
|
||||
super();
|
||||
this._domNode = document.createElement('div');
|
||||
this._domNode.className = 'lightbulb-glyph';
|
||||
|
||||
this._editor = editor;
|
||||
this._editor.addContentWidget(this);
|
||||
|
||||
this._disposables.push(this._editor.onDidChangeModel(_ => this._futureFixes.cancel()));
|
||||
this._disposables.push(this._editor.onDidChangeModelLanguage(_ => this._futureFixes.cancel()));
|
||||
this._disposables.push(this._editor.onDidChangeModelContent(_ => {
|
||||
this._register(this._editor.onDidChangeModel(_ => this._futureFixes.cancel()));
|
||||
this._register(this._editor.onDidChangeModelLanguage(_ => this._futureFixes.cancel()));
|
||||
this._register(this._editor.onDidChangeModelContent(_ => {
|
||||
// cancel when the line in question has been removed
|
||||
const editorModel = this._editor.getModel();
|
||||
if (!this.model || !this.model.position || !editorModel || this.model.position.lineNumber >= editorModel.getLineCount()) {
|
||||
if (this._state.type !== CodeActionsState.Type.Triggered || !editorModel || this._state.position.lineNumber >= editorModel.getLineCount()) {
|
||||
this._futureFixes.cancel();
|
||||
}
|
||||
}));
|
||||
this._disposables.push(dom.addStandardDisposableListener(this._domNode, 'click', e => {
|
||||
this._register(dom.addStandardDisposableListener(this._domNode, 'click', e => {
|
||||
if (this._state.type !== CodeActionsState.Type.Triggered) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure that focus / cursor location is not lost when clicking widget icon
|
||||
this._editor.focus();
|
||||
// a bit of extra work to make sure the menu
|
||||
@@ -53,16 +57,17 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
const { lineHeight } = this._editor.getConfiguration();
|
||||
|
||||
let pad = Math.floor(lineHeight / 3);
|
||||
if (this._position && this._model && this._model.position && this._position.position !== null && this._position.position.lineNumber < this._model.position.lineNumber) {
|
||||
if (this._position && this._position.position !== null && this._position.position.lineNumber < this._state.position.lineNumber) {
|
||||
pad += lineHeight;
|
||||
}
|
||||
|
||||
this._onClick.fire({
|
||||
x: e.posx,
|
||||
y: top + height + pad
|
||||
y: top + height + pad,
|
||||
state: this._state
|
||||
});
|
||||
}));
|
||||
this._disposables.push(dom.addDisposableListener(this._domNode, 'mouseenter', (e: MouseEvent) => {
|
||||
this._register(dom.addDisposableListener(this._domNode, 'mouseenter', (e: MouseEvent) => {
|
||||
if ((e.buttons & 1) !== 1) {
|
||||
return;
|
||||
}
|
||||
@@ -75,7 +80,7 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
monitor.dispose();
|
||||
});
|
||||
}));
|
||||
this._disposables.push(this._editor.onDidChangeConfiguration(e => {
|
||||
this._register(this._editor.onDidChangeConfiguration(e => {
|
||||
// hide when told to do so
|
||||
if (e.contribInfo && !this._editor.getConfiguration().contribInfo.lightbulbEnabled) {
|
||||
this.hide();
|
||||
@@ -84,7 +89,7 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this._disposables);
|
||||
super.dispose();
|
||||
this._editor.removeContentWidget(this);
|
||||
}
|
||||
|
||||
@@ -100,9 +105,9 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
return this._position;
|
||||
}
|
||||
|
||||
set model(value: CodeActionsComputeEvent | null) {
|
||||
tryShow(newState: CodeActionsState.State) {
|
||||
|
||||
if (!value || this._position && (!value.position || this._position.position && this._position.position.lineNumber !== value.position.lineNumber)) {
|
||||
if (newState.type !== CodeActionsState.Type.Triggered || this._position && (!newState.position || this._position.position && this._position.position.lineNumber !== newState.position.lineNumber)) {
|
||||
// hide when getting a 'hide'-request or when currently
|
||||
// showing on another line
|
||||
this.hide();
|
||||
@@ -113,14 +118,14 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
|
||||
this._futureFixes = new CancellationTokenSource();
|
||||
const { token } = this._futureFixes;
|
||||
this._model = value;
|
||||
this._state = newState;
|
||||
|
||||
if (!this._model || !this._model.actions) {
|
||||
if (this._state.type === CodeActionsState.Empty.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selection = this._model.rangeOrSelection;
|
||||
this._model.actions.then(fixes => {
|
||||
const selection = this._state.rangeOrSelection;
|
||||
this._state.actions.then(fixes => {
|
||||
if (!token.isCancellationRequested && fixes && fixes.length > 0 && selection) {
|
||||
this._show();
|
||||
} else {
|
||||
@@ -131,10 +136,6 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
});
|
||||
}
|
||||
|
||||
get model(): CodeActionsComputeEvent | null {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
set title(value: string) {
|
||||
this._domNode.title = value;
|
||||
}
|
||||
@@ -148,10 +149,10 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
if (!config.contribInfo.lightbulbEnabled) {
|
||||
return;
|
||||
}
|
||||
if (!this._model || !this._model.position) {
|
||||
if (this._state.type !== CodeActionsState.Type.Triggered) {
|
||||
return;
|
||||
}
|
||||
const { lineNumber, column } = this._model.position;
|
||||
const { lineNumber, column } = this._state.position;
|
||||
const model = this._editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
@@ -188,7 +189,7 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
|
||||
hide(): void {
|
||||
this._position = null;
|
||||
this._model = null;
|
||||
this._state = CodeActionsState.Empty;
|
||||
this._futureFixes.cancel();
|
||||
this._editor.layoutContentWidget(this);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import { CodeAction, CodeActionContext, CodeActionProvider, CodeActionProviderRe
|
||||
import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction';
|
||||
import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger';
|
||||
import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
|
||||
suite('CodeAction', () => {
|
||||
|
||||
@@ -115,7 +117,7 @@ suite('CodeAction', () => {
|
||||
testData.tsLint.abc
|
||||
];
|
||||
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1));
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'manual' }, CancellationToken.None);
|
||||
assert.equal(actions.length, 6);
|
||||
assert.deepEqual(actions, expected);
|
||||
});
|
||||
@@ -134,20 +136,20 @@ suite('CodeAction', () => {
|
||||
disposables.push(CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
{
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } });
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None);
|
||||
assert.equal(actions.length, 2);
|
||||
assert.strictEqual(actions[0].title, 'a');
|
||||
assert.strictEqual(actions[1].title, 'a.b');
|
||||
}
|
||||
|
||||
{
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b') } });
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b') } }, CancellationToken.None);
|
||||
assert.equal(actions.length, 1);
|
||||
assert.strictEqual(actions[0].title, 'a.b');
|
||||
}
|
||||
|
||||
{
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b.c') } });
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b.c') } }, CancellationToken.None);
|
||||
assert.equal(actions.length, 0);
|
||||
}
|
||||
});
|
||||
@@ -156,14 +158,14 @@ suite('CodeAction', () => {
|
||||
const provider = new class implements CodeActionProvider {
|
||||
provideCodeActions(_model: any, _range: Range, context: CodeActionContext, _token: any): CodeAction[] {
|
||||
return [
|
||||
{ title: context.only, kind: context.only }
|
||||
{ title: context.only || '', kind: context.only }
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
disposables.push(CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } });
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None);
|
||||
assert.equal(actions.length, 1);
|
||||
assert.strictEqual(actions[0].title, 'a');
|
||||
});
|
||||
@@ -181,13 +183,13 @@ suite('CodeAction', () => {
|
||||
disposables.push(CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
{
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto' });
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto' }, CancellationToken.None);
|
||||
assert.equal(actions.length, 1);
|
||||
assert.strictEqual(actions[0].title, 'b');
|
||||
}
|
||||
|
||||
{
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: CodeActionKind.Source, includeSourceActions: true } });
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: CodeActionKind.Source, includeSourceActions: true } }, CancellationToken.None);
|
||||
assert.equal(actions.length, 1);
|
||||
assert.strictEqual(actions[0].title, 'a');
|
||||
}
|
||||
@@ -211,8 +213,53 @@ suite('CodeAction', () => {
|
||||
filter: {
|
||||
kind: CodeActionKind.QuickFix
|
||||
}
|
||||
});
|
||||
}, CancellationToken.None);
|
||||
assert.strictEqual(actions.length, 0);
|
||||
assert.strictEqual(wasInvoked, false);
|
||||
});
|
||||
|
||||
test('getCodeActions requests for source actions should expand source actions range to entire document #53525', async function () {
|
||||
const provider = new class implements CodeActionProvider {
|
||||
provideCodeActions(model: ITextModel, range: Range): CodeAction[] {
|
||||
return [{
|
||||
title: rangeToString(range),
|
||||
kind: CodeActionKind.Source.value,
|
||||
}];
|
||||
}
|
||||
};
|
||||
|
||||
disposables.push(CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
{
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 1, 1), {
|
||||
type: 'manual',
|
||||
filter: {
|
||||
kind: CodeActionKind.Source,
|
||||
includeSourceActions: true,
|
||||
}
|
||||
}, CancellationToken.None);
|
||||
assert.strictEqual(actions.length, 1);
|
||||
assert.strictEqual(actions[0].title, rangeToString(model.getFullModelRange()));
|
||||
}
|
||||
|
||||
{
|
||||
const range = new Range(1, 1, 1, 2);
|
||||
|
||||
// But we should not expand for non-empty selections
|
||||
const actions = await getCodeActions(model, range, {
|
||||
type: 'manual',
|
||||
filter: {
|
||||
kind: CodeActionKind.Source,
|
||||
includeSourceActions: true,
|
||||
}
|
||||
}, CancellationToken.None);
|
||||
assert.strictEqual(actions.length, 1);
|
||||
assert.strictEqual(actions[0].title, rangeToString(range));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function rangeToString(range: Range): string {
|
||||
return `${range.startLineNumber},${range.startColumn} ${range.endLineNumber},${range.endColumn} `;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { CodeActionProviderRegistry, LanguageIdentifier } from 'vs/editor/common/modes';
|
||||
import { CodeActionOracle } from 'vs/editor/contrib/codeAction/codeActionModel';
|
||||
import { CodeActionOracle, CodeActionsState } from 'vs/editor/contrib/codeAction/codeActionModel';
|
||||
import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
|
||||
import { MarkerService } from 'vs/platform/markers/common/markerService';
|
||||
|
||||
@@ -47,11 +47,11 @@ suite('CodeAction', () => {
|
||||
const reg = CodeActionProviderRegistry.register(languageIdentifier.language, testProvider);
|
||||
disposables.push(reg);
|
||||
|
||||
const oracle = new CodeActionOracle(editor, markerService, e => {
|
||||
const oracle = new CodeActionOracle(editor, markerService, (e: CodeActionsState.Triggered) => {
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
assert.ok(e.actions);
|
||||
|
||||
e.actions.then(fixes => {
|
||||
e.actions!.then(fixes => {
|
||||
oracle.dispose();
|
||||
assert.equal(fixes.length, 1);
|
||||
done();
|
||||
@@ -85,10 +85,10 @@ suite('CodeAction', () => {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
const oracle = new CodeActionOracle(editor, markerService, e => {
|
||||
const oracle = new CodeActionOracle(editor, markerService, (e: CodeActionsState.Triggered) => {
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
assert.ok(e.actions);
|
||||
e.actions.then(fixes => {
|
||||
e.actions!.then(fixes => {
|
||||
oracle.dispose();
|
||||
assert.equal(fixes.length, 1);
|
||||
resolve(undefined);
|
||||
@@ -107,7 +107,7 @@ suite('CodeAction', () => {
|
||||
});
|
||||
disposables.push(reg);
|
||||
|
||||
editor.getModel().setValue('// @ts-check\n2\ncon\n');
|
||||
editor.getModel()!.setValue('// @ts-check\n2\ncon\n');
|
||||
|
||||
markerService.changeOne('fake', uri, [{
|
||||
startLineNumber: 3, startColumn: 1, endLineNumber: 3, endColumn: 4,
|
||||
@@ -120,7 +120,7 @@ suite('CodeAction', () => {
|
||||
// case 1 - drag selection over multiple lines -> range of enclosed marker, position or marker
|
||||
await new Promise(resolve => {
|
||||
|
||||
let oracle = new CodeActionOracle(editor, markerService, e => {
|
||||
let oracle = new CodeActionOracle(editor, markerService, (e: CodeActionsState.Triggered) => {
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
const selection = <Selection>e.rangeOrSelection;
|
||||
assert.deepEqual(selection.selectionStartLineNumber, 1);
|
||||
@@ -130,7 +130,7 @@ suite('CodeAction', () => {
|
||||
assert.deepEqual(e.position, { lineNumber: 3, column: 1 });
|
||||
|
||||
oracle.dispose();
|
||||
resolve(null);
|
||||
resolve(undefined);
|
||||
}, 5);
|
||||
|
||||
editor.setSelection({ startLineNumber: 1, startColumn: 1, endLineNumber: 4, endColumn: 1 });
|
||||
@@ -142,7 +142,7 @@ suite('CodeAction', () => {
|
||||
disposables.push(reg);
|
||||
|
||||
let triggerCount = 0;
|
||||
const oracle = new CodeActionOracle(editor, markerService, e => {
|
||||
const oracle = new CodeActionOracle(editor, markerService, (e: CodeActionsState.Triggered) => {
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
++triggerCount;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user