1use super::{SlashCommand, SlashCommandOutput};
2use crate::prompt_library::PromptStore;
3use anyhow::{anyhow, Context, Result};
4use assistant_slash_command::SlashCommandOutputSection;
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 store: Arc<PromptStore>,
13}
14
15impl PromptSlashCommand {
16 pub fn new(store: Arc<PromptStore>) -> Self {
17 Self { store }
18 }
19}
20
21impl SlashCommand for PromptSlashCommand {
22 fn name(&self) -> String {
23 "prompt".into()
24 }
25
26 fn description(&self) -> String {
27 "insert prompt from library".into()
28 }
29
30 fn menu_text(&self) -> String {
31 "Insert Prompt from Library".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 _workspace: WeakView<Workspace>,
43 cx: &mut AppContext,
44 ) -> Task<Result<Vec<String>>> {
45 let store = self.store.clone();
46 cx.background_executor().spawn(async move {
47 let prompts = store.search(query).await;
48 Ok(prompts
49 .into_iter()
50 .filter_map(|prompt| Some(prompt.title?.to_string()))
51 .collect())
52 })
53 }
54
55 fn run(
56 self: Arc<Self>,
57 title: Option<&str>,
58 _workspace: WeakView<Workspace>,
59 _delegate: Arc<dyn LspAdapterDelegate>,
60 cx: &mut WindowContext,
61 ) -> Task<Result<SlashCommandOutput>> {
62 let Some(title) = title else {
63 return Task::ready(Err(anyhow!("missing prompt name")));
64 };
65
66 let store = self.store.clone();
67 let title = SharedString::from(title.to_string());
68 let prompt = cx.background_executor().spawn({
69 let title = title.clone();
70 async move {
71 let prompt_id = store
72 .id_for_title(&title)
73 .with_context(|| format!("no prompt found with title {:?}", title))?;
74 let body = store.load(prompt_id).await?;
75 anyhow::Ok(body)
76 }
77 });
78 cx.foreground_executor().spawn(async move {
79 let prompt = prompt.await?;
80 let range = 0..prompt.len();
81 Ok(SlashCommandOutput {
82 text: prompt,
83 sections: vec![SlashCommandOutputSection {
84 range,
85 render_placeholder: Arc::new(move |id, unfold, _cx| {
86 PromptPlaceholder {
87 id,
88 unfold,
89 title: title.clone(),
90 }
91 .into_any_element()
92 }),
93 }],
94 })
95 })
96 }
97}
98
99#[derive(IntoElement)]
100pub struct PromptPlaceholder {
101 pub title: SharedString,
102 pub id: ElementId,
103 pub unfold: Arc<dyn Fn(&mut WindowContext)>,
104}
105
106impl RenderOnce for PromptPlaceholder {
107 fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
108 let unfold = self.unfold;
109 ButtonLike::new(self.id)
110 .style(ButtonStyle::Filled)
111 .layer(ElevationIndex::ElevatedSurface)
112 .child(Icon::new(IconName::Library))
113 .child(Label::new(self.title))
114 .on_click(move |_, cx| unfold(cx))
115 }
116}