readline.go 4.2 KB

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