123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771 |
- // License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
- package diff
- import (
- "errors"
- "fmt"
- "math"
- "os"
- "strconv"
- "strings"
- "kitty/tools/tui/graphics"
- "kitty/tools/tui/loop"
- "kitty/tools/tui/sgr"
- "kitty/tools/utils"
- "kitty/tools/utils/style"
- "kitty/tools/wcswidth"
- )
- var _ = fmt.Print
- type LineType int
- const (
- TITLE_LINE LineType = iota
- CHANGE_LINE
- CONTEXT_LINE
- HUNK_TITLE_LINE
- IMAGE_LINE
- EMPTY_LINE
- )
- type Reference struct {
- path string
- linenum int // 1 based
- }
- 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 {
- left, right HalfScreenLine
- }
- type LogicalLine struct {
- line_type LineType
- screen_lines []*ScreenLine
- is_full_width bool
- is_change_start bool
- left_reference, right_reference Reference
- left_image, right_image struct {
- key string
- count int
- }
- image_lines_offset int
- }
- func (self *LogicalLine) render_screen_line(n int, lp *loop.Loop, margin_size, columns int) {
- if n >= len(self.screen_lines) || n < 0 {
- return
- }
- sl := self.screen_lines[n]
- available_cols := columns/2 - margin_size
- if self.is_full_width {
- available_cols = columns - margin_size
- }
- left_margin := place_in(sl.left.marked_up_margin_text, margin_size)
- left_text := place_in(sl.left.marked_up_text, available_cols)
- if sl.left.is_filler {
- left_margin = format_as_sgr.margin_filler + left_margin
- left_text = format_as_sgr.filler + left_text
- } else {
- switch self.line_type {
- case CHANGE_LINE, IMAGE_LINE:
- left_margin = format_as_sgr.removed_margin + left_margin
- left_text = format_as_sgr.removed + left_text
- case HUNK_TITLE_LINE:
- left_margin = format_as_sgr.hunk_margin + left_margin
- left_text = format_as_sgr.hunk + left_text
- case TITLE_LINE:
- default:
- left_margin = format_as_sgr.margin + left_margin
- }
- }
- lp.QueueWriteString(left_margin + "\x1b[m")
- lp.QueueWriteString(left_text)
- if self.is_full_width {
- return
- }
- right_margin := place_in(sl.right.marked_up_margin_text, margin_size)
- right_text := place_in(sl.right.marked_up_text, available_cols)
- if sl.right.is_filler {
- right_margin = format_as_sgr.margin_filler + right_margin
- right_text = format_as_sgr.filler + right_text
- } else {
- switch self.line_type {
- case CHANGE_LINE, IMAGE_LINE:
- right_margin = format_as_sgr.added_margin + right_margin
- right_text = format_as_sgr.added + right_text
- case HUNK_TITLE_LINE:
- right_margin = format_as_sgr.hunk_margin + right_margin
- right_text = format_as_sgr.hunk + right_text
- case TITLE_LINE:
- default:
- right_margin = format_as_sgr.margin + right_margin
- }
- }
- lp.QueueWriteString("\x1b[m\r")
- lp.MoveCursorHorizontally(available_cols + margin_size)
- lp.QueueWriteString(right_margin + "\x1b[m")
- lp.QueueWriteString(right_text)
- }
- func (self *LogicalLine) IncrementScrollPosBy(pos *ScrollPos, amt int) (delta int) {
- if len(self.screen_lines) > 0 {
- npos := utils.Max(0, utils.Min(pos.screen_line+amt, len(self.screen_lines)-1))
- delta = npos - pos.screen_line
- pos.screen_line = npos
- }
- return
- }
- 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 format_as_sgr struct {
- title, margin, added, removed, added_margin, removed_margin, filler, margin_filler, hunk_margin, hunk, selection, search string
- }
- var statusline_format, added_count_format, removed_count_format, message_format func(...any) string
- func create_formatters() {
- ctx := style.Context{AllowEscapeCodes: true}
- only_open := func(x string) string {
- ans := ctx.SprintFunc(x)("|")
- ans, _, _ = strings.Cut(ans, "|")
- return ans
- }
- format_as_sgr.filler = only_open("bg=" + conf.Filler_bg.AsRGBSharp())
- if conf.Margin_filler_bg.IsSet {
- format_as_sgr.margin_filler = only_open("bg=" + conf.Margin_filler_bg.Color.AsRGBSharp())
- } else {
- format_as_sgr.margin_filler = only_open("bg=" + conf.Filler_bg.AsRGBSharp())
- }
- format_as_sgr.added = only_open("bg=" + conf.Added_bg.AsRGBSharp())
- format_as_sgr.added_margin = only_open(fmt.Sprintf("fg=%s bg=%s", conf.Margin_fg.AsRGBSharp(), conf.Added_margin_bg.AsRGBSharp()))
- format_as_sgr.removed = only_open("bg=" + conf.Removed_bg.AsRGBSharp())
- format_as_sgr.removed_margin = only_open(fmt.Sprintf("fg=%s bg=%s", conf.Margin_fg.AsRGBSharp(), conf.Removed_margin_bg.AsRGBSharp()))
- format_as_sgr.title = only_open(fmt.Sprintf("fg=%s bg=%s bold", conf.Title_fg.AsRGBSharp(), conf.Title_bg.AsRGBSharp()))
- format_as_sgr.margin = only_open(fmt.Sprintf("fg=%s bg=%s", conf.Margin_fg.AsRGBSharp(), conf.Margin_bg.AsRGBSharp()))
- format_as_sgr.hunk = only_open(fmt.Sprintf("fg=%s bg=%s", conf.Margin_fg.AsRGBSharp(), conf.Hunk_bg.AsRGBSharp()))
- format_as_sgr.hunk_margin = only_open(fmt.Sprintf("fg=%s bg=%s", conf.Margin_fg.AsRGBSharp(), conf.Hunk_margin_bg.AsRGBSharp()))
- format_as_sgr.search = only_open(fmt.Sprintf("fg=%s bg=%s", conf.Search_fg.AsRGBSharp(), conf.Search_bg.AsRGBSharp()))
- statusline_format = ctx.SprintFunc(fmt.Sprintf("fg=%s", conf.Margin_fg.AsRGBSharp()))
- added_count_format = ctx.SprintFunc(fmt.Sprintf("fg=%s", conf.Highlight_added_bg.AsRGBSharp()))
- removed_count_format = ctx.SprintFunc(fmt.Sprintf("fg=%s", conf.Highlight_removed_bg.AsRGBSharp()))
- message_format = ctx.SprintFunc("bold")
- if conf.Select_fg.IsSet {
- format_as_sgr.selection = only_open(fmt.Sprintf("fg=%s bg=%s", conf.Select_fg.Color.AsRGBSharp(), conf.Select_bg.AsRGBSharp()))
- } else {
- format_as_sgr.selection = only_open("bg=" + conf.Select_bg.AsRGBSharp())
- }
- }
- func center_span(ltype string, offset, size int) *sgr.Span {
- ans := sgr.NewSpan(offset, size)
- switch ltype {
- case "add":
- ans.SetBackground(conf.Highlight_added_bg).SetClosingBackground(conf.Added_bg)
- case "remove":
- ans.SetBackground(conf.Highlight_removed_bg).SetClosingBackground(conf.Removed_bg)
- }
- return ans
- }
- 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]
- available_cols := columns/2 - margin_size
- ll := LogicalLine{
- line_type: TITLE_LINE,
- left_reference: Reference{path: left_path}, right_reference: Reference{path: right_path},
- }
- sl := ScreenLine{}
- if right_name != "" && right_name != left_name {
- sl.left.marked_up_text = format_as_sgr.title + fit_in(sanitize(left_name), available_cols)
- sl.right.marked_up_text = format_as_sgr.title + fit_in(sanitize(right_name), available_cols)
- } else {
- sl.left.marked_up_text = format_as_sgr.title + fit_in(sanitize(left_name), columns-margin_size)
- ll.is_full_width = true
- }
- l2 := ll
- l2.line_type = EMPTY_LINE
- ll.screen_lines = append(ll.screen_lines, &sl)
- sl2 := ScreenLine{}
- sl2.left.marked_up_margin_text = "\x1b[m" + strings.Repeat("━", margin_size)
- sl2.left.marked_up_text = strings.Repeat("━", columns-margin_size)
- l2.is_full_width = true
- l2.screen_lines = append(l2.screen_lines, &sl2)
- return append(ans, &ll, &l2)
- }
- type LogicalLines struct {
- lines []*LogicalLine
- margin_size, columns int
- }
- func (self *LogicalLines) At(i int) *LogicalLine { return self.lines[i] }
- func (self *LogicalLines) ScreenLineAt(pos ScrollPos) *ScreenLine {
- if pos.logical_line < len(self.lines) && pos.logical_line >= 0 {
- line := self.lines[pos.logical_line]
- if pos.screen_line < len(line.screen_lines) && pos.screen_line >= 0 {
- return self.lines[pos.logical_line].screen_lines[pos.screen_line]
- }
- }
- return nil
- }
- func (self *LogicalLines) Len() int { return len(self.lines) }
- func (self *LogicalLines) NumScreenLinesTo(a ScrollPos) (ans int) {
- return self.Minus(a, ScrollPos{})
- }
- // a - b in terms of number of screen lines between the positions
- func (self *LogicalLines) Minus(a, b ScrollPos) (delta int) {
- if a.logical_line == b.logical_line {
- return a.screen_line - b.screen_line
- }
- amt := 1
- if a.Less(b) {
- amt = -1
- } else {
- a, b = b, a
- }
- for i := a.logical_line; i < utils.Min(len(self.lines), b.logical_line+1); i++ {
- line := self.lines[i]
- switch i {
- case a.logical_line:
- delta += utils.Max(0, len(line.screen_lines)-a.screen_line)
- case b.logical_line:
- delta += b.screen_line
- default:
- delta += len(line.screen_lines)
- }
- }
- return delta * amt
- }
- func (self *LogicalLines) IncrementScrollPosBy(pos *ScrollPos, amt int) (delta int) {
- if pos.logical_line < 0 || pos.logical_line >= len(self.lines) || amt == 0 {
- return
- }
- one := 1
- if amt < 0 {
- one = -1
- }
- for amt != 0 {
- line := self.lines[pos.logical_line]
- d := line.IncrementScrollPosBy(pos, amt)
- if d == 0 {
- nlp := pos.logical_line + one
- if nlp < 0 || nlp >= len(self.lines) {
- break
- }
- pos.logical_line = nlp
- if one > 0 {
- pos.screen_line = 0
- } else {
- pos.screen_line = len(self.lines[nlp].screen_lines) - 1
- }
- delta += one
- amt -= one
- } else {
- amt -= d
- delta += d
- }
- }
- return
- }
- func human_readable(size int64) 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 image_lines(left_path, right_path string, screen_size screen_size, margin_size int, image_size graphics.Size, ans []*LogicalLine) ([]*LogicalLine, error) {
- columns := screen_size.columns
- available_cols := columns/2 - margin_size
- ll, err := first_binary_line(left_path, right_path, columns, margin_size, func(path string) (string, error) {
- sz, err := size_for_path(path)
- if err != nil {
- return "", err
- }
- text := fmt.Sprintf("Size: %s", human_readable(sz))
- res := image_collection.ResolutionOf(path)
- if res.Width > -1 {
- text = fmt.Sprintf("Dimensions: %dx%d %s", res.Width, res.Height, text)
- }
- return text, nil
- })
- if err != nil {
- return nil, err
- }
- ll.image_lines_offset = len(ll.screen_lines)
- do_side := func(path string) []string {
- if path == "" {
- return nil
- }
- sz, err := image_collection.GetSizeIfAvailable(path, image_size)
- if err == nil {
- count := int(math.Ceil(float64(sz.Height) / float64(screen_size.cell_height)))
- return utils.Repeat("", count)
- }
- if errors.Is(err, graphics.ErrNotFound) {
- return splitlines("Loading image...", available_cols)
- }
- return splitlines(fmt.Sprintf("%s", err), available_cols)
- }
- left_lines := do_side(left_path)
- if ll.left_image.count = len(left_lines); ll.left_image.count > 0 {
- ll.left_image.key = left_path
- }
- right_lines := do_side(right_path)
- if ll.right_image.count = len(right_lines); ll.right_image.count > 0 {
- ll.right_image.key = right_path
- }
- for i := 0; i < utils.Max(len(left_lines), len(right_lines)); i++ {
- sl := ScreenLine{}
- if i < len(left_lines) {
- sl.left.marked_up_text = left_lines[i]
- } else {
- sl.left.is_filler = true
- }
- if i < len(right_lines) {
- sl.right.marked_up_text = right_lines[i]
- } else {
- sl.right.is_filler = true
- }
- ll.screen_lines = append(ll.screen_lines, &sl)
- }
- ll.line_type = IMAGE_LINE
- return append(ans, ll), nil
- }
- func first_binary_line(left_path, right_path string, columns, margin_size int, renderer func(path string) (string, error)) (*LogicalLine, error) {
- available_cols := columns/2 - margin_size
- ll := LogicalLine{
- is_change_start: true, line_type: CHANGE_LINE,
- left_reference: Reference{path: left_path}, right_reference: Reference{path: right_path},
- }
- if left_path == "" {
- line, err := renderer(right_path)
- if err != nil {
- return nil, err
- }
- for _, x := range splitlines(line, available_cols) {
- sl := ScreenLine{}
- sl.right.marked_up_text = x
- sl.left.is_filler = true
- ll.screen_lines = append(ll.screen_lines, &sl)
- }
- } else if right_path == "" {
- line, err := renderer(left_path)
- if err != nil {
- return nil, err
- }
- for _, x := range splitlines(line, available_cols) {
- sl := ScreenLine{}
- sl.right.is_filler = true
- sl.left.marked_up_text = x
- ll.screen_lines = append(ll.screen_lines, &sl)
- }
- } else {
- l, err := renderer(left_path)
- if err != nil {
- return nil, err
- }
- r, err := renderer(right_path)
- if err != nil {
- return nil, err
- }
- left_lines, right_lines := splitlines(l, available_cols), splitlines(r, available_cols)
- for i := 0; i < utils.Max(len(left_lines), len(right_lines)); i++ {
- sl := ScreenLine{}
- if i < len(left_lines) {
- sl.left.marked_up_text = left_lines[i]
- }
- if i < len(right_lines) {
- sl.right.marked_up_text = right_lines[i]
- }
- ll.screen_lines = append(ll.screen_lines, &sl)
- }
- }
- return &ll, nil
- }
- func binary_lines(left_path, right_path string, columns, margin_size int, ans []*LogicalLine) (ans2 []*LogicalLine, err error) {
- ll, err := first_binary_line(left_path, right_path, columns, margin_size, func(path string) (string, error) {
- sz, err := size_for_path(path)
- if err != nil {
- return "", err
- }
- return fmt.Sprintf("Binary file: %s", human_readable(sz)), nil
- })
- if err != nil {
- return nil, err
- }
- return append(ans, ll), nil
- }
- type DiffData struct {
- left_path, right_path string
- available_cols, margin_size int
- left_lines, right_lines []string
- }
- func hunk_title(hunk *Hunk) string {
- return fmt.Sprintf("@@ -%d,%d +%d,%d @@ %s", hunk.left_start+1, hunk.left_count, hunk.right_start+1, hunk.right_count, hunk.title)
- }
- 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: CONTEXT_LINE,
- left_reference: Reference{path: data.left_path, linenum: left_line_number + 1},
- right_reference: Reference{path: data.right_path, linenum: right_line_number + 1},
- }
- 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{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{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 = "", ""
- }
- ans = append(ans, &ll)
- }
- return ans
- }
- 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 {
- size := center.left_size
- if ltype != "remove" {
- size = center.right_size
- }
- if size > 0 {
- span := center_span(ltype, center.offset, size)
- line = sgr.InsertFormatting(line, span)
- }
- 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})
- lnum = ""
- }
- 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)
- ll, rl := make([]HalfScreenLine, 0, 32), make([]HalfScreenLine, 0, 32)
- for i := 0; i < utils.Max(chunk.left_count, chunk.right_count); i++ {
- ll, rl = ll[:0], rl[:0]
- var center Center
- left_lnum, right_lnum := 0, 0
- if i < len(chunk.centers) {
- center = chunk.centers[i]
- }
- 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_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_lnum++
- }
- if i < common {
- extra := len(ll) - len(rl)
- if extra < 0 {
- ll = append(ll, utils.Repeat(HalfScreenLine{}, -extra)...)
- } else if extra > 0 {
- rl = append(rl, utils.Repeat(HalfScreenLine{}, extra)...)
- }
- } else {
- if len(ll) > 0 {
- rl = append(rl, utils.Repeat(HalfScreenLine{is_filler: true}, len(ll))...)
- } else if len(rl) > 0 {
- ll = append(ll, utils.Repeat(HalfScreenLine{is_filler: true}, len(rl))...)
- }
- }
- logline := LogicalLine{
- line_type: CHANGE_LINE, is_change_start: i == 0,
- left_reference: Reference{path: data.left_path, linenum: left_lnum},
- right_reference: Reference{path: data.left_path, linenum: right_lnum},
- }
- for l := 0; l < len(ll); l++ {
- logline.screen_lines = append(logline.screen_lines, &ScreenLine{left: ll[l], right: 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) {
- ht := LogicalLine{
- line_type: HUNK_TITLE_LINE,
- left_reference: Reference{path: left_path}, right_reference: Reference{path: right_path},
- is_full_width: true,
- }
- if patch.Len() == 0 {
- txt := "The files are identical"
- if lstat, err := os.Stat(left_path); err == nil {
- if rstat, err := os.Stat(right_path); err == nil {
- if lstat.Mode() != rstat.Mode() {
- txt = fmt.Sprintf("Mode changed: %s to %s", lstat.Mode(), rstat.Mode())
- }
- }
- }
- for _, line := range splitlines(txt, columns-margin_size) {
- sl := ScreenLine{}
- sl.left.marked_up_text = line
- ht.screen_lines = append(ht.screen_lines, &sl)
- }
- ht.line_type = EMPTY_LINE
- ht.is_full_width = true
- 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}
- if left_path != "" {
- data.left_lines, err = highlighted_lines_for_path(left_path)
- if err != nil {
- return
- }
- }
- if right_path != "" {
- data.right_lines, err = highlighted_lines_for_path(right_path)
- if err != nil {
- return
- }
- }
- for hunk_num, hunk := range patch.all_hunks {
- htl := ht
- htl.left_reference.linenum = hunk.left_start + 1
- htl.right_reference.linenum = hunk.right_start + 1
- for _, line := range splitlines(hunk_title(hunk), columns-margin_size) {
- sl := ScreenLine{}
- sl.left.marked_up_text = line
- htl.screen_lines = append(htl.screen_lines, &sl)
- }
- 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 all_lines(path string, columns, margin_size int, is_add bool, ans []*LogicalLine) ([]*LogicalLine, error) {
- available_cols := columns/2 - margin_size
- ltype := `add`
- ll := LogicalLine{line_type: CHANGE_LINE}
- if !is_add {
- ltype = `remove`
- ll.left_reference.path = path
- } else {
- ll.right_reference.path = path
- }
- lines, err := highlighted_lines_for_path(path)
- if err != nil {
- return nil, err
- }
- var msg_lines []string
- if is_add {
- msg_lines = splitlines(`This file was added`, available_cols)
- } else {
- msg_lines = splitlines(`This file was removed`, available_cols)
- }
- for line_number, line := range lines {
- hlines := make([]HalfScreenLine, 0, 8)
- hlines = render_half_line(line_number, line, ltype, available_cols, Center{}, hlines)
- l := ll
- if is_add {
- l.right_reference.linenum = line_number + 1
- } else {
- l.left_reference.linenum = line_number + 1
- }
- l.is_change_start = line_number == 0
- for i, hl := range hlines {
- sl := ScreenLine{}
- if is_add {
- sl.right = hl
- if len(msg_lines) > 0 {
- sl.left.marked_up_text = msg_lines[i]
- sl.left.is_filler = true
- msg_lines = msg_lines[1:]
- } else {
- sl.left.is_filler = true
- }
- } else {
- sl.left = hl
- if len(msg_lines) > 0 {
- sl.right.marked_up_text = msg_lines[i]
- sl.right.is_filler = true
- msg_lines = msg_lines[1:]
- } else {
- sl.right.is_filler = true
- }
- }
- l.screen_lines = append(l.screen_lines, &sl)
- }
- ans = append(ans, &l)
- }
- return ans, nil
- }
- func rename_lines(path, other_path string, columns, margin_size int, ans []*LogicalLine) ([]*LogicalLine, error) {
- ll := LogicalLine{
- left_reference: Reference{path: path}, right_reference: Reference{path: other_path},
- line_type: CHANGE_LINE, is_change_start: true, is_full_width: true}
- for _, line := range splitlines(fmt.Sprintf(`The file %s was renamed to %s`, sanitize(path_name_map[path]), sanitize(path_name_map[other_path])), columns-margin_size) {
- sl := ScreenLine{}
- sl.right.marked_up_text = line
- ll.screen_lines = append(ll.screen_lines, &sl)
- }
- return append(ans, &ll), nil
- }
- func render(collection *Collection, diff_map map[string]*Patch, screen_size screen_size, largest_line_number int, image_size graphics.Size) (result *LogicalLines, err error) {
- margin_size := utils.Max(3, len(strconv.Itoa(largest_line_number))+1)
- ans := make([]*LogicalLine, 0, 1024)
- columns := screen_size.columns
- err = collection.Apply(func(path, item_type, changed_path string) error {
- ans = title_lines(path, changed_path, columns, margin_size, ans)
- defer func() {
- ans = append(ans, &LogicalLine{line_type: EMPTY_LINE, screen_lines: []*ScreenLine{{}}})
- }()
- 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
- switch item_type {
- case "diff":
- if is_binary {
- if is_img {
- ans, err = image_lines(path, changed_path, screen_size, margin_size, image_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":
- if is_binary {
- if is_img {
- ans, err = image_lines("", path, screen_size, margin_size, image_size, ans)
- } else {
- ans, err = binary_lines("", path, columns, margin_size, ans)
- }
- } else {
- ans, err = all_lines(path, columns, margin_size, true, ans)
- }
- if err != nil {
- return err
- }
- case "removal":
- if is_binary {
- if is_img {
- ans, err = image_lines(path, "", screen_size, margin_size, image_size, ans)
- } else {
- ans, err = binary_lines(path, "", columns, margin_size, ans)
- }
- } else {
- ans, err = all_lines(path, columns, margin_size, false, ans)
- }
- if err != nil {
- return err
- }
- case "rename":
- ans, err = rename_lines(path, changed_path, columns, margin_size, ans)
- if err != nil {
- return err
- }
- default:
- return fmt.Errorf("Unknown change type: %#v", item_type)
- }
- return nil
- })
- var ll []*LogicalLine
- if len(ans) > 1 {
- ll = ans[:len(ans)-1]
- } else {
- // Having am empty list of lines causes panics later on
- ll = []*LogicalLine{{line_type: EMPTY_LINE, screen_lines: []*ScreenLine{{}}}}
- }
- return &LogicalLines{lines: ll, margin_size: margin_size, columns: columns}, err
- }
|