Skip to content

Commit 735e61b

Browse files
authored
Simplify action widget api (microsoft#166763)
This changes the action widget take a list of items and a list of action bar actions. This simplifies using the action widget service and lets us move code action specific code (such as documentation items) up into the codeActionUi
1 parent 6b968ef commit 735e61b

File tree

7 files changed

+88
-112
lines changed

7 files changed

+88
-112
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,8 @@ export class CodeActionController extends Disposable implements IEditorContribut
128128
this._ui.getValue().update(newState);
129129
}
130130

131-
public showCodeActions(trigger: CodeActionTrigger, actions: CodeActionSet, at: IAnchor | IPosition) {
132-
return this._ui.getValue().showCodeActionList(trigger, actions, at, { includeDisabledActions: false, fromLightbulb: false });
131+
public showCodeActions(_trigger: CodeActionTrigger, actions: CodeActionSet, at: IAnchor | IPosition) {
132+
return this._ui.getValue().showCodeActionList(actions, at, { includeDisabledActions: false, fromLightbulb: false });
133133
}
134134

135135
public manualTriggerAtCurrentPosition(

src/vs/editor/contrib/codeAction/browser/codeActionUi.ts

Lines changed: 67 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,38 @@
55

66
import { getDomNodePagePosition } from 'vs/base/browser/dom';
77
import { IAnchor } from 'vs/base/browser/ui/contextview/contextview';
8+
import { IAction } from 'vs/base/common/actions';
89
import { onUnexpectedError } from 'vs/base/common/errors';
910
import { Lazy } from 'vs/base/common/lazy';
1011
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
1112
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
1213
import { IPosition, Position } from 'vs/editor/common/core/position';
1314
import { ScrollType } from 'vs/editor/common/editorCommon';
1415
import { CodeActionTriggerType } from 'vs/editor/common/languages';
15-
import { IActionShowOptions, IActionWidgetService, IRenderDelegate } from 'vs/platform/actionWidget/browser/actionWidget';
16+
import { toMenuItems } from 'vs/editor/contrib/codeAction/browser/codeActionMenuItems';
1617
import { MessageController } from 'vs/editor/contrib/message/browser/messageController';
18+
import { localize } from 'vs/nls';
19+
import { IActionWidgetService, IRenderDelegate } from 'vs/platform/actionWidget/browser/actionWidget';
20+
import { ICommandService } from 'vs/platform/commands/common/commands';
1721
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
1822
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
1923
import { CodeActionAutoApply, CodeActionItem, CodeActionSet, CodeActionTrigger } from '../common/types';
2024
import { CodeActionsState } from './codeActionModel';
2125
import { LightBulbWidget } from './lightBulbWidget';
22-
import { toMenuItems } from 'vs/editor/contrib/codeAction/browser/codeActionMenuItems';
26+
27+
export interface IActionShowOptions {
28+
readonly includeDisabledActions?: boolean;
29+
readonly fromLightbulb?: boolean;
30+
}
2331

2432
export class CodeActionUi extends Disposable {
2533
private readonly _lightBulbWidget: Lazy<LightBulbWidget>;
2634
private readonly _activeCodeActions = this._register(new MutableDisposable<CodeActionSet>());
2735

2836
#disposed = false;
2937

38+
private _showDisabled = false;
39+
3040
constructor(
3141
private readonly _editor: ICodeEditor,
3242
quickFixActionId: string,
@@ -36,14 +46,14 @@ export class CodeActionUi extends Disposable {
3646
},
3747
@IConfigurationService private readonly _configurationService: IConfigurationService,
3848
@IInstantiationService readonly instantiationService: IInstantiationService,
39-
@IActionWidgetService private readonly _actionWidgetService: IActionWidgetService
49+
@IActionWidgetService private readonly _actionWidgetService: IActionWidgetService,
50+
@ICommandService private readonly _commandService: ICommandService,
4051
) {
4152
super();
4253

43-
4454
this._lightBulbWidget = new Lazy(() => {
4555
const widget = this._register(instantiationService.createInstance(LightBulbWidget, this._editor, quickFixActionId, preferredFixActionId));
46-
this._register(widget.onClick(e => this.showCodeActionList(e.trigger, e.actions, e, { includeDisabledActions: false, fromLightbulb: true, showHeaders: this.shouldShowHeaders() })));
56+
this._register(widget.onClick(e => this.showCodeActionList(e.actions, e, { includeDisabledActions: false, fromLightbulb: true })));
4757
return widget;
4858
});
4959

@@ -112,7 +122,7 @@ export class CodeActionUi extends Disposable {
112122
}
113123

114124
this._activeCodeActions.value = actions;
115-
this.showCodeActionList(newState.trigger, actions, this.toCoords(newState.position), { includeDisabledActions, fromLightbulb: false, showHeaders: this.shouldShowHeaders() });
125+
this.showCodeActionList(actions, this.toCoords(newState.position), { includeDisabledActions, fromLightbulb: false });
116126
} else {
117127
// auto magically triggered
118128
if (this._actionWidgetService.isVisible) {
@@ -152,12 +162,17 @@ export class CodeActionUi extends Disposable {
152162
return undefined;
153163
}
154164

155-
public async showCodeActionList(trigger: CodeActionTrigger, actions: CodeActionSet, at: IAnchor | IPosition, options: IActionShowOptions): Promise<void> {
165+
public async showCodeActionList(actions: CodeActionSet, at: IAnchor | IPosition, options: IActionShowOptions): Promise<void> {
156166
const editorDom = this._editor.getDomNode();
157167
if (!editorDom) {
158168
return;
159169
}
160170

171+
const actionsToShow = options.includeDisabledActions && (this._showDisabled || actions.validActions.length === 0) ? actions.allActions : actions.validActions;
172+
if (!actionsToShow.length) {
173+
return;
174+
}
175+
161176
const anchor = Position.isIPosition(at) ? this.toCoords(at) : at;
162177

163178
const delegate: IRenderDelegate<CodeActionItem> = {
@@ -169,14 +184,14 @@ export class CodeActionUi extends Disposable {
169184
this._editor?.focus();
170185
}
171186
};
187+
172188
this._actionWidgetService.show(
173189
'codeActionWidget',
174-
toMenuItems,
190+
toMenuItems(actionsToShow, this._shouldShowHeaders()),
175191
delegate,
176-
actions,
177192
anchor,
178193
editorDom,
179-
{ ...options, showHeaders: this.shouldShowHeaders() });
194+
this._getActionBarActions(actions, at, options));
180195
}
181196

182197
private toCoords(position: IPosition): IAnchor {
@@ -196,8 +211,49 @@ export class CodeActionUi extends Disposable {
196211
return { x, y };
197212
}
198213

199-
private shouldShowHeaders(): boolean {
214+
private _shouldShowHeaders(): boolean {
200215
const model = this._editor?.getModel();
201216
return this._configurationService.getValue('editor.codeActionWidget.showHeaders', { resource: model?.uri });
202217
}
218+
219+
private _getActionBarActions(actions: CodeActionSet, at: IAnchor | IPosition, options: IActionShowOptions): IAction[] {
220+
if (options.fromLightbulb) {
221+
return [];
222+
}
223+
224+
const resultActions = actions.documentation.map((command): IAction => ({
225+
id: command.id,
226+
label: command.title,
227+
tooltip: command.tooltip ?? '',
228+
class: undefined,
229+
enabled: true,
230+
run: () => this._commandService.executeCommand(command.id, ...(command.commandArguments ?? [])),
231+
}));
232+
233+
if (options.includeDisabledActions && actions.validActions.length > 0 && actions.allActions.length !== actions.validActions.length) {
234+
resultActions.push(this._showDisabled ? {
235+
id: 'hideMoreActions',
236+
label: localize('hideMoreActions', 'Hide Disabled'),
237+
enabled: true,
238+
tooltip: '',
239+
class: undefined,
240+
run: () => {
241+
this._showDisabled = false;
242+
return this.showCodeActionList(actions, at, options);
243+
}
244+
} : {
245+
id: 'showMoreActions',
246+
label: localize('showMoreActions', 'Show Disabled'),
247+
enabled: true,
248+
tooltip: '',
249+
class: undefined,
250+
run: () => {
251+
this._showDisabled = true;
252+
return this.showCodeActionList(actions, at, options);
253+
}
254+
});
255+
}
256+
257+
return resultActions;
258+
}
203259
}

src/vs/editor/contrib/codeAction/common/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,4 +215,11 @@ export class CodeActionItem implements IActionItem {
215215
export interface CodeActionSet extends ActionSet<CodeActionItem> {
216216
readonly validActions: readonly CodeActionItem[];
217217
readonly allActions: readonly CodeActionItem[];
218+
219+
readonly documentation: readonly {
220+
id: string;
221+
title: string;
222+
tooltip?: string;
223+
commandArguments?: any[];
224+
}[];
218225
}

src/vs/platform/actionWidget/browser/actionWidget.ts

Lines changed: 10 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ import 'vs/css!./actionWidget';
1212
import { localize } from 'vs/nls';
1313
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
1414
import { acceptSelectedActionCommand, ActionList, IListMenuItem, previewSelectedActionCommand } from 'vs/platform/actionWidget/browser/actionList';
15-
import { ActionSet, IActionItem, IActionKeybindingResolver } from 'vs/platform/actionWidget/common/actionWidget';
16-
import { ICommandService } from 'vs/platform/commands/common/commands';
15+
import { IActionItem, IActionKeybindingResolver } from 'vs/platform/actionWidget/common/actionWidget';
1716
import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
1817
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
1918
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
@@ -30,18 +29,13 @@ export interface IRenderDelegate<T extends IActionItem> {
3029
onSelect(action: IActionItem, preview?: boolean): Promise<any>;
3130
}
3231

33-
export interface IActionShowOptions {
34-
readonly includeDisabledActions: boolean;
35-
readonly fromLightbulb?: boolean;
36-
readonly showHeaders?: boolean;
37-
}
38-
3932
export const IActionWidgetService = createDecorator<IActionWidgetService>('actionWidgetService');
4033

4134
export interface IActionWidgetService {
4235
readonly _serviceBrand: undefined;
4336

44-
show(user: string, toMenuItems: (inputQuickFixes: readonly any[], showHeaders: boolean) => IListMenuItem<IActionItem>[], delegate: IRenderDelegate<any>, actions: ActionSet<any>, anchor: IAnchor, container: HTMLElement | undefined, options: IActionShowOptions): Promise<void>;
37+
show(user: string, items: IListMenuItem<IActionItem>[], delegate: IRenderDelegate<any>, anchor: IAnchor, container: HTMLElement | undefined, actionBarActions?: readonly IAction[]): Promise<void>;
38+
4539
hide(): void;
4640

4741
readonly isVisible: boolean;
@@ -54,47 +48,25 @@ class ActionWidgetService extends Disposable implements IActionWidgetService {
5448
return ActionWidgetContextKeys.Visible.getValue(this._contextKeyService) || false;
5549
}
5650

57-
private _showDisabled = false;
58-
private _currentShowingContext?: {
59-
readonly user: string;
60-
readonly toMenuItems: (inputItems: readonly any[], showHeaders: boolean) => IListMenuItem<any>[];
61-
readonly options: IActionShowOptions;
62-
readonly anchor: IAnchor;
63-
readonly container: HTMLElement | undefined;
64-
readonly actions: ActionSet<unknown>;
65-
readonly delegate: IRenderDelegate<any>;
66-
readonly resolver?: IActionKeybindingResolver;
67-
};
68-
6951
private readonly _list = this._register(new MutableDisposable<ActionList<any>>());
7052

7153
constructor(
72-
@ICommandService private readonly _commandService: ICommandService,
7354
@IContextViewService private readonly contextViewService: IContextViewService,
7455
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
7556
@IInstantiationService private readonly _instantiationService: IInstantiationService
7657
) {
7758
super();
7859
}
7960

80-
async show(user: string, toMenuItems: (inputQuickFixes: readonly IActionItem[], showHeaders: boolean) => IListMenuItem<IActionItem>[], delegate: IRenderDelegate<any>, actions: ActionSet<any>, anchor: IAnchor, container: HTMLElement | undefined, options: IActionShowOptions, resolver?: IActionKeybindingResolver): Promise<void> {
81-
this._currentShowingContext = undefined;
61+
async show(user: string, items: IListMenuItem<IActionItem>[], delegate: IRenderDelegate<any>, anchor: IAnchor, container: HTMLElement | undefined, actionBarActions?: readonly IAction[], resolver?: IActionKeybindingResolver): Promise<void> {
8262
const visibleContext = ActionWidgetContextKeys.Visible.bindTo(this._contextKeyService);
8363

84-
const actionsToShow = options.includeDisabledActions && (this._showDisabled || actions.validActions.length === 0) ? actions.allActions : actions.validActions;
85-
if (!actionsToShow.length) {
86-
visibleContext.reset();
87-
return;
88-
}
89-
90-
this._currentShowingContext = { user, toMenuItems, delegate, actions, anchor, container, options, resolver };
91-
92-
const list = this._instantiationService.createInstance(ActionList, user, toMenuItems(actionsToShow, true), delegate, resolver);
64+
const list = this._instantiationService.createInstance(ActionList, user, items, delegate, resolver);
9365
this.contextViewService.showContextView({
9466
getAnchor: () => anchor,
9567
render: (container: HTMLElement) => {
9668
visibleContext.set(true);
97-
return this._renderWidget(container, list, actions, options);
69+
return this._renderWidget(container, list, actionBarActions ?? []);
9870
},
9971
onHide: (didCancel) => {
10072
visibleContext.reset();
@@ -124,7 +96,7 @@ class ActionWidgetService extends Disposable implements IActionWidgetService {
12496
this._list.clear();
12597
}
12698

127-
private _renderWidget(element: HTMLElement, list: ActionList<any>, actions: ActionSet<any>, options: IActionShowOptions): IDisposable {
99+
private _renderWidget(element: HTMLElement, list: ActionList<any>, actionBarActions: readonly IAction[]): IDisposable {
128100
const widget = document.createElement('div');
129101
widget.classList.add('action-widget');
130102
element.appendChild(widget);
@@ -154,8 +126,8 @@ class ActionWidgetService extends Disposable implements IActionWidgetService {
154126

155127
// Action bar
156128
let actionBarWidth = 0;
157-
if (!options.fromLightbulb) {
158-
const actionBar = this._createActionBar('.action-widget-action-bar', actions, options);
129+
if (actionBarActions.length) {
130+
const actionBar = this._createActionBar('.action-widget-action-bar', actionBarActions);
159131
if (actionBar) {
160132
widget.appendChild(actionBar.getContainer().parentElement!);
161133
renderDisposables.add(actionBar);
@@ -172,8 +144,7 @@ class ActionWidgetService extends Disposable implements IActionWidgetService {
172144
return renderDisposables;
173145
}
174146

175-
private _createActionBar(className: string, inputActions: ActionSet<any>, options: IActionShowOptions): ActionBar | undefined {
176-
const actions = this._getActionBarActions(inputActions, options);
147+
private _createActionBar(className: string, actions: readonly IAction[]): ActionBar | undefined {
177148
if (!actions.length) {
178149
return undefined;
179150
}
@@ -184,54 +155,7 @@ class ActionWidgetService extends Disposable implements IActionWidgetService {
184155
return actionBar;
185156
}
186157

187-
private _getActionBarActions(actions: ActionSet<any>, options: IActionShowOptions): IAction[] {
188-
const resultActions = actions.documentation.map((command): IAction => ({
189-
id: command.id,
190-
label: command.title,
191-
tooltip: command.tooltip ?? '',
192-
class: undefined,
193-
enabled: true,
194-
run: () => this._commandService.executeCommand(command.id, ...(command.commandArguments ?? [])),
195-
}));
196-
197-
if (options.includeDisabledActions && actions.validActions.length > 0 && actions.allActions.length !== actions.validActions.length) {
198-
resultActions.push(this._showDisabled ? {
199-
id: 'hideMoreActions',
200-
label: localize('hideMoreActions', 'Hide Disabled'),
201-
enabled: true,
202-
tooltip: '',
203-
class: undefined,
204-
run: () => this._toggleShowDisabled(false)
205-
} : {
206-
id: 'showMoreActions',
207-
label: localize('showMoreActions', 'Show Disabled'),
208-
enabled: true,
209-
tooltip: '',
210-
class: undefined,
211-
run: () => this._toggleShowDisabled(true)
212-
});
213-
}
214-
215-
return resultActions;
216-
}
217-
218-
/**
219-
* Toggles whether the disabled actions in the action widget are visible or not.
220-
*/
221-
private _toggleShowDisabled(newShowDisabled: boolean): void {
222-
const previousCtx = this._currentShowingContext;
223-
224-
this.hide();
225-
226-
this._showDisabled = newShowDisabled;
227-
228-
if (previousCtx) {
229-
this.show(previousCtx.user, previousCtx.toMenuItems, previousCtx.delegate, previousCtx.actions, previousCtx.anchor, previousCtx.container, previousCtx.options, previousCtx.resolver);
230-
}
231-
}
232-
233158
private _onWidgetClosed(didCancel?: boolean): void {
234-
this._currentShowingContext = undefined;
235159
this._list.value?.hide(didCancel);
236160
}
237161
}

src/vs/platform/actionWidget/common/actionWidget.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,6 @@ export interface ActionSet<T> extends IDisposable {
1010
readonly validActions: readonly T[];
1111
readonly allActions: readonly T[];
1212
readonly hasAutoFix: boolean;
13-
14-
readonly documentation: readonly {
15-
id: string;
16-
title: string;
17-
tooltip?: string;
18-
commandArguments?: any[];
19-
}[];
2013
}
2114

2215
export interface IActionItem {

src/vs/workbench/contrib/terminal/browser/terminalQuickFixBuiltinActions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { TerminalQuickFixMatchResult, ITerminalQuickFixOptions, ITerminalInstanc
88
import { ITerminalCommand } from 'vs/workbench/contrib/terminal/common/terminal';
99
import { IExtensionTerminalQuickFix } from 'vs/platform/terminal/common/terminal';
1010
import { TerminalQuickFixType } from 'vs/workbench/contrib/terminal/browser/widgets/terminalQuickFixMenuItems';
11+
1112
export const GitCommandLineRegex = /git/;
1213
export const GitPushCommandLineRegex = /git\s+push/;
1314
export const GitTwoDashesRegex = /error: did you mean `--(.+)` \(with two dashes\)\?/;

src/vs/workbench/contrib/terminal/browser/xterm/quickFixAddon.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -253,12 +253,7 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon,
253253
this._terminal?.focus();
254254
},
255255
};
256-
this._actionWidgetService.show('quickFixWidget', toMenuItems, delegate, actionSet, anchor, parentElement,
257-
{
258-
showHeaders: true,
259-
includeDisabledActions: false,
260-
fromLightbulb: true
261-
});
256+
this._actionWidgetService.show('quickFixWidget', toMenuItems(actionSet.validActions, true), delegate, anchor, parentElement);
262257
}));
263258
});
264259
}

0 commit comments

Comments
 (0)