1use super::{SlashCommand, SlashCommandOutput};
2use crate::prompts::PromptLibrary;
3use anyhow::{anyhow, Context, Result};
4use fuzzy::StringMatchCandidate;
5use gpui::{AppContext, Task, WeakView};
6use language::LspAdapterDelegate;
7use std::sync::{atomic::AtomicBool, Arc};
8use ui::{prelude::*, ButtonLike, ElevationIndex};
9use workspace::Workspace;
10
11pub(crate) struct PromptSlashCommand {
12 library: Arc<PromptLibrary>,
13}
14
15impl PromptSlashCommand {
16 pub fn new(library: Arc<PromptLibrary>) -> Self {
17 Self { library }
18 }
19}
20
21impl SlashCommand for PromptSlashCommand {
22 fn name(&self) -> String {
23 "prompt".into()
24 }
25
26 fn description(&self) -> String {
27 "insert a prompt from the library".into()
28 }
29
30 fn tooltip_text(&self) -> String {
31 "insert prompt".into()
32 }
33
34 fn requires_argument(&self) -> bool {
35 true
36 }
37
38 fn complete_argument(
39 &self,
40 query: String,
41 cancellation_flag: Arc<AtomicBool>,
42 cx: &mut AppContext,
43 ) -> Task<Result<Vec<String>>> {
44 let library = self.library.clone();
45 let executor = cx.background_executor().clone();
46 cx.background_executor().spawn(async move {
47 let candidates = library
48 .prompts()
49 .into_iter()
50 .enumerate()
51 .map(|(ix, prompt)| StringMatchCandidate::new(ix, prompt.1.title().to_string()))
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 _workspace: WeakView<Workspace>,
73 _delegate: Arc<dyn LspAdapterDelegate>,
74 cx: &mut WindowContext,
75 ) -> Task<Result<SlashCommandOutput>> {
76 let Some(title) = title else {
77 return Task::ready(Err(anyhow!("missing prompt name")));
78 };
79
80 let library = self.library.clone();
81 let title = SharedString::from(title.to_string());
82 let prompt = cx.background_executor().spawn({
83 let title = title.clone();
84 async move {
85 let prompt = library
86 .prompts()
87 .into_iter()
88 .map(|prompt| (prompt.1.title(), prompt))
89 .find(|(t, _)| t == &title)
90 .with_context(|| format!("no prompt found with title {:?}", title))?
91 .1;
92 anyhow::Ok(prompt.1.body())
93 }
94 });
95 cx.foreground_executor().spawn(async move {
96 let prompt = prompt.await?;
97 Ok(SlashCommandOutput {
98 text: prompt,
99 render_placeholder: Arc::new(move |id, unfold, _cx| {
100 ButtonLike::new(id)
101 .style(ButtonStyle::Filled)
102 .layer(ElevationIndex::ElevatedSurface)
103 .child(Icon::new(IconName::Library))
104 .child(Label::new(title.clone()))
105 .on_click(move |_, cx| unfold(cx))
106 .into_any_element()
107 }),
108 })
109 })
110 }
111}