123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227 |
- package terminal
- import (
- "bytes"
- "fmt"
- "io"
- "strconv"
- "strings"
- "syscall"
- "unsafe"
- "github.com/mattn/go-isatty"
- )
- var (
- cursorFunctions = map[rune]func(c *Cursor) func(int){
- 'A': func(c *Cursor) func(int) { return c.Up },
- 'B': func(c *Cursor) func(int) { return c.Down },
- 'C': func(c *Cursor) func(int) { return c.Forward },
- 'D': func(c *Cursor) func(int) { return c.Back },
- 'E': func(c *Cursor) func(int) { return c.NextLine },
- 'F': func(c *Cursor) func(int) { return c.PreviousLine },
- 'G': func(c *Cursor) func(int) { return c.HorizontalAbsolute },
- }
- )
- const (
- foregroundBlue = 0x1
- foregroundGreen = 0x2
- foregroundRed = 0x4
- foregroundIntensity = 0x8
- foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity)
- backgroundBlue = 0x10
- backgroundGreen = 0x20
- backgroundRed = 0x40
- backgroundIntensity = 0x80
- backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity)
- )
- type Writer struct {
- out FileWriter
- handle syscall.Handle
- orgAttr word
- }
- func NewAnsiStdout(out FileWriter) io.Writer {
- var csbi consoleScreenBufferInfo
- if !isatty.IsTerminal(out.Fd()) {
- return out
- }
- handle := syscall.Handle(out.Fd())
- procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
- return &Writer{out: out, handle: handle, orgAttr: csbi.attributes}
- }
- func NewAnsiStderr(out FileWriter) io.Writer {
- var csbi consoleScreenBufferInfo
- if !isatty.IsTerminal(out.Fd()) {
- return out
- }
- handle := syscall.Handle(out.Fd())
- procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
- return &Writer{out: out, handle: handle, orgAttr: csbi.attributes}
- }
- func (w *Writer) Write(data []byte) (n int, err error) {
- r := bytes.NewReader(data)
- for {
- ch, size, err := r.ReadRune()
- if err != nil {
- break
- }
- n += size
- switch ch {
- case '\x1b':
- size, err = w.handleEscape(r)
- n += size
- if err != nil {
- break
- }
- default:
- fmt.Fprint(w.out, string(ch))
- }
- }
- return
- }
- func (w *Writer) handleEscape(r *bytes.Reader) (n int, err error) {
- buf := make([]byte, 0, 10)
- buf = append(buf, "\x1b"...)
- // Check '[' continues after \x1b
- ch, size, err := r.ReadRune()
- if err != nil {
- fmt.Fprint(w.out, string(buf))
- return
- }
- n += size
- if ch != '[' {
- fmt.Fprint(w.out, string(buf))
- return
- }
- // Parse escape code
- var code rune
- argBuf := make([]byte, 0, 10)
- for {
- ch, size, err = r.ReadRune()
- if err != nil {
- fmt.Fprint(w.out, string(buf))
- return
- }
- n += size
- if ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') {
- code = ch
- break
- }
- argBuf = append(argBuf, string(ch)...)
- }
- w.applyEscapeCode(buf, string(argBuf), code)
- return
- }
- func (w *Writer) applyEscapeCode(buf []byte, arg string, code rune) {
- c := &Cursor{Out: w.out}
- switch arg + string(code) {
- case "?25h":
- c.Show()
- return
- case "?25l":
- c.Hide()
- return
- }
- if f, ok := cursorFunctions[code]; ok {
- if n, err := strconv.Atoi(arg); err == nil {
- f(c)(n)
- return
- }
- }
- switch code {
- case 'm':
- w.applySelectGraphicRendition(arg)
- default:
- buf = append(buf, string(code)...)
- fmt.Fprint(w.out, string(buf))
- }
- }
- // Original implementation: https://github.com/mattn/go-colorable
- func (w *Writer) applySelectGraphicRendition(arg string) {
- if arg == "" {
- procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.orgAttr))
- return
- }
- var csbi consoleScreenBufferInfo
- procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
- attr := csbi.attributes
- for _, param := range strings.Split(arg, ";") {
- n, err := strconv.Atoi(param)
- if err != nil {
- continue
- }
- switch {
- case n == 0 || n == 100:
- attr = w.orgAttr
- case 1 <= n && n <= 5:
- attr |= foregroundIntensity
- case 30 <= n && n <= 37:
- attr = (attr & backgroundMask)
- if (n-30)&1 != 0 {
- attr |= foregroundRed
- }
- if (n-30)&2 != 0 {
- attr |= foregroundGreen
- }
- if (n-30)&4 != 0 {
- attr |= foregroundBlue
- }
- case 40 <= n && n <= 47:
- attr = (attr & foregroundMask)
- if (n-40)&1 != 0 {
- attr |= backgroundRed
- }
- if (n-40)&2 != 0 {
- attr |= backgroundGreen
- }
- if (n-40)&4 != 0 {
- attr |= backgroundBlue
- }
- case 90 <= n && n <= 97:
- attr = (attr & backgroundMask)
- attr |= foregroundIntensity
- if (n-90)&1 != 0 {
- attr |= foregroundRed
- }
- if (n-90)&2 != 0 {
- attr |= foregroundGreen
- }
- if (n-90)&4 != 0 {
- attr |= foregroundBlue
- }
- case 100 <= n && n <= 107:
- attr = (attr & foregroundMask)
- attr |= backgroundIntensity
- if (n-100)&1 != 0 {
- attr |= backgroundRed
- }
- if (n-100)&2 != 0 {
- attr |= backgroundGreen
- }
- if (n-100)&4 != 0 {
- attr |= backgroundBlue
- }
- }
- }
- procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr))
- }
|