kitty/kittens/diff/mouse.go

185 lines
5.3 KiB
Go

// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
package diff
import (
"fmt"
"path/filepath"
"strconv"
"kitty"
"kitty/tools/config"
"kitty/tools/tui"
"kitty/tools/tui/loop"
"kitty/tools/utils"
"kitty/tools/wcswidth"
)
var _ = fmt.Print
type KittyOpts struct {
Wheel_scroll_multiplier int
}
func read_relevant_kitty_opts(path string) KittyOpts {
ans := KittyOpts{Wheel_scroll_multiplier: kitty.KittyConfigDefaults.Wheel_scroll_multiplier}
handle_line := func(key, val string) error {
switch key {
case "wheel_scroll_multiplier":
v, err := strconv.Atoi(val)
if err == nil {
ans.Wheel_scroll_multiplier = v
}
}
return nil
}
cp := config.ConfigParser{LineHandler: handle_line}
cp.ParseFiles(path)
return ans
}
var RelevantKittyOpts = (&utils.Once[KittyOpts]{Run: func() KittyOpts {
return read_relevant_kitty_opts(filepath.Join(utils.ConfigDir(), "kitty.conf"))
}}).Get
func (self *Handler) handle_wheel_event(up bool) {
amt := RelevantKittyOpts().Wheel_scroll_multiplier
if up {
amt *= -1
}
self.dispatch_action(`scroll_by`, strconv.Itoa(amt))
}
type line_pos struct {
min_x, max_x int
y ScrollPos
}
func (self *line_pos) MinX() int { return self.min_x }
func (self *line_pos) MaxX() int { return self.max_x }
func (self *line_pos) Equal(other tui.LinePos) bool {
if o, ok := other.(*line_pos); ok {
return self.y == o.y
}
return false
}
func (self *line_pos) LessThan(other tui.LinePos) bool {
if o, ok := other.(*line_pos); ok {
return self.y.Less(o.y)
}
return false
}
func (self *Handler) line_pos_from_pos(x int, pos ScrollPos) *line_pos {
ans := line_pos{min_x: self.logical_lines.margin_size, y: pos}
available_cols := self.logical_lines.columns / 2
if x >= available_cols {
ans.min_x += available_cols
ans.max_x = utils.Max(ans.min_x, ans.min_x+self.logical_lines.ScreenLineAt(pos).right.wcswidth()-1)
} else {
ans.max_x = utils.Max(ans.min_x, ans.min_x+self.logical_lines.ScreenLineAt(pos).left.wcswidth()-1)
}
return &ans
}
func (self *Handler) start_mouse_selection(ev *loop.MouseEvent) {
available_cols := self.logical_lines.columns / 2
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) {
return
}
pos := self.scroll_pos
self.logical_lines.IncrementScrollPosBy(&pos, ev.Cell.Y)
ll := self.logical_lines.At(pos.logical_line)
if ll.line_type == EMPTY_LINE || ll.line_type == IMAGE_LINE {
return
}
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)
}
func (self *Handler) update_mouse_selection(ev *loop.MouseEvent) {
if !self.mouse_selection.IsActive() {
return
}
pos := self.scroll_pos
y := ev.Cell.Y
y = utils.Max(0, utils.Min(y, self.screen_size.num_lines-1))
self.logical_lines.IncrementScrollPosBy(&pos, y)
x := self.mouse_selection.StartLine().MinX()
self.mouse_selection.Update(ev, self.line_pos_from_pos(x, pos))
self.draw_screen()
}
func (self *Handler) clear_mouse_selection() {
self.mouse_selection.Clear()
}
func (self *Handler) text_for_current_mouse_selection() string {
if self.mouse_selection.IsEmpty() {
return ""
}
text := make([]byte, 0, 2048)
start_pos, end_pos := *self.mouse_selection.StartLine().(*line_pos), *self.mouse_selection.EndLine().(*line_pos)
start, end := start_pos.y, end_pos.y
is_left := start_pos.min_x == self.logical_lines.margin_size
line_for_pos := func(pos ScrollPos) string {
if is_left {
return self.logical_lines.ScreenLineAt(pos).left.marked_up_text
}
return self.logical_lines.ScreenLineAt(pos).right.marked_up_text
}
for pos, prev_ll_idx := start, start.logical_line; pos.Less(end) || pos == end; self.logical_lines.IncrementScrollPosBy(&pos, 1) {
ll := self.logical_lines.At(pos.logical_line)
var line string
switch ll.line_type {
case EMPTY_LINE:
case IMAGE_LINE:
if pos.screen_line < ll.image_lines_offset {
line = line_for_pos(pos)
}
default:
line = line_for_pos(pos)
}
line = wcswidth.StripEscapeCodes(line)
s, e := self.mouse_selection.LineBounds(self.line_pos_from_pos(start_pos.min_x, pos))
s -= start_pos.min_x
e -= start_pos.min_x
line = wcswidth.TruncateToVisualLength(line, e+1)
if s > 0 {
prefix := wcswidth.TruncateToVisualLength(line, s)
line = line[len(prefix):]
}
// TODO: look at the original line from the source and handle leading tabs as per it
if pos.logical_line > prev_ll_idx {
line = "\n" + line
}
prev_ll_idx = pos.logical_line
if line != "" {
text = append(text, line...)
}
}
return utils.UnsafeBytesToString(text)
}
func (self *Handler) finish_mouse_selection(ev *loop.MouseEvent) {
if !self.mouse_selection.IsActive() {
return
}
self.update_mouse_selection(ev)
self.mouse_selection.Finish()
text := self.text_for_current_mouse_selection()
if text != "" {
self.lp.CopyTextToPrimarySelection(text)
}
}
func (self *Handler) add_mouse_selection_to_line(line_pos ScrollPos, y int) string {
if self.mouse_selection.IsEmpty() {
return ""
}
selection_sgr := format_as_sgr.selection
x := self.mouse_selection.StartLine().MinX()
return self.mouse_selection.LineFormatSuffix(self.line_pos_from_pos(x, line_pos), selection_sgr[2:len(selection_sgr)-1], y)
}