|
|
package readline
import ( "bufio" "fmt" "io" "strings" "sync" "sync/atomic" )
type Terminal struct { m sync.Mutex cfg *Config outchan chan rune closed int32 stopChan chan struct{} kickChan chan struct{} wg sync.WaitGroup isReading int32 sleeping int32
sizeChan chan string }
func NewTerminal(cfg *Config) (*Terminal, error) { if err := cfg.Init(); err != nil { return nil, err } t := &Terminal{ cfg: cfg, kickChan: make(chan struct{}, 1), outchan: make(chan rune), stopChan: make(chan struct{}, 1), sizeChan: make(chan string, 1), }
go t.ioloop() return t, nil }
// SleepToResume will sleep myself, and return only if I'm resumed.
func (t *Terminal) SleepToResume() { if !atomic.CompareAndSwapInt32(&t.sleeping, 0, 1) { return } defer atomic.StoreInt32(&t.sleeping, 0)
t.ExitRawMode() ch := WaitForResume() SuspendMe() <-ch t.EnterRawMode() }
func (t *Terminal) EnterRawMode() (err error) { return t.cfg.FuncMakeRaw() }
func (t *Terminal) ExitRawMode() (err error) { return t.cfg.FuncExitRaw() }
func (t *Terminal) Write(b []byte) (int, error) { return t.cfg.Stdout.Write(b) }
// WriteStdin prefill the next Stdin fetch
// Next time you call ReadLine() this value will be writen before the user input
func (t *Terminal) WriteStdin(b []byte) (int, error) { return t.cfg.StdinWriter.Write(b) }
type termSize struct { left int top int }
func (t *Terminal) GetOffset(f func(offset string)) { go func() { f(<-t.sizeChan) }() t.Write([]byte("\033[6n")) }
func (t *Terminal) Print(s string) { fmt.Fprintf(t.cfg.Stdout, "%s", s) }
func (t *Terminal) PrintRune(r rune) { fmt.Fprintf(t.cfg.Stdout, "%c", r) }
func (t *Terminal) Readline() *Operation { return NewOperation(t, t.cfg) }
// return rune(0) if meet EOF
func (t *Terminal) ReadRune() rune { ch, ok := <-t.outchan if !ok { return rune(0) } return ch }
func (t *Terminal) IsReading() bool { return atomic.LoadInt32(&t.isReading) == 1 }
func (t *Terminal) KickRead() { select { case t.kickChan <- struct{}{}: default: } }
func (t *Terminal) ioloop() { t.wg.Add(1) defer func() { t.wg.Done() close(t.outchan) }()
var ( isEscape bool isEscapeEx bool expectNextChar bool )
buf := bufio.NewReader(t.getStdin()) for { if !expectNextChar { atomic.StoreInt32(&t.isReading, 0) select { case <-t.kickChan: atomic.StoreInt32(&t.isReading, 1) case <-t.stopChan: return } } expectNextChar = false r, _, err := buf.ReadRune() if err != nil { if strings.Contains(err.Error(), "interrupted system call") { expectNextChar = true continue } break }
if isEscape { isEscape = false if r == CharEscapeEx { expectNextChar = true isEscapeEx = true continue } r = escapeKey(r, buf) } else if isEscapeEx { isEscapeEx = false if key := readEscKey(r, buf); key != nil { r = escapeExKey(key) // offset
if key.typ == 'R' { if _, _, ok := key.Get2(); ok { select { case t.sizeChan <- key.attr: default: } } expectNextChar = true continue } } if r == 0 { expectNextChar = true continue } }
expectNextChar = true switch r { case CharEsc: if t.cfg.VimMode { t.outchan <- r break } isEscape = true case CharInterrupt, CharEnter, CharCtrlJ, CharDelete: expectNextChar = false fallthrough default: t.outchan <- r } }
}
func (t *Terminal) Bell() { fmt.Fprintf(t, "%c", CharBell) }
func (t *Terminal) Close() error { if atomic.SwapInt32(&t.closed, 1) != 0 { return nil } if closer, ok := t.cfg.Stdin.(io.Closer); ok { closer.Close() } close(t.stopChan) t.wg.Wait() return t.ExitRawMode() }
func (t *Terminal) GetConfig() *Config { t.m.Lock() cfg := *t.cfg t.m.Unlock() return &cfg }
func (t *Terminal) getStdin() io.Reader { t.m.Lock() r := t.cfg.Stdin t.m.Unlock() return r }
func (t *Terminal) SetConfig(c *Config) error { if err := c.Init(); err != nil { return err } t.m.Lock() t.cfg = c t.m.Unlock() return nil }
|