prompt_command.rs

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