Path: blob/main/src/vs/workbench/contrib/chat/browser/promptSyntax/runPromptAction.ts
5260 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, IChatWidgetService } 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/actions/chatContextKeys.js';11import { assertDefined } from '../../../../../base/common/types.js';12import { ThemeIcon } from '../../../../../base/common/themables.js';13import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js';14import { PromptsType, PROMPT_LANGUAGE_ID } from '../../common/promptSyntax/promptTypes.js';15import { ILocalizedString, localize, localize2 } from '../../../../../nls.js';16import { UILabelProvider } from '../../../../../base/common/keybindingLabels.js';17import { ICommandAction } from '../../../../../platform/action/common/action.js';18import { PromptFilePickers } from './pickers/promptFilePickers.js';19import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';20import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js';21import { ICommandService } from '../../../../../platform/commands/common/commands.js';22import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js';23import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';24import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';25import { ResourceContextKey } from '../../../../common/contextkeys.js';26import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js';27import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';28import { IOpenerService } from '../../../../../platform/opener/common/opener.js';29import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js';30import { CancellationToken } from '../../../../../base/common/cancellation.js';3132/**33* Condition for the `Run Current Prompt` action.34*/35const EDITOR_ACTIONS_CONDITION = ContextKeyExpr.and(36ChatContextKeys.enabled,37ResourceContextKey.HasResource,38ResourceContextKey.LangId.isEqualTo(PROMPT_LANGUAGE_ID),39);4041/**42* Keybinding of the action.43*/44const COMMAND_KEY_BINDING = KeyMod.WinCtrl | KeyCode.Slash | KeyMod.Alt;4546/**47* Action ID for the `Run Current Prompt` action.48*/49const RUN_CURRENT_PROMPT_ACTION_ID = 'workbench.action.chat.run.prompt.current';5051/**52* Action ID for the `Run Prompt...` action.53*/54const RUN_SELECTED_PROMPT_ACTION_ID = 'workbench.action.chat.run.prompt';5556/**57* Action ID for the `Configure Prompt Files...` action.58*/59const CONFIGURE_PROMPTS_ACTION_ID = 'workbench.action.chat.configure.prompts';6061/**62* Constructor options for the `Run Prompt` base action.63*/64interface IRunPromptBaseActionConstructorOptions {65/**66* ID of the action to be registered.67*/68id: string;6970/**71* Title of the action.72*/73title: ILocalizedString;7475/**76* Icon of the action.77*/78icon: ThemeIcon;7980/**81* Keybinding of the action.82*/83keybinding: number;8485/**86* Alt action of the UI menu item.87*/88alt?: ICommandAction;89}9091/**92* Base class of the `Run Prompt` action.93*/94abstract class RunPromptBaseAction extends Action2 {95constructor(96options: IRunPromptBaseActionConstructorOptions,97) {98super({99id: options.id,100title: options.title,101f1: false,102precondition: ChatContextKeys.enabled,103category: CHAT_CATEGORY,104icon: options.icon,105keybinding: {106when: ContextKeyExpr.and(107EditorContextKeys.editorTextFocus,108EDITOR_ACTIONS_CONDITION,109),110weight: KeybindingWeight.WorkbenchContrib,111primary: options.keybinding,112},113menu: [114{115id: MenuId.EditorTitleRun,116group: 'navigation',117order: options.alt ? 0 : 1,118alt: options.alt,119when: EDITOR_ACTIONS_CONDITION,120},121],122});123}124125/**126* Executes the run prompt action with provided options.127*/128public async execute(129resource: URI | undefined,130inNewChat: boolean,131accessor: ServicesAccessor,132): Promise<IChatWidget | undefined> {133const commandService = accessor.get(ICommandService);134const promptsService = accessor.get(IPromptsService);135const widgetService = accessor.get(IChatWidgetService);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 widgetService.revealWidget();148if (widget) {149widget.setInput(`/${await promptsService.getPromptSlashCommandName(resource, CancellationToken.None)}`);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: ChatContextKeys.enabled,196keybinding: {197when: 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 commandService = accessor.get(ICommandService);209const instaService = accessor.get(IInstantiationService);210const promptsService = accessor.get(IPromptsService);211const widgetService = accessor.get(IChatWidgetService);212213const pickers = instaService.createInstance(PromptFilePickers);214215const placeholder = localize(216'commands.prompt.select-dialog.placeholder',217'Select the prompt file to run (hold {0}-key to use in new chat)',218UILabelProvider.modifierLabels[OS].ctrlKey219);220221const result = await pickers.selectPromptFile({ placeholder, type: PromptsType.prompt });222223if (result === undefined) {224return;225}226227const { promptFile, keyMods } = result;228229if (keyMods.ctrlCmd === true) {230await commandService.executeCommand(ACTION_ID_NEW_CHAT);231}232233const widget = await widgetService.revealWidget();234if (widget) {235widget.setInput(`/${await promptsService.getPromptSlashCommandName(promptFile, CancellationToken.None)}`);236// submit the prompt immediately237await widget.acceptInput();238widget.focusInput();239}240}241}242243class ManagePromptFilesAction extends Action2 {244constructor() {245super({246id: CONFIGURE_PROMPTS_ACTION_ID,247title: localize2('configure-prompts', "Configure Prompt Files..."),248shortTitle: localize2('configure-prompts.short', "Prompt Files"),249icon: Codicon.bookmark,250f1: true,251precondition: ChatContextKeys.enabled,252category: CHAT_CATEGORY,253menu: {254id: CHAT_CONFIG_MENU_ID,255when: ContextKeyExpr.and(ChatContextKeys.enabled, ContextKeyExpr.equals('view', ChatViewId)),256order: 11,257group: '0_level'258},259});260}261262public override async run(263accessor: ServicesAccessor,264): Promise<void> {265const openerService = accessor.get(IOpenerService);266const instaService = accessor.get(IInstantiationService);267268const pickers = instaService.createInstance(PromptFilePickers);269270const placeholder = localize(271'commands.prompt.manage-dialog.placeholder',272'Select the prompt file to open'273);274275const result = await pickers.selectPromptFile({ placeholder, type: PromptsType.prompt, optionEdit: false });276if (result !== undefined) {277await openerService.open(result.promptFile);278}279}280}281282283/**284* Gets `URI` of a prompt file open in an active editor instance, if any.285*/286function getActivePromptFileUri(accessor: ServicesAccessor): URI | undefined {287const codeEditorService = accessor.get(ICodeEditorService);288const model = codeEditorService.getActiveCodeEditor()?.getModel();289if (model?.getLanguageId() === PROMPT_LANGUAGE_ID) {290return model.uri;291}292return undefined;293}294295296/**297* Action ID for the `Run Current Prompt In New Chat` action.298*/299const RUN_CURRENT_PROMPT_IN_NEW_CHAT_ACTION_ID = 'workbench.action.chat.run-in-new-chat.prompt.current';300301const RUN_IN_NEW_CHAT_ACTION_TITLE = localize2(302'run-prompt-in-new-chat.capitalized',303"Run Prompt In New Chat",304);305306/**307* Icon for the `Run Current Prompt In New Chat` action.308*/309const RUN_IN_NEW_CHAT_ACTION_ICON = Codicon.play;310311/**312* `Run Current Prompt In New Chat` action.313*/314class RunCurrentPromptInNewChatAction extends RunPromptBaseAction {315constructor() {316super({317id: RUN_CURRENT_PROMPT_IN_NEW_CHAT_ACTION_ID,318title: RUN_IN_NEW_CHAT_ACTION_TITLE,319icon: RUN_IN_NEW_CHAT_ACTION_ICON,320keybinding: COMMAND_KEY_BINDING | KeyMod.CtrlCmd,321alt: {322id: RUN_CURRENT_PROMPT_ACTION_ID,323title: RUN_CURRENT_PROMPT_ACTION_TITLE,324icon: RUN_CURRENT_PROMPT_ACTION_ICON,325},326});327}328329public override async run(330accessor: ServicesAccessor,331resource: URI,332): Promise<IChatWidget | undefined> {333return await super.execute(334resource,335true,336accessor,337);338}339}340341/**342* Helper to register all the `Run Current Prompt` actions.343*/344export function registerRunPromptActions(): void {345registerAction2(RunCurrentPromptInNewChatAction);346registerAction2(RunCurrentPromptAction);347registerAction2(RunSelectedPromptAction);348registerAction2(ManagePromptFilesAction);349}350351352