1use super::{SlashCommand, SlashCommandOutput};
2use crate::prompts::PromptLibrary;
3use anyhow::{anyhow, Context, Result};
4use assistant_slash_command::SlashCommandOutputSection;
5use fuzzy::StringMatchCandidate;
6use gpui::{AppContext, Task, WeakView};
7use language::LspAdapterDelegate;
8use std::sync::{atomic::AtomicBool, Arc};
9use ui::{prelude::*, ButtonLike, ElevationIndex};
10use workspace::Workspace;
11
12pub(crate) struct PromptSlashCommand {
13 library: Arc<PromptLibrary>,
14}
15
16impl PromptSlashCommand {
17 pub fn new(library: Arc<PromptLibrary>) -> Self {
18 Self { library }
19 }
20}
21
22impl SlashCommand for PromptSlashCommand {
23 fn name(&self) -> String {
24 "prompt".into()
25 }
26
27 fn description(&self) -> String {
28 "insert prompt from library".into()
29 }
30
31 fn menu_text(&self) -> String {
32 "Insert Prompt from Library".into()
33 }
34
35 fn requires_argument(&self) -> bool {
36 true
37 }
38
39 fn complete_argument(
40 &self,
41 query: String,
42 cancellation_flag: Arc<AtomicBool>,
43 _workspace: WeakView<Workspace>,
44 cx: &mut AppContext,
45 ) -> Task<Result<Vec<String>>> {
46 let library = self.library.clone();
47 let executor = cx.background_executor().clone();
48 cx.background_executor().spawn(async move {
49 let candidates = library
50 .prompts()
51 .into_iter()
52 .enumerate()
53 .map(|(ix, prompt)| StringMatchCandidate::new(ix, prompt.1.title().to_string()))
54 .collect::<Vec<_>>();
55 let matches = fuzzy::match_strings(
56 &candidates,
57 &query,
58 false,
59 100,
60 &cancellation_flag,
61 executor,
62 )
63 .await;
64 Ok(matches
65 .into_iter()
66 .map(|mat| candidates[mat.candidate_id].string.clone())
67 .collect())
68 })
69 }
70
71 fn run(
72 self: Arc<Self>,
73 title: Option<&str>,
74 _workspace: WeakView<Workspace>,
75 _delegate: Arc<dyn LspAdapterDelegate>,
76 cx: &mut WindowContext,
77 ) -> Task<Result<SlashCommandOutput>> {
78 let Some(title) = title else {
79 return Task::ready(Err(anyhow!("missing prompt name")));
80 };
81
82 let library = self.library.clone();
83 let title = SharedString::from(title.to_string());
84 let prompt = cx.background_executor().spawn({
85 let title = title.clone();
86 async move {
87 let prompt = library
88 .prompts()
89 .into_iter()
90 .map(|prompt| (prompt.1.title(), prompt))
91 .find(|(t, _)| t == &title)
92 .with_context(|| format!("no prompt found with title {:?}", title))?
93 .1;
94 anyhow::Ok(prompt.1.body())
95 }
96 });
97 cx.foreground_executor().spawn(async move {
98 let prompt = prompt.await?;
99 let range = 0..prompt.len();
100 Ok(SlashCommandOutput {
101 text: prompt,
102 sections: vec![SlashCommandOutputSection {
103 range,
104 render_placeholder: Arc::new(move |id, unfold, _cx| {
105 ButtonLike::new(id)
106 .style(ButtonStyle::Filled)
107 .layer(ElevationIndex::ElevatedSurface)
108 .child(Icon::new(IconName::Library))
109 .child(Label::new(title.clone()))
110 .on_click(move |_, cx| unfold(cx))
111 .into_any_element()
112 }),
113 }],
114 })
115 })
116 }
117}