readline.go 4.2 KB

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