prompt.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. package prompt
  2. import (
  3. "bytes"
  4. "os"
  5. "time"
  6. "github.com/c-bata/go-prompt/internal/debug"
  7. )
  8. // Executor is called when user input something text.
  9. type Executor func(string)
  10. // ExitChecker is called after user input to check if prompt must stop and exit go-prompt Run loop.
  11. // User input means: selecting/typing an entry, then, if said entry content matches the ExitChecker function criteria:
  12. // - immediate exit (if breakline is false) without executor called
  13. // - exit after typing <return> (meaning breakline is true), and the executor is called first, before exit.
  14. // Exit means exit go-prompt (not the overall Go program)
  15. type ExitChecker func(in string, breakline bool) bool
  16. // Completer should return the suggest item from Document.
  17. type Completer func(Document) []Suggest
  18. // Prompt is core struct of go-prompt.
  19. type Prompt struct {
  20. in ConsoleParser
  21. buf *Buffer
  22. renderer *Render
  23. executor Executor
  24. history *History
  25. completion *CompletionManager
  26. keyBindings []KeyBind
  27. ASCIICodeBindings []ASCIICodeBind
  28. keyBindMode KeyBindMode
  29. completionOnDown bool
  30. exitChecker ExitChecker
  31. skipTearDown bool
  32. }
  33. // Exec is the struct contains user input context.
  34. type Exec struct {
  35. input string
  36. }
  37. // Run starts prompt.
  38. func (p *Prompt) Run() {
  39. p.skipTearDown = false
  40. defer debug.Teardown()
  41. debug.Log("start prompt")
  42. p.setUp()
  43. defer p.tearDown()
  44. if p.completion.showAtStart {
  45. p.completion.Update(*p.buf.Document())
  46. }
  47. p.renderer.Render(p.buf, p.completion)
  48. bufCh := make(chan []byte, 128)
  49. stopReadBufCh := make(chan struct{})
  50. go p.readBuffer(bufCh, stopReadBufCh)
  51. exitCh := make(chan int)
  52. winSizeCh := make(chan *WinSize)
  53. stopHandleSignalCh := make(chan struct{})
  54. go p.handleSignals(exitCh, winSizeCh, stopHandleSignalCh)
  55. for {
  56. select {
  57. case b := <-bufCh:
  58. if shouldExit, e := p.feed(b); shouldExit {
  59. p.renderer.BreakLine(p.buf)
  60. stopReadBufCh <- struct{}{}
  61. stopHandleSignalCh <- struct{}{}
  62. return
  63. } else if e != nil {
  64. // Stop goroutine to run readBuffer function
  65. stopReadBufCh <- struct{}{}
  66. stopHandleSignalCh <- struct{}{}
  67. // Unset raw mode
  68. // Reset to Blocking mode because returned EAGAIN when still set non-blocking mode.
  69. debug.AssertNoError(p.in.TearDown())
  70. p.executor(e.input)
  71. p.completion.Update(*p.buf.Document())
  72. p.renderer.Render(p.buf, p.completion)
  73. if p.exitChecker != nil && p.exitChecker(e.input, true) {
  74. p.skipTearDown = true
  75. return
  76. }
  77. // Set raw mode
  78. debug.AssertNoError(p.in.Setup())
  79. go p.readBuffer(bufCh, stopReadBufCh)
  80. go p.handleSignals(exitCh, winSizeCh, stopHandleSignalCh)
  81. } else {
  82. p.completion.Update(*p.buf.Document())
  83. p.renderer.Render(p.buf, p.completion)
  84. }
  85. case w := <-winSizeCh:
  86. p.renderer.UpdateWinSize(w)
  87. p.renderer.Render(p.buf, p.completion)
  88. case code := <-exitCh:
  89. p.renderer.BreakLine(p.buf)
  90. p.tearDown()
  91. os.Exit(code)
  92. default:
  93. time.Sleep(10 * time.Millisecond)
  94. }
  95. }
  96. }
  97. func (p *Prompt) feed(b []byte) (shouldExit bool, exec *Exec) {
  98. key := GetKey(b)
  99. p.buf.lastKeyStroke = key
  100. // completion
  101. completing := p.completion.Completing()
  102. p.handleCompletionKeyBinding(key, completing)
  103. switch key {
  104. case Enter, ControlJ, ControlM:
  105. p.renderer.BreakLine(p.buf)
  106. exec = &Exec{input: p.buf.Text()}
  107. p.buf = NewBuffer()
  108. if exec.input != "" {
  109. p.history.Add(exec.input)
  110. }
  111. case ControlC:
  112. p.renderer.BreakLine(p.buf)
  113. p.buf = NewBuffer()
  114. p.history.Clear()
  115. case Up, ControlP:
  116. if !completing { // Don't use p.completion.Completing() because it takes double operation when switch to selected=-1.
  117. if newBuf, changed := p.history.Older(p.buf); changed {
  118. p.buf = newBuf
  119. }
  120. }
  121. case Down, ControlN:
  122. if !completing { // Don't use p.completion.Completing() because it takes double operation when switch to selected=-1.
  123. if newBuf, changed := p.history.Newer(p.buf); changed {
  124. p.buf = newBuf
  125. }
  126. return
  127. }
  128. case ControlD:
  129. if p.buf.Text() == "" {
  130. shouldExit = true
  131. return
  132. }
  133. case NotDefined:
  134. if p.handleASCIICodeBinding(b) {
  135. return
  136. }
  137. p.buf.InsertText(string(b), false, true)
  138. }
  139. shouldExit = p.handleKeyBinding(key)
  140. return
  141. }
  142. func (p *Prompt) handleCompletionKeyBinding(key Key, completing bool) {
  143. switch key {
  144. case Down:
  145. if completing || p.completionOnDown {
  146. p.completion.Next()
  147. }
  148. case Tab, ControlI:
  149. p.completion.Next()
  150. case Up:
  151. if completing {
  152. p.completion.Previous()
  153. }
  154. case BackTab:
  155. p.completion.Previous()
  156. default:
  157. if s, ok := p.completion.GetSelectedSuggestion(); ok {
  158. w := p.buf.Document().GetWordBeforeCursorUntilSeparator(p.completion.wordSeparator)
  159. if w != "" {
  160. p.buf.DeleteBeforeCursor(len([]rune(w)))
  161. }
  162. p.buf.InsertText(s.Text, false, true)
  163. }
  164. p.completion.Reset()
  165. }
  166. }
  167. func (p *Prompt) handleKeyBinding(key Key) bool {
  168. shouldExit := false
  169. for i := range commonKeyBindings {
  170. kb := commonKeyBindings[i]
  171. if kb.Key == key {
  172. kb.Fn(p.buf)
  173. }
  174. }
  175. if p.keyBindMode == EmacsKeyBind {
  176. for i := range emacsKeyBindings {
  177. kb := emacsKeyBindings[i]
  178. if kb.Key == key {
  179. kb.Fn(p.buf)
  180. }
  181. }
  182. }
  183. // Custom key bindings
  184. for i := range p.keyBindings {
  185. kb := p.keyBindings[i]
  186. if kb.Key == key {
  187. kb.Fn(p.buf)
  188. }
  189. }
  190. if p.exitChecker != nil && p.exitChecker(p.buf.Text(), false) {
  191. shouldExit = true
  192. }
  193. return shouldExit
  194. }
  195. func (p *Prompt) handleASCIICodeBinding(b []byte) bool {
  196. checked := false
  197. for _, kb := range p.ASCIICodeBindings {
  198. if bytes.Equal(kb.ASCIICode, b) {
  199. kb.Fn(p.buf)
  200. checked = true
  201. }
  202. }
  203. return checked
  204. }
  205. // Input just returns user input text.
  206. func (p *Prompt) Input() string {
  207. defer debug.Teardown()
  208. debug.Log("start prompt")
  209. p.setUp()
  210. defer p.tearDown()
  211. if p.completion.showAtStart {
  212. p.completion.Update(*p.buf.Document())
  213. }
  214. p.renderer.Render(p.buf, p.completion)
  215. bufCh := make(chan []byte, 128)
  216. stopReadBufCh := make(chan struct{})
  217. go p.readBuffer(bufCh, stopReadBufCh)
  218. for {
  219. select {
  220. case b := <-bufCh:
  221. if shouldExit, e := p.feed(b); shouldExit {
  222. p.renderer.BreakLine(p.buf)
  223. stopReadBufCh <- struct{}{}
  224. return ""
  225. } else if e != nil {
  226. // Stop goroutine to run readBuffer function
  227. stopReadBufCh <- struct{}{}
  228. return e.input
  229. } else {
  230. p.completion.Update(*p.buf.Document())
  231. p.renderer.Render(p.buf, p.completion)
  232. }
  233. default:
  234. time.Sleep(10 * time.Millisecond)
  235. }
  236. }
  237. }
  238. func (p *Prompt) readBuffer(bufCh chan []byte, stopCh chan struct{}) {
  239. debug.Log("start reading buffer")
  240. for {
  241. select {
  242. case <-stopCh:
  243. debug.Log("stop reading buffer")
  244. return
  245. default:
  246. if b, err := p.in.Read(); err == nil && !(len(b) == 1 && b[0] == 0) {
  247. bufCh <- b
  248. }
  249. }
  250. time.Sleep(10 * time.Millisecond)
  251. }
  252. }
  253. func (p *Prompt) setUp() {
  254. debug.AssertNoError(p.in.Setup())
  255. p.renderer.Setup()
  256. p.renderer.UpdateWinSize(p.in.GetWinSize())
  257. }
  258. func (p *Prompt) tearDown() {
  259. if !p.skipTearDown {
  260. debug.AssertNoError(p.in.TearDown())
  261. }
  262. p.renderer.TearDown()
  263. }