completion.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. // License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package readline
  3. import (
  4. "fmt"
  5. "strings"
  6. "kitty/tools/cli"
  7. "kitty/tools/utils"
  8. "kitty/tools/wcswidth"
  9. )
  10. var _ = fmt.Print
  11. type completion struct {
  12. before_cursor, after_cursor string
  13. results *cli.Completions
  14. results_displayed, forwards bool
  15. num_of_matches, current_match int
  16. rendered_at_screen_width int
  17. rendered_lines []string
  18. last_rendered_above bool
  19. }
  20. func (self *completion) initialize() {
  21. self.num_of_matches = 0
  22. if self.results != nil {
  23. for _, g := range self.results.Groups {
  24. self.num_of_matches += len(g.Matches)
  25. }
  26. }
  27. self.current_match = -1
  28. if !self.forwards {
  29. self.current_match = self.num_of_matches
  30. }
  31. if self.num_of_matches == 1 {
  32. self.current_match = 0
  33. }
  34. }
  35. func (self *completion) current_match_text() string {
  36. if self.results != nil {
  37. i := 0
  38. for _, g := range self.results.Groups {
  39. for _, m := range g.Matches {
  40. if i == self.current_match {
  41. t := m.Word
  42. if !g.NoTrailingSpace && t != "" {
  43. t += " "
  44. }
  45. return t
  46. }
  47. i++
  48. }
  49. }
  50. }
  51. return ""
  52. }
  53. type completions struct {
  54. completer CompleterFunction
  55. current completion
  56. }
  57. func (self *Readline) complete(forwards bool, repeat_count uint) bool {
  58. c := &self.completions
  59. if c.completer == nil {
  60. return false
  61. }
  62. if self.last_action == ActionCompleteForward || self.last_action == ActionCompleteBackward {
  63. if c.current.num_of_matches == 0 {
  64. return false
  65. }
  66. delta := -1
  67. if forwards {
  68. delta = 1
  69. }
  70. repeat_count %= uint(c.current.num_of_matches)
  71. delta *= int(repeat_count)
  72. c.current.current_match = (c.current.current_match + delta + c.current.num_of_matches) % c.current.num_of_matches
  73. repeat_count = 0
  74. } else {
  75. before, after := self.text_upto_cursor_pos(), self.text_after_cursor_pos()
  76. c.current = completion{before_cursor: before, after_cursor: after, forwards: forwards, results: c.completer(before, after)}
  77. c.current.initialize()
  78. if repeat_count > 0 {
  79. repeat_count--
  80. }
  81. if c.current.current_match != 0 {
  82. if self.loop != nil {
  83. self.loop.Beep()
  84. }
  85. }
  86. }
  87. c.current.forwards = forwards
  88. if c.current.results == nil {
  89. return false
  90. }
  91. ct := c.current.current_match_text()
  92. if ct != "" {
  93. all_text_before_completion := self.AllText()
  94. before := c.current.before_cursor[:c.current.results.CurrentWordIdx] + ct
  95. after := c.current.after_cursor
  96. self.input_state.lines = utils.Splitlines(before)
  97. if len(self.input_state.lines) == 0 {
  98. self.input_state.lines = []string{""}
  99. }
  100. self.input_state.cursor.Y = len(self.input_state.lines) - 1
  101. self.input_state.cursor.X = len(self.input_state.lines[self.input_state.cursor.Y])
  102. al := utils.Splitlines(after)
  103. if len(al) > 0 {
  104. self.input_state.lines[self.input_state.cursor.Y] += al[0]
  105. self.input_state.lines = append(self.input_state.lines, al[1:]...)
  106. }
  107. if c.current.num_of_matches == 1 && self.AllText() == all_text_before_completion {
  108. // when there is only a single match and it has already been inserted there is no point iterating over current completions
  109. orig := self.last_action
  110. self.last_action = ActionNil
  111. self.complete(true, 1)
  112. self.last_action = orig
  113. }
  114. }
  115. if repeat_count > 0 {
  116. self.complete(forwards, repeat_count)
  117. }
  118. return true
  119. }
  120. func (self *Readline) screen_lines_for_match_group_with_descriptions(g *cli.MatchGroup, lines []string) []string {
  121. maxw := 0
  122. for _, m := range g.Matches {
  123. l := wcswidth.Stringwidth(m.Word)
  124. if l > 16 {
  125. maxw = 16
  126. break
  127. }
  128. if l > maxw {
  129. maxw = l
  130. }
  131. }
  132. for _, m := range g.Matches {
  133. lines = append(lines, utils.Splitlines(m.FormatForCompletionList(maxw, self.fmt_ctx, self.screen_width))...)
  134. }
  135. return lines
  136. }
  137. type cell struct {
  138. text string
  139. length int
  140. }
  141. func (self cell) whitespace(desired_length int) string {
  142. return strings.Repeat(" ", max(0, desired_length-self.length))
  143. }
  144. type column struct {
  145. cells []cell
  146. length int
  147. is_last bool
  148. }
  149. func (self *column) update_length() int {
  150. self.length = 0
  151. for _, c := range self.cells {
  152. if c.length > self.length {
  153. self.length = c.length
  154. }
  155. }
  156. if !self.is_last {
  157. self.length++
  158. }
  159. return self.length
  160. }
  161. func layout_words_in_table(words []string, lengths map[string]int, num_cols int) ([]column, int) {
  162. cols := make([]column, num_cols)
  163. for i, col := range cols {
  164. col.cells = make([]cell, 0, len(words))
  165. if i == len(cols)-1 {
  166. col.is_last = true
  167. }
  168. }
  169. c := 0
  170. for _, word := range words {
  171. cols[c].cells = append(cols[c].cells, cell{word, lengths[word]})
  172. c++
  173. if c >= num_cols {
  174. c = 0
  175. }
  176. }
  177. total_length := 0
  178. for i := range cols {
  179. if d := len(cols[0].cells) - len(cols[i].cells); d > 0 {
  180. cols[i].cells = append(cols[i].cells, make([]cell, d)...)
  181. }
  182. total_length += cols[i].update_length()
  183. }
  184. return cols, total_length
  185. }
  186. func (self *Readline) screen_lines_for_match_group_without_descriptions(g *cli.MatchGroup, lines []string) []string {
  187. words := make([]string, len(g.Matches))
  188. lengths := make(map[string]int, len(words))
  189. max_length := 0
  190. for i, m := range g.Matches {
  191. words[i] = m.Word
  192. l := wcswidth.Stringwidth(words[i])
  193. lengths[words[i]] = l
  194. if l > max_length {
  195. max_length = l
  196. }
  197. }
  198. var ans []column
  199. ncols := max(1, self.screen_width/(max_length+1))
  200. for {
  201. cols, total_length := layout_words_in_table(words, lengths, ncols)
  202. if total_length > self.screen_width {
  203. break
  204. }
  205. ans = cols
  206. ncols++
  207. }
  208. if ans == nil {
  209. for _, w := range words {
  210. if lengths[w] > self.screen_width {
  211. lines = append(lines, wcswidth.TruncateToVisualLength(w, self.screen_width))
  212. } else {
  213. lines = append(lines, w)
  214. }
  215. }
  216. } else {
  217. for r := 0; r < len(ans[0].cells); r++ {
  218. w := strings.Builder{}
  219. w.Grow(self.screen_width)
  220. for c := 0; c < len(ans); c++ {
  221. cell := ans[c].cells[r]
  222. w.WriteString(cell.text)
  223. if !ans[c].is_last {
  224. w.WriteString(cell.whitespace(ans[c].length))
  225. }
  226. }
  227. lines = append(lines, w.String())
  228. }
  229. }
  230. return lines
  231. }
  232. func (self *Readline) completion_screen_lines() ([]string, bool) {
  233. if self.completions.current.results == nil || self.completions.current.num_of_matches < 2 {
  234. return []string{}, false
  235. }
  236. if len(self.completions.current.rendered_lines) > 0 && self.completions.current.rendered_at_screen_width == self.screen_width {
  237. return self.completions.current.rendered_lines, true
  238. }
  239. lines := make([]string, 0, self.completions.current.num_of_matches)
  240. for _, g := range self.completions.current.results.Groups {
  241. if len(g.Matches) == 0 {
  242. continue
  243. }
  244. if g.Title != "" {
  245. lines = append(lines, self.fmt_ctx.Title(g.Title))
  246. }
  247. has_descriptions := false
  248. for _, m := range g.Matches {
  249. if m.Description != "" {
  250. has_descriptions = true
  251. break
  252. }
  253. }
  254. if has_descriptions {
  255. lines = self.screen_lines_for_match_group_with_descriptions(g, lines)
  256. } else {
  257. lines = self.screen_lines_for_match_group_without_descriptions(g, lines)
  258. }
  259. }
  260. self.completions.current.rendered_lines = lines
  261. self.completions.current.rendered_at_screen_width = self.screen_width
  262. return lines, false
  263. }