mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-20 20:10:11 -04:00
Merge from vscode 81d7885dc2e9dc617e1522697a2966bc4025a45d (#5949)
* Merge from vscode 81d7885dc2e9dc617e1522697a2966bc4025a45d * Fix vs unit tests and hygiene issue * Fix strict null check issue
This commit is contained in:
@@ -15,8 +15,14 @@ import { CodeAction, CodeActionContext, CodeActionProviderRegistry, CodeActionTr
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { CodeActionFilter, CodeActionKind, CodeActionTrigger, filtersAction, mayIncludeActionsOfKind } from './codeActionTrigger';
|
||||
import { TextModelCancellationTokenSource } from 'vs/editor/browser/core/editorState';
|
||||
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export class CodeActionSet {
|
||||
export interface CodeActionSet extends IDisposable {
|
||||
readonly actions: readonly CodeAction[];
|
||||
readonly hasAutoFix: boolean;
|
||||
}
|
||||
|
||||
class ManagedCodeActionSet extends Disposable implements CodeActionSet {
|
||||
|
||||
private static codeActionsComparator(a: CodeAction, b: CodeAction): number {
|
||||
if (isNonEmptyArray(a.diagnostics)) {
|
||||
@@ -34,8 +40,10 @@ export class CodeActionSet {
|
||||
|
||||
public readonly actions: readonly CodeAction[];
|
||||
|
||||
public constructor(actions: readonly CodeAction[]) {
|
||||
this.actions = mergeSort([...actions], CodeActionSet.codeActionsComparator);
|
||||
public constructor(actions: readonly CodeAction[], disposables: DisposableStore) {
|
||||
super();
|
||||
this._register(disposables);
|
||||
this.actions = mergeSort([...actions], ManagedCodeActionSet.codeActionsComparator);
|
||||
}
|
||||
|
||||
public get hasAutoFix() {
|
||||
@@ -59,12 +67,14 @@ export function getCodeActions(
|
||||
const cts = new TextModelCancellationTokenSource(model, token);
|
||||
const providers = getCodeActionProviders(model, filter);
|
||||
|
||||
const disposables = new DisposableStore();
|
||||
const promises = providers.map(provider => {
|
||||
return Promise.resolve(provider.provideCodeActions(model, rangeOrSelection, codeActionContext, cts.token)).then(providedCodeActions => {
|
||||
if (cts.token.isCancellationRequested || !Array.isArray(providedCodeActions)) {
|
||||
if (cts.token.isCancellationRequested || !providedCodeActions) {
|
||||
return [];
|
||||
}
|
||||
return providedCodeActions.filter(action => action && filtersAction(filter, action));
|
||||
disposables.add(providedCodeActions);
|
||||
return providedCodeActions.actions.filter(action => action && filtersAction(filter, action));
|
||||
}, (err): CodeAction[] => {
|
||||
if (isPromiseCanceledError(err)) {
|
||||
throw err;
|
||||
@@ -84,7 +94,7 @@ export function getCodeActions(
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(flatten)
|
||||
.then(actions => new CodeActionSet(actions))
|
||||
.then(actions => new ManagedCodeActionSet(actions, disposables))
|
||||
.finally(() => {
|
||||
listener.dispose();
|
||||
cts.dispose();
|
||||
@@ -106,7 +116,7 @@ function getCodeActionProviders(
|
||||
});
|
||||
}
|
||||
|
||||
registerLanguageCommand('_executeCodeActionProvider', function (accessor, args): Promise<ReadonlyArray<CodeAction>> {
|
||||
registerLanguageCommand('_executeCodeActionProvider', async function (accessor, args): Promise<ReadonlyArray<CodeAction>> {
|
||||
const { resource, range, kind } = args;
|
||||
if (!(resource instanceof URI) || !Range.isIRange(range)) {
|
||||
throw illegalArgument();
|
||||
@@ -117,9 +127,11 @@ registerLanguageCommand('_executeCodeActionProvider', function (accessor, args):
|
||||
throw illegalArgument();
|
||||
}
|
||||
|
||||
return getCodeActions(
|
||||
const codeActionSet = await getCodeActions(
|
||||
model,
|
||||
model.validateRange(range),
|
||||
{ type: 'manual', filter: { includeSourceActions: true, kind: kind && kind.value ? new CodeActionKind(kind.value) : undefined } },
|
||||
CancellationToken.None).then(actions => actions.actions);
|
||||
CancellationToken.None);
|
||||
codeActionSet.dispose();
|
||||
return codeActionSet.actions;
|
||||
});
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancelablePromise } from 'vs/base/common/async';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { escapeRegExpCharacters } from 'vs/base/common/strings';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorAction, EditorCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
@@ -20,10 +19,10 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo
|
||||
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 { ILocalProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { CodeActionModel, SUPPORTED_CODE_ACTIONS, CodeActionsState } from './codeActionModel';
|
||||
import { CodeActionAutoApply, CodeActionFilter, CodeActionKind } from './codeActionTrigger';
|
||||
import { CodeActionContextMenu } from './codeActionWidget';
|
||||
import { CodeActionAutoApply, CodeActionFilter, CodeActionKind, CodeActionTrigger } from './codeActionTrigger';
|
||||
import { CodeActionWidget } from './codeActionWidget';
|
||||
import { LightBulbWidget } from './lightBulbWidget';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
@@ -45,16 +44,15 @@ export class QuickFixController extends Disposable implements IEditorContributio
|
||||
|
||||
private readonly _editor: ICodeEditor;
|
||||
private readonly _model: CodeActionModel;
|
||||
private readonly _codeActionContextMenu: CodeActionContextMenu;
|
||||
private readonly _codeActionWidget: CodeActionWidget;
|
||||
private readonly _lightBulbWidget: LightBulbWidget;
|
||||
|
||||
private _activeRequest: CancelablePromise<CodeActionSet> | undefined;
|
||||
private _currentCodeActions: CodeActionSet | undefined;
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
@IMarkerService markerService: IMarkerService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IProgressService progressService: IProgressService,
|
||||
@ILocalProgressService progressService: ILocalProgressService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IKeybindingService private readonly _keybindingService: IKeybindingService,
|
||||
@@ -63,58 +61,70 @@ export class QuickFixController extends Disposable implements IEditorContributio
|
||||
super();
|
||||
|
||||
this._editor = editor;
|
||||
this._model = new CodeActionModel(this._editor, markerService, contextKeyService, progressService);
|
||||
this._codeActionContextMenu = new CodeActionContextMenu(editor, contextMenuService, action => this._onApplyCodeAction(action));
|
||||
this._model = this._register(new CodeActionModel(this._editor, markerService, contextKeyService, progressService));
|
||||
this._codeActionWidget = new CodeActionWidget(editor, contextMenuService, {
|
||||
onSelectCodeAction: async (action) => {
|
||||
try {
|
||||
await this._applyCodeAction(action);
|
||||
} finally {
|
||||
// Retrigger
|
||||
this._trigger({ type: 'auto', filter: {} });
|
||||
}
|
||||
}
|
||||
});
|
||||
this._lightBulbWidget = this._register(new LightBulbWidget(editor));
|
||||
|
||||
this._updateLightBulbTitle();
|
||||
|
||||
this._register(this._codeActionContextMenu.onDidExecuteCodeAction(_ => this._model.trigger({ type: 'auto', filter: {} })));
|
||||
this._register(this._lightBulbWidget.onClick(this._handleLightBulbSelect, this));
|
||||
this._register(this._model.onDidChangeState(e => this._onDidChangeCodeActionsState(e)));
|
||||
this._register(this._model.onDidChangeState((newState) => this._onDidChangeCodeActionsState(newState)));
|
||||
this._register(this._keybindingService.onDidUpdateKeybindings(this._updateLightBulbTitle, this));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
dipose() {
|
||||
super.dispose();
|
||||
this._model.dispose();
|
||||
dispose(this._currentCodeActions);
|
||||
}
|
||||
|
||||
private _onDidChangeCodeActionsState(newState: CodeActionsState.State): void {
|
||||
if (this._activeRequest) {
|
||||
this._activeRequest.cancel();
|
||||
this._activeRequest = undefined;
|
||||
}
|
||||
|
||||
if (newState.type === CodeActionsState.Type.Triggered) {
|
||||
this._activeRequest = newState.actions;
|
||||
newState.actions.then(actions => {
|
||||
dispose(this._currentCodeActions);
|
||||
this._currentCodeActions = actions;
|
||||
|
||||
if (!actions.actions.length && newState.trigger.context) {
|
||||
MessageController.get(this._editor).showMessage(newState.trigger.context.notAvailableMessage, newState.trigger.context.position);
|
||||
}
|
||||
});
|
||||
|
||||
if (newState.trigger.filter && newState.trigger.filter.kind) {
|
||||
// Triggered for specific scope
|
||||
newState.actions.then(fixes => {
|
||||
if (fixes.actions.length > 0) {
|
||||
newState.actions.then(codeActions => {
|
||||
if (codeActions.actions.length > 0) {
|
||||
// Apply if we only have one action or requested autoApply
|
||||
if (newState.trigger.autoApply === CodeActionAutoApply.First || (newState.trigger.autoApply === CodeActionAutoApply.IfSingle && fixes.actions.length === 1)) {
|
||||
this._onApplyCodeAction(fixes.actions[0]);
|
||||
if (newState.trigger.autoApply === CodeActionAutoApply.First || (newState.trigger.autoApply === CodeActionAutoApply.IfSingle && codeActions.actions.length === 1)) {
|
||||
this._applyCodeAction(codeActions.actions[0]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._codeActionContextMenu.show(newState.actions, newState.position);
|
||||
this._codeActionWidget.show(newState.actions, newState.position);
|
||||
|
||||
}).catch(onUnexpectedError);
|
||||
} else if (newState.trigger.type === 'manual') {
|
||||
this._codeActionContextMenu.show(newState.actions, newState.position);
|
||||
this._codeActionWidget.show(newState.actions, newState.position);
|
||||
} else {
|
||||
// auto magically triggered
|
||||
// * update an existing list of code actions
|
||||
// * manage light bulb
|
||||
if (this._codeActionContextMenu.isVisible) {
|
||||
this._codeActionContextMenu.show(newState.actions, newState.position);
|
||||
if (this._codeActionWidget.isVisible) {
|
||||
this._codeActionWidget.show(newState.actions, newState.position);
|
||||
} else {
|
||||
this._lightBulbWidget.tryShow(newState);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dispose(this._currentCodeActions);
|
||||
this._currentCodeActions = undefined;
|
||||
this._lightBulbWidget.hide();
|
||||
}
|
||||
}
|
||||
@@ -123,12 +133,26 @@ export class QuickFixController extends Disposable implements IEditorContributio
|
||||
return QuickFixController.ID;
|
||||
}
|
||||
|
||||
private _handleLightBulbSelect(e: { x: number, y: number, state: CodeActionsState.Triggered }): void {
|
||||
this._codeActionContextMenu.show(e.state.actions, e);
|
||||
public manualTriggerAtCurrentPosition(
|
||||
notAvailableMessage: string,
|
||||
filter?: CodeActionFilter,
|
||||
autoApply?: CodeActionAutoApply
|
||||
): void {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MessageController.get(this._editor).closeMessage();
|
||||
const triggerPosition = this._editor.getPosition();
|
||||
this._trigger({ type: 'manual', filter, autoApply, context: { notAvailableMessage, position: triggerPosition } });
|
||||
}
|
||||
|
||||
public triggerFromEditorSelection(filter?: CodeActionFilter, autoApply?: CodeActionAutoApply): Promise<CodeActionSet | undefined> {
|
||||
return this._model.trigger({ type: 'manual', filter, autoApply });
|
||||
private _trigger(trigger: CodeActionTrigger) {
|
||||
return this._model.trigger(trigger);
|
||||
}
|
||||
|
||||
private _handleLightBulbSelect(e: { x: number, y: number, state: CodeActionsState.Triggered }): void {
|
||||
this._codeActionWidget.show(e.state.actions, e);
|
||||
}
|
||||
|
||||
private _updateLightBulbTitle(): void {
|
||||
@@ -142,7 +166,7 @@ export class QuickFixController extends Disposable implements IEditorContributio
|
||||
this._lightBulbWidget.title = title;
|
||||
}
|
||||
|
||||
private _onApplyCodeAction(action: CodeAction): Promise<void> {
|
||||
private _applyCodeAction(action: CodeAction): Promise<void> {
|
||||
return applyCodeAction(action, this._bulkEditService, this._commandService, this._editor);
|
||||
}
|
||||
}
|
||||
@@ -161,28 +185,18 @@ export async function applyCodeAction(
|
||||
}
|
||||
}
|
||||
|
||||
function showCodeActionsForEditorSelection(
|
||||
function triggerCodeActionsForEditorSelection(
|
||||
editor: ICodeEditor,
|
||||
notAvailableMessage: string,
|
||||
filter?: CodeActionFilter,
|
||||
autoApply?: CodeActionAutoApply
|
||||
) {
|
||||
if (!editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const controller = QuickFixController.get(editor);
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
|
||||
MessageController.get(editor).closeMessage();
|
||||
const pos = editor.getPosition();
|
||||
controller.triggerFromEditorSelection(filter, autoApply).then(codeActions => {
|
||||
if (!codeActions || !codeActions.actions.length) {
|
||||
MessageController.get(editor).showMessage(notAvailableMessage, pos);
|
||||
filter: CodeActionFilter | undefined,
|
||||
autoApply: CodeActionAutoApply | undefined
|
||||
): void {
|
||||
if (editor.hasModel()) {
|
||||
const controller = QuickFixController.get(editor);
|
||||
if (controller) {
|
||||
controller.manualTriggerAtCurrentPosition(notAvailableMessage, filter, autoApply);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class QuickFixAction extends EditorAction {
|
||||
@@ -204,7 +218,7 @@ export class QuickFixAction extends EditorAction {
|
||||
}
|
||||
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
return showCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available"));
|
||||
return triggerCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available"), undefined, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,7 +298,7 @@ export class CodeActionCommand extends EditorCommand {
|
||||
kind: CodeActionKind.Empty,
|
||||
apply: CodeActionAutoApply.IfSingle,
|
||||
});
|
||||
return showCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available"),
|
||||
return triggerCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available"),
|
||||
{
|
||||
kind: args.kind,
|
||||
includeSourceActions: true,
|
||||
@@ -347,7 +361,7 @@ export class RefactorAction extends EditorAction {
|
||||
kind: CodeActionKind.Refactor,
|
||||
apply: CodeActionAutoApply.Never
|
||||
});
|
||||
return showCodeActionsForEditorSelection(editor,
|
||||
return triggerCodeActionsForEditorSelection(editor,
|
||||
nls.localize('editor.action.refactor.noneMessage', "No refactorings available"),
|
||||
{
|
||||
kind: CodeActionKind.Refactor.contains(args.kind) ? args.kind : CodeActionKind.Empty,
|
||||
@@ -402,7 +416,7 @@ export class SourceAction extends EditorAction {
|
||||
kind: CodeActionKind.Source,
|
||||
apply: CodeActionAutoApply.Never
|
||||
});
|
||||
return showCodeActionsForEditorSelection(editor,
|
||||
return triggerCodeActionsForEditorSelection(editor,
|
||||
nls.localize('editor.action.source.noneMessage', "No source actions available"),
|
||||
{
|
||||
kind: CodeActionKind.Source.contains(args.kind) ? args.kind : CodeActionKind.Empty,
|
||||
@@ -434,7 +448,7 @@ export class OrganizeImportsAction extends EditorAction {
|
||||
}
|
||||
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
return showCodeActionsForEditorSelection(editor,
|
||||
return triggerCodeActionsForEditorSelection(editor,
|
||||
nls.localize('editor.action.organize.noneMessage', "No organize imports action available"),
|
||||
{ kind: CodeActionKind.SourceOrganizeImports, includeSourceActions: true },
|
||||
CodeActionAutoApply.IfSingle);
|
||||
@@ -457,7 +471,7 @@ export class FixAllAction extends EditorAction {
|
||||
}
|
||||
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
return showCodeActionsForEditorSelection(editor,
|
||||
return triggerCodeActionsForEditorSelection(editor,
|
||||
nls.localize('fixAll.noneMessage', "No fix all action available"),
|
||||
{ kind: CodeActionKind.SourceFixAll, includeSourceActions: true },
|
||||
CodeActionAutoApply.IfSingle);
|
||||
@@ -488,7 +502,7 @@ export class AutoFixAction extends EditorAction {
|
||||
}
|
||||
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
return showCodeActionsForEditorSelection(editor,
|
||||
return triggerCodeActionsForEditorSelection(editor,
|
||||
nls.localize('editor.action.autoFix.noneMessage', "No auto fixes available"),
|
||||
{
|
||||
kind: CodeActionKind.QuickFix,
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancelablePromise, createCancelablePromise, TimeoutTimer } from 'vs/base/common/async';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { dispose, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
@@ -14,36 +14,34 @@ import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { CodeActionProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IMarkerService } from 'vs/platform/markers/common/markers';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { ILocalProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { getCodeActions, CodeActionSet } from './codeAction';
|
||||
import { CodeActionTrigger } from './codeActionTrigger';
|
||||
|
||||
export const SUPPORTED_CODE_ACTIONS = new RawContextKey<string>('supportedCodeAction', '');
|
||||
|
||||
export class CodeActionOracle {
|
||||
export type TriggeredCodeAction = undefined | {
|
||||
readonly selection: Selection;
|
||||
readonly trigger: CodeActionTrigger;
|
||||
readonly position: Position;
|
||||
};
|
||||
|
||||
private _disposables: IDisposable[] = [];
|
||||
private readonly _autoTriggerTimer = new TimeoutTimer();
|
||||
class CodeActionOracle extends Disposable {
|
||||
|
||||
private readonly _autoTriggerTimer = this._register(new TimeoutTimer());
|
||||
|
||||
constructor(
|
||||
private readonly _editor: ICodeEditor,
|
||||
private readonly _markerService: IMarkerService,
|
||||
private readonly _signalChange: (newState: CodeActionsState.State) => void,
|
||||
private readonly _signalChange: (triggered: TriggeredCodeAction) => void,
|
||||
private readonly _delay: number = 250,
|
||||
private readonly _progressService?: IProgressService,
|
||||
) {
|
||||
this._disposables.push(
|
||||
this._markerService.onMarkerChanged(e => this._onMarkerChanges(e)),
|
||||
this._editor.onDidChangeCursorPosition(() => this._onCursorChange()),
|
||||
);
|
||||
super();
|
||||
this._register(this._markerService.onMarkerChanged(e => this._onMarkerChanges(e)));
|
||||
this._register(this._editor.onDidChangeCursorPosition(() => this._onCursorChange()));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._disposables = dispose(this._disposables);
|
||||
this._autoTriggerTimer.cancel();
|
||||
}
|
||||
|
||||
trigger(trigger: CodeActionTrigger) {
|
||||
public trigger(trigger: CodeActionTrigger): TriggeredCodeAction {
|
||||
const selection = this._getRangeOfSelectionUnlessWhitespaceEnclosed(trigger);
|
||||
return this._createEventAndSignalChange(trigger, selection);
|
||||
}
|
||||
@@ -112,35 +110,24 @@ export class CodeActionOracle {
|
||||
return selection ? selection : undefined;
|
||||
}
|
||||
|
||||
private _createEventAndSignalChange(trigger: CodeActionTrigger, selection: Selection | undefined): Promise<CodeActionSet | undefined> {
|
||||
if (!selection) {
|
||||
private _createEventAndSignalChange(trigger: CodeActionTrigger, selection: Selection | undefined): TriggeredCodeAction {
|
||||
const model = this._editor.getModel();
|
||||
if (!selection || !model) {
|
||||
// cancel
|
||||
this._signalChange(CodeActionsState.Empty);
|
||||
return Promise.resolve(undefined);
|
||||
} else {
|
||||
const model = this._editor.getModel();
|
||||
if (!model) {
|
||||
// cancel
|
||||
this._signalChange(CodeActionsState.Empty);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
const markerRange = this._getRangeOfMarker(selection);
|
||||
const position = markerRange ? markerRange.getStartPosition() : selection.getStartPosition();
|
||||
const actions = createCancelablePromise(token => getCodeActions(model, selection, trigger, token));
|
||||
|
||||
if (this._progressService && trigger.type === 'manual') {
|
||||
this._progressService.showWhile(actions, 250);
|
||||
}
|
||||
|
||||
this._signalChange(new CodeActionsState.Triggered(
|
||||
trigger,
|
||||
selection,
|
||||
position,
|
||||
actions
|
||||
));
|
||||
return actions;
|
||||
this._signalChange(undefined);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const markerRange = this._getRangeOfMarker(selection);
|
||||
const position = markerRange ? markerRange.getStartPosition() : selection.getStartPosition();
|
||||
|
||||
const e: TriggeredCodeAction = {
|
||||
trigger,
|
||||
selection,
|
||||
position
|
||||
};
|
||||
this._signalChange(e);
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,36 +154,35 @@ export namespace CodeActionsState {
|
||||
export type State = typeof Empty | Triggered;
|
||||
}
|
||||
|
||||
export class CodeActionModel {
|
||||
export class CodeActionModel extends Disposable {
|
||||
|
||||
private _codeActionOracle?: CodeActionOracle;
|
||||
private _state: CodeActionsState.State = CodeActionsState.Empty;
|
||||
private _onDidChangeState = new Emitter<CodeActionsState.State>();
|
||||
private _disposables: IDisposable[] = [];
|
||||
private readonly _supportedCodeActions: IContextKey<string>;
|
||||
|
||||
private readonly _onDidChangeState = this._register(new Emitter<CodeActionsState.State>());
|
||||
public readonly onDidChangeState = this._onDidChangeState.event;
|
||||
|
||||
constructor(
|
||||
private readonly _editor: ICodeEditor,
|
||||
private readonly _markerService: IMarkerService,
|
||||
contextKeyService: IContextKeyService,
|
||||
private readonly _progressService: IProgressService
|
||||
private readonly _progressService?: ILocalProgressService
|
||||
) {
|
||||
super();
|
||||
this._supportedCodeActions = SUPPORTED_CODE_ACTIONS.bindTo(contextKeyService);
|
||||
|
||||
this._disposables.push(this._editor.onDidChangeModel(() => this._update()));
|
||||
this._disposables.push(this._editor.onDidChangeModelLanguage(() => this._update()));
|
||||
this._disposables.push(CodeActionProviderRegistry.onDidChange(() => this._update()));
|
||||
this._register(this._editor.onDidChangeModel(() => this._update()));
|
||||
this._register(this._editor.onDidChangeModelLanguage(() => this._update()));
|
||||
this._register(CodeActionProviderRegistry.onDidChange(() => this._update()));
|
||||
|
||||
this._update();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._disposables = dispose(this._disposables);
|
||||
super.dispose();
|
||||
dispose(this._codeActionOracle);
|
||||
}
|
||||
|
||||
get onDidChangeState(): Event<CodeActionsState.State> {
|
||||
return this._onDidChangeState.event;
|
||||
this.setState(CodeActionsState.Empty, true);
|
||||
}
|
||||
|
||||
private _update(): void {
|
||||
@@ -205,9 +191,6 @@ export class CodeActionModel {
|
||||
this._codeActionOracle = undefined;
|
||||
}
|
||||
|
||||
if (this._state.type === CodeActionsState.Type.Triggered) {
|
||||
this._state.actions.cancel();
|
||||
}
|
||||
this.setState(CodeActionsState.Empty);
|
||||
|
||||
const model = this._editor.getModel();
|
||||
@@ -224,25 +207,46 @@ export class CodeActionModel {
|
||||
|
||||
this._supportedCodeActions.set(supportedActions.join(' '));
|
||||
|
||||
this._codeActionOracle = new CodeActionOracle(this._editor, this._markerService, newState => this.setState(newState), undefined, this._progressService);
|
||||
this._codeActionOracle = new CodeActionOracle(this._editor, this._markerService, trigger => {
|
||||
if (!trigger) {
|
||||
this.setState(CodeActionsState.Empty);
|
||||
return;
|
||||
}
|
||||
|
||||
const actions = createCancelablePromise(token => getCodeActions(model, trigger.selection, trigger.trigger, token));
|
||||
if (this._progressService && trigger.trigger.type === 'manual') {
|
||||
this._progressService.showWhile(actions, 250);
|
||||
}
|
||||
|
||||
this.setState(new CodeActionsState.Triggered(trigger.trigger, trigger.selection, trigger.position, actions));
|
||||
|
||||
}, undefined);
|
||||
this._codeActionOracle.trigger({ type: 'auto' });
|
||||
} else {
|
||||
this._supportedCodeActions.reset();
|
||||
}
|
||||
}
|
||||
|
||||
public trigger(trigger: CodeActionTrigger): Promise<CodeActionSet | undefined> {
|
||||
public trigger(trigger: CodeActionTrigger) {
|
||||
if (this._codeActionOracle) {
|
||||
return this._codeActionOracle.trigger(trigger);
|
||||
this._codeActionOracle.trigger(trigger);
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
private setState(newState: CodeActionsState.State) {
|
||||
private setState(newState: CodeActionsState.State, skipNotify?: boolean) {
|
||||
if (newState === this._state) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel old request
|
||||
if (this._state.type === CodeActionsState.Type.Triggered) {
|
||||
this._state.actions.cancel();
|
||||
}
|
||||
|
||||
this._state = newState;
|
||||
this._onDidChangeState.fire(newState);
|
||||
|
||||
if (!skipNotify) {
|
||||
this._onDidChangeState.fire(newState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
import { CodeAction } from 'vs/editor/common/modes';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
|
||||
export class CodeActionKind {
|
||||
private static readonly sep = '.';
|
||||
@@ -90,4 +91,8 @@ export interface CodeActionTrigger {
|
||||
readonly type: 'auto' | 'manual';
|
||||
readonly filter?: CodeActionFilter;
|
||||
readonly autoApply?: CodeActionAutoApply;
|
||||
readonly context?: {
|
||||
readonly notAvailableMessage: string;
|
||||
readonly position: Position;
|
||||
};
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
import { getDomNodePagePosition } from 'vs/base/browser/dom';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { canceled } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
@@ -14,25 +13,32 @@ import { CodeAction } from 'vs/editor/common/modes';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction';
|
||||
|
||||
export class CodeActionContextMenu {
|
||||
interface CodeActionWidgetDelegate {
|
||||
onSelectCodeAction: (action: CodeAction) => Promise<any>;
|
||||
}
|
||||
|
||||
export class CodeActionWidget {
|
||||
|
||||
private _visible: boolean;
|
||||
|
||||
private readonly _onDidExecuteCodeAction = new Emitter<void>();
|
||||
public readonly onDidExecuteCodeAction: Event<void> = this._onDidExecuteCodeAction.event;
|
||||
|
||||
constructor(
|
||||
private readonly _editor: ICodeEditor,
|
||||
private readonly _contextMenuService: IContextMenuService,
|
||||
private readonly _onApplyCodeAction: (action: CodeAction) => Promise<any>
|
||||
private readonly _delegate: CodeActionWidgetDelegate
|
||||
) { }
|
||||
|
||||
async show(actionsToShow: Promise<CodeActionSet>, at?: { x: number; y: number } | Position): Promise<void> {
|
||||
public async show(actionsToShow: Promise<CodeActionSet>, at?: { x: number; y: number } | Position): Promise<void> {
|
||||
const codeActions = await actionsToShow;
|
||||
if (!codeActions.actions.length) {
|
||||
this._visible = false;
|
||||
return;
|
||||
}
|
||||
if (!this._editor.getDomNode()) {
|
||||
// cancel when editor went off-dom
|
||||
this._visible = false;
|
||||
return Promise.reject(canceled());
|
||||
}
|
||||
|
||||
this._visible = true;
|
||||
const actions = codeActions.actions.map(action => this.codeActionToAction(action));
|
||||
this._contextMenuService.showContextMenu({
|
||||
@@ -54,9 +60,7 @@ export class CodeActionContextMenu {
|
||||
private codeActionToAction(action: CodeAction): Action {
|
||||
const id = action.command ? action.command.id : action.title;
|
||||
const title = action.title;
|
||||
return new Action(id, title, undefined, true, () =>
|
||||
this._onApplyCodeAction(action)
|
||||
.finally(() => this._onDidExecuteCodeAction.fire(undefined)));
|
||||
return new Action(id, title, undefined, true, () => this._delegate.onSelectCodeAction(action));
|
||||
}
|
||||
|
||||
get isVisible(): boolean {
|
||||
|
||||
@@ -45,13 +45,14 @@ export class LightBulbWidget extends Disposable implements IContentWidget {
|
||||
this._futureFixes.cancel();
|
||||
}
|
||||
}));
|
||||
this._register(dom.addStandardDisposableListener(this._domNode, 'click', e => {
|
||||
this._register(dom.addStandardDisposableListener(this._domNode, 'mousedown', 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();
|
||||
dom.EventHelper.stop(e, true);
|
||||
// a bit of extra work to make sure the menu
|
||||
// doesn't cover the line-text
|
||||
const { top, height } = dom.getDomNodePagePosition(this._domNode);
|
||||
@@ -106,9 +107,8 @@ export class LightBulbWidget extends Disposable implements IContentWidget {
|
||||
return this._position;
|
||||
}
|
||||
|
||||
tryShow(newState: CodeActionsState.State) {
|
||||
|
||||
if (newState.type !== CodeActionsState.Type.Triggered || this._position && (!newState.position || this._position.position && this._position.position.lineNumber !== newState.position.lineNumber)) {
|
||||
tryShow(newState: CodeActionsState.Triggered) {
|
||||
if (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();
|
||||
@@ -121,10 +121,6 @@ export class LightBulbWidget extends Disposable implements IContentWidget {
|
||||
const { token } = this._futureFixes;
|
||||
this._state = newState;
|
||||
|
||||
if (this._state.type === CodeActionsState.Empty.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selection = this._state.rangeOrSelection;
|
||||
this._state.actions.then(fixes => {
|
||||
if (!token.isCancellationRequested && fixes.actions.length > 0 && selection) {
|
||||
|
||||
@@ -3,22 +3,34 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as assert from 'assert';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { CodeAction, CodeActionContext, CodeActionProvider, CodeActionProviderRegistry, Command, LanguageIdentifier, ResourceTextEdit, WorkspaceEdit } from 'vs/editor/common/modes';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
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';
|
||||
|
||||
function staticCodeActionProvider(...actions: modes.CodeAction[]): modes.CodeActionProvider {
|
||||
return new class implements modes.CodeActionProvider {
|
||||
provideCodeActions(): modes.CodeActionList {
|
||||
return {
|
||||
actions: actions,
|
||||
dispose: () => { }
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
suite('CodeAction', () => {
|
||||
|
||||
let langId = new LanguageIdentifier('fooLang', 17);
|
||||
let langId = new modes.LanguageIdentifier('fooLang', 17);
|
||||
let uri = URI.parse('untitled:path');
|
||||
let model: TextModel;
|
||||
let disposables: IDisposable[] = [];
|
||||
const disposables = new DisposableStore();
|
||||
let testData = {
|
||||
diagnostics: {
|
||||
abc: {
|
||||
@@ -46,7 +58,7 @@ suite('CodeAction', () => {
|
||||
},
|
||||
command: {
|
||||
abc: {
|
||||
command: new class implements Command {
|
||||
command: new class implements modes.Command {
|
||||
id: '1';
|
||||
title: 'abc';
|
||||
},
|
||||
@@ -56,8 +68,8 @@ suite('CodeAction', () => {
|
||||
spelling: {
|
||||
bcd: {
|
||||
diagnostics: <IMarkerData[]>[],
|
||||
edit: new class implements WorkspaceEdit {
|
||||
edits: ResourceTextEdit[];
|
||||
edit: new class implements modes.WorkspaceEdit {
|
||||
edits: modes.ResourceTextEdit[];
|
||||
},
|
||||
title: 'abc'
|
||||
}
|
||||
@@ -79,30 +91,27 @@ suite('CodeAction', () => {
|
||||
};
|
||||
|
||||
setup(function () {
|
||||
disposables.clear();
|
||||
model = TextModel.createFromString('test1\ntest2\ntest3', undefined, langId, uri);
|
||||
disposables = [model];
|
||||
disposables.add(model);
|
||||
});
|
||||
|
||||
teardown(function () {
|
||||
dispose(disposables);
|
||||
disposables.clear();
|
||||
});
|
||||
|
||||
test('CodeActions are sorted by type, #38623', async function () {
|
||||
|
||||
const provider = new class implements CodeActionProvider {
|
||||
provideCodeActions() {
|
||||
return [
|
||||
testData.command.abc,
|
||||
testData.diagnostics.bcd,
|
||||
testData.spelling.bcd,
|
||||
testData.tsLint.bcd,
|
||||
testData.tsLint.abc,
|
||||
testData.diagnostics.abc
|
||||
];
|
||||
}
|
||||
};
|
||||
const provider = staticCodeActionProvider(
|
||||
testData.command.abc,
|
||||
testData.diagnostics.bcd,
|
||||
testData.spelling.bcd,
|
||||
testData.tsLint.bcd,
|
||||
testData.tsLint.abc,
|
||||
testData.diagnostics.abc
|
||||
);
|
||||
|
||||
disposables.push(CodeActionProviderRegistry.register('fooLang', provider));
|
||||
disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
const expected = [
|
||||
// CodeActions with a diagnostics array are shown first ordered by diagnostics.message
|
||||
@@ -122,17 +131,13 @@ suite('CodeAction', () => {
|
||||
});
|
||||
|
||||
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' }
|
||||
];
|
||||
}
|
||||
};
|
||||
const provider = staticCodeActionProvider(
|
||||
{ title: 'a', kind: 'a' },
|
||||
{ title: 'b', kind: 'b' },
|
||||
{ title: 'a.b', kind: 'a.b' }
|
||||
);
|
||||
|
||||
disposables.push(CodeActionProviderRegistry.register('fooLang', provider));
|
||||
disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
{
|
||||
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None);
|
||||
@@ -154,15 +159,18 @@ suite('CodeAction', () => {
|
||||
});
|
||||
|
||||
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 }
|
||||
];
|
||||
const provider = new class implements modes.CodeActionProvider {
|
||||
provideCodeActions(_model: any, _range: Range, context: modes.CodeActionContext, _token: any): modes.CodeActionList {
|
||||
return {
|
||||
actions: [
|
||||
{ title: context.only || '', kind: context.only }
|
||||
],
|
||||
dispose: () => { }
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
disposables.push(CodeActionProviderRegistry.register('fooLang', provider));
|
||||
disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
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);
|
||||
@@ -170,16 +178,12 @@ suite('CodeAction', () => {
|
||||
});
|
||||
|
||||
test('getCodeActions should not return source code action by default', async function () {
|
||||
const provider = new class implements CodeActionProvider {
|
||||
provideCodeActions(): CodeAction[] {
|
||||
return [
|
||||
{ title: 'a', kind: CodeActionKind.Source.value },
|
||||
{ title: 'b', kind: 'b' }
|
||||
];
|
||||
}
|
||||
};
|
||||
const provider = staticCodeActionProvider(
|
||||
{ title: 'a', kind: CodeActionKind.Source.value },
|
||||
{ title: 'b', kind: 'b' }
|
||||
);
|
||||
|
||||
disposables.push(CodeActionProviderRegistry.register('fooLang', provider));
|
||||
disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
{
|
||||
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto' }, CancellationToken.None);
|
||||
@@ -196,16 +200,16 @@ suite('CodeAction', () => {
|
||||
|
||||
test('getCodeActions should not invoke code action providers filtered out by providedCodeActionKinds', async function () {
|
||||
let wasInvoked = false;
|
||||
const provider = new class implements CodeActionProvider {
|
||||
provideCodeActions() {
|
||||
const provider = new class implements modes.CodeActionProvider {
|
||||
provideCodeActions(): modes.CodeActionList {
|
||||
wasInvoked = true;
|
||||
return [];
|
||||
return { actions: [], dispose: () => { } };
|
||||
}
|
||||
|
||||
providedCodeActionKinds = [CodeActionKind.Refactor.value];
|
||||
};
|
||||
|
||||
disposables.push(CodeActionProviderRegistry.register('fooLang', provider));
|
||||
disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), {
|
||||
type: 'auto',
|
||||
|
||||
@@ -4,32 +4,38 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
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, CodeActionsState } from 'vs/editor/contrib/codeAction/codeActionModel';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { CodeActionModel, CodeActionsState } from 'vs/editor/contrib/codeAction/codeActionModel';
|
||||
import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
|
||||
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
|
||||
import { MarkerService } from 'vs/platform/markers/common/markerService';
|
||||
|
||||
const testProvider = {
|
||||
provideCodeActions() {
|
||||
return [{ id: 'test-command', title: 'test', arguments: [] }];
|
||||
provideCodeActions(): modes.CodeActionList {
|
||||
return {
|
||||
actions: [
|
||||
{ title: 'test', command: { id: 'test-command', title: 'test', arguments: [] } }
|
||||
],
|
||||
dispose() { /* noop*/ }
|
||||
};
|
||||
}
|
||||
};
|
||||
suite('CodeAction', () => {
|
||||
suite('CodeActionModel', () => {
|
||||
|
||||
const languageIdentifier = new LanguageIdentifier('foo-lang', 3);
|
||||
const languageIdentifier = new modes.LanguageIdentifier('foo-lang', 3);
|
||||
let uri = URI.parse('untitled:path');
|
||||
let model: TextModel;
|
||||
let markerService: MarkerService;
|
||||
let editor: ICodeEditor;
|
||||
let disposables: IDisposable[];
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
setup(() => {
|
||||
disposables = [];
|
||||
disposables.clear();
|
||||
markerService = new MarkerService();
|
||||
model = TextModel.createFromString('foobar foo bar\nfarboo far boo', undefined, languageIdentifier, uri);
|
||||
editor = createTestCodeEditor({ model: model });
|
||||
@@ -37,26 +43,28 @@ suite('CodeAction', () => {
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
dispose(disposables);
|
||||
disposables.clear();
|
||||
editor.dispose();
|
||||
model.dispose();
|
||||
markerService.dispose();
|
||||
});
|
||||
|
||||
test('Orcale -> marker added', done => {
|
||||
const reg = CodeActionProviderRegistry.register(languageIdentifier.language, testProvider);
|
||||
disposables.push(reg);
|
||||
const reg = modes.CodeActionProviderRegistry.register(languageIdentifier.language, testProvider);
|
||||
disposables.add(reg);
|
||||
|
||||
const oracle = new CodeActionOracle(editor, markerService, (e: CodeActionsState.Triggered) => {
|
||||
const contextKeys = new MockContextKeyService();
|
||||
const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined));
|
||||
disposables.add(model.onDidChangeState((e: CodeActionsState.Triggered) => {
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
assert.ok(e.actions);
|
||||
|
||||
e.actions.then(fixes => {
|
||||
oracle.dispose();
|
||||
model.dispose();
|
||||
assert.equal(fixes.actions.length, 1);
|
||||
done();
|
||||
}, done);
|
||||
});
|
||||
}));
|
||||
|
||||
// start here
|
||||
markerService.changeOne('fake', uri, [{
|
||||
@@ -70,8 +78,8 @@ suite('CodeAction', () => {
|
||||
});
|
||||
|
||||
test('Orcale -> position changed', () => {
|
||||
const reg = CodeActionProviderRegistry.register(languageIdentifier.language, testProvider);
|
||||
disposables.push(reg);
|
||||
const reg = modes.CodeActionProviderRegistry.register(languageIdentifier.language, testProvider);
|
||||
disposables.add(reg);
|
||||
|
||||
markerService.changeOne('fake', uri, [{
|
||||
startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 6,
|
||||
@@ -84,28 +92,29 @@ suite('CodeAction', () => {
|
||||
editor.setPosition({ lineNumber: 2, column: 1 });
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
const oracle = new CodeActionOracle(editor, markerService, (e: CodeActionsState.Triggered) => {
|
||||
const contextKeys = new MockContextKeyService();
|
||||
const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined));
|
||||
disposables.add(model.onDidChangeState((e: CodeActionsState.Triggered) => {
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
assert.ok(e.actions);
|
||||
e.actions.then(fixes => {
|
||||
oracle.dispose();
|
||||
model.dispose();
|
||||
assert.equal(fixes.actions.length, 1);
|
||||
resolve(undefined);
|
||||
}, reject);
|
||||
});
|
||||
}));
|
||||
// start here
|
||||
editor.setPosition({ lineNumber: 1, column: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
test('Lightbulb is in the wrong place, #29933', async function () {
|
||||
const reg = CodeActionProviderRegistry.register(languageIdentifier.language, {
|
||||
provideCodeActions(_doc, _range) {
|
||||
return [];
|
||||
const reg = modes.CodeActionProviderRegistry.register(languageIdentifier.language, {
|
||||
provideCodeActions(_doc, _range): modes.CodeActionList {
|
||||
return { actions: [], dispose() { /* noop*/ } };
|
||||
}
|
||||
});
|
||||
disposables.push(reg);
|
||||
disposables.add(reg);
|
||||
|
||||
editor.getModel()!.setValue('// @ts-check\n2\ncon\n');
|
||||
|
||||
@@ -119,8 +128,9 @@ 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: CodeActionsState.Triggered) => {
|
||||
const contextKeys = new MockContextKeyService();
|
||||
const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined));
|
||||
disposables.add(model.onDidChangeState((e: CodeActionsState.Triggered) => {
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
const selection = <Selection>e.rangeOrSelection;
|
||||
assert.deepEqual(selection.selectionStartLineNumber, 1);
|
||||
@@ -128,31 +138,32 @@ suite('CodeAction', () => {
|
||||
assert.deepEqual(selection.endLineNumber, 4);
|
||||
assert.deepEqual(selection.endColumn, 1);
|
||||
assert.deepEqual(e.position, { lineNumber: 3, column: 1 });
|
||||
|
||||
oracle.dispose();
|
||||
model.dispose();
|
||||
resolve(undefined);
|
||||
}, 5);
|
||||
}, 5));
|
||||
|
||||
editor.setSelection({ startLineNumber: 1, startColumn: 1, endLineNumber: 4, endColumn: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
test('Orcale -> should only auto trigger once for cursor and marker update right after each other', done => {
|
||||
const reg = CodeActionProviderRegistry.register(languageIdentifier.language, testProvider);
|
||||
disposables.push(reg);
|
||||
const reg = modes.CodeActionProviderRegistry.register(languageIdentifier.language, testProvider);
|
||||
disposables.add(reg);
|
||||
|
||||
let triggerCount = 0;
|
||||
const oracle = new CodeActionOracle(editor, markerService, (e: CodeActionsState.Triggered) => {
|
||||
const contextKeys = new MockContextKeyService();
|
||||
const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined));
|
||||
disposables.add(model.onDidChangeState((e: CodeActionsState.Triggered) => {
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
++triggerCount;
|
||||
|
||||
// give time for second trigger before completing test
|
||||
setTimeout(() => {
|
||||
oracle.dispose();
|
||||
model.dispose();
|
||||
assert.strictEqual(triggerCount, 1);
|
||||
done();
|
||||
}, 50);
|
||||
}, 5 /*delay*/);
|
||||
}, 5 /*delay*/));
|
||||
|
||||
markerService.changeOne('fake', uri, [{
|
||||
startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 6,
|
||||
|
||||
Reference in New Issue
Block a user