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 prompt from library".into()
 29    }
 30
 31    fn menu_text(&self) -> String {
 32        "Insert Prompt from Library".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        _workspace: WeakView<Workspace>,
 44        cx: &mut AppContext,
 45    ) -> Task<Result<Vec<String>>> {
 46        let library = self.library.clone();
 47        let executor = cx.background_executor().clone();
 48        cx.background_executor().spawn(async move {
 49            let candidates = library
 50                .prompts()
 51                .into_iter()
 52                .enumerate()
 53                .map(|(ix, prompt)| StringMatchCandidate::new(ix, prompt.1.title().to_string()))
 54                .collect::<Vec<_>>();
 55            let matches = fuzzy::match_strings(
 56                &candidates,
 57                &query,
 58                false,
 59                100,
 60                &cancellation_flag,
 61                executor,
 62            )
 63            .await;
 64            Ok(matches
 65                .into_iter()
 66                .map(|mat| candidates[mat.candidate_id].string.clone())
 67                .collect())
 68        })
 69    }
 70
 71    fn run(
 72        self: Arc<Self>,
 73        title: Option<&str>,
 74        _workspace: WeakView<Workspace>,
 75        _delegate: Arc<dyn LspAdapterDelegate>,
 76        cx: &mut WindowContext,
 77    ) -> Task<Result<SlashCommandOutput>> {
 78        let Some(title) = title else {
 79            return Task::ready(Err(anyhow!("missing prompt name")));
 80        };
 81
 82        let library = self.library.clone();
 83        let title = SharedString::from(title.to_string());
 84        let prompt = cx.background_executor().spawn({
 85            let title = title.clone();
 86            async move {
 87                let prompt = library
 88                    .prompts()
 89                    .into_iter()
 90                    .map(|prompt| (prompt.1.title(), prompt))
 91                    .find(|(t, _)| t == &title)
 92                    .with_context(|| format!("no prompt found with title {:?}", title))?
 93                    .1;
 94                anyhow::Ok(prompt.1.body())
 95            }
 96        });
 97        cx.foreground_executor().spawn(async move {
 98            let prompt = prompt.await?;
 99            let range = 0..prompt.len();
100            Ok(SlashCommandOutput {
101                text: prompt,
102                sections: vec![SlashCommandOutputSection {
103                    range,
104                    render_placeholder: Arc::new(move |id, unfold, _cx| {
105                        ButtonLike::new(id)
106                            .style(ButtonStyle::Filled)
107                            .layer(ElevationIndex::ElevatedSurface)
108                            .child(Icon::new(IconName::Library))
109                            .child(Label::new(title.clone()))
110                            .on_click(move |_, cx| unfold(cx))
111                            .into_any_element()
112                    }),
113                }],
114            })
115        })
116    }
117}