use std::{fmt, io};
use console::{style, Style, StyledObject, Term};
pub trait Theme {
#[inline]
fn format_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
write!(f, "{}:", prompt)
}
#[inline]
fn format_error(&self, f: &mut dyn fmt::Write, err: &str) -> fmt::Result {
write!(f, "error: {}", err)
}
fn format_confirm_prompt(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
default: Option<bool>,
) -> fmt::Result {
if !prompt.is_empty() {
write!(f, "{} ", &prompt)?;
}
match default {
None => {}
Some(true) => write!(f, "[Y/n] ")?,
Some(false) => write!(f, "[y/N] ")?,
}
Ok(())
}
fn format_confirm_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
selection: bool,
) -> fmt::Result {
if prompt.is_empty() {
write!(f, "{}", if selection { "yes" } else { "no" })
} else {
write!(f, "{} {}", &prompt, if selection { "yes" } else { "no" })
}
}
fn format_input_prompt(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
default: Option<&str>,
) -> fmt::Result {
match default {
Some(default) if prompt.is_empty() => write!(f, "[{}]: ", default),
Some(default) => write!(f, "{} [{}]: ", prompt, default),
None => write!(f, "{}: ", prompt),
}
}
#[inline]
fn format_input_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
sel: &str,
) -> fmt::Result {
write!(f, "{}: {}", prompt, sel)
}
#[inline]
fn format_password_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
self.format_input_prompt(f, prompt, None)
}
#[inline]
fn format_password_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
) -> fmt::Result {
self.format_input_prompt_selection(f, prompt, "[hidden]")
}
#[inline]
fn format_select_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
self.format_prompt(f, prompt)
}
#[inline]
fn format_select_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
sel: &str,
) -> fmt::Result {
self.format_input_prompt_selection(f, prompt, sel)
}
#[inline]
fn format_multi_select_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
self.format_prompt(f, prompt)
}
#[inline]
fn format_sort_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
self.format_prompt(f, prompt)
}
fn format_multi_select_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
selections: &[&str],
) -> fmt::Result {
write!(f, "{}: ", prompt)?;
for (idx, sel) in selections.iter().enumerate() {
write!(f, "{}{}", if idx == 0 { "" } else { ", " }, sel)?;
}
Ok(())
}
#[inline]
fn format_sort_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
selections: &[&str],
) -> fmt::Result {
self.format_multi_select_prompt_selection(f, prompt, selections)
}
fn format_select_prompt_item(
&self,
f: &mut dyn fmt::Write,
text: &str,
active: bool,
) -> fmt::Result {
write!(f, "{} {}", if active { ">" } else { " " }, text)
}
fn format_multi_select_prompt_item(
&self,
f: &mut dyn fmt::Write,
text: &str,
checked: bool,
active: bool,
) -> fmt::Result {
write!(
f,
"{} {}",
match (checked, active) {
(true, true) => "> [x]",
(true, false) => " [x]",
(false, true) => "> [ ]",
(false, false) => " [ ]",
},
text
)
}
fn format_sort_prompt_item(
&self,
f: &mut dyn fmt::Write,
text: &str,
picked: bool,
active: bool,
) -> fmt::Result {
write!(
f,
"{} {}",
match (picked, active) {
(true, true) => "> [x]",
(false, true) => "> [ ]",
(_, false) => " [ ]",
},
text
)
}
}
pub struct SimpleTheme;
impl Theme for SimpleTheme {}
pub struct CustomPromptCharacterTheme {
prompt_character: char,
}
impl CustomPromptCharacterTheme {
pub fn new(character: char) -> CustomPromptCharacterTheme {
CustomPromptCharacterTheme {
prompt_character: character,
}
}
}
impl Default for CustomPromptCharacterTheme {
fn default() -> Self {
CustomPromptCharacterTheme {
prompt_character: ':',
}
}
}
impl Theme for CustomPromptCharacterTheme {
fn format_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
write!(f, "{}{}", prompt, self.prompt_character)
}
fn format_input_prompt(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
default: Option<&str>,
) -> fmt::Result {
match default {
Some(default) if prompt.is_empty() => {
write!(f, "[{}]{} ", default, self.prompt_character)
}
Some(default) => write!(f, "{} [{}]{} ", prompt, default, self.prompt_character),
None => write!(f, "{}{} ", prompt, self.prompt_character),
}
}
fn format_input_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
sel: &str,
) -> fmt::Result {
write!(f, "{}{} {}", prompt, self.prompt_character, sel)
}
fn format_multi_select_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
selections: &[&str],
) -> fmt::Result {
write!(f, "{}{} ", prompt, self.prompt_character)?;
for (idx, sel) in selections.iter().enumerate() {
write!(f, "{}{}", if idx == 0 { "" } else { ", " }, sel)?;
}
Ok(())
}
}
pub struct ColorfulTheme {
pub defaults_style: Style,
pub prompt_style: Style,
pub prompt_prefix: StyledObject<String>,
pub prompt_suffix: StyledObject<String>,
pub success_prefix: StyledObject<String>,
pub success_suffix: StyledObject<String>,
pub error_prefix: StyledObject<String>,
pub error_style: Style,
pub hint_style: Style,
pub values_style: Style,
pub active_item_style: Style,
pub inactive_item_style: Style,
pub active_item_prefix: StyledObject<String>,
pub inactive_item_prefix: StyledObject<String>,
pub checked_item_prefix: StyledObject<String>,
pub unchecked_item_prefix: StyledObject<String>,
pub picked_item_prefix: StyledObject<String>,
pub unpicked_item_prefix: StyledObject<String>,
}
impl Default for ColorfulTheme {
fn default() -> ColorfulTheme {
ColorfulTheme {
defaults_style: Style::new().for_stderr().cyan(),
prompt_style: Style::new().for_stderr().bold(),
prompt_prefix: style("?".to_string()).for_stderr().yellow(),
prompt_suffix: style("›".to_string()).for_stderr().black().bright(),
success_prefix: style("✔".to_string()).for_stderr().green(),
success_suffix: style("·".to_string()).for_stderr().black().bright(),
error_prefix: style("✘".to_string()).for_stderr().red(),
error_style: Style::new().for_stderr().red(),
hint_style: Style::new().for_stderr().black().bright(),
values_style: Style::new().for_stderr().green(),
active_item_style: Style::new().for_stderr().cyan(),
inactive_item_style: Style::new().for_stderr(),
active_item_prefix: style("❯".to_string()).for_stderr().green(),
inactive_item_prefix: style(" ".to_string()).for_stderr(),
checked_item_prefix: style("✔".to_string()).for_stderr().green(),
unchecked_item_prefix: style("✔".to_string()).for_stderr().black(),
picked_item_prefix: style("❯".to_string()).for_stderr().green(),
unpicked_item_prefix: style(" ".to_string()).for_stderr(),
}
}
}
impl Theme for ColorfulTheme {
fn format_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
if !prompt.is_empty() {
write!(
f,
"{} {} ",
&self.prompt_prefix,
self.prompt_style.apply_to(prompt)
)?;
}
write!(f, "{}", &self.prompt_suffix)
}
fn format_error(&self, f: &mut dyn fmt::Write, err: &str) -> fmt::Result {
write!(
f,
"{} {}",
&self.error_prefix,
self.error_style.apply_to(err)
)
}
fn format_input_prompt(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
default: Option<&str>,
) -> fmt::Result {
if !prompt.is_empty() {
write!(
f,
"{} {} ",
&self.prompt_prefix,
self.prompt_style.apply_to(prompt)
)?;
}
match default {
Some(default) => write!(
f,
"{} {} ",
self.hint_style.apply_to(&format!("({})", default)),
&self.prompt_suffix
),
None => write!(f, "{} ", &self.prompt_suffix),
}
}
fn format_confirm_prompt(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
default: Option<bool>,
) -> fmt::Result {
if !prompt.is_empty() {
write!(
f,
"{} {} ",
&self.prompt_prefix,
self.prompt_style.apply_to(prompt)
)?;
}
match default {
None => write!(f, "{}", &self.prompt_suffix),
Some(true) => write!(
f,
"{} {} {}",
self.hint_style.apply_to("(Y/n)"),
&self.prompt_suffix,
self.defaults_style.apply_to("yes")
),
Some(false) => write!(
f,
"{} {} {}",
self.hint_style.apply_to("(y/N)"),
&self.prompt_suffix,
self.defaults_style.apply_to("no")
),
}
}
fn format_confirm_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
selection: bool,
) -> fmt::Result {
if !prompt.is_empty() {
write!(
f,
"{} {} ",
&self.success_prefix,
self.prompt_style.apply_to(prompt)
)?;
}
write!(
f,
"{} {}",
&self.success_suffix,
self.values_style
.apply_to(if selection { "yes" } else { "no" })
)
}
fn format_input_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
sel: &str,
) -> fmt::Result {
if !prompt.is_empty() {
write!(
f,
"{} {} ",
&self.success_prefix,
self.prompt_style.apply_to(prompt)
)?;
}
write!(
f,
"{} {}",
&self.success_suffix,
self.values_style.apply_to(sel)
)
}
fn format_password_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
) -> fmt::Result {
self.format_input_prompt_selection(f, prompt, "********")
}
fn format_multi_select_prompt_selection(
&self,
f: &mut dyn fmt::Write,
prompt: &str,
selections: &[&str],
) -> fmt::Result {
if !prompt.is_empty() {
write!(
f,
"{} {} ",
&self.success_prefix,
self.prompt_style.apply_to(prompt)
)?;
}
write!(f, "{} ", &self.success_suffix)?;
for (idx, sel) in selections.iter().enumerate() {
write!(
f,
"{}{}",
if idx == 0 { "" } else { ", " },
self.values_style.apply_to(sel)
)?;
}
Ok(())
}
fn format_select_prompt_item(
&self,
f: &mut dyn fmt::Write,
text: &str,
active: bool,
) -> fmt::Result {
let details = match active {
true => (
&self.active_item_prefix,
self.active_item_style.apply_to(text),
),
false => (
&self.inactive_item_prefix,
self.inactive_item_style.apply_to(text),
),
};
write!(f, "{} {}", details.0, details.1)
}
fn format_multi_select_prompt_item(
&self,
f: &mut dyn fmt::Write,
text: &str,
checked: bool,
active: bool,
) -> fmt::Result {
let details = match (checked, active) {
(true, true) => (
&self.checked_item_prefix,
self.active_item_style.apply_to(text),
),
(true, false) => (
&self.checked_item_prefix,
self.inactive_item_style.apply_to(text),
),
(false, true) => (
&self.unchecked_item_prefix,
self.active_item_style.apply_to(text),
),
(false, false) => (
&self.unchecked_item_prefix,
self.inactive_item_style.apply_to(text),
),
};
write!(f, "{} {}", details.0, details.1)
}
fn format_sort_prompt_item(
&self,
f: &mut dyn fmt::Write,
text: &str,
picked: bool,
active: bool,
) -> fmt::Result {
let details = match (picked, active) {
(true, true) => (
&self.picked_item_prefix,
self.active_item_style.apply_to(text),
),
(false, true) => (
&self.unpicked_item_prefix,
self.active_item_style.apply_to(text),
),
(_, false) => (
&self.unpicked_item_prefix,
self.inactive_item_style.apply_to(text),
),
};
write!(f, "{} {}", details.0, details.1)
}
}
pub(crate) struct TermThemeRenderer<'a> {
term: &'a Term,
theme: &'a dyn Theme,
height: usize,
prompt_height: usize,
prompts_reset_height: bool,
}
impl<'a> TermThemeRenderer<'a> {
pub fn new(term: &'a Term, theme: &'a dyn Theme) -> TermThemeRenderer<'a> {
TermThemeRenderer {
term,
theme,
height: 0,
prompt_height: 0,
prompts_reset_height: true,
}
}
pub fn set_prompts_reset_height(&mut self, val: bool) {
self.prompts_reset_height = val;
}
pub fn term(&self) -> &Term {
self.term
}
pub fn add_line(&mut self) {
self.height += 1;
}
fn write_formatted_str<
F: FnOnce(&mut TermThemeRenderer, &mut dyn fmt::Write) -> fmt::Result,
>(
&mut self,
f: F,
) -> io::Result<()> {
let mut buf = String::new();
f(self, &mut buf).map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
self.height += buf.chars().filter(|&x| x == '\n').count();
self.term.write_str(&buf)
}
fn write_formatted_line<
F: FnOnce(&mut TermThemeRenderer, &mut dyn fmt::Write) -> fmt::Result,
>(
&mut self,
f: F,
) -> io::Result<()> {
let mut buf = String::new();
f(self, &mut buf).map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
self.height += buf.chars().filter(|&x| x == '\n').count() + 1;
self.term.write_line(&buf)
}
fn write_formatted_prompt<
F: FnOnce(&mut TermThemeRenderer, &mut dyn fmt::Write) -> fmt::Result,
>(
&mut self,
f: F,
) -> io::Result<()> {
self.write_formatted_line(f)?;
if self.prompts_reset_height {
self.prompt_height = self.height;
self.height = 0;
}
Ok(())
}
pub fn error(&mut self, err: &str) -> io::Result<()> {
self.write_formatted_line(|this, buf| this.theme.format_error(buf, err))
}
pub fn confirm_prompt(&mut self, prompt: &str, default: Option<bool>) -> io::Result<()> {
self.write_formatted_str(|this, buf| this.theme.format_confirm_prompt(buf, prompt, default))
}
pub fn confirm_prompt_selection(&mut self, prompt: &str, sel: bool) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| {
this.theme.format_confirm_prompt_selection(buf, prompt, sel)
})
}
pub fn input_prompt(&mut self, prompt: &str, default: Option<&str>) -> io::Result<()> {
self.write_formatted_str(|this, buf| this.theme.format_input_prompt(buf, prompt, default))
}
pub fn input_prompt_selection(&mut self, prompt: &str, sel: &str) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| {
this.theme.format_input_prompt_selection(buf, prompt, sel)
})
}
pub fn password_prompt(&mut self, prompt: &str) -> io::Result<()> {
self.write_formatted_str(|this, buf| {
write!(buf, "\r")?;
this.theme.format_password_prompt(buf, prompt)
})
}
pub fn password_prompt_selection(&mut self, prompt: &str) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| {
this.theme.format_password_prompt_selection(buf, prompt)
})
}
pub fn select_prompt(&mut self, prompt: &str) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| this.theme.format_select_prompt(buf, prompt))
}
pub fn select_prompt_selection(&mut self, prompt: &str, sel: &str) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| {
this.theme.format_select_prompt_selection(buf, prompt, sel)
})
}
pub fn select_prompt_item(&mut self, text: &str, active: bool) -> io::Result<()> {
self.write_formatted_line(|this, buf| {
this.theme.format_select_prompt_item(buf, text, active)
})
}
pub fn multi_select_prompt(&mut self, prompt: &str) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| this.theme.format_multi_select_prompt(buf, prompt))
}
pub fn multi_select_prompt_selection(&mut self, prompt: &str, sel: &[&str]) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| {
this.theme
.format_multi_select_prompt_selection(buf, prompt, sel)
})
}
pub fn multi_select_prompt_item(
&mut self,
text: &str,
checked: bool,
active: bool,
) -> io::Result<()> {
self.write_formatted_line(|this, buf| {
this.theme
.format_multi_select_prompt_item(buf, text, checked, active)
})
}
pub fn sort_prompt(&mut self, prompt: &str) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| this.theme.format_sort_prompt(buf, prompt))
}
pub fn sort_prompt_selection(&mut self, prompt: &str, sel: &[&str]) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| {
this.theme.format_sort_prompt_selection(buf, prompt, sel)
})
}
pub fn sort_prompt_item(&mut self, text: &str, picked: bool, active: bool) -> io::Result<()> {
self.write_formatted_line(|this, buf| {
this.theme
.format_sort_prompt_item(buf, text, picked, active)
})
}
pub fn clear(&mut self) -> io::Result<()> {
self.term
.clear_last_lines(self.height + self.prompt_height)?;
self.height = 0;
Ok(())
}
pub fn clear_preserve_prompt(&mut self, size_vec: &[usize]) -> io::Result<()> {
let mut new_height = self.height;
for size in size_vec {
if *size > self.term.size().1 as usize {
new_height += 1;
}
}
self.term.clear_last_lines(new_height)?;
self.height = 0;
Ok(())
}
}