You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

238 lines
4.0 KiB

  1. package readline
  2. import (
  3. "bufio"
  4. "fmt"
  5. "io"
  6. "strings"
  7. "sync"
  8. "sync/atomic"
  9. )
  10. type Terminal struct {
  11. m sync.Mutex
  12. cfg *Config
  13. outchan chan rune
  14. closed int32
  15. stopChan chan struct{}
  16. kickChan chan struct{}
  17. wg sync.WaitGroup
  18. isReading int32
  19. sleeping int32
  20. sizeChan chan string
  21. }
  22. func NewTerminal(cfg *Config) (*Terminal, error) {
  23. if err := cfg.Init(); err != nil {
  24. return nil, err
  25. }
  26. t := &Terminal{
  27. cfg: cfg,
  28. kickChan: make(chan struct{}, 1),
  29. outchan: make(chan rune),
  30. stopChan: make(chan struct{}, 1),
  31. sizeChan: make(chan string, 1),
  32. }
  33. go t.ioloop()
  34. return t, nil
  35. }
  36. // SleepToResume will sleep myself, and return only if I'm resumed.
  37. func (t *Terminal) SleepToResume() {
  38. if !atomic.CompareAndSwapInt32(&t.sleeping, 0, 1) {
  39. return
  40. }
  41. defer atomic.StoreInt32(&t.sleeping, 0)
  42. t.ExitRawMode()
  43. ch := WaitForResume()
  44. SuspendMe()
  45. <-ch
  46. t.EnterRawMode()
  47. }
  48. func (t *Terminal) EnterRawMode() (err error) {
  49. return t.cfg.FuncMakeRaw()
  50. }
  51. func (t *Terminal) ExitRawMode() (err error) {
  52. return t.cfg.FuncExitRaw()
  53. }
  54. func (t *Terminal) Write(b []byte) (int, error) {
  55. return t.cfg.Stdout.Write(b)
  56. }
  57. // WriteStdin prefill the next Stdin fetch
  58. // Next time you call ReadLine() this value will be writen before the user input
  59. func (t *Terminal) WriteStdin(b []byte) (int, error) {
  60. return t.cfg.StdinWriter.Write(b)
  61. }
  62. type termSize struct {
  63. left int
  64. top int
  65. }
  66. func (t *Terminal) GetOffset(f func(offset string)) {
  67. go func() {
  68. f(<-t.sizeChan)
  69. }()
  70. t.Write([]byte("\033[6n"))
  71. }
  72. func (t *Terminal) Print(s string) {
  73. fmt.Fprintf(t.cfg.Stdout, "%s", s)
  74. }
  75. func (t *Terminal) PrintRune(r rune) {
  76. fmt.Fprintf(t.cfg.Stdout, "%c", r)
  77. }
  78. func (t *Terminal) Readline() *Operation {
  79. return NewOperation(t, t.cfg)
  80. }
  81. // return rune(0) if meet EOF
  82. func (t *Terminal) ReadRune() rune {
  83. ch, ok := <-t.outchan
  84. if !ok {
  85. return rune(0)
  86. }
  87. return ch
  88. }
  89. func (t *Terminal) IsReading() bool {
  90. return atomic.LoadInt32(&t.isReading) == 1
  91. }
  92. func (t *Terminal) KickRead() {
  93. select {
  94. case t.kickChan <- struct{}{}:
  95. default:
  96. }
  97. }
  98. func (t *Terminal) ioloop() {
  99. t.wg.Add(1)
  100. defer func() {
  101. t.wg.Done()
  102. close(t.outchan)
  103. }()
  104. var (
  105. isEscape bool
  106. isEscapeEx bool
  107. expectNextChar bool
  108. )
  109. buf := bufio.NewReader(t.getStdin())
  110. for {
  111. if !expectNextChar {
  112. atomic.StoreInt32(&t.isReading, 0)
  113. select {
  114. case <-t.kickChan:
  115. atomic.StoreInt32(&t.isReading, 1)
  116. case <-t.stopChan:
  117. return
  118. }
  119. }
  120. expectNextChar = false
  121. r, _, err := buf.ReadRune()
  122. if err != nil {
  123. if strings.Contains(err.Error(), "interrupted system call") {
  124. expectNextChar = true
  125. continue
  126. }
  127. break
  128. }
  129. if isEscape {
  130. isEscape = false
  131. if r == CharEscapeEx {
  132. expectNextChar = true
  133. isEscapeEx = true
  134. continue
  135. }
  136. r = escapeKey(r, buf)
  137. } else if isEscapeEx {
  138. isEscapeEx = false
  139. if key := readEscKey(r, buf); key != nil {
  140. r = escapeExKey(key)
  141. // offset
  142. if key.typ == 'R' {
  143. if _, _, ok := key.Get2(); ok {
  144. select {
  145. case t.sizeChan <- key.attr:
  146. default:
  147. }
  148. }
  149. expectNextChar = true
  150. continue
  151. }
  152. }
  153. if r == 0 {
  154. expectNextChar = true
  155. continue
  156. }
  157. }
  158. expectNextChar = true
  159. switch r {
  160. case CharEsc:
  161. if t.cfg.VimMode {
  162. t.outchan <- r
  163. break
  164. }
  165. isEscape = true
  166. case CharInterrupt, CharEnter, CharCtrlJ, CharDelete:
  167. expectNextChar = false
  168. fallthrough
  169. default:
  170. t.outchan <- r
  171. }
  172. }
  173. }
  174. func (t *Terminal) Bell() {
  175. fmt.Fprintf(t, "%c", CharBell)
  176. }
  177. func (t *Terminal) Close() error {
  178. if atomic.SwapInt32(&t.closed, 1) != 0 {
  179. return nil
  180. }
  181. if closer, ok := t.cfg.Stdin.(io.Closer); ok {
  182. closer.Close()
  183. }
  184. close(t.stopChan)
  185. t.wg.Wait()
  186. return t.ExitRawMode()
  187. }
  188. func (t *Terminal) GetConfig() *Config {
  189. t.m.Lock()
  190. cfg := *t.cfg
  191. t.m.Unlock()
  192. return &cfg
  193. }
  194. func (t *Terminal) getStdin() io.Reader {
  195. t.m.Lock()
  196. r := t.cfg.Stdin
  197. t.m.Unlock()
  198. return r
  199. }
  200. func (t *Terminal) SetConfig(c *Config) error {
  201. if err := c.Init(); err != nil {
  202. return err
  203. }
  204. t.m.Lock()
  205. t.cfg = c
  206. t.m.Unlock()
  207. return nil
  208. }