You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

285 lines
6.1 KiB

  1. package readline
  2. import (
  3. "bufio"
  4. "bytes"
  5. "fmt"
  6. "io"
  7. )
  8. type AutoCompleter interface {
  9. // Readline will pass the whole line and current offset to it
  10. // Completer need to pass all the candidates, and how long they shared the same characters in line
  11. // Example:
  12. // [go, git, git-shell, grep]
  13. // Do("g", 1) => ["o", "it", "it-shell", "rep"], 1
  14. // Do("gi", 2) => ["t", "t-shell"], 2
  15. // Do("git", 3) => ["", "-shell"], 3
  16. Do(line []rune, pos int) (newLine [][]rune, length int)
  17. }
  18. type TabCompleter struct{}
  19. func (t *TabCompleter) Do([]rune, int) ([][]rune, int) {
  20. return [][]rune{[]rune("\t")}, 0
  21. }
  22. type opCompleter struct {
  23. w io.Writer
  24. op *Operation
  25. width int
  26. inCompleteMode bool
  27. inSelectMode bool
  28. candidate [][]rune
  29. candidateSource []rune
  30. candidateOff int
  31. candidateChoise int
  32. candidateColNum int
  33. }
  34. func newOpCompleter(w io.Writer, op *Operation, width int) *opCompleter {
  35. return &opCompleter{
  36. w: w,
  37. op: op,
  38. width: width,
  39. }
  40. }
  41. func (o *opCompleter) doSelect() {
  42. if len(o.candidate) == 1 {
  43. o.op.buf.WriteRunes(o.candidate[0])
  44. o.ExitCompleteMode(false)
  45. return
  46. }
  47. o.nextCandidate(1)
  48. o.CompleteRefresh()
  49. }
  50. func (o *opCompleter) nextCandidate(i int) {
  51. o.candidateChoise += i
  52. o.candidateChoise = o.candidateChoise % len(o.candidate)
  53. if o.candidateChoise < 0 {
  54. o.candidateChoise = len(o.candidate) + o.candidateChoise
  55. }
  56. }
  57. func (o *opCompleter) OnComplete() bool {
  58. if o.width == 0 {
  59. return false
  60. }
  61. if o.IsInCompleteSelectMode() {
  62. o.doSelect()
  63. return true
  64. }
  65. buf := o.op.buf
  66. rs := buf.Runes()
  67. if o.IsInCompleteMode() && o.candidateSource != nil && runes.Equal(rs, o.candidateSource) {
  68. o.EnterCompleteSelectMode()
  69. o.doSelect()
  70. return true
  71. }
  72. o.ExitCompleteSelectMode()
  73. o.candidateSource = rs
  74. newLines, offset := o.op.cfg.AutoComplete.Do(rs, buf.idx)
  75. if len(newLines) == 0 {
  76. o.ExitCompleteMode(false)
  77. return true
  78. }
  79. // only Aggregate candidates in non-complete mode
  80. if !o.IsInCompleteMode() {
  81. if len(newLines) == 1 {
  82. buf.WriteRunes(newLines[0])
  83. o.ExitCompleteMode(false)
  84. return true
  85. }
  86. same, size := runes.Aggregate(newLines)
  87. if size > 0 {
  88. buf.WriteRunes(same)
  89. o.ExitCompleteMode(false)
  90. return true
  91. }
  92. }
  93. o.EnterCompleteMode(offset, newLines)
  94. return true
  95. }
  96. func (o *opCompleter) IsInCompleteSelectMode() bool {
  97. return o.inSelectMode
  98. }
  99. func (o *opCompleter) IsInCompleteMode() bool {
  100. return o.inCompleteMode
  101. }
  102. func (o *opCompleter) HandleCompleteSelect(r rune) bool {
  103. next := true
  104. switch r {
  105. case CharEnter, CharCtrlJ:
  106. next = false
  107. o.op.buf.WriteRunes(o.op.candidate[o.op.candidateChoise])
  108. o.ExitCompleteMode(false)
  109. case CharLineStart:
  110. num := o.candidateChoise % o.candidateColNum
  111. o.nextCandidate(-num)
  112. case CharLineEnd:
  113. num := o.candidateColNum - o.candidateChoise%o.candidateColNum - 1
  114. o.candidateChoise += num
  115. if o.candidateChoise >= len(o.candidate) {
  116. o.candidateChoise = len(o.candidate) - 1
  117. }
  118. case CharBackspace:
  119. o.ExitCompleteSelectMode()
  120. next = false
  121. case CharTab, CharForward:
  122. o.doSelect()
  123. case CharBell, CharInterrupt:
  124. o.ExitCompleteMode(true)
  125. next = false
  126. case CharNext:
  127. tmpChoise := o.candidateChoise + o.candidateColNum
  128. if tmpChoise >= o.getMatrixSize() {
  129. tmpChoise -= o.getMatrixSize()
  130. } else if tmpChoise >= len(o.candidate) {
  131. tmpChoise += o.candidateColNum
  132. tmpChoise -= o.getMatrixSize()
  133. }
  134. o.candidateChoise = tmpChoise
  135. case CharBackward:
  136. o.nextCandidate(-1)
  137. case CharPrev:
  138. tmpChoise := o.candidateChoise - o.candidateColNum
  139. if tmpChoise < 0 {
  140. tmpChoise += o.getMatrixSize()
  141. if tmpChoise >= len(o.candidate) {
  142. tmpChoise -= o.candidateColNum
  143. }
  144. }
  145. o.candidateChoise = tmpChoise
  146. default:
  147. next = false
  148. o.ExitCompleteSelectMode()
  149. }
  150. if next {
  151. o.CompleteRefresh()
  152. return true
  153. }
  154. return false
  155. }
  156. func (o *opCompleter) getMatrixSize() int {
  157. line := len(o.candidate) / o.candidateColNum
  158. if len(o.candidate)%o.candidateColNum != 0 {
  159. line++
  160. }
  161. return line * o.candidateColNum
  162. }
  163. func (o *opCompleter) OnWidthChange(newWidth int) {
  164. o.width = newWidth
  165. }
  166. func (o *opCompleter) CompleteRefresh() {
  167. if !o.inCompleteMode {
  168. return
  169. }
  170. lineCnt := o.op.buf.CursorLineCount()
  171. colWidth := 0
  172. for _, c := range o.candidate {
  173. w := runes.WidthAll(c)
  174. if w > colWidth {
  175. colWidth = w
  176. }
  177. }
  178. colWidth += o.candidateOff + 1
  179. same := o.op.buf.RuneSlice(-o.candidateOff)
  180. // -1 to avoid reach the end of line
  181. width := o.width - 1
  182. colNum := width / colWidth
  183. if colNum != 0 {
  184. colWidth += (width - (colWidth * colNum)) / colNum
  185. }
  186. o.candidateColNum = colNum
  187. buf := bufio.NewWriter(o.w)
  188. buf.Write(bytes.Repeat([]byte("\n"), lineCnt))
  189. colIdx := 0
  190. lines := 1
  191. buf.WriteString("\033[J")
  192. for idx, c := range o.candidate {
  193. inSelect := idx == o.candidateChoise && o.IsInCompleteSelectMode()
  194. if inSelect {
  195. buf.WriteString("\033[30;47m")
  196. }
  197. buf.WriteString(string(same))
  198. buf.WriteString(string(c))
  199. buf.Write(bytes.Repeat([]byte(" "), colWidth-len(c)-len(same)))
  200. if inSelect {
  201. buf.WriteString("\033[0m")
  202. }
  203. colIdx++
  204. if colIdx == colNum {
  205. buf.WriteString("\n")
  206. lines++
  207. colIdx = 0
  208. }
  209. }
  210. // move back
  211. fmt.Fprintf(buf, "\033[%dA\r", lineCnt-1+lines)
  212. fmt.Fprintf(buf, "\033[%dC", o.op.buf.idx+o.op.buf.PromptLen())
  213. buf.Flush()
  214. }
  215. func (o *opCompleter) aggCandidate(candidate [][]rune) int {
  216. offset := 0
  217. for i := 0; i < len(candidate[0]); i++ {
  218. for j := 0; j < len(candidate)-1; j++ {
  219. if i > len(candidate[j]) {
  220. goto aggregate
  221. }
  222. if candidate[j][i] != candidate[j+1][i] {
  223. goto aggregate
  224. }
  225. }
  226. offset = i
  227. }
  228. aggregate:
  229. return offset
  230. }
  231. func (o *opCompleter) EnterCompleteSelectMode() {
  232. o.inSelectMode = true
  233. o.candidateChoise = -1
  234. o.CompleteRefresh()
  235. }
  236. func (o *opCompleter) EnterCompleteMode(offset int, candidate [][]rune) {
  237. o.inCompleteMode = true
  238. o.candidate = candidate
  239. o.candidateOff = offset
  240. o.CompleteRefresh()
  241. }
  242. func (o *opCompleter) ExitCompleteSelectMode() {
  243. o.inSelectMode = false
  244. o.candidate = nil
  245. o.candidateChoise = -1
  246. o.candidateOff = -1
  247. o.candidateSource = nil
  248. }
  249. func (o *opCompleter) ExitCompleteMode(revent bool) {
  250. o.inCompleteMode = false
  251. o.ExitCompleteSelectMode()
  252. }