///|
/// Python's `terminal.TERMINAL_COLORS`: (light background, dark background)
/// color names per token type.
pub fn terminal_colors() -> Map[@token.TokenType, (String, String)] {
Map([
(@token.token, ("", "")),
(@token.whitespace, ("gray", "brightblack")),
(@token.comment, ("gray", "brightblack")),
(@token.comment_preproc, ("cyan", "brightcyan")),
(@token.keyword, ("blue", "brightblue")),
(@token.keyword_type, ("cyan", "brightcyan")),
(@token.operator_word, ("magenta", "brightmagenta")),
(@token.name_builtin, ("cyan", "brightcyan")),
(@token.name_function, ("green", "brightgreen")),
(@token.name_namespace, ("_cyan_", "_brightcyan_")),
(@token.name_class, ("_green_", "_brightgreen_")),
(@token.name_exception, ("cyan", "brightcyan")),
(@token.name_decorator, ("brightblack", "gray")),
(@token.name_variable, ("red", "brightred")),
(@token.name_constant, ("red", "brightred")),
(@token.name_attribute, ("cyan", "brightcyan")),
(@token.name_tag, ("brightblue", "brightblue")),
(@token.string, ("yellow", "yellow")),
(@token.number, ("blue", "brightblue")),
(@token.generic_deleted, ("brightred", "brightred")),
(@token.generic_inserted, ("green", "brightgreen")),
(@token.generic_heading, ("**", "**")),
(@token.generic_subheading, ("*magenta*", "*brightmagenta*")),
(@token.generic_prompt, ("**", "**")),
(@token.generic_error, ("brightred", "brightred")),
(@token.error, ("_brightred_", "_brightred_")),
])
}
///|
/// Metadata of Python's `TerminalFormatter`.
pub let terminal_info : FormatterInfo = {
class_name: "TerminalFormatter",
name: "Terminal",
aliases: ["terminal", "console"],
filenames: [],
description: "Format tokens with ANSI color sequences, for output in a text console. Color sequences are terminated at newlines, so that paging the output works correctly.",
}
///|
/// The color of the nearest ancestor of `ttype` in `scheme`.
fn scheme_color(
scheme : Map[@token.TokenType, (String, String)],
ttype : @token.TokenType,
) -> (String, String) {
let mut t = ttype
while true {
match scheme.get(t) {
Some(c) => return c
None =>
match t.parent() {
Some(p) => t = p
None => return ("", "")
}
}
}
("", "")
}
///|
/// Python's `TerminalFormatter` (options `bg` = `light`/`dark`, `linenos`;
/// `colorscheme` is the `colorscheme` option, which cannot be given as a
/// string). As in Python, the line counter continues across `format` calls.
pub fn terminal_formatter(
options? : @lexer.Options = Map([]),
style? : @styles.Style,
colorscheme? : Map[@token.TokenType, (String, String)],
) -> Formatter raise {
let base = BaseOptions::new(options, style?)
let darkbg = @lexer.get_choice_opt(options, "bg", ["light", "dark"], "light") ==
"dark"
let scheme = match colorscheme {
Some(s) if s.length() > 0 => s
_ => terminal_colors()
}
let linenos = truthy_opt(options, "linenos")
let lineno = Ref(0)
let write_lineno = (buf : StringBuilder) => {
lineno.val += 1
buf.write_string(if lineno.val != 1 { "\n" } else { "" })
buf.write_string(@pystr.rjust(lineno.val.to_string(), 4, fill='0'))
buf.write_string(": ")
}
Formatter::new(terminal_info, base, tokens => {
let buf = StringBuilder()
if linenos {
write_lineno(buf)
}
for tok in tokens {
let (ttype, value) = tok
let colors = scheme_color(scheme, ttype)
let color = if darkbg { colors.1 } else { colors.0 }
for line in @pystr.splitlines(value, keepends=true) {
let text = @pystr.rstrip_newlines(line)
if color != "" {
buf.write_string(ansiformat(color, text))
} else {
buf.write_string(text)
}
if line.has_suffix("\n") {
if linenos {
write_lineno(buf)
} else {
buf.write_string("\n")
}
}
}
}
if linenos {
buf.write_string("\n")
}
buf.to_string()
})
}