summary refs log tree commit diff
path: root/vendor/github.com/chzyer/readline/terminal.go
diff options
context:
space:
mode:
authorEmile <git@emile.space>2024-10-25 15:55:50 +0200
committerEmile <git@emile.space>2024-10-25 15:55:50 +0200
commitc90f36e3dd179d2de96f4f5fe38d8dc9a9de6dfe (patch)
tree89e9afb41c5bf76f48cfb09305a2d3db8d302b06 /vendor/github.com/chzyer/readline/terminal.go
parent98bbb0f559a8883bc47bae80607dbe326a448e61 (diff)
vendor HEAD main
Diffstat (limited to 'vendor/github.com/chzyer/readline/terminal.go')
-rw-r--r--vendor/github.com/chzyer/readline/terminal.go254
1 files changed, 254 insertions, 0 deletions
diff --git a/vendor/github.com/chzyer/readline/terminal.go b/vendor/github.com/chzyer/readline/terminal.go
new file mode 100644
index 0000000..38413d0
--- /dev/null
+++ b/vendor/github.com/chzyer/readline/terminal.go
@@ -0,0 +1,254 @@
+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
+		isEscapeSS3    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
+			} else if r == CharO {
+				// ^]O
+				expectNextChar = true
+				isEscapeSS3 = 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
+			}
+		} else if isEscapeSS3 {
+			isEscapeSS3 = false
+			if key := readEscKey(r, buf); key != nil {
+				r = escapeSS3Key(key)
+			}
+			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
+}