11package diffviewer
22
33import (
4- "bytes"
54 "fmt"
65 "os"
76 "os/exec"
@@ -13,6 +12,8 @@ import (
1312 "github.com/bluekeyes/go-gitdiff/gitdiff"
1413 "github.com/charmbracelet/x/ansi"
1514
15+ "github.com/dlvhdr/diffnav/pkg/filenode"
16+ "github.com/dlvhdr/diffnav/pkg/icons"
1617 "github.com/dlvhdr/diffnav/pkg/ui/common"
1718 "github.com/dlvhdr/diffnav/pkg/utils"
1819)
@@ -22,8 +23,9 @@ const dirHeaderHeight = 3
2223type Model struct {
2324 common.Common
2425 vp viewport.Model
25- buffer * bytes.Buffer
2626 file * gitdiff.File
27+ dir string
28+ dirFiles []* gitdiff.File
2729 sideBySide bool
2830}
2931
@@ -68,9 +70,6 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
6870}
6971
7072func (m Model ) View () string {
71- if m .buffer == nil {
72- return "Loading..."
73- }
7473 return lipgloss .JoinVertical (lipgloss .Left , m .headerView (), m .vp .View ())
7574}
7675
@@ -79,37 +78,60 @@ func (m *Model) SetSize(width, height int) tea.Cmd {
7978 m .Height = height
8079 m .vp .SetWidth (m .Width )
8180 m .vp .SetHeight (m .Height - dirHeaderHeight )
82- return diff (m .file , m .Width , m .sideBySide )
81+ return m .diff ()
82+ }
83+
84+ func (m * Model ) diff () tea.Cmd {
85+ if m .file != nil {
86+ return diffFile (m .file , m .Width , m .sideBySide )
87+ } else if m .dir != "" {
88+ return diffDir (m .dir , m .dirFiles , m .Width , m .sideBySide )
89+ }
90+
91+ return nil
8392}
8493
8594func (m Model ) headerView () string {
95+ if m .dir != "" {
96+ return m .dirHeaderView ()
97+ }
98+
8699 if m .file == nil {
87100 return ""
88101 }
89- name := m .file .NewName
90- if name == "" {
91- name = m .file .OldName
92- }
93-
102+ name := filenode .GetFileName (m .file )
94103 base := lipgloss .NewStyle ()
95- prefix := base .Render ("" ) + base .Render (" " )
104+
105+ fileIcon := icons .GetIcon (name , false )
106+ prefix := base .Render (fileIcon ) + base .Render (" " )
96107 name = utils .TruncateString (name , m .Width - lipgloss .Width (prefix ))
97108 top := prefix + base .Bold (true ).Render (name )
98109
99- var added int64 = 0
100- var deleted int64 = 0
101- frags := m .file .TextFragments
102- for _ , frag := range frags {
103- added += frag .LinesAdded
104- deleted += frag .LinesDeleted
105- }
110+ bottom := filenode .ViewFileLinesCounts (m .file , base )
106111
107- bottom := lipgloss .JoinHorizontal (
108- lipgloss .Top ,
109- base .Foreground (lipgloss .Color ("2" )).Render (fmt .Sprintf (" +%d " , added )),
110- base .Foreground (lipgloss .Color ("1" )).Render (fmt .Sprintf ("-%d" , deleted )),
111- )
112+ return base .
113+ Width (m .Width ).
114+ Height (dirHeaderHeight - 1 ).
115+ BorderStyle (lipgloss .NormalBorder ()).
116+ BorderBottom (true ).
117+ BorderForeground (lipgloss .Color ("8" )).
118+ Render (lipgloss .JoinVertical (lipgloss .Left , top , bottom ))
119+ }
120+
121+ func (m Model ) dirHeaderView () string {
122+ base := lipgloss .NewStyle ().Foreground (lipgloss .Blue )
123+ prefix := base .Render (" " )
124+ name := utils .TruncateString (m .dir , m .Width - lipgloss .Width (prefix ))
125+
126+ var additions , deletions int64
127+ for _ , file := range m .dirFiles {
128+ a , d := filenode .LinesCounts (file )
129+ additions += a
130+ deletions += d
131+ }
112132
133+ top := prefix + base .Bold (true ).Render (name )
134+ bottom := filenode .ViewLinesCounts (additions , deletions , base )
113135 return base .
114136 Width (m .Width ).
115137 Height (dirHeaderHeight - 1 ).
@@ -120,9 +142,16 @@ func (m Model) headerView() string {
120142}
121143
122144func (m Model ) SetFilePatch (file * gitdiff.File ) (Model , tea.Cmd ) {
123- m .buffer = new (bytes.Buffer )
124145 m .file = file
125- return m , diff (m .file , m .Width , m .sideBySide )
146+ m .dir = ""
147+ return m , diffFile (m .file , m .Width , m .sideBySide )
148+ }
149+
150+ func (m Model ) SetDirPatch (dirPath string , files []* gitdiff.File ) (Model , tea.Cmd ) {
151+ m .file = nil
152+ m .dir = dirPath
153+ m .dirFiles = files
154+ return m , diffDir (dirPath , files , m .Width , m .sideBySide )
126155}
127156
128157func (m * Model ) GoToTop () {
@@ -132,7 +161,7 @@ func (m *Model) GoToTop() {
132161// SetSideBySide updates the diff view mode and re-renders.
133162func (m * Model ) SetSideBySide (sideBySide bool ) tea.Cmd {
134163 m .sideBySide = sideBySide
135- return diff (m .file , m .Width , m .sideBySide )
164+ return diffFile (m .file , m .Width , m .sideBySide )
136165}
137166
138167// ScrollUp scrolls the viewport up by the given number of lines.
@@ -145,7 +174,7 @@ func (m *Model) ScrollDown(lines int) {
145174 m .vp .ScrollDown (lines )
146175}
147176
148- func diff (file * gitdiff.File , width int , sideBySidePreference bool ) tea.Cmd {
177+ func diffFile (file * gitdiff.File , width int , sideBySidePreference bool ) tea.Cmd {
149178 if width == 0 || file == nil {
150179 return nil
151180 }
@@ -172,6 +201,47 @@ func diff(file *gitdiff.File, width int, sideBySidePreference bool) tea.Cmd {
172201 }
173202}
174203
204+ func diffDir (dirPath string , files []* gitdiff.File , width int , sideBySidePreference bool ) tea.Cmd {
205+ if width == 0 || dirPath == "" {
206+ return nil
207+ }
208+ return func () tea.Msg {
209+ // Only use side-by-side if preference is true AND file is not new/deleted
210+ s := common .BgStyles [common .Selected ]
211+ c := common .LipglossColorToHex (common .Colors [common .Selected ])
212+ useSideBySide := sideBySidePreference
213+ args := []string {
214+ "--paging=never" ,
215+ fmt .Sprintf ("--file-modified-label=%s" ,
216+ utils .RemoveReset (s .Foreground (lipgloss .Yellow ).Render (" " ))),
217+ fmt .Sprintf ("--file-removed-label=%s" ,
218+ utils .RemoveReset (s .Foreground (lipgloss .Red ).Render (" " ))),
219+ fmt .Sprintf ("--file-added-label=%s" ,
220+ utils .RemoveReset (s .Foreground (lipgloss .Green ).Render (" " ))),
221+ fmt .Sprintf ("--file-style='%s bold %s'" , c , c ),
222+ fmt .Sprintf ("--file-decoration-style='%s box %s'" , c , c ),
223+ fmt .Sprintf ("-w=%d" , width ),
224+ fmt .Sprintf ("--max-line-length=%d" , width ),
225+ }
226+ if useSideBySide {
227+ args = append (args , "--side-by-side" )
228+ }
229+ deltac := exec .Command ("delta" , args ... )
230+ deltac .Env = os .Environ ()
231+ strs := strings.Builder {}
232+ for _ , file := range files {
233+ strs .WriteString (file .String ())
234+ }
235+ deltac .Stdin = strings .NewReader (strs .String () + "\n " )
236+ out , err := deltac .Output ()
237+ if err != nil {
238+ return common.ErrMsg {Err : err }
239+ }
240+
241+ return diffContentMsg {text : string (out )}
242+ }
243+ }
244+
175245type diffContentMsg struct {
176246 text string
177247}
0 commit comments