Skip to content

Commit 2213894

Browse files
authored
Merge pull request microsoft#76917 from mjbvz/fixes-76851
Fix lifecycle for code actions that are updated while the code action menu is already showing
2 parents a3d6fcf + efac204 commit 2213894

File tree

3 files changed

+30
-15
lines changed

3 files changed

+30
-15
lines changed

src/vs/editor/contrib/codeAction/codeActionCommands.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
7-
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
7+
import { Disposable } from 'vs/base/common/lifecycle';
88
import { escapeRegExpCharacters } from 'vs/base/common/strings';
99
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
1010
import { EditorAction, EditorCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
@@ -26,7 +26,6 @@ import { CodeActionWidget } from './codeActionWidget';
2626
import { LightBulbWidget } from './lightBulbWidget';
2727
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
2828
import { onUnexpectedError } from 'vs/base/common/errors';
29-
import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction';
3029

3130
function contextKeyForSupportedActions(kind: CodeActionKind) {
3231
return ContextKeyExpr.regex(
@@ -46,7 +45,6 @@ export class QuickFixController extends Disposable implements IEditorContributio
4645
private readonly _model: CodeActionModel;
4746
private readonly _codeActionWidget: CodeActionWidget;
4847
private readonly _lightBulbWidget: LightBulbWidget;
49-
private readonly _currentCodeActions = this._register(new MutableDisposable<CodeActionSet>());
5048

5149
constructor(
5250
editor: ICodeEditor,
@@ -81,29 +79,30 @@ export class QuickFixController extends Disposable implements IEditorContributio
8179
this._register(this._keybindingService.onDidUpdateKeybindings(this._updateLightBulbTitle, this));
8280
}
8381

84-
8582
private _onDidChangeCodeActionsState(newState: CodeActionsState.State): void {
8683
if (newState.type === CodeActionsState.Type.Triggered) {
8784
newState.actions.then(actions => {
88-
this._currentCodeActions.value = actions;
89-
9085
if (!actions.actions.length && newState.trigger.context) {
9186
MessageController.get(this._editor).showMessage(newState.trigger.context.notAvailableMessage, newState.trigger.context.position);
87+
actions.dispose();
9288
}
9389
});
9490

9591
if (newState.trigger.filter && newState.trigger.filter.kind) {
9692
// Triggered for specific scope
97-
newState.actions.then(codeActions => {
93+
newState.actions.then(async codeActions => {
9894
if (codeActions.actions.length > 0) {
9995
// Apply if we only have one action or requested autoApply
10096
if (newState.trigger.autoApply === CodeActionAutoApply.First || (newState.trigger.autoApply === CodeActionAutoApply.IfSingle && codeActions.actions.length === 1)) {
101-
this._applyCodeAction(codeActions.actions[0]);
97+
try {
98+
await this._applyCodeAction(codeActions.actions[0]);
99+
} finally {
100+
codeActions.dispose();
101+
}
102102
return;
103103
}
104104
}
105105
this._codeActionWidget.show(newState.actions, newState.position);
106-
107106
}).catch(onUnexpectedError);
108107
} else if (newState.trigger.type === 'manual') {
109108
this._codeActionWidget.show(newState.actions, newState.position);
@@ -118,7 +117,6 @@ export class QuickFixController extends Disposable implements IEditorContributio
118117
}
119118
}
120119
} else {
121-
this._currentCodeActions.clear();
122120
this._lightBulbWidget.hide();
123121
}
124122
}

src/vs/editor/contrib/codeAction/codeActionWidget.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,35 +12,49 @@ import { ScrollType } from 'vs/editor/common/editorCommon';
1212
import { CodeAction } from 'vs/editor/common/modes';
1313
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
1414
import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction';
15+
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
1516

1617
interface CodeActionWidgetDelegate {
1718
onSelectCodeAction: (action: CodeAction) => Promise<any>;
1819
}
1920

20-
export class CodeActionWidget {
21+
export class CodeActionWidget extends Disposable {
2122

2223
private _visible: boolean;
24+
private readonly _showingActions = this._register(new MutableDisposable<CodeActionSet>());
2325

2426
constructor(
2527
private readonly _editor: ICodeEditor,
2628
private readonly _contextMenuService: IContextMenuService,
27-
private readonly _delegate: CodeActionWidgetDelegate
28-
) { }
29+
private readonly _delegate: CodeActionWidgetDelegate,
30+
) {
31+
super();
32+
}
2933

3034
public async show(actionsToShow: Promise<CodeActionSet>, at?: { x: number; y: number } | Position): Promise<void> {
31-
const codeActions = await actionsToShow;
35+
let codeActions: CodeActionSet | undefined = await actionsToShow;
3236
if (!codeActions.actions.length) {
37+
codeActions.dispose();
3338
this._visible = false;
3439
return;
3540
}
3641
if (!this._editor.getDomNode()) {
3742
// cancel when editor went off-dom
3843
this._visible = false;
44+
codeActions.dispose();
3945
return Promise.reject(canceled());
4046
}
4147

48+
if (this._visible) {
49+
// TODO: Figure out if we should update the showing menu?
50+
codeActions.dispose();
51+
return;
52+
}
53+
4254
this._visible = true;
4355
const actions = codeActions.actions.map(action => this.codeActionToAction(action));
56+
57+
this._showingActions.value = codeActions;
4458
this._contextMenuService.showContextMenu({
4559
getAnchor: () => {
4660
if (Position.isIPosition(at)) {

src/vs/editor/contrib/codeAction/lightBulbWidget.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as dom from 'vs/base/browser/dom';
77
import { GlobalMouseMoveMonitor, IStandardMouseMoveEventData, standardMouseMoveMerger } from 'vs/base/browser/globalMouseMoveMonitor';
88
import { CancellationTokenSource } from 'vs/base/common/cancellation';
99
import { Emitter } from 'vs/base/common/event';
10-
import { Disposable } from 'vs/base/common/lifecycle';
10+
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
1111
import 'vs/css!./lightBulbWidget';
1212
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
1313
import { TextModel } from 'vs/editor/common/model/textModel';
@@ -27,6 +27,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget {
2727
private _position: IContentWidgetPosition | null;
2828
private _state: CodeActionsState.State = CodeActionsState.Empty;
2929
private _futureFixes = new CancellationTokenSource();
30+
private readonly _showingActions = this._register(new MutableDisposable<CodeActionSet>());
3031

3132
constructor(editor: ICodeEditor) {
3233
super();
@@ -116,13 +117,15 @@ export class LightBulbWidget extends Disposable implements IContentWidget {
116117
// cancel pending show request in any case
117118
this._futureFixes.cancel();
118119
}
120+
this._showingActions.clear();
119121

120122
this._futureFixes = new CancellationTokenSource();
121123
const { token } = this._futureFixes;
122124
this._state = newState;
123125

124126
const selection = this._state.rangeOrSelection;
125127
this._state.actions.then(fixes => {
128+
this._showingActions.value = fixes;
126129
if (!token.isCancellationRequested && fixes.actions.length > 0 && selection) {
127130
this._show(fixes);
128131
} else {

0 commit comments

Comments
 (0)