///|
/// Line style for drawn table borders.
pub(all) enum LineStyle {
Ascii
Unicode
} derive(Eq)
pub extend LineStyle with Eq::{not_equal, equal}
///|
/// 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,
halfwidth_punct? : Bool = false,
) -> String {
blocks
.map(b => {
term_render_block(b, line_style~, ansi~, disable_math~, halfwidth_punct~)
})
.filter(s => !s.is_empty())
.join("\n\n")
}
///|
fn term_render_block(
block : @parsing.Block,
line_style~ : LineStyle,
ansi~ : Bool,
disable_math~ : Bool,
halfwidth_punct~ : 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~,
halfwidth_punct~,
)
term_style(
content,
ansi~,
color~,
modifier=@chalk.Modifier::Bold,
modifier2=@chalk.Modifier::Underline,
)
}
Paragraph(inlines) =>
term_render_inlines(inlines, ansi~, disable_math~, halfwidth_punct~)
Blockquote(blocks) => {
let rendered = blocks
.map(b => {
term_render_block(
b,
line_style~,
ansi~,
disable_math~,
halfwidth_punct~,
)
})
.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~,
halfwidth_punct~,
)
OrderedList(items) =>
term_render_ordered_list(
items,
line_style~,
ansi~,
disable_math~,
halfwidth_punct~,
)
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~,
halfwidth_punct~,
)
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~,
halfwidth_punct~,
)
})
.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,
halfwidth_punct~ : Bool,
) -> String {
items
.map(i => {
term_render_list_item(
i,
"•",
line_style~,
ansi~,
disable_math~,
halfwidth_punct~,
)
})
.join("\n")
}
///|
fn term_render_ordered_list(
items : Array[@parsing.MdListItem],
line_style~ : LineStyle,
ansi~ : Bool,
disable_math~ : Bool,
halfwidth_punct~ : 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~,
halfwidth_punct~,
)
let children = item.children
.map(b => {
term_render_block(b, line_style~, ansi~, disable_math~, halfwidth_punct~)
})
.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,
halfwidth_punct~ : Bool,
) -> String {
let header_cells = header.cells.map(c => {
term_render_inlines(c, ansi~, disable_math~, halfwidth_punct~)
})
let body_cells = rows.map(r => {
r.cells.map(c => {
term_render_inlines(c, ansi~, disable_math~, halfwidth_punct~)
})
})
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 {
let buf = StringBuilder()
for i in 0.. 0 { inline_last_char(inlines[i - 1]) } else { prev }
let next_c = if i + 1 < inlines.length() {
inline_first_char(inlines[i + 1])
} else {
next
}
let convert = halfwidth_punct
buf.write_string(
term_render_inline(
inlines[i],
ansi~,
disable_math~,
convert~,
prev=prev_c,
next=next_c,
),
)
}
buf.to_string()
}
///|
fn term_render_inline(
inline : @parsing.Inline,
ansi~ : Bool,
disable_math~ : Bool,
convert~ : Bool,
prev~ : Char?,
next~ : Char?,
) -> String {
match inline {
Plain(text) => if convert { to_halfwidth_punct(text, prev~, next~) } else { text }
Strong(inlines) =>
term_style(
term_render_inlines(
inlines,
ansi~,
disable_math~,
halfwidth_punct=convert,
prev~,
next~,
),
ansi~,
modifier=@chalk.Modifier::Bold,
)
Emph(inlines) =>
term_style(
term_render_inlines(
inlines,
ansi~,
disable_math~,
halfwidth_punct=convert,
prev~,
next~,
),
ansi~,
modifier=@chalk.Modifier::Italic,
)
Code(text) =>
term_style(text, ansi~, background=@chalk.BackgroundColor::BgWhiteBright)
Image(alt, _url) =>
term_style(
"[img: \{if convert { to_halfwidth_punct(alt) } else { 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)
}
}