readline.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  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 CharEscapeEx:
  129. escex = true
  130. }
  131. continue
  132. }
  133. switch r {
  134. case CharNull:
  135. continue
  136. case CharEsc:
  137. esc = true
  138. case CharInterrupt:
  139. return "", ErrInterrupt
  140. case CharLineStart:
  141. buf.MoveToStart()
  142. case CharLineEnd:
  143. buf.MoveToEnd()
  144. case CharBackward:
  145. buf.MoveLeft()
  146. case CharForward:
  147. buf.MoveRight()
  148. case CharBackspace, CharCtrlH:
  149. buf.Remove()
  150. case CharTab:
  151. // todo: convert back to real tabs
  152. for cnt := 0; cnt < 8; cnt++ {
  153. buf.Add(' ')
  154. }
  155. case CharDelete:
  156. if buf.Size() > 0 {
  157. buf.Delete()
  158. } else {
  159. return "", io.EOF
  160. }
  161. case CharKill:
  162. buf.DeleteRemaining()
  163. case CharCtrlU:
  164. buf.DeleteBefore()
  165. case CharCtrlL:
  166. buf.ClearScreen()
  167. case CharCtrlW:
  168. buf.DeleteWord()
  169. case CharEnter:
  170. output := buf.String()
  171. if output != "" {
  172. i.History.Add([]rune(output))
  173. }
  174. buf.MoveToEnd()
  175. fmt.Println()
  176. switch pasteMode {
  177. case PasteModeStart:
  178. output = `"""` + output
  179. case PasteModeEnd:
  180. output = output + `"""`
  181. }
  182. return output, nil
  183. default:
  184. if metaDel {
  185. metaDel = false
  186. continue
  187. }
  188. if r >= CharSpace || r == CharEnter {
  189. buf.Add(r)
  190. }
  191. }
  192. }
  193. }
  194. func (i *Instance) HistoryEnable() {
  195. i.History.Enabled = true
  196. }
  197. func (i *Instance) HistoryDisable() {
  198. i.History.Enabled = false
  199. }
  200. func NewTerminal() (*Terminal, error) {
  201. t := &Terminal{
  202. outchan: make(chan rune),
  203. }
  204. go t.ioloop()
  205. return t, nil
  206. }
  207. func (t *Terminal) ioloop() {
  208. buf := bufio.NewReader(os.Stdin)
  209. for {
  210. r, _, err := buf.ReadRune()
  211. if err != nil {
  212. close(t.outchan)
  213. break
  214. }
  215. t.outchan <- r
  216. }
  217. }
  218. func (t *Terminal) Read() (rune, error) {
  219. r, ok := <-t.outchan
  220. if !ok {
  221. return 0, io.EOF
  222. }
  223. return r, nil
  224. }