prompt_command.rs

  1use super::{SlashCommand, SlashCommandOutput};
  2use crate::prompt_library::PromptStore;
  3use anyhow::{anyhow, Context, Result};
  4use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
  5use gpui::{Task, WeakView};
  6use language::LspAdapterDelegate;
  7use std::sync::{atomic::AtomicBool, Arc};
  8use ui::prelude::*;
  9use workspace::Workspace;
 10
 11pub(crate) struct PromptSlashCommand;
 12
 13impl SlashCommand for PromptSlashCommand {
 14    fn name(&self) -> String {
 15        "prompt".into()
 16    }
 17
 18    fn description(&self) -> String {
 19        "insert prompt from library".into()
 20    }
 21
 22    fn menu_text(&self) -> String {
 23        "Insert Prompt from Library".into()
 24    }
 25
 26    fn requires_argument(&self) -> bool {
 27        true
 28    }
 29
 30    fn complete_argument(
 31        self: Arc<Self>,
 32        arguments: &[String],
 33        _cancellation_flag: Arc<AtomicBool>,
 34        _workspace: Option<WeakView<Workspace>>,
 35        cx: &mut WindowContext,
 36    ) -> Task<Result<Vec<ArgumentCompletion>>> {
 37        let store = PromptStore::global(cx);
 38        let query = arguments.last().cloned().unwrap_or_default();
 39        cx.background_executor().spawn(async move {
 40            let prompts = store.await?.search(query).await;
 41            Ok(prompts
 42                .into_iter()
 43                .filter_map(|prompt| {
 44                    let prompt_title = prompt.title?.to_string();
 45                    Some(ArgumentCompletion {
 46                        label: prompt_title.clone().into(),
 47                        new_text: prompt_title,
 48                        run_command: true,
 49                    })
 50                })
 51                .collect())
 52        })
 53    }
 54
 55    fn run(
 56        self: Arc<Self>,
 57        arguments: &[String],
 58        _workspace: WeakView<Workspace>,
 59        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
 60        cx: &mut WindowContext,
 61    ) -> Task<Result<SlashCommandOutput>> {
 62        let Some(title) = arguments.first() else {
 63            return Task::ready(Err(anyhow!("missing prompt name")));
 64        };
 65
 66        let store = PromptStore::global(cx);
 67        let title = SharedString::from(title.to_string());
 68        let prompt = cx.background_executor().spawn({
 69            let title = title.clone();
 70            async move {
 71                let store = store.await?;
 72                let prompt_id = store
 73                    .id_for_title(&title)
 74                    .with_context(|| format!("no prompt found with title {:?}", title))?;
 75                let body = store.load(prompt_id).await?;
 76                anyhow::Ok(body)
 77            }
 78        });
 79        cx.foreground_executor().spawn(async move {
 80            let mut prompt = prompt.await?;
 81
 82            if prompt.starts_with('/') {
 83                // Prevent an edge case where the inserted prompt starts with a slash command (that leads to funky rendering).
 84                prompt.insert(0, '\n');
 85            }
 86            if prompt.is_empty() {
 87                prompt.push('\n');
 88            }
 89            let range = 0..prompt.len();
 90            Ok(SlashCommandOutput {
 91                text: prompt,
 92                sections: vec![SlashCommandOutputSection {
 93                    range,
 94                    icon: IconName::Library,
 95                    label: title,
 96                }],
 97                run_commands_in_text: true,
 98            })
 99        })
100    }
101}