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