summaryrefslogtreecommitdiff
path: root/tui/termfo
diff options
context:
space:
mode:
authorraven <citrons@mondecitronne.com>2026-02-20 13:42:12 -0600
committerraven <citrons@mondecitronne.com>2026-02-20 13:46:59 -0600
commit5b6196ebe67cf954bae8212c1a33b869da723e11 (patch)
treedce33c06621847c3862e64bda914b1e8a450317d /tui/termfo
parent05c068749740f9430d1fda7698c433697eef1652 (diff)
support builtin terminfo
copy termfo into the repository and modify it to embed an xterm terminfo to as a fallback
Diffstat (limited to 'tui/termfo')
-rw-r--r--tui/termfo/LICENSE21
-rw-r--r--tui/termfo/README.md291
-rw-r--r--tui/termfo/caps/caps.go629
-rw-r--r--tui/termfo/caps/table.go640
-rw-r--r--tui/termfo/cmd/termfo/build.go168
-rw-r--r--tui/termfo/cmd/termfo/findcap.go150
-rw-r--r--tui/termfo/cmd/termfo/internal/term/term.go80
-rw-r--r--tui/termfo/cmd/termfo/keyscan.go120
-rw-r--r--tui/termfo/cmd/termfo/lscap.go27
-rw-r--r--tui/termfo/cmd/termfo/lsterm.go55
-rw-r--r--tui/termfo/cmd/termfo/main.go119
-rw-r--r--tui/termfo/cmd/termfo/mkhist.go235
-rw-r--r--tui/termfo/cmd/termfo/oldterm.go328
-rw-r--r--tui/termfo/cmd/termfo/show.go107
-rw-r--r--tui/termfo/keys/key.go169
-rw-r--r--tui/termfo/keys/key_test.go31
-rw-r--r--tui/termfo/keys/keys.go120
-rw-r--r--tui/termfo/load.go368
-rw-r--r--tui/termfo/param.go506
-rw-r--r--tui/termfo/param_test.go111
-rw-r--r--tui/termfo/scaps/scaps.go621
-rwxr-xr-xtui/termfo/term.h.zsh427
-rw-r--r--tui/termfo/termfo.go287
-rw-r--r--tui/termfo/termfo_test.go87
-rw-r--r--tui/termfo/terminfo/xterm-256colorbin0 -> 4071 bytes
25 files changed, 5697 insertions, 0 deletions
diff --git a/tui/termfo/LICENSE b/tui/termfo/LICENSE
new file mode 100644
index 0000000..699f18b
--- /dev/null
+++ b/tui/termfo/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright © Martin Tournoij
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+The software is provided "as is", without warranty of any kind, express or
+implied, including but not limited to the warranties of merchantability,
+fitness for a particular purpose and noninfringement. In no event shall the
+authors or copyright holders be liable for any claim, damages or other
+liability, whether in an action of contract, tort or otherwise, arising
+from, out of or in connection with the software or the use or other dealings
+in the software.
diff --git a/tui/termfo/README.md b/tui/termfo/README.md
new file mode 100644
index 0000000..197d1d2
--- /dev/null
+++ b/tui/termfo/README.md
@@ -0,0 +1,291 @@
+termfo is a terminfo library for Go.
+
+It also has a little `termfo` commandline tool to list, search, and print
+various terminfo things that are not so easy to get with `infocmp` and/or are
+formatted a bit nicer.
+
+Import at `zgo.at/termfo`; API docs: https://godocs.io/zgo.at/termfo
+
+Current status: should be (mostly) usable and complete, but not widely tested
+yet and there are a few rough edges here and there. Also the API may change;
+specifically, I might rename some capability or key constants. For now I want to
+focus on the application I *wanted* to write rather than all this stuff :-)
+
+Usage
+-----
+*Note: you may want to read the "Some background and concepts" section below
+first if you're not already familiar with the basics of terminals and/or
+terminfo, which explains a bit of background that may be useful.*
+
+---
+
+First create a new `termfo.Terminfo` instance; the parameter is the terminal to
+load; it will use the `TERM` environment variable if it's empty, which is what
+you want >99% of the time:
+
+```go
+ti, err := termfo.New("")
+```
+
+Capabilities have three types: bool, number, and string; you can get them from
+the `Bools`, `Numbers`, and `Strings` maps:
+
+```go
+s, ok := ti.Strings[caps.EnterItalicsMode]
+if ok {
+ fmt.Println(s, "Italic text!", ti.GetString(caps.ExitItalicsMode))
+}
+
+n, ok := ti.Numbers[cap.MaxColors] // "max_colors" ("colors")
+if ok {
+ fmt.Printf("Supports %d colours\n")
+}
+
+_, ok := ti.Bools[caps.AutoRightMargin] // "am"; note this only lists values
+ // present, so it's always true.
+if ok {
+ fmt.Println("Has am")
+}
+```
+
+The capabilities themselves are in the `termfo/caps` subpackage as pointers to
+the `caps.Cap` struct, which also contains the short name (e.g. `sitm` for
+`enter_italics_mode`) and the description from terminfo(5). If you have an
+impressive unix beard and managed to memorize all the short codes then you can
+use the `scaps` package:
+
+```
+s, ok := ti.Strings[scaps.Sitm]
+if ok {
+ fmt.Println(s, "Italic text!", ti.GetString(scaps.Ritm))
+}
+```
+
+`sitm` instead of `enter_italics_mode` is just obscure, but having the mapping
+is useful at times, even if only to make it easier to find out what something
+does from looking at constants in C code.
+
+To add parameters use the `Get()` method:
+
+```go
+ti.Get(caps.ParmDeleteLine, 5) // Delete 5 lines
+```
+
+There is also `Put()` to write it, and `Supports()` to check if it's supported.
+
+NOTE: this part of the API still sucks a bit; some of the capabilities at least
+indicate they accept parameters with `Parm`, but some don't, and omitting the
+argument will send nonsense (this should typed). Not yet 100% sure what a nice
+API would look like. Part of the problem is that terminfo files can add
+user-defined extended attributes.
+
+### Keys
+There is some additional processing for keys; the most common way to use this is
+through the `Terminfo.FindKeys()` method; for example:
+
+```go
+ti, _ := termfo.New("")
+
+ch := ti.FindKeys(os.Stdin)
+for e := <-ch; ; e = <-ch {
+ fmt.Println("Pressed", e.Key)
+}
+```
+
+This will keep scanning for keys in stdin. Note that you'll need to put the
+terminal in "raw mode" by sending the appropriate ioctls to send keys without
+having to press Enter. I recommend using the `golang.org/x/term` package for
+this; it's not included here as it pulls in x/sys which is ~8.5M and a somewhat
+large dependency. You can also use `syscall`. See `internal/term` for an example
+of that.
+
+Keys are represented as a `Key`, which is an uint64. The lower 32 bits are used
+as a regular rune, and the remaining 32 for some other information like modifier
+keys. The upshot of this is that you can now use a single value to test for all
+combinations:
+
+ switch Key(0x61) {
+ case 'a': // 'a' w/o modifiers
+ case 'a' | keys.Ctrl: // 'a' with control
+ case 'a' | keys.Ctrl | keys.Shift: // 'a' with shift and control
+
+ case keys.Up: // Arrow up
+ case keys.Up | keys.Ctrl: // Arrow up with control
+ }
+
+Note that keys are always sent as lower-case; use `'a' | keys.Shift` to test for
+upper-case, and control characters are always sent as `'a' | keys.Ctrl` rather
+than 0x01.
+
+### Mouse support
+There is no direct support for this (yet), mostly because I simply don't need
+it.
+
+<!--
+TODO: Need to actually work on this; the current method adds way too much data.
+
+Using embed to add a terminfo database is probably better than compiling them as
+Go files. The "common" list added 1.5M to the binary, which is comparatively a lot.
+
+Compiling in terminfo definitions
+---------------------------------
+Use e.g. `go run ./cmd/termfo build xterm*` to generate a Go file with all
+`xterm*` terminfos present on your system. Add this somewhere in your
+application. This will call `termfo.SetBuiltin()`, after this `New()` will use
+it.
+
+The special name `%common` uses most common terminals in use today.
+
+The upshot of this is that you don't need a `/usr/share/terminfo` on the system,
+and it's a wee bit faster as it won't have to read anything from disk (although
+this should be more than fast enough really).
+-->
+
+Updating
+--------
+Various terminfo data in is generated from the ncurses source with `term.h.zsh`.
+This requires the ncurses source tree.
+
+This requires zsh, awk, and gofmt.
+
+
+Some background and concepts
+----------------------------
+A "terminfo" file is essentially a key/value database to tell applications about
+the properties of the terminal the user is using.
+
+To understand why this is needed you need to understand that terminals – and
+applications that run inside them – are completely text-based. If you press the
+`a` key then the terminal will send exactly the letter `a` the the application,
+and nothing more. There is no such thing as a "key down" and "key up event",
+it's just the byte for `a` that's sent. Special keys like F1, arrow keys, etc.
+are actually multiple characters, usually starting with the 0x1b character (the
+escape character, which I'll write as `\E` henceforth); for example on my system
+F1 sends `\EOP` and the arrow up key sends `\EOA`.
+
+Similarly, all *output* from applications to a terminal are also pure text. To
+do something more than just "display text" we again need to use escape
+sequences. For example `\E[1m` will make the text bold (until reset), or `\E[2J`
+will clear the screen. Some may also send back data; for example `\E[6n` to get
+the cursor position.
+
+The reason it all works like this is because "terminals" were originally actual
+devices with a screen and keyboard, connected over a serial port to a computer,
+and this was the only way to send *any* data. Early version from the 60s often
+had a printer rather than a screen.
+
+What you're using today is more accurately described as a *terminal emulator*;
+that is, a program that *emulates* one of those physical devices. Back in those
+days computers were very expensive (hundreds of thousands of dollars), and
+terminals were comparatively cheap (though still expensive, usually several
+*thousand* dollars in today's money!) I wrote a little bit more about the
+history at https://bestasciitable.com, which also includes some pictures.
+
+Now, the problem is that not every terminal (or "terminal emulator", but you can
+use them interchangeably) may agree what the escape sequence is to make text
+bold, or what the "F1 key" looks like. There is nothing "special" about `\E[1m`;
+it's just that most terminals agree that this is the sequence to make text bold,
+but it could also have been `\Ebold` or even just `BOLD` if you wanted (but that
+would make it impossible to write the text "BOLD", but you could if you wanted
+to).
+
+In the past there were dozens of brands and many different terminal devices,
+many of which had widely different escape sequences and logic, so people created
+databases to record all of this, and an application could work with multiple
+terminal devices rather than the one the author of the program was using. There
+are actually two solutions for this: `termcap` and `terminfo`, both more or less
+similar (terminfo has more features). Creating two different standards because
+there are too many standards ... classic. These days, systems almost exclusively
+use terminfo, although termcap compatibility is still provided by some systems.
+There have historically been a few different implementations of terminfo, but
+the one used almost universally today is the one that's part of ncurses,
+maintained by Thomas Dickey who also maintains xterm. terminfo is part of POSIX
+(as is curses), termcap is not.
+
+terminfo files are usually stored in `/usr/share/terminfo`; the files in there
+are "compiled" binary files. I guess parsing text was too expensive in 1981, and
+the binary format stuck (including 16bit alignment for old 16bit aligned systems
+like the PDP-11 by the way).
+
+Today a lot has been standardized and converged; ECMA-48 and "Xterm-style escape
+sequences" are what almost all (if not all) commonly used terminals use. This is
+why you can get away with just using `printf '\x1b[1mBOLD!\x1b[0m\n'` in your
+shell scripts and not worry too much about looking up the correct terminfo
+properties. The *True Right Way™* to do this is still to look up the terminfo
+entries though, and if you get beyond some of the basics like bold text this is
+still needed. There are still several "styles" of doing some things for some
+more advanced control codes (such as RGB colours, mouse support) and recognition
+of "special" keys ("backspace sends delete" is a common one).
+
+You can send this from the shell with the `tput` command, for example:
+
+ % printf "$(tput bold)Bold!$(tput sgr0) Not bold\n"
+
+`sgr0` is "set graphic reset" (I think?) and resets all graphical attributes.
+The names for these things range from "a bit obscure" or "an indecipherable set
+of letters with no obvious meaning" – party like it's UNIX. You also have long
+names (`exit_attribute_mode` for `sgr0`) but `tput` doesn't recognize them.
+
+You can see a list of the terminfo entries for your current terminal with
+`infocmp -1Lx` (`-L` to use long names, `-1` to print one per line, and `-x` to
+print extended data too). You can compare them too:
+
+ % infocmp -1Lx xterm-256color iterm2
+ comparing booleans.
+ backspaces_with_bs: T:F.
+ can_change: T:F.
+
+ comparing strings.
+ clear_screen: '\E[H\E[2J', '\E[H\E[J'.
+ cursor_normal: '\E[?12l\E[?25h', '\E[?25h'.
+ cursor_visible: '\E[?12;25h', NULL.
+
+There are actually many more differences between `xterm-256color` and `iterm2`,
+but I'm not going to show them all here. Note how `clear_screen` is slightly
+different.
+
+Aside from a simple key→value mapping, terminfo entries can also have
+parameters. For example `parm_delete_line` (or `dl`) is `\E[%p1%dM`. The way
+this works is with a small stack-based language; `%p1` pushes the first
+parameter on the stack, and `%d` pops a value and prints it as a number. So with
+`dl 5` we end up with `\E[5M`.
+
+There are all sorts of things you can do with this, like conditionals and
+arithmetic. This is useful because some may accept a RGB colour as hex in the
+range 0x00-0xff, whereas others may want it as a decimal in the range 0-255.
+Stuff like that. You don't really need to worry about this because the only
+people writing these files are authors or terminal applications (or people who
+write terminfo libraries). But it's fun to know that terminfo files are
+Turing-complete, no?
+
+So this is the short version on how terminals work, and what the point of
+terminfo is :-) There's more to tell here (as well as another way to control
+terminals, with the `ioctl()` syscall) but I'll tell that bedtime story next
+week, but only if you behave and don't do anything naughty!
+
+
+Others
+------
+Some other Go terminfo implementations I found:
+
+- https://github.com/nsf/go-termbox
+ Very incomplete.
+
+- https://github.com/gdamore/tcell
+ Incomplete, relies on `infocmp` for non-builtin terminals (yikes), and I
+ didn't care much for the API.
+
+- https://github.com/eiannone/keyboard
+ Copied from go-termbox, even more incomplete.
+
+- https://github.com/charithe/terminfo
+ This actually seems pretty decent, although it lacks the key scanning part. If
+ I had known about it I would have forked/used it. Ah well...
+
+- https://github.com/xo/terminfo
+ Fork of the above; adds a lot of ncurses-y TUI stuff.
+
+Some of these other packages (such as termbox and tcell) also do much more than
+just dealing with terminfo. This package is intended to *only* support doing
+useful things with terminfo and not much more. A big advantage is that it's a
+lot easier to use in simpler CLI apps that are not full-blown TUIs.
diff --git a/tui/termfo/caps/caps.go b/tui/termfo/caps/caps.go
new file mode 100644
index 0000000..b157210
--- /dev/null
+++ b/tui/termfo/caps/caps.go
@@ -0,0 +1,629 @@
+// Code generated by term.h.zsh; DO NOT EDIT.
+
+// Package caps contains a list of all terminfo capabilities.
+package caps
+
+// CursesVersion is the version of curses this data was generated with, as [implementation]-[version].
+const CursesVersion = `ncurses-6.5.20240511`
+
+// Cap represents a capability as listed in a terminfo file.
+type Cap struct {
+ Short string // Short terminfo name
+ Long string // Longer variable name from term.h
+ Desc string // Description from terminfo(5)
+}
+
+var (
+ AutoLeftMargin = &Cap{`bw`, `auto_left_margin`, `cub1 wraps from column 0 to last column`}
+ AutoRightMargin = &Cap{`am`, `auto_right_margin`, `terminal has automatic margins`}
+ NoEscCtlc = &Cap{`xsb`, `no_esc_ctlc`, `beehive (f1=escape, f2=ctrl C)`}
+ CeolStandoutGlitch = &Cap{`xhp`, `ceol_standout_glitch`, `standout not erased by overwriting (hp)`}
+ EatNewlineGlitch = &Cap{`xenl`, `eat_newline_glitch`, `newline ignored after 80 cols (concept)`}
+ EraseOverstrike = &Cap{`eo`, `erase_overstrike`, `can erase overstrikes with a blank`}
+ GenericType = &Cap{`gn`, `generic_type`, `generic line type`}
+ HardCopy = &Cap{`hc`, `hard_copy`, `hardcopy terminal`}
+ HasMetaKey = &Cap{`km`, `has_meta_key`, `Has a meta key (i.e., sets 8th-bit)`}
+ HasStatusLine = &Cap{`hs`, `has_status_line`, `has extra status line`}
+ InsertNullGlitch = &Cap{`in`, `insert_null_glitch`, `insert mode distinguishes nulls`}
+ MemoryAbove = &Cap{`da`, `memory_above`, `display may be retained above the screen`}
+ MemoryBelow = &Cap{`db`, `memory_below`, `display may be retained below the screen`}
+ MoveInsertMode = &Cap{`mir`, `move_insert_mode`, `safe to move while in insert mode`}
+ MoveStandoutMode = &Cap{`msgr`, `move_standout_mode`, `safe to move while in standout mode`}
+ OverStrike = &Cap{`os`, `over_strike`, `terminal can overstrike`}
+ StatusLineEscOk = &Cap{`eslok`, `status_line_esc_ok`, `escape can be used on the status line`}
+ DestTabsMagicSmso = &Cap{`xt`, `dest_tabs_magic_smso`, `tabs destructive, magic so char (t1061)`}
+ TildeGlitch = &Cap{`hz`, `tilde_glitch`, `cannot print ~'s (Hazeltine)`}
+ TransparentUnderline = &Cap{`ul`, `transparent_underline`, `underline character overstrikes`}
+ XonXoff = &Cap{`xon`, `xon_xoff`, `terminal uses xon/xoff handshaking`}
+ NeedsXonXoff = &Cap{`nxon`, `needs_xon_xoff`, `padding will not work, xon/xoff required`}
+ PrtrSilent = &Cap{`mc5i`, `prtr_silent`, `printer will not echo on screen`}
+ HardCursor = &Cap{`chts`, `hard_cursor`, `cursor is hard to see`}
+ NonRevRmcup = &Cap{`nrrmc`, `non_rev_rmcup`, `smcup does not reverse rmcup`}
+ NoPadChar = &Cap{`npc`, `no_pad_char`, `pad character does not exist`}
+ NonDestScrollRegion = &Cap{`ndscr`, `non_dest_scroll_region`, `scrolling region is non-destructive`}
+ CanChange = &Cap{`ccc`, `can_change`, `terminal can re-define existing colors`}
+ BackColorErase = &Cap{`bce`, `back_color_erase`, `screen erased with background color`}
+ HueLightnessSaturation = &Cap{`hls`, `hue_lightness_saturation`, `terminal uses only HLS color notation (Tektronix)`}
+ ColAddrGlitch = &Cap{`xhpa`, `col_addr_glitch`, `only positive motion for hpa/mhpa caps`}
+ CrCancelsMicroMode = &Cap{`crxm`, `cr_cancels_micro_mode`, `using cr turns off micro mode`}
+ HasPrintWheel = &Cap{`daisy`, `has_print_wheel`, `printer needs operator to change character set`}
+ RowAddrGlitch = &Cap{`xvpa`, `row_addr_glitch`, `only positive motion for vpa/mvpa caps`}
+ SemiAutoRightMargin = &Cap{`sam`, `semi_auto_right_margin`, `printing in last column causes cr`}
+ CpiChangesRes = &Cap{`cpix`, `cpi_changes_res`, `changing character pitch changes resolution`}
+ LpiChangesRes = &Cap{`lpix`, `lpi_changes_res`, `changing line pitch changes resolution`}
+ Columns = &Cap{`cols`, `columns`, `number of columns in a line`}
+ InitTabs = &Cap{`it`, `init_tabs`, `tabs initially every # spaces`}
+ Lines = &Cap{`lines`, `lines`, `number of lines on screen or page`}
+ LinesOfMemory = &Cap{`lm`, `lines_of_memory`, `lines of memory if > line. 0 means varies`}
+ MagicCookieGlitch = &Cap{`xmc`, `magic_cookie_glitch`, `number of blank characters left by smso or rmso`}
+ PaddingBaudRate = &Cap{`pb`, `padding_baud_rate`, `lowest baud rate where padding needed`}
+ VirtualTerminal = &Cap{`vt`, `virtual_terminal`, `virtual terminal number (CB/unix)`}
+ WidthStatusLine = &Cap{`wsl`, `width_status_line`, `number of columns in status line`}
+ NumLabels = &Cap{`nlab`, `num_labels`, `number of labels on screen`}
+ LabelHeight = &Cap{`lh`, `label_height`, `rows in each label`}
+ LabelWidth = &Cap{`lw`, `label_width`, `columns in each label`}
+ MaxAttributes = &Cap{`ma`, `max_attributes`, `maximum combined attributes terminal can handle`}
+ MaximumWindows = &Cap{`wnum`, `maximum_windows`, `maximum number of definable windows`}
+ MaxColors = &Cap{`colors`, `max_colors`, `maximum number of colors on screen`}
+ MaxPairs = &Cap{`pairs`, `max_pairs`, `maximum number of color-pairs on the screen`}
+ NoColorVideo = &Cap{`ncv`, `no_color_video`, `video attributes that cannot be used with colors`}
+ BufferCapacity = &Cap{`bufsz`, `buffer_capacity`, `numbers of bytes buffered before printing`}
+ DotVertSpacing = &Cap{`spinv`, `dot_vert_spacing`, `spacing of pins vertically in pins per inch`}
+ DotHorzSpacing = &Cap{`spinh`, `dot_horz_spacing`, `spacing of dots horizontally in dots per inch`}
+ MaxMicroAddress = &Cap{`maddr`, `max_micro_address`, `maximum value in micro_..._address`}
+ MaxMicroJump = &Cap{`mjump`, `max_micro_jump`, `maximum value in parm_..._micro`}
+ MicroColSize = &Cap{`mcs`, `micro_col_size`, `character step size when in micro mode`}
+ MicroLineSize = &Cap{`mls`, `micro_line_size`, `line step size when in micro mode`}
+ NumberOfPins = &Cap{`npins`, `number_of_pins`, `numbers of pins in print-head`}
+ OutputResChar = &Cap{`orc`, `output_res_char`, `horizontal resolution in units per line`}
+ OutputResLine = &Cap{`orl`, `output_res_line`, `vertical resolution in units per line`}
+ OutputResHorzInch = &Cap{`orhi`, `output_res_horz_inch`, `horizontal resolution in units per inch`}
+ OutputResVertInch = &Cap{`orvi`, `output_res_vert_inch`, `vertical resolution in units per inch`}
+ PrintRate = &Cap{`cps`, `print_rate`, `print rate in characters per second`}
+ WideCharSize = &Cap{`widcs`, `wide_char_size`, `character step size when in double wide mode`}
+ Buttons = &Cap{`btns`, `buttons`, `number of buttons on mouse`}
+ BitImageEntwining = &Cap{`bitwin`, `bit_image_entwining`, `number of passes for each bit-image row`}
+ BitImageType = &Cap{`bitype`, `bit_image_type`, `type of bit-image device`}
+ BackTab = &Cap{`cbt`, `back_tab`, `back tab (P)`}
+ Bell = &Cap{`bel`, `bell`, `audible signal (bell) (P)`}
+ CarriageReturn = &Cap{`cr`, `carriage_return`, `carriage return (P*) (P*)`}
+ ChangeScrollRegion = &Cap{`csr`, `change_scroll_region`, `change region to line #1 to line #2 (P)`}
+ ClearAllTabs = &Cap{`tbc`, `clear_all_tabs`, `clear all tab stops (P)`}
+ ClearScreen = &Cap{`clear`, `clear_screen`, `clear screen and home cursor (P*)`}
+ ClrEol = &Cap{`el`, `clr_eol`, `clear to end of line (P)`}
+ ClrEos = &Cap{`ed`, `clr_eos`, `clear to end of screen (P*)`}
+ ColumnAddress = &Cap{`hpa`, `column_address`, `horizontal position #1, absolute (P)`}
+ CommandCharacter = &Cap{`cmdch`, `command_character`, `terminal settable cmd character in prototype !?`}
+ CursorAddress = &Cap{`cup`, `cursor_address`, `move to row #1 columns #2`}
+ CursorDown = &Cap{`cud1`, `cursor_down`, `down one line`}
+ CursorHome = &Cap{`home`, `cursor_home`, `home cursor (if no cup)`}
+ CursorInvisible = &Cap{`civis`, `cursor_invisible`, `make cursor invisible`}
+ CursorLeft = &Cap{`cub1`, `cursor_left`, `move left one space`}
+ CursorMemAddress = &Cap{`mrcup`, `cursor_mem_address`, `memory relative cursor addressing, move to row #1 columns #2`}
+ CursorNormal = &Cap{`cnorm`, `cursor_normal`, `make cursor appear normal (undo civis/cvvis)`}
+ CursorRight = &Cap{`cuf1`, `cursor_right`, `non-destructive space (move right one space)`}
+ CursorToLl = &Cap{`ll`, `cursor_to_ll`, `last line, first column (if no cup)`}
+ CursorUp = &Cap{`cuu1`, `cursor_up`, `up one line`}
+ CursorVisible = &Cap{`cvvis`, `cursor_visible`, `make cursor very visible`}
+ DeleteCharacter = &Cap{`dch1`, `delete_character`, `delete character (P*)`}
+ DeleteLine = &Cap{`dl1`, `delete_line`, `delete line (P*)`}
+ DisStatusLine = &Cap{`dsl`, `dis_status_line`, `disable status line`}
+ DownHalfLine = &Cap{`hd`, `down_half_line`, `half a line down`}
+ EnterAltCharsetMode = &Cap{`smacs`, `enter_alt_charset_mode`, `start alternate character set (P)`}
+ EnterBlinkMode = &Cap{`blink`, `enter_blink_mode`, `turn on blinking`}
+ EnterBoldMode = &Cap{`bold`, `enter_bold_mode`, `turn on bold (extra bright) mode`}
+ EnterCaMode = &Cap{`smcup`, `enter_ca_mode`, `string to start programs using cup`}
+ EnterDeleteMode = &Cap{`smdc`, `enter_delete_mode`, `enter delete mode`}
+ EnterDimMode = &Cap{`dim`, `enter_dim_mode`, `turn on half-bright mode`}
+ EnterInsertMode = &Cap{`smir`, `enter_insert_mode`, `enter insert mode`}
+ EnterSecureMode = &Cap{`invis`, `enter_secure_mode`, `turn on blank mode (characters invisible)`}
+ EnterProtectedMode = &Cap{`prot`, `enter_protected_mode`, `turn on protected mode`}
+ EnterReverseMode = &Cap{`rev`, `enter_reverse_mode`, `turn on reverse video mode`}
+ EnterStandoutMode = &Cap{`smso`, `enter_standout_mode`, `begin standout mode`}
+ EnterUnderlineMode = &Cap{`smul`, `enter_underline_mode`, `begin underline mode`}
+ EraseChars = &Cap{`ech`, `erase_chars`, `erase #1 characters (P)`}
+ ExitAltCharsetMode = &Cap{`rmacs`, `exit_alt_charset_mode`, `end alternate character set (P)`}
+ ExitAttributeMode = &Cap{`sgr0`, `exit_attribute_mode`, `turn off all attributes`}
+ ExitCaMode = &Cap{`rmcup`, `exit_ca_mode`, `strings to end programs using cup`}
+ ExitDeleteMode = &Cap{`rmdc`, `exit_delete_mode`, `end delete mode`}
+ ExitInsertMode = &Cap{`rmir`, `exit_insert_mode`, `exit insert mode`}
+ ExitStandoutMode = &Cap{`rmso`, `exit_standout_mode`, `exit standout mode`}
+ ExitUnderlineMode = &Cap{`rmul`, `exit_underline_mode`, `exit underline mode`}
+ FlashScreen = &Cap{`flash`, `flash_screen`, `visible bell (may not move cursor)`}
+ FormFeed = &Cap{`ff`, `form_feed`, `hardcopy terminal page eject (P*)`}
+ FromStatusLine = &Cap{`fsl`, `from_status_line`, `return from status line`}
+ Init1string = &Cap{`is1`, `init_1string`, `initialization string`}
+ Init2string = &Cap{`is2`, `init_2string`, `initialization string`}
+ Init3string = &Cap{`is3`, `init_3string`, `initialization string`}
+ InitFile = &Cap{`if`, `init_file`, `name of initialization file`}
+ InsertCharacter = &Cap{`ich1`, `insert_character`, `insert character (P)`}
+ InsertLine = &Cap{`il1`, `insert_line`, `insert line (P*)`}
+ InsertPadding = &Cap{`ip`, `insert_padding`, `insert padding after inserted character`}
+ KeyBackspace = &Cap{`kbs`, `key_backspace`, `backspace key`}
+ KeyCatab = &Cap{`ktbc`, `key_catab`, `clear-all-tabs key`}
+ KeyClear = &Cap{`kclr`, `key_clear`, `clear-screen or erase key`}
+ KeyCtab = &Cap{`kctab`, `key_ctab`, `clear-tab key`}
+ KeyDc = &Cap{`kdch1`, `key_dc`, `delete-character key`}
+ KeyDl = &Cap{`kdl1`, `key_dl`, `delete-line key`}
+ KeyDown = &Cap{`kcud1`, `key_down`, `down-arrow key`}
+ KeyEic = &Cap{`krmir`, `key_eic`, `sent by rmir or smir in insert mode`}
+ KeyEol = &Cap{`kel`, `key_eol`, `clear-to-end-of-line key`}
+ KeyEos = &Cap{`ked`, `key_eos`, `clear-to-end-of-screen key`}
+ KeyF0 = &Cap{`kf0`, `key_f0`, `F0 function key`}
+ KeyF1 = &Cap{`kf1`, `key_f1`, `F1 function key`}
+ KeyF10 = &Cap{`kf10`, `key_f10`, `F10 function key`}
+ KeyF2 = &Cap{`kf2`, `key_f2`, `F2 function key`}
+ KeyF3 = &Cap{`kf3`, `key_f3`, `F3 function key`}
+ KeyF4 = &Cap{`kf4`, `key_f4`, `F4 function key`}
+ KeyF5 = &Cap{`kf5`, `key_f5`, `F5 function key`}
+ KeyF6 = &Cap{`kf6`, `key_f6`, `F6 function key`}
+ KeyF7 = &Cap{`kf7`, `key_f7`, `F7 function key`}
+ KeyF8 = &Cap{`kf8`, `key_f8`, `F8 function key`}
+ KeyF9 = &Cap{`kf9`, `key_f9`, `F9 function key`}
+ KeyHome = &Cap{`khome`, `key_home`, `home key`}
+ KeyIc = &Cap{`kich1`, `key_ic`, `insert-character key`}
+ KeyIl = &Cap{`kil1`, `key_il`, `insert-line key`}
+ KeyLeft = &Cap{`kcub1`, `key_left`, `left-arrow key`}
+ KeyLl = &Cap{`kll`, `key_ll`, `lower-left key (home down)`}
+ KeyNpage = &Cap{`knp`, `key_npage`, `next-page key`}
+ KeyPpage = &Cap{`kpp`, `key_ppage`, `previous-page key`}
+ KeyRight = &Cap{`kcuf1`, `key_right`, `right-arrow key`}
+ KeySf = &Cap{`kind`, `key_sf`, `scroll-forward key`}
+ KeySr = &Cap{`kri`, `key_sr`, `scroll-backward key`}
+ KeyStab = &Cap{`khts`, `key_stab`, `set-tab key`}
+ KeyUp = &Cap{`kcuu1`, `key_up`, `up-arrow key`}
+ KeypadLocal = &Cap{`rmkx`, `keypad_local`, `leave keyboard transmit mode`}
+ KeypadXmit = &Cap{`smkx`, `keypad_xmit`, `enter keyboard transmit mode`}
+ LabF0 = &Cap{`lf0`, `lab_f0`, `label on function key f0 if not f0`}
+ LabF1 = &Cap{`lf1`, `lab_f1`, `label on function key f1 if not f1`}
+ LabF10 = &Cap{`lf10`, `lab_f10`, `label on function key f10 if not f10`}
+ LabF2 = &Cap{`lf2`, `lab_f2`, `label on function key f2 if not f2`}
+ LabF3 = &Cap{`lf3`, `lab_f3`, `label on function key f3 if not f3`}
+ LabF4 = &Cap{`lf4`, `lab_f4`, `label on function key f4 if not f4`}
+ LabF5 = &Cap{`lf5`, `lab_f5`, `label on function key f5 if not f5`}
+ LabF6 = &Cap{`lf6`, `lab_f6`, `label on function key f6 if not f6`}
+ LabF7 = &Cap{`lf7`, `lab_f7`, `label on function key f7 if not f7`}
+ LabF8 = &Cap{`lf8`, `lab_f8`, `label on function key f8 if not f8`}
+ LabF9 = &Cap{`lf9`, `lab_f9`, `label on function key f9 if not f9`}
+ MetaOff = &Cap{`rmm`, `meta_off`, `turn off meta mode`}
+ MetaOn = &Cap{`smm`, `meta_on`, `turn on meta mode (8th-bit on)`}
+ Newline = &Cap{`nel`, `newline`, `newline (behave like cr followed by lf)`}
+ PadChar = &Cap{`pad`, `pad_char`, `padding char (instead of null)`}
+ ParmDch = &Cap{`dch`, `parm_dch`, `delete #1 characters (P*)`}
+ ParmDeleteLine = &Cap{`dl`, `parm_delete_line`, `delete #1 lines (P*)`}
+ ParmDownCursor = &Cap{`cud`, `parm_down_cursor`, `down #1 lines (P*)`}
+ ParmIch = &Cap{`ich`, `parm_ich`, `insert #1 characters (P*)`}
+ ParmIndex = &Cap{`indn`, `parm_index`, `scroll forward #1 lines (P)`}
+ ParmInsertLine = &Cap{`il`, `parm_insert_line`, `insert #1 lines (P*)`}
+ ParmLeftCursor = &Cap{`cub`, `parm_left_cursor`, `move #1 characters to the left (P)`}
+ ParmRightCursor = &Cap{`cuf`, `parm_right_cursor`, `move #1 characters to the right (P*)`}
+ ParmRindex = &Cap{`rin`, `parm_rindex`, `scroll back #1 lines (P)`}
+ ParmUpCursor = &Cap{`cuu`, `parm_up_cursor`, `up #1 lines (P*)`}
+ PkeyKey = &Cap{`pfkey`, `pkey_key`, `program function key #1 to type string #2`}
+ PkeyLocal = &Cap{`pfloc`, `pkey_local`, `program function key #1 to execute string #2`}
+ PkeyXmit = &Cap{`pfx`, `pkey_xmit`, `program function key #1 to transmit string #2`}
+ PrintScreen = &Cap{`mc0`, `print_screen`, `print contents of screen`}
+ PrtrOff = &Cap{`mc4`, `prtr_off`, `turn off printer`}
+ PrtrOn = &Cap{`mc5`, `prtr_on`, `turn on printer`}
+ RepeatChar = &Cap{`rep`, `repeat_char`, `repeat char #1 #2 times (P*)`}
+ Reset1string = &Cap{`rs1`, `reset_1string`, `reset string`}
+ Reset2string = &Cap{`rs2`, `reset_2string`, `reset string`}
+ Reset3string = &Cap{`rs3`, `reset_3string`, `reset string`}
+ ResetFile = &Cap{`rf`, `reset_file`, `name of reset file`}
+ RestoreCursor = &Cap{`rc`, `restore_cursor`, `restore cursor to position of last save_cursor`}
+ RowAddress = &Cap{`vpa`, `row_address`, `vertical position #1 absolute (P)`}
+ SaveCursor = &Cap{`sc`, `save_cursor`, `save current cursor position (P)`}
+ ScrollForward = &Cap{`ind`, `scroll_forward`, `scroll text up (P)`}
+ ScrollReverse = &Cap{`ri`, `scroll_reverse`, `scroll text down (P)`}
+ SetAttributes = &Cap{`sgr`, `set_attributes`, `define video attributes #1-#9 (PG9)`}
+ SetTab = &Cap{`hts`, `set_tab`, `set a tab in every row, current columns`}
+ SetWindow = &Cap{`wind`, `set_window`, `current window is lines #1-#2 cols #3-#4`}
+ Tab = &Cap{`ht`, `tab`, `tab to next 8-space hardware tab stop`}
+ ToStatusLine = &Cap{`tsl`, `to_status_line`, `move to status line, column #1`}
+ UnderlineChar = &Cap{`uc`, `underline_char`, `underline char and move past it`}
+ UpHalfLine = &Cap{`hu`, `up_half_line`, `half a line up`}
+ InitProg = &Cap{`iprog`, `init_prog`, `path name of program for initialization`}
+ KeyA1 = &Cap{`ka1`, `key_a1`, `upper left of keypad`}
+ KeyA3 = &Cap{`ka3`, `key_a3`, `upper right of keypad`}
+ KeyB2 = &Cap{`kb2`, `key_b2`, `center of keypad`}
+ KeyC1 = &Cap{`kc1`, `key_c1`, `lower left of keypad`}
+ KeyC3 = &Cap{`kc3`, `key_c3`, `lower right of keypad`}
+ PrtrNon = &Cap{`mc5p`, `prtr_non`, `turn on printer for #1 bytes`}
+ CharPadding = &Cap{`rmp`, `char_padding`, `like ip but when in insert mode`}
+ AcsChars = &Cap{`acsc`, `acs_chars`, `graphics charset pairs, based on vt100`}
+ PlabNorm = &Cap{`pln`, `plab_norm`, `program label #1 to show string #2`}
+ KeyBtab = &Cap{`kcbt`, `key_btab`, `back-tab key`}
+ EnterXonMode = &Cap{`smxon`, `enter_xon_mode`, `turn on xon/xoff handshaking`}
+ ExitXonMode = &Cap{`rmxon`, `exit_xon_mode`, `turn off xon/xoff handshaking`}
+ EnterAmMode = &Cap{`smam`, `enter_am_mode`, `turn on automatic margins`}
+ ExitAmMode = &Cap{`rmam`, `exit_am_mode`, `turn off automatic margins`}
+ XonCharacter = &Cap{`xonc`, `xon_character`, `XON character`}
+ XoffCharacter = &Cap{`xoffc`, `xoff_character`, `XOFF character`}
+ EnaAcs = &Cap{`enacs`, `ena_acs`, `enable alternate char set`}
+ LabelOn = &Cap{`smln`, `label_on`, `turn on soft labels`}
+ LabelOff = &Cap{`rmln`, `label_off`, `turn off soft labels`}
+ KeyBeg = &Cap{`kbeg`, `key_beg`, `begin key`}
+ KeyCancel = &Cap{`kcan`, `key_cancel`, `cancel key`}
+ KeyClose = &Cap{`kclo`, `key_close`, `close key`}
+ KeyCommand = &Cap{`kcmd`, `key_command`, `command key`}
+ KeyCopy = &Cap{`kcpy`, `key_copy`, `copy key`}
+ KeyCreate = &Cap{`kcrt`, `key_create`, `create key`}
+ KeyEnd = &Cap{`kend`, `key_end`, `end key`}
+ KeyEnter = &Cap{`kent`, `key_enter`, `enter/send key`}
+ KeyExit = &Cap{`kext`, `key_exit`, `exit key`}
+ KeyFind = &Cap{`kfnd`, `key_find`, `find key`}
+ KeyHelp = &Cap{`khlp`, `key_help`, `help key`}
+ KeyMark = &Cap{`kmrk`, `key_mark`, `mark key`}
+ KeyMessage = &Cap{`kmsg`, `key_message`, `message key`}
+ KeyMove = &Cap{`kmov`, `key_move`, `move key`}
+ KeyNext = &Cap{`knxt`, `key_next`, `next key`}
+ KeyOpen = &Cap{`kopn`, `key_open`, `open key`}
+ KeyOptions = &Cap{`kopt`, `key_options`, `options key`}
+ KeyPrevious = &Cap{`kprv`, `key_previous`, `previous key`}
+ KeyPrint = &Cap{`kprt`, `key_print`, `print key`}
+ KeyRedo = &Cap{`krdo`, `key_redo`, `redo key`}
+ KeyReference = &Cap{`kref`, `key_reference`, `reference key`}
+ KeyRefresh = &Cap{`krfr`, `key_refresh`, `refresh key`}
+ KeyReplace = &Cap{`krpl`, `key_replace`, `replace key`}
+ KeyRestart = &Cap{`krst`, `key_restart`, `restart key`}
+ KeyResume = &Cap{`kres`, `key_resume`, `resume key`}
+ KeySave = &Cap{`ksav`, `key_save`, `save key`}
+ KeySuspend = &Cap{`kspd`, `key_suspend`, `suspend key`}
+ KeyUndo = &Cap{`kund`, `key_undo`, `undo key`}
+ KeySbeg = &Cap{`kBEG`, `key_sbeg`, `shifted begin key`}
+ KeyScancel = &Cap{`kCAN`, `key_scancel`, `shifted cancel key`}
+ KeyScommand = &Cap{`kCMD`, `key_scommand`, `shifted command key`}
+ KeyScopy = &Cap{`kCPY`, `key_scopy`, `shifted copy key`}
+ KeyScreate = &Cap{`kCRT`, `key_screate`, `shifted create key`}
+ KeySdc = &Cap{`kDC`, `key_sdc`, `shifted delete-character key`}
+ KeySdl = &Cap{`kDL`, `key_sdl`, `shifted delete-line key`}
+ KeySelect = &Cap{`kslt`, `key_select`, `select key`}
+ KeySend = &Cap{`kEND`, `key_send`, `shifted end key`}
+ KeySeol = &Cap{`kEOL`, `key_seol`, `shifted clear-to-end-of-line key`}
+ KeySexit = &Cap{`kEXT`, `key_sexit`, `shifted exit key`}
+ KeySfind = &Cap{`kFND`, `key_sfind`, `shifted find key`}
+ KeyShelp = &Cap{`kHLP`, `key_shelp`, `shifted help key`}
+ KeyShome = &Cap{`kHOM`, `key_shome`, `shifted home key`}
+ KeySic = &Cap{`kIC`, `key_sic`, `shifted insert-character key`}
+ KeySleft = &Cap{`kLFT`, `key_sleft`, `shifted left-arrow key`}
+ KeySmessage = &Cap{`kMSG`, `key_smessage`, `shifted message key`}
+ KeySmove = &Cap{`kMOV`, `key_smove`, `shifted move key`}
+ KeySnext = &Cap{`kNXT`, `key_snext`, `shifted next key`}
+ KeySoptions = &Cap{`kOPT`, `key_soptions`, `shifted options key`}
+ KeySprevious = &Cap{`kPRV`, `key_sprevious`, `shifted previous key`}
+ KeySprint = &Cap{`kPRT`, `key_sprint`, `shifted print key`}
+ KeySredo = &Cap{`kRDO`, `key_sredo`, `shifted redo key`}
+ KeySreplace = &Cap{`kRPL`, `key_sreplace`, `shifted replace key`}
+ KeySright = &Cap{`kRIT`, `key_sright`, `shifted right-arrow key`}
+ KeySrsume = &Cap{`kRES`, `key_srsume`, `shifted resume key`}
+ KeySsave = &Cap{`kSAV`, `key_ssave`, `shifted save key`}
+ KeySsuspend = &Cap{`kSPD`, `key_ssuspend`, `shifted suspend key`}
+ KeySundo = &Cap{`kUND`, `key_sundo`, `shifted undo key`}
+ ReqForInput = &Cap{`rfi`, `req_for_input`, `send next input char (for ptys)`}
+ KeyF11 = &Cap{`kf11`, `key_f11`, `F11 function key`}
+ KeyF12 = &Cap{`kf12`, `key_f12`, `F12 function key`}
+ KeyF13 = &Cap{`kf13`, `key_f13`, `F13 function key`}
+ KeyF14 = &Cap{`kf14`, `key_f14`, `F14 function key`}
+ KeyF15 = &Cap{`kf15`, `key_f15`, `F15 function key`}
+ KeyF16 = &Cap{`kf16`, `key_f16`, `F16 function key`}
+ KeyF17 = &Cap{`kf17`, `key_f17`, `F17 function key`}
+ KeyF18 = &Cap{`kf18`, `key_f18`, `F18 function key`}
+ KeyF19 = &Cap{`kf19`, `key_f19`, `F19 function key`}
+ KeyF20 = &Cap{`kf20`, `key_f20`, `F20 function key`}
+ KeyF21 = &Cap{`kf21`, `key_f21`, `F21 function key`}
+ KeyF22 = &Cap{`kf22`, `key_f22`, `F22 function key`}
+ KeyF23 = &Cap{`kf23`, `key_f23`, `F23 function key`}
+ KeyF24 = &Cap{`kf24`, `key_f24`, `F24 function key`}
+ KeyF25 = &Cap{`kf25`, `key_f25`, `F25 function key`}
+ KeyF26 = &Cap{`kf26`, `key_f26`, `F26 function key`}
+ KeyF27 = &Cap{`kf27`, `key_f27`, `F27 function key`}
+ KeyF28 = &Cap{`kf28`, `key_f28`, `F28 function key`}
+ KeyF29 = &Cap{`kf29`, `key_f29`, `F29 function key`}
+ KeyF30 = &Cap{`kf30`, `key_f30`, `F30 function key`}
+ KeyF31 = &Cap{`kf31`, `key_f31`, `F31 function key`}
+ KeyF32 = &Cap{`kf32`, `key_f32`, `F32 function key`}
+ KeyF33 = &Cap{`kf33`, `key_f33`, `F33 function key`}
+ KeyF34 = &Cap{`kf34`, `key_f34`, `F34 function key`}
+ KeyF35 = &Cap{`kf35`, `key_f35`, `F35 function key`}
+ KeyF36 = &Cap{`kf36`, `key_f36`, `F36 function key`}
+ KeyF37 = &Cap{`kf37`, `key_f37`, `F37 function key`}
+ KeyF38 = &Cap{`kf38`, `key_f38`, `F38 function key`}
+ KeyF39 = &Cap{`kf39`, `key_f39`, `F39 function key`}
+ KeyF40 = &Cap{`kf40`, `key_f40`, `F40 function key`}
+ KeyF41 = &Cap{`kf41`, `key_f41`, `F41 function key`}
+ KeyF42 = &Cap{`kf42`, `key_f42`, `F42 function key`}
+ KeyF43 = &Cap{`kf43`, `key_f43`, `F43 function key`}
+ KeyF44 = &Cap{`kf44`, `key_f44`, `F44 function key`}
+ KeyF45 = &Cap{`kf45`, `key_f45`, `F45 function key`}
+ KeyF46 = &Cap{`kf46`, `key_f46`, `F46 function key`}
+ KeyF47 = &Cap{`kf47`, `key_f47`, `F47 function key`}
+ KeyF48 = &Cap{`kf48`, `key_f48`, `F48 function key`}
+ KeyF49 = &Cap{`kf49`, `key_f49`, `F49 function key`}
+ KeyF50 = &Cap{`kf50`, `key_f50`, `F50 function key`}
+ KeyF51 = &Cap{`kf51`, `key_f51`, `F51 function key`}
+ KeyF52 = &Cap{`kf52`, `key_f52`, `F52 function key`}
+ KeyF53 = &Cap{`kf53`, `key_f53`, `F53 function key`}
+ KeyF54 = &Cap{`kf54`, `key_f54`, `F54 function key`}
+ KeyF55 = &Cap{`kf55`, `key_f55`, `F55 function key`}
+ KeyF56 = &Cap{`kf56`, `key_f56`, `F56 function key`}
+ KeyF57 = &Cap{`kf57`, `key_f57`, `F57 function key`}
+ KeyF58 = &Cap{`kf58`, `key_f58`, `F58 function key`}
+ KeyF59 = &Cap{`kf59`, `key_f59`, `F59 function key`}
+ KeyF60 = &Cap{`kf60`, `key_f60`, `F60 function key`}
+ KeyF61 = &Cap{`kf61`, `key_f61`, `F61 function key`}
+ KeyF62 = &Cap{`kf62`, `key_f62`, `F62 function key`}
+ KeyF63 = &Cap{`kf63`, `key_f63`, `F63 function key`}
+ ClrBol = &Cap{`el1`, `clr_bol`, `Clear to beginning of line`}
+ ClearMargins = &Cap{`mgc`, `clear_margins`, `clear right and left soft margins`}
+ SetLeftMargin = &Cap{`smgl`, `set_left_margin`, `set left soft margin at current column (not in BSD \fItermcap\fP)`}
+ SetRightMargin = &Cap{`smgr`, `set_right_margin`, `set right soft margin at current column`}
+ LabelFormat = &Cap{`fln`, `label_format`, `label format`}
+ SetClock = &Cap{`sclk`, `set_clock`, `set clock, #1 hrs #2 mins #3 secs`}
+ DisplayClock = &Cap{`dclk`, `display_clock`, `display clock`}
+ RemoveClock = &Cap{`rmclk`, `remove_clock`, `remove clock`}
+ CreateWindow = &Cap{`cwin`, `create_window`, `define a window #1 from #2,#3 to #4,#5`}
+ GotoWindow = &Cap{`wingo`, `goto_window`, `go to window #1`}
+ Hangup = &Cap{`hup`, `hangup`, `hang-up phone`}
+ DialPhone = &Cap{`dial`, `dial_phone`, `dial number #1`}
+ QuickDial = &Cap{`qdial`, `quick_dial`, `dial number #1 without checking`}
+ Tone = &Cap{`tone`, `tone`, `select touch tone dialing`}
+ Pulse = &Cap{`pulse`, `pulse`, `select pulse dialing`}
+ FlashHook = &Cap{`hook`, `flash_hook`, `flash switch hook`}
+ FixedPause = &Cap{`pause`, `fixed_pause`, `pause for 2-3 seconds`}
+ WaitTone = &Cap{`wait`, `wait_tone`, `wait for dial-tone`}
+ User0 = &Cap{`u0`, `user0`, `User string #0`}
+ User1 = &Cap{`u1`, `user1`, `User string #1`}
+ User2 = &Cap{`u2`, `user2`, `User string #2`}
+ User3 = &Cap{`u3`, `user3`, `User string #3`}
+ User4 = &Cap{`u4`, `user4`, `User string #4`}
+ User5 = &Cap{`u5`, `user5`, `User string #5`}
+ User6 = &Cap{`u6`, `user6`, `User string #6`}
+ User7 = &Cap{`u7`, `user7`, `User string #7`}
+ User8 = &Cap{`u8`, `user8`, `User string #8`}
+ User9 = &Cap{`u9`, `user9`, `User string #9`}
+ OrigPair = &Cap{`op`, `orig_pair`, `Set default pair to its original value`}
+ OrigColors = &Cap{`oc`, `orig_colors`, `Set all color pairs to the original ones`}
+ InitializeColor = &Cap{`initc`, `initialize_color`, `initialize color #1 to (#2,#3,#4)`}
+ InitializePair = &Cap{`initp`, `initialize_pair`, `Initialize color pair #1 to fg=(#2,#3,#4), bg=(#5,#6,#7)`}
+ SetColorPair = &Cap{`scp`, `set_color_pair`, `Set current color pair to #1`}
+ SetForeground = &Cap{`setf`, `set_foreground`, `Set foreground color #1`}
+ SetBackground = &Cap{`setb`, `set_background`, `Set background color #1`}
+ ChangeCharPitch = &Cap{`cpi`, `change_char_pitch`, `Change number of characters per inch to #1`}
+ ChangeLinePitch = &Cap{`lpi`, `change_line_pitch`, `Change number of lines per inch to #1`}
+ ChangeResHorz = &Cap{`chr`, `change_res_horz`, `Change horizontal resolution to #1`}
+ ChangeResVert = &Cap{`cvr`, `change_res_vert`, `Change vertical resolution to #1`}
+ DefineChar = &Cap{`defc`, `define_char`, `Define a character #1, #2 dots wide, descender #3`}
+ EnterDoublewideMode = &Cap{`swidm`, `enter_doublewide_mode`, `Enter double-wide mode`}
+ EnterDraftQuality = &Cap{`sdrfq`, `enter_draft_quality`, `Enter draft-quality mode`}
+ EnterItalicsMode = &Cap{`sitm`, `enter_italics_mode`, `Enter italic mode`}
+ EnterLeftwardMode = &Cap{`slm`, `enter_leftward_mode`, `Start leftward carriage motion`}
+ EnterMicroMode = &Cap{`smicm`, `enter_micro_mode`, `Start micro-motion mode`}
+ EnterNearLetterQuality = &Cap{`snlq`, `enter_near_letter_quality`, `Enter NLQ mode`}
+ EnterNormalQuality = &Cap{`snrmq`, `enter_normal_quality`, `Enter normal-quality mode`}
+ EnterShadowMode = &Cap{`sshm`, `enter_shadow_mode`, `Enter shadow-print mode`}
+ EnterSubscriptMode = &Cap{`ssubm`, `enter_subscript_mode`, `Enter subscript mode`}
+ EnterSuperscriptMode = &Cap{`ssupm`, `enter_superscript_mode`, `Enter superscript mode`}
+ EnterUpwardMode = &Cap{`sum`, `enter_upward_mode`, `Start upward carriage motion`}
+ ExitDoublewideMode = &Cap{`rwidm`, `exit_doublewide_mode`, `End double-wide mode`}
+ ExitItalicsMode = &Cap{`ritm`, `exit_italics_mode`, `End italic mode`}
+ ExitLeftwardMode = &Cap{`rlm`, `exit_leftward_mode`, `End left-motion mode`}
+ ExitMicroMode = &Cap{`rmicm`, `exit_micro_mode`, `End micro-motion mode`}
+ ExitShadowMode = &Cap{`rshm`, `exit_shadow_mode`, `End shadow-print mode`}
+ ExitSubscriptMode = &Cap{`rsubm`, `exit_subscript_mode`, `End subscript mode`}
+ ExitSuperscriptMode = &Cap{`rsupm`, `exit_superscript_mode`, `End superscript mode`}
+ ExitUpwardMode = &Cap{`rum`, `exit_upward_mode`, `End reverse character motion`}
+ MicroColumnAddress = &Cap{`mhpa`, `micro_column_address`, `Like column_address in micro mode`}
+ MicroDown = &Cap{`mcud1`, `micro_down`, `Like cursor_down in micro mode`}
+ MicroLeft = &Cap{`mcub1`, `micro_left`, `Like cursor_left in micro mode`}
+ MicroRight = &Cap{`mcuf1`, `micro_right`, `Like cursor_right in micro mode`}
+ MicroRowAddress = &Cap{`mvpa`, `micro_row_address`, `Like row_address #1 in micro mode`}
+ MicroUp = &Cap{`mcuu1`, `micro_up`, `Like cursor_up in micro mode`}
+ OrderOfPins = &Cap{`porder`, `order_of_pins`, `Match software bits to print-head pins`}
+ ParmDownMicro = &Cap{`mcud`, `parm_down_micro`, `Like parm_down_cursor in micro mode`}
+ ParmLeftMicro = &Cap{`mcub`, `parm_left_micro`, `Like parm_left_cursor in micro mode`}
+ ParmRightMicro = &Cap{`mcuf`, `parm_right_micro`, `Like parm_right_cursor in micro mode`}
+ ParmUpMicro = &Cap{`mcuu`, `parm_up_micro`, `Like parm_up_cursor in micro mode`}
+ SelectCharSet = &Cap{`scs`, `select_char_set`, `Select character set, #1`}
+ SetBottomMargin = &Cap{`smgb`, `set_bottom_margin`, `Set bottom margin at current line`}
+ SetBottomMarginParm = &Cap{`smgbp`, `set_bottom_margin_parm`, `Set bottom margin at line #1 or (if smgtp is not given) #2 lines from bottom`}
+ SetLeftMarginParm = &Cap{`smglp`, `set_left_margin_parm`, `Set left (right) margin at column #1`}
+ SetRightMarginParm = &Cap{`smgrp`, `set_right_margin_parm`, `Set right margin at column #1`}
+ SetTopMargin = &Cap{`smgt`, `set_top_margin`, `Set top margin at current line`}
+ SetTopMarginParm = &Cap{`smgtp`, `set_top_margin_parm`, `Set top (bottom) margin at row #1`}
+ StartBitImage = &Cap{`sbim`, `start_bit_image`, `Start printing bit image graphics`}
+ StartCharSetDef = &Cap{`scsd`, `start_char_set_def`, `Start character set definition #1, with #2 characters in the set`}
+ StopBitImage = &Cap{`rbim`, `stop_bit_image`, `Stop printing bit image graphics`}
+ StopCharSetDef = &Cap{`rcsd`, `stop_char_set_def`, `End definition of character set #1`}
+ SubscriptCharacters = &Cap{`subcs`, `subscript_characters`, `List of subscriptable characters`}
+ SuperscriptCharacters = &Cap{`supcs`, `superscript_characters`, `List of superscriptable characters`}
+ TheseCauseCr = &Cap{`docr`, `these_cause_cr`, `Printing any of these characters causes CR`}
+ ZeroMotion = &Cap{`zerom`, `zero_motion`, `No motion for subsequent character`}
+ CharSetNames = &Cap{`csnm`, `char_set_names`, `Produce #1'th item from list of character set names`}
+ KeyMouse = &Cap{`kmous`, `key_mouse`, `Mouse event has occurred`}
+ MouseInfo = &Cap{`minfo`, `mouse_info`, `Mouse status information`}
+ ReqMousePos = &Cap{`reqmp`, `req_mouse_pos`, `Request mouse position`}
+ GetMouse = &Cap{`getm`, `get_mouse`, `Curses should get button events, parameter #1 not documented.`}
+ SetAForeground = &Cap{`setaf`, `set_a_foreground`, `Set foreground color to #1, using ANSI escape`}
+ SetABackground = &Cap{`setab`, `set_a_background`, `Set background color to #1, using ANSI escape`}
+ PkeyPlab = &Cap{`pfxl`, `pkey_plab`, `Program function key #1 to type string #2 and show string #3`}
+ DeviceType = &Cap{`devt`, `device_type`, `Indicate language, codeset support`}
+ CodeSetInit = &Cap{`csin`, `code_set_init`, `Init sequence for multiple codesets`}
+ Set0DesSeq = &Cap{`s0ds`, `set0_des_seq`, `Shift to codeset 0 (EUC set 0, ASCII)`}
+ Set1DesSeq = &Cap{`s1ds`, `set1_des_seq`, `Shift to codeset 1`}
+ Set2DesSeq = &Cap{`s2ds`, `set2_des_seq`, `Shift to codeset 2`}
+ Set3DesSeq = &Cap{`s3ds`, `set3_des_seq`, `Shift to codeset 3`}
+ SetLrMargin = &Cap{`smglr`, `set_lr_margin`, `Set both left and right margins to #1, #2. (ML is not in BSD termcap).`}
+ SetTbMargin = &Cap{`smgtb`, `set_tb_margin`, `Sets both top and bottom margins to #1, #2`}
+ BitImageRepeat = &Cap{`birep`, `bit_image_repeat`, `Repeat bit image cell #1 #2 times`}
+ BitImageNewline = &Cap{`binel`, `bit_image_newline`, `Move to next row of the bit image`}
+ BitImageCarriageReturn = &Cap{`bicr`, `bit_image_carriage_return`, `Move to beginning of same row`}
+ ColorNames = &Cap{`colornm`, `color_names`, `Give name for color #1`}
+ DefineBitImageRegion = &Cap{`defbi`, `define_bit_image_region`, `Define rectangular bit image region`}
+ EndBitImageRegion = &Cap{`endbi`, `end_bit_image_region`, `End a bit-image region`}
+ SetColorBand = &Cap{`setcolor`, `set_color_band`, `Change to ribbon color #1`}
+ SetPageLength = &Cap{`slines`, `set_page_length`, `Set page length to #1 lines`}
+ DisplayPcChar = &Cap{`dispc`, `display_pc_char`, `Display PC character #1`}
+ EnterPcCharsetMode = &Cap{`smpch`, `enter_pc_charset_mode`, `Enter PC character display mode`}
+ ExitPcCharsetMode = &Cap{`rmpch`, `exit_pc_charset_mode`, `Exit PC character display mode`}
+ EnterScancodeMode = &Cap{`smsc`, `enter_scancode_mode`, `Enter PC scancode mode`}
+ ExitScancodeMode = &Cap{`rmsc`, `exit_scancode_mode`, `Exit PC scancode mode`}
+ PcTermOptions = &Cap{`pctrm`, `pc_term_options`, `PC terminal options`}
+ ScancodeEscape = &Cap{`scesc`, `scancode_escape`, `Escape for scancode emulation`}
+ AltScancodeEsc = &Cap{`scesa`, `alt_scancode_esc`, `Alternate escape for scancode emulation`}
+ EnterHorizontalHlMode = &Cap{`ehhlm`, `enter_horizontal_hl_mode`, `Enter horizontal highlight mode`}
+ EnterLeftHlMode = &Cap{`elhlm`, `enter_left_hl_mode`, `Enter left highlight mode`}
+ EnterLowHlMode = &Cap{`elohlm`, `enter_low_hl_mode`, `Enter low highlight mode`}
+ EnterRightHlMode = &Cap{`erhlm`, `enter_right_hl_mode`, `Enter right highlight mode`}
+ EnterTopHlMode = &Cap{`ethlm`, `enter_top_hl_mode`, `Enter top highlight mode`}
+ EnterVerticalHlMode = &Cap{`evhlm`, `enter_vertical_hl_mode`, `Enter vertical highlight mode`}
+ SetAAttributes = &Cap{`sgr1`, `set_a_attributes`, `Define second set of video attributes #1-#6`}
+ SetPglenInch = &Cap{`slength`, `set_pglen_inch`, `Set page length to #1 hundredth of an inch (some implementations use sL for termcap).`}
+ TermcapInit2 = &Cap{`OTi2`, `termcap_init2`, `secondary initialization string`}
+ TermcapReset = &Cap{`OTrs`, `termcap_reset`, `terminal reset string`}
+ MagicCookieGlitchUl = &Cap{`OTug`, `magic_cookie_glitch_ul`, `number of blanks left by ul`}
+ BackspacesWithBs = &Cap{`OTbs`, `backspaces_with_bs`, `uses ^H to move left`}
+ CrtNoScrolling = &Cap{`OTns`, `crt_no_scrolling`, `crt cannot scroll`}
+ NoCorrectlyWorkingCr = &Cap{`OTnc`, `no_correctly_working_cr`, `no way to go to start of line`}
+ CarriageReturnDelay = &Cap{`OTdC`, `carriage_return_delay`, `pad needed for CR`}
+ NewLineDelay = &Cap{`OTdN`, `new_line_delay`, `pad needed for LF`}
+ LinefeedIfNotLf = &Cap{`OTnl`, `linefeed_if_not_lf`, `use to move down`}
+ BackspaceIfNotBs = &Cap{`OTbc`, `backspace_if_not_bs`, `move left, if not ^H`}
+ GnuHasMetaKey = &Cap{`OTMT`, `gnu_has_meta_key`, `has meta key`}
+ LinefeedIsNewline = &Cap{`OTNL`, `linefeed_is_newline`, `move down with \n`}
+ BackspaceDelay = &Cap{`OTdB`, `backspace_delay`, `padding required for ^H`}
+ HorizontalTabDelay = &Cap{`OTdT`, `horizontal_tab_delay`, `padding required for ^I`}
+ NumberOfFunctionKeys = &Cap{`OTkn`, `number_of_function_keys`, `count of function keys`}
+ OtherNonFunctionKeys = &Cap{`OTko`, `other_non_function_keys`, `list of self-mapped keycaps`}
+ ArrowKeyMap = &Cap{`OTma`, `arrow_key_map`, `map motion-keys for vi version 2`}
+ HasHardwareTabs = &Cap{`OTpt`, `has_hardware_tabs`, `has 8-char tabs invoked with ^I`}
+ ReturnDoesClrEol = &Cap{`OTxr`, `return_does_clr_eol`, `return clears the line`}
+ AcsUlcorner = &Cap{`OTG2`, `acs_ulcorner`, `single upper left`}
+ AcsLlcorner = &Cap{`OTG3`, `acs_llcorner`, `single lower left`}
+ AcsUrcorner = &Cap{`OTG1`, `acs_urcorner`, `single upper right`}
+ AcsLrcorner = &Cap{`OTG4`, `acs_lrcorner`, `single lower right`}
+ AcsLtee = &Cap{`OTGR`, `acs_ltee`, `tee pointing right`}
+ AcsRtee = &Cap{`OTGL`, `acs_rtee`, `tee pointing left`}
+ AcsBtee = &Cap{`OTGU`, `acs_btee`, `tee pointing up`}
+ AcsTtee = &Cap{`OTGD`, `acs_ttee`, `tee pointing down`}
+ AcsHline = &Cap{`OTGH`, `acs_hline`, `single horizontal line`}
+ AcsVline = &Cap{`OTGV`, `acs_vline`, `single vertical line`}
+ AcsPlus = &Cap{`OTGC`, `acs_plus`, `single intersection`}
+ MemoryLock = &Cap{`meml`, `memory_lock`, `lock memory above cursor`}
+ MemoryUnlock = &Cap{`memu`, `memory_unlock`, `unlock memory`}
+ BoxChars1 = &Cap{`box1`, `box_chars_1`, `box characters primary set`}
+
+ // Extentions
+ CO = &Cap{`CO`, `userdef`, `number of indexed colors overlaying RGB space (ncurses)`}
+ E3 = &Cap{`E3`, `userdef`, `clears the terminal's scrollback buffer. (ncurses)`}
+ NQ = &Cap{`NQ`, `userdef`, `terminal does not support query/response (ncurses)`}
+ RGB = &Cap{`RGB`, `userdef`, `use direct colors with 1/3 of color-pair bits per color. (ncurses)`}
+ TS = &Cap{`TS`, `userdef`, `like "tsl", but uses no parameter. (ncurses)`}
+ U8 = &Cap{`U8`, `userdef`, `terminal does/does not support VT100 SI/SO when processing UTF-8 encoding. (ncurses)`}
+ XM = &Cap{`XM`, `userdef`, `initialize alternate xterm mouse mode (ncurses)`}
+ Grbom = &Cap{`grbom`, `userdef`, `disable real bold (not intensity bright) mode. (ncurses)`}
+ Gsbom = &Cap{`gsbom`, `userdef`, `enable real bold (not intensity bright) mode. (ncurses)`}
+ Xm = &Cap{`xm`, `userdef`, `mouse response, no parameters (ncurses)`}
+ Rmol = &Cap{`Rmol`, `userdef`, `remove overline-mode (mintty)`}
+ Smol = &Cap{`Smol`, `userdef`, `set overline-mode (mintty)`}
+ Blink2 = &Cap{`blink2`, `userdef`, `turn on rapid blinking (mintty)`}
+ Norm = &Cap{`norm`, `userdef`, `turn off bold and half-bright mode (mintty)`}
+ Opaq = &Cap{`opaq`, `userdef`, `turn off blank mode (mintty)`}
+ Setal = &Cap{`setal`, `userdef`, `set underline-color (mintty)`}
+ Smul2 = &Cap{`smul2`, `userdef`, `begin double underline mode (mintty)`}
+ AN = &Cap{`AN`, `userdef`, `turn on autonuke. (screen)`}
+ AX = &Cap{`AX`, `userdef`, `understands ANSI set default fg/bg color (\E[39m / \E[49m). (screen)`}
+ C0 = &Cap{`C0`, `userdef`, `use the string as a conversion table for font '0', like acsc. (screen)`}
+ C8 = &Cap{`C8`, `userdef`, `terminal shows bold as high-intensity colors. (screen)`}
+ CE = &Cap{`CE`, `userdef`, `switch cursor-keys back to normal mode. (screen)`}
+ CS = &Cap{`CS`, `userdef`, `switch cursor-keys to application mode. (screen)`}
+ E0 = &Cap{`E0`, `userdef`, `switch charset 'G0' back to standard charset. Default is '\E(B'. (screen)`}
+ G0 = &Cap{`G0`, `userdef`, `terminal can deal with ISO 2022 font selection sequences. (screen)`}
+ KJ = &Cap{`KJ`, `userdef`, `set the encoding of the terminal. (screen)`}
+ OL = &Cap{`OL`, `userdef`, `set the screen program's output buffer limit. (screen)`}
+ S0 = &Cap{`S0`, `userdef`, `switch charset 'G0' to the specified charset. Default is '\E(%.'. (screen)`}
+ TF = &Cap{`TF`, `userdef`, `add missing capabilities to screen's termcap/info entry. (Set by default). (screen)`}
+ WS = &Cap{`WS`, `userdef`, `resize display. This capability has the desired width and height as arguments. SunView(tm) example: '\E[8;%d;%dt'. (screen)`}
+ XC = &Cap{`XC`, `userdef`, `describe a translation of characters to strings depending on the current font. (screen)`}
+ XT = &Cap{`XT`, `userdef`, `terminal understands special xterm sequences (OSC, mouse tracking). (screen)`}
+ Z0 = &Cap{`Z0`, `userdef`, `change width to 132 columns. (screen)`}
+ Z1 = &Cap{`Z1`, `userdef`, `change width to 80 columns. (screen)`}
+ Cr = &Cap{`Cr`, `userdef`, `restore the default cursor color. (tmux)`}
+ Cs = &Cap{`Cs`, `userdef`, `set the cursor color. (tmux)`}
+ Csr = &Cap{`Csr`, `userdef`, `change the cursor style, overriding Ss. (tmux)`}
+ Ms = &Cap{`Ms`, `userdef`, `store the current buffer in the host terminal's selection (clipboard). (tmux)`}
+ Se = &Cap{`Se`, `userdef`, `reset the cursor style to the terminal initial state. (tmux)`}
+ Smulx = &Cap{`Smulx`, `userdef`, `modify the appearance of underlines in VTE. (tmux)`}
+ Ss = &Cap{`Ss`, `userdef`, `change the cursor style. (tmux)`}
+ Rmxx = &Cap{`rmxx`, `userdef`, `reset ECMA-48 strikeout/crossed-out attributes. (tmux)`}
+ Smxx = &Cap{`smxx`, `userdef`, `set ECMA-48 strikeout/crossed-out attributes. (tmux)`}
+ BD = &Cap{`BD`, `userdef`, `disables bracketed paste (vim)`}
+ BE = &Cap{`BE`, `userdef`, `enables bracketed paste (vim)`}
+ PE = &Cap{`PE`, `userdef`, `is sent after pasted text (vim)`}
+ PS = &Cap{`PS`, `userdef`, `is sent before pasted text (vim)`}
+ RV = &Cap{`RV`, `userdef`, `report terminal secondary device attributes (vim)`}
+ XR = &Cap{`XR`, `userdef`, `report terminal version as a free-format string. (vim)`}
+ XF = &Cap{`XF`, `userdef`, `terminal supports xterm focus in/out (vim)`}
+ Fd = &Cap{`fd`, `userdef`, `disable xterm focus-events (vim)`}
+ Fe = &Cap{`fe`, `userdef`, `enable xterm focus-events (vim)`}
+ Rv = &Cap{`rv`, `userdef`, `response to RV, regular expression (vim)`}
+ Xr = &Cap{`xr`, `userdef`, `response to XR, regular expression (vim)`}
+ Csl = &Cap{`csl`, `userdef`, `clear status line (xterm)`}
+ KDC3 = &Cap{`kDC3`, `userdef`, `alt delete-character (xterm)`}
+ KDC4 = &Cap{`kDC4`, `userdef`, `shift+alt delete-character (xterm)`}
+ KDC5 = &Cap{`kDC5`, `userdef`, `control delete-character (xterm)`}
+ KDC6 = &Cap{`kDC6`, `userdef`, `shift+control delete-character (xterm)`}
+ KDC7 = &Cap{`kDC7`, `userdef`, `alt+control delete-character (xterm)`}
+ KDN = &Cap{`kDN`, `userdef`, `shift down-cursor (xterm)`}
+ KDN3 = &Cap{`kDN3`, `userdef`, `alt down-cursor (xterm)`}
+ KDN4 = &Cap{`kDN4`, `userdef`, `shift+alt down-cursor (xterm)`}
+ KDN5 = &Cap{`kDN5`, `userdef`, `control down-cursor (xterm)`}
+ KDN6 = &Cap{`kDN6`, `userdef`, `shift+control down-cursor (xterm)`}
+ KDN7 = &Cap{`kDN7`, `userdef`, `alt+control down-cursor (xterm)`}
+ KEND3 = &Cap{`kEND3`, `userdef`, `alt end (xterm)`}
+ KEND4 = &Cap{`kEND4`, `userdef`, `shift+alt end (xterm)`}
+ KEND5 = &Cap{`kEND5`, `userdef`, `control end (xterm)`}
+ KEND6 = &Cap{`kEND6`, `userdef`, `shift+control end (xterm)`}
+ KEND7 = &Cap{`kEND7`, `userdef`, `alt+control end (xterm)`}
+ KHOM3 = &Cap{`kHOM3`, `userdef`, `alt home (xterm)`}
+ KHOM4 = &Cap{`kHOM4`, `userdef`, `shift+alt home (xterm)`}
+ KHOM5 = &Cap{`kHOM5`, `userdef`, `control home (xterm)`}
+ KHOM6 = &Cap{`kHOM6`, `userdef`, `shift+control home (xterm)`}
+ KHOM7 = &Cap{`kHOM7`, `userdef`, `alt+control home (xterm)`}
+ KIC3 = &Cap{`kIC3`, `userdef`, `alt insert-character (xterm)`}
+ KIC4 = &Cap{`kIC4`, `userdef`, `shift+alt insert-character (xterm)`}
+ KIC5 = &Cap{`kIC5`, `userdef`, `control insert-character (xterm)`}
+ KIC6 = &Cap{`kIC6`, `userdef`, `shift+control insert-character (xterm)`}
+ KIC7 = &Cap{`kIC7`, `userdef`, `alt+control insert-character (xterm)`}
+ KLFT3 = &Cap{`kLFT3`, `userdef`, `alt left-cursor (xterm)`}
+ KLFT4 = &Cap{`kLFT4`, `userdef`, `shift+alt left-cursor (xterm)`}
+ KLFT5 = &Cap{`kLFT5`, `userdef`, `control left-cursor (xterm)`}
+ KLFT6 = &Cap{`kLFT6`, `userdef`, `shift+control left-cursor (xterm)`}
+ KLFT7 = &Cap{`kLFT7`, `userdef`, `alt+control left-cursor (xterm)`}
+ KNXT3 = &Cap{`kNXT3`, `userdef`, `alt next (xterm)`}
+ KNXT4 = &Cap{`kNXT4`, `userdef`, `shift+alt next (xterm)`}
+ KNXT5 = &Cap{`kNXT5`, `userdef`, `control next (xterm)`}
+ KNXT6 = &Cap{`kNXT6`, `userdef`, `shift+control next (xterm)`}
+ KNXT7 = &Cap{`kNXT7`, `userdef`, `alt+control next (xterm)`}
+ KPRV3 = &Cap{`kPRV3`, `userdef`, `alt previous (xterm)`}
+ KPRV4 = &Cap{`kPRV4`, `userdef`, `shift+alt previous (xterm)`}
+ KPRV5 = &Cap{`kPRV5`, `userdef`, `control previous (xterm)`}
+ KPRV6 = &Cap{`kPRV6`, `userdef`, `shift+control previous (xterm)`}
+ KPRV7 = &Cap{`kPRV7`, `userdef`, `alt+control previous (xterm)`}
+ KRIT3 = &Cap{`kRIT3`, `userdef`, `alt right-cursor (xterm)`}
+ KRIT4 = &Cap{`kRIT4`, `userdef`, `shift+alt right-cursor (xterm)`}
+ KRIT5 = &Cap{`kRIT5`, `userdef`, `control right-cursor (xterm)`}
+ KRIT6 = &Cap{`kRIT6`, `userdef`, `shift+control right-cursor (xterm)`}
+ KRIT7 = &Cap{`kRIT7`, `userdef`, `alt+control right-cursor (xterm)`}
+ KUP = &Cap{`kUP`, `userdef`, `shift up-cursor (xterm)`}
+ KUP3 = &Cap{`kUP3`, `userdef`, `alt up-cursor (xterm)`}
+ KUP4 = &Cap{`kUP4`, `userdef`, `shift+alt up-cursor (xterm)`}
+ KUP5 = &Cap{`kUP5`, `userdef`, `control up-cursor (xterm)`}
+ KUP6 = &Cap{`kUP6`, `userdef`, `shift+control up-cursor (xterm)`}
+ KUP7 = &Cap{`kUP7`, `userdef`, `alt+control up-cursor (xterm)`}
+ Ka2 = &Cap{`ka2`, `userdef`, `vt220-keypad extensions (xterm)`}
+ Kb1 = &Cap{`kb1`, `userdef`, `vt220-keypad extensions (xterm)`}
+ Kb3 = &Cap{`kb3`, `userdef`, `vt220-keypad extensions (xterm)`}
+ Kc2 = &Cap{`kc2`, `userdef`, `vt220-keypad extensions (xterm)`}
+ KxIN = &Cap{`kxIN`, `userdef`, `mouse response on focus-in (xterm)`}
+ KxOUT = &Cap{`kxOUT`, `userdef`, `mouse response on focus-out (xterm)`}
+)
diff --git a/tui/termfo/caps/table.go b/tui/termfo/caps/table.go
new file mode 100644
index 0000000..f0e4d6a
--- /dev/null
+++ b/tui/termfo/caps/table.go
@@ -0,0 +1,640 @@
+// Code generated by term.h.zsh; DO NOT EDIT.
+
+package caps
+
+var unused *Cap = nil
+
+var TableBools = []*Cap{
+ AutoLeftMargin,
+ AutoRightMargin,
+ NoEscCtlc,
+ CeolStandoutGlitch,
+ EatNewlineGlitch,
+ EraseOverstrike,
+ GenericType,
+ HardCopy,
+ HasMetaKey,
+ HasStatusLine,
+ InsertNullGlitch,
+ MemoryAbove,
+ MemoryBelow,
+ MoveInsertMode,
+ MoveStandoutMode,
+ OverStrike,
+ StatusLineEscOk,
+ DestTabsMagicSmso,
+ TildeGlitch,
+ TransparentUnderline,
+ XonXoff,
+ NeedsXonXoff,
+ PrtrSilent,
+ HardCursor,
+ NonRevRmcup,
+ NoPadChar,
+ NonDestScrollRegion,
+ CanChange,
+ BackColorErase,
+ HueLightnessSaturation,
+ ColAddrGlitch,
+ CrCancelsMicroMode,
+ HasPrintWheel,
+ RowAddrGlitch,
+ SemiAutoRightMargin,
+ CpiChangesRes,
+ LpiChangesRes,
+ BackspacesWithBs,
+ CrtNoScrolling,
+ NoCorrectlyWorkingCr,
+ GnuHasMetaKey,
+ LinefeedIsNewline,
+ HasHardwareTabs,
+ ReturnDoesClrEol,
+
+ // Extensions
+ NQ,
+ RGB,
+ AN,
+ AX,
+ C8,
+ G0,
+ TF,
+ XT,
+ XF,
+}
+
+var TableNums = []*Cap{
+ Columns,
+ InitTabs,
+ Lines,
+ LinesOfMemory,
+ MagicCookieGlitch,
+ PaddingBaudRate,
+ VirtualTerminal,
+ WidthStatusLine,
+ NumLabels,
+ LabelHeight,
+ LabelWidth,
+ MaxAttributes,
+ MaximumWindows,
+ MaxColors,
+ MaxPairs,
+ NoColorVideo,
+ BufferCapacity,
+ DotVertSpacing,
+ DotHorzSpacing,
+ MaxMicroAddress,
+ MaxMicroJump,
+ MicroColSize,
+ MicroLineSize,
+ NumberOfPins,
+ OutputResChar,
+ OutputResLine,
+ OutputResHorzInch,
+ OutputResVertInch,
+ PrintRate,
+ WideCharSize,
+ Buttons,
+ BitImageEntwining,
+ BitImageType,
+ MagicCookieGlitchUl,
+ CarriageReturnDelay,
+ NewLineDelay,
+ BackspaceDelay,
+ HorizontalTabDelay,
+ NumberOfFunctionKeys,
+
+ // Extensions
+ CO,
+ RGB,
+ U8,
+ OL,
+}
+
+var TableStrs = []*Cap{
+ BackTab,
+ Bell,
+ CarriageReturn,
+ ChangeScrollRegion,
+ ClearAllTabs,
+ ClearScreen,
+ ClrEol,
+ ClrEos,
+ ColumnAddress,
+ CommandCharacter,
+ CursorAddress,
+ CursorDown,
+ CursorHome,
+ CursorInvisible,
+ CursorLeft,
+ CursorMemAddress,
+ CursorNormal,
+ CursorRight,
+ CursorToLl,
+ CursorUp,
+ CursorVisible,
+ DeleteCharacter,
+ DeleteLine,
+ DisStatusLine,
+ DownHalfLine,
+ EnterAltCharsetMode,
+ EnterBlinkMode,
+ EnterBoldMode,
+ EnterCaMode,
+ EnterDeleteMode,
+ EnterDimMode,
+ EnterInsertMode,
+ EnterSecureMode,
+ EnterProtectedMode,
+ EnterReverseMode,
+ EnterStandoutMode,
+ EnterUnderlineMode,
+ EraseChars,
+ ExitAltCharsetMode,
+ ExitAttributeMode,
+ ExitCaMode,
+ ExitDeleteMode,
+ ExitInsertMode,
+ ExitStandoutMode,
+ ExitUnderlineMode,
+ FlashScreen,
+ FormFeed,
+ FromStatusLine,
+ Init1string,
+ Init2string,
+ Init3string,
+ InitFile,
+ InsertCharacter,
+ InsertLine,
+ InsertPadding,
+ KeyBackspace,
+ KeyCatab,
+ KeyClear,
+ KeyCtab,
+ KeyDc,
+ KeyDl,
+ KeyDown,
+ KeyEic,
+ KeyEol,
+ KeyEos,
+ KeyF0,
+ KeyF1,
+ KeyF10,
+ KeyF2,
+ KeyF3,
+ KeyF4,
+ KeyF5,
+ KeyF6,
+ KeyF7,
+ KeyF8,
+ KeyF9,
+ KeyHome,
+ KeyIc,
+ KeyIl,
+ KeyLeft,
+ KeyLl,
+ KeyNpage,
+ KeyPpage,
+ KeyRight,
+ KeySf,
+ KeySr,
+ KeyStab,
+ KeyUp,
+ KeypadLocal,
+ KeypadXmit,
+ LabF0,
+ LabF1,
+ LabF10,
+ LabF2,
+ LabF3,
+ LabF4,
+ LabF5,
+ LabF6,
+ LabF7,
+ LabF8,
+ LabF9,
+ MetaOff,
+ MetaOn,
+ Newline,
+ PadChar,
+ ParmDch,
+ ParmDeleteLine,
+ ParmDownCursor,
+ ParmIch,
+ ParmIndex,
+ ParmInsertLine,
+ ParmLeftCursor,
+ ParmRightCursor,
+ ParmRindex,
+ ParmUpCursor,
+ PkeyKey,
+ PkeyLocal,
+ PkeyXmit,
+ PrintScreen,
+ PrtrOff,
+ PrtrOn,
+ RepeatChar,
+ Reset1string,
+ Reset2string,
+ Reset3string,
+ ResetFile,
+ RestoreCursor,
+ RowAddress,
+ SaveCursor,
+ ScrollForward,
+ ScrollReverse,
+ SetAttributes,
+ SetTab,
+ SetWindow,
+ Tab,
+ ToStatusLine,
+ UnderlineChar,
+ UpHalfLine,
+ InitProg,
+ KeyA1,
+ KeyA3,
+ KeyB2,
+ KeyC1,
+ KeyC3,
+ PrtrNon,
+ CharPadding,
+ AcsChars,
+ PlabNorm,
+ KeyBtab,
+ EnterXonMode,
+ ExitXonMode,
+ EnterAmMode,
+ ExitAmMode,
+ XonCharacter,
+ XoffCharacter,
+ EnaAcs,
+ LabelOn,
+ LabelOff,
+ KeyBeg,
+ KeyCancel,
+ KeyClose,
+ KeyCommand,
+ KeyCopy,
+ KeyCreate,
+ KeyEnd,
+ KeyEnter,
+ KeyExit,
+ KeyFind,
+ KeyHelp,
+ KeyMark,
+ KeyMessage,
+ KeyMove,
+ KeyNext,
+ KeyOpen,
+ KeyOptions,
+ KeyPrevious,
+ KeyPrint,
+ KeyRedo,
+ KeyReference,
+ KeyRefresh,
+ KeyReplace,
+ KeyRestart,
+ KeyResume,
+ KeySave,
+ KeySuspend,
+ KeyUndo,
+ KeySbeg,
+ KeyScancel,
+ KeyScommand,
+ KeyScopy,
+ KeyScreate,
+ KeySdc,
+ KeySdl,
+ KeySelect,
+ KeySend,
+ KeySeol,
+ KeySexit,
+ KeySfind,
+ KeyShelp,
+ KeyShome,
+ KeySic,
+ KeySleft,
+ KeySmessage,
+ KeySmove,
+ KeySnext,
+ KeySoptions,
+ KeySprevious,
+ KeySprint,
+ KeySredo,
+ KeySreplace,
+ KeySright,
+ KeySrsume,
+ KeySsave,
+ KeySsuspend,
+ KeySundo,
+ ReqForInput,
+ KeyF11,
+ KeyF12,
+ KeyF13,
+ KeyF14,
+ KeyF15,
+ KeyF16,
+ KeyF17,
+ KeyF18,
+ KeyF19,
+ KeyF20,
+ KeyF21,
+ KeyF22,
+ KeyF23,
+ KeyF24,
+ KeyF25,
+ KeyF26,
+ KeyF27,
+ KeyF28,
+ KeyF29,
+ KeyF30,
+ KeyF31,
+ KeyF32,
+ KeyF33,
+ KeyF34,
+ KeyF35,
+ KeyF36,
+ KeyF37,
+ KeyF38,
+ KeyF39,
+ KeyF40,
+ KeyF41,
+ KeyF42,
+ KeyF43,
+ KeyF44,
+ KeyF45,
+ KeyF46,
+ KeyF47,
+ KeyF48,
+ KeyF49,
+ KeyF50,
+ KeyF51,
+ KeyF52,
+ KeyF53,
+ KeyF54,
+ KeyF55,
+ KeyF56,
+ KeyF57,
+ KeyF58,
+ KeyF59,
+ KeyF60,
+ KeyF61,
+ KeyF62,
+ KeyF63,
+ ClrBol,
+ ClearMargins,
+ SetLeftMargin,
+ SetRightMargin,
+ LabelFormat,
+ SetClock,
+ DisplayClock,
+ RemoveClock,
+ CreateWindow,
+ GotoWindow,
+ Hangup,
+ DialPhone,
+ QuickDial,
+ Tone,
+ Pulse,
+ FlashHook,
+ FixedPause,
+ WaitTone,
+ User0,
+ User1,
+ User2,
+ User3,
+ User4,
+ User5,
+ User6,
+ User7,
+ User8,
+ User9,
+ OrigPair,
+ OrigColors,
+ InitializeColor,
+ InitializePair,
+ SetColorPair,
+ SetForeground,
+ SetBackground,
+ ChangeCharPitch,
+ ChangeLinePitch,
+ ChangeResHorz,
+ ChangeResVert,
+ DefineChar,
+ EnterDoublewideMode,
+ EnterDraftQuality,
+ EnterItalicsMode,
+ EnterLeftwardMode,
+ EnterMicroMode,
+ EnterNearLetterQuality,
+ EnterNormalQuality,
+ EnterShadowMode,
+ EnterSubscriptMode,
+ EnterSuperscriptMode,
+ EnterUpwardMode,
+ ExitDoublewideMode,
+ ExitItalicsMode,
+ ExitLeftwardMode,
+ ExitMicroMode,
+ ExitShadowMode,
+ ExitSubscriptMode,
+ ExitSuperscriptMode,
+ ExitUpwardMode,
+ MicroColumnAddress,
+ MicroDown,
+ MicroLeft,
+ MicroRight,
+ MicroRowAddress,
+ MicroUp,
+ OrderOfPins,
+ ParmDownMicro,
+ ParmLeftMicro,
+ ParmRightMicro,
+ ParmUpMicro,
+ SelectCharSet,
+ SetBottomMargin,
+ SetBottomMarginParm,
+ SetLeftMarginParm,
+ SetRightMarginParm,
+ SetTopMargin,
+ SetTopMarginParm,
+ StartBitImage,
+ StartCharSetDef,
+ StopBitImage,
+ StopCharSetDef,
+ SubscriptCharacters,
+ SuperscriptCharacters,
+ TheseCauseCr,
+ ZeroMotion,
+ CharSetNames,
+ KeyMouse,
+ MouseInfo,
+ ReqMousePos,
+ GetMouse,
+ SetAForeground,
+ SetABackground,
+ PkeyPlab,
+ DeviceType,
+ CodeSetInit,
+ Set0DesSeq,
+ Set1DesSeq,
+ Set2DesSeq,
+ Set3DesSeq,
+ SetLrMargin,
+ SetTbMargin,
+ BitImageRepeat,
+ BitImageNewline,
+ BitImageCarriageReturn,
+ ColorNames,
+ DefineBitImageRegion,
+ EndBitImageRegion,
+ SetColorBand,
+ SetPageLength,
+ DisplayPcChar,
+ EnterPcCharsetMode,
+ ExitPcCharsetMode,
+ EnterScancodeMode,
+ ExitScancodeMode,
+ PcTermOptions,
+ ScancodeEscape,
+ AltScancodeEsc,
+ EnterHorizontalHlMode,
+ EnterLeftHlMode,
+ EnterLowHlMode,
+ EnterRightHlMode,
+ EnterTopHlMode,
+ EnterVerticalHlMode,
+ SetAAttributes,
+ SetPglenInch,
+ TermcapInit2,
+ TermcapReset,
+ LinefeedIfNotLf,
+ BackspaceIfNotBs,
+ OtherNonFunctionKeys,
+ ArrowKeyMap,
+ AcsUlcorner,
+ AcsLlcorner,
+ AcsUrcorner,
+ AcsLrcorner,
+ AcsLtee,
+ AcsRtee,
+ AcsBtee,
+ AcsTtee,
+ AcsHline,
+ AcsVline,
+ AcsPlus,
+ MemoryLock,
+ MemoryUnlock,
+ BoxChars1,
+
+ // Extensions
+ E3,
+ RGB,
+ TS,
+ XM,
+ Grbom,
+ Gsbom,
+ Xm,
+ Xm,
+ Xm,
+ Xm,
+ Xm,
+ Xm,
+ Xm,
+ Xm,
+ Xm,
+ Rmol,
+ Smol,
+ Blink2,
+ Norm,
+ Opaq,
+ Setal,
+ Smul2,
+ C0,
+ CE,
+ CS,
+ E0,
+ KJ,
+ S0,
+ WS,
+ XC,
+ Z0,
+ Z1,
+ Cr,
+ Cs,
+ Csr,
+ Ms,
+ Se,
+ Smulx,
+ Ss,
+ Rmxx,
+ Smxx,
+ BD,
+ BE,
+ PE,
+ PS,
+ RV,
+ XR,
+ Fd,
+ Fe,
+ Rv,
+ Xr,
+ Csl,
+ KDC3,
+ KDC4,
+ KDC5,
+ KDC6,
+ KDC7,
+ KDN,
+ KDN3,
+ KDN4,
+ KDN5,
+ KDN6,
+ KDN7,
+ KEND3,
+ KEND4,
+ KEND5,
+ KEND6,
+ KEND7,
+ KHOM3,
+ KHOM4,
+ KHOM5,
+ KHOM6,
+ KHOM7,
+ KIC3,
+ KIC4,
+ KIC5,
+ KIC6,
+ KIC7,
+ KLFT3,
+ KLFT4,
+ KLFT5,
+ KLFT6,
+ KLFT7,
+ KNXT3,
+ KNXT4,
+ KNXT5,
+ KNXT6,
+ KNXT7,
+ KPRV3,
+ KPRV4,
+ KPRV5,
+ KPRV6,
+ KPRV7,
+ KRIT3,
+ KRIT4,
+ KRIT5,
+ KRIT6,
+ KRIT7,
+ KUP,
+ KUP3,
+ KUP4,
+ KUP5,
+ KUP6,
+ KUP7,
+ Ka2,
+ Kb1,
+ Kb3,
+ Kc2,
+ KxIN,
+ KxOUT,
+}
diff --git a/tui/termfo/cmd/termfo/build.go b/tui/termfo/cmd/termfo/build.go
new file mode 100644
index 0000000..c7e1223
--- /dev/null
+++ b/tui/termfo/cmd/termfo/build.go
@@ -0,0 +1,168 @@
+//go:build ignore
+
+package main
+
+import (
+ "fmt"
+ "os"
+ "strings"
+)
+
+// This is a little bit of an educated guess. I'm not sure what defaults these
+// use.
+var commonTerms = []string{
+ "alacritty",
+ "ansi", "pcansi",
+ "cygwin",
+ "eterm", "eterm-color",
+ "gnome", "gnome-256color", "vte", "vte-2018", "vte-256color", "vte-direct",
+ "vscode", "vscode-direct",
+ "konsole", "konsole-256color",
+ "kterm",
+ "linux",
+ "rxvt", "rxvt-256color", "rxvt-88color", "rxvt-unicode", "rxvt-unicode-256color",
+ "screen", "screen-256color",
+ "st", "st-256color",
+ "termite",
+ "tmux", "tmux-256color",
+ "vt100", "vt102", "vt220", "vt320", "vt400", "vt420",
+ "xfce",
+ "xterm", "xterm-88color", "xterm-256color",
+ "xterm-kitty",
+}
+
+// TODO: make output more stable; lots of map usage here.
+// TODO: we can avoid a lot of duplication.
+//
+// TODO: change to:
+//
+// func init() {
+// termfo.Loaders = append(termfo.Loaders, func(t string) *termfo.Terminfo {
+// switch t {
+// case "foo":
+// extMap := ""
+// return &termfo.Terminfo{}
+//
+// case "bar":
+// // ...
+// }
+// })
+// }
+func build(pkg string, terms ...string) {
+ for i, t := range terms {
+ if t == "%common" {
+ terms = append(append(terms[:i], terms[i+1:]...), commonTerms...)
+ break
+ }
+ }
+ noDup := make(map[string]struct{})
+ for _, t := range terms {
+ noDup[t] = struct{}{}
+ }
+
+ b := new(strings.Builder)
+ b.Grow(10_000)
+ b.WriteString("// Code generated by termfo; DO NOT EDIT.\n\n/* Command to generate:\n\n")
+ fmt.Fprintf(b, " termfo %s \\\n ", pkg)
+ b.WriteString(strings.Join(os.Args[3:], " \\\n "))
+ fmt.Fprintf(b, "\n*/\n\npackage %s\n\nimport \"citrons.xyz/talk/tui/termfo\"\n\n", pkg)
+
+ // Extended caps are created first; we want them to be the same pointer
+ // in Bools and Extended.
+ fmt.Fprintf(b, "func init() {\n\textMap := map[string][]*terminfo.Cap{\n")
+ extMap := make(map[string]map[string]int)
+ for t := range noDup {
+ ti, err := termfo.New(t)
+ if err != nil {
+ fatalf("could not load terminfo for %q; make sure it's present on "+
+ "your system (you may need to install an additional package", t)
+ }
+
+ extMap[t] = make(map[string]int)
+ fmt.Fprintf(b, "\t\t%#v: {\n", t)
+ for i, e := range ti.Extended {
+ fmt.Fprintf(b, "\t\t\t%#v,\n", e)
+ extMap[t][e.Short] = i
+ i++
+ }
+ fmt.Fprintf(b, "\t\t},\n")
+ }
+
+ fmt.Fprintf(b, "\t}\n\n\ttermfo.Builtins(map[string]*termfo.Terminfo{\n")
+ for t := range noDup {
+ ti, err := termfo.New(t)
+ if err != nil {
+ fatalf("could not load terminfo for %q; make sure it's present on "+
+ "your system (you may need to install an additional package", t)
+ }
+
+ ext := ""
+ for _, e := range ti.Extended {
+ ext += fmt.Sprintf("\t\t\t\textMap[%#v][%d],\n", t, extMap[t][e.Short])
+ }
+ bools := ""
+ for c := range ti.Bools {
+ if e, ok := extMap[t][c.Short]; ok {
+ bools += fmt.Sprintf("\t\t\t\textMap[%#v][%d]: struct{}{},\n", t, e)
+ continue
+ }
+ for i, b := range termfo.CapBools {
+ if c.Short == b.Short {
+ bools += fmt.Sprintf("\t\t\t\ttermfo.CapBools[%d]: struct{}{},\n", i)
+ break
+ }
+ }
+ }
+ nums := ""
+ for c, v := range ti.Numbers {
+ if e, ok := extMap[t][c.Short]; ok {
+ nums += fmt.Sprintf("\t\t\t\textMap[%#v][%d]: %d,\n", t, e, v)
+ continue
+ }
+ for i, b := range termfo.CapNums {
+ if c.Short == b.Short {
+ nums += fmt.Sprintf("\t\t\t\ttermfo.CapNums[%d]: %d,\n", i, v)
+ break
+ }
+ }
+ }
+ strs := ""
+ for c, v := range ti.Strings {
+ if e, ok := extMap[t][c.Short]; ok {
+ strs += fmt.Sprintf("\t\t\t\textMap[%#v][%d]: %#v,\n", t, e, v)
+ continue
+ }
+ for i, b := range termfo.CapStrs {
+ if c.Short == b.Short {
+ strs += fmt.Sprintf("\t\t\t\ttermfo.CapStrs[%d]: %#v,\n", i, v)
+ break
+ }
+ }
+ }
+
+ fmt.Fprintf(b, "\t\t%#v: &termfo.Terminfo{", ti.Name)
+ fmt.Fprintf(b, `
+ Name: %#[1]v,
+ Desc: %#[2]v,
+ Aliases: %#[3]v,
+ IntSize: %[4]d,
+ Bools: map[*termfo.Cap]struct{}{
+%[5]s
+ },
+ Numbers: map[*termfo.Cap]int32{
+%[6]s
+ },
+ Strings: map[*termfo.Cap]string{
+%[7]s
+ },
+ Extended: []*termfo.Cap{
+%[8]s
+ },
+ },`, ti.Name, ti.Desc, ti.Aliases, ti.IntSize, bools, nums, strs, ext)
+ b.WriteRune('\n')
+ }
+
+ fmt.Fprintf(b, "\t})\n}\n")
+
+ fmt.Println(b)
+}
diff --git a/tui/termfo/cmd/termfo/findcap.go b/tui/termfo/cmd/termfo/findcap.go
new file mode 100644
index 0000000..7664c25
--- /dev/null
+++ b/tui/termfo/cmd/termfo/findcap.go
@@ -0,0 +1,150 @@
+package main
+
+import (
+ "fmt"
+ "sort"
+ "strings"
+
+ "citrons.xyz/talk/tui/termfo"
+ "citrons.xyz/talk/tui/termfo/caps"
+)
+
+func historical(term string) bool {
+ _, ok := oldterm[term]
+ return ok
+}
+
+func termWithCap(name string, inclHist, expand, notSupported bool) {
+ c := findCap(name)
+ if c == nil {
+ fatalf("no cap: %q", name)
+ }
+
+ list := make(map[string][]string)
+ for _, a := range allInfos("") {
+ if !inclHist && historical(a) {
+ continue
+ }
+ ti, err := termfo.New(a)
+ if err != nil {
+ fatalf(a, "\t", err)
+ continue
+ }
+
+ if v, ok := findCapInTi(ti, c); ok {
+ list[v] = append(list[v], a)
+ } else if notSupported {
+ list["[not-supported]"] = append(list["[not-supported]"], a)
+ }
+ }
+ type termWithCap struct {
+ cap string
+ terms []string
+ }
+ order := make([]termWithCap, 0, len(list))
+ pad := 0
+ for k, v := range list {
+ k = fmt.Sprintf("%q", k)
+ vv := v
+ if !expand {
+ vv = dedup(v)
+ }
+ order = append(order, termWithCap{cap: k, terms: vv})
+ if len(k) > pad {
+ pad = len(k)
+ }
+ }
+ if pad > 60 { // Some have ridiculously long escapes
+ pad = 60
+ }
+ sort.SliceStable(order, func(i, j int) bool { return len(order[i].terms) > len(order[j].terms) })
+
+ w := 100
+ pad += 4
+ padf := fmt.Sprintf("%%-%ds", pad)
+
+ fmt.Printf("Capability %q / %q: %s\n\n", c.Short, c.Long, c.Desc)
+ for _, o := range order {
+ p := o.terms[0]
+ l := pad
+ for _, vv := range o.terms[1:] {
+ if l > w {
+ p += "\n" + strings.Repeat(" ", pad+7) + vv
+ l = pad + len(vv) + 0
+ } else {
+ p += " " + vv
+ l += len(vv) + 2
+ }
+ }
+ fmt.Printf(padf+" → %3d %s\n", o.cap, len(o.terms), p)
+ if len(o.terms) > 10 {
+ fmt.Println()
+ }
+ }
+}
+
+func findCap(name string) *caps.Cap {
+ for _, c := range caps.TableBools {
+ if c.Short == name || c.Long == name {
+ return c
+ }
+ }
+ for _, c := range caps.TableNums {
+ if c.Short == name || c.Long == name {
+ return c
+ }
+ }
+ for _, c := range caps.TableStrs {
+ if c.Short == name || c.Long == name {
+ return c
+ }
+ }
+ return nil
+}
+
+func findCapInTi(ti *termfo.Terminfo, c *caps.Cap) (string, bool) {
+ if v, ok := ti.Strings[c]; ok {
+ return v, ok
+ }
+ if v2, ok := ti.Numbers[c]; ok {
+ return fmt.Sprintf("%v", v2), ok
+ }
+ if _, ok := ti.Bools[c]; ok {
+ return fmt.Sprintf("%v", ok), ok
+ }
+ return "", false
+}
+
+// List xterm, xterm-256color, xterm-mono, etc. as xterm*; no need to list them
+// all one-by-one here.
+func dedup(list []string) []string {
+ dup := make(map[string][]string)
+ for _, l := range list {
+ i := strings.IndexAny(l, "-+")
+ if i > -1 {
+ dup[l[:i]] = append(dup[l[:i]], l)
+ } else {
+ dup[l] = append(dup[l], l)
+ }
+ }
+
+ type o struct {
+ t string
+ l int
+ }
+ order := make([]o, 0, len(dup))
+ for k, v := range dup {
+ order = append(order, o{k, len(v)})
+ }
+ sort.Slice(order, func(i, j int) bool { return order[i].l > order[j].l })
+
+ ret := make([]string, 0, len(dup))
+ for _, v := range order {
+ if v.l == 1 {
+ ret = append(ret, v.t)
+ } else {
+ ret = append(ret, fmt.Sprintf("%s* (%d)", v.t, v.l))
+ }
+ }
+ return ret
+}
diff --git a/tui/termfo/cmd/termfo/internal/term/term.go b/tui/termfo/cmd/termfo/internal/term/term.go
new file mode 100644
index 0000000..be46580
--- /dev/null
+++ b/tui/termfo/cmd/termfo/internal/term/term.go
@@ -0,0 +1,80 @@
+package term
+
+import (
+ "fmt"
+ "syscall"
+ "unsafe"
+)
+
+// Some terminal ioctl stuff; we do it here with the stdlib syscall package
+// because I'd like to avoid depending on x/term and x/sys, as x/sys is quite
+// large (~8.5M). This is used only for the "keyscan" demo, and not any critical
+// functionality.
+//
+// The syscall package hasn't been maintained for quite a while, but this basic
+// stuff should (hopefully) work on most systems. If not: well, not a big deal
+// really. Maybe we can add a build flag to prefer x/term(?)
+
+type termios struct {
+ Iflag uint32
+ Oflag uint32
+ Cflag uint32
+ Lflag uint32
+ Line uint8
+ Cc [19]uint8
+ Ispeed uint32
+ Ospeed uint32
+}
+
+func getTermios() (termios, error) {
+ var t termios
+ _, _, err := syscall.Syscall(syscall.SYS_IOCTL, 1, syscall.TCGETS, uintptr(unsafe.Pointer(&t)))
+ if err > 0 {
+ return t, fmt.Errorf("%s", err)
+ }
+ return t, nil
+}
+
+func MakeRaw() (func(), error) {
+ termios, err := getTermios()
+ if err != nil {
+ return nil, err
+ }
+
+ old := termios
+ termios.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP |
+ syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON
+ termios.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN
+ termios.Oflag &^= syscall.OPOST
+ termios.Cflag &^= syscall.CSIZE | syscall.PARENB
+ termios.Cflag |= syscall.CS8
+ termios.Cc[syscall.VMIN] = 1
+ termios.Cc[syscall.VTIME] = 0
+
+ _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, 1, syscall.TCSETS, uintptr(unsafe.Pointer(&termios)))
+ if errno > 0 {
+ return nil, fmt.Errorf("%s", errno)
+ }
+
+ return func() {
+ syscall.Syscall(syscall.SYS_IOCTL, 1, syscall.TCSETS, uintptr(unsafe.Pointer(&old)))
+ }, nil
+}
+
+func IsTerminal() bool {
+ _, err := getTermios()
+ return err == nil
+}
+
+func Size() (int, int) {
+ var size struct {
+ Height, Width uint16
+ Xpixel, Ypixel uint16
+ }
+ _, _, err := syscall.Syscall(syscall.SYS_IOCTL, 0, syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&size)))
+ if err > 0 || size.Width <= 0 || size.Height <= 0 {
+ return -1, -1
+ }
+ return int(size.Width), int(size.Height)
+
+}
diff --git a/tui/termfo/cmd/termfo/keyscan.go b/tui/termfo/cmd/termfo/keyscan.go
new file mode 100644
index 0000000..c79a991
--- /dev/null
+++ b/tui/termfo/cmd/termfo/keyscan.go
@@ -0,0 +1,120 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "sort"
+ "strings"
+ "unicode"
+
+ "citrons.xyz/talk/tui/termfo"
+ "citrons.xyz/talk/tui/termfo/caps"
+ "citrons.xyz/talk/tui/termfo/cmd/termfo/internal/term"
+ "citrons.xyz/talk/tui/termfo/keys"
+)
+
+func keyscan() {
+ ti, err := termfo.New("")
+ if err != nil {
+ fatalf("%s", err)
+ }
+
+ restore, err := term.MakeRaw()
+ if err != nil {
+ fatalf("%s", err)
+ }
+ defer restore()
+
+ fmt.Printf("Loaded terminfo for %q\r\n", ti.Name)
+ fmt.Print("<C-k> to list key sequences we know about; <C-t> to print terminfo; <C-c> to quit\r\n\n")
+
+ fmt.Printf("%-48s │ %-20s\r\n", "Received", "Sent on channel")
+ fmt.Printf("%s┼%s\r\n", strings.Repeat("─", 49), strings.Repeat("─", 50))
+
+ ch := ti.FindKeys(os.Stdin)
+ for e := <-ch; ; e = <-ch {
+ if e.Err != nil {
+ fatalf("keyscan error: %s", e.Err)
+ }
+
+ showKey(e)
+
+ switch e.Key {
+ case 'c' | keys.Ctrl:
+ return
+ case 't' | keys.Ctrl:
+ more(ti, ch, fmtTerminfo(ti))
+ case 'k' | keys.Ctrl:
+ more(ti, ch, listKeys(ti))
+ }
+ }
+}
+
+// A very simple pager.
+func more(ti *termfo.Terminfo, ch <-chan termfo.Event, s string) {
+ if height <= 0 {
+ fmt.Print(strings.ReplaceAll(s, "\n", "\r\n"))
+ return
+ }
+
+ lines := strings.Split(s, "\n")
+ for i, l := range lines {
+ fmt.Print(l, "\r\n")
+ if i > 0 && (i+1)%(height-1) == 0 {
+ fmt.Printf("%s--- Displaying %d-%d of %d. Press any key to show more or <C-C> to abort%s\r",
+ ti.Strings[caps.EnterStandoutMode],
+ i-height+3, i+3, len(lines),
+ ti.Strings[caps.ExitStandoutMode])
+ if kk := <-ch; kk.Key == 'c'|keys.Ctrl {
+ fmt.Print("\r", ti.Strings[caps.ClrEol])
+ return
+ }
+ }
+ }
+}
+
+func showKey(e termfo.Event) {
+ fmt.Printf("Pressed %-40s │ → Key %-12s 0x%02x",
+ fmt.Sprintf("%-10s 0x% 2x", prSeq(string(e.Seq)), e.Seq), e.Key, rune(e.Key))
+ if e.Key.Shift() {
+ fmt.Print("|Shift")
+ }
+ if e.Key.Ctrl() {
+ fmt.Print("|Ctrl")
+ }
+ if e.Key.Alt() {
+ fmt.Print("|Alt")
+ }
+ if kk := e.Key.WithoutMods(); kk < unicode.MaxRune {
+ fmt.Printf(" U+%04X", rune(kk))
+ }
+ fmt.Print("\r\n")
+}
+
+func listKeys(ti *termfo.Terminfo) string {
+ all := make([]string, 0, len(ti.Keys))
+ for seq, k2 := range ti.Keys {
+ all = append(all, fmt.Sprintf(" %-22s → %-20s", prSeq(seq), k2))
+ }
+ sort.Strings(all)
+
+ b := new(strings.Builder)
+ fmt.Fprintf(b, "%d keys; note that not all of these may be supported by your terminal (especially multiple modifiers)\r\n", len(all))
+
+ w := width - 10
+ p := 0
+ for _, s := range all {
+ b.WriteString(s)
+ p += len(s)
+ if p > w {
+ b.WriteString("\r\n")
+ p = 0
+ }
+ }
+ b.WriteString("\r\n")
+ return b.String()
+}
+
+func prSeq(s string) string {
+ return strings.ReplaceAll(fmt.Sprintf("%q", s), `\x1b`, `\E`)
+}
diff --git a/tui/termfo/cmd/termfo/lscap.go b/tui/termfo/cmd/termfo/lscap.go
new file mode 100644
index 0000000..55ba0c1
--- /dev/null
+++ b/tui/termfo/cmd/termfo/lscap.go
@@ -0,0 +1,27 @@
+package main
+
+import (
+ "fmt"
+ "path/filepath"
+ "sort"
+
+ "citrons.xyz/talk/tui/termfo/caps"
+)
+
+func lsCap(match string) {
+ all := append(append(caps.TableBools, caps.TableNums...), caps.TableStrs...)
+ sort.Slice(all, func(i, j int) bool { return all[i].Long < all[j].Long })
+
+ for _, c := range all {
+ if match != "" {
+ m1, _ := filepath.Match(match, c.Short)
+ m2, _ := filepath.Match(match, c.Long)
+ if !m1 && !m2 {
+ continue
+ }
+ }
+
+ fmt.Printf("%-30s %-12s %s", c.Long, c.Short, c.Desc)
+ fmt.Println()
+ }
+}
diff --git a/tui/termfo/cmd/termfo/lsterm.go b/tui/termfo/cmd/termfo/lsterm.go
new file mode 100644
index 0000000..da7ea5f
--- /dev/null
+++ b/tui/termfo/cmd/termfo/lsterm.go
@@ -0,0 +1,55 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "citrons.xyz/talk/tui/termfo"
+)
+
+func lsTerm(match string) {
+ fmt.Printf("%-20s │ %-42s │ %s\n", "Name", "Data", "Aliases")
+ fmt.Printf("%s┼%s┼%s\n",
+ strings.Repeat("─", 21),
+ strings.Repeat("─", 44),
+ strings.Repeat("─", 30))
+
+ for _, a := range allInfos(match) {
+ if m, _ := filepath.Match(match, a); match != "" && !m {
+ continue
+ }
+ ti, err := termfo.New(a)
+ if err != nil {
+ fatalf("error reading %q: %s", a, err)
+ }
+
+ //fmt.Printf("%-20s %-40s │ %s\n", ti.Name, strings.Join(ti.Aliases, ", "), ti.Desc)
+ fmt.Printf("%-20s │ bool: %2d num: %2d str: %3d key: %3d ext: %2d │ %s",
+ ti.Name, len(ti.Bools), len(ti.Numbers), len(ti.Strings), len(ti.Keys), len(ti.Extended),
+ strings.Join(ti.Aliases, ", "))
+
+ fmt.Println()
+ }
+}
+
+func allInfos(match string) []string {
+ if _, err := os.Open("/usr/share/terminfo"); os.IsNotExist(err) {
+ fatalf("/usr/share/terminfo doesn't exist")
+ }
+
+ all, err := filepath.Glob("/usr/share/terminfo/*/*")
+ if err != nil {
+ fatalf("reading \"/usr/share/terminfo\" %s", err)
+ }
+
+ all2 := make([]string, 0, len(all))
+ for _, a := range all {
+ a = filepath.Base(a)
+ if !strings.ContainsRune(a, '.') {
+ all2 = append(all2, a)
+ }
+ }
+ return all2
+}
diff --git a/tui/termfo/cmd/termfo/main.go b/tui/termfo/cmd/termfo/main.go
new file mode 100644
index 0000000..e8ae805
--- /dev/null
+++ b/tui/termfo/cmd/termfo/main.go
@@ -0,0 +1,119 @@
+// Program termfo prints information about the terminfo database.
+package main
+
+import (
+ "fmt"
+ "os"
+ "strings"
+
+ "citrons.xyz/talk/tui/termfo/cmd/termfo/internal/term"
+)
+
+const usage = `Usage: termfo [cmd]
+
+Display information about terminfo files.
+
+Commands:
+ ls-term [glob] List terminals/terminfos.
+ ls-cap [glob] List capabilities.
+ show [term] [term...] Show terminfo for term; defaults to TERM if none
+ given. Use "all" to list all terminfo files.
+ keyscan Scan for keys.
+ find-cap [cap cap..] Show all terminals with these capabilities.
+
+ Flags:
+ -o, -old
+ Also show (very) old terminals.
+ -e, -expand
+ Show every terminfo name, rather than
+ grouping by prefix.
+ -n, -not, -not-supported
+ Also show terminals that don't support
+ this cap at all.
+`
+
+// build [pkg] [terms..] Generate a Go file for package [pkg] with terminals to compile in.
+// Use '%common' for a list of common terminals in use today.
+
+func fatalf(f string, a ...any) {
+ fmt.Fprintf(os.Stderr, f+"\n", a...)
+ os.Exit(1)
+}
+
+var (
+ width, height = term.Size()
+ isTerm = term.IsTerminal()
+)
+
+func main() {
+ if len(os.Args) == 1 {
+ fmt.Print(usage)
+ os.Exit(0)
+ }
+
+ switch os.Args[1] {
+ default:
+ fatalf("unknown command: %q", os.Args[1])
+ case "help", "-h", "-help", "--help":
+ fmt.Print(usage)
+ os.Exit(0)
+ case "ls-term":
+ m := ""
+ if len(os.Args) > 2 {
+ m = os.Args[2]
+ }
+ lsTerm(m)
+ case "ls-cap":
+ m := ""
+ if len(os.Args) > 2 {
+ m = os.Args[2]
+ }
+ lsCap(m)
+ case "show":
+ if len(os.Args) > 2 && os.Args[2] == "all" {
+ show(allInfos("*")...)
+ } else if len(os.Args) > 2 {
+ show(os.Args[2:]...)
+ } else {
+ show(os.Getenv("TERM"))
+ }
+ case "find-cap":
+ if len(os.Args) <= 2 {
+ fatalf("need a cap name")
+ }
+ var (
+ hist, expand, notSup bool
+ capName string
+ )
+ for _, a := range os.Args[2:] {
+ if len(a) > 0 && a[0] == '-' {
+ switch strings.TrimLeft(a, "-") {
+ case "o", "old":
+ hist = true
+ case "e", "expand":
+ expand = true
+ case "n", "not", "not-supported":
+ notSup = true
+ default:
+ fatalf("unknown flag: %q", a)
+ }
+ continue
+ }
+ if capName != "" {
+ fatalf("can only show one capability")
+ }
+ capName = a
+ }
+ termWithCap(capName, hist, expand, notSup)
+ //case "build":
+ // if len(os.Args) <= 2 {
+ // fatalf("need a package name to use")
+ // }
+ // if len(os.Args) <= 3 {
+ // fatalf("need at least one terminal name")
+ // }
+ // build(os.Args[2], os.Args[3:]...)
+ case "keyscan":
+ keyscan()
+ }
+}
diff --git a/tui/termfo/cmd/termfo/mkhist.go b/tui/termfo/cmd/termfo/mkhist.go
new file mode 100644
index 0000000..9270dd0
--- /dev/null
+++ b/tui/termfo/cmd/termfo/mkhist.go
@@ -0,0 +1,235 @@
+//go:build ignore
+
+package main
+
+import (
+ "cmp"
+ "fmt"
+ "os"
+ "slices"
+ "strings"
+)
+
+func main() {
+ f, err := os.ReadFile(os.Args[1])
+ if err != nil {
+ panic(err)
+ }
+
+ // ######## APPLE → #### Terminal.app → ...
+ var (
+ terms = make(map[string]map[string][]string)
+ sect, subsect string
+ )
+ for _, l := range strings.Split(string(f), "\n") {
+ if strings.HasPrefix(l, "########") {
+ sect = strings.TrimLeft(l, "# ")
+ terms[sect] = make(map[string][]string)
+ continue
+ }
+ if strings.HasPrefix(l, "####") && !strings.Contains(l, "TERMINFO FILE CAN BE SPLIT HERE") {
+ subsect = strings.TrimLeft(l, "# ")
+ terms[sect][subsect] = []string{}
+ continue
+ }
+ if !strings.HasPrefix(l, "\t") && strings.HasSuffix(l, ",") && strings.Contains(l, "|") {
+ t := strings.Split(l, "|")
+ terms[sect][subsect] = append(terms[sect][subsect], t[:len(t)-1]...)
+ }
+ }
+
+ // for k, v := range terms {
+ // fmt.Println(k)
+ // for k2, v2 := range v {
+ // fmt.Println(" ", k2)
+ // fmt.Println(" ", v2)
+ // }
+ // }
+ // return
+
+ fmt.Println("package main\n\nvar oldterm = map[string]int{")
+ dopr := func(k string, list []string) {
+ fmt.Printf("\t// %s\n\t", k)
+ for _, v := range list {
+ fmt.Printf("%q: 1,", v)
+ }
+ fmt.Println()
+ }
+ only := func(list []string, incl ...string) []string {
+ n := make([]string, 0, 16)
+ for _, v := range list {
+ for _, i := range incl {
+ if strings.HasPrefix(v, i) {
+ n = append(n, v)
+ continue
+ }
+ }
+ }
+ return n
+ }
+ for _, k := range ordered(terms) {
+ v := terms[k]
+ switch k {
+ // Pretty much all old hardware terminals.
+ case "OLDER TERMINAL TYPES", "OTHER OBSOLETE TYPES", "LCD DISPLAYS", "OBSOLETE VDT TYPES",
+ "COMMON TERMINAL TYPES": // Common hardware terminals in 1990
+ fmt.Printf("\t// %s\n", k)
+ for _, k2 := range ordered(v) {
+ dopr(k2, v[k2])
+ }
+
+ // All of these are very old and/or for outdated versions.
+ case "Non-Unix Consoles", "NON-ANSI TERMINAL EMULATIONS", "OBSOLETE UNIX CONSOLES",
+ "COMMERCIAL WORKSTATION CONSOLES", "OBSOLETE PERSONAL-MICRO CONSOLES AND EMULATIONS":
+ fmt.Printf("\t// %s\n", k)
+ for _, k2 := range ordered(v) {
+ dopr(k2, v[k2])
+ }
+ case "APPLE":
+ for _, k2 := range ordered(v) {
+ fmt.Printf("\t// %s\n", k)
+ switch k2 {
+ case "Terminal.app":
+ dopr(k2, only(v[k2], "nsterm-build", "nsterm+", "nsterm-7", "nsterm-old"))
+ // xnuppc* and darwin* is old PowerPC system console.
+ case "iTerm, iTerm2":
+ dopr(k2, only(v[k2], "xnuppc", "darwin"))
+ }
+ }
+ case "UNIX VIRTUAL TERMINALS, VIRTUAL CONSOLES, AND TELNET CLIENTS":
+ fmt.Printf("\t// %s\n", k)
+ for _, k2 := range ordered(v) {
+ switch k2 {
+ // Old Palm pilot, telnet
+ case "NCSA Telnet", "Pilot Pro Palm-Top":
+ dopr(k2, v[k2])
+ case "Screen":
+ dopr(k2, only(v[k2], "screen2", "screen3", "screen4", "screen5")) // Old versions
+ }
+ }
+ dopr("CB UNIX, early 80s", []string{"cbunix", "vremote", "pty"})
+ case "X TERMINAL EMULATORS":
+ fmt.Printf("\t// %s\n", k)
+ for _, k2 := range ordered(v) {
+ switch k2 {
+ // "Multi Gnome Terminal", hasn't been updated in 20 years
+ case "Other GNOME":
+ dopr(k2, only(v[k2], "mgt"))
+ // Old pre-VTE gnome-terminal, and specific gnome-terminal/VTE versions
+ case "GNOME (VTE)":
+ dopr(k2, only(v[k2], "gnome",
+ "vte-2007", "vte-2008", "vte-2012", "vte-2014", "vte-2017", "vte-2018"))
+ case "SIMPLETERM":
+ dopr(k2, only(v[k2], "st-0.", "old-st", "simpleterm")) // Old versions
+ case "TERMINOLOGY":
+ dopr(k2, only(v[k2], "terminology-0", "terminology-1")) // Old versions
+ case "MLTERM":
+ dopr(k2, only(v[k2], "mlterm3", "mlterm2")) // Old versions
+ case "KDE":
+ dopr(k2, only(v[k2],
+ // Old konsole name (<2001).
+ "kvt",
+ // Old seemingly obselete konsole variants.
+ "konsole-xf"))
+ case "XTERM":
+ // Old versions
+ dopr(k2, only(v[k2], "x10term", "xterm-r5", "xterm-r6", "xterm-old", "xterm-xf86", "xterm-xfree86",
+ "xterm-24", "vs100", "xterms", "xterm-new"))
+ case "XTERM Features":
+ dopr(k2, only(v[k2], "xterm-8bit", "xterm-vt52", "xterm1", "xterm-nic", "xterm-hp"))
+ case "Other XTERM":
+ dopr(k2, only(v[k2],
+ // Old xterm fork from before xterm supported ECMA-64 colors
+ "color_xterm", "cx",
+ // Weird terminal with weird overrides.
+ "xterm-pcolor",
+ // xterm as distributes with late 90s Solaris.
+ "xtermc", "xtermm", "xterm-sun", "xterms-sun",
+ ))
+ case
+ // Old Xfree86 term, or something.
+ "EMU",
+ // MGR is old Bell Labs thingy
+ // https://en.wikipedia.org/wiki/ManaGeR
+ // https://hack.org/mc/mgr/
+ "MGR",
+ // Seems to be some old (non-ANSI) emulator? Can't find much about it.
+ "MTERM",
+ // Seems to be (very) old version of "Rocket® Terminal Emulator"
+ // https://www.rocketsoftware.com/products/rocket-terminal-emulator/rocket-terminal-emulation
+ "MVTERM",
+ // Old versions of https://github.com/TragicWarrior/vwm
+ "VWM",
+ // RXVT hasn't been updated in >20 years; no one ships it
+ // any more (e.g. on Debian it links to rxvt-unicode).
+ "RXVT",
+ // Not updated for ages; not packaged by Debian.
+ "MRXVT",
+ // Old emulator from 90s with support for Kanji
+ // https://ja.wikipedia.org/wiki/Kterm
+ "KTERM",
+ // HP-UX hpterm terminal emulator; I'm not really sure if
+ // this is still in use on modern HP-UX systems. Even if it
+ // is: it's pretty obscure.
+ "HPTERM":
+ dopr(k2, v[k2])
+ }
+ }
+ case "ANSI, UNIX CONSOLE, AND SPECIAL TYPES":
+ fmt.Printf("\t// %s\n", k)
+ for _, k2 := range ordered(v) {
+ switch k2 {
+ // Only include vt220; no one really targets vt100, or vt400, or
+ // whatnot.
+ case "DEC VT100 and compatibles":
+ dopr(k2, only(v[k2], "vt100", "vt102", "vt125", "vt13", "vt200",
+ "vt220-old", "vt220-8", "vt220d", "vt3", "vt4", "vt5", "dec-"))
+ // Old NetBSD system consoles
+ case "NetBSD consoles":
+ dopr(k2, only(v[k2], "pcvt", "arm100", "x68k", "ofcons", "rcons", "mgterm", "netbsd6"))
+ case "ANSI.SYS/ISO 6429/ECMA-48 Capabilities":
+ dopr(k2, only(v[k2],
+ "ibcs2", // iBCS is some failed Intel standard
+ ))
+ case "ANSI/ECMA-48 terminals and terminal emulators":
+ dopr(k2, only(v[k2],
+ "ansi77", // 1977 ANSI
+ ))
+ case
+ "Specials",
+ "DEC VT52",
+ "VT100 emulations",
+ "SCO consoles", "SGI consoles", "386BSD and BSD/OS Consoles", // Old Unix
+ "Atari ST terminals", // Atari ST (1985)
+ "Mach", // No one uses this.
+ "BeOS", // Don't think this is used?
+ "DOS ANSI.SYS variants", // Old DOS; not used I think.
+ "QNX": // QNX 4 (1990); probably not used on modern QNX.
+ dopr(k2, v[k2])
+ }
+ }
+ case "DOS/WINDOWS":
+ fmt.Printf("\t// %s\n", k)
+ for _, k2 := range ordered(v) {
+ switch k2 {
+ case "Command prompt":
+ dopr(k2, only(v[k2], "vt100+", "ms-vt100", "ms-vt-", "vtnt", "vt-utf")) // WinNT 4
+ case "PuTTY":
+ dopr(k2, only(v[k2], "vt100-putty", "putty-vt100", "putty+fnkeys+vt100", "putty+fnkeys+vt400"))
+ }
+ }
+ // CRT is very old outdated version of SecureCRT
+ dopr("CRT is very old outdated version of SecureCRT", []string{"crt", "crt-vt220"})
+ }
+ }
+ fmt.Println("}")
+}
+
+func ordered[M ~map[K]V, K cmp.Ordered, V any](m M) []K {
+ r := make([]K, 0, len(m))
+ for k := range m {
+ r = append(r, k)
+ }
+ slices.Sort(r)
+ return r
+}
diff --git a/tui/termfo/cmd/termfo/oldterm.go b/tui/termfo/cmd/termfo/oldterm.go
new file mode 100644
index 0000000..8a42fd7
--- /dev/null
+++ b/tui/termfo/cmd/termfo/oldterm.go
@@ -0,0 +1,328 @@
+package main
+
+var oldterm = map[string]int{
+ // ANSI, UNIX CONSOLE, AND SPECIAL TYPES
+ // 386BSD and BSD/OS Consoles
+ "origpc3": 1,"origibmpc3": 1,"oldpc3": 1,"oldibmpc3": 1,"bsdos-pc": 1,"bsdos-pc-nobold": 1,"bsdos-pc-m": 1,"bsdos-pc-mono": 1,"pc3": 1,"ibmpc3": 1,"pc3-bold": 1,"bsdos-sparc": 1,"bsdos-ppc": 1,
+ // ANSI.SYS/ISO 6429/ECMA-48 Capabilities
+ "ibcs2": 1,
+ // ANSI/ECMA-48 terminals and terminal emulators
+ "ansi77": 1,
+ // Atari ST terminals
+ "tw52": 1,"tw52-color": 1,"tw52-m": 1,"tt52": 1,"st52-color": 1,"at-color": 1,"atari-color": 1,"atari_st-color": 1,"st52": 1,"st52-m": 1,"at": 1,"at-m": 1,"atari": 1,"atari-m": 1,"atari_st": 1,"atarist-m": 1,"tw100": 1,"stv52": 1,"stv52pc": 1,"atari-old": 1,"uniterm": 1,"uniterm49": 1,"st52-old": 1,
+ // BeOS
+ "beterm": 1,
+ // DEC VT100 and compatibles
+ "vt100+keypad": 1,"vt100+pfkeys": 1,"vt100+fnkeys": 1,"vt100+enq": 1,"vt102+enq": 1,"vt100": 1,"vt100-am": 1,"vt100+4bsd": 1,"vt100nam": 1,"vt100-nam": 1,"vt100-vb": 1,"vt100-w": 1,"vt100-w-am": 1,"vt100-w-nam": 1,"vt100-nam-w": 1,"vt100-nav": 1,"vt100-nav-w": 1,"vt100-w-nav": 1,"vt100-s": 1,"vt100-s-top": 1,"vt100-top-s": 1,"vt100-s-bot": 1,"vt100-bot-s": 1,"vt102": 1,"vt102-w": 1,"vt102-nsgr": 1,"vt125": 1,"vt131": 1,"vt132": 1,"vt220-old": 1,"vt200-old": 1,"vt200": 1,"vt200-w": 1,"vt220-8bit": 1,"vt220-8": 1,"vt200-8bit": 1,"vt200-8": 1,"vt220d": 1,"vt200-js": 1,"vt320nam": 1,"vt320": 1,"vt300": 1,"vt320-nam": 1,"vt300-nam": 1,"vt320-w": 1,"vt300-w": 1,"vt320-w-nam": 1,"vt300-w-nam": 1,"vt340": 1,"dec-vt340": 1,"vt330": 1,"dec-vt330": 1,"vt420+lrmm": 1,"vt400": 1,"vt400-24": 1,"dec-vt400": 1,"vt420": 1,"vt420pc": 1,"vt420pcdos": 1,"vt420f": 1,"vt510": 1,"vt510pc": 1,"vt510pcdos": 1,"vt520": 1,"vt525": 1,"vt520ansi": 1,
+ // DEC VT52
+ "vt52": 1,"vt52-basic": 1,"vt52+arrows": 1,
+ // DOS ANSI.SYS variants
+ "ansi.sys-old": 1,"ansi.sys": 1,"ansi.sysk": 1,"ansisysk": 1,"nansi.sys": 1,"nansisys": 1,"nansi.sysk": 1,"nansisysk": 1,
+ // Mach
+ "mach": 1,"mach-bold": 1,"mach-color": 1,"mach-gnu": 1,"mach-gnu-color": 1,"hurd": 1,
+ // NetBSD consoles
+ "pcvtXX": 1,"pcvt25": 1,"pcvt28": 1,"pcvt35": 1,"pcvt40": 1,"pcvt43": 1,"pcvt50": 1,"pcvt25w": 1,"pcvt28w": 1,"pcvt35w": 1,"pcvt40w": 1,"pcvt43w": 1,"pcvt50w": 1,"pcvt25-color": 1,"arm100": 1,"arm100-am": 1,"arm100-w": 1,"arm100-wam": 1,"x68k": 1,"x68k-ite": 1,"ofcons": 1,"netbsd6": 1,"rcons": 1,"rcons-color": 1,"mgterm": 1,
+ // QNX
+ "qnx": 1,"qnx4": 1,"qnxt": 1,"qnxt4": 1,"qnxm": 1,"qnxw": 1,"qnxtmono": 1,"qnxt2": 1,"qansi-g": 1,"qansi": 1,"qansi-t": 1,"qansi-m": 1,"qansi-w": 1,
+ // SCO consoles
+ "scoansi-old": 1,"scoansi-new": 1,"scoansi": 1,
+ // SGI consoles
+ "iris-ansi": 1,"iris-ansi-net": 1,"iris-ansi-ap": 1,"iris-color": 1,"xwsh": 1,
+ // Specials
+ "dumb": 1,"unknown": 1,"lpr": 1,"printer": 1,"glasstty": 1,"vanilla": 1,"9term": 1,
+ // VT100 emulations
+ "dec-vt100": 1,"dec-vt220": 1,"z340": 1,"z340-nam": 1,"tt": 1,"tkterm": 1,
+ // APPLE
+ // Terminal.app
+ "nsterm+7": 1,"nsterm+acs": 1,"nsterm+mac": 1,"nsterm+s": 1,"nsterm+c": 1,"nsterm+c41": 1,"nsterm-7-m": 1,"nsterm-7-m-s": 1,"nsterm-7": 1,"nsterm-7-c": 1,"nsterm-7-s": 1,"nsterm-7-c-s": 1,"nsterm-old": 1,"nsterm-build309": 1,"nsterm-build326": 1,"nsterm-build343": 1,"nsterm-build361": 1,"nsterm-build400": 1,"nsterm-build440": 1,
+ // APPLE
+ // iTerm, iTerm2
+ "xnuppc+basic": 1,"xnuppc+c": 1,"xnuppc+b": 1,"xnuppc+f": 1,"xnuppc+f2": 1,"xnuppc+80x25": 1,"xnuppc+80x30": 1,"xnuppc+90x30": 1,"xnuppc+100x37": 1,"xnuppc+112x37": 1,"xnuppc+128x40": 1,"xnuppc+128x48": 1,"xnuppc+144x48": 1,"xnuppc+160x64": 1,"xnuppc+200x64": 1,"xnuppc+200x75": 1,"xnuppc+256x96": 1,"xnuppc-m": 1,"darwin-m": 1,"xnuppc": 1,"darwin": 1,"xnuppc-m-b": 1,"darwin-m-b": 1,"xnuppc-b": 1,"darwin-b": 1,"xnuppc-m-f": 1,"darwin-m-f": 1,"xnuppc-f": 1,"darwin-f": 1,"xnuppc-m-f2": 1,"darwin-m-f2": 1,"xnuppc-f2": 1,"darwin-f2": 1,"xnuppc-80x25-m": 1,"darwin-80x25-m": 1,"xnuppc-80x25": 1,"darwin-80x25": 1,"xnuppc-80x30-m": 1,"darwin-80x30-m": 1,"xnuppc-80x30": 1,"darwin-80x30": 1,"xnuppc-90x30-m": 1,"darwin-90x30-m": 1,"xnuppc-90x30": 1,"darwin-90x30": 1,"xnuppc-100x37-m": 1,"darwin-100x37-m": 1,"xnuppc-100x37": 1,"darwin-100x37": 1,"xnuppc-112x37-m": 1,"darwin-112x37-m": 1,"xnuppc-112x37": 1,"darwin-112x37": 1,"xnuppc-128x40-m": 1,"darwin-128x40-m": 1,"xnuppc-128x40": 1,"darwin-128x40": 1,"xnuppc-128x48-m": 1,"darwin-128x48-m": 1,"xnuppc-128x48": 1,"darwin-128x48": 1,"xnuppc-144x48-m": 1,"darwin-144x48-m": 1,"xnuppc-144x48": 1,"darwin-144x48": 1,"xnuppc-160x64-m": 1,"darwin-160x64-m": 1,"xnuppc-160x64": 1,"darwin-160x64": 1,"xnuppc-200x64-m": 1,"darwin-200x64-m": 1,"xnuppc-200x64": 1,"darwin-200x64": 1,"xnuppc-200x75-m": 1,"darwin-200x75-m": 1,"xnuppc-200x75": 1,"darwin-200x75": 1,"xnuppc-256x96-m": 1,"darwin-256x96-m": 1,"xnuppc-256x96": 1,"darwin-256x96": 1,
+ // COMMERCIAL WORKSTATION CONSOLES
+ // Alpha consoles
+ "pccons": 1,"pcconsole": 1,
+ // Common Desktop Environment
+ "dtterm": 1,
+ // Iris consoles
+ "wsiris": 1,"iris40": 1,
+ // NeWS consoles
+ "psterm": 1,"psterm-basic": 1,"psterm-96x48": 1,"psterm-90x28": 1,"psterm-80x24": 1,"psterm-fast": 1,
+ // NeXT consoles
+ "next": 1,"nextshell": 1,
+ // Sony NEWS workstations
+ "news-unk": 1,"news-29": 1,"news-29-euc": 1,"news-29-sjis": 1,"news-33": 1,"news-33-euc": 1,"news-33-sjis": 1,"news-42": 1,"news-42-euc": 1,"news-42-sjis": 1,"news-old-unk": 1,"nwp512": 1,"news": 1,"nwp514": 1,"news40": 1,"vt100-bm": 1,"nwp512-o": 1,"nwp514-o": 1,"news-o": 1,"news40-o": 1,"vt100-bm-o": 1,"nwp512-a": 1,"nwp514-a": 1,"news-a": 1,"news42": 1,"news40-a": 1,"nwp513": 1,"nwp518": 1,"nwe501": 1,"newscbm": 1,"news31": 1,"nwp513-o": 1,"nwp518-o": 1,"nwe501-o": 1,"nwp251-o": 1,"newscbm-o": 1,"news31-o": 1,"nwp513-a": 1,"nwp518-a": 1,"nwe501-a": 1,"nwp251-a": 1,"newscbm-a": 1,"news31-a": 1,"newscbm33": 1,"news33": 1,"news28": 1,"news29": 1,"news28-a": 1,"nwp511": 1,"nwp-511": 1,"nwp517": 1,"nwp-517": 1,"nwp517-w": 1,"nwp-517-w": 1,
+ // Sun consoles
+ "oldsun": 1,"sun-il": 1,"sun-cgsix": 1,"sun-ss5": 1,"sun": 1,"sun1": 1,"sun2": 1,"sun+sl": 1,"sun-s": 1,"sun-e-s": 1,"sun-s-e": 1,"sun-48": 1,"sun-34": 1,"sun-24": 1,"sun-17": 1,"sun-12": 1,"sun-1": 1,"sun-e": 1,"sun-nic": 1,"sune": 1,"sun-c": 1,"sun-cmd": 1,"sun-type4": 1,"sun-color": 1,
+ // COMMON TERMINAL TYPES
+ // Altos
+ "altos2": 1,"alt2": 1,"altos-2": 1,"altos3": 1,"altos5": 1,"alt3": 1,"alt5": 1,"altos-3": 1,"altos-5": 1,"altos4": 1,"alt4": 1,"altos-4": 1,"altos7": 1,"alt7": 1,"altos7pc": 1,"alt7pc": 1,
+ // Hewlett-Packard (hp)
+ "hpgeneric": 1,"hp": 1,"hp110": 1,"hp+pfk+cr": 1,"hp+pfk-cr": 1,"hp+pfk+arrows": 1,"hp+arrows": 1,"hp262x": 1,"hp2621-ba": 1,"hp2621": 1,"hp2621a": 1,"hp2621A": 1,"2621": 1,"2621a": 1,"2621A": 1,"hp2621-wl": 1,"2621-wl": 1,"hp2621-fl": 1,"hp2621p": 1,"hp2621p-a": 1,"hp2621-k45": 1,"hp2621k45": 1,"k45": 1,"hp2621-48": 1,"hp2621-nl": 1,"hp2621-nt": 1,"hp2624": 1,"hp2624a": 1,"hp2624b": 1,"hp2624b-4p": 1,"hp2626": 1,"hp2626a": 1,"hp2626p": 1,"hp2626-s": 1,"hp2626-ns": 1,"hp2626-12": 1,"hp2626-12x40": 1,"hp2626-x40": 1,"hp2626-12-s": 1,"hp2627a-rev": 1,"hp2627a": 1,"hp2627c": 1,"hp2640a": 1,"hp2640b": 1,"hp2644a": 1,"hp2641a": 1,"hp2645a": 1,"hp2647a": 1,"hp2645": 1,"hp45": 1,"hp2648": 1,"hp2648a": 1,"hp150": 1,"hp2382a": 1,"hp2382": 1,"hp2621-a": 1,"hp2621a-a": 1,"newhpkeyboard": 1,"newhp": 1,"memhp": 1,"scrhp": 1,"hp+labels": 1,"hp+printer": 1,"hp2621b": 1,"hp2621b-p": 1,"hp2621b-kx": 1,"hp2621b-kx-p": 1,"hp2622": 1,"hp2622a": 1,"hp2623": 1,"hp2623a": 1,"hp2624b-p": 1,"hp2624b-4p-p": 1,"hp2624-10p": 1,"hp2624a-10p": 1,"hp2624b-10p": 1,"hp2624b-10p-p": 1,"hp+color": 1,"hp2397a": 1,"hp2397": 1,"hpansi": 1,"hp700": 1,"hp2392": 1,"hpsub": 1,"hpex": 1,"hp2": 1,"hpex2": 1,"hp236": 1,"hp300h": 1,"hp9837": 1,"hp98720": 1,"hp98721": 1,"hp9845": 1,"hp98550": 1,"hp98550a": 1,"hp98550-color": 1,"hp98550a-color": 1,"hp700-wy": 1,"hp70092": 1,"hp70092a": 1,"hp70092A": 1,"bobcat": 1,"sbobcat": 1,"gator-t": 1,"gator": 1,"gator-52": 1,"gator-52t": 1,
+ // Honeywell-Bull
+ "dku7003-dumb": 1,"dku7003": 1,
+ // Kermit terminal emulations
+ "kermit": 1,"kermit-am": 1,"pckermit": 1,"pckermit12": 1,"pckermit120": 1,"msk227": 1,"mskermit227": 1,"msk227am": 1,"mskermit227am": 1,"msk22714": 1,"mskermit22714": 1,"vt320-k3": 1,"vt320-k311": 1,
+ // Lear-Siegler (LSI adm)
+ "adm1a": 1,"adm1": 1,"adm2": 1,"adm3": 1,"adm3a": 1,"adm3a+": 1,"adm5": 1,"adm+sgr": 1,"adm11": 1,"adm12": 1,"adm20": 1,"adm21": 1,"adm22": 1,"adm31": 1,"adm31-old": 1,"o31": 1,"adm36": 1,"adm42": 1,"adm42-ns": 1,"adm1178": 1,"1178": 1,
+ // Prime
+ "pt100": 1,"pt200": 1,"wren": 1,"fenix": 1,"pt100w": 1,"pt200w": 1,"wrenw": 1,"fenixw": 1,"pt250": 1,"pt250w": 1,
+ // Qume (qvt)
+ "qvt101": 1,"qvt108": 1,"qvt101+": 1,"qvt101p": 1,"qvt102": 1,"qvt103": 1,"qvt103-w": 1,"qvt119+": 1,"qvt119p": 1,"qvt119": 1,"qvt119+-25": 1,"qvt119p-25": 1,"qvt119+-w": 1,"qvt119p-w": 1,"qvt119-w": 1,"qvt119+-25-w": 1,"qvt119p-25-w": 1,"qvt119-25-w": 1,"qvt203": 1,"qvt203+": 1,"qvt203-w": 1,"qvt203-w-am": 1,"qvt203-25": 1,"qvt203-25-w": 1,
+ // TeleVideo (tvi)
+ "tvi803": 1,"tvi910": 1,"tvi910+": 1,"tvi912": 1,"tvi914": 1,"tvi920": 1,"tvi912cc": 1,"tvi912b-unk": 1,"tvi912c-unk": 1,"tvi912b+printer": 1,"tvi912b+dim": 1,"tvi912b+mc": 1,"tvi912b+2p": 1,"tvi912b+vb": 1,"tvi920b+fn": 1,"tvi912b-2p-unk": 1,"tvi912c-2p-unk": 1,"tvi912b-unk-2p": 1,"tvi912c-unk-2p": 1,"tvi912b-vb-unk": 1,"tvi912c-vb-unk": 1,"tvi912b-unk-vb": 1,"tvi912c-unk-vb": 1,"tvi912b-p": 1,"tvi912c-p": 1,"tvi912b-2p-p": 1,"tvi912c-2p-p": 1,"tvi912b-p-2p": 1,"tvi912c-p-2p": 1,"tvi912b-vb-p": 1,"tvi912c-vb-p": 1,"tvi912b-p-vb": 1,"tvi912c-p-vb": 1,"tvi912b-2p": 1,"tvi912c-2p": 1,"tvi912b-2p-mc": 1,"tvi912c-2p-mc": 1,"tvi912b-mc-2p": 1,"tvi912c-mc-2p": 1,"tvi912b-vb": 1,"tvi912c-vb": 1,"tvi912b-vb-mc": 1,"tvi912c-vb-mc": 1,"tvi912b-mc-vb": 1,"tvi912c-mc-vb": 1,"tvi912b": 1,"tvi912c": 1,"tvi912b-mc": 1,"tvi912c-mc": 1,"tvi920b-unk": 1,"tvi920c-unk": 1,"tvi920b-2p-unk": 1,"tvi920c-2p-unk": 1,"tvi920b-unk-2p": 1,"tvi920c-unk-2p": 1,"tvi920b-vb-unk": 1,"tvi920c-vb-unk": 1,"tvi920b-unk-vb": 1,"tvi920c-unk-vb": 1,"tvi920b-p": 1,"tvi920c-p": 1,"tvi920b-2p-p": 1,"tvi920c-2p-p": 1,"tvi920b-p-2p": 1,"tvi920c-p-2p": 1,"tvi920b-vb-p": 1,"tvi920c-vb-p": 1,"tvi920b-p-vb": 1,"tvi920c-p-vb": 1,"tvi920b-2p": 1,"tvi920c-2p": 1,"tvi920b-2p-mc": 1,"tvi920c-2p-mc": 1,"tvi920b-mc-2p": 1,"tvi920c-mc-2p": 1,"tvi920b-vb": 1,"tvi920c-vb": 1,"tvi920b-vb-mc": 1,"tvi920c-vb-mc": 1,"tvi920b-mc-vb": 1,"tvi920c-mc-vb": 1,"tvi920b": 1,"tvi920c": 1,"tvi920b-mc": 1,"tvi920c-mc": 1,"tvi921": 1,"tvi92B": 1,"tvi92D": 1,"tvi924": 1,"tvi925": 1,"tvi925-hi": 1,"tvi950": 1,"tvi950-2p": 1,"tvi950-4p": 1,"tvi950-rv": 1,"tvi950-rv-2p": 1,"tvi950-rv-4p": 1,"tvi955": 1,"tvi955-w": 1,"955-w": 1,"tvi955-hb": 1,"955-hb": 1,"tvi970": 1,"tvi970-vb": 1,"tvi970-2p": 1,"tvipt": 1,"tvi9065": 1,
+ // Visual (vi)
+ "vi50": 1,"vi50adm": 1,"vi55": 1,"vi200": 1,"vi200-f": 1,"vi200-rv": 1,"vi300": 1,"vi300-old": 1,"vi500": 1,"vi550": 1,"vi603": 1,"visual603": 1,
+ // Wyse (wy)
+ "wy30": 1,"wyse30": 1,"wy30-mc": 1,"wyse30-mc": 1,"wy30-vb": 1,"wyse30-vb": 1,"wy50": 1,"wyse50": 1,"wyse+sl": 1,"wy50-mc": 1,"wyse50-mc": 1,"wy50-vb": 1,"wyse50-vb": 1,"wy50-w": 1,"wyse50-w": 1,"wy50-wvb": 1,"wyse50-wvb": 1,"wy350": 1,"wyse350": 1,"wy350-vb": 1,"wyse350-vb": 1,"wy350-w": 1,"wyse350-w": 1,"wy350-wvb": 1,"wyse350-wvb": 1,"wy100": 1,"wy120": 1,"wyse120": 1,"wy150": 1,"wyse150": 1,"wy120-w": 1,"wyse120-w": 1,"wy150-w": 1,"wyse150-w": 1,"wy120-25": 1,"wyse120-25": 1,"wy150-25": 1,"wyse150-25": 1,"wy120-25-w": 1,"wyse120-25-w": 1,"wy150-25-w": 1,"wyse150-25-w": 1,"wy120-vb": 1,"wyse120-vb": 1,"wy150-vb": 1,"wyse150-vb": 1,"wy120-w-vb": 1,"wy120-wvb": 1,"wyse120-wvb": 1,"wy150-w-vb": 1,"wyse150-w-vb": 1,"wy60": 1,"wyse60": 1,"wy60-w": 1,"wyse60-w": 1,"wy60-25": 1,"wyse60-25": 1,"wy60-25-w": 1,"wyse60-25-w": 1,"wy60-42": 1,"wyse60-42": 1,"wy60-42-w": 1,"wyse60-42-w": 1,"wy60-43": 1,"wyse60-43": 1,"wy60-43-w": 1,"wyse60-43-w": 1,"wy60-vb": 1,"wyse60-vb": 1,"wy60-w-vb": 1,"wy60-wvb": 1,"wyse60-wvb": 1,"wy99gt": 1,"wyse99gt": 1,"wy99gt-w": 1,"wyse99gt-w": 1,"wy99gt-25": 1,"wyse99gt-25": 1,"wy99gt-25-w": 1,"wyse99gt-25-w": 1,"wy99gt-vb": 1,"wyse99gt-vb": 1,"wy99gt-w-vb": 1,"wy99gt-wvb": 1,"wyse99gt-wvb": 1,"wy99-ansi": 1,"wy99a-ansi": 1,"wy99f": 1,"wy99fgt": 1,"wy-99fgt": 1,"wy99fa": 1,"wy99fgta": 1,"wy-99fgta": 1,"wy160": 1,"wyse160": 1,"wy160-w": 1,"wyse160-w": 1,"wy160-25": 1,"wyse160-25": 1,"wy160-25-w": 1,"wyse160-25-w": 1,"wy160-42": 1,"wyse160-42": 1,"wy160-42-w": 1,"wyse160-42-w": 1,"wy160-43": 1,"wyse160-43": 1,"wy160-43-w": 1,"wyse160-43-w": 1,"wy160-vb": 1,"wyse160-vb": 1,"wy160-w-vb": 1,"wy160-wvb": 1,"wyse160-wvb": 1,"wy75": 1,"wyse75": 1,"wy75-mc": 1,"wyse75-mc": 1,"wy75-vb": 1,"wyse75-vb": 1,"wy75-w": 1,"wyse75-w": 1,"wy75-wvb": 1,"wyse75-wvb": 1,"wy85": 1,"wyse85": 1,"wy85-vb": 1,"wyse85-vb": 1,"wy85-w": 1,"wyse85-w": 1,"wy85-wvb": 1,"wyse85-wvb": 1,"wy85-8bit": 1,"wyse85-8bit": 1,"wy185": 1,"wyse185": 1,"wy185-24": 1,"wyse185-24": 1,"wy185-vb": 1,"wyse185-vb": 1,"wy185-w": 1,"wyse185-w": 1,"wy185-wvb": 1,"wyse185-wvb": 1,"wy325": 1,"wyse325": 1,"wy325-vb": 1,"wyse325-vb": 1,"wy325-w": 1,"wyse325-w": 1,"wy325w-24": 1,"wy325-25": 1,"wyse325-25": 1,"wy325-80": 1,"wyse-325": 1,"wy325-25w": 1,"wyse325-25w": 1,"wy325-w-vb": 1,"wy325-wvb": 1,"wyse325-wvb": 1,"wy325-42": 1,"wyse325-42": 1,"wy325-42w": 1,"wyse325-42w": 1,"wy325-42w-vb": 1,"wy325-42wvb": 1,"wy325-43": 1,"wyse325-43": 1,"wy325-43w": 1,"wyse325-43w": 1,"wy325-43w-vb": 1,"wy325-43wvb": 1,"wy370-nk": 1,"wy370": 1,"wyse370": 1,"wy370-101k": 1,"wy370-105k": 1,"wy370-EPC": 1,"wy370-vb": 1,"wy370-w": 1,"wy370-wvb": 1,"wy370-rv": 1,"wy99gt-tek": 1,"wy160-tek": 1,"wy370-tek": 1,"wy520": 1,"wyse520": 1,"wy520-24": 1,"wyse520-24": 1,"wy520-vb": 1,"wyse520-vb": 1,"wy520-w": 1,"wyse520-w": 1,"wy520-wvb": 1,"wyse520-wvb": 1,"wy520-epc": 1,"wyse520-epc": 1,"wy520-epc-24": 1,"wyse520-pc-24": 1,"wy520-epc-vb": 1,"wyse520-pc-vb": 1,"wy520-epc-w": 1,"wyse520-epc-w": 1,"wy520-epc-wvb": 1,"wyse520-p-wvb": 1,"wy520-36": 1,"wyse520-36": 1,"wy520-48": 1,"wyse520-48": 1,"wy520-36w": 1,"wyse520-36w": 1,"wy520-48w": 1,"wyse520-48w": 1,"wy520-36pc": 1,"wyse520-36pc": 1,"wy520-48pc": 1,"wyse520-48pc": 1,"wy520-36wpc": 1,"wyse520-36wpc": 1,"wy520-48wpc": 1,"wyse520-48wpc": 1,"wyse-vp": 1,"wy75ap": 1,"wyse75ap": 1,"wy-75ap": 1,"wyse-75ap": 1,"wy100q": 1,
+ // DOS/WINDOWS
+ // Command prompt
+ "ms-vt100": 1,"ms-vt100-color": 1,"vtnt": 1,"ms-vt100+": 1,"vt100+": 1,"ms-vt-utf8": 1,"vt-utf8": 1,"ms-vt100-16color": 1,
+ // PuTTY
+ "vt100-putty": 1,"putty-vt100": 1,"putty+fnkeys+vt400": 1,"putty+fnkeys+vt100": 1,
+ // CRT is very old outdated version of SecureCRT
+ "crt": 1,"crt-vt220": 1,
+ // LCD DISPLAYS
+ // Matrix Orbital
+ "MtxOrb": 1,"MtxOrb204": 1,"MtxOrb162": 1,
+ // NON-ANSI TERMINAL EMULATIONS
+ // Avatar
+ "avatar0": 1,"avatar0+": 1,"avatar": 1,"avatar1": 1,
+ // RBcomm
+ "rbcomm": 1,"rbcomm-nam": 1,"rbcomm-w": 1,
+ // Non-Unix Consoles
+ // Cygwin
+ "cygwinB19": 1,"cygwin": 1,"cygwinDBG": 1,
+ // DJGPP
+ "djgpp": 1,"djgpp203": 1,"djgpp204": 1,
+ // EMX termcap.dat compatibility modes
+ "emx-base": 1,"ansi-emx": 1,"ansi-color-2-emx": 1,"ansi-color-3-emx": 1,"mono-emx": 1,
+ // Microsoft (miscellaneous)
+ "ansi-nt": 1,"psx_ansi": 1,"pcmw": 1,"interix": 1,"opennt": 1,"opennt-25": 1,"ntconsole": 1,"ntconsole-25": 1,"opennt-35": 1,"ntconsole-35": 1,"opennt-50": 1,"ntconsole-50": 1,"opennt-60": 1,"ntconsole-60": 1,"opennt-100": 1,"ntconsole-100": 1,"opennt-w": 1,"opennt-25-w": 1,"ntconsole-w": 1,"ntconsole-25-w": 1,"opennt-35-w": 1,"ntconsole-35-w": 1,"opennt-50-w": 1,"ntconsole-50-w": 1,"opennt-60-w": 1,"ntconsole-60-w": 1,"opennt-w-vt": 1,"opennt-25-w-vt": 1,"ntconsole-w-vt": 1,"ntconsole-25-w-vt": 1,"interix-nti": 1,"opennt-nti": 1,"opennt-25-nti": 1,"ntconsole-25-nti": 1,"opennt-35-nti": 1,"ntconsole-35-nti": 1,"opennt-50-nti": 1,"ntconsole-50-nti": 1,"opennt-60-nti": 1,"ntconsole-60-nti": 1,"opennt-100-nti": 1,"ntconsole-100-nti": 1,
+ // U/Win
+ "uwin": 1,
+ // OBSOLETE PERSONAL-MICRO CONSOLES AND EMULATIONS
+ // Apple II
+ "appleIIgs": 1,"appleIIe": 1,"appleIIc": 1,"apple2e": 1,"apple2e-p": 1,"apple-ae": 1,"appleII": 1,"apple-80": 1,"apple-soroc": 1,"apple-videx": 1,"apple-uterm-vb": 1,"apple-uterm": 1,"apple80p": 1,"apple-videx2": 1,"apple-videx3": 1,"vapple": 1,"aepro": 1,"apple-vm80": 1,"ap-vm80": 1,
+ // Apple Lisa & Macintosh
+ "lisa": 1,"liswb": 1,"lisaterm": 1,"lisaterm-w": 1,"mac": 1,"macintosh": 1,"mac-w": 1,"macterminal-w": 1,
+ // Commodore Business Machines
+ "amiga": 1,"amiga-h": 1,"amiga-8bit": 1,"amiga-vnc": 1,"morphos": 1,"commodore": 1,"b-128": 1,
+ // Console types for obsolete UNIX clones
+ "minix": 1,"minix-3.0": 1,"minix-1.7": 1,"minix-old": 1,"minix-1.5": 1,"minix-old-am": 1,"pc-minix": 1,"pc-coherent": 1,"pcz19": 1,"coherent": 1,"pc-venix": 1,"venix": 1,
+ // IBM PC and clones
+ "pcplot": 1,"kaypro": 1,"kaypro2": 1,"ibm-pc": 1,"ibm5051": 1,"5051": 1,"ibmpc": 1,"wy60-PC": 1,"wyse60-PC": 1,
+ // Miscellaneous microcomputer consoles
+ "mai": 1,"basic4": 1,"basis": 1,"luna": 1,"luna68k": 1,"megatek": 1,"xerox820": 1,"x820": 1,
+ // North Star
+ "northstar": 1,
+ // Osborne
+ "osborne-w": 1,"osborne1-w": 1,"osborne": 1,"osborne1": 1,"osexec": 1,
+ // Radio Shack/Tandy
+ "coco3": 1,"os9LII": 1,"trs2": 1,"trsII": 1,"trs80II": 1,"trs16": 1,
+ // Videotex and teletext
+ "m2-nam": 1,"minitel": 1,"minitel-2": 1,"minitel-2-nam": 1,"minitel1": 1,"minitel1b": 1,"minitel1b-80": 1,"minitel1-nb": 1,"minitel1b-nb": 1,"minitel2-80": 1,"minitel12-80": 1,"screen.minitel1": 1,"screen.minitel1b": 1,"screen.minitel1b-80": 1,"screen.minitel2-80": 1,"screen.minitel12-80": 1,"screen.minitel1-nb": 1,"screen.minitel1b-nb": 1,"linux-m1": 1,"linux-m1b": 1,"linux-m2": 1,"linux-s": 1,"screen.linux-m1": 1,"screen.linux-m1b": 1,"screen.linux-m2": 1,"putty-m1": 1,"putty-m1b": 1,"putty-m2": 1,"putty+screen": 1,"putty-screen": 1,"screen.putty-m1": 1,"screen.putty-m1b": 1,"screen.putty-m2": 1,"viewdata": 1,"viewdata-o": 1,"viewdata-rv": 1,
+ // OBSOLETE UNIX CONSOLES
+ // AT&T consoles
+ "att6386": 1,"at386": 1,"386at": 1,"pc6300plus": 1,"att7300": 1,"unixpc": 1,"pc7300": 1,"3b1": 1,"s4": 1,
+ // Apollo consoles
+ "apollo": 1,"apollo+vt132": 1,"apollo_15P": 1,"apollo_19L": 1,"apollo_color": 1,
+ // Convergent Technology
+ "aws": 1,"awsc": 1,
+ // DEC consoles
+ "qdss": 1,"qdcons": 1,
+ // Fortune Systems consoles
+ "fos": 1,"fortune": 1,
+ // Masscomp consoles
+ "masscomp": 1,"masscomp1": 1,"masscomp2": 1,
+ // OSF Unix
+ "pmcons": 1,"pmconsole": 1,
+ // Other consoles
+ "pcix": 1,"ibmpcx": 1,"xenix": 1,"ibmx": 1,
+ // OBSOLETE VDT TYPES
+ // Amtek Business Machines
+ "abm80": 1,
+ // Bell Labs blit terminals
+ "blit": 1,"jerq": 1,"cbblit": 1,"fixterm": 1,"oblit": 1,"ojerq": 1,
+ // Bolt, Beranek & Newman (bbn)
+ "bitgraph": 1,"bg2.0nv": 1,"bg3.10nv": 1,"bg2.0rv": 1,"bg3.10rv": 1,"bg2.0": 1,"bg3.10": 1,"bg1.25rv": 1,"bg1.25nv": 1,"bg1.25": 1,
+ // Bull (bq, dku, vip)
+ "tws-generic": 1,"dku7102": 1,"tws2102-sna": 1,"dku7102-sna": 1,"tws2103": 1,"xdku": 1,"tws2103-sna": 1,"dku7103-sna": 1,"dku7102-old": 1,"dku7202": 1,"bq300": 1,"bq300-rv": 1,"bq300-w": 1,"bq300-w-rv": 1,"bq300-8": 1,"bq300-8rv": 1,"bq300-8w": 1,"bq300-w-8rv": 1,"bq300-pc": 1,"bq300-pc-rv": 1,"bq300-pc-w": 1,"bq300-pc-w-rv": 1,"bq300-8-pc": 1,"Q306-8-pc": 1,"bq300-8-pc-rv": 1,"bq300-8-pc-w": 1,"bq300-8-pc-w-rv": 1,"vip": 1,"vip-w": 1,"vip7800-w": 1,"Q310-vip-w": 1,"Q310-vip-w-am": 1,"vip-H": 1,"vip7800-H": 1,"Q310-vip-H": 1,"Q310-vip-H-am": 1,"vip-Hw": 1,"vip7800-Hw": 1,"Q310-vip-Hw": 1,
+ // Chromatics
+ "cg7900": 1,"chromatics": 1,
+ // Computer Automation
+ "ca22851": 1,
+ // Cybernex
+ "cyb83": 1,"xl83": 1,"cyb110": 1,"mdl110": 1,
+ // DEC terminals (Obsolete types: DECwriter and VT40/42/50)
+ "vt52+keypad": 1,"gt40": 1,"gt42": 1,"vt50": 1,"vt50h": 1,"vt61": 1,"vt-61": 1,"vt61.5": 1,"gigi": 1,"vk100": 1,"pro350": 1,"decpro": 1,"dw1": 1,"dw2": 1,"decwriter": 1,"dw": 1,"dw3": 1,"la120": 1,"dw4": 1,"ln03": 1,"ln03-w": 1,
+ // Datapoint
+ "dp3360": 1,"datapoint": 1,"dp8242": 1,
+ // Delta Data (dd)
+ "delta": 1,"dd5000": 1,
+ // Digital Data Research (ddr)
+ "ddr": 1,"rebus3180": 1,"ddr3180": 1,
+ // Evans & Sutherland
+ "ps300": 1,
+ // General Electric (ge)
+ "terminet1200": 1,"terminet300": 1,"tn1200": 1,"tn300": 1,"terminet": 1,
+ // Heathkit/Zenith
+ "h19-a": 1,"h19a": 1,"heath-ansi": 1,"heathkit-a": 1,"h19-bs": 1,"h19-us": 1,"h19us": 1,"h19-smul": 1,"h19": 1,"heath": 1,"h19-b": 1,"heathkit": 1,"heath-19": 1,"z19": 1,"zenith": 1,"h19-u": 1,"h19-g": 1,"h19g": 1,"alto-h19": 1,"altoh19": 1,"altoheath": 1,"alto-heath": 1,"z29": 1,"zenith29": 1,"z29b": 1,"z29a": 1,"z29a-kc-bc": 1,"h29a-kc-bc": 1,"z29a-kc-uc": 1,"h29a-kc-uc": 1,"z29a-nkc-bc": 1,"h29a-nkc-bc": 1,"z29a-nkc-uc": 1,"h29a-nkc-uc": 1,"z39-a": 1,"z39a": 1,"zenith39-a": 1,"zenith39-ansi": 1,"z100": 1,"h100": 1,"z110": 1,"z-100": 1,"h-100": 1,"z100bw": 1,"h100bw": 1,"z110bw": 1,"z-100bw": 1,"h-100bw": 1,"p19": 1,"ztx": 1,"ztx11": 1,"zt-1": 1,"htx11": 1,"ztx-1-a": 1,
+ // IMS International (ims)
+ "ims950-b": 1,"ims950": 1,"ims950-rv": 1,"ims-ansi": 1,"ultima2": 1,"ultimaII": 1,
+ // Intertec Data Systems
+ "superbrain": 1,"intertube": 1,"intertec": 1,"intertube2": 1,
+ // Ithaca Intersystems
+ "graphos": 1,"graphos-30": 1,
+ // Modgraph
+ "modgraph": 1,"mod24": 1,"modgraph2": 1,"modgraph48": 1,"mod": 1,
+ // Morrow Designs
+ "mt70": 1,"mt-70": 1,
+ // Motorola
+ "ex155": 1,
+ // Omron
+ "omron": 1,
+ // RCA
+ "rca": 1,
+ // Ramtek
+ "rt6221": 1,"rt6221-w": 1,
+ // Selanar
+ "hirez100": 1,"hirez100-w": 1,
+ // Signetics
+ "vsc": 1,
+ // Soroc
+ "soroc120": 1,"iq120": 1,"soroc": 1,"soroc140": 1,"iq140": 1,
+ // Southwest Technical Products
+ "swtp": 1,"ct82": 1,
+ // Synertek
+ "synertek": 1,"ktm": 1,"synertek380": 1,
+ // Tab Office Products
+ "tab132": 1,"tab": 1,"tab132-15": 1,"tab132-w": 1,"tab132-rv": 1,"tab132-w-rv": 1,
+ // Teleray
+ "t3700": 1,"t3800": 1,"t1061": 1,"teleray": 1,"t1061f": 1,"t10": 1,"t16": 1,
+ // Texas Instruments (ti)
+ "ti700": 1,"ti733": 1,"ti735": 1,"ti745": 1,"ti800": 1,"ti703": 1,"ti707": 1,"ti703-w": 1,"ti707-w": 1,"ti916": 1,"ti916-220-7": 1,"ti916-8": 1,"ti916-220-8": 1,"ti916-132": 1,"ti916-8-132": 1,"ti924": 1,"ti924-8": 1,"ti924w": 1,"ti924-8w": 1,"ti931": 1,"ti926": 1,"ti926-8": 1,"ti_ansi": 1,"ti928": 1,"ti928-8": 1,
+ // Zentec (zen)
+ "zen30": 1,"z30": 1,"zen50": 1,"z50": 1,"cci": 1,"cci1": 1,"z8001": 1,"zen8001": 1,
+ // OLDER TERMINAL TYPES
+ // AT&T (att, tty)
+ "att2300": 1,"sv80": 1,"att2350": 1,"att5410v1": 1,"att4410v1": 1,"tty5410v1": 1,"att4410v1-w": 1,"att5410v1-w": 1,"tty5410v1-w": 1,"att4410": 1,"att5410": 1,"tty5410": 1,"att5410-w": 1,"att4410-w": 1,"4410-w": 1,"tty5410-w": 1,"5410-w": 1,"v5410": 1,"att4415": 1,"tty5420": 1,"att5420": 1,"att4415-w": 1,"tty5420-w": 1,"att5420-w": 1,"att4415-rv": 1,"tty5420-rv": 1,"att5420-rv": 1,"att4415-w-rv": 1,"tty5420-w-rv": 1,"att5420-w-rv": 1,"att4415+nl": 1,"tty5420+nl": 1,"att5420+nl": 1,"att4415-nl": 1,"tty5420-nl": 1,"att5420-nl": 1,"att4415-rv-nl": 1,"tty5420-rv-nl": 1,"att5420-rv-nl": 1,"att4415-w-nl": 1,"tty5420-w-nl": 1,"att5420-w-nl": 1,"att4415-w-rv-n": 1,"tty5420-w-rv-n": 1,"att5420-w-rv-n": 1,"att5420_2": 1,"att5420_2-w": 1,"att4418": 1,"att5418": 1,"att4418-w": 1,"att5418-w": 1,"att4420": 1,"tty4420": 1,"att4424": 1,"tty4424": 1,"att4424-1": 1,"tty4424-1": 1,"att4424m": 1,"tty4424m": 1,"att5425": 1,"tty5425": 1,"att4425": 1,"att5425-nl": 1,"tty5425-nl": 1,"att4425-nl": 1,"att5425-w": 1,"att4425-w": 1,"tty5425-w": 1,"att4426": 1,"tty4426": 1,"att510a": 1,"bct510a": 1,"att510d": 1,"bct510d": 1,"att500": 1,"att513": 1,"att5310": 1,"att5320": 1,"att5620-1": 1,"tty5620-1": 1,"dmd1": 1,"att5620": 1,"dmd": 1,"tty5620": 1,"ttydmd": 1,"5620": 1,"att5620-24": 1,"tty5620-24": 1,"dmd-24": 1,"att5620-34": 1,"tty5620-34": 1,"dmd-34": 1,"att5620-s": 1,"tty5620-s": 1,"layer": 1,"vitty": 1,"att605": 1,"att605-pc": 1,"att605-w": 1,"att610": 1,"att610-w": 1,"att610-103k": 1,"att610-103k-w": 1,"att615": 1,"att615-w": 1,"att615-103k": 1,"att615-103k-w": 1,"att620": 1,"att620-w": 1,"att620-103k": 1,"att620-103k-w": 1,"att630": 1,"att630-24": 1,"5630-24": 1,"5630DMD-24": 1,"630MTG-24": 1,"att700": 1,"att730": 1,"att730-41": 1,"730MTG-41": 1,"att730-24": 1,"730MTG-24": 1,"att730r": 1,"730MTGr": 1,"att730r-41": 1,"730MTG-41r": 1,"att730r-24": 1,"730MTGr-24": 1,"att505": 1,"pt505": 1,"att5430": 1,"gs5430": 1,"att505-24": 1,"pt505-24": 1,"gs5430-24": 1,"att505-22": 1,"pt505-22": 1,"gs5430-22": 1,
+ // Ampex (Dialogue)
+ "ampex80": 1,"a80": 1,"d80": 1,"dialogue": 1,"dialogue80": 1,"ampex175": 1,"ampex175-b": 1,"ampex210": 1,"a210": 1,"ampex219": 1,"ampex-219": 1,"amp219": 1,"ampex219w": 1,"ampex-219w": 1,"amp219w": 1,"ampex232": 1,"ampex-232": 1,"ampex232w": 1,
+ // Ann Arbor (aa)
+ "annarbor4080": 1,"aa4080": 1,"aas1901": 1,"aaa+unk": 1,"aaa-unk": 1,"aaa+rv": 1,"aaa+dec": 1,"aaa-18": 1,"aaa-18-rv": 1,"aaa-20": 1,"aaa-22": 1,"aaa-24": 1,"aaa-24-rv": 1,"aaa-26": 1,"aaa-28": 1,"aaa-30-s": 1,"aaa-s": 1,"aaa-30-s-rv": 1,"aaa-s-rv": 1,"aaa-s-ctxt": 1,"aaa-30-s-ctxt": 1,"aaa-s-rv-ctxt": 1,"aaa-30-s-rv-ct": 1,"aaa": 1,"aaa-30": 1,"ambas": 1,"ambassador": 1,"aaa-30-rv": 1,"aaa-rv": 1,"aaa-30-ctxt": 1,"aaa-ctxt": 1,"aaa-30-rv-ctxt": 1,"aaa-rv-ctxt": 1,"aaa-36": 1,"aaa-36-rv": 1,"aaa-40": 1,"aaa-40-rv": 1,"aaa-48": 1,"aaa-48-rv": 1,"aaa-60-s": 1,"aaa-60-s-rv": 1,"aaa-60-dec-rv": 1,"aaa-60": 1,"aaa-60-rv": 1,"aaa-db": 1,"guru": 1,"guru-33": 1,"guru+unk": 1,"guru+rv": 1,"guru-rv": 1,"guru-33-rv": 1,"guru+s": 1,"guru-nctxt": 1,"guru-s": 1,"guru-33-s": 1,"guru-24": 1,"guru-44": 1,"guru-44-s": 1,"guru-76": 1,"guru-76-s": 1,"guru-76-lp": 1,"guru-lp": 1,"guru-76-w": 1,"guru-76-w-s": 1,"guru-76-wm": 1,"aaa-rv-unk": 1,
+ // Applied Digital Data Systems (adds)
+ "regent": 1,"regent100": 1,"regent20": 1,"regent25": 1,"regent40": 1,"regent40+": 1,"regent60": 1,"regent200": 1,"adds200": 1,"viewpoint": 1,"addsviewpoint": 1,"screwpoint": 1,"vp3a+": 1,"viewpoint3a+": 1,"vp60": 1,"viewpoint60": 1,"addsvp60": 1,"vp90": 1,"viewpoint90": 1,"adds980": 1,"a980": 1,
+ // Beehive Medical Electronics
+ "beehive": 1,"bee": 1,"beehive3": 1,"bh3m": 1,"beehiveIIIm": 1,"beehive4": 1,"bh4": 1,"microb": 1,"microbee": 1,"ha8675": 1,"ha8686": 1,
+ // C. Itoh Electronics
+ "cit80": 1,"cit-80": 1,"cit101": 1,"citc": 1,"cit101e": 1,"cit101e-rv": 1,"cit101e-n": 1,"cit101e-132": 1,"cit101e-n132": 1,"cit500": 1,"citoh": 1,"ci8510": 1,"8510": 1,"citoh-pica": 1,"citoh-elite": 1,"citoh-comp": 1,"citoh-prop": 1,"citoh-ps": 1,"ips": 1,"citoh-6lpi": 1,"citoh-8lpi": 1,
+ // Contel Business Systems.
+ "contel300": 1,"contel320": 1,"c300": 1,"contel301": 1,"contel321": 1,"c301": 1,"c321": 1,
+ // Control Data (cdc)
+ "cdc456": 1,"cdc721": 1,"cdc721ll": 1,"cdc752": 1,"cdc756": 1,"cdc721-esc": 1,
+ // Data General (dg)
+ "dgkeys+8b": 1,"dgkeys+7b": 1,"dgkeys+11": 1,"dgkeys+15": 1,"dgunix+fixed": 1,"dg+fixed": 1,"dg+color8": 1,"dg+color": 1,"dgmode+color8": 1,"dgmode+color": 1,"dgunix+ccc": 1,"dg+ccc": 1,"dg-generic": 1,"dg200": 1,"dg210": 1,"dg-ansi": 1,"dg211": 1,"dg450": 1,"dg6134": 1,"dg460-ansi": 1,"dg6053-old": 1,"dg100": 1,"dg6053": 1,"6053": 1,"6053-dg": 1,"dg605x": 1,"605x": 1,"605x-dg": 1,"d2": 1,"d2-dg": 1,"d200": 1,"d200-dg": 1,"d210": 1,"d214": 1,"d210-dg": 1,"d214-dg": 1,"d211": 1,"d215": 1,"d211-7b": 1,"d215-7b": 1,"d211-dg": 1,"d215-dg": 1,"d216-dg": 1,"d216e-dg": 1,"d216+dg": 1,"d216e+dg": 1,"d217-dg": 1,"d216-unix": 1,"d216e-unix": 1,"d216+": 1,"d216e+": 1,"d216-unix-25": 1,"d216+25": 1,"d217-unix": 1,"d217-unix-25": 1,"d220": 1,"d220-7b": 1,"d220-dg": 1,"d230c": 1,"d230": 1,"d230c-dg": 1,"d230-dg": 1,"d400": 1,"d400-dg": 1,"d450": 1,"d450-dg": 1,"d410": 1,"d411": 1,"d460": 1,"d461": 1,"d410-7b": 1,"d411-7b": 1,"d460-7b": 1,"d461-7b": 1,"d410-dg": 1,"d460-dg": 1,"d411-dg": 1,"d461-dg": 1,"d410-w": 1,"d411-w": 1,"d460-w": 1,"d461-w": 1,"d410-7b-w": 1,"d411-7b-w": 1,"d460-7b-w": 1,"d461-7b-w": 1,"d412-dg": 1,"d462-dg": 1,"d462e-dg": 1,"d412+dg": 1,"d462+dg": 1,"d413-dg": 1,"d463-dg": 1,"d412-unix": 1,"d462-unix": 1,"d412+": 1,"d462+": 1,"d412-unix-w": 1,"d462-unix-w": 1,"d412+w": 1,"d462+w": 1,"d412-unix-25": 1,"d462-unix-25": 1,"d412+25": 1,"d462+25": 1,"d412-unix-s": 1,"d462-unix-s": 1,"d412+s": 1,"d462+s": 1,"d412-unix-sr": 1,"d462-unix-sr": 1,"d412+sr": 1,"d462+sr": 1,"d413-unix": 1,"d463-unix": 1,"d413-unix-w": 1,"d463-unix-w": 1,"d413-unix-25": 1,"d463-unix-25": 1,"d413-unix-s": 1,"d463-unix-s": 1,"d413-unix-sr": 1,"d463-unix-sr": 1,"d414-unix": 1,"d464-unix": 1,"d414-unix-w": 1,"d464-unix-w": 1,"d414-unix-25": 1,"d464-unix-25": 1,"d414-unix-s": 1,"d464-unix-s": 1,"d414-unix-sr": 1,"d464-unix-sr": 1,"d430c-dg": 1,"d430-dg": 1,"d430c-dg-ccc": 1,"d430-dg-ccc": 1,"d430c-unix": 1,"d430-unix": 1,"d430c-unix-w": 1,"d430-unix-w": 1,"d430c-unix-25": 1,"d430-unix-25": 1,"d430c-unix-s": 1,"d430-unix-s": 1,"d430c-unix-sr": 1,"d430-unix-sr": 1,"d430c-unix-ccc": 1,"d430-unix-ccc": 1,"d430c-unix-w-ccc": 1,"d430-unix-w-ccc": 1,"d430c-unix-25-ccc": 1,"d430-unix-25-ccc": 1,"d430c-unix-s-ccc": 1,"d430-unix-s-ccc": 1,"d430c-unix-sr-ccc": 1,"d430-unix-sr-ccc": 1,"d470c": 1,"d470": 1,"d470c-7b": 1,"d470-7b": 1,"d470c-dg": 1,"d470-dg": 1,"d555": 1,"d555-7b": 1,"d555-w": 1,"d555-7b-w": 1,"d555-dg": 1,"d577": 1,"d577-7b": 1,"d577-w": 1,"d577-7b-w": 1,"d577-dg": 1,"d578-dg": 1,"d578": 1,"d578-7b": 1,
+ // Datamedia (dm)
+ "cs10": 1,"colorscan": 1,"cs10-w": 1,"dm1520": 1,"dm1521": 1,"dm2500": 1,"datamedia2500": 1,"dmchat": 1,"dm3025": 1,"dm3045": 1,"dm80": 1,"dmdt80": 1,"dt80": 1,"dm80w": 1,"dmdt80w": 1,"dt80w": 1,"dt80-sas": 1,"excel62": 1,"excel64": 1,"excel62-w": 1,"excel64-w": 1,"excel62-rv": 1,"excel64-rv": 1,
+ // Falco
+ "falco": 1,"ts1": 1,"ts-1": 1,"falco-p": 1,"ts1p": 1,"ts-1p": 1,"ts100": 1,"ts100-sp": 1,"ts100-ctxt": 1,
+ // Florida Computer Graphics
+ "beacon": 1,
+ // Fluke
+ "f1720": 1,"f1720a": 1,
+ // Getronics
+ "visa50": 1,
+ // GraphOn (go)
+ "go140": 1,"go140w": 1,"go225": 1,"go-225": 1,
+ // Harris (Beehive)
+ "sb1": 1,"sbi": 1,"superbee": 1,"superbee-xsb": 1,"superbeeic": 1,"sb2": 1,"sb3": 1,
+ // Hazeltine
+ "hz1000": 1,"hz1420": 1,"hz1500": 1,"hz1510": 1,"hz1520": 1,"hz1520-noesc": 1,"hz1552": 1,"hz1552-rv": 1,"hz2000": 1,"esprit": 1,"esprit-am": 1,"hmod1": 1,"hazel": 1,"exec80": 1,"h80": 1,"he80": 1,
+ // Human Designed Systems (Concept)
+ "c108": 1,"concept108": 1,"c108-8p": 1,"concept108-8p": 1,"c108-4p": 1,"concept108-4p": 1,"c108-rv": 1,"c108-rv-8p": 1,"c108-rv-4p": 1,"concept108rv4p": 1,"c108-w": 1,"c108-w-8p": 1,"concept108-w-8": 1,"concept108-w8p": 1,"c100": 1,"concept100": 1,"concept": 1,"c104": 1,"c100-4p": 1,"c100-rv": 1,"c100-rv-4p": 1,"concept100-rv": 1,"oc100": 1,"oconcept": 1,"c100-1p": 1,"hds200": 1,"avt-ns": 1,"avt-rv-ns": 1,"avt-w-ns": 1,"avt-w-rv-ns": 1,"avt+s": 1,"avt": 1,"avt-s": 1,"concept-avt": 1,"avt-rv": 1,"avt-rv-s": 1,"avt-w": 1,"avt-w-s": 1,"avt-w-rv": 1,"avt-w-rv-s": 1,
+ // IBM
+ "ibm327x": 1,"ibm3101": 1,"i3101": 1,"ibm3151": 1,"ibm3161": 1,"ibm3163": 1,"wy60-316X": 1,"wyse60-316X": 1,"ibm3161-C": 1,"ibm3162": 1,"ibm3164": 1,"i3164": 1,"ibm5151": 1,"wy60-AT": 1,"wyse60-AT": 1,"ibmaed": 1,"ibm-apl": 1,"apl": 1,"ibmmono": 1,"ibmega": 1,"ibm+color": 1,"ibm+16color": 1,"ibm5154": 1,"ibmega-c": 1,"ibm5154-c": 1,"ibmvga-c": 1,"ibmvga": 1,"rtpc": 1,"ibmapa16": 1,"ibm6155": 1,"ibmapa8c": 1,"ibmapa8": 1,"ibmapa8c-c": 1,"ibm6154-c": 1,"ibm6154": 1,"ibm6153": 1,"ibm6153-90": 1,"ibm6153-40": 1,"ibm8512": 1,"ibm8513": 1,"hft-c": 1,"hft-c-old": 1,"hft-old": 1,"ibm-system1": 1,"system1": 1,"lft": 1,"lft-pc850": 1,"LFT-PC850": 1,"ibm5081": 1,"hft": 1,"ibm5081-c": 1,"ibmmpel-c": 1,"ibm8503": 1,"ibm8507": 1,"ibm8604": 1,"ibm8514": 1,"ibm8514-c": 1,"aixterm": 1,"aixterm+sl": 1,"aixterm-m": 1,"aixterm-m-old": 1,"jaixterm": 1,"jaixterm-m": 1,"aixterm-16color": 1,
+ // Infoton/General Terminal Corp.
+ "i100": 1,"gt100": 1,"gt100a": 1,"i400": 1,"addrinfo": 1,"infoton2": 1,"infoton": 1,"icl6404": 1,"kds7372": 1,"icl6402": 1,"kds6402": 1,"icl6404-w": 1,"kds7372-w": 1,
+ // Interactive Systems Corp
+ "intext": 1,"intext2": 1,"intextii": 1,
+ // Kimtron (abm, kt)
+ "abm85": 1,"abm85h": 1,"abm85e": 1,"abm85h-old": 1,"oabm85h": 1,"o85h": 1,"kt7": 1,"kt7ix": 1,
+ // Liberty Electronics (Freedom)
+ "f100": 1,"freedom": 1,"freedom100": 1,"f100-rv": 1,"freedom-rv": 1,"f110": 1,"freedom110": 1,"f110-14": 1,"f110-w": 1,"f110-14w": 1,"f200": 1,"freedom200": 1,"f200-w": 1,"f200vi": 1,"f200vi-w": 1,
+ // Microdata/MDIS
+ "prism2": 1,"prism4": 1,"p4": 1,"P4": 1,"prism5": 1,"p5": 1,"P5": 1,"prism7": 1,"p7": 1,"P7": 1,"prism8": 1,"p8": 1,"P8": 1,"prism8-w": 1,"p8-w": 1,"P8-W": 1,"prism9": 1,"p9": 1,"P9": 1,"prism9-w": 1,"p9-w": 1,"P9-W": 1,"prism9-8": 1,"p9-8": 1,"P9-8": 1,"prism9-8-w": 1,"p9-8-w": 1,"P9-8-W": 1,"prism12": 1,"p12": 1,"P12": 1,"prism12-w": 1,"p12-w": 1,"P12-W": 1,"prism12-m": 1,"p12-m": 1,"P12-M": 1,"prism12-m-w": 1,"p12-m-w": 1,"P12-M-W": 1,"prism14": 1,"p14": 1,"P14": 1,"prism14-w": 1,"p14-w": 1,"P14-W": 1,"prism14-m": 1,"p14-m": 1,"P14-M": 1,"prism14-m-w": 1,"p14-m-w": 1,"P14-M-W": 1,"p8gl": 1,"prism8gl": 1,
+ // Microterm (act, mime)
+ "act4": 1,"microterm": 1,"act5": 1,"microterm5": 1,"mime-fb": 1,"mime-hb": 1,"mime": 1,"mime1": 1,"mime2": 1,"mimei": 1,"mimeii": 1,"mime2a-s": 1,"mime2a": 1,"mime2a-v": 1,"mime3a": 1,"mime3ax": 1,"mime-3ax": 1,"mime314": 1,"mm314": 1,"mm340": 1,"mime340": 1,"mt4520-rv": 1,"ergo4000": 1,
+ // NCR
+ "ncr260intan": 1,"ncr260intwan": 1,"ncr260intpp": 1,"ncr260intwpp": 1,"ncr260vppp": 1,"ncr260vp+sl": 1,"ncr260vpwpp": 1,"ncr260vt100an": 1,"ncr260vt+sl": 1,"ncr260vt100wan": 1,"ncr260vt100pp": 1,"ncr260vt100wpp": 1,"ncr260vt200an": 1,"ncr260vt200wan": 1,"ncr260vt200pp": 1,"ncr260vt200wpp": 1,"ncr260vt300an": 1,"ncr260vt300wan": 1,"ncr260vt300pp": 1,"ncr260vt300wpp": 1,"NCR260VT300WPP": 1,"ncr260wy325pp": 1,"ncr260wy325wpp": 1,"ncr260wy350pp": 1,"ncr260wy350wpp": 1,"ncr260wy50+pp": 1,"ncr260wy50+wpp": 1,"ncr260wy60pp": 1,"ncr260wy60wpp": 1,"ncr160vppp": 1,"ncr160vpwpp": 1,"ncr160vt100an": 1,"ncr160vt100pp": 1,"ncr160vt100wan": 1,"ncr160vt100wpp": 1,"ncr160vt200an": 1,"ncr160vt200pp": 1,"ncr160vt200wan": 1,"ncr160vt200wpp": 1,"ncr160vt300an": 1,"ncr160vt300pp": 1,"ncr160vt300wan": 1,"ncr160vt300wpp": 1,"ncr160wy50+pp": 1,"ncr160wy50+wpp": 1,"ncr160wy60pp": 1,"ncr160wy60wpp": 1,"ncrvt100an": 1,"ncrvt100pp": 1,"ncrvt100wan": 1,"NCRVT100WPP": 1,"ncrvt100wpp": 1,"ncr7900i": 1,"ncr7900": 1,"n7900": 1,"ncr7900iv": 1,"ncr7901": 1,"ndr9500": 1,"nd9500": 1,"ndr9500-nl": 1,"ndr9500-25": 1,"ndr9500-25-nl": 1,"ndr9500-mc": 1,"ndr9500-25-mc": 1,"ndr9500-mc-nl": 1,"ndr9500-25-mc-nl": 1,
+ // Perkin-Elmer (Owl)
+ "bantam": 1,"pe550": 1,"pe6100": 1,"fox": 1,"pe1100": 1,"owl": 1,"pe1200": 1,"pe1251": 1,"pe6300": 1,"pe6312": 1,"pe7000m": 1,"pe7000c": 1,
+ // Sperry Univac
+ "uts30": 1,
+ // Tandem
+ "tandem6510": 1,"tandem653": 1,"t653x": 1,
+ // Tandy/Radio Shack
+ "dmterm": 1,"dt100": 1,"dt-100": 1,"dt100w": 1,"dt-100w": 1,"dt110": 1,"pt210": 1,
+ // Tektronix (tek)
+ "tek": 1,"tek4012": 1,"tek4013": 1,"tek4014": 1,"tek4015": 1,"tek4014-sm": 1,"tek4015-sm": 1,"tek4023": 1,"tek4024": 1,"tek4025": 1,"tek4027": 1,"tek4025-17": 1,"tek4025-17-ws": 1,"tek4025-ex": 1,"tek4027-ex": 1,"tek4025a": 1,"tek4025-cr": 1,"tek4025ex": 1,"4025ex": 1,"4027ex": 1,"tek4105": 1,"tek4105-30": 1,"tek4105a": 1,"tek4106brl": 1,"tek4107brl": 1,"tek4109brl": 1,"tek4107": 1,"tek4109": 1,"tek4207-s": 1,"otek4112": 1,"o4112-nd": 1,"otek4113": 1,"otek4114": 1,"tek4112": 1,"tek4114": 1,"tek4112-nd": 1,"tek4112-5": 1,"tek4113": 1,"tek4113-34": 1,"tek4113-nd": 1,"otek4115": 1,"tek4115": 1,"tek4125": 1,"tek4207": 1,"tek4404": 1,"ct8500": 1,"tek4205": 1,
+ // Teletype (tty)
+ "tty33": 1,"tty35": 1,"tty37": 1,"tty40": 1,"ds40": 1,"ds40-2": 1,"dataspeed40": 1,"tty43": 1,
+ // Tymshare
+ "scanset": 1,"sc410": 1,"sc415": 1,
+ // Volker-Craig (vc)
+ "vc303": 1,"vc103": 1,"vc203": 1,"vc303a": 1,"vc403a": 1,"vc404": 1,"vc404-s": 1,"vc414": 1,"vc414h": 1,"vc415": 1,
+ // OTHER OBSOLETE TYPES
+ // Daisy wheel printers
+ "diablo1620": 1,"diablo1720": 1,"diablo450": 1,"ipsi": 1,"diablo1620-m8": 1,"diablo1640-m8": 1,"diablo1640": 1,"diablo1730": 1,"diablo1740": 1,"diablo630": 1,"x1700": 1,"diablo": 1,"xerox": 1,"diablo1640-lm": 1,"diablo-lm": 1,"xerox-lm": 1,"diablo1740-lm": 1,"630-lm": 1,"1730-lm": 1,"x1700-lm": 1,"dtc382": 1,"dtc300s": 1,"gsi": 1,"aj830": 1,"aj832": 1,"aj": 1,"aj510": 1,"nec5520": 1,"nec": 1,"spinwriter": 1,"qume5": 1,"qume": 1,"xerox1720": 1,"x1720": 1,"x1750": 1,
+ // Miscellaneous obsolete terminals, manufacturers unknown
+ "cad68-3": 1,"cgc3": 1,"cad68-2": 1,"cgc2": 1,"cops10": 1,"cops": 1,"cops-10": 1,"d132": 1,"datagraphix": 1,"d800": 1,"digilog": 1,"dwk": 1,"dwk-vt": 1,"env230": 1,"envision230": 1,"ep48": 1,"ep4080": 1,"ep40": 1,"ep4000": 1,"ifmr": 1,"opus3n1+": 1,"teletec": 1,"v3220": 1,
+ // Obsolete non-ANSI software emulations
+ "ctrm": 1,"gs6300": 1,"emots": 1,"h19k": 1,"h19kermit": 1,"versaterm": 1,"xtalk": 1,"simterm": 1,
+ // UNIX VIRTUAL TERMINALS, VIRTUAL CONSOLES, AND TELNET CLIENTS
+ // NCSA Telnet
+ "ncsa-m": 1,"ncsa-vt220-8": 1,"ncsa": 1,"ncsa-ns": 1,"ncsa-m-ns": 1,"ncsa-vt220": 1,
+ // Pilot Pro Palm-Top
+ "pilot": 1,"tgtelnet": 1,"elks-glasstty": 1,"elks-vt52": 1,"elks-ansi": 1,"elks": 1,"sibo": 1,
+ // Screen
+ "screen2": 1,"screen3": 1,"screen4": 1,"screen5": 1,
+ // CB UNIX, early 80s
+ "cbunix": 1,"vremote": 1,"pty": 1,
+ // X TERMINAL EMULATORS
+ // EMU
+ "emu": 1,"emu-220": 1,
+ // GNOME (VTE)
+ "gnome-rh62": 1,"gnome-rh72": 1,"gnome-rh80": 1,"gnome-rh90": 1,"gnome-fc5": 1,"vte-2007": 1,"gnome-2007": 1,"vte-2008": 1,"gnome-2008": 1,"vte-2012": 1,"gnome-2012": 1,"gnome+pcfkeys": 1,"gnome": 1,"gnome-256color": 1,"vte-2014": 1,"vte-2017": 1,"vte-2018": 1,
+ // HPTERM
+ "hpterm": 1,"X-hpterm": 1,"hpterm-color": 1,"hpterm-color2": 1,"X-hpterm-color2": 1,
+ // KDE
+ "kvt": 1,"konsole-xf3x": 1,"konsole-xf4x": 1,
+ // KTERM
+ "kterm": 1,"kterm-color": 1,"kterm-co": 1,
+ // MGR
+ "mgr": 1,"mgr-sun": 1,"mgr-linux": 1,
+ // MLTERM
+ "mlterm3": 1,"mlterm2": 1,
+ // MRXVT
+ "mrxvt": 1,"mrxvt-256color": 1,
+ // MTERM
+ "mterm-ansi": 1,"mterm": 1,"mouse-sun": 1,"decansi": 1,
+ // MVTERM
+ "mvterm": 1,"vv100": 1,
+ // Other GNOME
+ "mgt": 1,
+ // Other XTERM
+ "xtermm": 1,"xtermc": 1,"xterm-pcolor": 1,"color_xterm": 1,"cx": 1,"cx100": 1,"xterm-sun": 1,"xterms-sun": 1,
+ // RXVT
+ "rxvt-basic": 1,"rxvt+pcfkeys": 1,"rxvt": 1,"rxvt-color": 1,"rxvt-256color": 1,"rxvt-88color": 1,"rxvt-xpm": 1,"rxvt-cygwin": 1,"rxvt-cygwin-native": 1,"rxvt-16color": 1,
+ // SIMPLETERM
+ "st-0.8": 1,"st-0.7": 1,"st-0.6": 1,"simpleterm": 1,"old-st": 1,
+ // TERMINOLOGY
+ "terminology-0.6.1": 1,"terminology-1.0.0": 1,"terminology-1.8.1": 1,
+ // VWM
+ "vwmterm": 1,
+ // XTERM
+ "x10term": 1,"vs100-x10": 1,"x10term+sl": 1,"xterm-r5": 1,"xterm-r6": 1,"xterm-old": 1,"xterm-xf86-v32": 1,"xterm-xf86-v33": 1,"xterm-xf86-v333": 1,"xterm-xf86-v40": 1,"xterm-xf86-v43": 1,"xterm-xf86-v44": 1,"xterm-xfree86": 1,"xterm-new": 1,
+ // XTERM Features
+ "xterm-8bit": 1,"xterm-hp": 1,"xterm-vt52": 1,"xterm-nic": 1,"xterm1": 1,
+}
diff --git a/tui/termfo/cmd/termfo/show.go b/tui/termfo/cmd/termfo/show.go
new file mode 100644
index 0000000..c0eef59
--- /dev/null
+++ b/tui/termfo/cmd/termfo/show.go
@@ -0,0 +1,107 @@
+package main
+
+import (
+ "fmt"
+ "sort"
+ "strings"
+
+ "citrons.xyz/talk/tui/termfo"
+ "citrons.xyz/talk/tui/termfo/caps"
+)
+
+func show(terms ...string) {
+ for _, t := range terms {
+ ti, err := termfo.New(t)
+ if err != nil {
+ fatalf("%s", err)
+ }
+ fmt.Println(fmtTerminfo(ti))
+ }
+}
+
+func fmtTerminfo(ti *termfo.Terminfo) string {
+ all := append(append(append(caps.TableBools, caps.TableNums...), caps.TableStrs...), ti.Extended...)
+ sort.Slice(all, func(i, j int) bool { return all[i].Long < all[j].Long })
+
+ // Highlight escape codes and such; makes it easier to read.
+ hi := func(s string) string { return s }
+ if isTerm {
+ hi = func(s string) string {
+ r := make([]byte, 0, len(s))
+ resetAfter := 0
+ for i, c := range []byte(s) {
+ if c == '\\' {
+ r = append(r, "\x1b[34m"...)
+ resetAfter = 1
+ if len(s) > i+1 && s[i+1] == 'x' {
+ resetAfter = 3
+ }
+ }
+
+ r = append(r, c)
+ if resetAfter > -1 {
+ resetAfter--
+ if resetAfter == -1 {
+ r = append(r, "\x1b[0m"...)
+ }
+ }
+ }
+ return string(r)
+ }
+ }
+
+ b := new(strings.Builder)
+ b.Grow(16384)
+ fmt.Fprintf(b, "Loaded from %s\n", ti.Location)
+ a := ""
+ if len(ti.Aliases) > 0 {
+ a = " (aliases: " + strings.Join(ti.Aliases, ", ") + ")"
+ }
+ fmt.Fprintf(b, "%s%s – %s\n\n", ti.Name, a, ti.Desc)
+
+ fmt.Fprintf(b, "%-8s │ %-26s │ %-26s │ Description\n", "Short", "Long", "Value")
+ fmt.Fprintf(b, "%s┼%s┼%s┼%s\n",
+ strings.Repeat("─", 9),
+ strings.Repeat("─", 28),
+ strings.Repeat("─", 28),
+ strings.Repeat("─", 22),
+ )
+ for _, k := range all {
+ var val string
+ if _, ok := ti.Bools[k]; ok {
+ val = "True"
+ } else if v, ok := ti.Numbers[k]; ok {
+ val = fmt.Sprintf("#%d", v)
+ } else if v, ok := ti.Strings[k]; ok {
+ val = strings.ReplaceAll(fmt.Sprintf("%#v", v), `\x1b`, `\E`)
+ val = val[1 : len(val)-1]
+ }
+
+ if val != "" {
+ var overflow string
+ if isTerm && len(val) > 26 {
+ overflow = val[26:]
+ val = val[:26]
+ }
+
+ // TODO: if it overflows at %\np then the "p" isn't highlighted
+ // (this is also why that reset is in there).
+ reset := ""
+ if isTerm {
+ reset = "\x1b[0m"
+ }
+ fmt.Fprintf(b, "%-8s │ %-26s │ %s%s │ %s\n", k.Short, k.Long, hi(fmt.Sprintf("%-26s", val)), reset, k.Desc)
+ for p, overflow := first(overflow, 26); p != ""; p, overflow = first(overflow, 26) {
+ fmt.Fprintf(b, "%-37s │ %s │\n", " ", hi(fmt.Sprintf("%-26s", p)))
+ }
+ }
+ }
+ return b.String()
+}
+
+func first(s string, n int) (string, string) {
+ if len(s) > n {
+ return s[:n], s[n:]
+ }
+ return s, ""
+}
diff --git a/tui/termfo/keys/key.go b/tui/termfo/keys/key.go
new file mode 100644
index 0000000..a4e454f
--- /dev/null
+++ b/tui/termfo/keys/key.go
@@ -0,0 +1,169 @@
+package keys
+
+import (
+ "fmt"
+ "strings"
+ "unicode/utf8"
+)
+
+// Modifiers keys.
+const (
+ Shift = 1 << 63
+ Ctrl = 1 << 62
+ Alt = 1 << 61
+
+ modmask = Shift | Ctrl | Alt
+)
+
+// Common control sequences better known by their name than letter+Ctrl
+// combination.
+const (
+ Tab = 'i' | Ctrl
+ Escape = '[' | Ctrl
+)
+
+// Key represents a keypress. This is formatted as follows:
+//
+// - First 32 bits → rune (int32)
+// - Next 16 bits → Named key constant.
+// - Bits 49-61 → Currently unused.
+// - Bit 62 → Alt
+// - Bit 63 → Ctrl
+// - Bit 64 → Shift
+//
+// The upshot of this is that you can now use a single value to test for all
+// combinations:
+//
+// switch Key(0x61) {
+// case 'a': // 'a' w/o modifiers
+// case 'a' | keys.Ctrl: // 'a' with control
+// case 'a' | keys.Ctrl | keys.Shift: // 'a' with shift and control
+//
+// case keys.Up: // Arrow up
+// case keys.Up | keys.Ctrl: // Arrow up with control
+// }
+//
+// Which is nicer than using two or three different variables to signal various
+// things.
+//
+// Letters are always in lower-case; use keys.Shift to test for upper case.
+//
+// Control sequences (0x00-0x1f, 0x1f), are used sent as key + Ctrl. So this:
+//
+// switch k {
+// case 0x09:
+// }
+//
+// Won't work. you need to use:
+//
+// switch k {
+// case 'i' | key.Ctrl:
+// }
+//
+// Or better yet:
+//
+// ti := termfo.New("")
+//
+// ...
+//
+// switch k {
+// case ti.Keys[keys.Tab]:
+// }
+//
+// This just makes more sense, because people press "<C-a>" not "Start of
+// heading".
+//
+// It's better to use the variables from the terminfo, in case it's something
+// different. Especially with things like Shift and Ctrl modifiers this can
+// differ.
+//
+// Note that support for multiple modifier keys is flaky across terminals.
+type Key uint64
+
+// Shift reports if the Shift modifier is set.
+func (k Key) Shift() bool { return k&Shift != 0 }
+
+// Ctrl reports if the Ctrl modifier is set.
+func (k Key) Ctrl() bool { return k&Ctrl != 0 }
+
+// Alt reports if the Alt modifier is set.
+func (k Key) Alt() bool { return k&Alt != 0 }
+
+// WithoutMods returns a copy of k without any modifier keys set.
+func (k Key) WithoutMods() Key { return k &^ modmask }
+
+// Valid reports if this key is valid.
+func (k Key) Valid() bool { return k&^modmask <= 1<<31 || k.Named() }
+
+// Named reports if this is a named key.
+func (k Key) Named() bool {
+ _, ok := keyNames[k&^modmask]
+ return ok
+}
+
+// Name gets the key name. This doesn't show if any modifiers are set; use
+// String() for that.
+func (k Key) Name() string {
+ k &^= modmask
+
+ n, ok := keyNames[k]
+ if ok {
+ return n
+ }
+ if !k.Valid() {
+ return fmt.Sprintf("Unknown key: 0x%x", uint64(k))
+ }
+ if rune(k) == utf8.RuneError {
+ return fmt.Sprintf("Invalid UTF-8: 0x%x", rune(k))
+ }
+
+ // TODO: maybe also other spaces like nbsp etc?
+ switch k {
+ case ' ':
+ return "Space"
+ case '\t':
+ return "Tab"
+ case Escape:
+ return "Esc"
+ }
+ return fmt.Sprintf("%c", rune(k))
+}
+
+func (k Key) String() string {
+ var (
+ hasMod = k.Ctrl() || k.Shift() || k.Alt()
+ name = k.Name()
+ named = utf8.RuneCountInString(name) > 1
+ b strings.Builder
+ )
+
+ b.Grow(8)
+
+ if hasMod || named {
+ b.WriteRune('<')
+ }
+
+ if k.Shift() {
+ b.WriteString("S-")
+ }
+ if k.Alt() {
+ b.WriteString("A-")
+ }
+
+ switch k {
+ case Tab:
+ b.WriteString("Tab")
+ case Escape:
+ b.WriteString("Esc")
+ default:
+ if k.Ctrl() {
+ b.WriteString("C-")
+ }
+ b.WriteString(k.Name())
+ }
+
+ if hasMod || named {
+ b.WriteRune('>')
+ }
+ return b.String()
+}
diff --git a/tui/termfo/keys/key_test.go b/tui/termfo/keys/key_test.go
new file mode 100644
index 0000000..2536ddf
--- /dev/null
+++ b/tui/termfo/keys/key_test.go
@@ -0,0 +1,31 @@
+package keys
+
+import (
+ "testing"
+)
+
+func TestKey(t *testing.T) {
+ t.Skip()
+ tests := []struct {
+ k Key
+ want string
+ }{
+ {'a', "<a>"},
+ {'a' | Shift, "<S-a>"},
+ {'a' | Ctrl | Shift, "<S-C-a>"},
+ {'a' | Ctrl | Shift | Alt, "<S-C-A-a>"},
+ {Tab, "<Tab>"},
+ {Tab | Ctrl, "<C-Tab>"},
+ {Up, "<Up>"},
+ {Up | Ctrl, "<C-Up>"},
+ }
+
+ for _, tt := range tests {
+ t.Run("", func(t *testing.T) {
+ h := tt.k.String()
+ if h != tt.want {
+ t.Errorf("\nwant: %s\nhave: %s", tt.want, h)
+ }
+ })
+ }
+}
diff --git a/tui/termfo/keys/keys.go b/tui/termfo/keys/keys.go
new file mode 100644
index 0000000..fe2424c
--- /dev/null
+++ b/tui/termfo/keys/keys.go
@@ -0,0 +1,120 @@
+// Code generated by term.h.zsh; DO NOT EDIT.
+
+package keys
+
+import "citrons.xyz/talk/tui/termfo/caps"
+
+// CursesVersion is the version of curses this data was generated with, as [implementation]-[version].
+const CursesVersion = `ncurses-6.5.20240511`
+
+// Keys maps caps.Cap to Key constants
+var Keys = map[*caps.Cap]Key{
+ caps.TableStrs[55]: Backspace,
+ caps.TableStrs[59]: Delete,
+ caps.TableStrs[61]: Down,
+ caps.TableStrs[66]: F1,
+ caps.TableStrs[67]: F10,
+ caps.TableStrs[68]: F2,
+ caps.TableStrs[69]: F3,
+ caps.TableStrs[70]: F4,
+ caps.TableStrs[71]: F5,
+ caps.TableStrs[72]: F6,
+ caps.TableStrs[73]: F7,
+ caps.TableStrs[74]: F8,
+ caps.TableStrs[75]: F9,
+ caps.TableStrs[76]: Home,
+ caps.TableStrs[77]: Insert,
+ caps.TableStrs[79]: Left,
+ caps.TableStrs[81]: PageDown,
+ caps.TableStrs[82]: PageUp,
+ caps.TableStrs[83]: Right,
+ caps.TableStrs[87]: Up,
+ caps.TableStrs[148]: BackTab,
+ caps.TableStrs[164]: End,
+ caps.TableStrs[165]: Enter,
+ caps.TableStrs[191]: ShiftDelete,
+ caps.TableStrs[194]: ShiftEnd,
+ caps.TableStrs[199]: ShiftHome,
+ caps.TableStrs[200]: ShiftInsert,
+ caps.TableStrs[201]: ShiftLeft,
+ caps.TableStrs[210]: ShiftRight,
+ caps.TableStrs[216]: F11,
+ caps.TableStrs[217]: F12,
+ caps.TableStrs[355]: Mouse,
+}
+
+// List of all key sequences we know about. This excludes most obscure ones not
+// present on modern devices.
+const (
+ // Special key used to signal errors.
+ UnknownSequence Key = iota + (1 << 32)
+
+ ShiftLeft
+ PageUp
+ Insert
+ ShiftInsert
+ Up
+ Right
+ F1
+ ShiftRight
+ F2
+ ShiftHome
+ F3
+ Delete
+ PageDown
+ F4
+ F10
+ F11
+ ShiftDelete
+ F5
+ Down
+ Mouse
+ F12
+ ShiftEnd
+ F6
+ Enter
+ Left
+ F7
+ End
+ F8
+ F9
+ Backspace
+ BackTab
+ Home
+)
+
+// Names of named key constants.
+var keyNames = map[Key]string{
+ ShiftLeft: `ShiftLeft`,
+ PageUp: `PageUp`,
+ Insert: `Insert`,
+ ShiftInsert: `ShiftInsert`,
+ Up: `Up`,
+ Right: `Right`,
+ F1: `F1`,
+ ShiftRight: `ShiftRight`,
+ F2: `F2`,
+ ShiftHome: `ShiftHome`,
+ F3: `F3`,
+ Delete: `Delete`,
+ PageDown: `PageDown`,
+ F4: `F4`,
+ F10: `F10`,
+ F11: `F11`,
+ ShiftDelete: `ShiftDelete`,
+ F5: `F5`,
+ Down: `Down`,
+ Mouse: `Mouse`,
+ F12: `F12`,
+ ShiftEnd: `ShiftEnd`,
+ F6: `F6`,
+ Enter: `Enter`,
+ Left: `Left`,
+ F7: `F7`,
+ End: `End`,
+ F8: `F8`,
+ F9: `F9`,
+ Backspace: `Backspace`,
+ BackTab: `BackTab`,
+ Home: `Home`,
+}
diff --git a/tui/termfo/load.go b/tui/termfo/load.go
new file mode 100644
index 0000000..4ceba63
--- /dev/null
+++ b/tui/termfo/load.go
@@ -0,0 +1,368 @@
+package termfo
+
+import (
+ "bytes"
+ "embed"
+ "encoding/binary"
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+
+ "citrons.xyz/talk/tui/termfo/caps"
+ "citrons.xyz/talk/tui/termfo/keys"
+)
+
+const (
+ headerMagic = 0o0432
+ headerMagicExt = 0o01036
+ headerSize = 12
+)
+
+// Loaders is a list of additional loader callbacks.
+//
+// See the documentation on New() for the exact loading order.
+//
+// Note that access to this isn't synchronized; it usually shouldn't be needed.
+var Loaders = []func(string) *Terminfo {loadBuiltin}
+
+func loadTerminfo(term string) (*Terminfo, error) {
+ if term == "" {
+ return nil, errors.New("TERM not set")
+ }
+
+ ti, fp, err := findTerminfo(term)
+ if err != nil {
+ return nil, fmt.Errorf("terminfo: %w", err)
+ }
+ if ti != nil {
+ return ti, nil
+ }
+
+ defer fp.Close()
+ return readTi(fp)
+}
+
+//go:embed terminfo/*
+var builtin embed.FS
+func loadBuiltin(term string) *Terminfo {
+ fp, err := builtin.Open("terminfo/" + term)
+ if err != nil {
+ return nil
+ }
+ ti, err := readTi(fp.(io.ReadSeeker))
+ if err != nil {
+ return nil
+ }
+ return ti
+}
+
+// See doc on New() for loading order.
+func findTerminfo(term string) (*Terminfo, *os.File, error) {
+ if terminfo := os.Getenv("TERMINFO"); terminfo != "" {
+ return fromPath(term, terminfo)
+ }
+
+ if _, ok := os.LookupEnv("NO_BUILTIN_TERMINFO"); !ok {
+ for _, l := range Loaders {
+ if ti := l(term); ti != nil {
+ return ti, nil, nil
+ }
+ }
+ }
+
+ if h := os.Getenv("HOME"); h != "" {
+ if _, fp, err := fromPath(term, h+"/.terminfo"); err == nil {
+ return nil, fp, nil
+ }
+ }
+
+ if dirs := os.Getenv("TERMINFO_DIRS"); dirs != "" {
+ for _, dir := range strings.Split(dirs, ":") {
+ if dir == "" {
+ dir = "/usr/share/terminfo"
+ }
+ if _, fp, err := fromPath(term, dir); err == nil {
+ return nil, fp, nil
+ }
+ }
+ }
+
+ if _, fp, err := fromPath(term, "/lib/terminfo"); err == nil {
+ return nil, fp, nil
+ }
+ return fromPath(term, "/usr/share/terminfo")
+}
+
+func fromPath(term, path string) (*Terminfo, *os.File, error) {
+ if _, err := os.Open(path); err != nil {
+ return nil, nil, err
+ }
+
+ fp, err := os.Open(path + "/" + term[0:1] + "/" + term) // e.g. x/xterm
+ if err == nil {
+ return nil, fp, nil
+ }
+
+ // 68/xterm; as used on Darwin/macOS.
+ fp, err = os.Open(path + "/" + hex.EncodeToString([]byte(term[:1])) + "/" + term)
+ return nil, fp, err
+}
+
+func readTi(fp io.ReadSeeker) (*Terminfo, error) {
+ // Read the header.
+ var header struct{ Magic, SizeNames, CountBools, CountNums, StrOffets, SizeTbl int16 }
+ if err := binary.Read(fp, binary.LittleEndian, &header); err != nil {
+ return nil, fmt.Errorf("terminfo: reading header: %w", err)
+ }
+
+ // The regular format has 16bit numbers, the "extended number format" is
+ // 32bits. It looks like tic will only compile them with 32bit numbers if
+ // needed, so both are common.
+ intSize := int16(2)
+ switch header.Magic {
+ case headerMagic:
+ case headerMagicExt:
+ intSize = 4
+ default:
+ return nil, fmt.Errorf("terminfo: unexpected magic number in header: 0o%o", header.Magic)
+ }
+
+ tiData := struct {
+ names []byte
+ bools []bool
+ align []byte
+ nums []byte // Can be 16 or 32 bit, will convert later.
+ strOffs []int16
+ strTbl []byte
+ }{
+ make([]byte, header.SizeNames),
+ make([]bool, header.CountBools),
+ make([]byte, align(header.SizeNames+header.CountBools)),
+ make([]byte, header.CountNums*intSize),
+ make([]int16, header.StrOffets),
+ make([]byte, header.SizeTbl),
+ }
+ err := readM(fp, &tiData.names, &tiData.bools, &tiData.align, &tiData.nums, &tiData.strOffs, &tiData.strTbl)
+ if err != nil {
+ return nil, fmt.Errorf("terminfo: reading data: %w", err)
+ }
+
+ // Terminal names separated by "|", with the last entry being the
+ // description. Ends with NUL.
+ snames := strings.Split(string(tiData.names[:len(tiData.names)-1]), "|")
+ ti := &Terminfo{
+ Name: snames[0],
+ Desc: snames[len(snames)-1],
+ Bools: make(map[*caps.Cap]struct{}, 8),
+ Numbers: make(map[*caps.Cap]int32, 8),
+ Strings: make(map[*caps.Cap]string, 32),
+ Keys: make(map[string]keys.Key, len(keys.Keys)),
+ IntSize: int(intSize),
+// Location: fp.Name(),
+ }
+ if len(snames) > 2 {
+ ti.Aliases = snames[1 : len(snames)-1]
+ }
+
+ // Booleans are one byte per value.
+ for i, b := range tiData.bools {
+ if b {
+ ti.Bools[caps.TableBools[i]] = struct{}{}
+ }
+ }
+ // Numbers can be 16 or 32bits, depending on the header. -1 means it's not
+ // present in the file.
+ for i, n := range toNum(tiData.nums, int(intSize)) {
+ if n > -1 {
+ ti.Numbers[caps.TableNums[i]] = n
+ }
+ }
+ // strOffs are offsets to an entry in strTbl; the table entries are ended by
+ // NULL bytes. -1 means the entry is missing.
+ for i, s := range tiData.strOffs {
+ if s > -1 {
+ ti.Strings[caps.TableStrs[i]] = string(tiData.strTbl[s : int(s)+bytes.IndexByte(tiData.strTbl[s:], 0)])
+ }
+ }
+
+ // The "extended storage format" has another header after the string table,
+ // which may or may not be present.
+
+ if tell, _ := fp.Seek(0, io.SeekCurrent); tell%2 != 0 {
+ fp.Read(make([]byte, 1))
+ }
+
+ var extHeader struct{ CountBools, CountNums, CountStrs, UsedStrs, SizeTbl int16 }
+ if err := binary.Read(fp, binary.LittleEndian, &extHeader); err != nil {
+ if errors.Is(err, io.EOF) { // No header: no problem.
+ return ti, nil
+ }
+ return nil, fmt.Errorf("terminfo: reading extended header: %w", err)
+ }
+ extData := struct {
+ bools []bool
+ align []byte
+ nums []byte
+ strOffs []int16
+ strTbl []byte
+ }{
+ make([]bool, extHeader.CountBools),
+ make([]byte, align(extHeader.CountBools)),
+ make([]byte, extHeader.CountNums*intSize),
+ make([]int16, extHeader.UsedStrs),
+ make([]byte, extHeader.SizeTbl),
+ }
+ if err := readM(fp, &extData.bools, &extData.align, &extData.nums, &extData.strOffs, &extData.strTbl); err != nil {
+ return nil, fmt.Errorf("terminfo: reading extended data: %w", err)
+ }
+
+ // The strings table includes both string values and the names of the
+ // extended capablities; CountStrs is the number of string values, UsedStrs
+ // is total number of strings.
+ startNames := -1
+ extStrs := make([]string, 0, extHeader.CountStrs)
+ for i := int16(0); i < extHeader.CountStrs; i++ {
+ s := extData.strOffs[i]
+ if s > -1 {
+ e := int(s) + bytes.IndexByte(extData.strTbl[s:], 0)
+ startNames = e
+ extStrs = append(extStrs, string(extData.strTbl[s:e]))
+ }
+ }
+
+ startNames++
+ ti.Extended = make([]*caps.Cap, extHeader.UsedStrs-extHeader.CountStrs)
+ for i := int16(0); i < extHeader.UsedStrs-extHeader.CountStrs; i++ {
+ s := extData.strOffs[i+extHeader.CountStrs] + int16(startNames)
+ e := int(s) + bytes.IndexByte(extData.strTbl[s:], 0)
+ name := string(extData.strTbl[s:e])
+
+ var c *caps.Cap
+ // TODO: it list AX and G0 in the file, but infocmp lists it as OTbs and
+ // OTpt? Hmm. Not sure where it gets that from.
+ for _, v := range caps.TableStrs {
+ if v.Short == name {
+ c = v
+ break
+ }
+ }
+ if c == nil {
+ for _, v := range caps.TableNums {
+ c = v
+ }
+ }
+ if c == nil {
+ for _, v := range caps.TableBools {
+ c = v
+ }
+ }
+ if c == nil {
+ c = &caps.Cap{Short: name, Long: name, Desc: "extended user-defined"}
+ }
+ ti.Extended[i] = c
+ }
+
+ // Don't need to check the value here, as it's never false or -1.
+ for i := range extData.bools {
+ ti.Bools[ti.Extended[i]] = struct{}{}
+ }
+ for i, n := range toNum(extData.nums, int(intSize)) {
+ ti.Numbers[ti.Extended[i+len(extData.bools)]] = n
+ }
+ for i, s := range extStrs {
+ ti.Strings[ti.Extended[i+len(extData.bools)+len(extData.nums)/int(intSize)]] = s
+ }
+ return ti, nil
+}
+
+// From term(5): "Between the boolean section and the number section, a null
+// byte will be inserted, if necessary, to ensure that the number section begins
+// on an even byte (this is a relic of the PDP-11's word-addressed architecture,
+// originally designed in to avoid IOT traps induced by addressing a word on an
+// odd byte boundary). All short integers are aligned on a short word boundary."
+func align(n int16) int {
+ if n%2 != 0 {
+ return 1
+ }
+ return 0
+}
+
+func readM(fp io.Reader, data ...any) error {
+ for _, d := range data {
+ if err := binary.Read(fp, binary.LittleEndian, d); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func toNum(read []byte, intSize int) []int32 {
+ nums := make([]int32, 0, len(read)/intSize)
+ for i := 0; i < len(read); i += intSize {
+ n := int32(read[i]) | int32(read[i+1])<<8
+ if intSize == 4 {
+ n |= int32(read[i+2])<<16 | int32(read[i+3])<<24
+ } else if n == 65535 { // -1 in int16; we need to add them as it's all offset based.
+ n = -1
+ }
+ nums = append(nums, n)
+ }
+ return nums
+}
+
+// This adds "PC-style function keys" modifiers, as Xterm does it. When a
+// modifier is used the character after the CSI is replaced with a modifier code
+// or inserted before the final ~. For example (CSI prefix omitted):
+//
+// F1 F5 Up
+// Regular OP 15~ OA
+// Ctrl 1;5P 15;5~ 1;5A
+// Shift 1;2P 15;2~ 1;2A
+// Alt 1;3P 15;3~ 1;3A
+//
+// Modifier codes:
+//
+// 2 Shift
+// 3 Alt
+// 4 Shift + Alt
+// 5 Ctrl
+// 6 Shift + Ctrl
+// 7 Alt + Ctrl
+// 8 Shift + Alt + Ctrl
+//
+// We don't do anything with meta.
+//
+// You tell me why it works like this... My guess that in 19verylongago it was
+// easier to do some bit banging like this on a very simple terminal (by the
+// standard of the last 30 years anyway), and now we're still stuck with this.
+//
+// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.pdf
+//
+// Anyway, pre-compute a list here so it's easier to check later. It adds about
+// 0.08ms startup time, which isn't too bad, and on the upside you'll save up to
+// a whopping 0.04ms latency on evey key stroke.
+func addModifierKeys(ti *Terminfo, seq string, k keys.Key) {
+ switch {
+ case strings.HasPrefix(seq, "\x1b[") && seq[len(seq)-1] == '~':
+ noTilde := seq[:len(seq)-1]
+ ti.Keys[noTilde+";2~"] = k | keys.Shift
+ ti.Keys[noTilde+";3~"] = k | keys.Alt
+ ti.Keys[noTilde+";4~"] = k | keys.Shift | keys.Alt
+ ti.Keys[noTilde+";5~"] = k | keys.Ctrl
+ ti.Keys[noTilde+";6~"] = k | keys.Shift | keys.Ctrl
+ ti.Keys[noTilde+";7~"] = k | keys.Ctrl | keys.Alt
+ ti.Keys[noTilde+";8~"] = k | keys.Shift | keys.Ctrl | keys.Alt
+ case strings.HasPrefix(seq, "\x1bO") && len(seq) == 3:
+ noCSI := seq[2:]
+ ti.Keys["\x1b[1;2"+noCSI] = k | keys.Shift
+ ti.Keys["\x1b[1;3"+noCSI] = k | keys.Alt
+ ti.Keys["\x1b[1;4"+noCSI] = k | keys.Shift | keys.Alt
+ ti.Keys["\x1b[1;5"+noCSI] = k | keys.Ctrl
+ ti.Keys["\x1b[1;6"+noCSI] = k | keys.Shift | keys.Ctrl
+ ti.Keys["\x1b[1;7"+noCSI] = k | keys.Ctrl | keys.Alt
+ ti.Keys["\x1b[1;8"+noCSI] = k | keys.Shift | keys.Ctrl | keys.Alt
+ }
+}
diff --git a/tui/termfo/param.go b/tui/termfo/param.go
new file mode 100644
index 0000000..dff556c
--- /dev/null
+++ b/tui/termfo/param.go
@@ -0,0 +1,506 @@
+package termfo
+
+import (
+ "fmt"
+ "reflect"
+ "runtime"
+ "strconv"
+ "strings"
+)
+
+// TODO: we can actually simplify a lot of this. This uses a two-stage "lex +
+// parse", but that's actually not really needed with this stack-based
+// mini-language since we don't really need to know all that much about syntax,
+// so we can just skip the entire lexing.
+//
+// Kind of obvious really but I didn't realize this until I implemented it.
+// Sometimes you need to implement something at least once to fully understand
+// it.
+//
+// Now I can't really be bothered to replace it. I didn't really *want* to write
+// a terminfo implementation, it was just needed for an application. Maybe
+// later.
+//
+// It also doesn't handle the named "dynamic" and "static" variables for now.
+// None of the common terminfos seem to use it so we can probably get away with
+// it. These are annoying as they persist across escape-string evaluations.
+// Something of a misfeature IMO.
+
+type item struct {
+ typ itemType
+ val string
+ pos int
+}
+
+type lexer struct {
+ input string
+ start, pos int
+ items chan item
+ state stateFn
+}
+
+func replaceParams(input string, args ...int) string {
+ l := &lexer{input: input, items: make(chan item, 2), state: lexTop}
+ var items []item
+ for {
+ select {
+ case item := <-l.items:
+ if item.typ == itemEOF {
+ return parseParams(items, args...)
+ }
+ items = append(items, item)
+ default:
+ l.state = l.state(l)
+ }
+ }
+}
+
+func printParams(input string, args ...int) string {
+ l := &lexer{input: input, items: make(chan item, 2), state: lexTop}
+ var items []item
+ for {
+ select {
+ case item := <-l.items:
+ if item.typ == itemEOF {
+ pnl := false
+ b := new(strings.Builder)
+ for _, c := range items {
+ if c.typ == itemIf && !pnl {
+ fmt.Fprint(b, "\n ")
+ }
+ pnl = false
+ fmt.Fprintf(b, "%s(%q) ", c.typ, c.val)
+ if c.typ == itemEndif {
+ fmt.Fprint(b, "\n ")
+ pnl = true
+ }
+ }
+ fmt.Fprint(b, "\n\n\n")
+ return b.String()
+ }
+ items = append(items, item)
+ default:
+ l.state = l.state(l)
+ }
+ }
+}
+
+func (l *lexer) peek() byte {
+ if l.pos+1 >= len(l.input) {
+ return 0
+ }
+ return l.input[l.pos+1]
+}
+func (l *lexer) backup() { l.pos-- }
+func (l *lexer) next() byte {
+ if l.pos >= len(l.input) {
+ return 0
+ }
+ l.pos++
+ return l.input[l.pos-1]
+}
+func (l *lexer) until(anyOf ...byte) {
+ for {
+ b := l.next()
+ for _, a := range anyOf {
+ if b == a {
+ return
+ }
+ }
+ }
+}
+
+func (l *lexer) emit(typ itemType) {
+ l.items <- item{typ: typ, pos: l.start, val: l.input[l.start:l.pos]}
+ l.start = l.pos
+}
+
+func lexTop(l *lexer) stateFn {
+ switch b := l.next(); b {
+ default:
+ return lexTop
+ case eof:
+ if l.pos > l.start {
+ l.emit(itemStr)
+ }
+ l.emit(itemEOF)
+ return lexTop
+ case '%':
+ l.backup()
+ if l.pos > l.start {
+ l.emit(itemStr)
+ }
+ l.next()
+ return lexPercent
+ }
+}
+
+// This doesn't do a lot of error checking; it sort-of assumes the terminfo is
+// sane.
+func lexPercent(l *lexer) stateFn {
+ switch b := l.next(); b {
+ case eof:
+ l.emit(itemEOF)
+ case '%':
+ l.emit(itemPercent)
+
+ // %[[:]flags][width[.precision]][doxXs]
+ case 'd', 'o', 'x', 'X', 's', 'c':
+ l.emit(itemPrint)
+ case ':', '1', '2', '3', '4', '5', '6', '7', '8', '9':
+ l.until('d', 'o', 'x', 'X', 's')
+ l.emit(itemPrint)
+ // %p[1-9]
+ case 'p':
+ l.next()
+ l.emit(itemPush)
+ // %P[a-z]
+ // %P[A-Z]
+ case 'P':
+ n := l.next()
+ if n >= 'a' && n <= 'z' {
+ l.emit(itemSetDynamic)
+ } else {
+ l.emit(itemSetStatic)
+ }
+ // %g[a-z]/
+ // %g[A-Z]
+ // The manpage lists this with a / at the end, but that seems a typo. It's
+ // not present in the actual terminfo files.
+ case 'g':
+ n := l.next()
+ if n >= 'a' && n <= 'z' {
+ l.emit(itemGetDynamic)
+ } else {
+ l.emit(itemGetStatic)
+ }
+ // %'c'
+ case '\'':
+ l.next()
+ l.next()
+ l.emit(itemCharConstant)
+ // %{nn}
+ case '{':
+ l.until('}')
+ l.emit(itemIntConstant)
+ // %l push strlen(pop)
+ case 'l':
+ l.emit(itemStrlen)
+ // %+, %-, %*, %/, %m
+ case '+':
+ l.emit(itemAdd)
+ case '-':
+ l.emit(itemSub)
+ case '*':
+ l.emit(itemMult)
+ case '/':
+ l.emit(itemDiv)
+ case 'm':
+ l.emit(itemMod)
+ // %&, %|, %^
+ case '&':
+ l.emit(itemAnd)
+ case '|':
+ l.emit(itemOr)
+ case '^':
+ l.emit(itemXor)
+ // %=, %>, %<
+ case '=':
+ l.emit(itemEq)
+ case '>':
+ l.emit(itemGt)
+ case '<':
+ l.emit(itemLt)
+ // %A, %O
+ case 'A':
+ l.emit(itemLogicalAnd)
+ case 'O':
+ l.emit(itemLogicalOr)
+ // %!, %~
+ case '!':
+ l.emit(itemBang)
+ case '~':
+ l.emit(itemTilde)
+ // %i
+ case 'i':
+ l.emit(itemIncParams)
+ // %? expr %t thenpart %e elsepart %;
+ case '?':
+ l.emit(itemIf)
+ case 't':
+ l.emit(itemThen)
+ case 'e':
+ l.emit(itemElse)
+ case ';':
+ l.emit(itemEndif)
+ }
+ return lexTop
+}
+
+func parseParams(items []item, args ...int) string {
+ params := make([]int, 9)
+ for i := range args {
+ params[i] = args[i]
+ }
+
+ var (
+ stack []int
+ doInc = 0
+ b = new(strings.Builder)
+ )
+
+ push := func(v int) { stack = append(stack, v) }
+ pop := func() int {
+ if len(stack) == 0 {
+ // Increment first two pops even if the stack is empty; a common
+ // "trick" is to use:
+ // \E[%i%d;%dR
+ // to print 0 for non-ANSI terminals and 1 for ANSI.
+ if doInc > 0 {
+ doInc--
+ return 1
+ }
+ return 0
+ }
+ pop := stack[len(stack)-1]
+ stack = stack[:len(stack)-1]
+ return pop
+ }
+ pushBool := func(v bool) {
+ if v {
+ stack = append(stack, 1)
+ } else {
+ stack = append(stack, 0)
+ }
+ }
+
+ cond := false
+ state := ""
+ for _, item := range items {
+ //fmt.Printf("%s -> %t\n", state, cond)
+ if item.typ == itemEndif {
+ state = ""
+ }
+ if state == "then" && !cond {
+ if item.typ == itemElse {
+ state = "else"
+ }
+ continue
+ }
+ if state == "else" && cond {
+ continue
+ }
+
+ switch item.typ {
+ case itemStr:
+ b.WriteString(item.val)
+ case itemPercent:
+ b.WriteByte('%')
+ case itemPush:
+ a, _ := strconv.Atoi(item.val[2:])
+ push(params[a-1])
+ case itemPrint:
+ p := pop()
+ as := item.val[len(item.val)-1]
+ if as == 's' {
+ as = 'v'
+ }
+ fmt.Fprintf(b, "%"+
+ strings.TrimLeft(item.val[1:len(item.val)-1], ":")+
+ string(as), p)
+ // from lib_tparam.c:
+ // Increment the first two parameters -- if they are numbers rather than
+ // strings. As a side effect, assign into the stack; if this is
+ // termcap, then the stack was populated using the termcap hack above
+ // rather than via the terminfo 'p' case.
+ case itemIncParams:
+ doInc = 2
+ params[0]++
+ params[1]++
+ case itemIntConstant:
+ n, _ := strconv.Atoi(item.val[2 : len(item.val)-1])
+ push(n)
+ case itemCharConstant: // %'c'
+ push(int(item.val[2]))
+ case itemStrlen:
+ push(len(strconv.Itoa(pop())))
+ case itemAdd:
+ a, b := pop(), pop()
+ push(b + a)
+ case itemSub:
+ a, b := pop(), pop()
+ push(b - a)
+ case itemMult:
+ a, b := pop(), pop()
+ push(b * a)
+ case itemDiv:
+ a, b := pop(), pop()
+ push(b / a)
+ case itemMod:
+ a, b := pop(), pop()
+ push(b % a)
+ case itemAnd:
+ a, b := pop(), pop()
+ push(b & a)
+ case itemOr:
+ a, b := pop(), pop()
+ push(b | a)
+ case itemXor:
+ a, b := pop(), pop()
+ push(b ^ a)
+ case itemTilde:
+ push(^pop())
+ case itemLogicalAnd:
+ a, b := pop() > 0, pop() > 0
+ pushBool(b && a)
+ case itemLogicalOr:
+ a, b := pop() > 0, pop() > 0
+ pushBool(b || a)
+ case itemEq:
+ a, b := pop(), pop()
+ pushBool(b == a)
+ case itemGt:
+ a, b := pop(), pop()
+ pushBool(b > a)
+ case itemLt:
+ a, b := pop(), pop()
+ pushBool(b < a)
+ case itemBang:
+ pushBool(!(pop() > 0))
+
+ case itemIf, itemEndif:
+ // Handled at start.
+ case itemElse:
+ state = "else"
+ case itemThen:
+ cond = pop() > 0
+ state = "then"
+ }
+ }
+ return b.String()
+}
+
+type stateFn func(l *lexer) stateFn
+
+func (s stateFn) String() string {
+ name := runtime.FuncForPC(reflect.ValueOf(s).Pointer()).Name()
+ if i := strings.LastIndexByte(name, '.'); i > -1 {
+ name = name[i+1:]
+ }
+ if s == nil {
+ name = "<nil>"
+ }
+ return name + "()"
+}
+
+type itemType int
+
+const (
+ itemEOF itemType = iota
+ itemStr
+ itemPrint
+ itemPush
+ itemCharConstant
+ itemIntConstant
+ itemPercent
+ itemIncParams
+ itemAdd
+ itemSub
+ itemMult
+ itemDiv
+ itemMod
+ itemAnd
+ itemOr
+ itemXor
+ itemLogicalAnd
+ itemLogicalOr
+ itemStrlen
+ itemIf
+ itemThen
+ itemElse
+ itemEndif
+ itemEq
+ itemGt
+ itemLt
+ itemBang
+ itemTilde
+
+ // Unhandled:
+ itemGetDynamic
+ itemGetStatic
+ itemSetDynamic
+ itemSetStatic
+)
+
+const eof = 0
+
+func (i itemType) String() string {
+ switch i {
+ default:
+ return "XXX"
+ case itemEOF:
+ return "EOF"
+ case itemStr:
+ return "str"
+ case itemPrint:
+ return "print"
+ case itemPush:
+ return "push"
+ case itemPercent:
+ return "%"
+ case itemSetDynamic:
+ return "setDyn"
+ case itemSetStatic:
+ return "setStat"
+ case itemGetDynamic:
+ return "getDyn"
+ case itemGetStatic:
+ return "getStat"
+ case itemCharConstant:
+ return "charConstant"
+ case itemIntConstant:
+ return "intConstant"
+ case itemStrlen:
+ return "strlen"
+ case itemAdd:
+ return "add"
+ case itemSub:
+ return "sub"
+ case itemMult:
+ return "mult"
+ case itemDiv:
+ return "div"
+ case itemMod:
+ return "mod"
+ case itemAnd:
+ return "and"
+ case itemOr:
+ return "or"
+ case itemXor:
+ return "xor"
+ case itemEq:
+ return "eq"
+ case itemGt:
+ return "gt"
+ case itemLt:
+ return "lt"
+ case itemLogicalAnd:
+ return "logicalAnd"
+ case itemLogicalOr:
+ return "logicalOr"
+ case itemBang:
+ return "bang"
+ case itemTilde:
+ return "tilde"
+ case itemIncParams:
+ return "incParams"
+ case itemIf:
+ return "if"
+ case itemThen:
+ return "then"
+ case itemElse:
+ return "else"
+ case itemEndif:
+ return "endif"
+ }
+}
diff --git a/tui/termfo/param_test.go b/tui/termfo/param_test.go
new file mode 100644
index 0000000..7408ce7
--- /dev/null
+++ b/tui/termfo/param_test.go
@@ -0,0 +1,111 @@
+package termfo
+
+import (
+ "os/exec"
+ "strconv"
+ "testing"
+)
+
+func TestParamsTput(t *testing.T) {
+ if _, err := exec.LookPath("tput"); err != nil {
+ t.Skipf("needs tput in PATH: %s", err)
+ }
+
+ tests := []struct {
+ term, capName string
+ in string
+ params []int
+ }{
+ {"xterm", "ech", "\x1b[%p1%dX", []int{5}},
+ // TODO: some more tests.
+ }
+
+ for _, tt := range tests {
+ t.Run("", func(t *testing.T) {
+ cmd := []string{"-T" + tt.term, tt.capName}
+ for _, a := range tt.params {
+ cmd = append(cmd, strconv.Itoa(a))
+ }
+ o, err := exec.Command("tput", cmd...).CombinedOutput()
+ if err != nil {
+ t.Fatal(err)
+ }
+ want := string(o)
+ have := replaceParams(tt.in, tt.params...)
+ if have != want {
+ t.Errorf("\nin: %q\nhave: %q\nwant: %q\n%s\n",
+ tt.in, have, want, printParams(tt.in, tt.params...))
+ }
+ })
+ }
+}
+
+func TestParams(t *testing.T) {
+ tests := []struct {
+ in, want string
+ }{
+ // xterm-256color
+ {"\x1b[%p1%dX", "\x1b[666X"}, // ech
+ {"\x1b[%i%p1%dG", "\x1b[667G"}, // hpa
+ {"\x1b[%p1%d q", "\x1b[666 q"}, // Ss
+ {"\x1b]12;%p1%s\a", "\x1b]12;666\a"}, // Cs
+ {"\x1b[%i%p1%dd", "\x1b[667d"}, // vpa
+ {"\x1b]52;%p1%s;%p2%s\a", "\x1b]52;666;777\a"}, // Ms
+ {"\x1b[%i%p1%d;%p2%dr", "\x1b[667;778r"}, // csr
+ {"\x1b[%i%p1%d;%p2%dH", "\x1b[667;778H"}, // cup
+ {"%p1%c\x1b[%p2%{1}%-%db", "ʚ\x1b[776b"}, // rep
+ {"\x1b[?69h\x1b[%i%p1%d;%p2%ds", "\x1b[?69h\x1b[667;778s"}, // smglr
+ {"\x1b[%i%d;%dR", "\x1b[1;1R"}, // u6
+ {"\x1b[?1006;1000%?%p1%{1}%=%th%el%;", "\x1b[?1006;1000l"}, // XM
+ {"\x1b[<%i%p3%d;%p1%d;%p2%d;%?%p4%tM%em%;", "\x1b[<888;667;778;M"}, // xm
+
+ {"\x1b]4;%p1%d;rgb:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\x1b\\",
+ "\x1b]4;666;rgb:C6/E2/FE\x1b\\"}, // initc
+
+ {"\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
+ "\x1b[48;5;666m"}, // setab
+ {"\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
+ "\x1b[38;5;666m"}, // setaf
+ {"%?%p9%t\x1b(0%e\x1b(B%;\x1b[0%?%p6%t;1%;%?%p5%t;2%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m",
+ "\x1b(B\x1b[0;4;7;5m"}, // sgr
+
+ // TODO: what is %[? Is this correct?
+ {"\x1b[?%[;0123456789]c", "\x1b[?%[;0123456789]c"}, // u8
+
+ // tmux terminfo
+ {"\x1b]52;%p1%s;%p2%s\a", "\x1b]52;666;777\a"}, // Ms
+ {"\x1b(%p1%c", "\x1b(ʚ"}, // S0
+ {"\x1b[4:%p1%dm", "\x1b[4:666m"}, // Smulx
+ {"\x1b[%i%p1%d;%p2%dr", "\x1b[667;778r"}, // csr
+ {"\x1b[%i%p1%d;%p2%dH", "\x1b[667;778H"}, // cup
+ {"\x1b[%i%p1%dd", "\x1b[667d"}, // vpa
+
+ {"\x1b[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p5%t;2%;%?%p7%t;8%;m%?%p9%t\x0e%e\x0f%;",
+ "\x1b[0;4;7;5m\x0f"}, // sgr
+
+ // alacritty
+ {"\x1b[%i%p1%d;%p2%dr", "\x1b[667;778r"}, // change_scroll_region
+ {"\x1b[%i%p1%d;%p2%dH", "\x1b[667;778H"}, // cursor_address
+ {"\x1bP=%p1%ds\x1b\\", "\x1bP=666s\x1b\\"}, // Sync
+ {"\x1b]12;%p1%s^G", "\x1b]12;666^G"}, // Cs
+ {"\x1b[4\072%p1%dm", "\x1b[4\072666m"}, // Smulx
+ {"\x1b]52;%p1%s;%p2%s^G", "\x1b]52;666;777^G"}, // Ms
+
+ {"\x1b[%?%p1%{8}%<%t4%p1%d%e48\0722\072\072%p1%{65536}%/%d\072%p1%{256}%/%{255}%&%d\072%p1%{255}%&%d%;m",
+ "\x1b[48:2::0:2:154m"}, // set_a_background
+
+ {"\x1b[%?%p1%{8}%<%t3%p1%d%e38\0722\072\072%p1%{65536}%/%d\072%p1%{256}%/%{255}%&%d\072%p1%{255}%&%d%;m",
+ "\x1b[38:2::0:2:154m"}, // set_a_foreground
+ }
+
+ for _, tt := range tests {
+ t.Run("", func(t *testing.T) {
+ args := []int{666, 777, 888, 999}
+ have := replaceParams(tt.in, args...)
+ if have != tt.want {
+ t.Errorf("\nin: %q\nhave: %q\nwant: %q\n%s\n",
+ tt.in, have, tt.want, printParams(tt.in, args...))
+ }
+ })
+ }
+}
diff --git a/tui/termfo/scaps/scaps.go b/tui/termfo/scaps/scaps.go
new file mode 100644
index 0000000..2e3fded
--- /dev/null
+++ b/tui/termfo/scaps/scaps.go
@@ -0,0 +1,621 @@
+// Code generated by term.h.zsh; DO NOT EDIT.
+
+// Package scaps contains a list of all terminfo capabilities.
+package scaps
+
+import "citrons.xyz/talk/tui/termfo/caps"
+
+// CursesVersion is the version of curses this data was generated with, as [implementation]-[version].
+const CursesVersion = `ncurses-6.5.20240511`
+
+var (
+ Bw = caps.AutoLeftMargin
+ Am = caps.AutoRightMargin
+ Xsb = caps.NoEscCtlc
+ Xhp = caps.CeolStandoutGlitch
+ Xenl = caps.EatNewlineGlitch
+ Eo = caps.EraseOverstrike
+ Gn = caps.GenericType
+ Hc = caps.HardCopy
+ Km = caps.HasMetaKey
+ Hs = caps.HasStatusLine
+ In = caps.InsertNullGlitch
+ Da = caps.MemoryAbove
+ Db = caps.MemoryBelow
+ Mir = caps.MoveInsertMode
+ Msgr = caps.MoveStandoutMode
+ Os = caps.OverStrike
+ Eslok = caps.StatusLineEscOk
+ Xt = caps.DestTabsMagicSmso
+ Hz = caps.TildeGlitch
+ Ul = caps.TransparentUnderline
+ Xon = caps.XonXoff
+ Nxon = caps.NeedsXonXoff
+ Mc5i = caps.PrtrSilent
+ Chts = caps.HardCursor
+ Nrrmc = caps.NonRevRmcup
+ Npc = caps.NoPadChar
+ Ndscr = caps.NonDestScrollRegion
+ Ccc = caps.CanChange
+ Bce = caps.BackColorErase
+ Hls = caps.HueLightnessSaturation
+ Xhpa = caps.ColAddrGlitch
+ Crxm = caps.CrCancelsMicroMode
+ Daisy = caps.HasPrintWheel
+ Xvpa = caps.RowAddrGlitch
+ Sam = caps.SemiAutoRightMargin
+ Cpix = caps.CpiChangesRes
+ Lpix = caps.LpiChangesRes
+ Cols = caps.Columns
+ It = caps.InitTabs
+ Lines = caps.Lines
+ Lm = caps.LinesOfMemory
+ Xmc = caps.MagicCookieGlitch
+ Pb = caps.PaddingBaudRate
+ Vt = caps.VirtualTerminal
+ Wsl = caps.WidthStatusLine
+ Nlab = caps.NumLabels
+ Lh = caps.LabelHeight
+ Lw = caps.LabelWidth
+ Ma = caps.MaxAttributes
+ Wnum = caps.MaximumWindows
+ Colors = caps.MaxColors
+ Pairs = caps.MaxPairs
+ Ncv = caps.NoColorVideo
+ Bufsz = caps.BufferCapacity
+ Spinv = caps.DotVertSpacing
+ Spinh = caps.DotHorzSpacing
+ Maddr = caps.MaxMicroAddress
+ Mjump = caps.MaxMicroJump
+ Mcs = caps.MicroColSize
+ Mls = caps.MicroLineSize
+ Npins = caps.NumberOfPins
+ Orc = caps.OutputResChar
+ Orl = caps.OutputResLine
+ Orhi = caps.OutputResHorzInch
+ Orvi = caps.OutputResVertInch
+ Cps = caps.PrintRate
+ Widcs = caps.WideCharSize
+ Btns = caps.Buttons
+ Bitwin = caps.BitImageEntwining
+ Bitype = caps.BitImageType
+ Cbt = caps.BackTab
+ Bel = caps.Bell
+ Cr = caps.CarriageReturn
+ Csr = caps.ChangeScrollRegion
+ Tbc = caps.ClearAllTabs
+ Clear = caps.ClearScreen
+ El = caps.ClrEol
+ Ed = caps.ClrEos
+ Hpa = caps.ColumnAddress
+ Cmdch = caps.CommandCharacter
+ Cup = caps.CursorAddress
+ Cud1 = caps.CursorDown
+ Home = caps.CursorHome
+ Civis = caps.CursorInvisible
+ Cub1 = caps.CursorLeft
+ Mrcup = caps.CursorMemAddress
+ Cnorm = caps.CursorNormal
+ Cuf1 = caps.CursorRight
+ Ll = caps.CursorToLl
+ Cuu1 = caps.CursorUp
+ Cvvis = caps.CursorVisible
+ Dch1 = caps.DeleteCharacter
+ Dl1 = caps.DeleteLine
+ Dsl = caps.DisStatusLine
+ Hd = caps.DownHalfLine
+ Smacs = caps.EnterAltCharsetMode
+ Blink = caps.EnterBlinkMode
+ Bold = caps.EnterBoldMode
+ Smcup = caps.EnterCaMode
+ Smdc = caps.EnterDeleteMode
+ Dim = caps.EnterDimMode
+ Smir = caps.EnterInsertMode
+ Invis = caps.EnterSecureMode
+ Prot = caps.EnterProtectedMode
+ Rev = caps.EnterReverseMode
+ Smso = caps.EnterStandoutMode
+ Smul = caps.EnterUnderlineMode
+ Ech = caps.EraseChars
+ Rmacs = caps.ExitAltCharsetMode
+ Sgr0 = caps.ExitAttributeMode
+ Rmcup = caps.ExitCaMode
+ Rmdc = caps.ExitDeleteMode
+ Rmir = caps.ExitInsertMode
+ Rmso = caps.ExitStandoutMode
+ Rmul = caps.ExitUnderlineMode
+ Flash = caps.FlashScreen
+ Ff = caps.FormFeed
+ Fsl = caps.FromStatusLine
+ Is1 = caps.Init1string
+ Is2 = caps.Init2string
+ Is3 = caps.Init3string
+ If = caps.InitFile
+ Ich1 = caps.InsertCharacter
+ Il1 = caps.InsertLine
+ Ip = caps.InsertPadding
+ Kbs = caps.KeyBackspace
+ Ktbc = caps.KeyCatab
+ Kclr = caps.KeyClear
+ Kctab = caps.KeyCtab
+ Kdch1 = caps.KeyDc
+ Kdl1 = caps.KeyDl
+ Kcud1 = caps.KeyDown
+ Krmir = caps.KeyEic
+ Kel = caps.KeyEol
+ Ked = caps.KeyEos
+ Kf0 = caps.KeyF0
+ Kf1 = caps.KeyF1
+ Kf10 = caps.KeyF10
+ Kf2 = caps.KeyF2
+ Kf3 = caps.KeyF3
+ Kf4 = caps.KeyF4
+ Kf5 = caps.KeyF5
+ Kf6 = caps.KeyF6
+ Kf7 = caps.KeyF7
+ Kf8 = caps.KeyF8
+ Kf9 = caps.KeyF9
+ Khome = caps.KeyHome
+ Kich1 = caps.KeyIc
+ Kil1 = caps.KeyIl
+ Kcub1 = caps.KeyLeft
+ Kll = caps.KeyLl
+ Knp = caps.KeyNpage
+ Kpp = caps.KeyPpage
+ Kcuf1 = caps.KeyRight
+ Kind = caps.KeySf
+ Kri = caps.KeySr
+ Khts = caps.KeyStab
+ Kcuu1 = caps.KeyUp
+ Rmkx = caps.KeypadLocal
+ Smkx = caps.KeypadXmit
+ Lf0 = caps.LabF0
+ Lf1 = caps.LabF1
+ Lf10 = caps.LabF10
+ Lf2 = caps.LabF2
+ Lf3 = caps.LabF3
+ Lf4 = caps.LabF4
+ Lf5 = caps.LabF5
+ Lf6 = caps.LabF6
+ Lf7 = caps.LabF7
+ Lf8 = caps.LabF8
+ Lf9 = caps.LabF9
+ Rmm = caps.MetaOff
+ Smm = caps.MetaOn
+ Nel = caps.Newline
+ Pad = caps.PadChar
+ Dch = caps.ParmDch
+ Dl = caps.ParmDeleteLine
+ Cud = caps.ParmDownCursor
+ Ich = caps.ParmIch
+ Indn = caps.ParmIndex
+ Il = caps.ParmInsertLine
+ Cub = caps.ParmLeftCursor
+ Cuf = caps.ParmRightCursor
+ Rin = caps.ParmRindex
+ Cuu = caps.ParmUpCursor
+ Pfkey = caps.PkeyKey
+ Pfloc = caps.PkeyLocal
+ Pfx = caps.PkeyXmit
+ Mc0 = caps.PrintScreen
+ Mc4 = caps.PrtrOff
+ Mc5 = caps.PrtrOn
+ Rep = caps.RepeatChar
+ Rs1 = caps.Reset1string
+ Rs2 = caps.Reset2string
+ Rs3 = caps.Reset3string
+ Rf = caps.ResetFile
+ Rc = caps.RestoreCursor
+ Vpa = caps.RowAddress
+ Sc = caps.SaveCursor
+ Ind = caps.ScrollForward
+ Ri = caps.ScrollReverse
+ Sgr = caps.SetAttributes
+ Hts = caps.SetTab
+ Wind = caps.SetWindow
+ Ht = caps.Tab
+ Tsl = caps.ToStatusLine
+ Uc = caps.UnderlineChar
+ Hu = caps.UpHalfLine
+ Iprog = caps.InitProg
+ Ka1 = caps.KeyA1
+ Ka3 = caps.KeyA3
+ Kb2 = caps.KeyB2
+ Kc1 = caps.KeyC1
+ Kc3 = caps.KeyC3
+ Mc5p = caps.PrtrNon
+ Rmp = caps.CharPadding
+ Acsc = caps.AcsChars
+ Pln = caps.PlabNorm
+ Kcbt = caps.KeyBtab
+ Smxon = caps.EnterXonMode
+ Rmxon = caps.ExitXonMode
+ Smam = caps.EnterAmMode
+ Rmam = caps.ExitAmMode
+ Xonc = caps.XonCharacter
+ Xoffc = caps.XoffCharacter
+ Enacs = caps.EnaAcs
+ Smln = caps.LabelOn
+ Rmln = caps.LabelOff
+ Kbeg = caps.KeyBeg
+ Kcan = caps.KeyCancel
+ Kclo = caps.KeyClose
+ Kcmd = caps.KeyCommand
+ Kcpy = caps.KeyCopy
+ Kcrt = caps.KeyCreate
+ Kend = caps.KeyEnd
+ Kent = caps.KeyEnter
+ Kext = caps.KeyExit
+ Kfnd = caps.KeyFind
+ Khlp = caps.KeyHelp
+ Kmrk = caps.KeyMark
+ Kmsg = caps.KeyMessage
+ Kmov = caps.KeyMove
+ Knxt = caps.KeyNext
+ Kopn = caps.KeyOpen
+ Kopt = caps.KeyOptions
+ Kprv = caps.KeyPrevious
+ Kprt = caps.KeyPrint
+ Krdo = caps.KeyRedo
+ Kref = caps.KeyReference
+ Krfr = caps.KeyRefresh
+ Krpl = caps.KeyReplace
+ Krst = caps.KeyRestart
+ Kres = caps.KeyResume
+ Ksav = caps.KeySave
+ Kspd = caps.KeySuspend
+ Kund = caps.KeyUndo
+ KBEG = caps.KeySbeg
+ KCAN = caps.KeyScancel
+ KCMD = caps.KeyScommand
+ KCPY = caps.KeyScopy
+ KCRT = caps.KeyScreate
+ KDC = caps.KeySdc
+ KDL = caps.KeySdl
+ Kslt = caps.KeySelect
+ KEND = caps.KeySend
+ KEOL = caps.KeySeol
+ KEXT = caps.KeySexit
+ KFND = caps.KeySfind
+ KHLP = caps.KeyShelp
+ KHOM = caps.KeyShome
+ KIC = caps.KeySic
+ KLFT = caps.KeySleft
+ KMSG = caps.KeySmessage
+ KMOV = caps.KeySmove
+ KNXT = caps.KeySnext
+ KOPT = caps.KeySoptions
+ KPRV = caps.KeySprevious
+ KPRT = caps.KeySprint
+ KRDO = caps.KeySredo
+ KRPL = caps.KeySreplace
+ KRIT = caps.KeySright
+ KRES = caps.KeySrsume
+ KSAV = caps.KeySsave
+ KSPD = caps.KeySsuspend
+ KUND = caps.KeySundo
+ Rfi = caps.ReqForInput
+ Kf11 = caps.KeyF11
+ Kf12 = caps.KeyF12
+ Kf13 = caps.KeyF13
+ Kf14 = caps.KeyF14
+ Kf15 = caps.KeyF15
+ Kf16 = caps.KeyF16
+ Kf17 = caps.KeyF17
+ Kf18 = caps.KeyF18
+ Kf19 = caps.KeyF19
+ Kf20 = caps.KeyF20
+ Kf21 = caps.KeyF21
+ Kf22 = caps.KeyF22
+ Kf23 = caps.KeyF23
+ Kf24 = caps.KeyF24
+ Kf25 = caps.KeyF25
+ Kf26 = caps.KeyF26
+ Kf27 = caps.KeyF27
+ Kf28 = caps.KeyF28
+ Kf29 = caps.KeyF29
+ Kf30 = caps.KeyF30
+ Kf31 = caps.KeyF31
+ Kf32 = caps.KeyF32
+ Kf33 = caps.KeyF33
+ Kf34 = caps.KeyF34
+ Kf35 = caps.KeyF35
+ Kf36 = caps.KeyF36
+ Kf37 = caps.KeyF37
+ Kf38 = caps.KeyF38
+ Kf39 = caps.KeyF39
+ Kf40 = caps.KeyF40
+ Kf41 = caps.KeyF41
+ Kf42 = caps.KeyF42
+ Kf43 = caps.KeyF43
+ Kf44 = caps.KeyF44
+ Kf45 = caps.KeyF45
+ Kf46 = caps.KeyF46
+ Kf47 = caps.KeyF47
+ Kf48 = caps.KeyF48
+ Kf49 = caps.KeyF49
+ Kf50 = caps.KeyF50
+ Kf51 = caps.KeyF51
+ Kf52 = caps.KeyF52
+ Kf53 = caps.KeyF53
+ Kf54 = caps.KeyF54
+ Kf55 = caps.KeyF55
+ Kf56 = caps.KeyF56
+ Kf57 = caps.KeyF57
+ Kf58 = caps.KeyF58
+ Kf59 = caps.KeyF59
+ Kf60 = caps.KeyF60
+ Kf61 = caps.KeyF61
+ Kf62 = caps.KeyF62
+ Kf63 = caps.KeyF63
+ El1 = caps.ClrBol
+ Mgc = caps.ClearMargins
+ Smgl = caps.SetLeftMargin
+ Smgr = caps.SetRightMargin
+ Fln = caps.LabelFormat
+ Sclk = caps.SetClock
+ Dclk = caps.DisplayClock
+ Rmclk = caps.RemoveClock
+ Cwin = caps.CreateWindow
+ Wingo = caps.GotoWindow
+ Hup = caps.Hangup
+ Dial = caps.DialPhone
+ Qdial = caps.QuickDial
+ Tone = caps.Tone
+ Pulse = caps.Pulse
+ Hook = caps.FlashHook
+ Pause = caps.FixedPause
+ Wait = caps.WaitTone
+ U0 = caps.User0
+ U1 = caps.User1
+ U2 = caps.User2
+ U3 = caps.User3
+ U4 = caps.User4
+ U5 = caps.User5
+ U6 = caps.User6
+ U7 = caps.User7
+ U8 = caps.User8
+ U9 = caps.User9
+ Op = caps.OrigPair
+ Oc = caps.OrigColors
+ Initc = caps.InitializeColor
+ Initp = caps.InitializePair
+ Scp = caps.SetColorPair
+ Setf = caps.SetForeground
+ Setb = caps.SetBackground
+ Cpi = caps.ChangeCharPitch
+ Lpi = caps.ChangeLinePitch
+ Chr = caps.ChangeResHorz
+ Cvr = caps.ChangeResVert
+ Defc = caps.DefineChar
+ Swidm = caps.EnterDoublewideMode
+ Sdrfq = caps.EnterDraftQuality
+ Sitm = caps.EnterItalicsMode
+ Slm = caps.EnterLeftwardMode
+ Smicm = caps.EnterMicroMode
+ Snlq = caps.EnterNearLetterQuality
+ Snrmq = caps.EnterNormalQuality
+ Sshm = caps.EnterShadowMode
+ Ssubm = caps.EnterSubscriptMode
+ Ssupm = caps.EnterSuperscriptMode
+ Sum = caps.EnterUpwardMode
+ Rwidm = caps.ExitDoublewideMode
+ Ritm = caps.ExitItalicsMode
+ Rlm = caps.ExitLeftwardMode
+ Rmicm = caps.ExitMicroMode
+ Rshm = caps.ExitShadowMode
+ Rsubm = caps.ExitSubscriptMode
+ Rsupm = caps.ExitSuperscriptMode
+ Rum = caps.ExitUpwardMode
+ Mhpa = caps.MicroColumnAddress
+ Mcud1 = caps.MicroDown
+ Mcub1 = caps.MicroLeft
+ Mcuf1 = caps.MicroRight
+ Mvpa = caps.MicroRowAddress
+ Mcuu1 = caps.MicroUp
+ Porder = caps.OrderOfPins
+ Mcud = caps.ParmDownMicro
+ Mcub = caps.ParmLeftMicro
+ Mcuf = caps.ParmRightMicro
+ Mcuu = caps.ParmUpMicro
+ Scs = caps.SelectCharSet
+ Smgb = caps.SetBottomMargin
+ Smgbp = caps.SetBottomMarginParm
+ Smglp = caps.SetLeftMarginParm
+ Smgrp = caps.SetRightMarginParm
+ Smgt = caps.SetTopMargin
+ Smgtp = caps.SetTopMarginParm
+ Sbim = caps.StartBitImage
+ Scsd = caps.StartCharSetDef
+ Rbim = caps.StopBitImage
+ Rcsd = caps.StopCharSetDef
+ Subcs = caps.SubscriptCharacters
+ Supcs = caps.SuperscriptCharacters
+ Docr = caps.TheseCauseCr
+ Zerom = caps.ZeroMotion
+ Csnm = caps.CharSetNames
+ Kmous = caps.KeyMouse
+ Minfo = caps.MouseInfo
+ Reqmp = caps.ReqMousePos
+ Getm = caps.GetMouse
+ Setaf = caps.SetAForeground
+ Setab = caps.SetABackground
+ Pfxl = caps.PkeyPlab
+ Devt = caps.DeviceType
+ Csin = caps.CodeSetInit
+ S0ds = caps.Set0DesSeq
+ S1ds = caps.Set1DesSeq
+ S2ds = caps.Set2DesSeq
+ S3ds = caps.Set3DesSeq
+ Smglr = caps.SetLrMargin
+ Smgtb = caps.SetTbMargin
+ Birep = caps.BitImageRepeat
+ Binel = caps.BitImageNewline
+ Bicr = caps.BitImageCarriageReturn
+ Colornm = caps.ColorNames
+ Defbi = caps.DefineBitImageRegion
+ Endbi = caps.EndBitImageRegion
+ Setcolor = caps.SetColorBand
+ Slines = caps.SetPageLength
+ Dispc = caps.DisplayPcChar
+ Smpch = caps.EnterPcCharsetMode
+ Rmpch = caps.ExitPcCharsetMode
+ Smsc = caps.EnterScancodeMode
+ Rmsc = caps.ExitScancodeMode
+ Pctrm = caps.PcTermOptions
+ Scesc = caps.ScancodeEscape
+ Scesa = caps.AltScancodeEsc
+ Ehhlm = caps.EnterHorizontalHlMode
+ Elhlm = caps.EnterLeftHlMode
+ Elohlm = caps.EnterLowHlMode
+ Erhlm = caps.EnterRightHlMode
+ Ethlm = caps.EnterTopHlMode
+ Evhlm = caps.EnterVerticalHlMode
+ Sgr1 = caps.SetAAttributes
+ Slength = caps.SetPglenInch
+ OTi2 = caps.TermcapInit2
+ OTrs = caps.TermcapReset
+ OTug = caps.MagicCookieGlitchUl
+ OTbs = caps.BackspacesWithBs
+ OTns = caps.CrtNoScrolling
+ OTnc = caps.NoCorrectlyWorkingCr
+ OTdC = caps.CarriageReturnDelay
+ OTdN = caps.NewLineDelay
+ OTnl = caps.LinefeedIfNotLf
+ OTbc = caps.BackspaceIfNotBs
+ OTMT = caps.GnuHasMetaKey
+ OTNL = caps.LinefeedIsNewline
+ OTdB = caps.BackspaceDelay
+ OTdT = caps.HorizontalTabDelay
+ OTkn = caps.NumberOfFunctionKeys
+ OTko = caps.OtherNonFunctionKeys
+ OTma = caps.ArrowKeyMap
+ OTpt = caps.HasHardwareTabs
+ OTxr = caps.ReturnDoesClrEol
+ OTG2 = caps.AcsUlcorner
+ OTG3 = caps.AcsLlcorner
+ OTG1 = caps.AcsUrcorner
+ OTG4 = caps.AcsLrcorner
+ OTGR = caps.AcsLtee
+ OTGL = caps.AcsRtee
+ OTGU = caps.AcsBtee
+ OTGD = caps.AcsTtee
+ OTGH = caps.AcsHline
+ OTGV = caps.AcsVline
+ OTGC = caps.AcsPlus
+ Meml = caps.MemoryLock
+ Memu = caps.MemoryUnlock
+ Box1 = caps.BoxChars1
+
+ // Extentions
+ CO = caps.CO
+ E3 = caps.E3
+ NQ = caps.NQ
+ RGB = caps.RGB
+ TS = caps.TS
+ XM = caps.XM
+ Grbom = caps.Grbom
+ Gsbom = caps.Gsbom
+ Xm = caps.Xm
+ Rmol = caps.Rmol
+ Smol = caps.Smol
+ Blink2 = caps.Blink2
+ Norm = caps.Norm
+ Opaq = caps.Opaq
+ Setal = caps.Setal
+ Smul2 = caps.Smul2
+ AN = caps.AN
+ AX = caps.AX
+ C0 = caps.C0
+ C8 = caps.C8
+ CE = caps.CE
+ CS = caps.CS
+ E0 = caps.E0
+ G0 = caps.G0
+ KJ = caps.KJ
+ OL = caps.OL
+ S0 = caps.S0
+ TF = caps.TF
+ WS = caps.WS
+ XC = caps.XC
+ XT = caps.XT
+ Z0 = caps.Z0
+ Z1 = caps.Z1
+ Cs = caps.Cs
+ Ms = caps.Ms
+ Se = caps.Se
+ Smulx = caps.Smulx
+ Ss = caps.Ss
+ Rmxx = caps.Rmxx
+ Smxx = caps.Smxx
+ BD = caps.BD
+ BE = caps.BE
+ PE = caps.PE
+ PS = caps.PS
+ RV = caps.RV
+ XR = caps.XR
+ XF = caps.XF
+ Fd = caps.Fd
+ Fe = caps.Fe
+ Rv = caps.Rv
+ Xr = caps.Xr
+ Csl = caps.Csl
+ KDC3 = caps.KDC3
+ KDC4 = caps.KDC4
+ KDC5 = caps.KDC5
+ KDC6 = caps.KDC6
+ KDC7 = caps.KDC7
+ KDN = caps.KDN
+ KDN3 = caps.KDN3
+ KDN4 = caps.KDN4
+ KDN5 = caps.KDN5
+ KDN6 = caps.KDN6
+ KDN7 = caps.KDN7
+ KEND3 = caps.KEND3
+ KEND4 = caps.KEND4
+ KEND5 = caps.KEND5
+ KEND6 = caps.KEND6
+ KEND7 = caps.KEND7
+ KHOM3 = caps.KHOM3
+ KHOM4 = caps.KHOM4
+ KHOM5 = caps.KHOM5
+ KHOM6 = caps.KHOM6
+ KHOM7 = caps.KHOM7
+ KIC3 = caps.KIC3
+ KIC4 = caps.KIC4
+ KIC5 = caps.KIC5
+ KIC6 = caps.KIC6
+ KIC7 = caps.KIC7
+ KLFT3 = caps.KLFT3
+ KLFT4 = caps.KLFT4
+ KLFT5 = caps.KLFT5
+ KLFT6 = caps.KLFT6
+ KLFT7 = caps.KLFT7
+ KNXT3 = caps.KNXT3
+ KNXT4 = caps.KNXT4
+ KNXT5 = caps.KNXT5
+ KNXT6 = caps.KNXT6
+ KNXT7 = caps.KNXT7
+ KPRV3 = caps.KPRV3
+ KPRV4 = caps.KPRV4
+ KPRV5 = caps.KPRV5
+ KPRV6 = caps.KPRV6
+ KPRV7 = caps.KPRV7
+ KRIT3 = caps.KRIT3
+ KRIT4 = caps.KRIT4
+ KRIT5 = caps.KRIT5
+ KRIT6 = caps.KRIT6
+ KRIT7 = caps.KRIT7
+ KUP = caps.KUP
+ KUP3 = caps.KUP3
+ KUP4 = caps.KUP4
+ KUP5 = caps.KUP5
+ KUP6 = caps.KUP6
+ KUP7 = caps.KUP7
+ Ka2 = caps.Ka2
+ Kb1 = caps.Kb1
+ Kb3 = caps.Kb3
+ Kc2 = caps.Kc2
+ KxIN = caps.KxIN
+ KxOUT = caps.KxOUT
+)
diff --git a/tui/termfo/term.h.zsh b/tui/termfo/term.h.zsh
new file mode 100755
index 0000000..aba713b
--- /dev/null
+++ b/tui/termfo/term.h.zsh
@@ -0,0 +1,427 @@
+#!/usr/bin/env zsh
+[ "${ZSH_VERSION:-}" = "" ] && echo >&2 "Only works with zsh" && exit 1 # Just in case people type "bash term.h.zsh".
+setopt err_exit no_unset no_clobber pipefail
+
+# TODO: also look at capalias and infoalias in Caps-ncurses.
+#
+# https://github.com/benhoyt/goawk can run from within Go; that might actually
+# be quite nice for this and removes the dependency on zsh and awk.
+
+src=${1:-~/ncurses}
+if [[ ! -d $src ]]; then
+ print >&2 "ncurses source not in $src; can't generate term.h.go"
+ print >&2 'Usage: term.h.zsh [source-tree]'
+ exit 1
+fi
+
+main() {
+ mkdir -p caps scaps keys
+ caps >|caps/caps.go
+ scaps >|scaps/scaps.go
+ caps_table >|caps/table.go
+ keys >|keys/keys.go
+ gofmt -w caps/caps.go scaps/scaps.go caps/table.go keys/keys.go
+}
+
+funs=$(<<'EOF'
+ function ucfirst(s) {
+ return toupper(substr(s, 1, 1)) substr(s, 2)
+ }
+
+ function camelCase(s) {
+ while (i = index(s, "_"))
+ s = substr(s, 0, i-1) toupper(substr(s, i+1, 1)) substr(s, i+2)
+ return ucfirst(s)
+ }
+EOF
+)
+
+# Print package header.
+# pkg pkgname [pkgcomment] [add-version] [import]
+pkg() {
+ print '// Code generated by term.h.zsh; DO NOT EDIT.\n'
+ (( $# > 1 && ${#2:-} > 0 )) && print "// Package $1 $2"
+ print "package $1\n"
+ (( $# > 2 )) && print $3
+ if (( $# < 4 )); then
+ print '// CursesVersion is the version of curses this data was generated with, as [implementation]-[version].'
+ awk '{printf "const CursesVersion = `ncurses-%s.%s`\n\n", $2, $3 }' $src/VERSION
+ fi
+ return 0
+}
+
+cap_ignore=$(<<-'EOF'
+ BEGIN {
+ ignore["tilde_glitch"] = 1 # Hacks for ancient hardware terms.
+ ignore["eat_newline_glitch"] = 1
+ ignore["insert_null_glitch"] = 1
+ ignore["col_addr_glitch"] = 1
+ ignore["row_addr_glitch"] = 1
+ ignore["magic_cookie_glitch"] = 1
+ ignore["magic_cookie_glitch_ul"] = 1
+ ignore["ceol_standout_glitch"] = 1
+ ignore["hard_cursor"] = 1
+ ignore["no_esc_ctlc"] = 1
+ ignore["new_line_delay"] = 1
+ ignore["carriage_return_delay"] = 1
+ ignore["no_correctly_working_cr"] = 1
+ ignore["crt_no_scrolling"] = 1
+ ignore["linefeed_if_not_lf"] = 1
+ ignore["backspace_if_not_bs"] = 1
+ ignore["linefeed_is_newline"] = 1
+ ignore["backspace_delay"] = 1
+ ignore["horizontal_tab_delay"] = 1
+ ignore["other_non_function_keys"] = 1
+ ignore["number_of_function_keys"] = 1
+ ignore["arrow_key_map"] = 1
+ ignore["return_does_clr_eol"] = 1
+ ignore["dest_tabs_magic_smso"] = 1
+ ignore["non_dest_scroll_region"] = 1
+ ignore["non_rev_rmcup"] = 1
+ ignore["padding_baud_rate"] = 1
+ ignore["has_print_wheel"] = 1 # No vaguely modern terminal has this.
+ ignore["set_left_margin"] = 1
+ ignore["set_right_margin"] = 1
+ ignore["set_bottom_margin"] = 1
+ ignore["set_bottom_margin_parm"] = 1
+ ignore["set_top_margin"] = 1
+ ignore["set_top_margin_parm"] = 1
+ ignore["set_tb_margin"] = 1
+ ignore["order_of_pins"] = 1
+ ignore["lines_of_memory"] = 1
+ ignore["output_res_char"] = 1
+ ignore["output_res_line"] = 1
+ ignore["wide_char_size"] = 1
+ ignore["enter_doublewide_mode"] = 1
+ ignore["exit_doublewide_mode"] = 1
+ ignore["hue_lightness_saturation"] = 1
+ ignore["fixed_pause"] = 1
+ ignore["hangup"] = 1
+ ignore["dial_phone"] = 1
+ ignore["pulse"] = 1
+ ignore["flash_hook"] = 1
+ ignore["quick_dial"] = 1
+ ignore["wait_tone"] = 1
+ ignore["tone"] = 1
+ ignore["set_clock"] = 1
+ ignore["display_clock"] = 1
+ ignore["remove_clock"] = 1
+ ignore["create_window"] = 1
+ ignore["goto_window"] = 1
+ ignore["set_window"] = 1
+ ignore["maximum_windows"] = 1
+ ignore["number_of_pins"] = 1
+ ignore["plab_norm"] = 1
+ ignore["label_format"] = 1
+ ignore["num_labels"] = 1
+ ignore["label_height"] = 1
+ ignore["label_width"] = 1
+ ignore["label_on"] = 1
+ ignore["label_off"] = 1
+ ignore["lab_f0"] = 1
+ ignore["lab_f1"] = 1
+ ignore["lab_f10"] = 1
+ ignore["lab_f2"] = 1
+ ignore["lab_f3"] = 1
+ ignore["lab_f4"] = 1
+ ignore["lab_f5"] = 1
+ ignore["lab_f6"] = 1
+ ignore["lab_f7"] = 1
+ ignore["lab_f8"] = 1
+ ignore["lab_f9"] = 1
+ ignore["semi_auto_right_margin"] = 1
+ ignore["enter_draft_quality"] = 1
+ ignore["enter_near_letter_quality"] = 1
+ ignore["enter_normal_quality"] = 1
+ ignore["generic_type"] = 1
+ ignore["set_left_margin_parm"] = 1 # Setting margins kind-of in e.g. xterm, but it's pretty broken and useless.
+ ignore["set_right_margin_parm"] = 1
+ ignore["set_lr_margin"] = 1
+ ignore["clear_margins"] = 1
+ ignore["auto_left_margin"] = 1 # I can't figure out what these "automatic margins" do, or how they work. So I'm going to guess it's not really useful.
+ ignore["auto_right_margin"] = 1
+ ignore["enter_am_mode"] = 1
+ ignore["exit_am_mode"] = 1
+ ignore["cr_cancels_micro_mode"] = 1 # "Micro mode", whatever that is; only on QNX 4.
+ ignore["max_micro_address"] = 1
+ ignore["max_micro_jump"] = 1
+ ignore["micro_col_size"] = 1
+ ignore["micro_line_size"] = 1
+ ignore["enter_micro_mode"] = 1
+ ignore["exit_micro_mode"] = 1
+ ignore["micro_column_address"] = 1
+ ignore["micro_down"] = 1
+ ignore["micro_left"] = 1
+ ignore["micro_right"] = 1
+ ignore["micro_row_address"] = 1
+ ignore["micro_up"] = 1
+ ignore["parm_down_micro"] = 1
+ ignore["parm_left_micro"] = 1
+ ignore["parm_right_micro"] = 1
+ ignore["parm_up_micro"] = 1
+ ignore["prtr_silent"] = 1 # Printer support; no one has that.
+ ignore["prtr_non"] = 1
+ ignore["set_pglen_inch"] = 1
+ ignore["change_char_pitch"] = 1
+ ignore["change_line_pitch"] = 1
+ ignore["output_res_horz_inch"] = 1
+ ignore["output_res_vert_inch"] = 1
+ ignore["dot_vert_spacing"] = 1
+ ignore["dot_horz_spacing"] = 1
+ ignore["buffer_capacity"] = 1
+ ignore["print_rate"] = 1
+ ignore["print_screen"] = 1
+ ignore["prtr_off"] = 1
+ ignore["prtr_on"] = 1
+ ignore["prtr_non"] = 1
+ ignore["start_bit_image"] = 1
+ ignore["stop_bit_image"] = 1
+ ignore["these_cause_cr"] = 1
+ ignore["hard_copy"] = 1
+ ignore["bit_image_entwining"] = 1 # Not supported by anything.
+ ignore["bit_image_type"] = 1
+ ignore["bit_image_repeat"] = 1
+ ignore["bit_image_newline"] = 1
+ ignore["bit_image_carriage_return"] = 1
+ ignore["define_bit_image_region"] = 1
+ ignore["end_bit_image_region"] = 1
+
+ # dsl / disable_status_line
+ # kitty = clear window title
+
+ names[""] = ""
+ }
+EOF
+)
+
+# Generate caps/caps.go
+caps() {
+ pkg caps 'contains a list of all terminfo capabilities.'
+ cat <<-'EOF'
+ // Cap represents a capability as listed in a terminfo file.
+ type Cap struct {
+ Short string // Short terminfo name
+ Long string // Longer variable name from term.h
+ Desc string // Description from terminfo(5)
+ }
+ EOF
+
+ print "var ("
+ awk <$src/include/Caps "$funs $cap_ignore $(<<-'EOF'
+ /^[^#]/ {
+ # TODO: breaks too much
+ # if (ignore[$1] != "" || match($1, "^key_"))
+ # next
+ name = names[$1]
+ if (name == "")
+ name = camelCase($1)
+ printf "\t%s = &Cap{`%s`, `%s`, `%s", name, $2, $1, $8
+ for (i=9; i<=NF; i++)
+ printf " %s", $i
+ print "`}"
+ }
+ EOF
+ )"
+
+ print '\n// Extentions'
+ awk <$src/include/Caps-ncurses "$funs $cap_ignore $(<<-'EOF'
+ $1 == "used_by" { used_by = $2 }
+ $1 == "userdef" {
+ # Some entries are listed more than once (xm and RGB).
+ # TODO: append the descriptions?
+ if (uniq[$2])
+ next
+ uniq[$2] = 1
+
+ printf "\t%s = &Cap{`%s`, `%s`, `%s", camelCase($2), $2, $1, $5
+ for (i=6; i<=NF; i++)
+ printf " %s", $i
+ printf " (%s)`}\n", used_by
+ }
+ EOF
+ )"
+ print ")"
+}
+
+# Generage scaps/scaps.go
+scaps() {
+ pkg scaps 'contains a list of all terminfo capabilities.' 'import "zgo.at/termfo/caps"'
+
+ print "var ("
+ awk <$src/include/Caps "$funs $(<<-'EOF'
+ /^[^#]/ {
+ print "\t" ucfirst($2) " = caps." camelCase($1)
+ }
+ EOF
+ )"
+
+ print '\n// Extentions'
+ awk <$src/include/Caps-ncurses "$funs $(<<-'EOF'
+ $1 == "used_by" { used_by = $2 }
+ $1 == "userdef" {
+ if (uniq[$2])
+ next
+ uniq[$2] = 1
+ print "\t" ucfirst($2) " = caps." ucfirst($2)
+ }
+ EOF
+ )"
+ print ")"
+}
+
+# Generate caps/table.go
+caps_table() {
+ pkg caps '' '' 'no-version'
+ print "var unused *Cap = nil\n"
+
+ for t in bool num str; do
+ tu=${t[1]:u}${t[2,-1]}
+ print "var Table${tu}s = []*Cap{"
+ awk <$src/include/Caps "$funs $cap_ignore ${$(<<-'EOF'
+ /^[^#]/ && $3 == "TYPE" {
+ name = names[$1]
+ if (name == "")
+ name = camelCase($1)
+ # TODO: breaks too much
+ # if (ignore[$1] != "" || match($1, "^key_"))
+ # name = "unused"
+ print name ","
+ }
+ EOF
+ )//TYPE/$t}"
+
+ print '\n// Extensions'
+ awk <$src/include/Caps-ncurses "$funs $cap_ignore ${$(<<-'EOF'
+ $1 == "userdef" && $3 == "TYPE" {
+ print camelCase($2) ","
+ }
+ EOF
+ )//TYPE/$t}"
+ print "}\n"
+ done
+}
+
+# List all key caps.
+keys() {
+ pkg keys '' 'import "zgo.at/termfo/caps"'
+ # pkg keys '' ''
+
+ awk <$src/include/Caps "$funs $(<<-'EOF'
+ BEGIN {
+ print "// Keys maps caps.Cap to Key constants"
+ print "var Keys = map[*caps.Cap]Key{"
+ # print "var Keys = map[Key]string{"
+ i = -1
+
+ # Obscure keys present on very old devices; most people have neither
+ # heard nor used any of these devices or keys, so there's not much
+ # point including them. Can still use the termCaps if you really
+ # want to, just don't need a shortcut for them.
+ #
+ # Some of these are still useful codes to send, but they're just not
+ # keys (anymore).
+ #
+ # Use ./cmd/termfo to print terminals which have a certain capability.
+ ignore["Catab"] = 1
+ ignore["Ctab"] = 1
+ ignore["Dl"] = 1; ignore["Sdl"] = 1
+ ignore["Eic"] = 1
+ ignore["Eol"] = 1; ignore["Seol"] = 1
+ ignore["Eos"] = 1
+ ignore["Il"] = 1
+ ignore["Sr"] = 1
+ ignore["Sf"] = 1
+ ignore["Clear"] = 1
+ ignore["F0"] = 1
+ ignore["Ll"] = 1
+ ignore["Stab"] = 1
+ ignore["A1"] = 1
+ ignore["A3"] = 1
+ ignore["B2"] = 1
+ ignore["C1"] = 1
+ ignore["C3"] = 1
+ ignore["Beg"] = 1; ignore["Sbeg"] = 1
+ ignore["Cancel"] = 1; ignore["Scancel"] = 1
+ ignore["Close"] = 1; ignore["Close"] = 1
+ ignore["Command"] = 1; ignore["Scommand"] = 1
+ ignore["Copy"] = 1; ignore["Scopy"] = 1
+ ignore["Create"] = 1; ignore["Screate"] = 1
+ ignore["Exit"] = 1; ignore["Sexit"] = 1
+ ignore["Find"] = 1; ignore["Sfind"] = 1
+ ignore["Help"] = 1; ignore["Shelp"] = 1
+ ignore["Mark"] = 1; ignore["Smark"] = 1
+ ignore["Message"] = 1; ignore["Smessage"] = 1
+ ignore["Move"] = 1; ignore["Smove"] = 1
+ ignore["Next"] = 1; ignore["Snext"] = 1
+ ignore["Open"] = 1; ignore["Sopen"] = 1
+ ignore["Options"] = 1; ignore["Soptions"] = 1
+ ignore["Save"] = 1; ignore["Ssave"] = 1
+ ignore["Previous"] = 1; ignore["Sprevious"] = 1
+ ignore["Print"] = 1; ignore["Sprint"] = 1
+ ignore["Redo"] = 1; ignore["Sredo"] = 1
+ ignore["Reference"] = 1; ignore["sreference"] = 1
+ ignore["Refresh"] = 1; ignore["srefresh"] = 1
+ ignore["Replace"] = 1; ignore["Sreplace"] = 1
+ ignore["Restart"] = 1; ignore["srestart"] = 1
+ ignore["Resume"] = 1; ignore["Srsume"] = 1
+ ignore["Suspend"] = 1; ignore["Ssuspend"] = 1
+ ignore["Undo"] = 1; ignore["Sundo"] = 1
+ ignore["Select"] = 1; ignore["Sselect"] = 1
+
+ # Rename some things for clarity.
+ rename["Ic"] = "Insert"; rename["Sic"] = "ShiftInsert"
+ rename["Dc"] = "Delete"; rename["Sdc"] = "ShiftDelete"
+ rename["Ppage"] = "PageUp"
+ rename["Npage"] = "PageDown"
+ rename["Btab"] = "BackTab"
+ rename["Shome"] = "ShiftHome"
+ rename["Send"] = "ShiftEnd"
+ rename["Sleft"] = "ShiftLeft"
+ rename["Sright"] = "ShiftRight"
+ }
+
+ /^[^#]/ && $3 == "str" {
+ i++
+ }
+
+ $5 ~ /^KEY_/ {
+ name = toupper(substr($1, 5, 1)) substr($1, 6)
+ if (ignore[name] != "")
+ next
+ if (match(name, /^F([2-9][0-9]|1[3-9])/)) # Who has 64 function keys?!
+ next
+ if (rename[name] != "")
+ name = rename[name]
+ allkeys[name] = ""
+
+ # TODO: should print:
+ # var Keys = map[Key]string{
+ # F1: "\x1bOP",
+ # }
+ # printf "\t%s: \"%s\",\n", name, "TODO"
+ printf "\tcaps.TableStrs[%d]: %s,\n", i, name
+ }
+
+ END {
+ print "}\n"
+
+ print "// List of all key sequences we know about. This excludes most obscure ones not"
+ print "// present on modern devices."
+ print "const ("
+ print "// Special key used to signal errors."
+ print "UnknownSequence Key = iota + (1 << 32)\n"
+ for (k in allkeys) # TODO: weird order?
+ print k
+ print ")"
+
+ print "// Names of named key constants."
+ print "var keyNames = map[Key]string{"
+ for (k in allkeys)
+ printf "\t%s: `%s`,\n", k, k
+ print "}\n"
+ }
+ EOF
+ )"
+}
+
+main
diff --git a/tui/termfo/termfo.go b/tui/termfo/termfo.go
new file mode 100644
index 0000000..849af07
--- /dev/null
+++ b/tui/termfo/termfo.go
@@ -0,0 +1,287 @@
+//go:generate zsh term.h.zsh
+
+package termfo
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "unicode/utf8"
+
+ "citrons.xyz/talk/tui/termfo/caps"
+ "citrons.xyz/talk/tui/termfo/keys"
+)
+
+// Terminfo describes the terminfo database for a single terminal.
+type Terminfo struct {
+ Name string // Main name as listed in the terminfo file.
+ Desc string // Some textual description.
+ Aliases []string // Aliases for this terminal.
+ Location string // Where it was loaded from; path or "builtin".
+
+ Bools map[*caps.Cap]struct{} // Boolean capabilities.
+ Numbers map[*caps.Cap]int32 // Number capabilities.
+ Strings map[*caps.Cap]string // String capabilities.
+
+ // Capabilities listed in the "extended" section. The values are in the
+ // Bools, Numbers, and Strings maps.
+ Extended []*caps.Cap
+
+ // The default format uses int16, but the "extended number format" uses
+ // int32. This lists the integer size as 2 or 4.
+ IntSize int
+
+ // List of keys, as sequence → Key mapping. e.g. "\x1b[OP" → KeyF1.
+ //
+ // This contains all key_* capabilities, plus a few generated ones for
+ // modifier keys and such.
+ Keys map[string]keys.Key
+}
+
+// New reads the terminfo for term. If term is an empty string then the value of
+// the TERM environment variable is used.
+//
+// It tries to load a terminfo file according to these rules:
+//
+// 1. Use the path in TERMINFO if it's set and don't search any other
+// locations.
+//
+// 2. Try built-in ones unless set NO_BUILTIN_TERMINFO is set.
+//
+// 3. Try ~/.terminfo/ as the database path.
+//
+// 4. Look in the paths listed in TERMINFO_DIRS.
+//
+// 5. Look in /lib/terminfo/
+//
+// 6. Look in /usr/share/terminfo/
+//
+// These are the same rules as ncurses, except that step 2 was added.
+//
+// TODO: curses allows setting a different path at compile-time; we can use
+// infocmp -D to get this. Probably want to add this as step 7(?)
+func New(term string) (*Terminfo, error) {
+ if term == "" {
+ term = os.Getenv("TERM")
+ if term == "" {
+ return nil, errors.New("terminfo: TERM not set")
+ }
+ }
+ ti, err := loadTerminfo(term)
+ if err != nil {
+ return nil, err
+ }
+
+ // Add all the keys.
+ for o, k := range keys.Keys {
+ seq, ok := ti.Strings[o]
+ if ok {
+ ti.Keys[seq] = k
+ }
+ }
+
+ // From tcell:
+ //
+ // Sadly, xterm handling of keycodes is somewhat erratic. In particular,
+ // different codes are sent depending on application mode is in use or
+ // not, and the entries for many of these are simply absent from terminfo
+ // on many systems. So we insert a number of escape sequences if they are
+ // not already used, in order to have the widest correct usage. Note that
+ // prepareKey will not inject codes if the escape sequence is already
+ // known. We also only do this for terminals that have the application
+ // mode present.
+ if _, ok := ti.Strings[caps.KeypadXmit]; ok {
+ ti.Keys["\x1b[A"] = keys.Up
+ ti.Keys["\x1b[B"] = keys.Down
+ ti.Keys["\x1b[C"] = keys.Right
+ ti.Keys["\x1b[D"] = keys.Left
+ ti.Keys["\x1b[F"] = keys.End
+ ti.Keys["\x1b[H"] = keys.Home
+ ti.Keys["\x1b[3~"] = keys.Delete
+ ti.Keys["\x1b[1~"] = keys.Home
+ ti.Keys["\x1b[4~"] = keys.End
+ ti.Keys["\x1b[5~"] = keys.PageUp
+ ti.Keys["\x1b[6~"] = keys.PageDown
+ // Application mode
+ ti.Keys["\x1bOA"] = keys.Up
+ ti.Keys["\x1bOB"] = keys.Down
+ ti.Keys["\x1bOC"] = keys.Right
+ ti.Keys["\x1bOD"] = keys.Left
+ ti.Keys["\x1bOH"] = keys.Home
+ }
+
+ for seq, k := range ti.Keys {
+ addModifierKeys(ti, seq, k)
+ }
+ return ti, nil
+}
+
+func (ti Terminfo) String() string {
+ return fmt.Sprintf("Terminfo file for %q from %q with %d properties", ti.Name, ti.Location,
+ len(ti.Bools)+len(ti.Numbers)+len(ti.Strings))
+}
+
+// Supports reports if this terminal supports the given capability.
+func (ti Terminfo) Supports(c *caps.Cap) bool {
+ if _, ok := ti.Bools[c]; ok {
+ return true
+ }
+ if _, ok := ti.Numbers[c]; ok {
+ return true
+ }
+ if v := ti.Strings[c]; v != "" {
+ return true
+ }
+
+ return false
+}
+
+// Get a capability.
+func (ti Terminfo) Get(c *caps.Cap, args ...int) string {
+ v, ok := ti.Strings[c]
+ if !ok {
+ return ""
+ }
+ return replaceParams(v, args...)
+}
+
+func (ti Terminfo) Put(w io.Writer, c *caps.Cap, args ...int) {
+ w.Write([]byte(ti.Get(c, args...)))
+}
+
+// Event sent by FindKeys.
+type Event struct {
+ Key keys.Key // Processed key that was pressed.
+ Seq []byte // Unprocessed text; only usedful for debugging really.
+ Err error // Error; only set for read errors.
+}
+
+// FindKeys finds all keys in the given reader (usually stdin) and sends them in
+// the channel.
+//
+// Any read error will send an Event with Err set and it will stop reading keys.
+func (ti Terminfo) FindKeys(fp io.Reader) <-chan Event {
+ var (
+ ch = make(chan Event)
+ pbuf []byte
+ )
+ go func() {
+ for {
+ buf := make([]byte, 32)
+ n, err := fp.Read(buf)
+ if err != nil {
+ ch <- Event{Err: err}
+ break
+ }
+ buf = buf[:n]
+ if pbuf != nil {
+ buf = append(pbuf, buf...)
+ pbuf = nil
+ }
+
+ for {
+ k, n := ti.FindKey(buf)
+ if n == 0 {
+ break
+ }
+
+ // Possible the buffer just ran out in the middle of a multibyte
+ // character, so try again.
+ if k == utf8.RuneError && len(buf) < 4 {
+ pbuf = buf
+ break
+ }
+
+ seq := buf[:n]
+ buf = buf[n:]
+ ch <- Event{Key: k, Seq: seq}
+ }
+ }
+ }()
+ return ch
+}
+
+// Find the first valid keypress in s.
+//
+// Returns the key and number of bytes processed. On errors it will return
+// UnknownSequence and the length of the string.
+func (ti Terminfo) FindKey(b []byte) (keys.Key, int) {
+ // TODO: this only works for ASCII; not entirely sure how non-ASCII input is
+ // done wrt. Control key etc.
+ // TODO: doesn't deal with characters consisting of multiple codepoints.
+ // Maybe want to add: https://github.com/arp242/termtext
+ //
+ // TODO: on my system <C-Tab> sends \E[Z, which isn't recognized(?)
+ // Also: <C-End>
+ // <S-End> is "<Send>"?
+ if len(b) == 0 {
+ return 0, 0
+ }
+
+ // No escape sequence.
+ if b[0] != 0x1b {
+ return toKey(b)
+ }
+
+ // Single \E
+ if len(b) == 1 {
+ return keys.Escape, 1
+ }
+
+ // Exact match.
+ k, ok := ti.Keys[string(b)]
+ if ok {
+ return k, len(b)
+ }
+
+ // Find first matching.
+ for seq, k := range ti.Keys {
+ if bytes.HasPrefix(b, []byte(seq)) {
+ return k, len(seq)
+ }
+ }
+
+ // Alt keys are sent as \Ek.
+ // TODO: I think this depends on the "mode"? Xterm has settings for it anyway.
+ if len(b) == 2 {
+ k, _ := toKey(b[1:])
+ return k | keys.Alt, 2
+ }
+ return keys.UnknownSequence, len(b)
+}
+
+func toKey(b []byte) (keys.Key, int) {
+ // TODO: we probably want to use rivo/uniseg here; otherwise something like:
+ //
+ // U+1F3F4 (🏴) U+200D U+2620 (☠️)
+ //
+ // will be sent as three characters, rather than one. It should be the
+ // "pirate flag" emoji.
+ //
+ // Actually, this kinda sucks because this is where my clever "encode
+ // everything in a uint64!"-scheme kind of breaks down, since this can't be
+ // represented by that.
+ //
+ // Perhaps change the return signature to (Key, string, int), and then send
+ // a special MultiCodepoint as the Key? I don't know...
+ //
+ // Or maybe just don't support it. Many applications will work just fine
+ // anyway; e.g. if you print text it will just output those three bytes and
+ // it's all grand, and modifiers aren't sent with that in the first place.
+ r, n := utf8.DecodeRune(b)
+ switch {
+ case r == 0x7f:
+ return keys.Backspace, n
+ case r == 0x0d:
+ return keys.Enter, n
+ case r < 0x1f:
+ return keys.Key(r) | 0x20 | 0x40 | keys.Ctrl, n
+ case r >= 'A' && r <= 'Z':
+ return keys.Key(r) ^ 0x20 | keys.Shift, n
+ default:
+ return keys.Key(r), n
+ }
+
+}
diff --git a/tui/termfo/termfo_test.go b/tui/termfo/termfo_test.go
new file mode 100644
index 0000000..60564ef
--- /dev/null
+++ b/tui/termfo/termfo_test.go
@@ -0,0 +1,87 @@
+package termfo
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "strings"
+ "testing"
+)
+
+func BenchmarkNew(b *testing.B) {
+ t := os.Getenv("TERM")
+ b.ReportAllocs()
+ for n := 0; n < b.N; n++ {
+ New(t)
+ }
+}
+
+func TestNew(t *testing.T) {
+ tests := []struct {
+ term string
+ intSize int
+ }{
+ {"xterm", 2},
+ {"xterm-256color", 4},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.term, func(t *testing.T) {
+ ti, err := New(tt.term)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if ti.Name != tt.term {
+ t.Errorf("wrong name: %q", ti.Name)
+ }
+ if ti.IntSize != tt.intSize {
+ t.Errorf("intsize: %d", ti.IntSize)
+ }
+
+ // Just a basic sanity check.
+ if len(ti.Bools) < 10 || len(ti.Numbers) < 5 || len(ti.Strings) < 200 || len(ti.Extended) < 50 {
+ t.Errorf("bools: %d nums: %d strs: %d ext: %d", len(ti.Bools), len(ti.Numbers), len(ti.Strings), len(ti.Extended))
+ }
+ })
+ }
+}
+
+func TestKeys(t *testing.T) {
+ run := func(ti *Terminfo, seq string) []string {
+ var collect []string
+ ch := ti.FindKeys(strings.NewReader(seq))
+ for e := <-ch; ; e = <-ch {
+ if e.Err != nil {
+ if e.Err == io.EOF {
+ break
+ }
+ t.Fatal(e.Err)
+ }
+ collect = append(collect, e.Key.String())
+ }
+ return collect
+ }
+
+ {
+ ti, err := New("xterm")
+ if err != nil {
+ t.Fatal(err)
+ }
+ f := run(ti, "\x1bOP\x1b[1;2P\x1bOA")
+ if fmt.Sprintf("%s", f) != "[<F1> <S-F1> <Up>]" {
+ t.Error(f)
+ }
+ }
+
+ {
+ ti, err := New("vi200")
+ if err != nil {
+ t.Fatal(err)
+ }
+ f := run(ti, "\x1b?q\x1bA")
+ if fmt.Sprintf("%s", f) != "[<F1> <Up>]" {
+ t.Error(f)
+ }
+ }
+}
diff --git a/tui/termfo/terminfo/xterm-256color b/tui/termfo/terminfo/xterm-256color
new file mode 100644
index 0000000..2a9f42b
--- /dev/null
+++ b/tui/termfo/terminfo/xterm-256color
Binary files differ