prompt_command.rs

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