mouse.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. // License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package diff
  3. import (
  4. "fmt"
  5. "path/filepath"
  6. "strconv"
  7. "strings"
  8. "sync"
  9. "kitty"
  10. "kitty/tools/config"
  11. "kitty/tools/tty"
  12. "kitty/tools/tui"
  13. "kitty/tools/tui/loop"
  14. "kitty/tools/utils"
  15. "kitty/tools/wcswidth"
  16. )
  17. var _ = fmt.Print
  18. type KittyOpts struct {
  19. Wheel_scroll_multiplier int
  20. Copy_on_select bool
  21. }
  22. func read_relevant_kitty_opts(path string) KittyOpts {
  23. ans := KittyOpts{Wheel_scroll_multiplier: kitty.KittyConfigDefaults.Wheel_scroll_multiplier}
  24. handle_line := func(key, val string) error {
  25. switch key {
  26. case "wheel_scroll_multiplier":
  27. v, err := strconv.Atoi(val)
  28. if err == nil {
  29. ans.Wheel_scroll_multiplier = v
  30. }
  31. case "copy_on_select":
  32. ans.Copy_on_select = strings.ToLower(val) == "clipboard"
  33. }
  34. return nil
  35. }
  36. cp := config.ConfigParser{LineHandler: handle_line}
  37. _ = cp.ParseFiles(path)
  38. return ans
  39. }
  40. var RelevantKittyOpts = sync.OnceValue(func() KittyOpts {
  41. return read_relevant_kitty_opts(filepath.Join(utils.ConfigDir(), "kitty.conf"))
  42. })
  43. func (self *Handler) handle_wheel_event(up bool) {
  44. amt := RelevantKittyOpts().Wheel_scroll_multiplier
  45. if up {
  46. amt *= -1
  47. }
  48. _ = self.dispatch_action(`scroll_by`, strconv.Itoa(amt))
  49. }
  50. type line_pos struct {
  51. min_x, max_x int
  52. y ScrollPos
  53. }
  54. func (self *line_pos) MinX() int { return self.min_x }
  55. func (self *line_pos) MaxX() int { return self.max_x }
  56. func (self *line_pos) Equal(other tui.LinePos) bool {
  57. if o, ok := other.(*line_pos); ok {
  58. return self.y == o.y
  59. }
  60. return false
  61. }
  62. func (self *line_pos) LessThan(other tui.LinePos) bool {
  63. if o, ok := other.(*line_pos); ok {
  64. return self.y.Less(o.y)
  65. }
  66. return false
  67. }
  68. func (self *Handler) line_pos_from_pos(x int, pos ScrollPos) *line_pos {
  69. ans := line_pos{min_x: self.logical_lines.margin_size, y: pos}
  70. available_cols := self.logical_lines.columns / 2
  71. if x >= available_cols {
  72. ans.min_x += available_cols
  73. ans.max_x = utils.Max(ans.min_x, ans.min_x+self.logical_lines.ScreenLineAt(pos).right.wcswidth()-1)
  74. } else {
  75. ans.max_x = utils.Max(ans.min_x, ans.min_x+self.logical_lines.ScreenLineAt(pos).left.wcswidth()-1)
  76. }
  77. return &ans
  78. }
  79. func (self *Handler) start_mouse_selection(ev *loop.MouseEvent) {
  80. available_cols := self.logical_lines.columns / 2
  81. if ev.Cell.Y >= self.screen_size.num_lines || ev.Cell.X < self.logical_lines.margin_size || (ev.Cell.X >= available_cols && ev.Cell.X < available_cols+self.logical_lines.margin_size) {
  82. return
  83. }
  84. pos := self.scroll_pos
  85. self.logical_lines.IncrementScrollPosBy(&pos, ev.Cell.Y)
  86. ll := self.logical_lines.At(pos.logical_line)
  87. if ll.line_type == EMPTY_LINE || ll.line_type == IMAGE_LINE {
  88. return
  89. }
  90. self.mouse_selection.StartNewSelection(ev, self.line_pos_from_pos(ev.Cell.X, pos), 0, self.screen_size.num_lines-1, self.screen_size.cell_width, self.screen_size.cell_height)
  91. }
  92. func (self *Handler) drag_scroll_tick(timer_id loop.IdType) error {
  93. return self.mouse_selection.DragScrollTick(timer_id, self.lp, self.drag_scroll_tick, func(amt int, ev *loop.MouseEvent) error {
  94. if self.scroll_lines(amt) != 0 {
  95. self.do_update_mouse_selection(ev)
  96. self.draw_screen()
  97. }
  98. return nil
  99. })
  100. }
  101. var debugprintln = tty.DebugPrintln
  102. func (self *Handler) update_mouse_selection(ev *loop.MouseEvent) {
  103. if !self.mouse_selection.IsActive() {
  104. return
  105. }
  106. if self.mouse_selection.OutOfVerticalBounds(ev) {
  107. self.mouse_selection.DragScroll(ev, self.lp, self.drag_scroll_tick)
  108. return
  109. }
  110. self.do_update_mouse_selection(ev)
  111. }
  112. func (self *Handler) do_update_mouse_selection(ev *loop.MouseEvent) {
  113. pos := self.scroll_pos
  114. y := ev.Cell.Y
  115. y = utils.Max(0, utils.Min(y, self.screen_size.num_lines-1))
  116. self.logical_lines.IncrementScrollPosBy(&pos, y)
  117. x := self.mouse_selection.StartLine().MinX()
  118. self.mouse_selection.Update(ev, self.line_pos_from_pos(x, pos))
  119. self.draw_screen()
  120. }
  121. func (self *Handler) clear_mouse_selection() {
  122. self.mouse_selection.Clear()
  123. }
  124. func (self *Handler) text_for_current_mouse_selection() string {
  125. if self.mouse_selection.IsEmpty() {
  126. return ""
  127. }
  128. text := make([]byte, 0, 2048)
  129. start_pos, end_pos := *self.mouse_selection.StartLine().(*line_pos), *self.mouse_selection.EndLine().(*line_pos)
  130. // if start is after end, swap them
  131. if end_pos.y.Less(start_pos.y) {
  132. start_pos, end_pos = end_pos, start_pos
  133. }
  134. start, end := start_pos.y, end_pos.y
  135. is_left := start_pos.min_x == self.logical_lines.margin_size
  136. line_for_pos := func(pos ScrollPos) string {
  137. if is_left {
  138. return self.logical_lines.ScreenLineAt(pos).left.marked_up_text
  139. }
  140. return self.logical_lines.ScreenLineAt(pos).right.marked_up_text
  141. }
  142. for pos, prev_ll_idx := start, start.logical_line; pos.Less(end) || pos == end; {
  143. ll := self.logical_lines.At(pos.logical_line)
  144. var line string
  145. switch ll.line_type {
  146. case EMPTY_LINE:
  147. case IMAGE_LINE:
  148. if pos.screen_line < ll.image_lines_offset {
  149. line = line_for_pos(pos)
  150. }
  151. default:
  152. line = line_for_pos(pos)
  153. }
  154. line = wcswidth.StripEscapeCodes(line)
  155. s, e := self.mouse_selection.LineBounds(self.line_pos_from_pos(start_pos.min_x, pos))
  156. s -= start_pos.min_x
  157. e -= start_pos.min_x
  158. line = wcswidth.TruncateToVisualLength(line, e+1)
  159. if s > 0 {
  160. prefix := wcswidth.TruncateToVisualLength(line, s)
  161. line = line[len(prefix):]
  162. }
  163. // TODO: look at the original line from the source and handle leading tabs as per it
  164. if pos.logical_line > prev_ll_idx {
  165. line = "\n" + line
  166. }
  167. prev_ll_idx = pos.logical_line
  168. if line != "" {
  169. text = append(text, line...)
  170. }
  171. if self.logical_lines.IncrementScrollPosBy(&pos, 1) == 0 {
  172. break
  173. }
  174. }
  175. return utils.UnsafeBytesToString(text)
  176. }
  177. func (self *Handler) finish_mouse_selection(ev *loop.MouseEvent) {
  178. if !self.mouse_selection.IsActive() {
  179. return
  180. }
  181. self.update_mouse_selection(ev)
  182. self.mouse_selection.Finish()
  183. text := self.text_for_current_mouse_selection()
  184. if text != "" {
  185. if RelevantKittyOpts().Copy_on_select {
  186. self.lp.CopyTextToClipboard(text)
  187. } else {
  188. self.lp.CopyTextToPrimarySelection(text)
  189. }
  190. }
  191. }
  192. func (self *Handler) add_mouse_selection_to_line(line_pos ScrollPos, y int) string {
  193. if self.mouse_selection.IsEmpty() {
  194. return ""
  195. }
  196. selection_sgr := format_as_sgr.selection
  197. x := self.mouse_selection.StartLine().MinX()
  198. return self.mouse_selection.LineFormatSuffix(self.line_pos_from_pos(x, line_pos), selection_sgr[2:len(selection_sgr)-1], y)
  199. }