mirror of
https://github.com/gdamore/tcell.git
synced 2025-04-24 13:48:51 +08:00

This adds Bracketed Paste support for terminals that have mouse support and support it. The bracketing events are EventPaste, with methods to note Start() or End() of the paste. Content comes in as normal rune events. Programs must opt-in to this by calling screen.EnablePaste().
804 lines
19 KiB
Go
804 lines
19 KiB
Go
// Copyright 2020 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 terminfo
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
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.
|
|
type Terminfo struct {
|
|
Name string
|
|
Aliases []string
|
|
Columns int // cols
|
|
Lines int // lines
|
|
Colors int // colors
|
|
Bell string // bell
|
|
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
|
|
CursorBack1 string // cub1
|
|
CursorUp1 string // cuu1
|
|
PadChar string // pad
|
|
KeyBackspace string // kbs
|
|
KeyF1 string // kf1
|
|
KeyF2 string // kf2
|
|
KeyF3 string // kf3
|
|
KeyF4 string // kf4
|
|
KeyF5 string // kf5
|
|
KeyF6 string // kf6
|
|
KeyF7 string // kf7
|
|
KeyF8 string // kf8
|
|
KeyF9 string // kf9
|
|
KeyF10 string // kf10
|
|
KeyF11 string // kf11
|
|
KeyF12 string // kf12
|
|
KeyF13 string // kf13
|
|
KeyF14 string // kf14
|
|
KeyF15 string // kf15
|
|
KeyF16 string // kf16
|
|
KeyF17 string // kf17
|
|
KeyF18 string // kf18
|
|
KeyF19 string // kf19
|
|
KeyF20 string // kf20
|
|
KeyF21 string // kf21
|
|
KeyF22 string // kf22
|
|
KeyF23 string // kf23
|
|
KeyF24 string // kf24
|
|
KeyF25 string // kf25
|
|
KeyF26 string // kf26
|
|
KeyF27 string // kf27
|
|
KeyF28 string // kf28
|
|
KeyF29 string // kf29
|
|
KeyF30 string // kf30
|
|
KeyF31 string // kf31
|
|
KeyF32 string // kf32
|
|
KeyF33 string // kf33
|
|
KeyF34 string // kf34
|
|
KeyF35 string // kf35
|
|
KeyF36 string // kf36
|
|
KeyF37 string // kf37
|
|
KeyF38 string // kf38
|
|
KeyF39 string // kf39
|
|
KeyF40 string // kf40
|
|
KeyF41 string // kf41
|
|
KeyF42 string // kf42
|
|
KeyF43 string // kf43
|
|
KeyF44 string // kf44
|
|
KeyF45 string // kf45
|
|
KeyF46 string // kf46
|
|
KeyF47 string // kf47
|
|
KeyF48 string // kf48
|
|
KeyF49 string // kf49
|
|
KeyF50 string // kf50
|
|
KeyF51 string // kf51
|
|
KeyF52 string // kf52
|
|
KeyF53 string // kf53
|
|
KeyF54 string // kf54
|
|
KeyF55 string // kf55
|
|
KeyF56 string // kf56
|
|
KeyF57 string // kf57
|
|
KeyF58 string // kf58
|
|
KeyF59 string // kf59
|
|
KeyF60 string // kf60
|
|
KeyF61 string // kf61
|
|
KeyF62 string // kf62
|
|
KeyF63 string // kf63
|
|
KeyF64 string // kf64
|
|
KeyInsert string // kich1
|
|
KeyDelete string // kdch1
|
|
KeyHome string // khome
|
|
KeyEnd string // kend
|
|
KeyHelp string // khlp
|
|
KeyPgUp string // kpp
|
|
KeyPgDn string // knp
|
|
KeyUp string // kcuu1
|
|
KeyDown string // kcud1
|
|
KeyLeft string // kcub1
|
|
KeyRight string // kcuf1
|
|
KeyBacktab string // kcbt
|
|
KeyExit string // kext
|
|
KeyClear string // kclr
|
|
KeyPrint string // kprt
|
|
KeyCancel string // kcan
|
|
Mouse string // kmous
|
|
MouseMode string // XM
|
|
AltChars string // acsc
|
|
EnterAcs string // smacs
|
|
ExitAcs string // rmacs
|
|
EnableAcs string // enacs
|
|
KeyShfRight string // kRIT
|
|
KeyShfLeft string // kLFT
|
|
KeyShfHome string // kHOM
|
|
KeyShfEnd string // kEND
|
|
KeyShfInsert string // kIC
|
|
KeyShfDelete string // kDC
|
|
|
|
// 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
|
|
KeyShfUp string // shift-up
|
|
KeyShfDown string // shift-down
|
|
KeyShfPgUp string // shift-kpp
|
|
KeyShfPgDn string // shift-knp
|
|
KeyCtrlUp string // ctrl-up
|
|
KeyCtrlDown string // ctrl-left
|
|
KeyCtrlRight string // ctrl-right
|
|
KeyCtrlLeft string // ctrl-left
|
|
KeyMetaUp string // meta-up
|
|
KeyMetaDown string // meta-left
|
|
KeyMetaRight string // meta-right
|
|
KeyMetaLeft string // meta-left
|
|
KeyAltUp string // alt-up
|
|
KeyAltDown string // alt-left
|
|
KeyAltRight string // alt-right
|
|
KeyAltLeft string // alt-left
|
|
KeyCtrlHome string
|
|
KeyCtrlEnd string
|
|
KeyMetaHome string
|
|
KeyMetaEnd string
|
|
KeyAltHome string
|
|
KeyAltEnd string
|
|
KeyAltShfUp string
|
|
KeyAltShfDown string
|
|
KeyAltShfLeft string
|
|
KeyAltShfRight string
|
|
KeyMetaShfUp string
|
|
KeyMetaShfDown string
|
|
KeyMetaShfLeft string
|
|
KeyMetaShfRight string
|
|
KeyCtrlShfUp string
|
|
KeyCtrlShfDown string
|
|
KeyCtrlShfLeft string
|
|
KeyCtrlShfRight string
|
|
KeyCtrlShfHome string
|
|
KeyCtrlShfEnd string
|
|
KeyAltShfHome string
|
|
KeyAltShfEnd string
|
|
KeyMetaShfHome string
|
|
KeyMetaShfEnd string
|
|
EnablePaste string // bracketed paste mode
|
|
DisablePaste string
|
|
PasteStart string
|
|
PasteEnd string
|
|
Modifiers int
|
|
TrueColor bool // true if the terminal supports direct color
|
|
}
|
|
|
|
const (
|
|
ModifiersNone = 0
|
|
ModifiersXTerm = 1
|
|
)
|
|
|
|
type stackElem struct {
|
|
s string
|
|
i int
|
|
isStr bool
|
|
isInt bool
|
|
}
|
|
|
|
type stack []stackElem
|
|
|
|
func (st stack) Push(v string) stack {
|
|
e := stackElem{
|
|
s: v,
|
|
isStr: true,
|
|
}
|
|
return append(st, e)
|
|
}
|
|
|
|
func (st stack) Pop() (string, stack) {
|
|
v := ""
|
|
if len(st) > 0 {
|
|
e := st[len(st)-1]
|
|
st = st[:len(st)-1]
|
|
if e.isStr {
|
|
v = e.s
|
|
} else {
|
|
v = strconv.Itoa(e.i)
|
|
}
|
|
}
|
|
return v, st
|
|
}
|
|
|
|
func (st stack) PopInt() (int, stack) {
|
|
if len(st) > 0 {
|
|
e := st[len(st)-1]
|
|
st = st[:len(st)-1]
|
|
if e.isInt {
|
|
return e.i, st
|
|
} else if e.isStr {
|
|
// If the string that was pushed was the representation
|
|
// of a number e.g. '123', then return the number. If the
|
|
// conversion doesn't work, assume the string pushed was
|
|
// intended to return, as an int, the ascii representation
|
|
// of the (one and only) character.
|
|
i, err := strconv.Atoi(e.s)
|
|
if err == nil {
|
|
return i, st
|
|
} else if len(e.s) >= 1 {
|
|
return int(e.s[0]), st
|
|
}
|
|
}
|
|
}
|
|
return 0, st
|
|
}
|
|
|
|
func (st stack) PopBool() (bool, stack) {
|
|
if len(st) > 0 {
|
|
e := st[len(st)-1]
|
|
st = st[:len(st)-1]
|
|
if e.isStr {
|
|
if e.s == "1" {
|
|
return true, st
|
|
}
|
|
return false, st
|
|
} else if e.i == 1 {
|
|
return true, st
|
|
} else {
|
|
return false, st
|
|
}
|
|
}
|
|
return false, st
|
|
}
|
|
|
|
func (st stack) PushInt(i int) stack {
|
|
e := stackElem{
|
|
i: i,
|
|
isInt: true,
|
|
}
|
|
return append(st, e)
|
|
}
|
|
|
|
func (st stack) PushBool(i bool) stack {
|
|
if i {
|
|
return st.PushInt(1)
|
|
}
|
|
return st.PushInt(0)
|
|
}
|
|
|
|
// static vars
|
|
var svars [26]string
|
|
|
|
// paramsBuffer handles some persistent state for TParam. Technically we
|
|
// could probably dispense with this, but caching buffer arrays gives us
|
|
// a nice little performance boost. Furthermore, we know that TParam is
|
|
// rarely (never?) called re-entrantly, so we can just reuse the same
|
|
// buffers, making it thread-safe by stashing a lock.
|
|
type paramsBuffer struct {
|
|
out bytes.Buffer
|
|
buf bytes.Buffer
|
|
lk sync.Mutex
|
|
}
|
|
|
|
// Start initializes the params buffer with the initial string data.
|
|
// It also locks the paramsBuffer. The caller must call End() when
|
|
// finished.
|
|
func (pb *paramsBuffer) Start(s string) {
|
|
pb.lk.Lock()
|
|
pb.out.Reset()
|
|
pb.buf.Reset()
|
|
pb.buf.WriteString(s)
|
|
}
|
|
|
|
// End returns the final output from TParam, but it also releases the lock.
|
|
func (pb *paramsBuffer) End() string {
|
|
s := pb.out.String()
|
|
pb.lk.Unlock()
|
|
return s
|
|
}
|
|
|
|
// NextCh returns the next input character to the expander.
|
|
func (pb *paramsBuffer) NextCh() (byte, error) {
|
|
return pb.buf.ReadByte()
|
|
}
|
|
|
|
// PutCh "emits" (rather schedules for output) a single byte character.
|
|
func (pb *paramsBuffer) PutCh(ch byte) {
|
|
pb.out.WriteByte(ch)
|
|
}
|
|
|
|
// PutString schedules a string for output.
|
|
func (pb *paramsBuffer) PutString(s string) {
|
|
pb.out.WriteString(s)
|
|
}
|
|
|
|
var pb = ¶msBuffer{}
|
|
|
|
// TParm takes a terminfo parameterized string, such as setaf or cup, and
|
|
// evaluates the string, and returns the result with the parameter
|
|
// applied.
|
|
func (t *Terminfo) TParm(s string, p ...int) string {
|
|
var stk stack
|
|
var a, b string
|
|
var ai, bi int
|
|
var ab bool
|
|
var dvars [26]string
|
|
var params [9]int
|
|
|
|
pb.Start(s)
|
|
|
|
// make sure we always have 9 parameters -- makes it easier
|
|
// later to skip checks
|
|
for i := 0; i < len(params) && i < len(p); i++ {
|
|
params[i] = p[i]
|
|
}
|
|
|
|
nest := 0
|
|
|
|
for {
|
|
|
|
ch, err := pb.NextCh()
|
|
if err != nil {
|
|
break
|
|
}
|
|
|
|
if ch != '%' {
|
|
pb.PutCh(ch)
|
|
continue
|
|
}
|
|
|
|
ch, err = pb.NextCh()
|
|
if err != nil {
|
|
// XXX Error
|
|
break
|
|
}
|
|
|
|
switch ch {
|
|
case '%': // quoted %
|
|
pb.PutCh(ch)
|
|
|
|
case 'i': // increment both parameters (ANSI cup support)
|
|
params[0]++
|
|
params[1]++
|
|
|
|
case 'c', 's':
|
|
// NB: these, and 'd' below are special cased for
|
|
// efficiency. They could be handled by the richer
|
|
// format support below, less efficiently.
|
|
a, stk = stk.Pop()
|
|
pb.PutString(a)
|
|
|
|
case 'd':
|
|
ai, stk = stk.PopInt()
|
|
pb.PutString(strconv.Itoa(ai))
|
|
|
|
case '0', '1', '2', '3', '4', '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 of test cases.
|
|
f := "%"
|
|
if ch == ':' {
|
|
ch, _ = pb.NextCh()
|
|
}
|
|
f += string(ch)
|
|
for ch == '+' || ch == '-' || ch == '#' || ch == ' ' {
|
|
ch, _ = pb.NextCh()
|
|
f += string(ch)
|
|
}
|
|
for (ch >= '0' && ch <= '9') || ch == '.' {
|
|
ch, _ = pb.NextCh()
|
|
f += string(ch)
|
|
}
|
|
switch ch {
|
|
case 'd', 'x', 'X', 'o':
|
|
ai, stk = stk.PopInt()
|
|
pb.PutString(fmt.Sprintf(f, ai))
|
|
case 'c', 's':
|
|
a, stk = stk.Pop()
|
|
pb.PutString(fmt.Sprintf(f, a))
|
|
}
|
|
|
|
case 'p': // push parameter
|
|
ch, _ = pb.NextCh()
|
|
ai = int(ch - '1')
|
|
if ai >= 0 && ai < len(params) {
|
|
stk = stk.PushInt(params[ai])
|
|
} else {
|
|
stk = stk.PushInt(0)
|
|
}
|
|
|
|
case 'P': // pop & store variable
|
|
ch, _ = pb.NextCh()
|
|
if ch >= 'A' && ch <= 'Z' {
|
|
svars[int(ch-'A')], stk = stk.Pop()
|
|
} else if ch >= 'a' && ch <= 'z' {
|
|
dvars[int(ch-'a')], stk = stk.Pop()
|
|
}
|
|
|
|
case 'g': // recall & push variable
|
|
ch, _ = pb.NextCh()
|
|
if ch >= 'A' && ch <= 'Z' {
|
|
stk = stk.Push(svars[int(ch-'A')])
|
|
} else if ch >= 'a' && ch <= 'z' {
|
|
stk = stk.Push(dvars[int(ch-'a')])
|
|
}
|
|
|
|
case '\'': // push(char)
|
|
ch, _ = pb.NextCh()
|
|
pb.NextCh() // must be ' but we don't check
|
|
stk = stk.Push(string(ch))
|
|
|
|
case '{': // push(int)
|
|
ai = 0
|
|
ch, _ = pb.NextCh()
|
|
for ch >= '0' && ch <= '9' {
|
|
ai *= 10
|
|
ai += int(ch - '0')
|
|
ch, _ = pb.NextCh()
|
|
}
|
|
// ch must be '}' but no verification
|
|
stk = stk.PushInt(ai)
|
|
|
|
case 'l': // push(strlen(pop))
|
|
a, stk = stk.Pop()
|
|
stk = stk.PushInt(len(a))
|
|
|
|
case '+':
|
|
bi, stk = stk.PopInt()
|
|
ai, stk = stk.PopInt()
|
|
stk = stk.PushInt(ai + bi)
|
|
|
|
case '-':
|
|
bi, stk = stk.PopInt()
|
|
ai, stk = stk.PopInt()
|
|
stk = stk.PushInt(ai - bi)
|
|
|
|
case '*':
|
|
bi, stk = stk.PopInt()
|
|
ai, stk = stk.PopInt()
|
|
stk = stk.PushInt(ai * bi)
|
|
|
|
case '/':
|
|
bi, stk = stk.PopInt()
|
|
ai, stk = stk.PopInt()
|
|
if bi != 0 {
|
|
stk = stk.PushInt(ai / bi)
|
|
} else {
|
|
stk = stk.PushInt(0)
|
|
}
|
|
|
|
case 'm': // push(pop mod pop)
|
|
bi, stk = stk.PopInt()
|
|
ai, stk = stk.PopInt()
|
|
if bi != 0 {
|
|
stk = stk.PushInt(ai % bi)
|
|
} else {
|
|
stk = stk.PushInt(0)
|
|
}
|
|
|
|
case '&': // AND
|
|
bi, stk = stk.PopInt()
|
|
ai, stk = stk.PopInt()
|
|
stk = stk.PushInt(ai & bi)
|
|
|
|
case '|': // OR
|
|
bi, stk = stk.PopInt()
|
|
ai, stk = stk.PopInt()
|
|
stk = stk.PushInt(ai | bi)
|
|
|
|
case '^': // XOR
|
|
bi, stk = stk.PopInt()
|
|
ai, stk = stk.PopInt()
|
|
stk = stk.PushInt(ai ^ bi)
|
|
|
|
case '~': // bit complement
|
|
ai, stk = stk.PopInt()
|
|
stk = stk.PushInt(ai ^ -1)
|
|
|
|
case '!': // logical NOT
|
|
ai, stk = stk.PopInt()
|
|
stk = stk.PushBool(ai != 0)
|
|
|
|
case '=': // numeric compare or string compare
|
|
b, stk = stk.Pop()
|
|
a, stk = stk.Pop()
|
|
stk = stk.PushBool(a == b)
|
|
|
|
case '>': // greater than, numeric
|
|
bi, stk = stk.PopInt()
|
|
ai, stk = stk.PopInt()
|
|
stk = stk.PushBool(ai > bi)
|
|
|
|
case '<': // less than, numeric
|
|
bi, stk = stk.PopInt()
|
|
ai, stk = stk.PopInt()
|
|
stk = stk.PushBool(ai < bi)
|
|
|
|
case '?': // start conditional
|
|
|
|
case 't':
|
|
ab, stk = stk.PopBool()
|
|
if ab {
|
|
// just keep going
|
|
break
|
|
}
|
|
nest = 0
|
|
ifloop:
|
|
// this loop consumes everything until we hit our else,
|
|
// or the end of the conditional
|
|
for {
|
|
ch, err = pb.NextCh()
|
|
if err != nil {
|
|
break
|
|
}
|
|
if ch != '%' {
|
|
continue
|
|
}
|
|
ch, _ = pb.NextCh()
|
|
switch ch {
|
|
case ';':
|
|
if nest == 0 {
|
|
break ifloop
|
|
}
|
|
nest--
|
|
case '?':
|
|
nest++
|
|
case 'e':
|
|
if nest == 0 {
|
|
break ifloop
|
|
}
|
|
}
|
|
}
|
|
|
|
case 'e':
|
|
// if we got here, it means we didn't use the else
|
|
// in the 't' case above, and we should skip until
|
|
// the end of the conditional
|
|
nest = 0
|
|
elloop:
|
|
for {
|
|
ch, err = pb.NextCh()
|
|
if err != nil {
|
|
break
|
|
}
|
|
if ch != '%' {
|
|
continue
|
|
}
|
|
ch, _ = pb.NextCh()
|
|
switch ch {
|
|
case ';':
|
|
if nest == 0 {
|
|
break elloop
|
|
}
|
|
nest--
|
|
case '?':
|
|
nest++
|
|
}
|
|
}
|
|
|
|
case ';': // endif
|
|
|
|
}
|
|
}
|
|
|
|
return pb.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 (t *Terminfo) TPuts(w io.Writer, s string) {
|
|
for {
|
|
beg := strings.Index(s, "$<")
|
|
if beg < 0 {
|
|
// Most strings don't need padding, which is good news!
|
|
io.WriteString(w, s)
|
|
return
|
|
}
|
|
io.WriteString(w, s[:beg])
|
|
s = s[beg+2:]
|
|
end := strings.Index(s, ">")
|
|
if end < 0 {
|
|
// unterminated.. just emit bytes unadulterated
|
|
io.WriteString(w, "$<"+s)
|
|
return
|
|
}
|
|
val := s[:end]
|
|
s = s[end+1:]
|
|
padus := 0
|
|
unit := time.Millisecond
|
|
dot := false
|
|
loop:
|
|
for i := range val {
|
|
switch val[i] {
|
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
|
padus *= 10
|
|
padus += int(val[i] - '0')
|
|
if dot {
|
|
unit /= 10
|
|
}
|
|
case '.':
|
|
if !dot {
|
|
dot = true
|
|
} else {
|
|
break loop
|
|
}
|
|
default:
|
|
break loop
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
if len(t.PadChar) > 0 {
|
|
time.Sleep(unit * time.Duration(padus))
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 (t *Terminfo) TGoto(col, row int) string {
|
|
return t.TParm(t.SetCursor, row, col)
|
|
}
|
|
|
|
// TColor returns a string corresponding to the given foreground and background
|
|
// colors. Either fg or bg can be set to -1 to elide.
|
|
func (t *Terminfo) TColor(fi, bi int) string {
|
|
rv := ""
|
|
// 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 t.Colors == 8 {
|
|
if fi > 7 && fi < 16 {
|
|
fi -= 8
|
|
}
|
|
if bi > 7 && bi < 16 {
|
|
bi -= 8
|
|
}
|
|
}
|
|
if t.Colors > fi && fi >= 0 {
|
|
rv += t.TParm(t.SetFg, fi)
|
|
}
|
|
if t.Colors > bi && bi >= 0 {
|
|
rv += t.TParm(t.SetBg, bi)
|
|
}
|
|
return rv
|
|
}
|
|
|
|
var (
|
|
dblock sync.Mutex
|
|
terminfos = make(map[string]*Terminfo)
|
|
aliases = make(map[string]string)
|
|
)
|
|
|
|
// AddTerminfo can be called to register a new Terminfo entry.
|
|
func AddTerminfo(t *Terminfo) {
|
|
dblock.Lock()
|
|
terminfos[t.Name] = t
|
|
for _, x := range t.Aliases {
|
|
terminfos[x] = t
|
|
}
|
|
dblock.Unlock()
|
|
}
|
|
|
|
// LookupTerminfo attempts to find a definition for the named $TERM.
|
|
func LookupTerminfo(name string) (*Terminfo, error) {
|
|
if name == "" {
|
|
// else on windows: index out of bounds
|
|
// on the name[0] reference below
|
|
return nil, ErrTermNotFound
|
|
}
|
|
|
|
addtruecolor := false
|
|
switch os.Getenv("COLORTERM") {
|
|
case "truecolor", "24bit", "24-bit":
|
|
addtruecolor = true
|
|
}
|
|
dblock.Lock()
|
|
t := terminfos[name]
|
|
dblock.Unlock()
|
|
|
|
// If the name ends in -truecolor, then fabricate an entry
|
|
// from the corresponding -256color, -color, or bare terminal.
|
|
if t != nil && t.TrueColor {
|
|
addtruecolor = true
|
|
} else if t == nil && strings.HasSuffix(name, "-truecolor") {
|
|
|
|
suffixes := []string{
|
|
"-256color",
|
|
"-88color",
|
|
"-color",
|
|
"",
|
|
}
|
|
base := name[:len(name)-len("-truecolor")]
|
|
for _, s := range suffixes {
|
|
if t, _ = LookupTerminfo(base + s); t != nil {
|
|
addtruecolor = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if t == nil {
|
|
return nil, ErrTermNotFound
|
|
}
|
|
|
|
switch os.Getenv("TCELL_TRUECOLOR") {
|
|
case "":
|
|
case "disable":
|
|
addtruecolor = false
|
|
default:
|
|
addtruecolor = 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 addtruecolor &&
|
|
t.SetFgBgRGB == "" &&
|
|
t.SetFgRGB == "" &&
|
|
t.SetBgRGB == "" {
|
|
|
|
// Supply vanilla ISO 8613-6:1994 24-bit color sequences.
|
|
t.SetFgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%dm"
|
|
t.SetBgRGB = "\x1b[48;2;%p1%d;%p2%d;%p3%dm"
|
|
t.SetFgBgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%d;" +
|
|
"48;2;%p4%d;%p5%d;%p6%dm"
|
|
}
|
|
|
|
return t, nil
|
|
}
|