prompt_command.rs

  1use super::{SlashCommand, SlashCommandOutput};
  2use crate::prompts::PromptLibrary;
  3use anyhow::{anyhow, Context, Result};
  4use assistant_slash_command::SlashCommandOutputSection;
  5use fuzzy::StringMatchCandidate;
  6use gpui::{AppContext, Task, WeakView};
  7use language::LspAdapterDelegate;
  8use std::sync::{atomic::AtomicBool, Arc};
  9use ui::{prelude::*, ButtonLike, ElevationIndex};
 10use workspace::Workspace;
 11
 12pub(crate) struct PromptSlashCommand {
 13    library: Arc<PromptLibrary>,
 14}
 15
 16impl PromptSlashCommand {
 17    pub fn new(library: Arc<PromptLibrary>) -> Self {
 18        Self { library }
 19    }
 20}
 21
 22impl SlashCommand for PromptSlashCommand {
 23    fn name(&self) -> String {
 24        "prompt".into()
 25    }
 26
 27    fn description(&self) -> String {
 28        "insert a prompt from the library".into()
 29    }
 30
 31    fn tooltip_text(&self) -> String {
 32        "insert prompt".into()
 33    }
 34
 35    fn requires_argument(&self) -> bool {
 36        true
 37    }
 38
 39    fn complete_argument(
 40        &self,
 41        query: String,
 42        cancellation_flag: Arc<AtomicBool>,
 43        cx: &mut AppContext,
 44    ) -> Task<Result<Vec<String>>> {
 45        let library = self.library.clone();
 46        let executor = cx.background_executor().clone();
 47        cx.background_executor().spawn(async move {
 48            let candidates = library
 49                .prompts()
 50                .into_iter()
 51                .enumerate()
 52                .map(|(ix, prompt)| StringMatchCandidate::new(ix, prompt.1.title().to_string()))
 53                .collect::<Vec<_>>();
 54            let matches = fuzzy::match_strings(
 55                &candidates,
 56                &query,
 57                false,
 58                100,
 59                &cancellation_flag,
 60                executor,
 61            )
 62            .await;
 63            Ok(matches
 64                .into_iter()
 65                .map(|mat| candidates[mat.candidate_id].string.clone())
 66                .collect())
 67        })
 68    }
 69
 70    fn run(
 71        self: Arc<Self>,
 72        title: Option<&str>,
 73        _workspace: WeakView<Workspace>,
 74        _delegate: Arc<dyn LspAdapterDelegate>,
 75        cx: &mut WindowContext,
 76    ) -> Task<Result<SlashCommandOutput>> {
 77        let Some(title) = title else {
 78            return Task::ready(Err(anyhow!("missing prompt name")));
 79        };
 80
 81        let library = self.library.clone();
 82        let title = SharedString::from(title.to_string());
 83        let prompt = cx.background_executor().spawn({
 84            let title = title.clone();
 85            async move {
 86                let prompt = library
 87                    .prompts()
 88                    .into_iter()
 89                    .map(|prompt| (prompt.1.title(), prompt))
 90                    .find(|(t, _)| t == &title)
 91                    .with_context(|| format!("no prompt found with title {:?}", title))?
 92                    .1;
 93                anyhow::Ok(prompt.1.body())
 94            }
 95        });
 96        cx.foreground_executor().spawn(async move {
 97            let prompt = prompt.await?;
 98            let range = 0..prompt.len();
 99            Ok(SlashCommandOutput {
100                text: prompt,
101                sections: vec![SlashCommandOutputSection {
102                    range,
103                    render_placeholder: Arc::new(move |id, unfold, _cx| {
104                        ButtonLike::new(id)
105                            .style(ButtonStyle::Filled)
106                            .layer(ElevationIndex::ElevatedSurface)
107                            .child(Icon::new(IconName::Library))
108                            .child(Label::new(title.clone()))
109                            .on_click(move |_, cx| unfold(cx))
110                            .into_any_element()
111                    }),
112                }],
113            })
114        })
115    }
116}