// Copyright 2025 The TCell Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use file except in compliance with the License.// You may obtain a copy of the license at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.package terminfoimport ()var (// ErrTermNotFound indicates that a suitable terminal entry could // not be found. This can result from either not having TERM set, // or from the TERM failing to support certain minimal functionality, // in particular absolute cursor addressability (the cup capability) // is required. For example, legacy "adm3" lacks this capability, // whereas the slightly newer "adm3a" supports it. This failure // occurs most often with "dumb".ErrTermNotFound = errors.New("terminal entry not found"))// Terminfo represents a terminfo entry. Note that we use friendly names// in Go, but when we write out JSON, we use the same names as terminfo.// The name, aliases and smous, rmous fields do not come from terminfo directly.typeTerminfostruct { Name string Aliases []string Columns int// cols Lines int// lines Colors int// colors Clear string// clear EnterCA string// smcup ExitCA string// rmcup ShowCursor string// cnorm HideCursor string// civis AttrOff string// sgr0 Underline string// smul Bold string// bold Blink string// blink Reverse string// rev Dim string// dim Italic string// sitm EnterKeypad string// smkx ExitKeypad string// rmkx SetFg string// setaf SetBg string// setab ResetFgBg string// op SetCursor string// cup PadChar string// pad Mouse string// kmous AltChars string// acsc EnterAcs string// smacs ExitAcs string// rmacs EnableAcs string// enacs// These are non-standard extensions to terminfo. This includes // true color support, and some additional keys. Its kind of bizarre // that shifted variants of left and right exist, but not up and down. // Terminal support for these are going to vary amongst XTerm // emulations, so don't depend too much on them in your application. StrikeThrough string// smxx SetFgBg string// setfgbg SetFgBgRGB string// setfgbgrgb SetFgRGB string// setfrgb SetBgRGB string// setbrgb InsertChar string// string to insert a character (ich1) AutoMargin bool// true if writing to last cell in line advances TrueColor bool// true if the terminal supports direct color DisableAutoMargin string// smam EnableAutoMargin string// rmam XTermLike bool// (XT) has XTerm extensions}type stack []anyfunc ( stack) ( any) stack {if , := .(bool); {if {returnappend(, 1) } else {returnappend(, 0) } }returnappend(, )}func ( stack) () (string, stack) {iflen() > 0 { := [len()-1]varstringswitch v := .(type) {caseint: = strconv.Itoa()casestring: = }return , [:len()-1] }return"", }func ( stack) () (int, stack) {iflen() > 0 { := [len()-1]varintswitch v := .(type) {caseint: = casestring: , _ = strconv.Atoi() }return , [:len()-1] }return0, }// static varsvar svars [26]stringtype paramsBuffer struct { out bytes.Buffer buf bytes.Buffer}// Start initializes the params buffer with the initial string data.// It also locks the paramsBuffer. The caller must call End() when// finished.func ( *paramsBuffer) ( string) { .out.Reset() .buf.Reset() .buf.WriteString()}// End returns the final output from TParam, but it also releases the lock.func ( *paramsBuffer) () string { := .out.String()return}// NextCh returns the next input character to the expander.func ( *paramsBuffer) () (byte, error) {return .buf.ReadByte()}// PutCh "emits" (rather schedules for output) a single byte character.func ( *paramsBuffer) ( byte) { .out.WriteByte()}// PutString schedules a string for output.func ( *paramsBuffer) ( string) { .out.WriteString()}// TParm takes a terminfo parameterized string, such as setaf or cup, and// evaluates the string, and returns the result with the parameter// applied.func ( *Terminfo) ( string, ...any) string {varstackvarstringvar , intvar [26]stringvar [9]anyvar = ¶msBuffer{} .Start()// make sure we always have 9 parameters -- makes it easier // later to skip checksfor := 0; < len() && < len(); ++ { [] = [] }const ( = iota ) := for { , := .NextCh()if != nil {break }if != '%' {if == { .PutCh() }continue } , = .NextCh()if != nil {// XXX Errorbreak }if == {if == ';' { = }continue } elseif == {if == 'e' || == ';' { = }continue }switch {case'%': // quoted % .PutCh()case'i': // increment both parameters (ANSI cup support)if , := [0].(int); { [0] = + 1 }if , := [1].(int); { [1] = + 1 }case's':// NB: 's', 'c', and 'd' below are special cased for // efficiency. They could be handled by the richer // format support below, less efficiently. , = .PopString() .PutString()case'c':// Integer as special character. , = .PopInt() .PutCh(byte())case'd': , = .PopInt() .PutString(strconv.Itoa())case'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'x', 'X', 'o', ':':// This is pretty suboptimal, but this is rarely used. // None of the mainstream terminals use any of this, // and it would surprise me if this code is ever // executed outside test cases. := "%"if == ':' { , _ = .NextCh() } += string()for == '+' || == '-' || == '#' || == ' ' { , _ = .NextCh() += string() }for ( >= '0' && <= '9') || == '.' { , _ = .NextCh() += string() }switch {case'd', 'x', 'X', 'o': , = .PopInt() .PutString(fmt.Sprintf(, ))case's': , = .PopString() .PutString(fmt.Sprintf(, ))case'c': , = .PopInt() .PutString(fmt.Sprintf(, )) }case'p': // push parameter , _ = .NextCh() = int( - '1')if >= 0 && < len() { = .Push([]) } else { = .Push(0) }case'P': // pop & store variable , _ = .NextCh()if >= 'A' && <= 'Z' {svars[int(-'A')], = .PopString() } elseif >= 'a' && <= 'z' { [int(-'a')], = .PopString() }case'g': // recall & push variable , _ = .NextCh()if >= 'A' && <= 'Z' { = .Push(svars[int(-'A')]) } elseif >= 'a' && <= 'z' { = .Push([int(-'a')]) }case'\'': // push(char) - the integer value of it , _ = .NextCh() _, _ = .NextCh() // must be ' but we don't check = .Push(int())case'{': // push(int) = 0 , _ = .NextCh()for >= '0' && <= '9' { *= 10 += int( - '0') , _ = .NextCh() }// ch must be '}' but no verification = .Push()case'l': // push(strlen(pop)) , = .PopString() = .Push(len())case'+': , = .PopInt() , = .PopInt() = .Push( + )case'-': , = .PopInt() , = .PopInt() = .Push( - )case'*': , = .PopInt() , = .PopInt() = .Push( * )case'/': , = .PopInt() , = .PopInt()if != 0 { = .Push( / ) } else { = .Push(0) }case'm': // push(pop mod pop) , = .PopInt() , = .PopInt()if != 0 { = .Push( % ) } else { = .Push(0) }case'&': // AND , = .PopInt() , = .PopInt() = .Push( & )case'|': // OR , = .PopInt() , = .PopInt() = .Push( | )case'^': // XOR , = .PopInt() , = .PopInt() = .Push( ^ )case'~': // bit complement , = .PopInt() = .Push( ^ -1)case'!': // logical NOT , = .PopInt() = .Push( == 0)case'=': // numeric compare , = .PopInt() , = .PopInt() = .Push( == )case'>': // greater than, numeric , = .PopInt() , = .PopInt() = .Push( > )case'<': // less than, numeric , = .PopInt() , = .PopInt() = .Push( < )case'?': // start conditionalcase';': = case't': , = .PopInt()if == 0 { = }case'e': = default: .PutString("%" + string()) } }return .End()}// TPuts emits the string to the writer, but expands inline padding// indications (of the form $<[delay]> where [delay] is msec) to// a suitable time (unless the terminfo string indicates this isn't needed// by specifying npc - no padding). All Terminfo based strings should be// emitted using this function.func ( *Terminfo) ( io.Writer, string) {for { := strings.Index(, "$<")if < 0 {// Most strings don't need padding, which is good news! _, _ = io.WriteString(, )return } _, _ = io.WriteString(, [:]) = [+2:] := strings.Index(, ">")if < 0 {// unterminated.. just emit bytes unadulterated _, _ = io.WriteString(, "$<"+)return } := [:] = [+1:] := 0 := time.Millisecond := false :for := range {switch [] {case'0', '1', '2', '3', '4', '5', '6', '7', '8', '9': *= 10 += int([] - '0')if { /= 10 }case'.':if ! { = true } else {break }default:break } }// Curses historically uses padding to achieve "fine grained" // delays. We have much better clocks these days, and so we // do not rely on padding but simply sleep a bit.iflen(.PadChar) > 0 {time.Sleep( * time.Duration()) } }}// TGoto returns a string suitable for addressing the cursor at the given// row and column. The origin 0, 0 is in the upper left corner of the screen.func ( *Terminfo) (, int) string {return .TParm(.SetCursor, , )}// TColor returns a string corresponding to the given foreground and background// colors. Either fg or bg can be set to -1 to elide.func ( *Terminfo) (, int) string { := ""// As a special case, we map bright colors to lower versions if the // color table only holds 8. For the remaining 240 colors, the user // is out of luck. Someday we could create a mapping table, but its // not worth it.if .Colors == 8 {if > 7 && < 16 { -= 8 }if > 7 && < 16 { -= 8 } }if .Colors > && >= 0 { += .TParm(.SetFg, ) }if .Colors > && >= 0 { += .TParm(.SetBg, ) }return}var ( dblock sync.Mutex terminfos = make(map[string]*Terminfo))// AddTerminfo can be called to register a new Terminfo entry.func ( *Terminfo) {dblock.Lock()terminfos[.Name] = for , := range .Aliases {terminfos[] = }dblock.Unlock()}// LookupTerminfo attempts to find a definition for the named $TERM.func ( string) (*Terminfo, error) {if == "" {// else on windows: index out of bounds // on the name[0] reference belowreturnnil, ErrTermNotFound } := false := falseswitchos.Getenv("COLORTERM") {case"truecolor", "24bit", "24-bit": = true }dblock.Lock() := terminfos[]dblock.Unlock()// If the name ends in -truecolor, then fabricate an entry // from the corresponding -256color, -color, or bare terminal.if != nil && .TrueColor { = true } elseif == nil && strings.HasSuffix(, "-truecolor") { := []string{"-256color","-88color","-color","", } := [:len()-len("-truecolor")]for , := range {if , _ = ( + ); != nil { = truebreak } } }// If the name ends in -256color, maybe fabricate using the xterm 256 color sequencesif == nil && strings.HasSuffix(, "-256color") { := []string{"-88color","-color", } := [:len()-len("-256color")]for , := range {if , _ = ( + ); != nil { = truebreak } } }if == nil {returnnil, ErrTermNotFound }switchos.Getenv("TCELL_TRUECOLOR") {case"":case"disable": = falsedefault: = true }// If the user has requested 24-bit color with $COLORTERM, then // amend the value (unless already present). This means we don't // need to have a value present.if && .SetFgBgRGB == "" && .SetFgRGB == "" && .SetBgRGB == "" {// Supply vanilla ISO 8613-6:1994 24-bit color sequences. .SetFgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%dm" .SetBgRGB = "\x1b[48;2;%p1%d;%p2%d;%p3%dm" .SetFgBgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%d;" +"48;2;%p4%d;%p5%d;%p6%dm" }if { .Colors = 256 .SetFg = "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m" .SetBg = "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m" .SetFgBg = "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m" .ResetFgBg = "\x1b[39;49m" }return , nil}func () []string { := make([]string, 0, len(terminfos))for := rangeterminfos { = append(, ) }return}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.