1use super::{SlashCommand, SlashCommandCleanup, SlashCommandInvocation};
2use crate::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 .map(|(ix, prompt)| StringMatchCandidate::new(ix, prompt.title))
46 .collect::<Vec<_>>();
47 let matches = fuzzy::match_strings(
48 &candidates,
49 &query,
50 false,
51 100,
52 &cancellation_flag,
53 executor,
54 )
55 .await;
56 Ok(matches
57 .into_iter()
58 .map(|mat| candidates[mat.candidate_id].string.clone())
59 .collect())
60 })
61 }
62
63 fn run(&self, title: Option<&str>, cx: &mut AppContext) -> SlashCommandInvocation {
64 let Some(title) = title else {
65 return SlashCommandInvocation {
66 output: Task::ready(Err(anyhow!("missing prompt name"))),
67 invalidated: oneshot::channel().1,
68 cleanup: SlashCommandCleanup::default(),
69 };
70 };
71
72 let library = self.library.clone();
73 let title = title.to_string();
74 let output = cx.background_executor().spawn(async move {
75 let prompt = library
76 .prompts()
77 .into_iter()
78 .find(|prompt| prompt.title == title)
79 .with_context(|| format!("no prompt found with title {:?}", title))?;
80 Ok(prompt.prompt)
81 });
82 SlashCommandInvocation {
83 output,
84 invalidated: oneshot::channel().1,
85 cleanup: SlashCommandCleanup::default(),
86 }
87 }
88}