ui.go 21 KB

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