@@ -5,328 +5,16 @@ import (
5
5
"fmt"
6
6
"io"
7
7
"os"
8
- "path/filepath"
9
- "slices"
10
8
"strings"
11
9
"time"
12
10
13
- "github.com/bluekeyes/go-gitdiff/gitdiff"
14
- "github.com/charmbracelet/bubbles/help"
15
- "github.com/charmbracelet/bubbles/key"
16
- "github.com/charmbracelet/bubbles/textinput"
17
- "github.com/charmbracelet/bubbles/viewport"
18
11
tea "github.com/charmbracelet/bubbletea"
19
- "github.com/charmbracelet/lipgloss"
20
12
"github.com/charmbracelet/log"
21
13
"github.com/charmbracelet/x/ansi"
22
14
23
- "github.com/dlvhdr/diffnav/pkg/constants"
24
- filetree "github.com/dlvhdr/diffnav/pkg/file_tree"
25
- "github.com/dlvhdr/diffnav/pkg/utils"
15
+ "github.com/dlvhdr/diffnav/pkg/ui"
26
16
)
27
17
28
- type mainModel struct {
29
- input string
30
- files []* gitdiff.File
31
- cursor int
32
- fileTree tea.Model
33
- diffViewer tea.Model
34
- width int
35
- height int
36
- isShowingFileTree bool
37
- search textinput.Model
38
- help help.Model
39
- resultsVp viewport.Model
40
- resultsCursor int
41
- searching bool
42
- filtered []string
43
- }
44
-
45
- func newModel (input string ) mainModel {
46
- m := mainModel {input : input , isShowingFileTree : true }
47
- m .fileTree = initialFileTreeModel ()
48
- m .diffViewer = initialDiffModel ()
49
-
50
- m .help = help .New ()
51
- helpSt := lipgloss .NewStyle ()
52
- m .help .ShortSeparator = " · "
53
- m .help .Styles .ShortKey = helpSt
54
- m .help .Styles .ShortDesc = helpSt
55
- m .help .Styles .ShortSeparator = helpSt
56
- m .help .Styles .ShortKey = helpSt .Foreground (lipgloss .Color ("254" ))
57
- m .help .Styles .ShortDesc = helpSt
58
- m .help .Styles .ShortSeparator = helpSt
59
- m .help .Styles .Ellipsis = helpSt
60
-
61
- m .search = textinput .New ()
62
- m .search .ShowSuggestions = true
63
- m .search .KeyMap .AcceptSuggestion = key .NewBinding (key .WithKeys ("tab" ))
64
- m .search .Prompt = " "
65
- m .search .PromptStyle = lipgloss .NewStyle ().Foreground (lipgloss .Color ("8" ))
66
- m .search .Placeholder = "Filter files "
67
- m .search .PlaceholderStyle = lipgloss .NewStyle ().MaxWidth (lipgloss .Width (m .search .Placeholder )).Foreground (lipgloss .Color ("8" ))
68
- m .search .Width = constants .OpenFileTreeWidth - 5
69
-
70
- m .resultsVp = viewport.Model {}
71
-
72
- return m
73
- }
74
-
75
- func (m mainModel ) Init () tea.Cmd {
76
- return tea .Batch (tea .EnterAltScreen , m .fetchFileTree , m .diffViewer .Init ())
77
- }
78
-
79
- func (m mainModel ) Update (msg tea.Msg ) (tea.Model , tea.Cmd ) {
80
- var cmd tea.Cmd
81
- var cmds []tea.Cmd
82
-
83
- if ! m .searching {
84
- switch msg := msg .(type ) {
85
- case tea.KeyMsg :
86
- switch msg .String () {
87
- case "ctrl+c" , "q" :
88
- return m , tea .Quit
89
- case "t" :
90
- m .searching = true
91
- m .search .Width = m .sidebarWidth () - 5
92
- m .search .SetValue ("" )
93
- m .resultsCursor = 0
94
- m .filtered = make ([]string , 0 )
95
-
96
- m .resultsVp .Width = constants .SearchingFileTreeWidth
97
- m .resultsVp .Height = m .height - footerHeight - headerHeight - searchHeight
98
- m .resultsVp .SetContent (m .resultsView ())
99
-
100
- df , dfCmd := m .setDiffViewerDimensions ()
101
- cmds = append (cmds , dfCmd )
102
- m .diffViewer = df
103
- cmds = append (cmds , m .search .Focus ())
104
- case "e" :
105
- m .isShowingFileTree = ! m .isShowingFileTree
106
- df , dfCmd := m .setDiffViewerDimensions ()
107
- cmds = append (cmds , dfCmd )
108
- m .diffViewer = df
109
- case "up" , "k" , "ctrl+p" :
110
- if m .cursor > 0 {
111
- m .cursor --
112
- m .diffViewer , cmd = m .diffViewer .(diffModel ).SetFilePatch (m .files [m .cursor ])
113
- cmds = append (cmds , cmd )
114
- }
115
- case "down" , "j" , "ctrl+n" :
116
- if m .cursor < len (m .files )- 1 {
117
- m .cursor ++
118
- m .diffViewer , cmd = m .diffViewer .(diffModel ).SetFilePatch (m .files [m .cursor ])
119
- cmds = append (cmds , cmd )
120
- }
121
- }
122
-
123
- case tea.WindowSizeMsg :
124
- m .help .Width = msg .Width
125
- m .width = msg .Width
126
- m .height = msg .Height
127
- df , dfCmd := m .setDiffViewerDimensions ()
128
- m .diffViewer = df
129
- cmds = append (cmds , dfCmd )
130
- ft , ftCmd := m .fileTree .(ftModel ).Update (dimensionsMsg {Width : m .sidebarWidth (), Height : m .height - footerHeight - headerHeight - searchHeight })
131
- m .fileTree = ft
132
- cmds = append (cmds , ftCmd )
133
-
134
- case fileTreeMsg :
135
- m .files = msg .files
136
- if len (m .files ) == 0 {
137
- return m , tea .Quit
138
- }
139
- m .fileTree = m .fileTree .(ftModel ).SetFiles (m .files )
140
- m .diffViewer , cmd = m .diffViewer .(diffModel ).SetFilePatch (m .files [0 ])
141
- cmds = append (cmds , cmd )
142
-
143
- case errMsg :
144
- fmt .Printf ("Error: %v\n " , msg .err )
145
- log .Fatal (msg .err )
146
- }
147
- } else {
148
- var sCmds []tea.Cmd
149
- m , sCmds = m .searchUpdate (msg )
150
- cmds = append (cmds , sCmds ... )
151
- }
152
-
153
- m .fileTree = m .fileTree .(ftModel ).SetCursor (m .cursor )
154
-
155
- m .diffViewer , cmd = m .diffViewer .Update (msg )
156
- cmds = append (cmds , cmd )
157
-
158
- m .fileTree , cmd = m .fileTree .Update (msg )
159
- cmds = append (cmds , cmd )
160
-
161
- return m , tea .Batch (cmds ... )
162
- }
163
-
164
- func (m mainModel ) searchUpdate (msg tea.Msg ) (mainModel , []tea.Cmd ) {
165
- var cmd tea.Cmd
166
- var cmds []tea.Cmd
167
- if m .search .Focused () {
168
- switch msg := msg .(type ) {
169
- case tea.KeyMsg :
170
- switch msg .String () {
171
- case "esc" :
172
- m .stopSearch ()
173
- df , dfCmd := m .setDiffViewerDimensions ()
174
- m .diffViewer = df
175
- cmds = append (cmds , dfCmd )
176
- case "ctrl+c" :
177
- return m , []tea.Cmd {tea .Quit }
178
- case "enter" :
179
- m .stopSearch ()
180
- df , dfCmd := m .setDiffViewerDimensions ()
181
- cmds = append (cmds , dfCmd )
182
- m .diffViewer = df
183
-
184
- selected := m .filtered [m .resultsCursor ]
185
- for i , f := range m .files {
186
- if filetree .GetFileName (f ) == selected {
187
- m .cursor = i
188
- m .diffViewer , cmd = m .diffViewer .(diffModel ).SetFilePatch (f )
189
- cmds = append (cmds , cmd )
190
- break
191
- }
192
- }
193
-
194
- case "ctrl+n" , "down" :
195
- m .resultsCursor = min (len (m .files )- 1 , m .resultsCursor + 1 )
196
- m .resultsVp .LineDown (1 )
197
- case "ctrl+p" , "up" :
198
- m .resultsCursor = max (0 , m .resultsCursor - 1 )
199
- m .resultsVp .LineUp (1 )
200
- default :
201
- m .resultsCursor = 0
202
- }
203
- }
204
- s , sc := m .search .Update (msg )
205
- cmds = append (cmds , sc )
206
- m .search = s
207
- filtered := make ([]string , 0 )
208
- for _ , f := range m .files {
209
- if strings .Contains (strings .ToLower (filetree .GetFileName (f )), strings .ToLower (m .search .Value ())) {
210
- filtered = append (filtered , filetree .GetFileName (f ))
211
- }
212
- }
213
- m .filtered = filtered
214
- m .resultsVp .SetContent (m .resultsView ())
215
- }
216
-
217
- return m , cmds
218
- }
219
-
220
- func (m mainModel ) View () string {
221
- header := lipgloss .NewStyle ().Width (m .width ).
222
- Border (lipgloss .NormalBorder (), false , false , true , false ).
223
- BorderForeground (lipgloss .Color ("8" )).
224
- Foreground (lipgloss .Color ("6" )).
225
- Bold (true ).
226
- Render ("DIFFNAV" )
227
- footer := m .footerView ()
228
-
229
- sidebar := ""
230
- if m .isShowingFileTree {
231
- search := lipgloss .NewStyle ().
232
- Border (lipgloss .RoundedBorder ()).
233
- BorderForeground (lipgloss .Color ("8" )).
234
- MaxHeight (3 ).
235
- Width (m .sidebarWidth () - 2 ).
236
- Render (m .search .View ())
237
-
238
- content := ""
239
- width := m .sidebarWidth ()
240
- if m .searching {
241
- content = m .resultsVp .View ()
242
- } else {
243
- content = m .fileTree .View ()
244
- }
245
-
246
- content = lipgloss .NewStyle ().
247
- Width (width ).
248
- Height (m .height - footerHeight - headerHeight ).Render (lipgloss .JoinVertical (lipgloss .Left , search , content ))
249
-
250
- sidebar = lipgloss .NewStyle ().
251
- Width (width ).
252
- Border (lipgloss .NormalBorder (), false , true , false , false ).
253
- BorderForeground (lipgloss .Color ("8" )).Render (content )
254
- }
255
- dv := lipgloss .NewStyle ().MaxHeight (m .height - footerHeight - headerHeight ).Width (m .width - m .sidebarWidth ()).Render (m .diffViewer .View ())
256
- return lipgloss .JoinVertical (lipgloss .Left ,
257
- header ,
258
- lipgloss .JoinHorizontal (lipgloss .Top , sidebar , dv ),
259
- footer ,
260
- )
261
- }
262
-
263
- type dimensionsMsg struct {
264
- Width int
265
- Height int
266
- }
267
-
268
- func (m mainModel ) fetchFileTree () tea.Msg {
269
- // TODO: handle error
270
- files , _ , err := gitdiff .Parse (strings .NewReader (m .input + "\n " ))
271
- if err != nil {
272
- return errMsg {err }
273
- }
274
- sortFiles (files )
275
-
276
- return fileTreeMsg {files : files }
277
- }
278
-
279
- type fileTreeMsg struct {
280
- files []* gitdiff.File
281
- }
282
-
283
- func sortFiles (files []* gitdiff.File ) {
284
- slices .SortFunc (files , func (a * gitdiff.File , b * gitdiff.File ) int {
285
- nameA := filetree .GetFileName (a )
286
- nameB := filetree .GetFileName (b )
287
- dira := filepath .Dir (nameA )
288
- dirb := filepath .Dir (nameB )
289
- if dira != "." && dirb != "." && dira == dirb {
290
- return strings .Compare (strings .ToLower (nameA ), strings .ToLower (nameB ))
291
- }
292
-
293
- if dira != "." && dirb == "." {
294
- return - 1
295
- }
296
- if dirb != "." && dira == "." {
297
- return 1
298
- }
299
-
300
- if dira != "." && dirb != "." {
301
- if strings .HasPrefix (dira , dirb ) {
302
- return - 1
303
- }
304
-
305
- if strings .HasPrefix (dirb , dira ) {
306
- return 1
307
- }
308
- }
309
-
310
- return strings .Compare (strings .ToLower (nameA ), strings .ToLower (nameB ))
311
- })
312
- }
313
-
314
- const (
315
- footerHeight = 2
316
- headerHeight = 2
317
- searchHeight = 3
318
- )
319
-
320
- func (m mainModel ) footerView () string {
321
- return lipgloss .NewStyle ().
322
- Width (m .width ).
323
- Border (lipgloss .NormalBorder (), true , false , false , false ).
324
- BorderForeground (lipgloss .Color ("8" )).
325
- Height (1 ).
326
- Render (m .help .ShortHelpView (getKeys ()))
327
-
328
- }
329
-
330
18
func main () {
331
19
stat , err := os .Stdin .Stat ()
332
20
if err != nil {
@@ -375,44 +63,9 @@ func main() {
375
63
fmt .Println ("No input provided, exiting" )
376
64
os .Exit (1 )
377
65
}
378
- p := tea .NewProgram (newModel (input ), tea .WithMouseAllMotion ())
66
+ p := tea .NewProgram (ui . New (input ), tea .WithMouseAllMotion ())
379
67
380
68
if _ , err := p .Run (); err != nil {
381
69
log .Fatal (err )
382
70
}
383
71
}
384
-
385
- func (m mainModel ) resultsView () string {
386
- sb := strings.Builder {}
387
- for i , f := range m .filtered {
388
- fName := utils .TruncateString (" " + f , constants .SearchingFileTreeWidth - 2 )
389
- if i == m .resultsCursor {
390
- sb .WriteString (lipgloss .NewStyle ().Background (lipgloss .Color ("#1b1b33" )).Bold (true ).Render (fName ) + "\n " )
391
- } else {
392
- sb .WriteString (fName + "\n " )
393
- }
394
- }
395
- return sb .String ()
396
- }
397
-
398
- func (m mainModel ) sidebarWidth () int {
399
- if m .searching {
400
- return constants .SearchingFileTreeWidth
401
- } else if m .isShowingFileTree {
402
- return constants .OpenFileTreeWidth
403
- } else {
404
- return 0
405
- }
406
- }
407
-
408
- func (m mainModel ) setDiffViewerDimensions () (tea.Model , tea.Cmd ) {
409
- df , dfCmd := m .diffViewer .(diffModel ).Update (dimensionsMsg {Width : m .width - m .sidebarWidth (), Height : m .height - footerHeight - headerHeight })
410
- return df , dfCmd
411
- }
412
-
413
- func (m * mainModel ) stopSearch () {
414
- m .searching = false
415
- m .search .SetValue ("" )
416
- m .search .Blur ()
417
- m .search .Width = m .sidebarWidth () - 5
418
- }
0 commit comments