///|
/// terminfo - inspect terminal capabilities and emit their escape
/// sequences, a small tput-like demo for the terminfo library.
///
/// usage: terminfo [--term NAME] COMMAND [ARGS]
///
/// COMMAND is one of:
/// longname the terminal's verbose name
/// cols | lines | colors a numeric capability
/// clear | smcup | rmcup | sgr0 | bold | init | reset
/// the matching escape sequence
/// setaf N | setab N a color-setting sequence
/// cup R C the cursor-positioning sequence
/// cap NAME [N ...] any string capability, with numeric parameters
/// list-terms the built-in terminal entries
/// dump the compiled binary entry, as hex
///
/// The terminal entry for TERM comes from the built-in table
/// (xterm-256color, xterm, linux, vt100, screen, ansi, adm3a). TERM is
/// taken from --term, then the TERM environment variable, then
/// xterm-256color.
///|
/// One built-in entry: a names line and a comma-separated fields line.
fn entry_line(names : String, fields : String) -> String {
names + "\n\t" + fields
}
///|
/// The built-in terminal table, written in the terminfo source language.
fn builtin_source() -> String {
[
entry_line(
"xterm-256color|xterm with 256 colors", "am, bce, km, mc5i, mir, msgr, npc, xenl, colors#256, cols#80, it#8, lines#24, pairs#32767, acsc=``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, bel=^G, blink=\\E[5m, bold=\\E[1m, civis=\\E[?25l, clear=\\E[H\\E[2J, cnorm=\\E[?12l\\E[?25h, cr=^M, csr=\\E[%i%p1%d;%p2%dr, cub1=^H, cud1=\\E[B, cuf1=\\E[C, cup=\\E[%i%p1%d;%p2%dH, cuu1=\\E[A, dch1=\\E[P, dl1=\\E[M, ed=\\E[J, el=\\E[K, el1=\\E[1K, flash=\\E[?5h$<100/>\\E[?5l, home=\\E[H, hpa=\\E[%i%p1%dG, ht=^I, hts=\\EH, ich1=\\E[@, il1=\\E[L, ind=^J, invis=\\E[8m, is2=\\E[!p\\E[?3;4l\\E[4l\\E>, kbs=^?, kcub1=\\EOD, kcud1=\\EOB, kcuf1=\\EOC, kcuu1=\\EOA, kdch1=\\E[3~, kend=\\EOF, kf1=\\EOP, kf10=\\E[21~, kf2=\\EOQ, kf3=\\EOR, kf4=\\EOS, khome=\\EOH, kich1=\\E[2~, kmous=\\E[M, knp=\\E[6~, kpp=\\E[5~, op=\\E[39;49m, rc=\\E8, rev=\\E[7m, ri=\\EM, rmacs=\\E(B, rmcup=\\E[?1049l, rmir=\\E[4l, rmkx=\\E[?1l\\E>, rmso=\\E[27m, rmul=\\E[24m, rs1=\\Ec, rs2=\\E[!p\\E[?3;4l\\E[4l\\E>, sc=\\E7, setab=\\E[4%p1%dm, setaf=\\E[3%p1%dm, sgr=\\E[%?%p1%t7;%;%?%p2%t4;%;%?%p3%t7;%;%?%p4%t5;%;%?%p6%t1;%;m, sgr0=\\E(B\\E[m, smacs=\\E(0, smcup=\\E[?1049h, smir=\\E[4h, smkx=\\E[?1h\\E=, smso=\\E[7m, smul=\\E[4m, tbc=\\E[3g,",
),
entry_line(
"xterm|xterm terminal emulator (X Window System)", "am, bce, km, mc5i, mir, msgr, npc, xenl, colors#8, cols#80, it#8, lines#24, pairs#64, acsc=``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, bel=^G, blink=\\E[5m, bold=\\E[1m, civis=\\E[?25l, clear=\\E[H\\E[2J, cnorm=\\E[?12l\\E[?25h, cr=^M, csr=\\E[%i%p1%d;%p2%dr, cub1=^H, cud1=\\E[B, cuf1=\\E[C, cup=\\E[%i%p1%d;%p2%dH, cuu1=\\E[A, dch1=\\E[P, dl1=\\E[M, ed=\\E[J, el=\\E[K, home=\\E[H, ht=^I, hts=\\EH, ich1=\\E[@, il1=\\E[L, ind=^J, kbs=^?, kcub1=\\EOD, kcud1=\\EOB, kcuf1=\\EOC, kcuu1=\\EOA, kdch1=\\E[3~, kend=\\EOF, kf1=\\EOP, kf10=\\E[21~, kf2=\\EOQ, kf3=\\EOR, kf4=\\EOS, khome=\\EOH, kich1=\\E[2~, knp=\\E[6~, kpp=\\E[5~, op=\\E[39;49m, rc=\\E8, rev=\\E[7m, ri=\\EM, rmacs=\\E(B, rmcup=\\E[?1049l, rmir=\\E[4l, rmkx=\\E[?1l\\E>, rmso=\\E[27m, rmul=\\E[24m, rs1=\\Ec, rs2=\\E[!p\\E[?3;4l\\E[4l\\E>, sc=\\E7, setab=\\E[4%p1%dm, setaf=\\E[3%p1%dm, sgr=\\E[%?%p1%t7;%;%?%p2%t4;%;%?%p3%t7;%;%?%p4%t5;%;%?%p6%t1;%;m, sgr0=\\E(B\\E[m, smacs=\\E(0, smcup=\\E[?1049h, smir=\\E[4h, smkx=\\E[?1h\\E=, smso=\\E[7m, smul=\\E[4m, tbc=\\E[3g,",
),
entry_line(
"linux|linux console", "am, bce, eo, mir, msgr, xenl, xon, colors#8, cols#80, it#8, lines#25, pairs#64, acsc=+\\020\\,\\021-\\030.^Y0\\333`\\004a\\261f\\370g\\361h\\260i\\316j\\331k\\277l\\332m\\300n\\305o~p\\304q\\304r\\304s_t\\303u\\264v\\301w\\302x\\263y\\363z\\362{\\343|\\330}\\234~\\376, bel=^G, clear=\\E[H\\E[J, cr=^M, csr=\\E[%i%p1%d;%p2%dr, cub1=^H, cud1=^J, cuf1=\\E[C, cup=\\E[%i%p1%d;%p2%dH, cuu1=\\E[A, dch1=\\E[P, dim=\\E[2m, dl1=\\E[M, ed=\\E[J, el=\\E[K, flash=\\E[?5h\\E[?5l$<200/>, home=\\E[H, hpa=\\E[%i%p1%dG, ht=^I, hts=\\EH, ich1=\\E[@, il1=\\E[L, ind=^J, kb2=\\E[G, kbs=^?, kcub1=\\E[D, kcud1=\\E[B, kcuf1=\\E[C, kcuu1=\\E[A, kdch1=\\E[3~, kend=\\E[4~, kf1=\\E[[A, kf10=\\E[21~, kf2=\\E[[B, kf3=\\E[[C, kf4=\\E[[D, kf5=\\E[[E, kf6=\\E[17~, kf7=\\E[18~, kf8=\\E[19~, kf9=\\E[20~, khome=\\E[1~, kich1=\\E[2~, knp=\\E[6~, kpp=\\E[5~, kspd=^Z, nel=^M^J, op=\\E[39;49m, rc=\\E8, rev=\\E[7m, ri=\\EM, rmacs=^O, rmso=\\E[27m, rmul=\\E[24m, sc=\\E7, setab=\\E[4%p1%dm, setaf=\\E[3%p1%dm, sgr0=\\E[m\\017, smacs=^N, smso=\\E[7m, smul=\\E[4m, tbc=\\E[3g, vpa=\\E[%i%p1%dd,",
),
entry_line(
"vt100|vt100-am|dec vt100 (w/advanced video)", "am, mc5i, msgr, xenl, xon, cols#80, it#8, lines#24, vt#3, acsc=``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, bel=^G, blink=\\E[5m$<2>, bold=\\E[1m$<2>, clear=\\E[H\\E[J$<50>, cr=^M, csr=\\E[%i%p1%d;%p2%dr, cub=\\E[%p1%dD, cub1=^H, cud=\\E[%p1%dB, cud1=^J, cuf=\\E[%p1%dC, cuf1=\\E[C$<2>, cup=\\E[%i%p1%d;%p2%dH$<5>, cuu=\\E[%p1%dA, cuu1=\\E[A$<2>, ed=\\E[J$<50>, el=\\E[K$<3>, el1=\\E[1K$<3>, enacs=\\E(B\\E)0, home=\\E[H, ht=^I, hts=\\EH, ind=^J, ka1=\\EOq, ka3=\\EOs, kb2=\\EOr, kbs=^H, kc1=\\EOp, kc3=\\EOn, kcub1=\\EOD, kcud1=\\EOB, kcuf1=\\EOC, kcuu1=\\EOA, kent=\\EOM, kf0=\\EOy, kf1=\\EOP, kf10=\\EOx, kf2=\\EOQ, kf3=\\EOR, kf4=\\EOS, kf5=\\EOt, kf6=\\EOu, kf7=\\EOv, kf8=\\EOl, kf9=\\EOw, rc=\\E8, rev=\\E[7m$<2>, ri=\\EM$<5>, rmacs=^O, rmam=\\E[?7l, rmkx=\\E[?1l\\E>, rmso=\\E[m$<2>, rmul=\\E[m$<2>, rs2=\\E>\\E[?3l\\E[?4l\\E[?5l\\E[?7h\\E[?8h, sc=\\E7, sgr=\\E[0%?%p1%p6%|%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;m%?%p9%t\\016%e\\017%;$<2>, sgr0=\\E[m\\017$<2>, smacs=^N, smam=\\E[?7h, smkx=\\E[?1h\\E=, smso=\\E[7m$<2>, smul=\\E[4m$<2>, tbc=\\E[3g,",
),
entry_line(
"screen|VT 100/ANSI X3.64 virtual terminal", "am, km, mir, msgr, xenl, colors#8, cols#80, it#8, lines#24, pairs#64, acsc=++\\,\\,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, bel=^G, blink=\\E[5m, bold=\\E[1m, civis=\\E[?25l, clear=\\E[H\\E[J, cnorm=\\E[34h\\E[?25h, cr=^M, csr=\\E[%i%p1%d;%p2%dr, cub=\\E[%p1%dD, cub1=^H, cud=\\E[%p1%dB, cud1=^J, cuf=\\E[%p1%dC, cuf1=\\E[C, cup=\\E[%i%p1%d;%p2%dH, cuu=\\E[%p1%dA, cuu1=\\EM, dch=\\E[%p1%dP, dch1=\\E[P, dl=\\E[%p1%dM, dl1=\\E[M, ed=\\E[J, el=\\E[K, el1=\\E[1K, enacs=\\E(B\\E)0, home=\\E[H, ht=^I, hts=\\EH, ich=\\E[%p1%d@, il=\\E[%p1%dL, il1=\\E[L, ind=^J, kbs=^?, kcub1=\\EOD, kcud1=\\EOB, kcuf1=\\EOC, kcuu1=\\EOA, kdch1=\\E[3~, kend=\\E[4~, kf1=\\EOP, kf10=\\E[21~, kf2=\\EOQ, kf3=\\EOR, kf4=\\EOS, kf5=\\E[15~, kf6=\\E[17~, kf7=\\E[18~, kf8=\\E[19~, kf9=\\E[20~, khome=\\E[1~, kich1=\\E[2~, knp=\\E[6~, kpp=\\E[5~, nel=\\EE, op=\\E[39;49m, rc=\\E8, rev=\\E[7m, ri=\\EM, rmacs=^O, rmcup=\\E[?1049l, rmir=\\E[4l, rmkx=\\E[?1l\\E>, rmso=\\E[23m, rmul=\\E[24m, rs2=\\Ec\\E[?1000l\\E[?25h, sc=\\E7, setab=\\E[4%p1%dm, setaf=\\E[3%p1%dm, sgr=\\E[0%?%p6%t;1%;%?%p1%t;7%;%?%p2%t;4%;%?%p3%t;7%;%?%p4%t;5%;%?%p5%t;2%;m%?%p9%t\\016%e\\017%;, sgr0=\\E[m\\017, smacs=^N, smcup=\\E[?1049h, smir=\\E[4h, smkx=\\E[?1h\\E=, smso=\\E[3m, smul=\\E[4m, tbc=\\E[3g,",
),
entry_line(
"ansi|ansi/pc-term compatible with color", "am, mc5i, mir, msgr, colors#8, cols#80, it#8, lines#24, pairs#64, acsc=+\\020\\,\\021-\\030.^Y0\\333`\\004a\\261f\\370g\\361h\\260j\\331k\\277l\\332m\\300n\\305o~q\\304r\\304s_t\\303u\\264v\\301w\\302x\\263y\\363z\\362{\\343|\\330}\\234~\\376, bel=^G, blink=\\E[5m, bold=\\E[1m, clear=\\E[H\\E[J, cr=^M, cub1=\\E[D, cud1=\\E[B, cuf1=\\E[C, cup=\\E[%i%p1%d;%p2%dH, cuu1=\\E[A, dch1=\\E[P, dl1=\\E[M, ed=\\E[J, el=\\E[K, home=\\E[H, ht=^I, hts=\\EH, ich1=\\E[@, il1=\\E[L, ind=\\E[S, kbs=^H, kcub1=\\E[D, kcud1=\\E[B, kcuf1=\\E[C, kcuu1=\\E[A, khome=\\E[H, rc=\\E[u, rev=\\E[7m, ri=\\E[T, rmso=\\E[m, rmul=\\E[m, sc=\\E[s, setab=\\E[4%p1%dm, setaf=\\E[3%p1%dm, sgr0=\\E[0;10m, smso=\\E[7m, smul=\\E[4m, tbc=\\E[2g,",
),
entry_line(
"adm3a|lsi adm3a", "am, cols#80, lines#24, bel=^G, clear=\\032$<1>, cr=^M, cub1=^H, cud1=^J, cuf1=^L, cup=\\E=%p1%{32}%+%c%p2%{32}%+%c, cuu1=^K, home=^^, ind=^J,",
),
].join("\n")
}
///|
fn parse_int(s : String) -> Int? {
let n = s.length()
if n == 0 {
return None
}
let mut i = 0
let mut neg = false
if s[0] == '-' || s[0] == '+' {
neg = s[0] == '-'
i += 1
}
if i >= n {
return None
}
let mut v = 0
while i < n {
let c = s[i]
if c < '0' || c > '9' {
return None
}
v = v * 10 + (c.to_int() - b'0'.to_int())
i += 1
}
Some(if neg { -v } else { v })
}
///|
fn collect_params(args : Array[String], from : Int) -> Array[Int] {
let out : Array[Int] = []
for j = from; j < args.length(); j = j + 1 {
match parse_int(args[j]) {
Some(v) => out.push(v)
None => {
println("bad number: \{args[j]}")
return []
}
}
}
out
}
///|
fn find_entry(entries : Array[@lib.SourceEntry], term : String) -> @lib.Entry? {
for es in entries {
if es.names[0] == term {
return Some(@lib.compile(entries, term)) catch {
err => {
println("cannot compile \{term}: \{(err : Error).to_string()}")
None
}
}
}
}
// TERM values like screen-256color: retry with the color suffix dropped.
if term.has_suffix("-256color") {
return find_entry(entries, term[0:term.length() - 8].to_owned())
}
None
}
///|
/// Write raw capability bytes to stdout. Each byte is mapped to its Latin-1
/// code point so the UTF-8 output reproduces ASCII escape sequences
/// byte-for-byte, while high bytes render as the matching glyph on UTF-8
/// terminals.
fn print_raw(bytes : Bytes) -> Unit {
let chars : Array[Char] = []
for b in bytes {
chars.push(b.to_int().unsafe_to_char())
}
println(String::from_array(chars))
}
///|
fn emit(entry : @lib.Entry, cap : String, params : Array[Int]) -> Unit {
match entry.get_str(cap) {
None => println("no such capability: \{cap}")
Some(raw) =>
print_raw(@lib.tparm(raw, params)) catch {
e => println("cannot expand \{cap}: \{(e : Error).to_string()}")
}
}
}
///|
fn print_num(entry : @lib.Entry, cap : String) -> Unit {
match entry.get_num(cap) {
Some(v) => println(v)
None => println("no such capability: \{cap}")
}
}
///|
fn init_reset(entry : @lib.Entry, reset : Bool) -> Unit {
if reset {
emit(entry, "rs1", [])
emit(entry, "rs2", [])
emit(entry, "rs3", [])
emit(entry, "rmcup", [])
emit(entry, "sgr0", [])
} else {
emit(entry, "is1", [])
emit(entry, "is2", [])
emit(entry, "is3", [])
}
emit(entry, "clear", [])
}
///|
fn hex_digit(d : Int) -> Char {
match d {
0 => '0'
1 => '1'
2 => '2'
3 => '3'
4 => '4'
5 => '5'
6 => '6'
7 => '7'
8 => '8'
9 => '9'
10 => 'a'
11 => 'b'
12 => 'c'
13 => 'd'
14 => 'e'
_ => 'f'
}
}
///|
fn hex_of(bytes : Bytes) -> String {
let buf = StringBuilder(size_hint=bytes.length() * 3)
for i = 0; i < bytes.length(); i = i + 1 {
if i > 0 {
buf.write_char(' ')
}
let b = bytes[i].to_int()
buf.write_char(hex_digit(b / 16))
buf.write_char(hex_digit(b % 16))
}
buf.to_string()
}
///|
fn usage() -> Unit {
println("usage: terminfo [--term NAME] COMMAND [ARGS]")
println("commands: longname, cols, lines, colors, clear, smcup, rmcup,")
println(" sgr0, bold, init, reset, setaf N, setab N, cup R C,")
println(" cap NAME [N ...], list-terms, dump")
}
///|
fn main {
let args = @env.args()
let mut i = 1
let mut term_opt : String? = None
while i < args.length() &&
args[i].length() >= 2 &&
args[i][0:2].to_owned() == "--" {
let flag = args[i]
if flag == "--term" && i + 1 < args.length() {
term_opt = Some(args[i + 1])
i += 2
} else if flag.length() > 7 && flag[0:7].to_owned() == "--term=" {
term_opt = Some(flag[7:].to_owned())
i += 1
} else {
break
}
}
let cmd = if i < args.length() { args[i] } else { "" }
let entries = @lib.parse_source(builtin_source()) catch {
e => {
println("built-in table failed to parse: \{(e : Error).to_string()}")
return
}
}
let term = match term_opt {
Some(t) => t
None =>
match @env.get_env_var("TERM") {
Some(t) => t
None => "xterm-256color"
}
}
let entry = match find_entry(entries, term) {
Some(e) => e
None => {
println("no built-in entry for TERM=\{term}; built-in entries:")
for es in entries {
println(" \{es.names[0]}")
}
return
}
}
match cmd {
"longname" => println(entry.longname())
"cols" => print_num(entry, "columns")
"lines" => print_num(entry, "lines")
"colors" => print_num(entry, "colors")
"clear" => emit(entry, "clear", [])
"smcup" => emit(entry, "smcup", [])
"rmcup" => emit(entry, "rmcup", [])
"sgr0" => emit(entry, "sgr0", [])
"bold" => emit(entry, "bold", [])
"setaf" => emit(entry, "setaf", collect_params(args, i + 1))
"setab" => emit(entry, "setab", collect_params(args, i + 1))
"cup" => emit(entry, "cup", collect_params(args, i + 1))
"cap" =>
if i + 1 < args.length() {
emit(entry, args[i + 1], collect_params(args, i + 2))
} else {
println("cap: missing capability name")
}
"init" => init_reset(entry, false)
"reset" => init_reset(entry, true)
"list-terms" =>
for es in entries {
println(es.names[0])
}
"dump" =>
println(hex_of(@lib.to_bytes(entry))) catch {
e => println("dump failed: \{(e : Error).to_string()}")
}
_ => usage()
}
}