diff --git a/kittens/diff/mouse.go b/kittens/diff/mouse.go index de3074b23..8edc14483 100644 --- a/kittens/diff/mouse.go +++ b/kittens/diff/mouse.go @@ -6,10 +6,10 @@ import ( "fmt" "path/filepath" "strconv" - "strings" "kitty" "kitty/tools/config" + "kitty/tools/tui" "kitty/tools/tui/loop" "kitty/tools/utils" "kitty/tools/wcswidth" @@ -50,6 +50,38 @@ func (self *Handler) handle_wheel_event(up bool) { 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) { @@ -61,14 +93,7 @@ func (self *Handler) start_mouse_selection(ev *loop.MouseEvent) { if ll.line_type == EMPTY_LINE || ll.line_type == IMAGE_LINE { return } - - min_x := self.logical_lines.margin_size - max_x := available_cols - 1 - if ev.Cell.X >= available_cols { - min_x += available_cols - max_x += available_cols - } - self.mouse_selection.StartNewSelection(ev, &pos, min_x, max_x, 0, self.screen_size.num_lines-1, self.screen_size.cell_width, self.screen_size.cell_height) + 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) { @@ -79,7 +104,8 @@ func (self *Handler) update_mouse_selection(ev *loop.MouseEvent) { y := ev.Cell.Y y = utils.Max(0, utils.Min(y, self.screen_size.num_lines-1)) self.logical_lines.IncrementScrollPosBy(&pos, y) - self.mouse_selection.Update(ev, &pos) + x := self.mouse_selection.StartLine().MinX() + self.mouse_selection.Update(ev, self.line_pos_from_pos(x, pos)) self.draw_screen() } @@ -92,31 +118,39 @@ func (self *Handler) text_for_current_mouse_selection() string { return "" } text := make([]byte, 0, 2048) - start, end := *self.mouse_selection.StartLine().(*ScrollPos), *self.mouse_selection.EndLine().(*ScrollPos) - for pos, prev_ll_idx := start, start.logical_line; pos.Less(end) || pos.Equal(&end); self.logical_lines.IncrementScrollPosBy(&pos, 1) { + 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 = self.logical_lines.ScreenLineAt(pos) + line = line_for_pos(pos) } default: - line = self.logical_lines.ScreenLineAt(pos) + line = line_for_pos(pos) } line = wcswidth.StripEscapeCodes(line) - s, e := self.mouse_selection.LineBounds(&pos) + 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 and trailing spaces as per it - tline := strings.TrimRight(line, " ") - if len(tline) < len(line) { - line = tline + " " - } + // 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 } @@ -140,6 +174,11 @@ func (self *Handler) finish_mouse_selection(ev *loop.MouseEvent) { } } -func (self *Handler) add_mouse_selection_to_line(line string, line_pos ScrollPos, y int) string { - return line + self.mouse_selection.LineFormatSuffix(&line_pos, selection_sgr, y) +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) } diff --git a/kittens/diff/render.go b/kittens/diff/render.go index a41ef268c..bf6daf262 100644 --- a/kittens/diff/render.go +++ b/kittens/diff/render.go @@ -39,6 +39,14 @@ type HalfScreenLine struct { marked_up_margin_text string marked_up_text string is_filler bool + cached_wcswidth int +} + +func (self *HalfScreenLine) wcswidth() int { + if self.cached_wcswidth == 0 && self.marked_up_text != "" { + self.cached_wcswidth = wcswidth.Stringwidth(self.marked_up_text) + } + return self.cached_wcswidth } type ScreenLine struct { @@ -466,10 +474,10 @@ func lines_for_context_chunk(data *DiffData, hunk_num int, chunk *Chunk, chunk_n left_line_number_s := strconv.Itoa(left_line_number + 1) right_line_number_s := strconv.Itoa(right_line_number + 1) for _, text := range splitlines(data.left_lines[left_line_number], data.available_cols) { - left_line := HalfScreenLine{left_line_number_s, text, false} + left_line := HalfScreenLine{marked_up_margin_text: left_line_number_s, marked_up_text: text} right_line := left_line if right_line_number_s != left_line_number_s { - right_line = HalfScreenLine{right_line_number_s, text, false} + right_line = HalfScreenLine{marked_up_margin_text: right_line_number_s, marked_up_text: text} } ll.screen_lines = append(ll.screen_lines, &ScreenLine{left_line, right_line}) left_line_number_s, right_line_number_s = "", "" @@ -494,7 +502,7 @@ func render_half_line(line_number int, line, ltype string, available_cols int, c } lnum := strconv.Itoa(line_number + 1) for _, sc := range splitlines(line, available_cols) { - ans = append(ans, HalfScreenLine{lnum, sc, false}) + ans = append(ans, HalfScreenLine{marked_up_margin_text: lnum, marked_up_text: sc}) lnum = "" } return ans diff --git a/kittens/diff/ui.go b/kittens/diff/ui.go index 53f2eea8f..d5c5514e6 100644 --- a/kittens/diff/ui.go +++ b/kittens/diff/ui.go @@ -38,20 +38,6 @@ func (self ScrollPos) Less(other ScrollPos) bool { return self.logical_line < other.logical_line || (self.logical_line == other.logical_line && self.screen_line < other.screen_line) } -func (self *ScrollPos) Equal(other tui.LinePos) bool { - if o, ok := other.(*ScrollPos); ok { - return *self == *o - } - return false -} - -func (self *ScrollPos) LessThan(other tui.LinePos) bool { - if o, ok := other.(*ScrollPos); ok { - return self.Less(*o) - } - return false -} - func (self ScrollPos) Add(other ScrollPos) ScrollPos { return ScrollPos{self.logical_line + other.logical_line, self.screen_line + other.screen_line} } @@ -361,16 +347,19 @@ func (self *Handler) draw_screen() { for num_written := 0; num_written < self.screen_size.num_lines; num_written++ { ll := self.logical_lines.At(pos.logical_line) is_image := ll != nil && ll.line_type == IMAGE_LINE - sl := self.logical_lines.ScreenLineAt(pos) + ll.render_screen_line(pos.screen_line, lp, self.logical_lines.margin_size, self.logical_lines.columns) if is_image && !seen_images.Has(pos.logical_line) && pos.screen_line >= ll.image_lines_offset { seen_images.Add(pos.logical_line) self.draw_image_pair(ll, pos.screen_line-ll.image_lines_offset) } if self.current_search != nil { - sl = self.current_search.markup_line(sl, pos) + if mkp := self.current_search.markup_line(pos, num_written); mkp != "" { + lp.QueueWriteString(mkp) + } + } + if mkp := self.add_mouse_selection_to_line(pos, num_written); mkp != "" { + lp.QueueWriteString(mkp) } - sl = self.add_mouse_selection_to_line(sl, pos, num_written) - lp.QueueWriteString(strings.ReplaceAll(sl, FILLER_CHAR, " ")) lp.MoveCursorVertically(1) lp.QueueWriteString("\x1b[m\r") if self.logical_lines.IncrementScrollPosBy(&pos, 1) == 0 { diff --git a/tools/tui/mouse.go b/tools/tui/mouse.go index 19ba7130e..ff6055ef1 100644 --- a/tools/tui/mouse.go +++ b/tools/tui/mouse.go @@ -14,6 +14,8 @@ var _ = fmt.Print type LinePos interface { LessThan(other LinePos) bool Equal(other LinePos) bool + MinX() int + MaxX() int } type SelectionBoundary struct { @@ -48,7 +50,6 @@ func (self *SelectionBoundary) Equal(other SelectionBoundary) bool { type MouseSelection struct { start, end SelectionBoundary is_active bool - min_x, max_x int min_y, max_y int cell_width, cell_height int } @@ -58,10 +59,10 @@ func (self *MouseSelection) IsActive() bool { return self.is_active } func (self *MouseSelection) Finish() { self.is_active = false } func (self *MouseSelection) Clear() { *self = MouseSelection{} } -func (ms *MouseSelection) StartNewSelection(ev *loop.MouseEvent, line LinePos, min_x, max_x, min_y, max_y, cell_width, cell_height int) { - *ms = MouseSelection{min_x: min_x, max_x: max_x, cell_width: cell_width, cell_height: cell_height, min_y: min_y, max_y: max_y} +func (ms *MouseSelection) StartNewSelection(ev *loop.MouseEvent, line LinePos, min_y, max_y, cell_width, cell_height int) { + *ms = MouseSelection{cell_width: cell_width, cell_height: cell_height, min_y: min_y, max_y: max_y} ms.start.line = line - ms.start.x = utils.Max(ms.min_x, utils.Min(ev.Cell.X, ms.max_x)) + ms.start.x = utils.Max(line.MinX(), utils.Min(ev.Cell.X, line.MaxX())) cell_start := cell_width * ev.Cell.X ms.start.in_first_half_of_cell = ev.Pixel.X <= cell_start+cell_width/2 ms.end = ms.start @@ -70,7 +71,7 @@ func (ms *MouseSelection) StartNewSelection(ev *loop.MouseEvent, line LinePos, m func (ms *MouseSelection) Update(ev *loop.MouseEvent, line LinePos) { if ms.is_active { - ms.end.x = utils.Max(ms.min_x, utils.Min(ev.Cell.X, ms.max_x)) + ms.end.x = utils.Max(line.MinX(), utils.Min(ev.Cell.X, line.MaxX())) cell_start := ms.cell_width * ms.end.x ms.end.in_first_half_of_cell = ev.Pixel.X <= cell_start+ms.cell_width/2 ms.end.line = line @@ -126,13 +127,13 @@ func (ms *MouseSelection) LineBounds(line_pos LinePos) (start_x, end_x int) { if a.line.LessThan(line_pos) { if line_pos.LessThan(b.line) { - return ms.min_x, ms.max_x + return line_pos.MinX(), line_pos.MaxX() } else if b.line.Equal(line_pos) { - return adjust_end(ms.min_x, b) + return adjust_end(line_pos.MinX(), b) } } else if a.line.Equal(line_pos) { if line_pos.LessThan(b.line) { - return adjust_start(a, ms.max_x) + return adjust_start(a, line_pos.MaxX()) } else if b.line.Equal(line_pos) { return adjust_both(a, b) }