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 a prompt from the library".into()
29 }
30
31 fn tooltip_text(&self) -> String {
32 "insert prompt".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 cx: &mut AppContext,
44 ) -> Task<Result<Vec<String>>> {
45 let library = self.library.clone();
46 let executor = cx.background_executor().clone();
47 cx.background_executor().spawn(async move {
48 let candidates = library
49 .prompts()
50 .into_iter()
51 .enumerate()
52 .map(|(ix, prompt)| StringMatchCandidate::new(ix, prompt.1.title().to_string()))
53 .collect::<Vec<_>>();
54 let matches = fuzzy::match_strings(
55 &candidates,
56 &query,
57 false,
58 100,
59 &cancellation_flag,
60 executor,
61 )
62 .await;
63 Ok(matches
64 .into_iter()
65 .map(|mat| candidates[mat.candidate_id].string.clone())
66 .collect())
67 })
68 }
69
70 fn run(
71 self: Arc<Self>,
72 title: Option<&str>,
73 _workspace: WeakView<Workspace>,
74 _delegate: Arc<dyn LspAdapterDelegate>,
75 cx: &mut WindowContext,
76 ) -> Task<Result<SlashCommandOutput>> {
77 let Some(title) = title else {
78 return Task::ready(Err(anyhow!("missing prompt name")));
79 };
80
81 let library = self.library.clone();
82 let title = SharedString::from(title.to_string());
83 let prompt = cx.background_executor().spawn({
84 let title = title.clone();
85 async move {
86 let prompt = library
87 .prompts()
88 .into_iter()
89 .map(|prompt| (prompt.1.title(), prompt))
90 .find(|(t, _)| t == &title)
91 .with_context(|| format!("no prompt found with title {:?}", title))?
92 .1;
93 anyhow::Ok(prompt.1.body())
94 }
95 });
96 cx.foreground_executor().spawn(async move {
97 let prompt = prompt.await?;
98 let range = 0..prompt.len();
99 Ok(SlashCommandOutput {
100 text: prompt,
101 sections: vec![SlashCommandOutputSection {
102 range,
103 render_placeholder: Arc::new(move |id, unfold, _cx| {
104 ButtonLike::new(id)
105 .style(ButtonStyle::Filled)
106 .layer(ElevationIndex::ElevatedSurface)
107 .child(Icon::new(IconName::Library))
108 .child(Label::new(title.clone()))
109 .on_click(move |_, cx| unfold(cx))
110 .into_any_element()
111 }),
112 }],
113 })
114 })
115 }
116}