Path: blob/main/src/vs/workbench/contrib/chat/browser/promptSyntax/runPromptAction.ts
3296 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 { ChatViewId, IChatWidget, showChatView } from '../chat.js';6import { ACTION_ID_NEW_CHAT, CHAT_CATEGORY, CHAT_CONFIG_MENU_ID } from '../actions/chatActions.js';7import { URI } from '../../../../../base/common/uri.js';8import { OS } from '../../../../../base/common/platform.js';9import { Codicon } from '../../../../../base/common/codicons.js';10import { ChatContextKeys } from '../../common/chatContextKeys.js';11import { assertDefined } from '../../../../../base/common/types.js';12import { ThemeIcon } from '../../../../../base/common/themables.js';13import { ResourceContextKey } from '../../../../common/contextkeys.js';14import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js';15import { PromptsType, PROMPT_LANGUAGE_ID } from '../../common/promptSyntax/promptTypes.js';16import { ILocalizedString, localize, localize2 } from '../../../../../nls.js';17import { UILabelProvider } from '../../../../../base/common/keybindingLabels.js';18import { ICommandAction } from '../../../../../platform/action/common/action.js';19import { PromptsConfig } from '../../common/promptSyntax/config/config.js';20import { IViewsService } from '../../../../services/views/common/viewsService.js';21import { PromptFilePickers } from './pickers/promptFilePickers.js';22import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';23import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js';24import { ICommandService } from '../../../../../platform/commands/common/commands.js';25import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';26import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js';27import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';28import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js';29import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';30import { IOpenerService } from '../../../../../platform/opener/common/opener.js';31import { getPromptCommandName } from '../../common/promptSyntax/service/promptsServiceImpl.js';3233/**34* Condition for the `Run Current Prompt` action.35*/36const EDITOR_ACTIONS_CONDITION = ContextKeyExpr.and(37ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled),38ResourceContextKey.HasResource,39ResourceContextKey.LangId.isEqualTo(PROMPT_LANGUAGE_ID),40);4142/**43* Keybinding of the action.44*/45const COMMAND_KEY_BINDING = KeyMod.WinCtrl | KeyCode.Slash | KeyMod.Alt;4647/**48* Action ID for the `Run Current Prompt` action.49*/50const RUN_CURRENT_PROMPT_ACTION_ID = 'workbench.action.chat.run.prompt.current';5152/**53* Action ID for the `Run Prompt...` action.54*/55const RUN_SELECTED_PROMPT_ACTION_ID = 'workbench.action.chat.run.prompt';5657/**58* Action ID for the `Configure Prompt Files...` action.59*/60const CONFIGURE_PROMPTS_ACTION_ID = 'workbench.action.chat.configure.prompts';6162/**63* Constructor options for the `Run Prompt` base action.64*/65interface IRunPromptBaseActionConstructorOptions {66/**67* ID of the action to be registered.68*/69id: string;7071/**72* Title of the action.73*/74title: ILocalizedString;7576/**77* Icon of the action.78*/79icon: ThemeIcon;8081/**82* Keybinding of the action.83*/84keybinding: number;8586/**87* Alt action of the UI menu item.88*/89alt?: ICommandAction;90}9192/**93* Base class of the `Run Prompt` action.94*/95abstract class RunPromptBaseAction extends Action2 {96constructor(97options: IRunPromptBaseActionConstructorOptions,98) {99super({100id: options.id,101title: options.title,102f1: false,103precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled),104category: CHAT_CATEGORY,105icon: options.icon,106keybinding: {107when: ContextKeyExpr.and(108EditorContextKeys.editorTextFocus,109EDITOR_ACTIONS_CONDITION,110),111weight: KeybindingWeight.WorkbenchContrib,112primary: options.keybinding,113},114menu: [115{116id: MenuId.EditorTitleRun,117group: 'navigation',118order: options.alt ? 0 : 1,119alt: options.alt,120when: EDITOR_ACTIONS_CONDITION,121},122],123});124}125126/**127* Executes the run prompt action with provided options.128*/129public async execute(130resource: URI | undefined,131inNewChat: boolean,132accessor: ServicesAccessor,133): Promise<IChatWidget | undefined> {134const viewsService = accessor.get(IViewsService);135const commandService = accessor.get(ICommandService);136137resource ||= getActivePromptFileUri(accessor);138assertDefined(139resource,140'Cannot find URI resource for an active text editor.',141);142143if (inNewChat === true) {144await commandService.executeCommand(ACTION_ID_NEW_CHAT);145}146147const widget = await showChatView(viewsService);148if (widget) {149widget.setInput(`/${getPromptCommandName(resource.path)}`);150// submit the prompt immediately151await widget.acceptInput();152}153return widget;154}155}156157const RUN_CURRENT_PROMPT_ACTION_TITLE = localize2(158'run-prompt.capitalized',159"Run Prompt in Current Chat"160);161const RUN_CURRENT_PROMPT_ACTION_ICON = Codicon.playCircle;162163/**164* The default `Run Current Prompt` action.165*/166class RunCurrentPromptAction extends RunPromptBaseAction {167constructor() {168super({169id: RUN_CURRENT_PROMPT_ACTION_ID,170title: RUN_CURRENT_PROMPT_ACTION_TITLE,171icon: RUN_CURRENT_PROMPT_ACTION_ICON,172keybinding: COMMAND_KEY_BINDING,173});174}175176public override async run(177accessor: ServicesAccessor,178resource: URI | undefined,179): Promise<IChatWidget | undefined> {180return await super.execute(181resource,182false,183accessor,184);185}186}187188class RunSelectedPromptAction extends Action2 {189constructor() {190super({191id: RUN_SELECTED_PROMPT_ACTION_ID,192title: localize2('run-prompt.capitalized.ellipses', "Run Prompt..."),193icon: Codicon.bookmark,194f1: true,195precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled),196keybinding: {197when: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled),198weight: KeybindingWeight.WorkbenchContrib,199primary: COMMAND_KEY_BINDING,200},201category: CHAT_CATEGORY,202});203}204205public override async run(206accessor: ServicesAccessor,207): Promise<void> {208const viewsService = accessor.get(IViewsService);209const commandService = accessor.get(ICommandService);210const instaService = accessor.get(IInstantiationService);211212const pickers = instaService.createInstance(PromptFilePickers);213214const placeholder = localize(215'commands.prompt.select-dialog.placeholder',216'Select the prompt file to run (hold {0}-key to use in new chat)',217UILabelProvider.modifierLabels[OS].ctrlKey218);219220const result = await pickers.selectPromptFile({ placeholder, type: PromptsType.prompt });221222if (result === undefined) {223return;224}225226const { promptFile, keyMods } = result;227228if (keyMods.ctrlCmd === true) {229await commandService.executeCommand(ACTION_ID_NEW_CHAT);230}231232const widget = await showChatView(viewsService);233if (widget) {234widget.setInput(`/${getPromptCommandName(promptFile.path)}`);235// submit the prompt immediately236await widget.acceptInput();237widget.focusInput();238}239}240}241242class ManagePromptFilesAction extends Action2 {243constructor() {244super({245id: CONFIGURE_PROMPTS_ACTION_ID,246title: localize2('configure-prompts', "Configure Prompt Files..."),247shortTitle: localize2('configure-prompts.short', "Prompt Files"),248icon: Codicon.bookmark,249f1: true,250precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled),251category: CHAT_CATEGORY,252menu: {253id: CHAT_CONFIG_MENU_ID,254when: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled, ContextKeyExpr.equals('view', ChatViewId)),255order: 10,256group: '0_level'257},258});259}260261public override async run(262accessor: ServicesAccessor,263): Promise<void> {264const openerService = accessor.get(IOpenerService);265const instaService = accessor.get(IInstantiationService);266267const pickers = instaService.createInstance(PromptFilePickers);268269const placeholder = localize(270'commands.prompt.manage-dialog.placeholder',271'Select the prompt file to open'272);273274const result = await pickers.selectPromptFile({ placeholder, type: PromptsType.prompt, optionEdit: false });275if (result !== undefined) {276await openerService.open(result.promptFile);277}278}279}280281282/**283* Gets `URI` of a prompt file open in an active editor instance, if any.284*/285function getActivePromptFileUri(accessor: ServicesAccessor): URI | undefined {286const codeEditorService = accessor.get(ICodeEditorService);287const model = codeEditorService.getActiveCodeEditor()?.getModel();288if (model?.getLanguageId() === PROMPT_LANGUAGE_ID) {289return model.uri;290}291return undefined;292}293294295/**296* Action ID for the `Run Current Prompt In New Chat` action.297*/298const RUN_CURRENT_PROMPT_IN_NEW_CHAT_ACTION_ID = 'workbench.action.chat.run-in-new-chat.prompt.current';299300const RUN_IN_NEW_CHAT_ACTION_TITLE = localize2(301'run-prompt-in-new-chat.capitalized',302"Run Prompt In New Chat",303);304305/**306* Icon for the `Run Current Prompt In New Chat` action.307*/308const RUN_IN_NEW_CHAT_ACTION_ICON = Codicon.play;309310/**311* `Run Current Prompt In New Chat` action.312*/313class RunCurrentPromptInNewChatAction extends RunPromptBaseAction {314constructor() {315super({316id: RUN_CURRENT_PROMPT_IN_NEW_CHAT_ACTION_ID,317title: RUN_IN_NEW_CHAT_ACTION_TITLE,318icon: RUN_IN_NEW_CHAT_ACTION_ICON,319keybinding: COMMAND_KEY_BINDING | KeyMod.CtrlCmd,320alt: {321id: RUN_CURRENT_PROMPT_ACTION_ID,322title: RUN_CURRENT_PROMPT_ACTION_TITLE,323icon: RUN_CURRENT_PROMPT_ACTION_ICON,324},325});326}327328public override async run(329accessor: ServicesAccessor,330resource: URI,331): Promise<IChatWidget | undefined> {332return await super.execute(333resource,334true,335accessor,336);337}338}339340/**341* Helper to register all the `Run Current Prompt` actions.342*/343export function registerRunPromptActions(): void {344registerAction2(RunCurrentPromptInNewChatAction);345registerAction2(RunCurrentPromptAction);346registerAction2(RunSelectedPromptAction);347registerAction2(ManagePromptFilesAction);348}349350351