///|
/// A piece of HTML output: `(1, line)` for a line of highlighted source,
/// `(0, text)` for wrapping markup (Python's generator protocol).
type Piece = (Int, String)
///|
/// Python's `_format_lines`: the highlighted lines, without wrapping.
fn HtmlFormatter::format_lines(
self : HtmlFormatter,
tokens : Tokens,
) -> Array[Piece] {
let out : Array[Piece] = []
let nocls = self.noclasses
let lsep = self.lineseparator
let mut lspan = ""
let line : Array[String] = []
for tok in tokens {
let (ttype, value) = tok
let cspan = match self.span_openers.get(ttype) {
Some(s) => s
None => {
let title = if self.debug_token_types {
" title=\"\{ttype_repr_tail(ttype)}\""
} else {
""
}
let s = if nocls {
let css = self.get_css_inline_styles(ttype)
if css != "" {
let st = self.class2style.get(css).map(v => v.0).unwrap_or("")
""
} else {
""
}
} else {
let css = self.get_css_classes(ttype)
if css != "" {
""
} else {
""
}
}
self.span_openers[ttype] = s
s
}
}
let close_c = if cspan != "" { "" } else { "" }
let parts = @pystr.split(@pystr.html_escape(value, quote=false), "\n")
for i in 0..<(parts.length() - 1) {
let part = parts[i]
if line.length() > 0 {
let close_l = if lspan != "" { "" } else { "" }
if lspan != cspan && part != "" {
line.push(close_l)
line.push(cspan)
line.push(part)
line.push(close_c)
line.push(lsep)
} else {
line.push(part)
line.push(close_l)
line.push(lsep)
}
out.push((1, line.join("")))
line.clear()
} else if part != "" {
out.push((1, cspan + part + close_c + lsep))
} else {
out.push((1, lsep))
}
}
let last = parts[parts.length() - 1]
if line.length() > 0 && last != "" {
if lspan != cspan {
line.push(if lspan != "" { "" } else { "" })
line.push(cspan)
line.push(last)
lspan = cspan
} else {
line.push(last)
}
} else if last != "" {
line.clear()
line.push(cspan)
line.push(last)
lspan = cspan
}
}
if line.length() > 0 {
line.push(if lspan != "" { "" } else { "" })
line.push(lsep)
out.push((1, line.join("")))
}
out
}
///|
/// Python's `'%*d' % (width, n)`.
fn pad_num(n : Int, width : Int) -> String {
@pystr.rjust(n.to_string(), width)
}
///|
/// Python's `_wrap_tablelinenos`.
fn HtmlFormatter::wrap_tablelinenos(
self : HtmlFormatter,
inner : Array[Piece],
) -> Array[Piece] raise FormatterError {
let dummy = StringBuilder()
let mut lncount = 0
for p in inner {
if p.0 != 0 {
lncount += 1
}
dummy.write_string(p.1)
}
let fl = self.linenostart
let mw = (lncount + fl - 1).to_string().length()
let sp = self.linenospecial
let st = self.linenostep
let anchor_name = if self.lineanchors != "" {
self.lineanchors
} else {
self.linespans
}
let lines = []
for i = fl; i < fl + lncount; i = i + 1 {
let print_line = py_mod(i, st) == 0
let special_line = sp != 0 && py_mod(i, sp) == 0
let mut line = if print_line {
let l = pad_num(i, mw)
if self.anchorlinenos {
"\{l}"
} else {
l
}
} else {
" ".repeat(mw)
}
let style = if self.noclasses {
if special_line {
" style=\"\{self.linenos_special_style()}\""
} else {
" style=\"\{self.linenos_style()}\""
}
} else if special_line {
" class=\"special\""
} else {
" class=\"normal\""
}
line = "\{line}"
lines.push(line)
}
let ls = lines.join("\n")
let filename_tr = if self.filename != "" {
"| " +
self.filename +
" |
"
} else {
""
}
[
(
0,
"" +
filename_tr +
" | ",
),
(0, " "),
(0, dummy.to_string()),
(0, " "),
(0, " |
"),
]
}
///|
/// Python's `_wrap_inlinelinenos`.
fn HtmlFormatter::wrap_inlinelinenos(
self : HtmlFormatter,
inner : Array[Piece],
) -> Array[Piece] raise FormatterError {
let sp = self.linenospecial
let st = self.linenostep
let mut num = self.linenostart
let mw = (inner.length() + num - 1).to_string().length()
let anchor_name = if self.lineanchors != "" {
self.lineanchors
} else {
self.linespans
}
let out = []
for p in inner {
let print_line = py_mod(num, st) == 0
let special_line = sp != 0 && py_mod(num, sp) == 0
let line = if print_line { pad_num(num, mw) } else { " ".repeat(mw) }
let style = if self.noclasses {
if special_line {
" style=\"\{self.linenos_special_style()}\""
} else {
" style=\"\{self.linenos_style()}\""
}
} else if special_line {
" class=\"linenos special\""
} else {
" class=\"linenos\""
}
let linenos = "\{line}"
if self.anchorlinenos {
out.push((1, "\{linenos}" + p.1))
} else {
out.push((1, linenos + p.1))
}
num += 1
}
out
}
///|
/// Python's `_wrap_lineanchors`.
fn HtmlFormatter::wrap_lineanchors(
self : HtmlFormatter,
inner : Array[Piece],
) -> Array[Piece] {
let s = self.lineanchors
let mut i = self.linenostart - 1
inner.map(p => {
if p.0 != 0 {
i += 1
let href = if self.linenos != 0 { "" } else { " href=\"#\{s}-\{i}\"" }
(1, "" + p.1)
} else {
(0, p.1)
}
})
}
///|
/// Python's `_wrap_linespans`.
fn HtmlFormatter::wrap_linespans(
self : HtmlFormatter,
inner : Array[Piece],
) -> Array[Piece] {
let s = self.linespans
let mut i = self.linenostart - 1
inner.map(p => {
if p.0 != 0 {
i += 1
(1, "\{p.1}")
} else {
(0, p.1)
}
})
}
///|
/// Python's `_wrap_div`.
fn HtmlFormatter::wrap_div(
self : HtmlFormatter,
inner : Array[Piece],
) -> Array[Piece] {
let style = []
if self.noclasses && !self.nobackground {
match self.base.style.background_color {
Some(bg) => style.push("background: \{bg}")
None => ()
}
}
if self.cssstyles != "" {
style.push(self.cssstyles)
}
let style = style.join("; ")
let cls = if self.cssclass != "" { " class=\"\{self.cssclass}\"" } else { "" }
let st = if style != "" { " style=\"\{style}\"" } else { "" }
let out = [(0, "")]
out.append(inner)
out.push((0, "
\n"))
out
}
///|
/// Python's `_wrap_pre`.
fn HtmlFormatter::wrap_pre(
self : HtmlFormatter,
inner : Array[Piece],
) -> Array[Piece] {
let style = []
if self.prestyles != "" {
style.push(self.prestyles)
}
if self.noclasses {
style.push(pre_style)
}
let style = style.join("; ")
let out = []
if self.filename != "" && self.linenos != 1 {
out.push((0, "" + self.filename + ""))
}
let st = if style != "" { " style=\"\{style}\"" } else { "" }
out.push((0, ""))
out.append(inner)
out.push((0, "
"))
out
}
///|
/// Python's `_wrap_code`.
fn wrap_code(inner : Array[Piece]) -> Array[Piece] {
let out = [(0, "")]
out.append(inner)
out.push((0, ""))
out
}
///|
/// Python's `_highlight_lines`.
fn HtmlFormatter::highlight_lines(
self : HtmlFormatter,
inner : Array[Piece],
) -> Array[Piece] {
let out = []
for i, p in inner {
let (t, value) = p
if t != 1 {
out.push((t, value))
}
if self.hl_lines.contains(i + 1) {
if self.noclasses {
let style = match self.base.style.highlight_color {
Some(c) => " style=\"background-color: \{c}\""
None => ""
}
out.push((1, "\{value}"))
} else {
out.push((1, "\{value}"))
}
} else {
out.push((1, value))
}
}
out
}
///|
/// Python's `_wrap_full`. With `cssfile`, the CSS file itself is not
/// written (see `cssfile_contents`).
fn HtmlFormatter::wrap_full(
self : HtmlFormatter,
inner : Array[Piece],
) -> Array[Piece] {
let encoding = self.base.encoding.unwrap_or("None")
let header = if self.cssfile != "" {
html_doc_header_externalcss(self.base.title, self.cssfile, encoding)
} else {
html_doc_header(self.base.title, self.get_style_defs(arg="body"), encoding)
}
let out = [(0, header)]
out.append(inner)
out.push((0, html_doc_footer))
out
}
///|
/// Python's `format`: the HTML for `tokens`.
///
/// ```mbt check
/// test {
/// let f = @formatters.HtmlFormatter::new()
/// inspect(
/// f.format([(@token.keyword, "def"), (@token.text, " f\n")]),
/// content=(
/// #|
/// #|
/// ),
/// )
/// }
/// ```
pub fn HtmlFormatter::format(
self : HtmlFormatter,
tokens : Tokens,
) -> String raise FormatterError {
let mut source = self.format_lines(tokens)
if !self.nowrap && self.linenos == 2 {
source = self.wrap_inlinelinenos(source)
}
if self.hl_lines.length() > 0 {
source = self.highlight_lines(source)
}
if !self.nowrap {
if self.lineanchors != "" {
source = self.wrap_lineanchors(source)
}
if self.linespans != "" {
source = self.wrap_linespans(source)
}
if self.wrapcode {
source = wrap_code(source)
}
source = self.wrap_pre(source)
if self.linenos == 1 {
source = self.wrap_tablelinenos(source)
}
source = self.wrap_div(source)
if self.base.full {
source = self.wrap_full(source)
}
}
let buf = StringBuilder()
for p in source {
buf.write_string(p.1)
}
buf.to_string()
}