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        query: 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        cx.background_executor().spawn(async move {
 39            let prompts = store.await?.search(query).await;
 40            Ok(prompts
 41                .into_iter()
 42                .filter_map(|prompt| {
 43                    let prompt_title = prompt.title?.to_string();
 44                    Some(ArgumentCompletion {
 45                        label: prompt_title.clone().into(),
 46                        new_text: prompt_title,
 47                        run_command: true,
 48                    })
 49                })
 50                .collect())
 51        })
 52    }
 53
 54    fn run(
 55        self: Arc<Self>,
 56        title: Option<&str>,
 57        _workspace: WeakView<Workspace>,
 58        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
 59        cx: &mut WindowContext,
 60    ) -> Task<Result<SlashCommandOutput>> {
 61        let Some(title) = title else {
 62            return Task::ready(Err(anyhow!("missing prompt name")));
 63        };
 64
 65        let store = PromptStore::global(cx);
 66        let title = SharedString::from(title.to_string());
 67        let prompt = cx.background_executor().spawn({
 68            let title = title.clone();
 69            async move {
 70                let store = store.await?;
 71                let prompt_id = store
 72                    .id_for_title(&title)
 73                    .with_context(|| format!("no prompt found with title {:?}", title))?;
 74                let body = store.load(prompt_id).await?;
 75                anyhow::Ok(body)
 76            }
 77        });
 78        cx.foreground_executor().spawn(async move {
 79            let mut prompt = prompt.await?;
 80
 81            if prompt.starts_with('/') {
 82                // Prevent an edge case where the inserted prompt starts with a slash command (that leads to funky rendering).
 83                prompt.insert(0, '\n');
 84            }
 85            if prompt.is_empty() {
 86                prompt.push('\n');
 87            }
 88            let range = 0..prompt.len();
 89            Ok(SlashCommandOutput {
 90                text: prompt,
 91                sections: vec![SlashCommandOutputSection {
 92                    range,
 93                    icon: IconName::Library,
 94                    label: title,
 95                }],
 96                run_commands_in_text: true,
 97            })
 98        })
 99    }
100}