///|
/// Line style for drawn table borders.
pub(all) enum LineStyle {
Ascii
Unicode
} derive(Eq)
///|
/// The horizontal line character for a table under the given style.
fn line_style_h_bar(style : LineStyle) -> String {
match style {
Ascii => "-"
Unicode => "─"
}
}
///|
/// The crossing line character for a table under the given style.
fn line_style_cross(style : LineStyle) -> String {
match style {
Ascii => "+"
Unicode => "┼"
}
}
///|
/// The vertical line character for a table under the given style.
fn line_style_v_bar(style : LineStyle) -> String {
match style {
Ascii => "|"
Unicode => "│"
}
}
///|
/// Apply ANSI styling to `text`, or return it unchanged when `ansi` is off.
fn term_style(
text : String,
ansi~ : Bool,
color? : @chalk.Color,
background? : @chalk.BackgroundColor,
modifier? : @chalk.Modifier,
modifier2? : @chalk.Modifier,
) -> String {
if !ansi {
return text
}
let mut chalk = @chalk.chalk()
if color is Some(c) {
chalk = chalk.color(c)
}
if background is Some(b) {
chalk = chalk.background(b)
}
if modifier is Some(m) {
chalk = chalk.modifier(m)
}
if modifier2 is Some(m2) {
chalk = chalk.modifier(m2)
}
chalk.render(text)
}
///|
pub fn render_terminal_document(
blocks : Array[@parsing.Block],
line_style? : LineStyle = Unicode,
ansi? : Bool = true,
disable_math? : Bool = false,
) -> String {
blocks
.map(b => term_render_block(b, line_style~, ansi~, disable_math~))
.filter(s => !s.is_empty())
.join("\n\n")
}
///|
fn term_render_block(
block : @parsing.Block,
line_style~ : LineStyle,
ansi~ : Bool,
disable_math~ : Bool,
) -> String {
match block {
Heading(level, inlines) => {
let color = match level {
1 => @chalk.Color::Red
2 => @chalk.Color::Yellow
3 => @chalk.Color::Green
_ => @chalk.Color::White
}
let content = term_render_inlines(inlines, ansi~, disable_math~)
term_style(
content,
ansi~,
color~,
modifier=@chalk.Modifier::Bold,
modifier2=@chalk.Modifier::Underline,
)
}
Paragraph(inlines) => term_render_inlines(inlines, ansi~, disable_math~)
Blockquote(blocks) => {
let rendered = blocks
.map(b => term_render_block(b, line_style~, ansi~, disable_math~))
.join("\n\n")
let lines = rendered.split("\n")
let joined = lines.map(fn(line) { "│ \{line}" }).join("\n")
term_style(
joined,
ansi~,
color=@chalk.Color::WhiteBright,
modifier=@chalk.Modifier::Italic,
)
}
UnorderedList(items) =>
term_render_list(items, line_style~, ansi~, disable_math~)
OrderedList(items) =>
term_render_ordered_list(items, line_style~, ansi~, disable_math~)
FencedCode(_, code) =>
term_style(
code.trim_end().to_owned(),
ansi~,
background=@chalk.BackgroundColor::BgWhiteBright,
modifier=@chalk.Modifier::Dim,
)
ThematicBreak =>
term_style("─".repeat(32), ansi~, modifier=@chalk.Modifier::Dim)
Table(header, rows) =>
term_render_table(header, rows, line_style~, ansi~, disable_math~)
DisplayMath(latex) =>
if disable_math {
"$$\{latex}$$"
} else {
render_math(latex, true, target=Unicode)
}
FootnoteDef(label, blocks) => {
let defined = blocks
.map(b => term_render_block(b, line_style~, ansi~, disable_math~))
.join("\n\n")
let sb = StringBuilder()
sb <+ "[\{label}]: \{defined}"
sb.to_string()
}
}
}
///|
fn term_render_list(
items : Array[@parsing.MdListItem],
line_style~ : LineStyle,
ansi~ : Bool,
disable_math~ : Bool,
) -> String {
items
.map(i => term_render_list_item(i, "•", line_style~, ansi~, disable_math~))
.join("\n")
}
///|
fn term_render_ordered_list(
items : Array[@parsing.MdListItem],
line_style~ : LineStyle,
ansi~ : Bool,
disable_math~ : Bool,
) -> String {
let rendered = Array::new()
for i in 0.. String {
let marker = match item.checked {
Some(true) =>
term_style(
"✔",
ansi~,
color=@chalk.Color::Green,
modifier=@chalk.Modifier::Bold,
)
Some(false) => term_style("○", ansi~, color=@chalk.Color::WhiteBright)
None => marker
}
let content = term_render_inlines(item.content, ansi~, disable_math~)
let children = item.children
.map(b => term_render_block(b, line_style~, ansi~, disable_math~))
.join("\n\n")
let indented_children = children
.split("\n")
.map(fn(line) { " \{line}" })
.join("\n")
let result = StringBuilder()
result <+ "\{marker} \{content}"
if !children.is_empty() {
result.write_string("\n")
result <+ "\{indented_children}"
}
result.to_string()
}
///|
fn term_render_table(
header : @parsing.TableRow,
rows : Array[@parsing.TableRow],
line_style~ : LineStyle,
ansi~ : Bool,
disable_math~ : Bool,
) -> String {
let header_cells = header.cells.map(c => {
term_render_inlines(c, ansi~, disable_math~)
})
let body_cells = rows.map(r => {
r.cells.map(c => term_render_inlines(c, ansi~, disable_math~))
})
let col_count = header_cells.length()
let widths = term_calculate_widths(header_cells, body_cells, col_count)
let header_row = term_table_row_internal(
header_cells,
widths,
@chalk.Color::Blue,
@chalk.Modifier::Bold,
line_style~,
ansi~,
)
let h_bar = line_style_h_bar(line_style)
let cross = line_style_cross(line_style)
let separator = term_style(
widths.map(fn(w) { h_bar.repeat(w) }).join(cross),
ansi~,
modifier=@chalk.Modifier::Dim,
)
.trim()
.to_owned()
let body_rows = body_cells
.map(r => {
term_table_row_internal(
r,
widths,
@chalk.Color::White,
@chalk.Modifier::Reset,
line_style~,
ansi~,
)
})
.join("\n")
let sb = StringBuilder()
sb <+ "\{header_row}"
sb.write_string("\n")
sb <+ "\{separator}"
sb.write_string("\n")
sb <+ "\{body_rows}"
sb.to_string()
}
///|
fn term_calculate_widths(
header : Array[String],
body : Array[Array[String]],
count : Int,
) -> Array[Int] {
let widths = Array::make(count, 0)
for i in 0.. widths[i] {
widths[i] = row[i].length()
}
}
}
widths
}
///|
fn term_table_row_internal(
cells : Array[String],
widths : Array[Int],
color : @chalk.Color,
modifier : @chalk.Modifier,
line_style~ : LineStyle,
ansi~ : Bool,
) -> String {
let parts = Array::new()
for i in 0.. String {
inlines.map(i => term_render_inline(i, ansi~, disable_math~)).join("")
}
///|
fn term_render_inline(
inline : @parsing.Inline,
ansi~ : Bool,
disable_math~ : Bool,
) -> String {
match inline {
Plain(text) => text
Strong(inlines) =>
term_style(
term_render_inlines(inlines, ansi~, disable_math~),
ansi~,
modifier=@chalk.Modifier::Bold,
)
Emph(inlines) =>
term_style(
term_render_inlines(inlines, ansi~, disable_math~),
ansi~,
modifier=@chalk.Modifier::Italic,
)
Code(text) =>
term_style(text, ansi~, background=@chalk.BackgroundColor::BgWhiteBright)
Link(inlines, url) =>
term_style(
"\{term_render_inlines(inlines, ansi~, disable_math~)} (\{url})",
ansi~,
color=@chalk.Color::Blue,
modifier=@chalk.Modifier::Underline,
)
Image(alt, _url) =>
term_style(
"[img: \{alt}]",
ansi~,
color=@chalk.Color::Yellow,
modifier=@chalk.Modifier::Italic,
)
SoftBreak => "\n"
HardBreak => "\n"
InlineMath(latex) =>
if disable_math {
"$\{latex}$"
} else {
render_math(latex, false, target=Unicode)
}
FootnoteRef(label) =>
term_style("[\{label}]", ansi~, color=@chalk.Color::Yellow)
}
}