Skip to content

Commit

Permalink
Handle promise cancelled for codeActionModel
Browse files Browse the repository at this point in the history
Fixes #114031

This handles the case where an external caller is waiting on the actions, but the model is disposed. In this case, we should return an empty code action set instead of throwing an exception
  • Loading branch information
mjbvz committed Feb 22, 2021
1 parent 2b44e04 commit b6377b8
Showing 1 changed file with 40 additions and 7 deletions.
47 changes: 40 additions & 7 deletions src/vs/editor/contrib/codeAction/codeActionModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@
*--------------------------------------------------------------------------------------------*/

import { CancelablePromise, createCancelablePromise, TimeoutTimer } from 'vs/base/common/async';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { Emitter } from 'vs/base/common/event';
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { CodeActionProviderRegistry, CodeActionTriggerType } from 'vs/editor/common/modes';
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IMarkerService } from 'vs/platform/markers/common/markers';
import { IEditorProgressService, Progress } from 'vs/platform/progress/common/progress';
import { getCodeActions, CodeActionSet } from './codeAction';
import { CodeActionSet, getCodeActions } from './codeAction';
import { CodeActionTrigger } from './types';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { isEqual } from 'vs/base/common/resources';

export const SUPPORTED_CODE_ACTIONS = new RawContextKey<string>('supportedCodeAction', '');

Expand Down Expand Up @@ -147,17 +148,38 @@ export namespace CodeActionsState {
export class Triggered {
readonly type = Type.Triggered;

public readonly actions: Promise<CodeActionSet>;

constructor(
public readonly trigger: CodeActionTrigger,
public readonly rangeOrSelection: Range | Selection,
public readonly position: Position,
public readonly actions: CancelablePromise<CodeActionSet>,
) { }
private readonly _cancellablePromise: CancelablePromise<CodeActionSet>,
) {
this.actions = _cancellablePromise.catch((e): CodeActionSet => {
if (isPromiseCanceledError(e)) {
return emptyCodeActionSet;
}
throw e;
});
}

public cancel() {
this._cancellablePromise.cancel();
}
}

export type State = typeof Empty | Triggered;
}

const emptyCodeActionSet: CodeActionSet = {
allActions: [],
validActions: [],
dispose: () => { },
documentation: [],
hasAutoFix: false
};

export class CodeActionModel extends Disposable {

private readonly _codeActionOracle = this._register(new MutableDisposable<CodeActionOracle>());
Expand All @@ -167,6 +189,8 @@ export class CodeActionModel extends Disposable {
private readonly _onDidChangeState = this._register(new Emitter<CodeActionsState.State>());
public readonly onDidChangeState = this._onDidChangeState.event;

#isDisposed = false;

constructor(
private readonly _editor: ICodeEditor,
private readonly _markerService: IMarkerService,
Expand All @@ -184,11 +208,20 @@ export class CodeActionModel extends Disposable {
}

dispose(): void {
if (this.#isDisposed) {
return;
}
this.#isDisposed = true;

super.dispose();
this.setState(CodeActionsState.Empty, true);
}

private _update(): void {
if (this.#isDisposed) {
return;
}

this._codeActionOracle.value = undefined;

this.setState(CodeActionsState.Empty);
Expand Down Expand Up @@ -240,12 +273,12 @@ export class CodeActionModel extends Disposable {

// Cancel old request
if (this._state.type === CodeActionsState.Type.Triggered) {
this._state.actions.cancel();
this._state.cancel();
}

this._state = newState;

if (!skipNotify) {
if (!skipNotify && !this.#isDisposed) {
this._onDidChangeState.fire(newState);
}
}
Expand Down

0 comments on commit b6377b8

Please sign in to comment.