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