prompt_command.rs

 1use super::{SlashCommand, SlashCommandCleanup, SlashCommandInvocation};
 2use crate::PromptLibrary;
 3use anyhow::{anyhow, Context, Result};
 4use futures::channel::oneshot;
 5use fuzzy::StringMatchCandidate;
 6use gpui::{AppContext, Task};
 7use std::sync::{atomic::AtomicBool, Arc};
 8
 9pub(crate) struct PromptSlashCommand {
10    library: Arc<PromptLibrary>,
11}
12
13impl PromptSlashCommand {
14    pub fn new(library: Arc<PromptLibrary>) -> Self {
15        Self { library }
16    }
17}
18
19impl SlashCommand for PromptSlashCommand {
20    fn name(&self) -> String {
21        "prompt".into()
22    }
23
24    fn description(&self) -> String {
25        "insert a prompt from the library".into()
26    }
27
28    fn requires_argument(&self) -> bool {
29        true
30    }
31
32    fn complete_argument(
33        &self,
34        query: String,
35        cancellation_flag: Arc<AtomicBool>,
36        cx: &mut AppContext,
37    ) -> Task<Result<Vec<String>>> {
38        let library = self.library.clone();
39        let executor = cx.background_executor().clone();
40        cx.background_executor().spawn(async move {
41            let candidates = library
42                .prompts()
43                .into_iter()
44                .enumerate()
45                .map(|(ix, prompt)| StringMatchCandidate::new(ix, prompt.title))
46                .collect::<Vec<_>>();
47            let matches = fuzzy::match_strings(
48                &candidates,
49                &query,
50                false,
51                100,
52                &cancellation_flag,
53                executor,
54            )
55            .await;
56            Ok(matches
57                .into_iter()
58                .map(|mat| candidates[mat.candidate_id].string.clone())
59                .collect())
60        })
61    }
62
63    fn run(&self, title: Option<&str>, cx: &mut AppContext) -> SlashCommandInvocation {
64        let Some(title) = title else {
65            return SlashCommandInvocation {
66                output: Task::ready(Err(anyhow!("missing prompt name"))),
67                invalidated: oneshot::channel().1,
68                cleanup: SlashCommandCleanup::default(),
69            };
70        };
71
72        let library = self.library.clone();
73        let title = title.to_string();
74        let output = cx.background_executor().spawn(async move {
75            let prompt = library
76                .prompts()
77                .into_iter()
78                .find(|prompt| prompt.title == title)
79                .with_context(|| format!("no prompt found with title {:?}", title))?;
80            Ok(prompt.prompt)
81        });
82        SlashCommandInvocation {
83            output,
84            invalidated: oneshot::channel().1,
85            cleanup: SlashCommandCleanup::default(),
86        }
87    }
88}