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 .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}