common.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. /*
  2. Package liner implements a simple command line editor, inspired by linenoise
  3. (https://github.com/antirez/linenoise/). This package supports WIN32 in
  4. addition to the xterm codes supported by everything else.
  5. */
  6. package liner
  7. import (
  8. "bufio"
  9. "container/ring"
  10. "errors"
  11. "fmt"
  12. "io"
  13. "strings"
  14. "sync"
  15. "unicode/utf8"
  16. )
  17. type commonState struct {
  18. terminalSupported bool
  19. outputRedirected bool
  20. inputRedirected bool
  21. history []string
  22. historyMutex sync.RWMutex
  23. completer WordCompleter
  24. columns int
  25. killRing *ring.Ring
  26. ctrlCAborts bool
  27. r *bufio.Reader
  28. tabStyle TabStyle
  29. multiLineMode bool
  30. cursorRows int
  31. maxRows int
  32. shouldRestart ShouldRestart
  33. noBeep bool
  34. needRefresh bool
  35. }
  36. // TabStyle is used to select how tab completions are displayed.
  37. type TabStyle int
  38. // Two tab styles are currently available:
  39. //
  40. // TabCircular cycles through each completion item and displays it directly on
  41. // the prompt
  42. //
  43. // TabPrints prints the list of completion items to the screen after a second
  44. // tab key is pressed. This behaves similar to GNU readline and BASH (which
  45. // uses readline)
  46. const (
  47. TabCircular TabStyle = iota
  48. TabPrints
  49. )
  50. // ErrPromptAborted is returned from Prompt or PasswordPrompt when the user presses Ctrl-C
  51. // if SetCtrlCAborts(true) has been called on the State
  52. var ErrPromptAborted = errors.New("prompt aborted")
  53. // ErrNotTerminalOutput is returned from Prompt or PasswordPrompt if the
  54. // platform is normally supported, but stdout has been redirected
  55. var ErrNotTerminalOutput = errors.New("standard output is not a terminal")
  56. // ErrInvalidPrompt is returned from Prompt or PasswordPrompt if the
  57. // prompt contains any unprintable runes (including substrings that could
  58. // be colour codes on some platforms).
  59. var ErrInvalidPrompt = errors.New("invalid prompt")
  60. // ErrInternal is returned when liner experiences an error that it cannot
  61. // handle. For example, if the number of colums becomes zero during an
  62. // active call to Prompt
  63. var ErrInternal = errors.New("liner: internal error")
  64. // KillRingMax is the max number of elements to save on the killring.
  65. const KillRingMax = 60
  66. // HistoryLimit is the maximum number of entries saved in the scrollback history.
  67. const HistoryLimit = 1000
  68. // ReadHistory reads scrollback history from r. Returns the number of lines
  69. // read, and any read error (except io.EOF).
  70. func (s *State) ReadHistory(r io.Reader) (num int, err error) {
  71. s.historyMutex.Lock()
  72. defer s.historyMutex.Unlock()
  73. in := bufio.NewReader(r)
  74. num = 0
  75. for {
  76. line, part, err := in.ReadLine()
  77. if err == io.EOF {
  78. break
  79. }
  80. if err != nil {
  81. return num, err
  82. }
  83. if part {
  84. return num, fmt.Errorf("line %d is too long", num+1)
  85. }
  86. if !utf8.Valid(line) {
  87. return num, fmt.Errorf("invalid string at line %d", num+1)
  88. }
  89. num++
  90. s.history = append(s.history, string(line))
  91. if len(s.history) > HistoryLimit {
  92. s.history = s.history[1:]
  93. }
  94. }
  95. return num, nil
  96. }
  97. // WriteHistory writes scrollback history to w. Returns the number of lines
  98. // successfully written, and any write error.
  99. //
  100. // Unlike the rest of liner's API, WriteHistory is safe to call
  101. // from another goroutine while Prompt is in progress.
  102. // This exception is to facilitate the saving of the history buffer
  103. // during an unexpected exit (for example, due to Ctrl-C being invoked)
  104. func (s *State) WriteHistory(w io.Writer) (num int, err error) {
  105. s.historyMutex.RLock()
  106. defer s.historyMutex.RUnlock()
  107. for _, item := range s.history {
  108. _, err := fmt.Fprintln(w, item)
  109. if err != nil {
  110. return num, err
  111. }
  112. num++
  113. }
  114. return num, nil
  115. }
  116. // AppendHistory appends an entry to the scrollback history. AppendHistory
  117. // should be called iff Prompt returns a valid command.
  118. func (s *State) AppendHistory(item string) {
  119. s.historyMutex.Lock()
  120. defer s.historyMutex.Unlock()
  121. if len(s.history) > 0 {
  122. if item == s.history[len(s.history)-1] {
  123. return
  124. }
  125. }
  126. s.history = append(s.history, item)
  127. if len(s.history) > HistoryLimit {
  128. s.history = s.history[1:]
  129. }
  130. }
  131. // ClearHistory clears the scrollback history.
  132. func (s *State) ClearHistory() {
  133. s.historyMutex.Lock()
  134. defer s.historyMutex.Unlock()
  135. s.history = nil
  136. }
  137. // Returns the history lines starting with prefix
  138. func (s *State) getHistoryByPrefix(prefix string) (ph []string) {
  139. for _, h := range s.history {
  140. if strings.HasPrefix(h, prefix) {
  141. ph = append(ph, h)
  142. }
  143. }
  144. return
  145. }
  146. // Returns the history lines matching the intelligent search
  147. func (s *State) getHistoryByPattern(pattern string) (ph []string, pos []int) {
  148. if pattern == "" {
  149. return
  150. }
  151. for _, h := range s.history {
  152. if i := strings.Index(h, pattern); i >= 0 {
  153. ph = append(ph, h)
  154. pos = append(pos, i)
  155. }
  156. }
  157. return
  158. }
  159. // Completer takes the currently edited line content at the left of the cursor
  160. // and returns a list of completion candidates.
  161. // If the line is "Hello, wo!!!" and the cursor is before the first '!', "Hello, wo" is passed
  162. // to the completer which may return {"Hello, world", "Hello, Word"} to have "Hello, world!!!".
  163. type Completer func(line string) []string
  164. // WordCompleter takes the currently edited line with the cursor position and
  165. // returns the completion candidates for the partial word to be completed.
  166. // If the line is "Hello, wo!!!" and the cursor is before the first '!', ("Hello, wo!!!", 9) is passed
  167. // to the completer which may returns ("Hello, ", {"world", "Word"}, "!!!") to have "Hello, world!!!".
  168. type WordCompleter func(line string, pos int) (head string, completions []string, tail string)
  169. // SetCompleter sets the completion function that Liner will call to
  170. // fetch completion candidates when the user presses tab.
  171. func (s *State) SetCompleter(f Completer) {
  172. if f == nil {
  173. s.completer = nil
  174. return
  175. }
  176. s.completer = func(line string, pos int) (string, []string, string) {
  177. return "", f(string([]rune(line)[:pos])), string([]rune(line)[pos:])
  178. }
  179. }
  180. // SetWordCompleter sets the completion function that Liner will call to
  181. // fetch completion candidates when the user presses tab.
  182. func (s *State) SetWordCompleter(f WordCompleter) {
  183. s.completer = f
  184. }
  185. // SetTabCompletionStyle sets the behvavior when the Tab key is pressed
  186. // for auto-completion. TabCircular is the default behavior and cycles
  187. // through the list of candidates at the prompt. TabPrints will print
  188. // the available completion candidates to the screen similar to BASH
  189. // and GNU Readline
  190. func (s *State) SetTabCompletionStyle(tabStyle TabStyle) {
  191. s.tabStyle = tabStyle
  192. }
  193. // ModeApplier is the interface that wraps a representation of the terminal
  194. // mode. ApplyMode sets the terminal to this mode.
  195. type ModeApplier interface {
  196. ApplyMode() error
  197. }
  198. // SetCtrlCAborts sets whether Prompt on a supported terminal will return an
  199. // ErrPromptAborted when Ctrl-C is pressed. The default is false (will not
  200. // return when Ctrl-C is pressed). Unsupported terminals typically raise SIGINT
  201. // (and Prompt does not return) regardless of the value passed to SetCtrlCAborts.
  202. func (s *State) SetCtrlCAborts(aborts bool) {
  203. s.ctrlCAborts = aborts
  204. }
  205. // SetMultiLineMode sets whether line is auto-wrapped. The default is false (single line).
  206. func (s *State) SetMultiLineMode(mlmode bool) {
  207. s.multiLineMode = mlmode
  208. }
  209. // ShouldRestart is passed the error generated by readNext and returns true if
  210. // the the read should be restarted or false if the error should be returned.
  211. type ShouldRestart func(err error) bool
  212. // SetShouldRestart sets the restart function that Liner will call to determine
  213. // whether to retry the call to, or return the error returned by, readNext.
  214. func (s *State) SetShouldRestart(f ShouldRestart) {
  215. s.shouldRestart = f
  216. }
  217. // SetBeep sets whether liner should beep the terminal at various times (output
  218. // ASCII BEL, 0x07). Default is true (will beep).
  219. func (s *State) SetBeep(beep bool) {
  220. s.noBeep = !beep
  221. }
  222. func (s *State) promptUnsupported(p string) (string, error) {
  223. if !s.inputRedirected || !s.terminalSupported {
  224. fmt.Print(p)
  225. }
  226. linebuf, _, err := s.r.ReadLine()
  227. if err != nil {
  228. return "", err
  229. }
  230. return string(linebuf), nil
  231. }