diff options
| author | raven <citrons@mondecitronne.com> | 2026-02-20 13:42:12 -0600 |
|---|---|---|
| committer | raven <citrons@mondecitronne.com> | 2026-02-20 13:46:59 -0600 |
| commit | 5b6196ebe67cf954bae8212c1a33b869da723e11 (patch) | |
| tree | dce33c06621847c3862e64bda914b1e8a450317d | |
| parent | 05c068749740f9430d1fda7698c433697eef1652 (diff) | |
support builtin terminfo
copy termfo into the repository and modify it to embed an xterm terminfo to as
a fallback
32 files changed, 5705 insertions, 11 deletions
diff --git a/client/ui.go b/client/ui.go index 0e7ce55..1c090f2 100644 --- a/client/ui.go +++ b/client/ui.go @@ -5,7 +5,7 @@ import ( "citrons.xyz/talk/client/window" "citrons.xyz/talk/tui" "citrons.xyz/talk/proto" - "zgo.at/termfo/keys" + "citrons.xyz/talk/tui/termfo/keys" "strings" ) diff --git a/football/main.go b/football/main.go index ed1154a..82164db 100644 --- a/football/main.go +++ b/football/main.go @@ -2,7 +2,7 @@ package main import ( "citrons.xyz/talk/tui" - "zgo.at/termfo/keys" + "citrons.xyz/talk/tui/termfo/keys" "math/rand" "log" "os" @@ -7,7 +7,6 @@ require ( go.etcd.io/bbolt v1.3.6 golang.org/x/crypto v0.43.0 golang.org/x/term v0.36.0 - zgo.at/termfo v0.0.0-20240522162355-df5e07d67a5a ) require golang.org/x/sys v0.37.0 // indirect @@ -9,5 +9,3 @@ golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= -zgo.at/termfo v0.0.0-20240522162355-df5e07d67a5a h1:xUxSj/In2mmaBYwNcmDZyyh+lMv9MpYnAUskTdjtJhc= -zgo.at/termfo v0.0.0-20240522162355-df5e07d67a5a/go.mod h1:p6tU/Jw86OwNL/dvosR/GtaB+ABfGMB5U0wQgxBVstA= diff --git a/tui/draw.go b/tui/draw.go index 1f89b13..f70c98e 100644 --- a/tui/draw.go +++ b/tui/draw.go @@ -3,8 +3,8 @@ package tui import ( "github.com/rivo/uniseg" "golang.org/x/term" - "zgo.at/termfo" - "zgo.at/termfo/caps" + "citrons.xyz/talk/tui/termfo" + "citrons.xyz/talk/tui/termfo/caps" "unicode" "unicode/utf8" "bufio" @@ -94,7 +94,7 @@ func Start() error { } terminfo, err = termfo.New("") if err != nil { - if terminfo, err = termfo.New("xterm"); err != nil { + if terminfo, err = termfo.New("xterm-256color"); err != nil { return err } } diff --git a/tui/event.go b/tui/event.go index 9d7a9a2..d24bd2b 100644 --- a/tui/event.go +++ b/tui/event.go @@ -3,8 +3,8 @@ package tui import ( "os" "os/signal" - "zgo.at/termfo" - "zgo.at/termfo/keys" + "citrons.xyz/talk/tui/termfo" + "citrons.xyz/talk/tui/termfo/keys" "strings" "strconv" "bufio" 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 Binary files differnew file mode 100644 index 0000000..2a9f42b --- /dev/null +++ b/tui/termfo/terminfo/xterm-256color diff --git a/tui/text_input.go b/tui/text_input.go index 24792bc..33dc02c 100644 --- a/tui/text_input.go +++ b/tui/text_input.go @@ -2,7 +2,7 @@ package tui import ( "github.com/rivo/uniseg" - "zgo.at/termfo/keys" + "citrons.xyz/talk/tui/termfo/keys" "strings" "unicode" ) |
