readline.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. package readline
  2. import (
  3. "bufio"
  4. "fmt"
  5. "io"
  6. "os"
  7. "syscall"
  8. )
  9. type Prompt struct {
  10. Prompt string
  11. AltPrompt string
  12. Placeholder string
  13. AltPlaceholder string
  14. UseAlt bool
  15. }
  16. func (p *Prompt) prompt() string {
  17. if p.UseAlt {
  18. return p.AltPrompt
  19. }
  20. return p.Prompt
  21. }
  22. func (p *Prompt) placeholder() string {
  23. if p.UseAlt {
  24. return p.AltPlaceholder
  25. }
  26. return p.Placeholder
  27. }
  28. type Terminal struct {
  29. outchan chan rune
  30. }
  31. type Instance struct {
  32. Prompt *Prompt
  33. Terminal *Terminal
  34. History *History
  35. Pasting bool
  36. }
  37. func New(prompt Prompt) (*Instance, error) {
  38. term, err := NewTerminal()
  39. if err != nil {
  40. return nil, err
  41. }
  42. history, err := NewHistory()
  43. if err != nil {
  44. return nil, err
  45. }
  46. return &Instance{
  47. Prompt: &prompt,
  48. Terminal: term,
  49. History: history,
  50. }, nil
  51. }
  52. func (i *Instance) Readline() (string, error) {
  53. prompt := i.Prompt.prompt()
  54. if i.Pasting {
  55. // force alt prompt when pasting
  56. prompt = i.Prompt.AltPrompt
  57. }
  58. fmt.Print(prompt)
  59. fd := int(syscall.Stdin)
  60. termios, err := SetRawMode(fd)
  61. if err != nil {
  62. return "", err
  63. }
  64. // nolint: errcheck
  65. defer UnsetRawMode(fd, termios)
  66. buf, _ := NewBuffer(i.Prompt)
  67. var esc bool
  68. var escex bool
  69. var metaDel bool
  70. var currentLineBuf []rune
  71. for {
  72. // don't show placeholder when pasting unless we're in multiline mode
  73. showPlaceholder := !i.Pasting || i.Prompt.UseAlt
  74. if buf.IsEmpty() && showPlaceholder {
  75. ph := i.Prompt.placeholder()
  76. fmt.Printf(ColorGrey + ph + fmt.Sprintf(CursorLeftN, len(ph)) + ColorDefault)
  77. }
  78. r, err := i.Terminal.Read()
  79. if buf.IsEmpty() {
  80. fmt.Print(ClearToEOL)
  81. }
  82. if err != nil {
  83. return "", io.EOF
  84. }
  85. if escex {
  86. escex = false
  87. switch r {
  88. case KeyUp:
  89. if i.History.Pos > 0 {
  90. if i.History.Pos == i.History.Size() {
  91. currentLineBuf = []rune(buf.String())
  92. }
  93. buf.Replace(i.History.Prev())
  94. }
  95. case KeyDown:
  96. if i.History.Pos < i.History.Size() {
  97. buf.Replace(i.History.Next())
  98. if i.History.Pos == i.History.Size() {
  99. buf.Replace(currentLineBuf)
  100. }
  101. }
  102. case KeyLeft:
  103. buf.MoveLeft()
  104. case KeyRight:
  105. buf.MoveRight()
  106. case CharBracketedPaste:
  107. var code string
  108. for cnt := 0; cnt < 3; cnt++ {
  109. r, err = i.Terminal.Read()
  110. if err != nil {
  111. return "", io.EOF
  112. }
  113. code += string(r)
  114. }
  115. if code == CharBracketedPasteStart {
  116. i.Pasting = true
  117. } else if code == CharBracketedPasteEnd {
  118. i.Pasting = false
  119. }
  120. case KeyDel:
  121. if buf.Size() > 0 {
  122. buf.Delete()
  123. }
  124. metaDel = true
  125. case MetaStart:
  126. buf.MoveToStart()
  127. case MetaEnd:
  128. buf.MoveToEnd()
  129. default:
  130. // skip any keys we don't know about
  131. continue
  132. }
  133. continue
  134. } else if esc {
  135. esc = false
  136. switch r {
  137. case 'b':
  138. buf.MoveLeftWord()
  139. case 'f':
  140. buf.MoveRightWord()
  141. case CharBackspace:
  142. buf.DeleteWord()
  143. case CharEscapeEx:
  144. escex = true
  145. }
  146. continue
  147. }
  148. switch r {
  149. case CharNull:
  150. continue
  151. case CharEsc:
  152. esc = true
  153. case CharInterrupt:
  154. return "", ErrInterrupt
  155. case CharLineStart:
  156. buf.MoveToStart()
  157. case CharLineEnd:
  158. buf.MoveToEnd()
  159. case CharBackward:
  160. buf.MoveLeft()
  161. case CharForward:
  162. buf.MoveRight()
  163. case CharBackspace, CharCtrlH:
  164. buf.Remove()
  165. case CharTab:
  166. // todo: convert back to real tabs
  167. for cnt := 0; cnt < 8; cnt++ {
  168. buf.Add(' ')
  169. }
  170. case CharDelete:
  171. if buf.Size() > 0 {
  172. buf.Delete()
  173. } else {
  174. return "", io.EOF
  175. }
  176. case CharKill:
  177. buf.DeleteRemaining()
  178. case CharCtrlU:
  179. buf.DeleteBefore()
  180. case CharCtrlL:
  181. buf.ClearScreen()
  182. case CharCtrlW:
  183. buf.DeleteWord()
  184. case CharCtrlZ:
  185. return handleCharCtrlZ(fd, termios)
  186. case CharEnter:
  187. output := buf.String()
  188. if output != "" {
  189. i.History.Add([]rune(output))
  190. }
  191. buf.MoveToEnd()
  192. fmt.Println()
  193. return output, nil
  194. default:
  195. if metaDel {
  196. metaDel = false
  197. continue
  198. }
  199. if r >= CharSpace || r == CharEnter {
  200. buf.Add(r)
  201. }
  202. }
  203. }
  204. }
  205. func (i *Instance) HistoryEnable() {
  206. i.History.Enabled = true
  207. }
  208. func (i *Instance) HistoryDisable() {
  209. i.History.Enabled = false
  210. }
  211. func NewTerminal() (*Terminal, error) {
  212. t := &Terminal{
  213. outchan: make(chan rune),
  214. }
  215. go t.ioloop()
  216. return t, nil
  217. }
  218. func (t *Terminal) ioloop() {
  219. buf := bufio.NewReader(os.Stdin)
  220. for {
  221. r, _, err := buf.ReadRune()
  222. if err != nil {
  223. close(t.outchan)
  224. break
  225. }
  226. t.outchan <- r
  227. }
  228. }
  229. func (t *Terminal) Read() (rune, error) {
  230. r, ok := <-t.outchan
  231. if !ok {
  232. return 0, io.EOF
  233. }
  234. return r, nil
  235. }