1use super::{SlashCommand, SlashCommandCleanup, SlashCommandInvocation};
2use crate::prompts::prompt_library::PromptLibrary;
3use anyhow::{anyhow, Context, Result};
4use futures::channel::oneshot;
5use fuzzy::StringMatchCandidate;
6use gpui::{AppContext, Task};
7use std::sync::{atomic::AtomicBool, Arc};
8
9pub(crate) struct PromptSlashCommand {
10 library: Arc<PromptLibrary>,
11}
12
13impl PromptSlashCommand {
14 pub fn new(library: Arc<PromptLibrary>) -> Self {
15 Self { library }
16 }
17}
18
19impl SlashCommand for PromptSlashCommand {
20 fn name(&self) -> String {
21 "prompt".into()
22 }
23
24 fn description(&self) -> String {
25 "insert a prompt from the library".into()
26 }
27
28 fn requires_argument(&self) -> bool {
29 true
30 }
31
32 fn complete_argument(
33 &self,
34 query: String,
35 cancellation_flag: Arc<AtomicBool>,
36 cx: &mut AppContext,
37 ) -> Task<Result<Vec<String>>> {
38 let library = self.library.clone();
39 let executor = cx.background_executor().clone();
40 cx.background_executor().spawn(async move {
41 let candidates = library
42 .prompts()
43 .into_iter()
44 .enumerate()
45 .filter_map(|(ix, prompt)| {
46 prompt
47 .1
48 .title()
49 .map(|title| StringMatchCandidate::new(ix, title.into()))
50 })
51 .collect::<Vec<_>>();
52 let matches = fuzzy::match_strings(
53 &candidates,
54 &query,
55 false,
56 100,
57 &cancellation_flag,
58 executor,
59 )
60 .await;
61 Ok(matches
62 .into_iter()
63 .map(|mat| candidates[mat.candidate_id].string.clone())
64 .collect())
65 })
66 }
67
68 fn run(&self, title: Option<&str>, cx: &mut AppContext) -> SlashCommandInvocation {
69 let Some(title) = title else {
70 return SlashCommandInvocation {
71 output: Task::ready(Err(anyhow!("missing prompt name"))),
72 invalidated: oneshot::channel().1,
73 cleanup: SlashCommandCleanup::default(),
74 };
75 };
76
77 let library = self.library.clone();
78 let title = title.to_string();
79 let output = cx.background_executor().spawn(async move {
80 let prompt = library
81 .prompts()
82 .into_iter()
83 .filter_map(|prompt| prompt.1.title().map(|title| (title, prompt)))
84 .find(|(t, _)| t == &title)
85 .with_context(|| format!("no prompt found with title {:?}", title))?
86 .1;
87 Ok(prompt.1.content().to_owned())
88 });
89 SlashCommandInvocation {
90 output,
91 invalidated: oneshot::channel().1,
92 cleanup: SlashCommandCleanup::default(),
93 }
94 }
95}