mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-08 15:55:15 +01:00
chat: add manage action gear icon to installed agent plugins (#299052)
* chat: add manage action gear icon to installed agent plugins Adds a ManagePluginAction that displays a gear icon in the plugin list for installed plugins. Clicking the gear icon shows a context menu with management options (enable/disable, open folder, open README, uninstall), making it consistent with how extensions and MCP servers display their management UI. - Adds ManagePluginAction class that provides dropdown menu with management actions for installed plugins - Adds ManagePluginActionViewItem to render the action as a clickable gear icon with context menu - Updates AgentPluginRenderer to include the gear icon in the action bar for installed plugins - Context menu displays enable/disable, open folder, open README, and uninstall options Fixes https://github.com/microsoft/vscode/issues/298461 (Commit message generated by Copilot) * chat: clean up manage plugin action to follow MCP pattern - Extract shared getInstalledPluginContextMenuActionGroups() to deduplicate action construction between gear menu and context menu - Restructure ManagePluginAction with createActionViewItem() pattern matching MCP/extensions DropDownAction approach - Add onHide disposal for context menu actions - Remove redundant getDomNodePagePosition import
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
|
||||
import * as dom from '../../../../base/browser/dom.js';
|
||||
import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';
|
||||
import { ActionViewItem, IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';
|
||||
import { IListContextMenuEvent } from '../../../../base/browser/ui/list/list.js';
|
||||
import { IPagedRenderer } from '../../../../base/browser/ui/list/listPaging.js';
|
||||
import { Action, IAction, Separator } from '../../../../base/common/actions.js';
|
||||
@@ -12,7 +13,8 @@ import { RunOnceScheduler } from '../../../../base/common/async.js';
|
||||
import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
|
||||
import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { Event } from '../../../../base/common/event.js';
|
||||
import { Disposable, DisposableStore, IDisposable, isDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { Disposable, DisposableStore, disposeIfDisposable, IDisposable, isDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { ThemeIcon } from '../../../../base/common/themables.js';
|
||||
import { autorun } from '../../../../base/common/observable.js';
|
||||
import { IPagedModel, PagedModel } from '../../../../base/common/paging.js';
|
||||
import { basename, dirname, joinPath } from '../../../../base/common/resources.js';
|
||||
@@ -38,6 +40,7 @@ import { IWorkbenchContribution } from '../../../common/contributions.js';
|
||||
import { IViewDescriptorService, IViewsRegistry, Extensions as ViewExtensions } from '../../../common/views.js';
|
||||
import { IEditorService, MODAL_GROUP } from '../../../services/editor/common/editorService.js';
|
||||
import { VIEW_CONTAINER } from '../../extensions/browser/extensions.contribution.js';
|
||||
import { manageExtensionIcon } from '../../extensions/browser/extensionsIcons.js';
|
||||
import { AbstractExtensionsListView } from '../../extensions/browser/extensionsViews.js';
|
||||
import { DefaultViewsContext, extensionsFilterSubMenu, IExtensionsWorkbenchService, SearchAgentPluginsContext } from '../../extensions/common/extensions.js';
|
||||
import { ChatContextKeys } from '../common/actions/chatContextKeys.js';
|
||||
@@ -180,6 +183,71 @@ class OpenPluginReadmeAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
function getInstalledPluginContextMenuActionGroups(plugin: IAgentPlugin, instantiationService: IInstantiationService): IAction[][] {
|
||||
const groups: IAction[][] = [];
|
||||
if (plugin.enabled.get()) {
|
||||
groups.push([instantiationService.createInstance(DisablePluginAction, plugin)]);
|
||||
} else {
|
||||
groups.push([instantiationService.createInstance(EnablePluginAction, plugin)]);
|
||||
}
|
||||
groups.push([
|
||||
instantiationService.createInstance(OpenPluginFolderAction, plugin),
|
||||
instantiationService.createInstance(OpenPluginReadmeAction, joinPath(plugin.uri, 'README.md')),
|
||||
]);
|
||||
groups.push([instantiationService.createInstance(UninstallPluginAction, plugin)]);
|
||||
return groups;
|
||||
}
|
||||
|
||||
class ManagePluginAction extends Action {
|
||||
static readonly ID = 'agentPlugin.manage';
|
||||
static readonly CLASS = `extension-action icon manage ${ThemeIcon.asClassName(manageExtensionIcon)}`;
|
||||
|
||||
private _actionViewItem: DropDownActionViewItem | null = null;
|
||||
|
||||
constructor(
|
||||
private readonly getActionGroups: () => IAction[][],
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
) {
|
||||
super(ManagePluginAction.ID, '', ManagePluginAction.CLASS, true);
|
||||
this.tooltip = localize('manage', "Manage");
|
||||
}
|
||||
|
||||
createActionViewItem(options: IActionViewItemOptions): DropDownActionViewItem {
|
||||
this._actionViewItem = this.instantiationService.createInstance(DropDownActionViewItem, this, options);
|
||||
return this._actionViewItem;
|
||||
}
|
||||
|
||||
override async run(): Promise<void> {
|
||||
this._actionViewItem?.showMenu(this.getActionGroups());
|
||||
}
|
||||
}
|
||||
|
||||
class DropDownActionViewItem extends ActionViewItem {
|
||||
constructor(
|
||||
action: IAction,
|
||||
options: IActionViewItemOptions,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService,
|
||||
) {
|
||||
super(null, action, { ...options, icon: true, label: false });
|
||||
}
|
||||
|
||||
showMenu(actionGroups: IAction[][]): void {
|
||||
if (!this.element) {
|
||||
return;
|
||||
}
|
||||
const actions = actionGroups.flatMap(group => [...group, new Separator()]);
|
||||
if (actions.length > 0) {
|
||||
actions.pop();
|
||||
}
|
||||
const { left, top, height } = dom.getDomNodePagePosition(this.element);
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => ({ x: left, y: top + height + 10 }),
|
||||
getActions: () => actions,
|
||||
onHide: () => disposeIfDisposable(actions),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Renderer
|
||||
@@ -213,7 +281,15 @@ class AgentPluginRenderer implements IPagedRenderer<IAgentPluginItem, IAgentPlug
|
||||
const footer = dom.append(details, dom.$('.footer'));
|
||||
const detailContainer = dom.append(footer, dom.$('.publisher-container'));
|
||||
const detail = dom.append(detailContainer, dom.$('span.publisher-name'));
|
||||
const actionbar = new ActionBar(footer, { focusOnlyEnabledItems: true });
|
||||
const actionbar = new ActionBar(footer, {
|
||||
focusOnlyEnabledItems: true,
|
||||
actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => {
|
||||
if (action instanceof ManagePluginAction) {
|
||||
return action.createActionViewItem(options);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
actionbar.setFocusable(false);
|
||||
return { root, name, description, detail, actionbar, disposables: [actionbar], elementDisposables: [] };
|
||||
}
|
||||
@@ -244,6 +320,10 @@ class AgentPluginRenderer implements IPagedRenderer<IAgentPluginItem, IAgentPlug
|
||||
data.actionbar.push([installAction], { icon: true, label: true });
|
||||
} else {
|
||||
data.detail.textContent = element.marketplace ?? '';
|
||||
const manageAction = this.instantiationService.createInstance(ManagePluginAction,
|
||||
() => getInstalledPluginContextMenuActionGroups(element.plugin, this.instantiationService));
|
||||
data.elementDisposables.push(manageAction);
|
||||
data.actionbar.push([manageAction], { icon: true, label: false });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,20 +463,15 @@ export class AgentPluginsListView extends AbstractExtensionsListView<IAgentPlugi
|
||||
}
|
||||
|
||||
private getContextMenuActions(item: IAgentPluginItem): IAction[] {
|
||||
const actions: IAction[] = [];
|
||||
let actions: IAction[];
|
||||
if (item.kind === AgentPluginItemKind.Installed) {
|
||||
if (item.plugin.enabled.get()) {
|
||||
actions.push(this.instantiationService.createInstance(DisablePluginAction, item.plugin));
|
||||
} else {
|
||||
actions.push(this.instantiationService.createInstance(EnablePluginAction, item.plugin));
|
||||
const groups = getInstalledPluginContextMenuActionGroups(item.plugin, this.instantiationService);
|
||||
actions = groups.flatMap(group => [...group, new Separator()]);
|
||||
if (actions.length > 0) {
|
||||
actions.pop();
|
||||
}
|
||||
|
||||
actions.push(new Separator());
|
||||
actions.push(this.instantiationService.createInstance(OpenPluginFolderAction, item.plugin));
|
||||
actions.push(this.instantiationService.createInstance(OpenPluginReadmeAction, joinPath(item.plugin.uri, 'README.md')));
|
||||
actions.push(new Separator());
|
||||
actions.push(this.instantiationService.createInstance(UninstallPluginAction, item.plugin));
|
||||
} else {
|
||||
actions = [];
|
||||
if (item.readmeUri) {
|
||||
actions.push(this.instantiationService.createInstance(OpenPluginReadmeAction, item.readmeUri));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user