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 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                .filter_map(|(ix, prompt)| {
46                    prompt
47                        .1
48                        .title()
49                        .map(|title| StringMatchCandidate::new(ix, title.into()))
50                })
51                .collect::<Vec<_>>();
52            let matches = fuzzy::match_strings(
53                &candidates,
54                &query,
55                false,
56                100,
57                &cancellation_flag,
58                executor,
59            )
60            .await;
61            Ok(matches
62                .into_iter()
63                .map(|mat| candidates[mat.candidate_id].string.clone())
64                .collect())
65        })
66    }
67
68    fn run(&self, title: Option<&str>, cx: &mut AppContext) -> SlashCommandInvocation {
69        let Some(title) = title else {
70            return SlashCommandInvocation {
71                output: Task::ready(Err(anyhow!("missing prompt name"))),
72                invalidated: oneshot::channel().1,
73                cleanup: SlashCommandCleanup::default(),
74            };
75        };
76
77        let library = self.library.clone();
78        let title = title.to_string();
79        let output = cx.background_executor().spawn(async move {
80            let prompt = library
81                .prompts()
82                .into_iter()
83                .filter_map(|prompt| prompt.1.title().map(|title| (title, prompt)))
84                .find(|(t, _)| t == &title)
85                .with_context(|| format!("no prompt found with title {:?}", title))?
86                .1;
87            Ok(prompt.1.content().to_owned())
88        });
89        SlashCommandInvocation {
90            output,
91            invalidated: oneshot::channel().1,
92            cleanup: SlashCommandCleanup::default(),
93        }
94    }
95}