More work on porting diff kitten

This commit is contained in:
Kovid Goyal 2023-03-20 17:21:53 +05:30
parent 4f5fc1000d
commit 1c7d1094d4
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 259 additions and 10 deletions

View File

@ -53,11 +53,13 @@ func set_diff_command(q string) error {
return err return err
} }
type Center struct{ prefix_count, suffix_count int }
type Chunk struct { type Chunk struct {
is_context bool is_context bool
left_start, right_start int left_start, right_start int
left_count, right_count int left_count, right_count int
centers []struct{ prefix_count, suffix_count int } centers []Center
} }
func (self *Chunk) add_line() { func (self *Chunk) add_line() {

View File

@ -18,6 +18,7 @@ type LineType int
const ( const (
TITLE_LINE LineType = iota TITLE_LINE LineType = iota
CHANGE_LINE CHANGE_LINE
HUNK_TITLE_LINE
IMAGE_LINE IMAGE_LINE
EMPTY_LINE EMPTY_LINE
) )
@ -56,7 +57,7 @@ func place_in(text string, sz int) string {
return fill_in(fit_in(text, sz), sz) return fill_in(fit_in(text, sz), sz)
} }
var title_format, text_format, margin_format, added_format, removed_format, added_margin_format, removed_margin_format, filler_format, margin_filler_format, hunk_margin_format, hunk_format func(...any) string var title_format, text_format, margin_format, added_format, removed_format, added_margin_format, removed_margin_format, filler_format, margin_filler_format, hunk_margin_format, hunk_format, added_center, removed_center func(...any) string
func create_formatters() { func create_formatters() {
ctx := style.Context{AllowEscapeCodes: true} ctx := style.Context{AllowEscapeCodes: true}
@ -75,6 +76,31 @@ func create_formatters() {
margin_format = ctx.SprintFunc(fmt.Sprintf("fg=%s bg=%s", conf.Margin_fg.AsRGBSharp(), conf.Margin_bg.AsRGBSharp())) margin_format = ctx.SprintFunc(fmt.Sprintf("fg=%s bg=%s", conf.Margin_fg.AsRGBSharp(), conf.Margin_bg.AsRGBSharp()))
hunk_format = ctx.SprintFunc(fmt.Sprintf("fg=%s bg=%s", conf.Margin_fg.AsRGBSharp(), conf.Hunk_bg.AsRGBSharp())) hunk_format = ctx.SprintFunc(fmt.Sprintf("fg=%s bg=%s", conf.Margin_fg.AsRGBSharp(), conf.Hunk_bg.AsRGBSharp()))
hunk_margin_format = ctx.SprintFunc(fmt.Sprintf("fg=%s bg=%s", conf.Margin_fg.AsRGBSharp(), conf.Hunk_margin_bg.AsRGBSharp())) hunk_margin_format = ctx.SprintFunc(fmt.Sprintf("fg=%s bg=%s", conf.Margin_fg.AsRGBSharp(), conf.Hunk_margin_bg.AsRGBSharp()))
make_bracketer := func(start, end string) func(...any) string {
s, e := ctx.SprintFunc(start), ctx.SprintFunc(end)
end = e(" ")
idx := strings.LastIndexByte(end, ' ')
end = end[:idx]
start = s(" ")
idx = strings.LastIndexByte(start, ' ')
start = start[:idx]
return func(args ...any) string {
return start + fmt.Sprint(args) + end
}
}
added_center = make_bracketer("bg="+conf.Highlight_added_bg.AsRGBSharp(), "bg="+conf.Added_bg.AsRGBSharp())
removed_center = make_bracketer("bg="+conf.Highlight_removed_bg.AsRGBSharp(), "bg="+conf.Removed_bg.AsRGBSharp())
}
func highlight_boundaries(ltype, text string) string {
switch ltype {
case "add":
return added_center(text)
case "remove":
return removed_center(text)
}
return text
} }
func title_lines(left_path, right_path string, columns, margin_size int, ans []*LogicalLine) []*LogicalLine { func title_lines(left_path, right_path string, columns, margin_size int, ans []*LogicalLine) []*LogicalLine {
@ -103,7 +129,207 @@ type LogicalLines struct {
margin_size, columns int margin_size, columns int
} }
func render(collection *Collection, diff_map map[string]*Patch, columns int) (*LogicalLines, error) { func image_lines(left_path, right_path string, columns, margin_size int, ans []*LogicalLine) ([]*LogicalLine, error) {
// TODO: Implement this
return ans, nil
}
func human_readable(size int) string {
divisor, suffix := 1, "B"
for i, candidate := range []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"} {
if size < (1 << ((i + 1) * 10)) {
divisor, suffix = (1 << (i * 10)), candidate
break
}
}
fs := float64(size) / float64(divisor)
s := strconv.FormatFloat(fs, 'f', 2, 64)
if idx := strings.Index(s, "."); idx > -1 {
s = s[:idx+2]
}
if strings.HasSuffix(s, ".0") || strings.HasSuffix(s, ".00") {
idx := strings.IndexByte(s, '.')
s = s[:idx]
}
return s + " " + suffix
}
func render_diff_line(number, text, ltype string, margin_size int, available_cols int) string {
m, c := margin_format, text_format
switch ltype {
case `filler`:
m = margin_filler_format
c = filler_format
case `remove`:
m = removed_margin_format
c = removed_format
case `add`:
m = added_margin_format
c = added_format
}
margin := m(place_in(number, margin_size))
content := c(fill_in(text, available_cols))
return margin + content
}
func binary_lines(left_path, right_path string, columns, margin_size int, ans []*LogicalLine) (ans2 []*LogicalLine, err error) {
available_cols := columns/2 - margin_size
fl := func(path string, formatter func(...any) string) string {
if err == nil {
var data string
data, err = data_for_path(path)
text := fmt.Sprintf("Binary file: %s", human_readable(len(data)))
text = place_in(text, available_cols)
return margin_format(strings.Repeat(` `, margin_size)) + formatter(text)
}
return ""
}
line := ""
if left_path == "" {
filler := render_diff_line(``, ``, `filler`, margin_size, available_cols)
line = filler + fl(right_path, added_format)
} else if right_path == "" {
filler := render_diff_line(``, ``, `filler`, margin_size, available_cols)
line = fl(left_path, removed_format) + filler
} else {
line = fl(left_path, removed_format) + fl(right_path, added_format)
}
ll := LogicalLine{line_type: CHANGE_LINE, src: Reference{path: left_path, linenum: 0}, screen_lines: []string{line}}
return append(ans, &ll), err
}
type DiffData struct {
left_path, right_path string
available_cols, margin_size int
left_lines, right_lines []string
filler_line, left_filler_line, right_filler_line string
}
func hunk_title(hunk_num int, hunk *Hunk, margin_size, available_cols int) string {
m := hunk_margin_format(strings.Repeat(" ", margin_size))
t := fmt.Sprintf("@@ %d,%d +%d,%d @@ %s", hunk.left_start+1, hunk.left_count, hunk.right_start+1, hunk.right_count, hunk.title)
return m + hunk_format(place_in(t, available_cols))
}
func lines_for_context_chunk(data *DiffData, hunk_num int, chunk *Chunk, chunk_num int, ans []*LogicalLine) []*LogicalLine {
for i := 0; i < chunk.left_count; i++ {
left_line_number := chunk.left_start + i
right_line_number := chunk.right_start + i
ll := LogicalLine{line_type: CHANGE_LINE, src: Reference{path: data.left_path, linenum: left_line_number}}
left_line_number_s := strconv.Itoa(left_line_number + 1)
right_line_number_s := strconv.Itoa(right_line_number + 1)
for _, text := range style.WrapTextAsLines(data.left_lines[left_line_number], "", data.available_cols) {
line := render_diff_line(left_line_number_s, text, `context`, data.margin_size, data.available_cols)
if right_line_number_s == left_line_number_s {
line += line
} else {
line += render_diff_line(right_line_number_s, text, `context`, data.margin_size, data.available_cols)
}
ll.screen_lines = append(ll.screen_lines, line)
left_line_number_s, right_line_number_s = "", ""
}
ans = append(ans, &ll)
}
return ans
}
func render_half_line(line_number int, line, ltype string, margin_size, available_cols int, center Center, ans []string) []string {
if center.prefix_count > 0 {
line_sz := len(line)
if center.prefix_count+center.suffix_count < line_sz {
end := len(line) - center.suffix_count
line = line[:center.prefix_count] + highlight_boundaries(ltype, line[center.prefix_count:end]) + line[end:]
}
}
lnum := strconv.Itoa(line_number + 1)
for _, sc := range style.WrapTextAsLines(line, "", available_cols) {
ans = append(ans, render_diff_line(lnum, sc, ltype, margin_size, available_cols))
}
return ans
}
func lines_for_diff_chunk(data *DiffData, hunk_num int, chunk *Chunk, chunk_num int, ans []*LogicalLine) []*LogicalLine {
common := utils.Min(chunk.left_count, chunk.right_count)
for i := 0; i < utils.Max(chunk.left_count, chunk.right_count); i++ {
var ll, rl []string
ref_ln, ref_path := 0, ""
var center Center
if i < len(chunk.centers) {
center = chunk.centers[i]
}
if i < chunk.left_count {
ref_path = data.left_path
ref_ln = chunk.left_start + i
ll = render_half_line(ref_ln, data.left_lines[ref_ln], "remove", data.margin_size, data.available_cols, center, ll)
}
if i < chunk.right_count {
ref_path = data.right_path
ref_ln = chunk.right_start + i
ll = render_half_line(ref_ln, data.right_lines[ref_ln], "add", data.margin_size, data.available_cols, center, ll)
}
if i < common {
extra := len(ll) - len(rl)
if extra < 0 {
ll = append(ll, utils.Repeat(data.left_filler_line, -extra)...)
} else if extra > 0 {
rl = append(rl, utils.Repeat(data.right_filler_line, extra)...)
}
} else {
if len(ll) > 0 {
rl = append(rl, utils.Repeat(data.filler_line, len(ll))...)
} else if len(rl) > 0 {
ll = append(ll, utils.Repeat(data.filler_line, len(rl))...)
}
}
logline := LogicalLine{line_type: CHANGE_LINE, src: Reference{path: ref_path, linenum: ref_ln}}
for l := 0; l < len(ll); l++ {
logline.screen_lines = append(logline.screen_lines, ll[l]+rl[l])
}
ans = append(ans, &logline)
}
return ans
}
func lines_for_diff(left_path string, right_path string, patch *Patch, columns, margin_size int, ans []*LogicalLine) (result []*LogicalLine, err error) {
available_cols := columns/2 - margin_size
data := DiffData{left_path: left_path, right_path: right_path, available_cols: available_cols, margin_size: margin_size}
if left_path != "" {
data.left_lines, err = lines_for_path(left_path)
if err != nil {
return
}
}
if right_path != "" {
data.right_lines, err = lines_for_path(right_path)
if err != nil {
return
}
}
data.filler_line = render_diff_line("", "", "filler", margin_size, available_cols)
data.left_filler_line = render_diff_line("", "", "remove", margin_size, available_cols)
data.right_filler_line = render_diff_line("", "", "add", margin_size, available_cols)
ht := LogicalLine{line_type: HUNK_TITLE_LINE, src: Reference{path: left_path}}
for hunk_num, hunk := range patch.all_hunks {
htl := ht
htl.src.linenum = hunk.left_start
htl.screen_lines = []string{hunk_title(hunk_num, hunk, margin_size, columns-margin_size)}
ans = append(ans, &htl)
for cnum, chunk := range hunk.chunks {
if chunk.is_context {
ans = lines_for_context_chunk(&data, hunk_num, chunk, cnum, ans)
} else {
ans = lines_for_diff_chunk(&data, hunk_num, chunk, cnum, ans)
}
}
}
return ans, nil
}
func render(collection *Collection, diff_map map[string]*Patch, columns int) (result *LogicalLines, err error) {
largest_line_number := 0 largest_line_number := 0
collection.Apply(func(path, typ, changed_path string) error { collection.Apply(func(path, typ, changed_path string) error {
if typ == "diff" { if typ == "diff" {
@ -117,7 +343,7 @@ func render(collection *Collection, diff_map map[string]*Patch, columns int) (*L
margin_size := utils.Max(3, len(strconv.Itoa(largest_line_number))+1) margin_size := utils.Max(3, len(strconv.Itoa(largest_line_number))+1)
ans := make([]*LogicalLine, 0, 1024) ans := make([]*LogicalLine, 0, 1024)
empty_line := LogicalLine{line_type: EMPTY_LINE} empty_line := LogicalLine{line_type: EMPTY_LINE}
err := collection.Apply(func(path, item_type, changed_path string) error { err = collection.Apply(func(path, item_type, changed_path string) error {
ans = title_lines(path, changed_path, columns, margin_size, ans) ans = title_lines(path, changed_path, columns, margin_size, ans)
defer func() { defer func() {
el := empty_line el := empty_line
@ -130,14 +356,27 @@ func render(collection *Collection, diff_map map[string]*Patch, columns int) (*L
} }
is_img := is_binary && is_image(path) || (item_type == `diff` && is_image(changed_path)) is_img := is_binary && is_image(path) || (item_type == `diff` && is_image(changed_path))
_ = is_img _ = is_img
switch item_type {
case "diff":
if is_binary {
if is_img {
ans, err = image_lines(path, changed_path, columns, margin_size, ans)
} else {
ans, err = binary_lines(path, changed_path, columns, margin_size, ans)
}
} else {
ans, err = lines_for_diff(path, changed_path, diff_map[path], columns, margin_size, ans)
}
if err != nil {
return err
}
case "add":
case "removal":
case "rename":
}
return nil return nil
}) })
if err != nil { return &LogicalLines{lines: ans[:len(ans)-1], margin_size: margin_size, columns: columns}, err
return nil, err
}
return &LogicalLines{lines: ans[:len(ans)-1], margin_size: margin_size, columns: columns}, nil
} }
func (self *LogicalLines) num_of_screen_lines() (ans int) { func (self *LogicalLines) num_of_screen_lines() (ans int) {

View File

@ -66,6 +66,14 @@ func Map[T any, O any](f func(x T) O, s []T) []O {
return ans return ans
} }
func Repeat[T any](x T, n int) []T {
ans := make([]T, n)
for i := 0; i < n; i++ {
ans[i] = x
}
return ans
}
func Sort[T any](s []T, less func(a, b T) bool) []T { func Sort[T any](s []T, less func(a, b T) bool) []T {
sort.Slice(s, func(i, j int) bool { return less(s[i], s[j]) }) sort.Slice(s, func(i, j int) bool { return less(s[i], s[j]) })
return s return s