use std::fmt::Display;
use cursive::theme::{Effect, Style};
use cursive::utils::markup::StyledString;
use cursive::utils::span::Span;
pub struct Pluralize<'a> {
pub determiner: Option<(&'a str, &'a str)>,
pub amount: usize,
pub unit: (&'a str, &'a str),
}
impl Display for Pluralize<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self {
amount: 1,
unit: (unit, _),
determiner: None,
} => write!(f, "{} {}", 1, unit),
Self {
amount,
unit: (_, unit),
determiner: None,
} => write!(f, "{} {}", amount, unit),
Self {
amount: 1,
unit: (unit, _),
determiner: Some((determiner, _)),
} => write!(f, "{} {} {}", determiner, 1, unit),
Self {
amount,
unit: (_, unit),
determiner: Some((_, determiner)),
} => write!(f, "{} {} {}", determiner, amount, unit),
}
}
}
#[derive(Clone)]
pub struct Glyphs {
pub should_write_ansi_escape_codes: bool,
pub line: &'static str,
pub line_with_offshoot: &'static str,
pub vertical_ellipsis: &'static str,
pub split: &'static str,
pub merge: &'static str,
pub commit_visible: &'static str,
pub commit_visible_head: &'static str,
pub commit_obsolete: &'static str,
pub commit_obsolete_head: &'static str,
pub commit_main: &'static str,
pub commit_main_head: &'static str,
pub commit_main_obsolete: &'static str,
pub commit_main_obsolete_head: &'static str,
pub commit_omitted: &'static str,
pub commit_merge: &'static str,
pub branch_arrow: &'static str,
pub bullet_point: &'static str,
pub cycle_arrow: &'static str,
pub cycle_horizontal_line: &'static str,
pub cycle_vertical_line: &'static str,
pub cycle_upper_left_corner: &'static str,
pub cycle_lower_left_corner: &'static str,
}
impl Glyphs {
pub fn detect() -> Self {
let color_support = concolor::get(concolor::Stream::Stdout);
if color_support.color() {
Glyphs::pretty()
} else {
Glyphs::text()
}
}
pub fn text() -> Self {
Glyphs {
should_write_ansi_escape_codes: false,
line: "|",
line_with_offshoot: "|",
vertical_ellipsis: ":",
split: "\\",
merge: "/",
commit_visible: "o",
commit_visible_head: "@",
commit_obsolete: "x",
commit_obsolete_head: "%",
commit_main: "O",
commit_main_head: "@",
commit_main_obsolete: "X",
commit_main_obsolete_head: "%",
commit_omitted: "#",
commit_merge: "&",
branch_arrow: ">",
bullet_point: "-",
cycle_arrow: ">",
cycle_horizontal_line: "-",
cycle_vertical_line: "|",
cycle_upper_left_corner: ",",
cycle_lower_left_corner: "`",
}
}
pub fn pretty() -> Self {
Glyphs {
should_write_ansi_escape_codes: true,
line: "┃",
line_with_offshoot: "┣",
vertical_ellipsis: "⋮",
split: "━┓",
merge: "━┛",
commit_visible: "◯",
commit_visible_head: "●",
commit_obsolete: "✕",
commit_obsolete_head: "⦻",
commit_omitted: "⊘",
commit_merge: "↓",
commit_main: "◇",
commit_main_head: "◆",
commit_main_obsolete: "✕",
commit_main_obsolete_head: "❖",
branch_arrow: "ᐅ",
bullet_point: "•",
cycle_arrow: "ᐅ",
cycle_horizontal_line: "─",
cycle_vertical_line: "│",
cycle_upper_left_corner: "┌",
cycle_lower_left_corner: "└",
}
}
pub fn render(&self, string: StyledString) -> eyre::Result<String> {
let result = string
.spans()
.map(|span| {
let Span {
content,
attr,
width: _,
} = span;
if self.should_write_ansi_escape_codes {
Ok(render_style_as_ansi(content, *attr)?)
} else {
Ok(content.to_string())
}
})
.collect::<eyre::Result<String>>()?;
Ok(result)
}
}
impl std::fmt::Debug for Glyphs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"<Glyphs pretty={:?}>",
self.should_write_ansi_escape_codes
)
}
}
pub struct StyledStringBuilder {
elements: Vec<StyledString>,
}
impl Default for StyledStringBuilder {
fn default() -> Self {
StyledStringBuilder::new()
}
}
impl StyledStringBuilder {
pub fn new() -> Self {
Self {
elements: Vec::new(),
}
}
fn append_plain_inner(mut self, text: &str) -> Self {
self.elements.push(StyledString::plain(text));
self
}
pub fn append_plain(self, text: impl AsRef<str>) -> Self {
self.append_plain_inner(text.as_ref())
}
fn append_styled_inner(mut self, text: &str, style: Style) -> Self {
self.elements.push(StyledString::styled(text, style));
self
}
pub fn append_styled(self, text: impl AsRef<str>, style: impl Into<Style>) -> Self {
self.append_styled_inner(text.as_ref(), style.into())
}
fn append_inner(mut self, text: StyledString) -> Self {
self.elements.push(text);
self
}
pub fn append(self, text: impl Into<StyledString>) -> Self {
self.append_inner(text.into())
}
pub fn build(self) -> StyledString {
let mut result = StyledString::new();
for element in self.elements {
result.append(element);
}
result
}
pub fn join(delimiter: &str, strings: Vec<StyledString>) -> StyledString {
let mut result = Self::new();
let mut is_first = true;
for string in strings {
if is_first {
is_first = false;
} else {
result = result.append_plain(delimiter);
}
result = result.append(string);
}
result.into()
}
pub fn from_lines(lines: Vec<StyledString>) -> StyledString {
let mut result = Self::new();
for line in lines {
result = result.append(line);
result = result.append_plain("\n");
}
result.into()
}
}
pub fn set_effect(mut string: StyledString, effect: Effect) -> StyledString {
string.spans_raw_attr_mut().for_each(|span| {
span.attr.effects.insert(effect);
});
string
}
impl From<StyledStringBuilder> for StyledString {
fn from(builder: StyledStringBuilder) -> Self {
builder.build()
}
}
fn render_style_as_ansi(content: &str, style: Style) -> eyre::Result<String> {
let Style { effects, color } = style;
let output = {
use console::style;
use cursive::theme::{BaseColor, Color, ColorType};
let output = content.to_string();
match color.front {
ColorType::Palette(_) => {
eyre::bail!("Not implemented: using cursive palette colors")
}
ColorType::Color(Color::Rgb(..)) | ColorType::Color(Color::RgbLowRes(..)) => {
eyre::bail!("Not implemented: using raw RGB colors")
}
ColorType::InheritParent | ColorType::Color(Color::TerminalDefault) => style(output),
ColorType::Color(Color::Light(color)) => match color {
BaseColor::Black => style(output).black().bright(),
BaseColor::Red => style(output).red().bright(),
BaseColor::Green => style(output).green().bright(),
BaseColor::Yellow => style(output).yellow().bright(),
BaseColor::Blue => style(output).blue().bright(),
BaseColor::Magenta => style(output).magenta().bright(),
BaseColor::Cyan => style(output).cyan().bright(),
BaseColor::White => style(output).white().bright(),
},
ColorType::Color(Color::Dark(color)) => match color {
BaseColor::Black => style(output).black(),
BaseColor::Red => style(output).red(),
BaseColor::Green => style(output).green(),
BaseColor::Yellow => style(output).yellow(),
BaseColor::Blue => style(output).blue(),
BaseColor::Magenta => style(output).magenta(),
BaseColor::Cyan => style(output).cyan(),
BaseColor::White => style(output).white(),
},
}
};
let output = {
let mut output = output;
for effect in effects.iter() {
output = match effect {
Effect::Simple => output,
Effect::Dim => output.dim(),
Effect::Reverse => output.reverse(),
Effect::Bold => output.bold(),
Effect::Italic => output.italic(),
Effect::Strikethrough => eyre::bail!("Not implemented: Effect::Strikethrough"),
Effect::Underline => output.underlined(),
Effect::Blink => output.blink(),
};
}
output
};
let output = output.force_styling(true);
Ok(output.to_string())
}