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