draw.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. // License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package readline
  3. import (
  4. "fmt"
  5. "kitty/tools/tui/loop"
  6. "kitty/tools/utils"
  7. "kitty/tools/wcswidth"
  8. "strings"
  9. )
  10. var _ = fmt.Print
  11. func (self *Readline) update_current_screen_size() {
  12. var screen_size loop.ScreenSize
  13. var err error
  14. if self.loop != nil {
  15. screen_size, err = self.loop.ScreenSize()
  16. if err != nil {
  17. screen_size.WidthCells = 80
  18. screen_size.HeightCells = 24
  19. }
  20. } else {
  21. screen_size.WidthCells = 80
  22. screen_size.HeightCells = 24
  23. }
  24. self.screen_width = max(1, int(screen_size.WidthCells))
  25. self.screen_height = max(1, int(screen_size.HeightCells))
  26. }
  27. type ScreenLine struct {
  28. ParentLineNumber, OffsetInParentLine int
  29. Prompt Prompt
  30. TextLengthInCells, CursorCell, CursorTextPos int
  31. Text string
  32. AfterLineBreak bool
  33. }
  34. func (self *Readline) format_arg_prompt(cna string) string {
  35. return fmt.Sprintf("(arg: %s) ", self.fmt_ctx.Yellow(cna))
  36. }
  37. func (self *Readline) prompt_for_line_number(i int) Prompt {
  38. is_line_with_cursor := i == self.input_state.cursor.Y
  39. if is_line_with_cursor && self.keyboard_state.current_numeric_argument != "" {
  40. return self.make_prompt(self.format_arg_prompt(self.keyboard_state.current_numeric_argument), i > 0)
  41. }
  42. if i == 0 {
  43. if self.history_search != nil {
  44. return self.make_prompt(self.history_search_prompt(), i > 0)
  45. }
  46. return self.prompt
  47. }
  48. return self.continuation_prompt
  49. }
  50. func (self *Readline) apply_syntax_highlighting() (lines []string, cursor Position) {
  51. highlighter := self.syntax_highlighted.highlighter
  52. highlighter_name := "default"
  53. if self.history_search != nil {
  54. highlighter = self.history_search_highlighter
  55. highlighter_name = "## history ##"
  56. }
  57. if highlighter == nil {
  58. return self.input_state.lines, self.input_state.cursor
  59. }
  60. src := strings.Join(self.input_state.lines, "\n")
  61. if len(self.syntax_highlighted.lines) > 0 && self.syntax_highlighted.last_highlighter_name == highlighter_name && self.syntax_highlighted.src_for_last_highlight == src {
  62. lines = self.syntax_highlighted.lines
  63. } else {
  64. if src == "" {
  65. lines = []string{""}
  66. } else {
  67. text := highlighter(src, self.input_state.cursor.X, self.input_state.cursor.Y)
  68. lines = utils.Splitlines(text)
  69. for len(lines) < len(self.input_state.lines) {
  70. lines = append(lines, "syntax highlighter malfunctioned")
  71. }
  72. }
  73. }
  74. line := lines[self.input_state.cursor.Y]
  75. w := wcswidth.Stringwidth(self.input_state.lines[self.input_state.cursor.Y][:self.input_state.cursor.X])
  76. x := len(wcswidth.TruncateToVisualLength(line, w))
  77. return lines, Position{X: x, Y: self.input_state.cursor.Y}
  78. }
  79. func (self *Readline) get_screen_lines() []*ScreenLine {
  80. if self.screen_width == 0 || self.screen_height == 0 {
  81. self.update_current_screen_size()
  82. }
  83. lines, cursor := self.apply_syntax_highlighting()
  84. ans := make([]*ScreenLine, 0, len(lines))
  85. found_cursor := false
  86. cursor_at_start_of_next_line := false
  87. for i, line := range lines {
  88. prompt := self.prompt_for_line_number(i)
  89. offset := 0
  90. has_cursor := i == cursor.Y
  91. for is_first := true; is_first || offset < len(line); is_first = false {
  92. l, width := wcswidth.TruncateToVisualLengthWithWidth(line[offset:], self.screen_width-prompt.Length)
  93. sl := ScreenLine{
  94. ParentLineNumber: i, OffsetInParentLine: offset,
  95. Prompt: prompt, TextLengthInCells: width,
  96. CursorCell: -1, Text: l, CursorTextPos: -1, AfterLineBreak: is_first,
  97. }
  98. if cursor_at_start_of_next_line {
  99. cursor_at_start_of_next_line = false
  100. sl.CursorCell = prompt.Length
  101. sl.CursorTextPos = 0
  102. found_cursor = true
  103. }
  104. ans = append(ans, &sl)
  105. if has_cursor && !found_cursor && offset <= cursor.X && cursor.X <= offset+len(l) {
  106. found_cursor = true
  107. ctpos := cursor.X - offset
  108. ccell := prompt.Length + wcswidth.Stringwidth(l[:ctpos])
  109. if ccell >= self.screen_width {
  110. if offset+len(l) < len(line) || i < len(lines)-1 {
  111. cursor_at_start_of_next_line = true
  112. } else {
  113. ans = append(ans, &ScreenLine{ParentLineNumber: i, OffsetInParentLine: len(line)})
  114. }
  115. } else {
  116. sl.CursorTextPos = ctpos
  117. sl.CursorCell = ccell
  118. }
  119. }
  120. prompt = Prompt{}
  121. offset += len(l)
  122. }
  123. }
  124. return ans
  125. }
  126. func (self *Readline) redraw() {
  127. if self.screen_width == 0 || self.screen_height == 0 {
  128. self.update_current_screen_size()
  129. }
  130. if self.screen_width < 4 {
  131. return
  132. }
  133. if self.cursor_y > 0 {
  134. self.loop.MoveCursorVertically(-self.cursor_y)
  135. }
  136. self.loop.QueueWriteString("\r")
  137. self.loop.ClearToEndOfScreen()
  138. prompt_lines := self.get_screen_lines()
  139. csl, csl_cached := self.completion_screen_lines()
  140. render_completion_above := len(csl)+len(prompt_lines) > self.screen_height
  141. completion_needs_render := len(csl) > 0 && (!render_completion_above || !self.completions.current.last_rendered_above || !csl_cached)
  142. final_cursor_x := -1
  143. cursor_y := 0
  144. move_cursor_up_by := 0
  145. render_completion_lines := func() int {
  146. if completion_needs_render {
  147. if render_completion_above {
  148. self.loop.QueueWriteString("\r")
  149. } else {
  150. self.loop.QueueWriteString("\r\n")
  151. }
  152. for i, cl := range csl {
  153. self.loop.QueueWriteString(cl)
  154. if i < len(csl)-1 || render_completion_above {
  155. self.loop.QueueWriteString("\n\r")
  156. }
  157. }
  158. return len(csl)
  159. }
  160. return 0
  161. }
  162. self.loop.AllowLineWrapping(false)
  163. if render_completion_above {
  164. render_completion_lines()
  165. }
  166. self.loop.AllowLineWrapping(true)
  167. self.loop.QueueWriteString("\r")
  168. text_length := 0
  169. for i, sl := range prompt_lines {
  170. cursor_moved_down := false
  171. if i > 0 && sl.AfterLineBreak {
  172. self.loop.QueueWriteString("\r\n")
  173. cursor_moved_down = true
  174. text_length = 0
  175. }
  176. if sl.Prompt.Length > 0 {
  177. p := self.prompt_for_line_number(i)
  178. self.loop.QueueWriteString(p.Text)
  179. text_length += p.Length
  180. }
  181. self.loop.QueueWriteString(sl.Text)
  182. text_length += sl.TextLengthInCells
  183. if text_length == self.screen_width && sl.Text == "" && i == len(prompt_lines)-1 {
  184. self.loop.QueueWriteString("\r\n")
  185. cursor_moved_down = true
  186. text_length = 0
  187. }
  188. if text_length > self.screen_width {
  189. cursor_moved_down = true
  190. text_length -= self.screen_width
  191. }
  192. if sl.CursorCell > -1 {
  193. final_cursor_x = sl.CursorCell
  194. } else if final_cursor_x > -1 {
  195. if cursor_moved_down {
  196. move_cursor_up_by++
  197. }
  198. }
  199. if cursor_moved_down {
  200. cursor_y++
  201. }
  202. }
  203. if !render_completion_above {
  204. move_cursor_up_by += render_completion_lines()
  205. }
  206. self.loop.MoveCursorVertically(-move_cursor_up_by)
  207. self.loop.QueueWriteString("\r")
  208. self.loop.MoveCursorHorizontally(final_cursor_x)
  209. self.cursor_y = 0
  210. cursor_y -= move_cursor_up_by
  211. if cursor_y > 0 {
  212. self.cursor_y = cursor_y
  213. }
  214. }