1// Copyright 2019 The TCell Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use file except in compliance with the License. 5// You may obtain a copy of the license at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package terminfo 16 17import ( 18 "bytes" 19 "compress/gzip" 20 "crypto/sha1" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "io" 25 "os" 26 "path" 27 "path/filepath" 28 "strconv" 29 "strings" 30 "sync" 31) 32 33var ( 34 // ErrTermNotFound indicates that a suitable terminal entry could 35 // not be found. This can result from either not having TERM set, 36 // or from the TERM failing to support certain minimal functionality, 37 // in particular absolute cursor addressability (the cup capability) 38 // is required. For example, legacy "adm3" lacks this capability, 39 // whereas the slightly newer "adm3a" supports it. This failure 40 // occurs most often with "dumb". 41 ErrTermNotFound = errors.New("terminal entry not found") 42) 43 44// Terminfo represents a terminfo entry. Note that we use friendly names 45// in Go, but when we write out JSON, we use the same names as terminfo. 46// The name, aliases and smous, rmous fields do not come from terminfo directly. 47type Terminfo struct { 48 Name string `json:"name"` 49 Aliases []string `json:"aliases,omitempty"` 50 Columns int `json:"cols,omitempty"` // cols 51 Lines int `json:"lines,omitempty"` // lines 52 Colors int `json:"colors,omitempty"` // colors 53 Bell string `json:"bell,omitempty"` // bell 54 Clear string `json:"clear,omitempty"` // clear 55 EnterCA string `json:"smcup,omitempty"` // smcup 56 ExitCA string `json:"rmcup,omitempty"` // rmcup 57 ShowCursor string `json:"cnorm,omitempty"` // cnorm 58 HideCursor string `json:"civis,omitempty"` // civis 59 AttrOff string `json:"sgr0,omitempty"` // sgr0 60 Underline string `json:"smul,omitempty"` // smul 61 Bold string `json:"bold,omitempty"` // bold 62 Blink string `json:"blink,omitempty"` // blink 63 Reverse string `json:"rev,omitempty"` // rev 64 Dim string `json:"dim,omitempty"` // dim 65 EnterKeypad string `json:"smkx,omitempty"` // smkx 66 ExitKeypad string `json:"rmkx,omitempty"` // rmkx 67 SetFg string `json:"setaf,omitempty"` // setaf 68 SetBg string `json:"setbg,omitempty"` // setab 69 SetCursor string `json:"cup,omitempty"` // cup 70 CursorBack1 string `json:"cub1,omitempty"` // cub1 71 CursorUp1 string `json:"cuu1,omitempty"` // cuu1 72 PadChar string `json:"pad,omitempty"` // pad 73 KeyBackspace string `json:"kbs,omitempty"` // kbs 74 KeyF1 string `json:"kf1,omitempty"` // kf1 75 KeyF2 string `json:"kf2,omitempty"` // kf2 76 KeyF3 string `json:"kf3,omitempty"` // kf3 77 KeyF4 string `json:"kf4,omitempty"` // kf4 78 KeyF5 string `json:"kf5,omitempty"` // kf5 79 KeyF6 string `json:"kf6,omitempty"` // kf6 80 KeyF7 string `json:"kf7,omitempty"` // kf7 81 KeyF8 string `json:"kf8,omitempty"` // kf8 82 KeyF9 string `json:"kf9,omitempty"` // kf9 83 KeyF10 string `json:"kf10,omitempty"` // kf10 84 KeyF11 string `json:"kf11,omitempty"` // kf11 85 KeyF12 string `json:"kf12,omitempty"` // kf12 86 KeyF13 string `json:"kf13,omitempty"` // kf13 87 KeyF14 string `json:"kf14,omitempty"` // kf14 88 KeyF15 string `json:"kf15,omitempty"` // kf15 89 KeyF16 string `json:"kf16,omitempty"` // kf16 90 KeyF17 string `json:"kf17,omitempty"` // kf17 91 KeyF18 string `json:"kf18,omitempty"` // kf18 92 KeyF19 string `json:"kf19,omitempty"` // kf19 93 KeyF20 string `json:"kf20,omitempty"` // kf20 94 KeyF21 string `json:"kf21,omitempty"` // kf21 95 KeyF22 string `json:"kf22,omitempty"` // kf22 96 KeyF23 string `json:"kf23,omitempty"` // kf23 97 KeyF24 string `json:"kf24,omitempty"` // kf24 98 KeyF25 string `json:"kf25,omitempty"` // kf25 99 KeyF26 string `json:"kf26,omitempty"` // kf26 100 KeyF27 string `json:"kf27,omitempty"` // kf27 101 KeyF28 string `json:"kf28,omitempty"` // kf28 102 KeyF29 string `json:"kf29,omitempty"` // kf29 103 KeyF30 string `json:"kf30,omitempty"` // kf30 104 KeyF31 string `json:"kf31,omitempty"` // kf31 105 KeyF32 string `json:"kf32,omitempty"` // kf32 106 KeyF33 string `json:"kf33,omitempty"` // kf33 107 KeyF34 string `json:"kf34,omitempty"` // kf34 108 KeyF35 string `json:"kf35,omitempty"` // kf35 109 KeyF36 string `json:"kf36,omitempty"` // kf36 110 KeyF37 string `json:"kf37,omitempty"` // kf37 111 KeyF38 string `json:"kf38,omitempty"` // kf38 112 KeyF39 string `json:"kf39,omitempty"` // kf39 113 KeyF40 string `json:"kf40,omitempty"` // kf40 114 KeyF41 string `json:"kf41,omitempty"` // kf41 115 KeyF42 string `json:"kf42,omitempty"` // kf42 116 KeyF43 string `json:"kf43,omitempty"` // kf43 117 KeyF44 string `json:"kf44,omitempty"` // kf44 118 KeyF45 string `json:"kf45,omitempty"` // kf45 119 KeyF46 string `json:"kf46,omitempty"` // kf46 120 KeyF47 string `json:"kf47,omitempty"` // kf47 121 KeyF48 string `json:"kf48,omitempty"` // kf48 122 KeyF49 string `json:"kf49,omitempty"` // kf49 123 KeyF50 string `json:"kf50,omitempty"` // kf50 124 KeyF51 string `json:"kf51,omitempty"` // kf51 125 KeyF52 string `json:"kf52,omitempty"` // kf52 126 KeyF53 string `json:"kf53,omitempty"` // kf53 127 KeyF54 string `json:"kf54,omitempty"` // kf54 128 KeyF55 string `json:"kf55,omitempty"` // kf55 129 KeyF56 string `json:"kf56,omitempty"` // kf56 130 KeyF57 string `json:"kf57,omitempty"` // kf57 131 KeyF58 string `json:"kf58,omitempty"` // kf58 132 KeyF59 string `json:"kf59,omitempty"` // kf59 133 KeyF60 string `json:"kf60,omitempty"` // kf60 134 KeyF61 string `json:"kf61,omitempty"` // kf61 135 KeyF62 string `json:"kf62,omitempty"` // kf62 136 KeyF63 string `json:"kf63,omitempty"` // kf63 137 KeyF64 string `json:"kf64,omitempty"` // kf64 138 KeyInsert string `json:"kich,omitempty"` // kich1 139 KeyDelete string `json:"kdch,omitempty"` // kdch1 140 KeyHome string `json:"khome,omitempty"` // khome 141 KeyEnd string `json:"kend,omitempty"` // kend 142 KeyHelp string `json:"khlp,omitempty"` // khlp 143 KeyPgUp string `json:"kpp,omitempty"` // kpp 144 KeyPgDn string `json:"knp,omitempty"` // knp 145 KeyUp string `json:"kcuu1,omitempty"` // kcuu1 146 KeyDown string `json:"kcud1,omitempty"` // kcud1 147 KeyLeft string `json:"kcub1,omitempty"` // kcub1 148 KeyRight string `json:"kcuf1,omitempty"` // kcuf1 149 KeyBacktab string `json:"kcbt,omitempty"` // kcbt 150 KeyExit string `json:"kext,omitempty"` // kext 151 KeyClear string `json:"kclr,omitempty"` // kclr 152 KeyPrint string `json:"kprt,omitempty"` // kprt 153 KeyCancel string `json:"kcan,omitempty"` // kcan 154 Mouse string `json:"kmous,omitempty"` // kmous 155 MouseMode string `json:"XM,omitempty"` // XM 156 AltChars string `json:"acsc,omitempty"` // acsc 157 EnterAcs string `json:"smacs,omitempty"` // smacs 158 ExitAcs string `json:"rmacs,omitempty"` // rmacs 159 EnableAcs string `json:"enacs,omitempty"` // enacs 160 KeyShfRight string `json:"kRIT,omitempty"` // kRIT 161 KeyShfLeft string `json:"kLFT,omitempty"` // kLFT 162 KeyShfHome string `json:"kHOM,omitempty"` // kHOM 163 KeyShfEnd string `json:"kEND,omitempty"` // kEND 164 165 // These are non-standard extensions to terminfo. This includes 166 // true color support, and some additional keys. Its kind of bizarre 167 // that shifted variants of left and right exist, but not up and down. 168 // Terminal support for these are going to vary amongst XTerm 169 // emulations, so don't depend too much on them in your application. 170 171 SetFgBg string `json:"_setfgbg,omitempty"` // setfgbg 172 SetFgBgRGB string `json:"_setfgbgrgb,omitempty"` // setfgbgrgb 173 SetFgRGB string `json:"_setfrgb,omitempty"` // setfrgb 174 SetBgRGB string `json:"_setbrgb,omitempty"` // setbrgb 175 KeyShfUp string `json:"_kscu1,omitempty"` // shift-up 176 KeyShfDown string `json:"_kscud1,omitempty"` // shift-down 177 KeyCtrlUp string `json:"_kccu1,omitempty"` // ctrl-up 178 KeyCtrlDown string `json:"_kccud1,omitempty"` // ctrl-left 179 KeyCtrlRight string `json:"_kccuf1,omitempty"` // ctrl-right 180 KeyCtrlLeft string `json:"_kccub1,omitempty"` // ctrl-left 181 KeyMetaUp string `json:"_kmcu1,omitempty"` // meta-up 182 KeyMetaDown string `json:"_kmcud1,omitempty"` // meta-left 183 KeyMetaRight string `json:"_kmcuf1,omitempty"` // meta-right 184 KeyMetaLeft string `json:"_kmcub1,omitempty"` // meta-left 185 KeyAltUp string `json:"_kacu1,omitempty"` // alt-up 186 KeyAltDown string `json:"_kacud1,omitempty"` // alt-left 187 KeyAltRight string `json:"_kacuf1,omitempty"` // alt-right 188 KeyAltLeft string `json:"_kacub1,omitempty"` // alt-left 189 KeyCtrlHome string `json:"_kchome,omitempty"` 190 KeyCtrlEnd string `json:"_kcend,omitempty"` 191 KeyMetaHome string `json:"_kmhome,omitempty"` 192 KeyMetaEnd string `json:"_kmend,omitempty"` 193 KeyAltHome string `json:"_kahome,omitempty"` 194 KeyAltEnd string `json:"_kaend,omitempty"` 195 KeyAltShfUp string `json:"_kascu1,omitempty"` 196 KeyAltShfDown string `json:"_kascud1,omitempty"` 197 KeyAltShfLeft string `json:"_kascub1,omitempty"` 198 KeyAltShfRight string `json:"_kascuf1,omitempty"` 199 KeyMetaShfUp string `json:"_kmscu1,omitempty"` 200 KeyMetaShfDown string `json:"_kmscud1,omitempty"` 201 KeyMetaShfLeft string `json:"_kmscub1,omitempty"` 202 KeyMetaShfRight string `json:"_kmscuf1,omitempty"` 203 KeyCtrlShfUp string `json:"_kcscu1,omitempty"` 204 KeyCtrlShfDown string `json:"_kcscud1,omitempty"` 205 KeyCtrlShfLeft string `json:"_kcscub1,omitempty"` 206 KeyCtrlShfRight string `json:"_kcscuf1,omitempty"` 207 KeyCtrlShfHome string `json:"_kcHOME,omitempty"` 208 KeyCtrlShfEnd string `json:"_kcEND,omitempty"` 209 KeyAltShfHome string `json:"_kaHOME,omitempty"` 210 KeyAltShfEnd string `json:"_kaEND,omitempty"` 211 KeyMetaShfHome string `json:"_kmHOME,omitempty"` 212 KeyMetaShfEnd string `json:"_kmEND,omitempty"` 213} 214 215type stackElem struct { 216 s string 217 i int 218 isStr bool 219 isInt bool 220} 221 222type stack []stackElem 223 224func (st stack) Push(v string) stack { 225 e := stackElem{ 226 s: v, 227 isStr: true, 228 } 229 return append(st, e) 230} 231 232func (st stack) Pop() (string, stack) { 233 v := "" 234 if len(st) > 0 { 235 e := st[len(st)-1] 236 st = st[:len(st)-1] 237 if e.isStr { 238 v = e.s 239 } else { 240 v = strconv.Itoa(e.i) 241 } 242 } 243 return v, st 244} 245 246func (st stack) PopInt() (int, stack) { 247 if len(st) > 0 { 248 e := st[len(st)-1] 249 st = st[:len(st)-1] 250 if e.isInt { 251 return e.i, st 252 } else if e.isStr { 253 i, _ := strconv.Atoi(e.s) 254 return i, st 255 } 256 } 257 return 0, st 258} 259 260func (st stack) PopBool() (bool, stack) { 261 if len(st) > 0 { 262 e := st[len(st)-1] 263 st = st[:len(st)-1] 264 if e.isStr { 265 if e.s == "1" { 266 return true, st 267 } 268 return false, st 269 } else if e.i == 1 { 270 return true, st 271 } else { 272 return false, st 273 } 274 } 275 return false, st 276} 277 278func (st stack) PushInt(i int) stack { 279 e := stackElem{ 280 i: i, 281 isInt: true, 282 } 283 return append(st, e) 284} 285 286func (st stack) PushBool(i bool) stack { 287 if i { 288 return st.PushInt(1) 289 } 290 return st.PushInt(0) 291} 292 293func nextch(s string, index int) (byte, int) { 294 if index < len(s) { 295 return s[index], index + 1 296 } 297 return 0, index 298} 299 300// static vars 301var svars [26]string 302 303// paramsBuffer handles some persistent state for TParam. Technically we 304// could probably dispense with this, but caching buffer arrays gives us 305// a nice little performance boost. Furthermore, we know that TParam is 306// rarely (never?) called re-entrantly, so we can just reuse the same 307// buffers, making it thread-safe by stashing a lock. 308type paramsBuffer struct { 309 out bytes.Buffer 310 buf bytes.Buffer 311 lk sync.Mutex 312} 313 314// Start initializes the params buffer with the initial string data. 315// It also locks the paramsBuffer. The caller must call End() when 316// finished. 317func (pb *paramsBuffer) Start(s string) { 318 pb.lk.Lock() 319 pb.out.Reset() 320 pb.buf.Reset() 321 pb.buf.WriteString(s) 322} 323 324// End returns the final output from TParam, but it also releases the lock. 325func (pb *paramsBuffer) End() string { 326 s := pb.out.String() 327 pb.lk.Unlock() 328 return s 329} 330 331// NextCh returns the next input character to the expander. 332func (pb *paramsBuffer) NextCh() (byte, error) { 333 return pb.buf.ReadByte() 334} 335 336// PutCh "emits" (rather schedules for output) a single byte character. 337func (pb *paramsBuffer) PutCh(ch byte) { 338 pb.out.WriteByte(ch) 339} 340 341// PutString schedules a string for output. 342func (pb *paramsBuffer) PutString(s string) { 343 pb.out.WriteString(s) 344} 345 346var pb = ¶msBuffer{} 347 348// TParm takes a terminfo parameterized string, such as setaf or cup, and 349// evaluates the string, and returns the result with the parameter 350// applied. 351func (t *Terminfo) TParm(s string, p ...int) string { 352 var stk stack 353 var a, b string 354 var ai, bi int 355 var ab bool 356 var dvars [26]string 357 var params [9]int 358 359 pb.Start(s) 360 361 // make sure we always have 9 parameters -- makes it easier 362 // later to skip checks 363 for i := 0; i < len(params) && i < len(p); i++ { 364 params[i] = p[i] 365 } 366 367 nest := 0 368 369 for { 370 371 ch, err := pb.NextCh() 372 if err != nil { 373 break 374 } 375 376 if ch != '%' { 377 pb.PutCh(ch) 378 continue 379 } 380 381 ch, err = pb.NextCh() 382 if err != nil { 383 // XXX Error 384 break 385 } 386 387 switch ch { 388 case '%': // quoted % 389 pb.PutCh(ch) 390 391 case 'i': // increment both parameters (ANSI cup support) 392 params[0]++ 393 params[1]++ 394 395 case 'c', 's': 396 // NB: these, and 'd' below are special cased for 397 // efficiency. They could be handled by the richer 398 // format support below, less efficiently. 399 a, stk = stk.Pop() 400 pb.PutString(a) 401 402 case 'd': 403 ai, stk = stk.PopInt() 404 pb.PutString(strconv.Itoa(ai)) 405 406 case '0', '1', '2', '3', '4', 'x', 'X', 'o', ':': 407 // This is pretty suboptimal, but this is rarely used. 408 // None of the mainstream terminals use any of this, 409 // and it would surprise me if this code is ever 410 // executed outside of test cases. 411 f := "%" 412 if ch == ':' { 413 ch, _ = pb.NextCh() 414 } 415 f += string(ch) 416 for ch == '+' || ch == '-' || ch == '#' || ch == ' ' { 417 ch, _ = pb.NextCh() 418 f += string(ch) 419 } 420 for (ch >= '0' && ch <= '9') || ch == '.' { 421 ch, _ = pb.NextCh() 422 f += string(ch) 423 } 424 switch ch { 425 case 'd', 'x', 'X', 'o': 426 ai, stk = stk.PopInt() 427 pb.PutString(fmt.Sprintf(f, ai)) 428 case 'c', 's': 429 a, stk = stk.Pop() 430 pb.PutString(fmt.Sprintf(f, a)) 431 } 432 433 case 'p': // push parameter 434 ch, _ = pb.NextCh() 435 ai = int(ch - '1') 436 if ai >= 0 && ai < len(params) { 437 stk = stk.PushInt(params[ai]) 438 } else { 439 stk = stk.PushInt(0) 440 } 441 442 case 'P': // pop & store variable 443 ch, _ = pb.NextCh() 444 if ch >= 'A' && ch <= 'Z' { 445 svars[int(ch-'A')], stk = stk.Pop() 446 } else if ch >= 'a' && ch <= 'z' { 447 dvars[int(ch-'a')], stk = stk.Pop() 448 } 449 450 case 'g': // recall & push variable 451 ch, _ = pb.NextCh() 452 if ch >= 'A' && ch <= 'Z' { 453 stk = stk.Push(svars[int(ch-'A')]) 454 } else if ch >= 'a' && ch <= 'z' { 455 stk = stk.Push(dvars[int(ch-'a')]) 456 } 457 458 case '\'': // push(char) 459 ch, _ = pb.NextCh() 460 pb.NextCh() // must be ' but we don't check 461 stk = stk.Push(string(ch)) 462 463 case '{': // push(int) 464 ai = 0 465 ch, _ = pb.NextCh() 466 for ch >= '0' && ch <= '9' { 467 ai *= 10 468 ai += int(ch - '0') 469 ch, _ = pb.NextCh() 470 } 471 // ch must be '}' but no verification 472 stk = stk.PushInt(ai) 473 474 case 'l': // push(strlen(pop)) 475 a, stk = stk.Pop() 476 stk = stk.PushInt(len(a)) 477 478 case '+': 479 bi, stk = stk.PopInt() 480 ai, stk = stk.PopInt() 481 stk = stk.PushInt(ai + bi) 482 483 case '-': 484 bi, stk = stk.PopInt() 485 ai, stk = stk.PopInt() 486 stk = stk.PushInt(ai - bi) 487 488 case '*': 489 bi, stk = stk.PopInt() 490 ai, stk = stk.PopInt() 491 stk = stk.PushInt(ai * bi) 492 493 case '/': 494 bi, stk = stk.PopInt() 495 ai, stk = stk.PopInt() 496 if bi != 0 { 497 stk = stk.PushInt(ai / bi) 498 } else { 499 stk = stk.PushInt(0) 500 } 501 502 case 'm': // push(pop mod pop) 503 bi, stk = stk.PopInt() 504 ai, stk = stk.PopInt() 505 if bi != 0 { 506 stk = stk.PushInt(ai % bi) 507 } else { 508 stk = stk.PushInt(0) 509 } 510 511 case '&': // AND 512 bi, stk = stk.PopInt() 513 ai, stk = stk.PopInt() 514 stk = stk.PushInt(ai & bi) 515 516 case '|': // OR 517 bi, stk = stk.PopInt() 518 ai, stk = stk.PopInt() 519 stk = stk.PushInt(ai | bi) 520 521 case '^': // XOR 522 bi, stk = stk.PopInt() 523 ai, stk = stk.PopInt() 524 stk = stk.PushInt(ai ^ bi) 525 526 case '~': // bit complement 527 ai, stk = stk.PopInt() 528 stk = stk.PushInt(ai ^ -1) 529 530 case '!': // logical NOT 531 ai, stk = stk.PopInt() 532 stk = stk.PushBool(ai != 0) 533 534 case '=': // numeric compare or string compare 535 b, stk = stk.Pop() 536 a, stk = stk.Pop() 537 stk = stk.PushBool(a == b) 538 539 case '>': // greater than, numeric 540 bi, stk = stk.PopInt() 541 ai, stk = stk.PopInt() 542 stk = stk.PushBool(ai > bi) 543 544 case '<': // less than, numeric 545 bi, stk = stk.PopInt() 546 ai, stk = stk.PopInt() 547 stk = stk.PushBool(ai < bi) 548 549 case '?': // start conditional 550 551 case 't': 552 ab, stk = stk.PopBool() 553 if ab { 554 // just keep going 555 break 556 } 557 nest = 0 558 ifloop: 559 // this loop consumes everything until we hit our else, 560 // or the end of the conditional 561 for { 562 ch, err = pb.NextCh() 563 if err != nil { 564 break 565 } 566 if ch != '%' { 567 continue 568 } 569 ch, _ = pb.NextCh() 570 switch ch { 571 case ';': 572 if nest == 0 { 573 break ifloop 574 } 575 nest-- 576 case '?': 577 nest++ 578 case 'e': 579 if nest == 0 { 580 break ifloop 581 } 582 } 583 } 584 585 case 'e': 586 // if we got here, it means we didn't use the else 587 // in the 't' case above, and we should skip until 588 // the end of the conditional 589 nest = 0 590 elloop: 591 for { 592 ch, err = pb.NextCh() 593 if err != nil { 594 break 595 } 596 if ch != '%' { 597 continue 598 } 599 ch, _ = pb.NextCh() 600 switch ch { 601 case ';': 602 if nest == 0 { 603 break elloop 604 } 605 nest-- 606 case '?': 607 nest++ 608 } 609 } 610 611 case ';': // endif 612 613 } 614 } 615 616 return pb.End() 617} 618 619// TPuts emits the string to the writer, but expands inline padding 620// indications (of the form $<[delay]> where [delay] is msec) to 621// a suitable number of padding characters (usually null bytes) based 622// upon the supplied baud. At high baud rates, more padding characters 623// will be inserted. All Terminfo based strings should be emitted using 624// this function. 625func (t *Terminfo) TPuts(w io.Writer, s string, baud int) { 626 for { 627 beg := strings.Index(s, "$<") 628 if beg < 0 { 629 // Most strings don't need padding, which is good news! 630 io.WriteString(w, s) 631 return 632 } 633 io.WriteString(w, s[:beg]) 634 s = s[beg+2:] 635 end := strings.Index(s, ">") 636 if end < 0 { 637 // unterminated.. just emit bytes unadulterated 638 io.WriteString(w, "$<"+s) 639 return 640 } 641 val := s[:end] 642 s = s[end+1:] 643 padus := 0 644 unit := 1000 645 dot := false 646 loop: 647 for i := range val { 648 switch val[i] { 649 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 650 padus *= 10 651 padus += int(val[i] - '0') 652 if dot { 653 unit *= 10 654 } 655 case '.': 656 if !dot { 657 dot = true 658 } else { 659 break loop 660 } 661 default: 662 break loop 663 } 664 } 665 cnt := int(((baud / 8) * padus) / unit) 666 for cnt > 0 { 667 io.WriteString(w, t.PadChar) 668 cnt-- 669 } 670 } 671} 672 673// TGoto returns a string suitable for addressing the cursor at the given 674// row and column. The origin 0, 0 is in the upper left corner of the screen. 675func (t *Terminfo) TGoto(col, row int) string { 676 return t.TParm(t.SetCursor, row, col) 677} 678 679// TColor returns a string corresponding to the given foreground and background 680// colors. Either fg or bg can be set to -1 to elide. 681func (t *Terminfo) TColor(fi, bi int) string { 682 rv := "" 683 // As a special case, we map bright colors to lower versions if the 684 // color table only holds 8. For the remaining 240 colors, the user 685 // is out of luck. Someday we could create a mapping table, but its 686 // not worth it. 687 if t.Colors == 8 { 688 if fi > 7 && fi < 16 { 689 fi -= 8 690 } 691 if bi > 7 && bi < 16 { 692 bi -= 8 693 } 694 } 695 if t.Colors > fi && fi >= 0 { 696 rv += t.TParm(t.SetFg, fi) 697 } 698 if t.Colors > bi && bi >= 0 { 699 rv += t.TParm(t.SetBg, bi) 700 } 701 return rv 702} 703 704var ( 705 dblock sync.Mutex 706 terminfos = make(map[string]*Terminfo) 707 aliases = make(map[string]string) 708) 709 710// AddTerminfo can be called to register a new Terminfo entry. 711func AddTerminfo(t *Terminfo) { 712 dblock.Lock() 713 terminfos[t.Name] = t 714 for _, x := range t.Aliases { 715 terminfos[x] = t 716 } 717 dblock.Unlock() 718} 719 720func loadFromFile(fname string, term string) (*Terminfo, error) { 721 var e error 722 var f io.Reader 723 if f, e = os.Open(fname); e != nil { 724 return nil, e 725 } 726 if strings.HasSuffix(fname, ".gz") { 727 if f, e = gzip.NewReader(f); e != nil { 728 return nil, e 729 } 730 } 731 d := json.NewDecoder(f) 732 for { 733 t := &Terminfo{} 734 if e := d.Decode(t); e != nil { 735 if e == io.EOF { 736 return nil, ErrTermNotFound 737 } 738 return nil, e 739 } 740 if t.SetCursor == "" { 741 // This must be an alias record, return it. 742 return t, nil 743 } 744 if t.Name == term { 745 return t, nil 746 } 747 for _, a := range t.Aliases { 748 if a == term { 749 return t, nil 750 } 751 } 752 } 753} 754 755// LookupTerminfo attempts to find a definition for the named $TERM. 756// It first looks in the builtin database, which should cover just about 757// everyone. If it can't find one there, then it will attempt to read 758// one from the JSON file located in either $TCELLDB, $HOME/.tcelldb, 759// or as a database file. 760// 761// The database files are named by taking terminal name, hashing it through 762// sha1, and then a subdirectory of the form database/hash[0:2]/hash[0:8] 763// (with an optional .gz extension). 764// 765// For other local database files, we will look for the database file using 766// the terminal name, so database/term[0:2]/term[0:8], again with optional 767// .gz extension. 768func LookupTerminfo(name string) (*Terminfo, error) { 769 if name == "" { 770 // else on windows: index out of bounds 771 // on the name[0] reference below 772 return nil, ErrTermNotFound 773 } 774 775 addtruecolor := false 776 switch os.Getenv("COLORTERM") { 777 case "truecolor", "24bit", "24-bit": 778 addtruecolor = true 779 } 780 dblock.Lock() 781 t := terminfos[name] 782 dblock.Unlock() 783 784 if t == nil { 785 786 var files []string 787 letter := fmt.Sprintf("%02x", name[0]) 788 gzfile := path.Join(letter, name+".gz") 789 jsfile := path.Join(letter, name) 790 hash := fmt.Sprintf("%x", sha1.Sum([]byte(name))) 791 gzhfile := path.Join(hash[0:2], hash[0:8]+".gz") 792 jshfile := path.Join(hash[0:2], hash[0:8]) 793 794 // Build up the search path. Old versions of tcell used a 795 // single database file, whereas the new ones locate them 796 // in JSON (optionally compressed) files. 797 // 798 // The search path for "xterm" (SHA1 sig e2e28a8e...) looks 799 // like this: 800 // 801 // $TCELLDB/78/xterm.gz 802 // $TCELLDB/78/xterm 803 // $TCELLDB 804 // $HOME/.tcelldb/e2/e2e28a8e.gz 805 // $HOME/.tcelldb/e2/e2e28a8e 806 // $HOME/.tcelldb/78/xterm.gz 807 // $HOME/.tcelldb/78/xterm 808 // $HOME/.tcelldb 809 // $GOPATH/terminfo/database/e2/e2e28a8e.gz 810 // $GOPATH/terminfo/database/e2/e2e28a8e 811 // $GOPATH/terminfo/database/78/xterm.gz 812 // $GOPATH/terminfo/database/78/xterm 813 // 814 // Note that the legacy name lookups (78/xterm etc.) are 815 // provided for compatibility. We do not actually deliver 816 // any files with this style of naming, to avoid collisions 817 // on case insensitive filesystems. (*cough* mac *cough*). 818 819 // If $GOPATH set, honor it, else assume $HOME/go just like 820 // modern golang does. 821 gopath := os.Getenv("GOPATH") 822 if gopath == "" { 823 gopath = path.Join(os.Getenv("HOME"), "go") 824 } 825 if pth := os.Getenv("TCELLDB"); pth != "" { 826 files = append(files, 827 path.Join(pth, gzfile), 828 path.Join(pth, jsfile), 829 pth) 830 } 831 if pth := os.Getenv("HOME"); pth != "" { 832 pth = path.Join(pth, ".tcelldb") 833 files = append(files, 834 path.Join(pth, gzhfile), 835 path.Join(pth, jshfile), 836 path.Join(pth, gzfile), 837 path.Join(pth, jsfile), 838 pth) 839 } 840 841 for _, pth := range filepath.SplitList(gopath) { 842 pth = path.Join(pth, "src", "github.com", 843 "gdamore", "tcell", "terminfo", "database") 844 files = append(files, 845 path.Join(pth, gzhfile), 846 path.Join(pth, jshfile), 847 path.Join(pth, gzfile), 848 path.Join(pth, jsfile)) 849 } 850 851 for _, fname := range files { 852 t, _ = loadFromFile(fname, name) 853 if t != nil { 854 break 855 } 856 } 857 if t != nil { 858 if t.Name != name { 859 // Check for a database loop (no infinite 860 // recursion). 861 dblock.Lock() 862 if aliases[name] != "" { 863 dblock.Unlock() 864 return nil, ErrTermNotFound 865 } 866 aliases[name] = t.Name 867 dblock.Unlock() 868 return LookupTerminfo(t.Name) 869 } 870 dblock.Lock() 871 terminfos[name] = t 872 dblock.Unlock() 873 } 874 } 875 876 // If the name ends in -truecolor, then fabricate an entry 877 // from the corresponding -256color, -color, or bare terminal. 878 if t == nil && strings.HasSuffix(name, "-truecolor") { 879 880 suffixes := []string{ 881 "-256color", 882 "-88color", 883 "-color", 884 "", 885 } 886 base := name[:len(name)-len("-truecolor")] 887 for _, s := range suffixes { 888 if t, _ = LookupTerminfo(base + s); t != nil { 889 addtruecolor = true 890 break 891 } 892 } 893 } 894 895 if t == nil { 896 return nil, ErrTermNotFound 897 } 898 899 switch os.Getenv("TCELL_TRUECOLOR") { 900 case "": 901 case "disable": 902 addtruecolor = false 903 default: 904 addtruecolor = true 905 } 906 907 // If the user has requested 24-bit color with $COLORTERM, then 908 // amend the value (unless already present). This means we don't 909 // need to have a value present. 910 if addtruecolor && 911 t.SetFgBgRGB == "" && 912 t.SetFgRGB == "" && 913 t.SetBgRGB == "" { 914 915 // Supply vanilla ISO 8613-6:1994 24-bit color sequences. 916 t.SetFgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%dm" 917 t.SetBgRGB = "\x1b[48;2;%p1%d;%p2%d;%p3%dm" 918 t.SetFgBgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%d;" + 919 "48;2;%p4%d;%p5%d;%p6%dm" 920 } 921 922 return t, nil 923} 924