use {
crate::{
*,
crossterm::{
queue,
style::{
Attribute,
Color,
Print,
},
},
errors::Result,
table_border_chars::*,
tbl::*,
},
minimad::{
Alignment,
Composite,
Compound,
Line,
OwningTemplateExpander,
TextTemplate,
TextTemplateExpander,
MAX_HEADER_DEPTH,
},
std::{
collections::HashMap,
fmt,
io::Write,
},
unicode_width::UnicodeWidthStr,
};
#[derive(Clone, Debug, PartialEq)]
pub struct MadSkin {
pub paragraph: LineStyle,
pub bold: CompoundStyle,
pub italic: CompoundStyle,
pub strikeout: CompoundStyle,
pub inline_code: CompoundStyle,
pub code_block: LineStyle,
pub headers: [LineStyle; MAX_HEADER_DEPTH],
pub scrollbar: ScrollBarStyle,
pub table: LineStyle, pub bullet: StyledChar,
pub quote_mark: StyledChar,
pub horizontal_rule: StyledChar,
pub ellipsis: CompoundStyle,
pub table_border_chars: &'static TableBorderChars,
pub list_items_indentation_mode: ListItemsIndentationMode,
#[cfg(feature = "special-renders")]
pub special_chars: HashMap<Compound<'static>, StyledChar>,
}
impl Default for MadSkin {
fn default() -> Self {
let mut skin = Self {
paragraph: LineStyle::default(),
bold: CompoundStyle::with_attr(Attribute::Bold),
italic: CompoundStyle::with_attr(Attribute::Italic),
strikeout: CompoundStyle::with_attr(Attribute::CrossedOut),
inline_code: CompoundStyle::with_fgbg(gray(17), gray(3)),
code_block: LineStyle::default(),
headers: Default::default(),
scrollbar: ScrollBarStyle::new(),
table: CompoundStyle::with_fg(gray(7)).into(),
bullet: StyledChar::from_fg_char(gray(8), '•'),
quote_mark: StyledChar::new(
CompoundStyle::new(Some(gray(12)), None, Attribute::Bold.into()),
'▐',
),
horizontal_rule: StyledChar::from_fg_char(gray(6), '―'),
ellipsis: CompoundStyle::default(),
table_border_chars: STANDARD_TABLE_BORDER_CHARS,
list_items_indentation_mode: Default::default(),
#[cfg(feature = "special-renders")]
special_chars: HashMap::new(),
};
skin.code_block.set_fgbg(gray(17), gray(3));
for h in &mut skin.headers {
h.add_attr(Attribute::Underlined);
}
skin.headers[0].add_attr(Attribute::Bold);
skin.headers[0].align = Alignment::Center;
skin
}
}
impl MadSkin {
pub fn no_style() -> Self {
Self {
paragraph: LineStyle::default(),
bold: CompoundStyle::default(),
italic: CompoundStyle::default(),
strikeout: CompoundStyle::default(),
inline_code: CompoundStyle::default(),
code_block: LineStyle::default(),
headers: Default::default(),
scrollbar: ScrollBarStyle::new(),
table: LineStyle::default(),
bullet: StyledChar::nude('•'),
quote_mark: StyledChar::nude('▐'),
horizontal_rule: StyledChar::nude('―'),
ellipsis: CompoundStyle::default(),
list_items_indentation_mode: Default::default(),
#[cfg(feature = "special-renders")]
special_chars: HashMap::new(),
table_border_chars: STANDARD_TABLE_BORDER_CHARS,
}
}
pub fn default_dark() -> Self {
let mut skin = Self::default();
skin.code_block.set_fgbg(gray(20), gray(5));
skin.inline_code.set_fgbg(gray(20), gray(5));
skin.headers[0].set_fg(gray(22));
skin.headers[1].set_fg(gray(21));
skin.headers[2].set_fg(gray(20));
skin
}
pub fn default_light() -> Self {
let mut skin = Self::default();
skin.code_block.set_fgbg(gray(3), gray(20));
skin.inline_code.set_fgbg(gray(4), gray(20));
skin.headers[0].set_fg(gray(0));
skin.headers[1].set_fg(gray(2));
skin.headers[2].set_fg(gray(4));
skin
}
pub fn limit_to_ascii(&mut self) {
self.table_border_chars = ASCII_TABLE_BORDER_CHARS;
self.bullet.set_char('*');
self.quote_mark.set_char('>');
self.horizontal_rule.set_char('-');
}
pub fn blend_with<C: Into<coolor::Color> + Copy>(&mut self, color: C, weight: f32) {
self.paragraph.compound_style.blend_with(color, weight);
self.bold.blend_with(color, weight);
self.italic.blend_with(color, weight);
self.inline_code.blend_with(color, weight);
self.code_block.blend_with(color, weight);
self.table.compound_style.blend_with(color, weight);
self.strikeout.blend_with(color, weight);
for h in &mut self.headers {
h.blend_with(color, weight);
}
self.bullet.blend_with(color, weight);
self.quote_mark.blend_with(color, weight);
self.horizontal_rule.blend_with(color, weight);
self.ellipsis.blend_with(color, weight);
}
pub fn set_fg(&mut self, fg: Color) {
self.paragraph.compound_style.set_fg(fg);
self.bold.set_fg(fg);
self.italic.set_fg(fg);
self.strikeout.set_fg(fg);
self.set_headers_fg(fg);
self.bullet.set_fg(fg);
self.quote_mark.set_fg(fg);
self.horizontal_rule.set_fg(fg);
self.ellipsis.set_fg(fg);
#[cfg(feature = "special-renders")]
{
for (_, sc) in self.special_chars.iter_mut() {
sc.set_fg(fg);
}
}
}
pub fn set_bg(&mut self, bg: Color) {
self.paragraph.compound_style.set_bg(bg);
self.bold.set_bg(bg);
self.italic.set_bg(bg);
self.strikeout.set_bg(bg);
self.set_headers_bg(bg);
self.table.compound_style.set_bg(bg);
self.bullet.set_bg(bg);
self.quote_mark.set_bg(bg);
self.horizontal_rule.set_bg(bg);
self.ellipsis.set_bg(bg);
self.scrollbar.set_bg(bg);
#[cfg(feature = "special-renders")]
{
for (_, sc) in self.special_chars.iter_mut() {
sc.set_bg(bg);
}
}
}
pub fn set_headers_fg(&mut self, c: Color) {
for h in &mut self.headers {
h.set_fg(c);
}
}
pub fn set_headers_bg(&mut self, c: Color) {
for h in &mut self.headers {
h.set_bg(c);
}
}
pub fn set_global_bg(&mut self, c: Color) {
self.set_headers_bg(c);
self.paragraph.set_bg(c);
self.horizontal_rule.set_bg(c);
}
pub fn visible_composite_length(
&self,
kind: CompositeKind,
compounds: &[Compound<'_>],
) -> usize {
let compounds_width: usize = compounds.iter().map(|c| c.src.width()).sum();
(match kind {
CompositeKind::ListItem(depth) => 2 + depth as usize, CompositeKind::ListItemFollowUp(depth) => match self.list_items_indentation_mode {
ListItemsIndentationMode::FirstLineOnly => 0,
ListItemsIndentationMode::Block => 2 + depth as usize, },
CompositeKind::Quote => 2, _ => 0,
}) + compounds_width
}
pub fn visible_line_length(&self, line: &Line<'_>) -> usize {
match line {
Line::Normal(composite) => self.visible_composite_length(composite.style.into(), &composite.compounds),
_ => 0, }
}
pub const fn line_style(&self, kind: CompositeKind) -> &LineStyle {
match kind {
CompositeKind::Code => &self.code_block,
CompositeKind::Header(level) if level <= MAX_HEADER_DEPTH as u8 => {
&self.headers[level as usize - 1]
}
_ => &self.paragraph,
}
}
fn compound_style(&self, line_style: &LineStyle, compound: &Compound<'_>) -> CompoundStyle {
if *compound.src == *crate::fit::ELLIPSIS {
return self.ellipsis.clone();
}
let mut os = line_style.compound_style.clone();
if compound.italic {
os.overwrite_with(&self.italic);
}
if compound.strikeout {
os.overwrite_with(&self.strikeout);
}
if compound.bold {
os.overwrite_with(&self.bold);
}
if compound.code {
os.overwrite_with(&self.inline_code);
}
os
}
pub fn inline<'k, 's>(&'k self, src: &'s str) -> FmtInline<'k, 's> {
let composite = FmtComposite::from(Composite::from_inline(src), self);
FmtInline {
skin: self,
composite,
}
}
pub fn text<'k, 's>(&'k self, src: &'s str, width: Option<usize>) -> FmtText<'k, 's> {
FmtText::from(self, src, width)
}
pub fn term_text<'k, 's>(&'k self, src: &'s str) -> FmtText<'k, 's> {
let (width, _) = terminal_size();
FmtText::from(self, src, Some(width as usize))
}
pub fn area_text<'k, 's>(&'k self, src: &'s str, area: &Area) -> FmtText<'k, 's> {
FmtText::from(self, src, Some(area.width as usize - 1))
}
pub fn write_in_area(&self, markdown: &str, area: &Area) -> Result<()> {
let mut w = std::io::stdout();
self.write_in_area_on(&mut w, markdown, area)?;
w.flush()?;
Ok(())
}
pub fn write_in_area_on<W: Write>(&self, w: &mut W, markdown: &str, area: &Area) -> Result<()> {
let text = self.area_text(markdown, area);
let mut view = TextView::from(area, &text);
view.show_scrollbar = false;
view.write_on(w)
}
pub fn print_inline(&self, src: &str) {
print!("{}", self.inline(src));
}
pub fn print_text(&self, src: &str) {
print!("{}", self.term_text(src));
}
pub fn print_expander(&self, expander: TextTemplateExpander<'_, '_>) {
let (width, _) = terminal_size();
let text = expander.expand();
let fmt_text = FmtText::from_text(self, text, Some(width as usize));
print!("{}", fmt_text);
}
pub fn print_owning_expander(
&self,
expander: &OwningTemplateExpander<'_>,
template: &TextTemplate<'_>,
) {
let (width, _) = terminal_size();
let text = expander.expand(template);
let fmt_text = FmtText::from_text(self, text, Some(width as usize));
print!("{}", fmt_text);
}
pub fn print_owning_expander_md<T: Into<String>>(
&self,
expander: &OwningTemplateExpander<'_>,
template: T,
) {
let (width, _) = terminal_size();
let template_md: String = template.into();
let template = TextTemplate::from(&*template_md);
let text = expander.expand(&template);
let fmt_text = FmtText::from_text(self, text, Some(width as usize));
print!("{}", fmt_text);
}
pub fn print_composite(&self, composite: Composite<'_>) {
print!(
"{}",
FmtInline {
skin: self,
composite: FmtComposite::from(composite, self),
}
);
}
pub fn write_composite<W>(&self, w: &mut W, composite: Composite<'_>) -> Result<()>
where
W: std::io::Write,
{
Ok(queue!(
w,
Print(FmtInline {
skin: self,
composite: FmtComposite::from(composite, self),
})
)?)
}
pub fn write_composite_fill<W>(
&self,
w: &mut W,
composite: Composite<'_>,
width: usize,
align: Alignment,
) -> Result<()>
where
W: std::io::Write,
{
let mut fc = FmtComposite::from(composite, self);
fc.fill_width(width, align, self);
Ok(queue!(
w,
Print(FmtInline {
skin: self,
composite: fc,
})
)?)
}
pub fn write_inline_on<W: Write>(&self, w: &mut W, src: &str) -> Result<()> {
Ok(queue!(w, Print(self.inline(src)))?)
}
pub fn write_text_on<W: Write>(&self, w: &mut W, src: &str) -> Result<()> {
Ok(queue!(w, Print(self.term_text(src)))?)
}
pub fn write_inline(&self, src: &str) -> Result<()> {
let mut w = std::io::stdout();
self.write_inline_on(&mut w, src)?;
w.flush()?;
Ok(())
}
pub fn write_text(&self, src: &str) -> Result<()> {
let mut w = std::io::stdout();
self.write_text_on(&mut w, src)?;
w.flush()?;
Ok(())
}
pub fn write_fmt_composite(
&self,
f: &mut fmt::Formatter<'_>,
fc: &FmtComposite<'_>,
outer_width: Option<usize>,
with_right_completion: bool,
with_margins: bool,
) -> fmt::Result {
let ls = self.line_style(fc.kind);
let (left_margin, right_margin) = if with_margins {
ls.margins_in(outer_width)
} else {
(0, 0)
};
let (lpi, rpi) = fc.completions(); let inner_width = fc.spacing.map_or(fc.visible_length, |sp| sp.width);
let (lpo, rpo) = Spacing::optional_completions(
ls.align,
inner_width + left_margin + right_margin,
outer_width,
);
self.paragraph.repeat_space(f, lpo + left_margin)?;
ls.compound_style.repeat_space(f, lpi)?;
if let CompositeKind::ListItem(depth) = fc.kind {
for _ in 0..depth {
write!(f, "{}", self.paragraph.compound_style.apply_to(' '))?;
}
write!(f, "{}", self.bullet)?;
write!(f, "{}", self.paragraph.compound_style.apply_to(' '))?;
}
if self.list_items_indentation_mode == ListItemsIndentationMode::Block {
if let CompositeKind::ListItemFollowUp(depth) = fc.kind {
for _ in 0..depth+1 {
write!(f, "{}", self.paragraph.compound_style.apply_to(' '))?;
}
write!(f, "{}", self.paragraph.compound_style.apply_to(' '))?;
}
}
if fc.kind == CompositeKind::Quote {
write!(f, "{}", self.quote_mark)?;
write!(f, "{}", self.paragraph.compound_style.apply_to(' '))?;
}
#[cfg(feature = "special-renders")]
for c in &fc.compounds {
if let Some(replacement) = self.special_chars.get(c) {
write!(f, "{}", replacement)?;
} else {
let os = self.compound_style(ls, c);
write!(f, "{}", os.apply_to(c.as_str()))?;
}
}
#[cfg(not(feature = "special-renders"))]
for c in &fc.composite.compounds {
let os = self.compound_style(ls, c);
write!(f, "{}", os.apply_to(c.as_str()))?;
}
ls.compound_style.repeat_space(f, rpi)?;
if with_right_completion {
self.paragraph.repeat_space(f, rpo + right_margin)?;
}
Ok(())
}
pub fn write_fmt_line(
&self,
f: &mut fmt::Formatter<'_>,
line: &FmtLine<'_>,
width: Option<usize>,
with_right_completion: bool,
) -> fmt::Result {
let tbc = &self.table_border_chars;
match line {
FmtLine::Normal(fc) => {
self.write_fmt_composite(f, fc, width, with_right_completion, true)?;
}
FmtLine::TableRow(FmtTableRow { cells }) => {
let tbl_width = 1 + cells.iter().fold(0, |sum, cell| {
if let Some(spacing) = cell.spacing {
sum + spacing.width + 1
} else {
sum + cell.visible_length + 1
}
});
let (lpo, rpo) = Spacing::optional_completions(self.table.align, tbl_width, width);
self.paragraph.repeat_space(f, lpo)?;
for cell in cells {
write!(f, "{}", self.table.compound_style.apply_to(tbc.vertical))?;
self.write_fmt_composite(f, cell, None, false, false)?;
}
write!(f, "{}", self.table.compound_style.apply_to(tbc.vertical))?;
if with_right_completion {
self.paragraph.repeat_space(f, rpo)?;
}
}
FmtLine::TableRule(rule) => {
let tbl_width = 1 + rule.widths.iter().fold(0, |sum, w| sum + w + 1);
let (lpo, rpo) = Spacing::optional_completions(self.table.align, tbl_width, width);
self.paragraph.repeat_space(f, lpo)?;
write!(
f,
"{}",
self.table.compound_style.apply_to(match rule.position {
RelativePosition::Top => tbc.top_left_corner,
RelativePosition::Other => tbc.left_junction,
RelativePosition::Bottom => tbc.bottom_left_corner,
})
)?;
for (idx, &width) in rule.widths.iter().enumerate() {
if idx > 0 {
write!(
f,
"{}",
self.table.compound_style.apply_to(match rule.position {
RelativePosition::Top => tbc.top_junction,
RelativePosition::Other => tbc.cross,
RelativePosition::Bottom => tbc.bottom_junction,
})
)?;
}
self.table.repeat_char(f, tbc.horizontal, width)?;
}
write!(
f,
"{}",
self.table.compound_style.apply_to(match rule.position {
RelativePosition::Top => tbc.top_right_corner,
RelativePosition::Other => tbc.right_junction,
RelativePosition::Bottom => tbc.bottom_right_corner,
})
)?;
if with_right_completion {
self.paragraph.repeat_space(f, rpo)?;
}
}
FmtLine::HorizontalRule => {
if let Some(w) = width {
write!(f, "{}", self.horizontal_rule.repeated(w))?;
}
}
}
Ok(())
}
}