prompt_command.rs

  1use super::{SlashCommand, SlashCommandCleanup, SlashCommandInvocation};
  2use crate::prompts::prompt_library::PromptLibrary;
  3use anyhow::{anyhow, Context, Result};
  4use futures::channel::oneshot;
  5use fuzzy::StringMatchCandidate;
  6use gpui::{AppContext, Task};
  7use language::LspAdapterDelegate;
  8use std::sync::{atomic::AtomicBool, Arc};
  9
 10pub(crate) struct PromptSlashCommand {
 11    library: Arc<PromptLibrary>,
 12}
 13
 14impl PromptSlashCommand {
 15    pub fn new(library: Arc<PromptLibrary>) -> Self {
 16        Self { library }
 17    }
 18}
 19
 20impl SlashCommand for PromptSlashCommand {
 21    fn name(&self) -> String {
 22        "prompt".into()
 23    }
 24
 25    fn description(&self) -> String {
 26        "insert a prompt from the library".into()
 27    }
 28
 29    fn requires_argument(&self) -> bool {
 30        true
 31    }
 32
 33    fn complete_argument(
 34        &self,
 35        query: String,
 36        cancellation_flag: Arc<AtomicBool>,
 37        cx: &mut AppContext,
 38    ) -> Task<Result<Vec<String>>> {
 39        let library = self.library.clone();
 40        let executor = cx.background_executor().clone();
 41        cx.background_executor().spawn(async move {
 42            let candidates = library
 43                .prompts()
 44                .into_iter()
 45                .enumerate()
 46                .filter_map(|(ix, prompt)| {
 47                    prompt
 48                        .1
 49                        .title()
 50                        .map(|title| StringMatchCandidate::new(ix, title.into()))
 51                })
 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        _delegate: Arc<dyn LspAdapterDelegate>,
 73        cx: &mut AppContext,
 74    ) -> SlashCommandInvocation {
 75        let Some(title) = title else {
 76            return SlashCommandInvocation {
 77                output: Task::ready(Err(anyhow!("missing prompt name"))),
 78                invalidated: oneshot::channel().1,
 79                cleanup: SlashCommandCleanup::default(),
 80            };
 81        };
 82
 83        let library = self.library.clone();
 84        let title = title.to_string();
 85        let output = cx.background_executor().spawn(async move {
 86            let prompt = library
 87                .prompts()
 88                .into_iter()
 89                .filter_map(|prompt| prompt.1.title().map(|title| (title, prompt)))
 90                .find(|(t, _)| t == &title)
 91                .with_context(|| format!("no prompt found with title {:?}", title))?
 92                .1;
 93            Ok(prompt.1.content().to_owned())
 94        });
 95        SlashCommandInvocation {
 96            output,
 97            invalidated: oneshot::channel().1,
 98            cleanup: SlashCommandCleanup::default(),
 99        }
100    }
101}