Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/sessions/contrib/aiCustomizationTreeView/browser/aiCustomizationTreeView.contribution.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 { localize, localize2 } from '../../../../nls.js';
7
import { Action2, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js';
8
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
9
import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
10
import { AI_CUSTOMIZATION_CATEGORY, AI_CUSTOMIZATION_VIEW_ID, AICustomizationItemMenuId, FOCUS_AI_CUSTOMIZATION_VIEW_ID } from './aiCustomizationTreeView.js';
11
import { AICustomizationItemDisabledContextKey, AICustomizationItemStorageContextKey, AICustomizationItemTypeContextKey, AICustomizationViewPane } from './aiCustomizationTreeViewViews.js';
12
import { PromptsType } from '../../../../workbench/contrib/chat/common/promptSyntax/promptTypes.js';
13
import { Codicon } from '../../../../base/common/codicons.js';
14
import { ICommandService } from '../../../../platform/commands/common/commands.js';
15
import { URI } from '../../../../base/common/uri.js';
16
import { IEditorService } from '../../../../workbench/services/editor/common/editorService.js';
17
import { IFileService, FileSystemProviderCapabilities } from '../../../../platform/files/common/files.js';
18
import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
19
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
20
import { IPromptsService } from '../../../../workbench/contrib/chat/common/promptSyntax/service/promptsService.js';
21
import { IViewsService } from '../../../../workbench/services/views/common/viewsService.js';
22
import { BUILTIN_STORAGE } from '../../chat/common/builtinPromptsStorage.js';
23
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
24
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
25
import { SessionsView, SessionsViewId } from '../../sessions/browser/views/sessionsView.js';
26
import { IsSessionsWindowContext } from '../../../../workbench/common/contextkeys.js';
27
import { TerminalContextKeys } from '../../../../workbench/contrib/terminal/common/terminalContextKey.js';
28
29
//#region Utilities
30
31
/**
32
* Type for context passed to actions from tree context menus.
33
* Handles both direct URI arguments and serialized context objects.
34
*/
35
type ItemContext = { uri: URI | string; promptType?: string; disabled?: boolean;[key: string]: unknown } | URI | string;
36
37
/**
38
* Extracts a URI from various context formats.
39
* Context can be a URI, string, or an object with uri property.
40
*/
41
function extractURI(context: ItemContext): URI {
42
if (URI.isUri(context)) {
43
return context;
44
}
45
if (typeof context === 'string') {
46
return URI.parse(context);
47
}
48
if (URI.isUri(context.uri)) {
49
return context.uri;
50
}
51
return URI.parse(context.uri as string);
52
}
53
54
//#endregion
55
56
//#region Context Menu Actions
57
58
// Open file action
59
const OPEN_AI_CUSTOMIZATION_FILE_ID = 'aiCustomization.openFile';
60
registerAction2(class extends Action2 {
61
constructor() {
62
super({
63
id: OPEN_AI_CUSTOMIZATION_FILE_ID,
64
title: localize2('open', "Open"),
65
icon: Codicon.goToFile,
66
});
67
}
68
async run(accessor: ServicesAccessor, context: ItemContext): Promise<void> {
69
const editorService = accessor.get(IEditorService);
70
await editorService.openEditor({
71
resource: extractURI(context)
72
});
73
}
74
});
75
76
77
// Run prompt action
78
const RUN_PROMPT_FROM_VIEW_ID = 'aiCustomization.runPrompt';
79
registerAction2(class extends Action2 {
80
constructor() {
81
super({
82
id: RUN_PROMPT_FROM_VIEW_ID,
83
title: localize2('runPrompt', "Run Prompt"),
84
icon: Codicon.play,
85
});
86
}
87
async run(accessor: ServicesAccessor, context: ItemContext): Promise<void> {
88
const commandService = accessor.get(ICommandService);
89
await commandService.executeCommand('workbench.action.chat.run.prompt.current', extractURI(context));
90
}
91
});
92
93
// Delete file action
94
const DELETE_AI_CUSTOMIZATION_FILE_ID = 'aiCustomization.deleteFile';
95
registerAction2(class extends Action2 {
96
constructor() {
97
super({
98
id: DELETE_AI_CUSTOMIZATION_FILE_ID,
99
title: localize2('delete', "Delete"),
100
icon: Codicon.trash,
101
});
102
}
103
async run(accessor: ServicesAccessor, context: ItemContext): Promise<void> {
104
const fileService = accessor.get(IFileService);
105
const dialogService = accessor.get(IDialogService);
106
const uri = extractURI(context);
107
const name = typeof context === 'object' && !URI.isUri(context) ? (context as { name?: string }).name ?? '' : '';
108
109
if (uri.scheme !== 'file') {
110
return;
111
}
112
113
const confirmation = await dialogService.confirm({
114
message: localize('confirmDelete', "Are you sure you want to delete '{0}'?", name || uri.path),
115
primaryButton: localize('delete', "Delete"),
116
});
117
118
if (confirmation.confirmed) {
119
const useTrash = fileService.hasCapability(uri, FileSystemProviderCapabilities.Trash);
120
await fileService.del(uri, { useTrash, recursive: true });
121
}
122
}
123
});
124
125
// Copy path action
126
const COPY_AI_CUSTOMIZATION_PATH_ID = 'aiCustomization.copyPath';
127
registerAction2(class extends Action2 {
128
constructor() {
129
super({
130
id: COPY_AI_CUSTOMIZATION_PATH_ID,
131
title: localize2('copyPath', "Copy Path"),
132
icon: Codicon.clippy,
133
});
134
}
135
async run(accessor: ServicesAccessor, context: ItemContext): Promise<void> {
136
const clipboardService = accessor.get(IClipboardService);
137
const uri = extractURI(context);
138
const textToCopy = uri.scheme === 'file' ? uri.fsPath : uri.toString(true);
139
await clipboardService.writeText(textToCopy);
140
}
141
});
142
143
// Register context menu items
144
145
// Inline hover actions (shown as icon buttons on hover)
146
MenuRegistry.appendMenuItem(AICustomizationItemMenuId, {
147
command: { id: DELETE_AI_CUSTOMIZATION_FILE_ID, title: localize('delete', "Delete"), icon: Codicon.trash },
148
group: 'inline',
149
order: 10,
150
});
151
152
// Context menu items (shown on right-click)
153
MenuRegistry.appendMenuItem(AICustomizationItemMenuId, {
154
command: { id: OPEN_AI_CUSTOMIZATION_FILE_ID, title: localize('open', "Open") },
155
group: '1_open',
156
order: 1,
157
});
158
159
MenuRegistry.appendMenuItem(AICustomizationItemMenuId, {
160
command: { id: RUN_PROMPT_FROM_VIEW_ID, title: localize('runPrompt', "Run Prompt"), icon: Codicon.play },
161
group: '2_run',
162
order: 1,
163
when: ContextKeyExpr.equals(AICustomizationItemTypeContextKey.key, PromptsType.prompt),
164
});
165
166
MenuRegistry.appendMenuItem(AICustomizationItemMenuId, {
167
command: { id: COPY_AI_CUSTOMIZATION_PATH_ID, title: localize('copyPath', "Copy Path") },
168
group: '3_modify',
169
order: 1,
170
});
171
172
MenuRegistry.appendMenuItem(AICustomizationItemMenuId, {
173
command: { id: DELETE_AI_CUSTOMIZATION_FILE_ID, title: localize('delete', "Delete") },
174
group: '3_modify',
175
order: 10,
176
});
177
178
// Disable item action
179
const DISABLE_AI_CUSTOMIZATION_ITEM_ID = 'aiCustomization.disableItem';
180
registerAction2(class extends Action2 {
181
constructor() {
182
super({
183
id: DISABLE_AI_CUSTOMIZATION_ITEM_ID,
184
title: localize2('disable', "Disable"),
185
icon: Codicon.eyeClosed,
186
});
187
}
188
async run(accessor: ServicesAccessor, context: ItemContext): Promise<void> {
189
if (typeof context !== 'object' || URI.isUri(context)) {
190
return;
191
}
192
const promptsService = accessor.get(IPromptsService);
193
const viewsService = accessor.get(IViewsService);
194
const uri = extractURI(context);
195
const promptType = context.promptType as PromptsType | undefined;
196
if (!promptType) {
197
return;
198
}
199
200
const disabled = promptsService.getDisabledPromptFiles(promptType);
201
disabled.add(uri);
202
promptsService.setDisabledPromptFiles(promptType, disabled);
203
204
const view = viewsService.getActiveViewWithId<AICustomizationViewPane>(AI_CUSTOMIZATION_VIEW_ID);
205
view?.refresh();
206
}
207
});
208
209
// Enable item action
210
const ENABLE_AI_CUSTOMIZATION_ITEM_ID = 'aiCustomization.enableItem';
211
registerAction2(class extends Action2 {
212
constructor() {
213
super({
214
id: ENABLE_AI_CUSTOMIZATION_ITEM_ID,
215
title: localize2('enable', "Enable"),
216
icon: Codicon.eye,
217
});
218
}
219
async run(accessor: ServicesAccessor, context: ItemContext): Promise<void> {
220
if (typeof context !== 'object' || URI.isUri(context)) {
221
return;
222
}
223
const promptsService = accessor.get(IPromptsService);
224
const viewsService = accessor.get(IViewsService);
225
const uri = extractURI(context);
226
const promptType = context.promptType as PromptsType | undefined;
227
if (!promptType) {
228
return;
229
}
230
231
const disabled = promptsService.getDisabledPromptFiles(promptType);
232
disabled.delete(uri);
233
promptsService.setDisabledPromptFiles(promptType, disabled);
234
235
const view = viewsService.getActiveViewWithId<AICustomizationViewPane>(AI_CUSTOMIZATION_VIEW_ID);
236
view?.refresh();
237
}
238
});
239
240
// Context menu: Disable (shown when builtin item is enabled)
241
MenuRegistry.appendMenuItem(AICustomizationItemMenuId, {
242
command: { id: DISABLE_AI_CUSTOMIZATION_ITEM_ID, title: localize('disable', "Disable") },
243
group: '4_toggle',
244
order: 1,
245
when: ContextKeyExpr.and(
246
ContextKeyExpr.equals(AICustomizationItemDisabledContextKey.key, false),
247
ContextKeyExpr.equals(AICustomizationItemStorageContextKey.key, BUILTIN_STORAGE),
248
ContextKeyExpr.equals(AICustomizationItemTypeContextKey.key, PromptsType.skill),
249
),
250
});
251
252
// Context menu: Enable (shown when builtin item is disabled)
253
MenuRegistry.appendMenuItem(AICustomizationItemMenuId, {
254
command: { id: ENABLE_AI_CUSTOMIZATION_ITEM_ID, title: localize('enable', "Enable") },
255
group: '4_toggle',
256
order: 1,
257
when: ContextKeyExpr.and(
258
ContextKeyExpr.equals(AICustomizationItemDisabledContextKey.key, true),
259
ContextKeyExpr.equals(AICustomizationItemStorageContextKey.key, BUILTIN_STORAGE),
260
ContextKeyExpr.equals(AICustomizationItemTypeContextKey.key, PromptsType.skill),
261
),
262
});
263
264
// Inline hover: Disable (shown when builtin item is enabled)
265
MenuRegistry.appendMenuItem(AICustomizationItemMenuId, {
266
command: { id: DISABLE_AI_CUSTOMIZATION_ITEM_ID, title: localize('disable', "Disable"), icon: Codicon.eyeClosed },
267
group: 'inline',
268
order: 5,
269
when: ContextKeyExpr.and(
270
ContextKeyExpr.equals(AICustomizationItemDisabledContextKey.key, false),
271
ContextKeyExpr.equals(AICustomizationItemStorageContextKey.key, BUILTIN_STORAGE),
272
ContextKeyExpr.equals(AICustomizationItemTypeContextKey.key, PromptsType.skill),
273
),
274
});
275
276
// Inline hover: Enable (shown when builtin item is disabled)
277
MenuRegistry.appendMenuItem(AICustomizationItemMenuId, {
278
command: { id: ENABLE_AI_CUSTOMIZATION_ITEM_ID, title: localize('enable', "Enable"), icon: Codicon.eye },
279
group: 'inline',
280
order: 5,
281
when: ContextKeyExpr.and(
282
ContextKeyExpr.equals(AICustomizationItemDisabledContextKey.key, true),
283
ContextKeyExpr.equals(AICustomizationItemStorageContextKey.key, BUILTIN_STORAGE),
284
ContextKeyExpr.equals(AICustomizationItemTypeContextKey.key, PromptsType.skill),
285
),
286
});
287
288
//#endregion
289
290
//#region Focus Action
291
292
registerAction2(class extends Action2 {
293
constructor() {
294
super({
295
id: FOCUS_AI_CUSTOMIZATION_VIEW_ID,
296
title: localize2('focusCustomizations', "Focus Chat Customizations"),
297
category: AI_CUSTOMIZATION_CATEGORY,
298
precondition: IsSessionsWindowContext,
299
f1: true,
300
keybinding: {
301
weight: KeybindingWeight.WorkbenchContrib,
302
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyC,
303
when: ContextKeyExpr.and(IsSessionsWindowContext, TerminalContextKeys.focus.negate()),
304
},
305
});
306
}
307
async run(accessor: ServicesAccessor): Promise<void> {
308
const viewsService = accessor.get(IViewsService);
309
const sessionsView = await viewsService.openView<SessionsView>(SessionsViewId, false);
310
sessionsView?.focusCustomizations();
311
}
312
});
313
314
//#endregion
315
316