Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/browser/agentPluginActions.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Action, IAction, IActionChangeEvent } from '../../../../base/common/actions.js';
7
import { Emitter } from '../../../../base/common/event.js';
8
import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';
9
import { ActionWithDropdownActionViewItem, IActionWithDropdownActionViewItemOptions } from '../../../../base/browser/ui/dropdown/dropdownActionViewItem.js';
10
import { IContextMenuProvider } from '../../../../base/browser/contextmenu.js';
11
import { localize } from '../../../../nls.js';
12
import { ICommandService } from '../../../../platform/commands/common/commands.js';
13
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
14
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
15
import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';
16
import { dirname, joinPath } from '../../../../base/common/resources.js';
17
import { ContributionEnablementState, IEnablementModel, isContributionDisabled, isContributionEnabled } from '../common/enablement.js';
18
import { IAgentPlugin, IAgentPluginService } from '../common/plugins/agentPluginService.js';
19
import { IPluginInstallService } from '../common/plugins/pluginInstallService.js';
20
import { IMarketplacePluginItem } from './agentPluginEditor/agentPluginItems.js';
21
import { buildEnablementContextMenuGroup } from './enablementActions.js';
22
import { hasKey } from '../../../../base/common/types.js';
23
24
//#region Simple actions
25
26
export class InstallPluginAction extends Action {
27
constructor(
28
item: IMarketplacePluginItem,
29
@IPluginInstallService pluginInstallService: IPluginInstallService,
30
) {
31
super('agentPlugin.install', localize('install', "Install"), 'extension-action label prominent install', true,
32
() => pluginInstallService.installPlugin({
33
name: item.name,
34
description: item.description,
35
version: '',
36
source: item.source,
37
sourceDescriptor: item.sourceDescriptor,
38
marketplace: item.marketplace,
39
marketplaceReference: item.marketplaceReference,
40
marketplaceType: item.marketplaceType,
41
readmeUri: item.readmeUri,
42
}));
43
}
44
}
45
46
export class UninstallPluginAction extends Action {
47
constructor(plugin: IAgentPlugin) {
48
super('agentPlugin.uninstall', localize('uninstall', "Uninstall"), 'extension-action label uninstall', true,
49
() => { plugin.remove(); return Promise.resolve(); });
50
}
51
}
52
53
export class OpenPluginFolderAction extends Action {
54
constructor(
55
plugin: IAgentPlugin,
56
@ICommandService commandService: ICommandService,
57
@IOpenerService openerService: IOpenerService,
58
) {
59
super('agentPlugin.openFolder', localize('openPluginFolder', "Open Plugin Folder"), undefined, true,
60
async () => {
61
try {
62
await commandService.executeCommand('revealFileInOS', plugin.uri);
63
} catch {
64
await openerService.open(dirname(plugin.uri));
65
}
66
});
67
}
68
}
69
70
export class OpenPluginReadmeAction extends Action {
71
constructor(
72
readmeUri: import('../../../../base/common/uri.js').URI,
73
@IOpenerService openerService: IOpenerService,
74
) {
75
super('agentPlugin.openReadme', localize('openReadme', "Open README"), undefined, true,
76
() => openerService.open(readmeUri));
77
}
78
}
79
80
//#endregion
81
82
//#region Context menu
83
84
/**
85
* Builds the standard context menu action groups for an installed plugin.
86
*/
87
export function getInstalledPluginContextMenuActions(plugin: IAgentPlugin, instantiationService: IInstantiationService): IAction[][] {
88
return instantiationService.invokeFunction(accessor => {
89
const agentPluginService = accessor.get(IAgentPluginService);
90
const workspaceService = accessor.get(IWorkspaceContextService);
91
const groups: IAction[][] = [];
92
groups.push(buildEnablementContextMenuGroup(
93
plugin.enablement.get(),
94
plugin.uri.toString(),
95
agentPluginService.enablementModel,
96
workspaceService,
97
'agentPlugin',
98
));
99
groups.push([
100
instantiationService.createInstance(OpenPluginFolderAction, plugin),
101
instantiationService.createInstance(OpenPluginReadmeAction, joinPath(plugin.uri, 'README.md')),
102
]);
103
if (plugin.fromMarketplace) {
104
groups.push([new UninstallPluginAction(plugin)]);
105
}
106
return groups;
107
});
108
}
109
110
//#endregion
111
112
//#region Dropdown enablement actions for editor-style action bars
113
114
/**
115
* Sub-action base class that auto-hides when disabled, for use inside
116
* {@link EnablementDropDownAction}.
117
*/
118
class EnablementSubAction extends Action {
119
private _hidden: boolean;
120
get hidden(): boolean { return this._hidden; }
121
set hidden(v: boolean) { this._hidden = v; }
122
123
constructor(id: string, label: string, cssClass: string, enabled: boolean, actionCallback: () => Promise<void>) {
124
super(id, label, cssClass, enabled, actionCallback);
125
this._hidden = !enabled;
126
}
127
128
protected override _setEnabled(value: boolean): void {
129
super._setEnabled(value);
130
this.hidden = !value;
131
}
132
}
133
134
interface IEnablementActionChangeEvent extends IActionChangeEvent {
135
readonly menuActions?: IAction[];
136
}
137
138
/**
139
* Dropdown action that aggregates enablement sub-actions and shows the
140
* first visible one as the primary button, with others in the dropdown.
141
* Hides itself entirely when all sub-actions are hidden.
142
*/
143
export class EnablementDropDownAction extends Action {
144
readonly menuActionClassNames = ['extension-action', 'label', 'action-dropdown'];
145
private _menuActions: IAction[] = [];
146
get menuActions(): IAction[] { return [...this._menuActions]; }
147
148
private _isHidden = false;
149
get isHidden(): boolean { return this._isHidden; }
150
151
protected override readonly _onDidChange = new Emitter<IEnablementActionChangeEvent>();
152
override get onDidChange() { return this._onDidChange.event; }
153
154
private readonly subActions: EnablementSubAction[];
155
156
constructor(id: string, subActions: EnablementSubAction[]) {
157
super(id, undefined, 'extension-action label action-dropdown');
158
this.subActions = subActions;
159
for (const a of subActions) {
160
a.onDidChange(() => this._updateDropdown());
161
}
162
this._updateDropdown();
163
}
164
165
private _updateDropdown(): void {
166
const visible = this.subActions.filter(a => !a.hidden);
167
const primary = visible[0];
168
this._menuActions = visible.length > 1 ? [...visible] : [];
169
170
if (primary) {
171
this._isHidden = false;
172
this.enabled = true;
173
this.label = primary.label;
174
this.tooltip = primary.tooltip;
175
} else {
176
this._isHidden = true;
177
this.enabled = false;
178
}
179
this._onDidChange.fire({ menuActions: this._menuActions });
180
}
181
182
override async run(): Promise<void> {
183
const primary = this.subActions.find(a => !a.hidden);
184
await primary?.run();
185
}
186
187
override dispose(): void {
188
for (const a of this.subActions) {
189
a.dispose();
190
}
191
super.dispose();
192
}
193
}
194
195
/**
196
* View item for {@link EnablementDropDownAction} that properly hides
197
* the dropdown chevron when there are no secondary actions.
198
*/
199
export class EnablementDropdownActionViewItem extends ActionWithDropdownActionViewItem {
200
constructor(
201
action: EnablementDropDownAction,
202
options: IActionViewItemOptions & IActionWithDropdownActionViewItemOptions,
203
contextMenuProvider: IContextMenuProvider,
204
) {
205
super(null, action, options, contextMenuProvider);
206
this._register(action.onDidChange(e => {
207
if (hasKey(e, { menuActions: true })) {
208
this.updateClass();
209
}
210
}));
211
}
212
213
override render(container: HTMLElement): void {
214
super.render(container);
215
this.updateClass();
216
}
217
218
protected override updateClass(): void {
219
super.updateClass();
220
if (this.element && this.dropdownMenuActionViewItem?.element) {
221
const action = this._action as EnablementDropDownAction;
222
this.element.classList.toggle('hide', action.isHidden);
223
const isMenuEmpty = action.menuActions.length === 0;
224
this.element.classList.toggle('empty', isMenuEmpty);
225
this.dropdownMenuActionViewItem.element.classList.toggle('hide', isMenuEmpty);
226
}
227
}
228
}
229
230
/**
231
* Creates the enable dropdown action for a plugin, containing Enable
232
* and Enable (Workspace) sub-actions.
233
*/
234
export function createEnablePluginDropDown(
235
plugin: IAgentPlugin,
236
enablementModel: IEnablementModel,
237
workspaceContextService: IWorkspaceContextService,
238
): EnablementDropDownAction {
239
const key = plugin.uri.toString();
240
const hasWorkspace = workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY;
241
242
const enable = new EnablementSubAction('agentPlugin.enable', localize('enable', "Enable"), 'extension-action label prominent',
243
isContributionDisabled(plugin.enablement.get()),
244
() => { enablementModel.setEnabled(key, ContributionEnablementState.EnabledProfile); return Promise.resolve(); });
245
246
const enableWorkspace = new EnablementSubAction('agentPlugin.enableForWorkspace', localize('enableForWorkspace', "Enable (Workspace)"), 'extension-action label',
247
isContributionDisabled(plugin.enablement.get()) && hasWorkspace,
248
() => { enablementModel.setEnabled(key, ContributionEnablementState.EnabledWorkspace); return Promise.resolve(); });
249
250
return new EnablementDropDownAction('agentPlugin.enableDropdown', [enable, enableWorkspace]);
251
}
252
253
/**
254
* Creates the disable dropdown action for a plugin, containing Disable
255
* and Disable (Workspace) sub-actions.
256
*/
257
export function createDisablePluginDropDown(
258
plugin: IAgentPlugin,
259
enablementModel: IEnablementModel,
260
workspaceContextService: IWorkspaceContextService,
261
): EnablementDropDownAction {
262
const key = plugin.uri.toString();
263
const hasWorkspace = workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY;
264
265
const disable = new EnablementSubAction('agentPlugin.disable', localize('disable', "Disable"), 'extension-action label disable',
266
isContributionEnabled(plugin.enablement.get()),
267
() => { enablementModel.setEnabled(key, ContributionEnablementState.DisabledProfile); return Promise.resolve(); });
268
269
const disableWorkspace = new EnablementSubAction('agentPlugin.disableForWorkspace', localize('disableForWorkspace', "Disable (Workspace)"), 'extension-action label disable',
270
isContributionEnabled(plugin.enablement.get()) && hasWorkspace,
271
() => { enablementModel.setEnabled(key, ContributionEnablementState.DisabledWorkspace); return Promise.resolve(); });
272
273
return new EnablementDropDownAction('agentPlugin.disableDropdown', [disable, disableWorkspace]);
274
}
275
276
//#endregion
277
278