1use super::{SlashCommand, SlashCommandOutput};
2use crate::prompt_library::PromptStore;
3use anyhow::{anyhow, Context, Result};
4use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
5use gpui::{Task, WeakView};
6use language::{BufferSnapshot, LspAdapterDelegate};
7use std::sync::{atomic::AtomicBool, Arc};
8use ui::prelude::*;
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 self.description()
24 }
25
26 fn requires_argument(&self) -> bool {
27 true
28 }
29
30 fn complete_argument(
31 self: Arc<Self>,
32 arguments: &[String],
33 _cancellation_flag: Arc<AtomicBool>,
34 _workspace: Option<WeakView<Workspace>>,
35 cx: &mut WindowContext,
36 ) -> Task<Result<Vec<ArgumentCompletion>>> {
37 let store = PromptStore::global(cx);
38 let query = arguments.to_owned().join(" ");
39 cx.background_executor().spawn(async move {
40 let prompts = store.await?.search(query).await;
41 Ok(prompts
42 .into_iter()
43 .filter_map(|prompt| {
44 let prompt_title = prompt.title?.to_string();
45 Some(ArgumentCompletion {
46 label: prompt_title.clone().into(),
47 new_text: prompt_title,
48 after_completion: true.into(),
49 replace_previous_arguments: true,
50 })
51 })
52 .collect())
53 })
54 }
55
56 fn run(
57 self: Arc<Self>,
58 arguments: &[String],
59 _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
60 _context_buffer: BufferSnapshot,
61 _workspace: WeakView<Workspace>,
62 _delegate: Option<Arc<dyn LspAdapterDelegate>>,
63 cx: &mut WindowContext,
64 ) -> Task<Result<SlashCommandOutput>> {
65 let title = arguments.to_owned().join(" ");
66 if title.trim().is_empty() {
67 return Task::ready(Err(anyhow!("missing prompt name")));
68 };
69
70 let store = PromptStore::global(cx);
71 let title = SharedString::from(title.clone());
72 let prompt = cx.background_executor().spawn({
73 let title = title.clone();
74 async move {
75 let store = store.await?;
76 let prompt_id = store
77 .id_for_title(&title)
78 .with_context(|| format!("no prompt found with title {:?}", title))?;
79 let body = store.load(prompt_id).await?;
80 anyhow::Ok(body)
81 }
82 });
83 cx.foreground_executor().spawn(async move {
84 let mut prompt = prompt.await?;
85
86 if prompt.starts_with('/') {
87 // Prevent an edge case where the inserted prompt starts with a slash command (that leads to funky rendering).
88 prompt.insert(0, '\n');
89 }
90 if prompt.is_empty() {
91 prompt.push('\n');
92 }
93 let range = 0..prompt.len();
94 Ok(SlashCommandOutput {
95 text: prompt,
96 sections: vec![SlashCommandOutputSection {
97 range,
98 icon: IconName::Library,
99 label: title,
100 metadata: None,
101 }],
102 run_commands_in_text: true,
103 })
104 })
105 }
106}