mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-06 01:05:48 +02:00
committed by
Kovid Goyal
parent
c78592174d
commit
f45345c7a7
@@ -152,6 +152,12 @@ opt('dark_highlight_added_bg', '#31503d', option_type='to_color')
|
||||
opt('added_margin_bg', '#cdffd8', option_type='to_color')
|
||||
opt('dark_added_margin_bg', '#31503d', option_type='to_color')
|
||||
|
||||
opt('moved_bg', '#fffde7', option_type='to_color', long_text='Moved text backgrounds (same text that was removed in one place and added in another)')
|
||||
opt('dark_moved_bg', '#2c2200', option_type='to_color')
|
||||
|
||||
opt('moved_margin_bg', '#fff3b0', option_type='to_color')
|
||||
opt('dark_moved_margin_bg', '#4a3800', option_type='to_color')
|
||||
|
||||
opt('filler_bg', '#fafbfc', option_type='to_color', long_text='Filler (empty) line background')
|
||||
opt('dark_filler_bg', '#262c36', option_type='to_color')
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"sync"
|
||||
|
||||
parallel "github.com/kovidgoyal/go-parallel"
|
||||
"github.com/kovidgoyal/kitty/tools/simdstring"
|
||||
"github.com/kovidgoyal/kitty/tools/utils"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/images"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/shlex"
|
||||
@@ -336,6 +337,7 @@ func (self *Hunk) finalize(left_lines, right_lines []string) error {
|
||||
type Patch struct {
|
||||
all_hunks []*Hunk
|
||||
largest_line_number, added_count, removed_count int
|
||||
left_moved_lines, right_moved_lines *utils.Set[int]
|
||||
}
|
||||
|
||||
func (self *Patch) Len() int { return len(self.all_hunks) }
|
||||
@@ -451,6 +453,62 @@ func (self *Patch) compute_centers(left_lines, right_lines []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *Patch) detect_moved_lines(left_lines, right_lines []string) {
|
||||
// Build maps from line text to lists of line numbers for removed and added lines.
|
||||
removed := make(map[string][]int) // text -> left line numbers
|
||||
added := make(map[string][]int) // text -> right line numbers
|
||||
// Use SIMD to efficiently find non-blank lines: a line is non-blank if it
|
||||
// contains at least one character that is not a space or tab.
|
||||
is_non_blank := func(text string) bool {
|
||||
for len(text) > 0 {
|
||||
idx := simdstring.IndexByte2String(text, ' ', '\t')
|
||||
if idx != 0 {
|
||||
// idx < 0: no space/tab found, remaining chars are non-blank;
|
||||
// idx > 0: non-blank chars exist before the first space/tab.
|
||||
return true
|
||||
}
|
||||
text = text[1:]
|
||||
}
|
||||
return false
|
||||
}
|
||||
for _, hunk := range self.all_hunks {
|
||||
for _, chunk := range hunk.chunks {
|
||||
if !chunk.is_context {
|
||||
for i := 0; i < chunk.left_count; i++ {
|
||||
lnum := chunk.left_start + i
|
||||
text := left_lines[lnum]
|
||||
if is_non_blank(text) {
|
||||
removed[text] = append(removed[text], lnum)
|
||||
}
|
||||
}
|
||||
for i := 0; i < chunk.right_count; i++ {
|
||||
rnum := chunk.right_start + i
|
||||
text := right_lines[rnum]
|
||||
if is_non_blank(text) {
|
||||
added[text] = append(added[text], rnum)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Lines that appear in both removed and added sets are moved lines. When a
|
||||
// line appears multiple times on each side, only min(left_count,
|
||||
// right_count) occurrences are marked as moved.
|
||||
self.left_moved_lines = utils.NewSet[int]()
|
||||
self.right_moved_lines = utils.NewSet[int]()
|
||||
for text, lnums := range removed {
|
||||
if rnums, ok := added[text]; ok {
|
||||
count := min(len(lnums), len(rnums))
|
||||
for _, lnum := range lnums[:count] {
|
||||
self.left_moved_lines.Add(lnum)
|
||||
}
|
||||
for _, rnum := range rnums[:count] {
|
||||
self.right_moved_lines.Add(rnum)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parse_patch(raw string, left_lines, right_lines []string) (ans *Patch, err error) {
|
||||
ans = &Patch{all_hunks: make([]*Hunk, 0, 32)}
|
||||
var current_hunk *Hunk
|
||||
@@ -486,6 +544,9 @@ func parse_patch(raw string, left_lines, right_lines []string) (ans *Patch, err
|
||||
ans.largest_line_number = ans.all_hunks[len(ans.all_hunks)-1].largest_line_number
|
||||
}
|
||||
err = ans.compute_centers(left_lines, right_lines)
|
||||
if err == nil {
|
||||
ans.detect_moved_lines(left_lines, right_lines)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ type HalfScreenLine struct {
|
||||
marked_up_margin_text string
|
||||
marked_up_text string
|
||||
is_filler bool
|
||||
is_moved bool
|
||||
cached_wcswidth int
|
||||
}
|
||||
|
||||
@@ -81,6 +82,9 @@ func (self *LogicalLine) render_screen_line(n int, lp *loop.Loop, margin_size, c
|
||||
if sl.left.is_filler {
|
||||
left_margin = format_as_sgr.margin_filler + left_margin
|
||||
left_text = format_as_sgr.filler + left_text
|
||||
} else if sl.left.is_moved {
|
||||
left_margin = format_as_sgr.moved_margin + left_margin
|
||||
left_text = format_as_sgr.moved + left_text
|
||||
} else {
|
||||
switch self.line_type {
|
||||
case CHANGE_LINE, IMAGE_LINE:
|
||||
@@ -104,6 +108,9 @@ func (self *LogicalLine) render_screen_line(n int, lp *loop.Loop, margin_size, c
|
||||
if sl.right.is_filler {
|
||||
right_margin = format_as_sgr.margin_filler + right_margin
|
||||
right_text = format_as_sgr.filler + right_text
|
||||
} else if sl.right.is_moved {
|
||||
right_margin = format_as_sgr.moved_margin + right_margin
|
||||
right_text = format_as_sgr.moved + right_text
|
||||
} else {
|
||||
switch self.line_type {
|
||||
case CHANGE_LINE, IMAGE_LINE:
|
||||
@@ -156,7 +163,7 @@ func place_in(text string, sz int) string {
|
||||
}
|
||||
|
||||
var format_as_sgr struct {
|
||||
title, margin, added, removed, added_margin, removed_margin, filler, margin_filler, hunk_margin, hunk, selection, search string
|
||||
title, margin, added, removed, added_margin, removed_margin, filler, margin_filler, hunk_margin, hunk, selection, search, moved, moved_margin string
|
||||
}
|
||||
|
||||
var statusline_format, added_count_format, removed_count_format, message_format func(...any) string
|
||||
@@ -175,6 +182,8 @@ type ResolvedColors struct {
|
||||
Margin_bg style.RGBA
|
||||
Margin_fg style.RGBA
|
||||
Margin_filler_bg style.NullableColor
|
||||
Moved_bg style.RGBA
|
||||
Moved_margin_bg style.RGBA
|
||||
Removed_bg style.RGBA
|
||||
Removed_margin_bg style.RGBA
|
||||
Search_bg style.RGBA
|
||||
@@ -202,6 +211,8 @@ func create_formatters() {
|
||||
rc.Margin_bg = conf.Dark_margin_bg
|
||||
rc.Margin_fg = conf.Dark_margin_fg
|
||||
rc.Margin_filler_bg = conf.Dark_margin_filler_bg
|
||||
rc.Moved_bg = conf.Dark_moved_bg
|
||||
rc.Moved_margin_bg = conf.Dark_moved_margin_bg
|
||||
rc.Removed_bg = conf.Dark_removed_bg
|
||||
rc.Removed_margin_bg = conf.Dark_removed_margin_bg
|
||||
rc.Search_bg = conf.Dark_search_bg
|
||||
@@ -223,6 +234,8 @@ func create_formatters() {
|
||||
rc.Margin_bg = conf.Margin_bg
|
||||
rc.Margin_fg = conf.Margin_fg
|
||||
rc.Margin_filler_bg = conf.Margin_filler_bg
|
||||
rc.Moved_bg = conf.Moved_bg
|
||||
rc.Moved_margin_bg = conf.Moved_margin_bg
|
||||
rc.Removed_bg = conf.Removed_bg
|
||||
rc.Removed_margin_bg = conf.Removed_margin_bg
|
||||
rc.Search_bg = conf.Search_bg
|
||||
@@ -248,6 +261,8 @@ func create_formatters() {
|
||||
format_as_sgr.added_margin = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Margin_fg.AsRGBSharp(), rc.Added_margin_bg.AsRGBSharp()))
|
||||
format_as_sgr.removed = only_open("bg=" + rc.Removed_bg.AsRGBSharp())
|
||||
format_as_sgr.removed_margin = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Margin_fg.AsRGBSharp(), rc.Removed_margin_bg.AsRGBSharp()))
|
||||
format_as_sgr.moved = only_open("bg=" + rc.Moved_bg.AsRGBSharp())
|
||||
format_as_sgr.moved_margin = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Margin_fg.AsRGBSharp(), rc.Moved_margin_bg.AsRGBSharp()))
|
||||
format_as_sgr.title = only_open(fmt.Sprintf("fg=%s bg=%s bold", rc.Title_fg.AsRGBSharp(), rc.Title_bg.AsRGBSharp()))
|
||||
format_as_sgr.margin = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Margin_fg.AsRGBSharp(), rc.Margin_bg.AsRGBSharp()))
|
||||
format_as_sgr.hunk = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Margin_fg.AsRGBSharp(), rc.Hunk_bg.AsRGBSharp()))
|
||||
@@ -532,7 +547,9 @@ type DiffData struct {
|
||||
left_path, right_path string
|
||||
available_cols, margin_size int
|
||||
|
||||
left_lines, right_lines []string
|
||||
left_lines, right_lines []string
|
||||
left_moved_lines *utils.Set[int]
|
||||
right_moved_lines *utils.Set[int]
|
||||
}
|
||||
|
||||
func hunk_title(hunk *Hunk) string {
|
||||
@@ -567,7 +584,7 @@ func splitlines(text string, width int) []string {
|
||||
return style.WrapTextAsLines(text, width, style.WrapOptions{})
|
||||
}
|
||||
|
||||
func render_half_line(line_number int, line, ltype string, available_cols int, center Center, ans []HalfScreenLine) []HalfScreenLine {
|
||||
func render_half_line(line_number int, line, ltype string, available_cols int, center Center, is_moved bool, ans []HalfScreenLine) []HalfScreenLine {
|
||||
var regions []Region
|
||||
if ltype == "remove" {
|
||||
regions = center.left_regions
|
||||
@@ -583,7 +600,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{marked_up_margin_text: lnum, marked_up_text: sc})
|
||||
ans = append(ans, HalfScreenLine{marked_up_margin_text: lnum, marked_up_text: sc, is_moved: is_moved})
|
||||
lnum = ""
|
||||
}
|
||||
return ans
|
||||
@@ -601,13 +618,15 @@ func lines_for_diff_chunk(data *DiffData, _ int, chunk *Chunk, _ int, ans []*Log
|
||||
}
|
||||
if i < chunk.left_count {
|
||||
left_lnum = chunk.left_start + i
|
||||
ll = render_half_line(left_lnum, data.left_lines[left_lnum], "remove", data.available_cols, center, ll)
|
||||
left_is_moved := data.left_moved_lines != nil && data.left_moved_lines.Has(left_lnum)
|
||||
ll = render_half_line(left_lnum, data.left_lines[left_lnum], "remove", data.available_cols, center, left_is_moved, ll)
|
||||
left_lnum++
|
||||
}
|
||||
|
||||
if i < chunk.right_count {
|
||||
right_lnum = chunk.right_start + i
|
||||
rl = render_half_line(right_lnum, data.right_lines[right_lnum], "add", data.available_cols, center, rl)
|
||||
right_is_moved := data.right_moved_lines != nil && data.right_moved_lines.Has(right_lnum)
|
||||
rl = render_half_line(right_lnum, data.right_lines[right_lnum], "add", data.available_cols, center, right_is_moved, rl)
|
||||
right_lnum++
|
||||
}
|
||||
|
||||
@@ -663,7 +682,10 @@ func lines_for_diff(left_path string, right_path string, patch *Patch, columns,
|
||||
return append(ans, &ht), nil
|
||||
}
|
||||
available_cols := columns/2 - margin_size
|
||||
data := DiffData{left_path: left_path, right_path: right_path, available_cols: available_cols, margin_size: margin_size}
|
||||
data := DiffData{
|
||||
left_path: left_path, right_path: right_path, available_cols: available_cols, margin_size: margin_size,
|
||||
left_moved_lines: patch.left_moved_lines, right_moved_lines: patch.right_moved_lines,
|
||||
}
|
||||
if left_path != "" {
|
||||
data.left_lines, err = highlighted_lines_for_path(left_path)
|
||||
if err != nil {
|
||||
@@ -720,7 +742,7 @@ func all_lines(path string, columns, margin_size int, is_add bool, ans []*Logica
|
||||
}
|
||||
for line_number, line := range lines {
|
||||
hlines := make([]HalfScreenLine, 0, 8)
|
||||
hlines = render_half_line(line_number, line, ltype, available_cols, Center{}, hlines)
|
||||
hlines = render_half_line(line_number, line, ltype, available_cols, Center{}, false, hlines)
|
||||
l := ll
|
||||
if is_add {
|
||||
l.right_reference.linenum = line_number + 1
|
||||
|
||||
Reference in New Issue
Block a user