More work on porting diff kitten
This commit is contained in:
parent
ee82cb5a52
commit
5d8b5ab720
@ -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)
|
||||||
|
|||||||
@ -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
143
tools/cmd/diff/render.go
Normal 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
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user