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