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}