Path: blob/main/src/vs/workbench/contrib/chat/browser/agentPluginActions.ts
13401 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Action, IAction, IActionChangeEvent } from '../../../../base/common/actions.js';6import { Emitter } from '../../../../base/common/event.js';7import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';8import { ActionWithDropdownActionViewItem, IActionWithDropdownActionViewItemOptions } from '../../../../base/browser/ui/dropdown/dropdownActionViewItem.js';9import { IContextMenuProvider } from '../../../../base/browser/contextmenu.js';10import { localize } from '../../../../nls.js';11import { ICommandService } from '../../../../platform/commands/common/commands.js';12import { IOpenerService } from '../../../../platform/opener/common/opener.js';13import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';14import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';15import { dirname, joinPath } from '../../../../base/common/resources.js';16import { ContributionEnablementState, IEnablementModel, isContributionDisabled, isContributionEnabled } from '../common/enablement.js';17import { IAgentPlugin, IAgentPluginService } from '../common/plugins/agentPluginService.js';18import { IPluginInstallService } from '../common/plugins/pluginInstallService.js';19import { IMarketplacePluginItem } from './agentPluginEditor/agentPluginItems.js';20import { buildEnablementContextMenuGroup } from './enablementActions.js';21import { hasKey } from '../../../../base/common/types.js';2223//#region Simple actions2425export class InstallPluginAction extends Action {26constructor(27item: IMarketplacePluginItem,28@IPluginInstallService pluginInstallService: IPluginInstallService,29) {30super('agentPlugin.install', localize('install', "Install"), 'extension-action label prominent install', true,31() => pluginInstallService.installPlugin({32name: item.name,33description: item.description,34version: '',35source: item.source,36sourceDescriptor: item.sourceDescriptor,37marketplace: item.marketplace,38marketplaceReference: item.marketplaceReference,39marketplaceType: item.marketplaceType,40readmeUri: item.readmeUri,41}));42}43}4445export class UninstallPluginAction extends Action {46constructor(plugin: IAgentPlugin) {47super('agentPlugin.uninstall', localize('uninstall', "Uninstall"), 'extension-action label uninstall', true,48() => { plugin.remove(); return Promise.resolve(); });49}50}5152export class OpenPluginFolderAction extends Action {53constructor(54plugin: IAgentPlugin,55@ICommandService commandService: ICommandService,56@IOpenerService openerService: IOpenerService,57) {58super('agentPlugin.openFolder', localize('openPluginFolder', "Open Plugin Folder"), undefined, true,59async () => {60try {61await commandService.executeCommand('revealFileInOS', plugin.uri);62} catch {63await openerService.open(dirname(plugin.uri));64}65});66}67}6869export class OpenPluginReadmeAction extends Action {70constructor(71readmeUri: import('../../../../base/common/uri.js').URI,72@IOpenerService openerService: IOpenerService,73) {74super('agentPlugin.openReadme', localize('openReadme', "Open README"), undefined, true,75() => openerService.open(readmeUri));76}77}7879//#endregion8081//#region Context menu8283/**84* Builds the standard context menu action groups for an installed plugin.85*/86export function getInstalledPluginContextMenuActions(plugin: IAgentPlugin, instantiationService: IInstantiationService): IAction[][] {87return instantiationService.invokeFunction(accessor => {88const agentPluginService = accessor.get(IAgentPluginService);89const workspaceService = accessor.get(IWorkspaceContextService);90const groups: IAction[][] = [];91groups.push(buildEnablementContextMenuGroup(92plugin.enablement.get(),93plugin.uri.toString(),94agentPluginService.enablementModel,95workspaceService,96'agentPlugin',97));98groups.push([99instantiationService.createInstance(OpenPluginFolderAction, plugin),100instantiationService.createInstance(OpenPluginReadmeAction, joinPath(plugin.uri, 'README.md')),101]);102if (plugin.fromMarketplace) {103groups.push([new UninstallPluginAction(plugin)]);104}105return groups;106});107}108109//#endregion110111//#region Dropdown enablement actions for editor-style action bars112113/**114* Sub-action base class that auto-hides when disabled, for use inside115* {@link EnablementDropDownAction}.116*/117class EnablementSubAction extends Action {118private _hidden: boolean;119get hidden(): boolean { return this._hidden; }120set hidden(v: boolean) { this._hidden = v; }121122constructor(id: string, label: string, cssClass: string, enabled: boolean, actionCallback: () => Promise<void>) {123super(id, label, cssClass, enabled, actionCallback);124this._hidden = !enabled;125}126127protected override _setEnabled(value: boolean): void {128super._setEnabled(value);129this.hidden = !value;130}131}132133interface IEnablementActionChangeEvent extends IActionChangeEvent {134readonly menuActions?: IAction[];135}136137/**138* Dropdown action that aggregates enablement sub-actions and shows the139* first visible one as the primary button, with others in the dropdown.140* Hides itself entirely when all sub-actions are hidden.141*/142export class EnablementDropDownAction extends Action {143readonly menuActionClassNames = ['extension-action', 'label', 'action-dropdown'];144private _menuActions: IAction[] = [];145get menuActions(): IAction[] { return [...this._menuActions]; }146147private _isHidden = false;148get isHidden(): boolean { return this._isHidden; }149150protected override readonly _onDidChange = new Emitter<IEnablementActionChangeEvent>();151override get onDidChange() { return this._onDidChange.event; }152153private readonly subActions: EnablementSubAction[];154155constructor(id: string, subActions: EnablementSubAction[]) {156super(id, undefined, 'extension-action label action-dropdown');157this.subActions = subActions;158for (const a of subActions) {159a.onDidChange(() => this._updateDropdown());160}161this._updateDropdown();162}163164private _updateDropdown(): void {165const visible = this.subActions.filter(a => !a.hidden);166const primary = visible[0];167this._menuActions = visible.length > 1 ? [...visible] : [];168169if (primary) {170this._isHidden = false;171this.enabled = true;172this.label = primary.label;173this.tooltip = primary.tooltip;174} else {175this._isHidden = true;176this.enabled = false;177}178this._onDidChange.fire({ menuActions: this._menuActions });179}180181override async run(): Promise<void> {182const primary = this.subActions.find(a => !a.hidden);183await primary?.run();184}185186override dispose(): void {187for (const a of this.subActions) {188a.dispose();189}190super.dispose();191}192}193194/**195* View item for {@link EnablementDropDownAction} that properly hides196* the dropdown chevron when there are no secondary actions.197*/198export class EnablementDropdownActionViewItem extends ActionWithDropdownActionViewItem {199constructor(200action: EnablementDropDownAction,201options: IActionViewItemOptions & IActionWithDropdownActionViewItemOptions,202contextMenuProvider: IContextMenuProvider,203) {204super(null, action, options, contextMenuProvider);205this._register(action.onDidChange(e => {206if (hasKey(e, { menuActions: true })) {207this.updateClass();208}209}));210}211212override render(container: HTMLElement): void {213super.render(container);214this.updateClass();215}216217protected override updateClass(): void {218super.updateClass();219if (this.element && this.dropdownMenuActionViewItem?.element) {220const action = this._action as EnablementDropDownAction;221this.element.classList.toggle('hide', action.isHidden);222const isMenuEmpty = action.menuActions.length === 0;223this.element.classList.toggle('empty', isMenuEmpty);224this.dropdownMenuActionViewItem.element.classList.toggle('hide', isMenuEmpty);225}226}227}228229/**230* Creates the enable dropdown action for a plugin, containing Enable231* and Enable (Workspace) sub-actions.232*/233export function createEnablePluginDropDown(234plugin: IAgentPlugin,235enablementModel: IEnablementModel,236workspaceContextService: IWorkspaceContextService,237): EnablementDropDownAction {238const key = plugin.uri.toString();239const hasWorkspace = workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY;240241const enable = new EnablementSubAction('agentPlugin.enable', localize('enable', "Enable"), 'extension-action label prominent',242isContributionDisabled(plugin.enablement.get()),243() => { enablementModel.setEnabled(key, ContributionEnablementState.EnabledProfile); return Promise.resolve(); });244245const enableWorkspace = new EnablementSubAction('agentPlugin.enableForWorkspace', localize('enableForWorkspace', "Enable (Workspace)"), 'extension-action label',246isContributionDisabled(plugin.enablement.get()) && hasWorkspace,247() => { enablementModel.setEnabled(key, ContributionEnablementState.EnabledWorkspace); return Promise.resolve(); });248249return new EnablementDropDownAction('agentPlugin.enableDropdown', [enable, enableWorkspace]);250}251252/**253* Creates the disable dropdown action for a plugin, containing Disable254* and Disable (Workspace) sub-actions.255*/256export function createDisablePluginDropDown(257plugin: IAgentPlugin,258enablementModel: IEnablementModel,259workspaceContextService: IWorkspaceContextService,260): EnablementDropDownAction {261const key = plugin.uri.toString();262const hasWorkspace = workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY;263264const disable = new EnablementSubAction('agentPlugin.disable', localize('disable', "Disable"), 'extension-action label disable',265isContributionEnabled(plugin.enablement.get()),266() => { enablementModel.setEnabled(key, ContributionEnablementState.DisabledProfile); return Promise.resolve(); });267268const disableWorkspace = new EnablementSubAction('agentPlugin.disableForWorkspace', localize('disableForWorkspace', "Disable (Workspace)"), 'extension-action label disable',269isContributionEnabled(plugin.enablement.get()) && hasWorkspace,270() => { enablementModel.setEnabled(key, ContributionEnablementState.DisabledWorkspace); return Promise.resolve(); });271272return new EnablementDropDownAction('agentPlugin.disableDropdown', [disable, disableWorkspace]);273}274275//#endregion276277278