More work on porting diff kitten

This commit is contained in:
Kovid Goyal 2023-03-19 17:17:41 +05:30
parent ee82cb5a52
commit 5d8b5ab720
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 210 additions and 8 deletions

View File

@ -64,9 +64,13 @@ func data_for_path(path string) (string, error) {
}) })
} }
func is_image(path string) bool {
return strings.HasPrefix(mimetype_for_path(path), "image/")
}
func is_path_text(path string) bool { func is_path_text(path string) bool {
return is_text_cache.MustGetOrCreate(path, func(path string) bool { return is_text_cache.MustGetOrCreate(path, func(path string) bool {
if strings.HasPrefix(mimetype_for_path(path), "image/") { if is_image(path) {
return false return false
} }
s1, err := os.Stat(path) s1, err := os.Stat(path)

View File

@ -109,6 +109,7 @@ func main(_ *cli.Command, opts_ *Options, args []string) (rc int, err error) {
return 1, err return 1, err
} }
init_caches() init_caches()
create_formatters()
defer func() { defer func() {
for tdir := range remote_dirs { for tdir := range remote_dirs {
os.RemoveAll(tdir) os.RemoveAll(tdir)
@ -148,6 +149,7 @@ func main(_ *cli.Command, opts_ *Options, args []string) (rc int, err error) {
lp.SetCursorVisible(true) lp.SetCursorVisible(true)
return "" return ""
} }
lp.OnResize = h.on_resize
err = lp.Run() err = lp.Run()
if err != nil { if err != nil {
return 1, err return 1, err

143
tools/cmd/diff/render.go Normal file
View File

@ -0,0 +1,143 @@
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
package diff
import (
"fmt"
"kitty/tools/utils"
"kitty/tools/utils/style"
"kitty/tools/wcswidth"
"strconv"
"strings"
)
var _ = fmt.Print
type LineType int
const (
TITLE_LINE LineType = iota
CHANGE_LINE
IMAGE_LINE
)
type Reference struct {
path string
linenum int
}
type LogicalLine struct {
src Reference
line_type LineType
margin_size, columns int
screen_lines []string
}
func fit_in(text string, count int) string {
truncated := wcswidth.TruncateToVisualLength(text, count)
if len(truncated) >= len(text) {
return text
}
if count > 1 {
truncated = wcswidth.TruncateToVisualLength(text, count-1)
}
return truncated + ``
}
func fill_in(text string, sz int) string {
w := wcswidth.Stringwidth(text)
if w < sz {
text += strings.Repeat(` `, (sz - w))
}
return text
}
func place_in(text string, sz int) string {
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
func create_formatters() {
ctx := style.Context{AllowEscapeCodes: true}
text_format = ctx.SprintFunc(fmt.Sprintf("bg=%s", conf.Background.AsRGBSharp()))
filler_format = ctx.SprintFunc(fmt.Sprintf("bg=%s", conf.Filler_bg.AsRGBSharp()))
if conf.Margin_filler_bg.IsNull {
margin_filler_format = ctx.SprintFunc(fmt.Sprintf("bg=%s", conf.Filler_bg.AsRGBSharp()))
} else {
margin_filler_format = ctx.SprintFunc(fmt.Sprintf("bg=%s", conf.Margin_filler_bg.Color.AsRGBSharp()))
}
added_format = ctx.SprintFunc(fmt.Sprintf("bg=%s", conf.Added_bg.AsRGBSharp()))
added_margin_format = ctx.SprintFunc(fmt.Sprintf("fg=%s bg=%s", conf.Margin_fg.AsRGBSharp(), conf.Added_margin_bg.AsRGBSharp()))
removed_format = ctx.SprintFunc(fmt.Sprintf("bg=%s", conf.Removed_bg.AsRGBSharp()))
removed_margin_format = ctx.SprintFunc(fmt.Sprintf("fg=%s bg=%s", conf.Margin_fg.AsRGBSharp(), conf.Removed_margin_bg.AsRGBSharp()))
title_format = ctx.SprintFunc(fmt.Sprintf("fg=%s bg=%s", conf.Title_fg.AsRGBSharp(), conf.Title_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_margin_format = ctx.SprintFunc(fmt.Sprintf("fg=%s bg=%s", conf.Margin_fg.AsRGBSharp(), conf.Hunk_margin_bg.AsRGBSharp()))
}
func title_lines(left_path, right_path string, columns, margin_size int, ans []*LogicalLine) []*LogicalLine {
left_name, right_name := path_name_map[left_path], path_name_map[right_path]
name := ""
m := strings.Repeat(` `, margin_size)
if right_name != "" && right_name != left_name {
n1 := fit_in(m+sanitize(left_name), columns/2-margin_size)
n1 = place_in(n1, columns/2)
n2 := fit_in(m+sanitize(right_name), columns/2-margin_size)
n2 = place_in(n2, columns/2)
name = n1 + n2
} else {
name = place_in(m+sanitize(left_name), columns)
}
ll := LogicalLine{columns: columns, margin_size: margin_size, line_type: TITLE_LINE, src: Reference{path: left_path, linenum: 0}}
l1 := ll
l1.screen_lines = []string{title_format(name)}
l2 := ll
l2.screen_lines = []string{title_format(name)}
return append(ans, &l1, &l2)
}
type LogicalLines struct {
lines []*LogicalLine
margin_size, columns int
}
func render(collection *Collection, diff_map map[string]*Patch, columns int) (*LogicalLines, error) {
largest_line_number := 0
collection.Apply(func(path, typ, changed_path string) error {
if typ == "diff" {
patch := diff_map[path]
if patch != nil {
largest_line_number = utils.Max(largest_line_number, patch.largest_line_number)
}
}
return nil
})
margin_size := utils.Max(3, len(strconv.Itoa(largest_line_number))+1)
ans := make([]*LogicalLine, 0, 1024)
err := collection.Apply(func(path, item_type, changed_path string) error {
ans = title_lines(path, changed_path, columns, margin_size, ans)
is_binary := !is_path_text(path)
if !is_binary && item_type == `diff` && !is_path_text(changed_path) {
is_binary = true
}
is_img := is_binary && is_image(path) || (item_type == `diff` && is_image(changed_path))
_ = is_img
return nil
})
if err != nil {
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) {
for _, l := range self.lines {
ans += len(l.screen_lines)
}
return
}

View File

@ -4,6 +4,7 @@ package diff
import ( import (
"fmt" "fmt"
"kitty/tools/tui/graphics"
"kitty/tools/tui/loop" "kitty/tools/tui/loop"
) )
@ -17,7 +18,8 @@ const (
HIGHLIGHT HIGHLIGHT
) )
type Reference struct { type ScrollPos struct {
logical_line, screen_line int
} }
type AsyncResult struct { type AsyncResult struct {
@ -32,9 +34,12 @@ type Handler struct {
left, right string left, right string
collection *Collection collection *Collection
diff_map map[string]*Patch diff_map map[string]*Patch
logical_lines *LogicalLines
lp *loop.Loop lp *loop.Loop
current_context_count, original_context_count int current_context_count, original_context_count int
added_count, removed_count int added_count, removed_count int
screen_size struct{ rows, columns, num_lines int }
scroll_pos ScrollPos
} }
func (self *Handler) calculate_statistics() { func (self *Handler) calculate_statistics() {
@ -45,11 +50,18 @@ func (self *Handler) calculate_statistics() {
} }
} }
var DebugPrintln func(...any)
func (self *Handler) initialize() { func (self *Handler) initialize() {
DebugPrintln = self.lp.DebugPrintln
self.current_context_count = opts.Context self.current_context_count = opts.Context
if self.current_context_count < 0 { if self.current_context_count < 0 {
self.current_context_count = int(conf.Num_context_lines) self.current_context_count = int(conf.Num_context_lines)
} }
sz, _ := self.lp.ScreenSize()
self.screen_size.rows = int(sz.HeightCells)
self.screen_size.columns = int(sz.WidthCells)
self.screen_size.num_lines = self.screen_size.rows - 1
self.original_context_count = self.current_context_count self.original_context_count = self.current_context_count
self.lp.SetDefaultColor(loop.FOREGROUND, conf.Foreground) self.lp.SetDefaultColor(loop.FOREGROUND, conf.Foreground)
self.lp.SetDefaultColor(loop.CURSOR, conf.Foreground) self.lp.SetDefaultColor(loop.CURSOR, conf.Foreground)
@ -65,6 +77,7 @@ func (self *Handler) initialize() {
self.async_results <- r self.async_results <- r
self.lp.WakeupMainThread() self.lp.WakeupMainThread()
}() }()
self.draw_screen()
} }
func (self *Handler) generate_diff() { func (self *Handler) generate_diff() {
@ -112,14 +125,54 @@ func (self *Handler) handle_async_result(r AsyncResult) error {
case DIFF: case DIFF:
self.diff_map = r.diff_map self.diff_map = r.diff_map
self.calculate_statistics() self.calculate_statistics()
self.render_diff() err := self.render_diff()
self.scroll_pos = 0 if err != nil {
if self.restore_position != nil { return err
self.set_current_position(self.restore_position)
self.restore_position = nil
} }
self.scroll_pos = ScrollPos{}
// TODO: restore_position uncomment and implement below
// if self.restore_position != nil {
// self.set_current_position(self.restore_position)
// self.restore_position = nil
// }
self.draw_screen() self.draw_screen()
case HIGHLIGHT: case HIGHLIGHT:
} }
return nil return nil
} }
func (self *Handler) on_resize(old_size, new_size loop.ScreenSize) error {
self.screen_size.rows = int(new_size.HeightCells)
self.screen_size.num_lines = self.screen_size.rows - 1
self.screen_size.columns = int(new_size.WidthCells)
if self.diff_map != nil && self.collection != nil {
err := self.render_diff()
if err != nil {
return err
}
}
self.draw_screen()
return nil
}
func (self *Handler) render_diff() (err error) {
self.logical_lines, err = render(self.collection, self.diff_map, self.screen_size.columns)
if err != nil {
return err
}
return nil
// TODO: current search see python implementation
}
func (self *Handler) draw_screen() {
self.lp.StartAtomicUpdate()
defer self.lp.EndAtomicUpdate()
g := (&graphics.GraphicsCommand{}).SetAction(graphics.GRT_action_delete).SetDelete(graphics.GRT_delete_visible)
g.WriteWithPayloadToLoop(self.lp, nil)
lp.MoveCursorTo(1, 1)
if self.logical_lines == nil || self.diff_map == nil || self.collection == nil {
lp.Println(`Calculating diff, please wait...`)
return
}
}

View File

@ -146,7 +146,7 @@ func Memset[T any](dest []T, pattern ...T) []T {
} }
var ControlCodesPat = (&Once[*regexp.Regexp]{Run: func() *regexp.Regexp { var ControlCodesPat = (&Once[*regexp.Regexp]{Run: func() *regexp.Regexp {
return regexp.MustCompile("[\x00-\x09\x0b-\x1f\x7f\x80-\x9f]") return regexp.MustCompile("[\x00-\x09\x0b-\x1f\x7f\u0080-\u009f]")
}}).Get }}).Get
func SanitizeControlCodes(raw string, replace_with ...string) string { func SanitizeControlCodes(raw string, replace_with ...string) string {