Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pkg/filenode/file_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ func (f *FileNode) SetHidden(bool) {}
func (f *FileNode) SetValue(any) {}

func LinesCounts(file *gitdiff.File) (int64, int64) {
if file == nil {
return 0, 0
}
var added int64 = 0
var deleted int64 = 0
frags := file.TextFragments
Expand Down
115 changes: 84 additions & 31 deletions pkg/ui/panes/diffviewer/diffviewer.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,30 @@ import (

const dirHeaderHeight = 3

type cachedNode struct {
path string
files []*gitdiff.File
additions int64
deletions int64
diff string
}

type nodeCache map[string]*cachedNode

type Model struct {
common.Common
vp viewport.Model
file *gitdiff.File
dir string
dirFiles []*gitdiff.File
file *cachedNode
dir *cachedNode
cache nodeCache
sideBySide bool
}

func New(sideBySide bool) Model {
return Model{
vp: viewport.Model{},
sideBySide: sideBySide,
cache: map[string]*cachedNode{},
}
}

Expand Down Expand Up @@ -63,7 +74,11 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
lines[i] = ansi.Truncate(line, m.vp.Width(), "")
}
}
m.vp.SetContent(strings.Join(lines, "\n"))
diff := strings.Join(lines, "\n")
if _, ok := m.cache[msg.path]; ok {
m.cache[msg.path].diff = diff
}
m.vp.SetContent(diff)
}

return m, tea.Batch(cmds...)
Expand All @@ -83,31 +98,37 @@ func (m *Model) SetSize(width, height int) tea.Cmd {

func (m *Model) diff() tea.Cmd {
if m.file != nil {
if m.file.diff != "" {
return nil
}
return diffFile(m.file, m.Width, m.sideBySide)
} else if m.dir != "" {
return diffDir(m.dir, m.dirFiles, m.Width, m.sideBySide)
} else if m.dir != nil {
if m.dir.diff != "" {
return nil
}
return diffDir(m.dir, m.Width, m.sideBySide)
}

return nil
}

func (m Model) headerView() string {
if m.dir != "" {
if m.dir != nil {
return m.dirHeaderView()
}

if m.file == nil {
if m.file == nil || len(m.file.files) != 1 {
return ""
}
name := filenode.GetFileName(m.file)
name := m.file.path
base := lipgloss.NewStyle()

fileIcon := icons.GetIcon(name, false)
prefix := base.Render(fileIcon) + base.Render(" ")
name = utils.TruncateString(name, m.Width-lipgloss.Width(prefix))
top := prefix + base.Bold(true).Render(name)

bottom := filenode.ViewFileLinesCounts(m.file, base)
bottom := filenode.ViewFileLinesCounts(m.file.files[0], base)

return base.
Width(m.Width).
Expand All @@ -121,17 +142,10 @@ func (m Model) headerView() string {
func (m Model) dirHeaderView() string {
base := lipgloss.NewStyle().Foreground(lipgloss.Blue)
prefix := base.Render(" ")
name := utils.TruncateString(m.dir, m.Width-lipgloss.Width(prefix))

var additions, deletions int64
for _, file := range m.dirFiles {
a, d := filenode.LinesCounts(file)
additions += a
deletions += d
}
name := utils.TruncateString(m.dir.path, m.Width-lipgloss.Width(prefix))

top := prefix + base.Bold(true).Render(name)
bottom := filenode.ViewLinesCounts(additions, deletions, base)
bottom := filenode.ViewLinesCounts(m.dir.additions, m.dir.deletions, base)
return base.
Width(m.Width).
Height(dirHeaderHeight - 1).
Expand All @@ -142,16 +156,52 @@ func (m Model) dirHeaderView() string {
}

func (m Model) SetFilePatch(file *gitdiff.File) (Model, tea.Cmd) {
m.file = file
m.dir = ""
m.dir = nil

fname := filenode.GetFileName(file)
if cached, ok := m.cache[fname]; ok {
m.file = cached
m.vp.SetContent(cached.diff)
return m, nil
}

files := make([]*gitdiff.File, 1)
files[0] = file
additions, deletions := filenode.LinesCounts(file)
m.file = &cachedNode{
path: fname,
files: files,
additions: additions,
deletions: deletions,
}
m.cache[fname] = m.file

return m, diffFile(m.file, m.Width, m.sideBySide)
}

func (m Model) SetDirPatch(dirPath string, files []*gitdiff.File) (Model, tea.Cmd) {
m.file = nil
m.dir = dirPath
m.dirFiles = files
return m, diffDir(dirPath, files, m.Width, m.sideBySide)

if cached, ok := m.cache[dirPath]; ok {
m.dir = cached
m.vp.SetContent(cached.diff)
return m, nil
}

var added, deleted int64
for _, file := range files {
na, nd := filenode.LinesCounts(file)
added += na
deleted += nd
}
m.dir = &cachedNode{
path: dirPath,
files: files,
additions: added,
deletions: deleted,
}
m.cache[dirPath] = m.dir
return m, diffDir(m.dir, m.Width, m.sideBySide)
}

func (m *Model) GoToTop() {
Expand All @@ -174,10 +224,12 @@ func (m *Model) ScrollDown(lines int) {
m.vp.ScrollDown(lines)
}

func diffFile(file *gitdiff.File, width int, sideBySidePreference bool) tea.Cmd {
if width == 0 || file == nil {
func diffFile(node *cachedNode, width int, sideBySidePreference bool) tea.Cmd {
if width == 0 || node == nil || len(node.files) != 1 {
return nil
}

file := node.files[0]
return func() tea.Msg {
// Only use side-by-side if preference is true AND file is not new/deleted
useSideBySide := sideBySidePreference && !file.IsNew && !file.IsDelete
Expand All @@ -197,12 +249,12 @@ func diffFile(file *gitdiff.File, width int, sideBySidePreference bool) tea.Cmd
return common.ErrMsg{Err: err}
}

return diffContentMsg{text: string(out)}
return diffContentMsg{path: filenode.GetFileName(file), text: string(out)}
}
}

func diffDir(dirPath string, files []*gitdiff.File, width int, sideBySidePreference bool) tea.Cmd {
if width == 0 || dirPath == "" {
func diffDir(dir *cachedNode, width int, sideBySidePreference bool) tea.Cmd {
if width == 0 || dir == nil {
return nil
}
return func() tea.Msg {
Expand All @@ -229,7 +281,7 @@ func diffDir(dirPath string, files []*gitdiff.File, width int, sideBySidePrefere
deltac := exec.Command("delta", args...)
deltac.Env = os.Environ()
strs := strings.Builder{}
for _, file := range files {
for _, file := range dir.files {
strs.WriteString(file.String())
}
deltac.Stdin = strings.NewReader(strs.String() + "\n")
Expand All @@ -238,10 +290,11 @@ func diffDir(dirPath string, files []*gitdiff.File, width int, sideBySidePrefere
return common.ErrMsg{Err: err}
}

return diffContentMsg{text: string(out)}
return diffContentMsg{path: dir.path, text: string(out)}
}
}

type diffContentMsg struct {
path string
text string
}