ui.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687
  1. // License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package diff
  3. import (
  4. "fmt"
  5. "regexp"
  6. "strconv"
  7. "strings"
  8. "kitty/tools/config"
  9. "kitty/tools/tui"
  10. "kitty/tools/tui/graphics"
  11. "kitty/tools/tui/loop"
  12. "kitty/tools/tui/readline"
  13. "kitty/tools/utils"
  14. "kitty/tools/wcswidth"
  15. )
  16. var _ = fmt.Print
  17. type ResultType int
  18. const (
  19. COLLECTION ResultType = iota
  20. DIFF
  21. HIGHLIGHT
  22. IMAGE_LOAD
  23. IMAGE_RESIZE
  24. )
  25. type ScrollPos struct {
  26. logical_line, screen_line int
  27. }
  28. func (self ScrollPos) Less(other ScrollPos) bool {
  29. return self.logical_line < other.logical_line || (self.logical_line == other.logical_line && self.screen_line < other.screen_line)
  30. }
  31. func (self ScrollPos) Add(other ScrollPos) ScrollPos {
  32. return ScrollPos{self.logical_line + other.logical_line, self.screen_line + other.screen_line}
  33. }
  34. type AsyncResult struct {
  35. err error
  36. rtype ResultType
  37. collection *Collection
  38. diff_map map[string]*Patch
  39. page_size graphics.Size
  40. }
  41. var image_collection *graphics.ImageCollection
  42. type screen_size struct{ rows, columns, num_lines, cell_width, cell_height int }
  43. type Handler struct {
  44. async_results chan AsyncResult
  45. mouse_selection tui.MouseSelection
  46. image_count int
  47. shortcut_tracker config.ShortcutTracker
  48. left, right string
  49. collection *Collection
  50. diff_map map[string]*Patch
  51. logical_lines *LogicalLines
  52. lp *loop.Loop
  53. current_context_count, original_context_count int
  54. added_count, removed_count int
  55. screen_size screen_size
  56. scroll_pos, max_scroll_pos ScrollPos
  57. restore_position *ScrollPos
  58. inputting_command bool
  59. statusline_message string
  60. rl *readline.Readline
  61. current_search *Search
  62. current_search_is_regex, current_search_is_backward bool
  63. largest_line_number int
  64. images_resized_to graphics.Size
  65. }
  66. func (self *Handler) calculate_statistics() {
  67. self.added_count, self.removed_count = self.collection.added_count, self.collection.removed_count
  68. self.largest_line_number = 0
  69. for _, patch := range self.diff_map {
  70. self.added_count += patch.added_count
  71. self.removed_count += patch.removed_count
  72. self.largest_line_number = utils.Max(patch.largest_line_number, self.largest_line_number)
  73. }
  74. }
  75. func (self *Handler) update_screen_size(sz loop.ScreenSize) {
  76. self.screen_size.rows = int(sz.HeightCells)
  77. self.screen_size.columns = int(sz.WidthCells)
  78. self.screen_size.num_lines = self.screen_size.rows - 1
  79. self.screen_size.cell_height = int(sz.CellHeight)
  80. self.screen_size.cell_width = int(sz.CellWidth)
  81. }
  82. func (self *Handler) on_escape_code(etype loop.EscapeCodeType, payload []byte) error {
  83. switch etype {
  84. case loop.APC:
  85. gc := graphics.GraphicsCommandFromAPC(payload)
  86. if gc != nil {
  87. if !image_collection.HandleGraphicsCommand(gc) {
  88. self.draw_screen()
  89. }
  90. }
  91. }
  92. return nil
  93. }
  94. func (self *Handler) finalize() {
  95. image_collection.Finalize(self.lp)
  96. }
  97. func (self *Handler) initialize() {
  98. self.rl = readline.New(self.lp, readline.RlInit{DontMarkPrompts: true, Prompt: "/"})
  99. self.lp.OnEscapeCode = self.on_escape_code
  100. image_collection = graphics.NewImageCollection()
  101. self.current_context_count = opts.Context
  102. if self.current_context_count < 0 {
  103. self.current_context_count = int(conf.Num_context_lines)
  104. }
  105. sz, _ := self.lp.ScreenSize()
  106. self.update_screen_size(sz)
  107. self.original_context_count = self.current_context_count
  108. self.lp.SetDefaultColor(loop.FOREGROUND, conf.Foreground)
  109. self.lp.SetDefaultColor(loop.CURSOR, conf.Foreground)
  110. self.lp.SetDefaultColor(loop.BACKGROUND, conf.Background)
  111. self.lp.SetDefaultColor(loop.SELECTION_BG, conf.Select_bg)
  112. if conf.Select_fg.IsSet {
  113. self.lp.SetDefaultColor(loop.SELECTION_FG, conf.Select_fg.Color)
  114. }
  115. self.async_results = make(chan AsyncResult, 32)
  116. go func() {
  117. r := AsyncResult{}
  118. r.collection, r.err = create_collection(self.left, self.right)
  119. self.async_results <- r
  120. self.lp.WakeupMainThread()
  121. }()
  122. self.draw_screen()
  123. }
  124. func (self *Handler) generate_diff() {
  125. self.diff_map = nil
  126. jobs := make([]diff_job, 0, 32)
  127. _ = self.collection.Apply(func(path, typ, changed_path string) error {
  128. if typ == "diff" {
  129. if is_path_text(path) && is_path_text(changed_path) {
  130. jobs = append(jobs, diff_job{path, changed_path})
  131. }
  132. }
  133. return nil
  134. })
  135. go func() {
  136. r := AsyncResult{rtype: DIFF}
  137. r.diff_map, r.err = diff(jobs, self.current_context_count)
  138. self.async_results <- r
  139. self.lp.WakeupMainThread()
  140. }()
  141. }
  142. func (self *Handler) on_wakeup() error {
  143. var r AsyncResult
  144. for {
  145. select {
  146. case r = <-self.async_results:
  147. if r.err != nil {
  148. return r.err
  149. }
  150. r.err = self.handle_async_result(r)
  151. if r.err != nil {
  152. return r.err
  153. }
  154. default:
  155. return nil
  156. }
  157. }
  158. }
  159. func (self *Handler) highlight_all() {
  160. text_files := utils.Filter(self.collection.paths_to_highlight.AsSlice(), is_path_text)
  161. go func() {
  162. r := AsyncResult{rtype: HIGHLIGHT}
  163. highlight_all(text_files)
  164. self.async_results <- r
  165. self.lp.WakeupMainThread()
  166. }()
  167. }
  168. func (self *Handler) load_all_images() {
  169. _ = self.collection.Apply(func(path, item_type, changed_path string) error {
  170. if path != "" && is_image(path) {
  171. image_collection.AddPaths(path)
  172. self.image_count++
  173. }
  174. if changed_path != "" && is_image(changed_path) {
  175. image_collection.AddPaths(changed_path)
  176. self.image_count++
  177. }
  178. return nil
  179. })
  180. if self.image_count > 0 {
  181. image_collection.Initialize(self.lp)
  182. go func() {
  183. r := AsyncResult{rtype: IMAGE_LOAD}
  184. image_collection.LoadAll()
  185. self.async_results <- r
  186. self.lp.WakeupMainThread()
  187. }()
  188. }
  189. }
  190. func (self *Handler) resize_all_images_if_needed() {
  191. if self.logical_lines == nil {
  192. return
  193. }
  194. margin_size := self.logical_lines.margin_size
  195. columns := self.logical_lines.columns
  196. available_cols := columns/2 - margin_size
  197. sz := graphics.Size{
  198. Width: available_cols * self.screen_size.cell_width,
  199. Height: self.screen_size.num_lines * 2 * self.screen_size.cell_height,
  200. }
  201. if sz != self.images_resized_to && self.image_count > 0 {
  202. go func() {
  203. image_collection.ResizeForPageSize(sz.Width, sz.Height)
  204. r := AsyncResult{rtype: IMAGE_RESIZE, page_size: sz}
  205. self.async_results <- r
  206. self.lp.WakeupMainThread()
  207. }()
  208. }
  209. }
  210. func (self *Handler) rerender_diff() error {
  211. if self.diff_map != nil && self.collection != nil {
  212. err := self.render_diff()
  213. if err != nil {
  214. return err
  215. }
  216. self.draw_screen()
  217. }
  218. return nil
  219. }
  220. func (self *Handler) handle_async_result(r AsyncResult) error {
  221. switch r.rtype {
  222. case COLLECTION:
  223. self.collection = r.collection
  224. self.generate_diff()
  225. self.highlight_all()
  226. self.load_all_images()
  227. case DIFF:
  228. self.diff_map = r.diff_map
  229. self.calculate_statistics()
  230. self.clear_mouse_selection()
  231. err := self.render_diff()
  232. if err != nil {
  233. return err
  234. }
  235. self.scroll_pos = ScrollPos{}
  236. if self.restore_position != nil {
  237. self.scroll_pos = *self.restore_position
  238. if self.max_scroll_pos.Less(self.scroll_pos) {
  239. self.scroll_pos = self.max_scroll_pos
  240. }
  241. self.restore_position = nil
  242. }
  243. self.draw_screen()
  244. case IMAGE_RESIZE:
  245. self.images_resized_to = r.page_size
  246. return self.rerender_diff()
  247. case IMAGE_LOAD, HIGHLIGHT:
  248. return self.rerender_diff()
  249. }
  250. return nil
  251. }
  252. func (self *Handler) on_resize(old_size, new_size loop.ScreenSize) error {
  253. self.clear_mouse_selection()
  254. self.update_screen_size(new_size)
  255. if self.diff_map != nil && self.collection != nil {
  256. err := self.render_diff()
  257. if err != nil {
  258. return err
  259. }
  260. if self.max_scroll_pos.Less(self.scroll_pos) {
  261. self.scroll_pos = self.max_scroll_pos
  262. }
  263. }
  264. self.draw_screen()
  265. return nil
  266. }
  267. func (self *Handler) render_diff() (err error) {
  268. if self.screen_size.columns < 8 {
  269. return fmt.Errorf("Screen too narrow, need at least 8 columns")
  270. }
  271. if self.screen_size.rows < 2 {
  272. return fmt.Errorf("Screen too short, need at least 2 rows")
  273. }
  274. self.logical_lines, err = render(self.collection, self.diff_map, self.screen_size, self.largest_line_number, self.images_resized_to)
  275. if err != nil {
  276. return err
  277. }
  278. last := self.logical_lines.Len() - 1
  279. self.max_scroll_pos.logical_line = last
  280. if last > -1 {
  281. self.max_scroll_pos.screen_line = len(self.logical_lines.At(last).screen_lines) - 1
  282. } else {
  283. self.max_scroll_pos.screen_line = 0
  284. }
  285. self.logical_lines.IncrementScrollPosBy(&self.max_scroll_pos, -self.screen_size.num_lines+1)
  286. if self.current_search != nil {
  287. self.current_search.search(self.logical_lines)
  288. }
  289. return nil
  290. }
  291. func (self *Handler) draw_image(key string, num_rows, starting_row int) {
  292. image_collection.PlaceImageSubRect(self.lp, key, self.images_resized_to, 0, self.screen_size.cell_height*starting_row, -1, -1)
  293. }
  294. func (self *Handler) draw_image_pair(ll *LogicalLine, starting_row int) {
  295. if ll.left_image.key == "" && ll.right_image.key == "" {
  296. return
  297. }
  298. defer self.lp.QueueWriteString("\r")
  299. if ll.left_image.key != "" {
  300. self.lp.QueueWriteString("\r")
  301. self.lp.MoveCursorHorizontally(self.logical_lines.margin_size)
  302. self.draw_image(ll.left_image.key, ll.left_image.count, starting_row)
  303. }
  304. if ll.right_image.key != "" {
  305. self.lp.QueueWriteString("\r")
  306. self.lp.MoveCursorHorizontally(self.logical_lines.margin_size + self.logical_lines.columns/2)
  307. self.draw_image(ll.right_image.key, ll.right_image.count, starting_row)
  308. }
  309. }
  310. func (self *Handler) draw_screen() {
  311. self.lp.StartAtomicUpdate()
  312. defer self.lp.EndAtomicUpdate()
  313. if self.image_count > 0 {
  314. self.resize_all_images_if_needed()
  315. image_collection.DeleteAllVisiblePlacements(self.lp)
  316. }
  317. lp.MoveCursorTo(1, 1)
  318. lp.ClearToEndOfScreen()
  319. if self.logical_lines == nil || self.diff_map == nil || self.collection == nil {
  320. lp.Println(`Calculating diff, please wait...`)
  321. return
  322. }
  323. pos := self.scroll_pos
  324. seen_images := utils.NewSet[int]()
  325. for num_written := 0; num_written < self.screen_size.num_lines; num_written++ {
  326. ll := self.logical_lines.At(pos.logical_line)
  327. if ll == nil || self.logical_lines.ScreenLineAt(pos) == nil {
  328. num_written--
  329. } else {
  330. is_image := ll.line_type == IMAGE_LINE
  331. ll.render_screen_line(pos.screen_line, lp, self.logical_lines.margin_size, self.logical_lines.columns)
  332. if is_image && !seen_images.Has(pos.logical_line) && pos.screen_line >= ll.image_lines_offset {
  333. seen_images.Add(pos.logical_line)
  334. self.draw_image_pair(ll, pos.screen_line-ll.image_lines_offset)
  335. }
  336. if self.current_search != nil {
  337. if mkp := self.current_search.markup_line(pos, num_written); mkp != "" {
  338. lp.QueueWriteString(mkp)
  339. }
  340. }
  341. if mkp := self.add_mouse_selection_to_line(pos, num_written); mkp != "" {
  342. lp.QueueWriteString(mkp)
  343. }
  344. lp.MoveCursorVertically(1)
  345. lp.QueueWriteString("\x1b[m\r")
  346. }
  347. if self.logical_lines.IncrementScrollPosBy(&pos, 1) == 0 {
  348. break
  349. }
  350. }
  351. self.draw_status_line()
  352. }
  353. func (self *Handler) draw_status_line() {
  354. if self.logical_lines == nil || self.diff_map == nil {
  355. return
  356. }
  357. self.lp.MoveCursorTo(1, self.screen_size.rows)
  358. self.lp.ClearToEndOfLine()
  359. self.lp.SetCursorVisible(self.inputting_command)
  360. if self.inputting_command {
  361. self.rl.RedrawNonAtomic()
  362. } else if self.statusline_message != "" {
  363. self.lp.QueueWriteString(message_format(wcswidth.TruncateToVisualLength(sanitize(self.statusline_message), self.screen_size.columns)))
  364. } else {
  365. num := self.logical_lines.NumScreenLinesTo(self.scroll_pos)
  366. den := self.logical_lines.NumScreenLinesTo(self.max_scroll_pos)
  367. var frac int
  368. if den > 0 {
  369. frac = int((float64(num) * 100.0) / float64(den))
  370. }
  371. sp := statusline_format(fmt.Sprintf("%d%%", frac))
  372. var counts string
  373. if self.current_search == nil {
  374. counts = added_count_format(strconv.Itoa(self.added_count)) + statusline_format(`,`) + removed_count_format(strconv.Itoa(self.removed_count))
  375. } else {
  376. counts = statusline_format(fmt.Sprintf("%d matches", self.current_search.Len()))
  377. }
  378. suffix := counts + " " + sp
  379. prefix := statusline_format(":")
  380. filler := strings.Repeat(" ", utils.Max(0, self.screen_size.columns-wcswidth.Stringwidth(prefix)-wcswidth.Stringwidth(suffix)))
  381. self.lp.QueueWriteString(prefix + filler + suffix)
  382. }
  383. }
  384. func (self *Handler) on_text(text string, a, b bool) error {
  385. if self.inputting_command {
  386. defer self.draw_status_line()
  387. return self.rl.OnText(text, a, b)
  388. }
  389. if self.statusline_message != "" {
  390. self.statusline_message = ""
  391. self.draw_status_line()
  392. return nil
  393. }
  394. return nil
  395. }
  396. func (self *Handler) do_search(query string) {
  397. self.current_search = nil
  398. if len(query) < 2 {
  399. return
  400. }
  401. if !self.current_search_is_regex {
  402. query = regexp.QuoteMeta(query)
  403. }
  404. pat, err := regexp.Compile(`(?i)` + query)
  405. if err != nil {
  406. self.statusline_message = fmt.Sprintf("Bad regex: %s", err)
  407. self.lp.Beep()
  408. return
  409. }
  410. self.current_search = do_search(pat, self.logical_lines)
  411. if self.current_search.Len() == 0 {
  412. self.current_search = nil
  413. self.statusline_message = fmt.Sprintf("No matches for: %#v", query)
  414. self.lp.Beep()
  415. } else {
  416. if self.scroll_to_next_match(false, true) {
  417. self.draw_screen()
  418. } else {
  419. self.lp.Beep()
  420. }
  421. }
  422. }
  423. func (self *Handler) on_key_event(ev *loop.KeyEvent) error {
  424. if self.inputting_command {
  425. defer self.draw_status_line()
  426. if ev.MatchesPressOrRepeat("esc") {
  427. self.inputting_command = false
  428. ev.Handled = true
  429. return nil
  430. }
  431. if ev.MatchesPressOrRepeat("enter") {
  432. self.inputting_command = false
  433. ev.Handled = true
  434. self.do_search(self.rl.AllText())
  435. self.draw_screen()
  436. return nil
  437. }
  438. return self.rl.OnKeyEvent(ev)
  439. }
  440. if self.statusline_message != "" {
  441. if ev.Type != loop.RELEASE {
  442. ev.Handled = true
  443. self.statusline_message = ""
  444. self.draw_status_line()
  445. }
  446. return nil
  447. }
  448. if self.current_search != nil && ev.MatchesPressOrRepeat("esc") {
  449. self.current_search = nil
  450. self.draw_screen()
  451. return nil
  452. }
  453. ac := self.shortcut_tracker.Match(ev, conf.KeyboardShortcuts)
  454. if ac != nil {
  455. ev.Handled = true
  456. return self.dispatch_action(ac.Name, ac.Args)
  457. }
  458. return nil
  459. }
  460. func (self *Handler) scroll_lines(amt int) (delta int) {
  461. before := self.scroll_pos
  462. delta = self.logical_lines.IncrementScrollPosBy(&self.scroll_pos, amt)
  463. if delta > 0 && self.max_scroll_pos.Less(self.scroll_pos) {
  464. self.scroll_pos = self.max_scroll_pos
  465. delta = self.logical_lines.Minus(self.scroll_pos, before)
  466. }
  467. return
  468. }
  469. func (self *Handler) scroll_to_next_change(backwards bool) bool {
  470. if backwards {
  471. for i := self.scroll_pos.logical_line - 1; i >= 0; i-- {
  472. line := self.logical_lines.At(i)
  473. if line.is_change_start {
  474. self.scroll_pos = ScrollPos{i, 0}
  475. return true
  476. }
  477. }
  478. } else {
  479. for i := self.scroll_pos.logical_line + 1; i < self.logical_lines.Len(); i++ {
  480. line := self.logical_lines.At(i)
  481. if line.is_change_start {
  482. self.scroll_pos = ScrollPos{i, 0}
  483. return true
  484. }
  485. }
  486. }
  487. return false
  488. }
  489. func (self *Handler) scroll_to_next_match(backwards, include_current_match bool) bool {
  490. if self.current_search == nil {
  491. return false
  492. }
  493. if self.current_search_is_backward {
  494. backwards = !backwards
  495. }
  496. offset, delta := 1, 1
  497. if include_current_match {
  498. offset = 0
  499. }
  500. if backwards {
  501. offset *= -1
  502. delta *= -1
  503. }
  504. pos := self.scroll_pos
  505. if offset != 0 && self.logical_lines.IncrementScrollPosBy(&pos, offset) == 0 {
  506. return false
  507. }
  508. for {
  509. if self.current_search.Has(pos) {
  510. self.scroll_pos = pos
  511. self.draw_screen()
  512. return true
  513. }
  514. if self.logical_lines.IncrementScrollPosBy(&pos, delta) == 0 || self.max_scroll_pos.Less(pos) {
  515. break
  516. }
  517. }
  518. return false
  519. }
  520. func (self *Handler) change_context_count(val int) bool {
  521. val = utils.Max(0, val)
  522. if val == self.current_context_count {
  523. return false
  524. }
  525. self.current_context_count = val
  526. p := self.scroll_pos
  527. self.restore_position = &p
  528. self.clear_mouse_selection()
  529. self.generate_diff()
  530. self.draw_screen()
  531. return true
  532. }
  533. func (self *Handler) start_search(is_regex, is_backward bool) {
  534. if self.inputting_command {
  535. self.lp.Beep()
  536. return
  537. }
  538. self.inputting_command = true
  539. self.current_search_is_regex = is_regex
  540. self.current_search_is_backward = is_backward
  541. self.rl.SetText(``)
  542. self.draw_status_line()
  543. }
  544. func (self *Handler) dispatch_action(name, args string) error {
  545. switch name {
  546. case `quit`:
  547. self.lp.Quit(0)
  548. case `copy_to_clipboard`:
  549. text := self.text_for_current_mouse_selection()
  550. if text == "" {
  551. self.lp.Beep()
  552. } else {
  553. self.lp.CopyTextToClipboard(text)
  554. }
  555. case `copy_to_clipboard_or_exit`:
  556. text := self.text_for_current_mouse_selection()
  557. if text == "" {
  558. self.lp.Quit(0)
  559. } else {
  560. self.lp.CopyTextToClipboard(text)
  561. }
  562. case `scroll_by`:
  563. if args == "" {
  564. args = "1"
  565. }
  566. amt, err := strconv.Atoi(args)
  567. if err == nil {
  568. if self.scroll_lines(amt) == 0 {
  569. self.lp.Beep()
  570. } else {
  571. self.draw_screen()
  572. }
  573. } else {
  574. self.lp.Beep()
  575. }
  576. case `scroll_to`:
  577. done := false
  578. switch {
  579. case strings.Contains(args, `change`):
  580. done = self.scroll_to_next_change(strings.Contains(args, `prev`))
  581. case strings.Contains(args, `match`):
  582. done = self.scroll_to_next_match(strings.Contains(args, `prev`), false)
  583. case strings.Contains(args, `page`):
  584. amt := self.screen_size.num_lines
  585. if strings.Contains(args, `prev`) {
  586. amt *= -1
  587. }
  588. done = self.scroll_lines(amt) != 0
  589. default:
  590. npos := ScrollPos{}
  591. if strings.Contains(args, `end`) {
  592. npos = self.max_scroll_pos
  593. }
  594. done = npos != self.scroll_pos
  595. self.scroll_pos = npos
  596. }
  597. if done {
  598. self.draw_screen()
  599. } else {
  600. self.lp.Beep()
  601. }
  602. case `change_context`:
  603. new_ctx := self.current_context_count
  604. switch args {
  605. case `all`:
  606. new_ctx = 100000
  607. case `default`:
  608. new_ctx = self.original_context_count
  609. default:
  610. delta, _ := strconv.Atoi(args)
  611. new_ctx += delta
  612. }
  613. if !self.change_context_count(new_ctx) {
  614. self.lp.Beep()
  615. }
  616. case `start_search`:
  617. if self.diff_map != nil && self.logical_lines != nil {
  618. a, b, _ := strings.Cut(args, " ")
  619. self.start_search(config.StringToBool(a), config.StringToBool(b))
  620. }
  621. }
  622. return nil
  623. }
  624. func (self *Handler) on_mouse_event(ev *loop.MouseEvent) error {
  625. if self.logical_lines == nil {
  626. return nil
  627. }
  628. if ev.Event_type == loop.MOUSE_PRESS && ev.Buttons&(loop.MOUSE_WHEEL_UP|loop.MOUSE_WHEEL_DOWN) != 0 {
  629. self.handle_wheel_event(ev.Buttons&(loop.MOUSE_WHEEL_UP) != 0)
  630. return nil
  631. }
  632. if ev.Event_type == loop.MOUSE_PRESS && ev.Buttons&loop.LEFT_MOUSE_BUTTON != 0 {
  633. self.start_mouse_selection(ev)
  634. return nil
  635. }
  636. if ev.Event_type == loop.MOUSE_MOVE {
  637. self.update_mouse_selection(ev)
  638. return nil
  639. }
  640. if ev.Event_type == loop.MOUSE_RELEASE && ev.Buttons&loop.LEFT_MOUSE_BUTTON != 0 {
  641. self.finish_mouse_selection(ev)
  642. return nil
  643. }
  644. return nil
  645. }