1use core::fmt;2use std::borrow::Cow;3use std::future::Future;4use std::result;56pub mod bifrost;7pub mod dbus;8pub mod rofi;910#[derive(thiserror::Error, Debug, serde::Serialize, serde::Deserialize)]11pub enum Error {12 #[error("user has cancelled input")]13 Cancel,14 #[error("input error: {0}")]15 InputError(String),16 #[error("unknown remote error: {0}")]17 Remote(String),18}1920pub type Result<T, E = Error> = result::Result<T, E>;2122#[cfg_attr(feature = "dbus", derive(zbus::zvariant::Type))]23#[derive(serde::Serialize, serde::Deserialize, Clone)]24pub struct Source(pub Cow<'static, str>);25impl fmt::Display for Source {26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {27 write!(f, "<u>{}</u>", self.0)28 }29}3031pub trait Prompter: Send + Sync {32 fn prompt_radio(33 &self,34 prompt: &str,35 description: &str,36 source: &[Source],37 ) -> impl Future<Output = Result<bool>> + Send {38 let fut = self.prompt_enum(prompt, description, &["No", "Yes"], source);39 async { fut.await.map(|v| v == 1) }40 }41 fn prompt_enum(42 &self,43 prompt: &str,44 description: &str,45 variants: &[&str],46 source: &[Source],47 ) -> impl Future<Output = Result<u32>> + Send;48 fn prompt_text(49 &self,50 echo: bool,51 prompt: &str,52 description: &str,53 source: &[Source],54 ) -> impl Future<Output = Result<String>> + Send;55 fn display_text(56 &self,57 error: bool,58 description: &str,59 source: &[Source],60 ) -> impl Future<Output = Result<()>> + Send;61}62pub trait BlockingPrompter {63 fn prompt_radio(&self, prompt: &str, description: &str, source: &[Source]) -> Result<bool> {64 self.prompt_enum(prompt, description, &["No", "Yes"], source)65 .map(|v| v == 1)66 }67 fn prompt_enum(68 &self,69 prompt: &str,70 description: &str,71 variants: &[&str],72 source: &[Source],73 ) -> Result<u32>;74 fn prompt_text(75 &self,76 echo: bool,77 prompt: &str,78 description: &str,79 source: &[Source],80 ) -> Result<String>;81 fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()>;82}83impl<P> Prompter for &P84where85 P: Prompter,86{87 fn prompt_radio(88 &self,89 prompt: &str,90 description: &str,91 source: &[Source],92 ) -> impl Future<Output = Result<bool>> + Send {93 (*self).prompt_radio(prompt, description, source)94 }9596 fn prompt_enum(97 &self,98 prompt: &str,99 description: &str,100 variants: &[&str],101 source: &[Source],102 ) -> impl Future<Output = Result<u32>> + Send {103 (*self).prompt_enum(prompt, description, variants, source)104 }105106 fn prompt_text(107 &self,108 echo: bool,109 prompt: &str,110 description: &str,111 source: &[Source],112 ) -> impl Future<Output = Result<String>> + Send {113 (*self).prompt_text(echo, prompt, description, source)114 }115116 fn display_text(117 &self,118 error: bool,119 description: &str,120 source: &[Source],121 ) -> impl Future<Output = Result<()>> + Send {122 (*self).display_text(error, description, source)123 }124}125126pub struct PrependSourcePrompter<P> {127 pub prompter: P,128 pub source: Vec<Source>,129 pub description: String,130}131impl<P> PrependSourcePrompter<P> {132 fn source(&self, input: &[Source]) -> Vec<Source> {133 let mut out = self.source.clone();134 out.extend(input.iter().cloned());135 out136 }137 fn description(&self, input: &str) -> String {138 if self.description.is_empty() {139 input.to_owned()140 } else if input.is_empty() {141 self.description.to_owned()142 } else {143 format!("{input}\n\n{}", self.description)144 }145 }146}147impl<P> Prompter for PrependSourcePrompter<P>148where149 P: Prompter + Sync,150{151 async fn prompt_radio(152 &self,153 prompt: &str,154 description: &str,155 source: &[Source],156 ) -> Result<bool> {157 self.prompter158 .prompt_radio(prompt, &self.description(description), &self.source(source))159 .await160 }161162 async fn prompt_enum(163 &self,164 prompt: &str,165 description: &str,166 variants: &[&str],167 source: &[Source],168 ) -> Result<u32> {169 self.prompter170 .prompt_enum(171 prompt,172 &self.description(description),173 variants,174 &self.source(source),175 )176 .await177 }178179 async fn prompt_text(180 &self,181 echo: bool,182 prompt: &str,183 description: &str,184 source: &[Source],185 ) -> Result<String> {186 self.prompter187 .prompt_text(188 echo,189 prompt,190 &self.description(description),191 &self.source(source),192 )193 .await194 }195196 async fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {197 self.prompter198 .display_text(error, &self.description(description), &self.source(source))199 .await200 }201}