Skip to content

Commit 3f9a3cd

Browse files
committed
refactor: move to subpackages and introduce component with a setSize
1 parent ebbfbbb commit 3f9a3cd

File tree

8 files changed

+422
-400
lines changed

8 files changed

+422
-400
lines changed

main.go

Lines changed: 2 additions & 349 deletions
Original file line numberDiff line numberDiff line change
@@ -5,328 +5,16 @@ import (
55
"fmt"
66
"io"
77
"os"
8-
"path/filepath"
9-
"slices"
108
"strings"
119
"time"
1210

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"
1811
tea "github.com/charmbracelet/bubbletea"
19-
"github.com/charmbracelet/lipgloss"
2012
"github.com/charmbracelet/log"
2113
"github.com/charmbracelet/x/ansi"
2214

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"
2616
)
2717

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-
33018
func main() {
33119
stat, err := os.Stdin.Stat()
33220
if err != nil {
@@ -375,44 +63,9 @@ func main() {
37563
fmt.Println("No input provided, exiting")
37664
os.Exit(1)
37765
}
378-
p := tea.NewProgram(newModel(input), tea.WithMouseAllMotion())
66+
p := tea.NewProgram(ui.New(input), tea.WithMouseAllMotion())
37967

38068
if _, err := p.Run(); err != nil {
38169
log.Fatal(err)
38270
}
38371
}
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-
}

pkg/file_tree/file_node.go renamed to pkg/filenode/file_node.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package filetree
1+
package filenode
22

33
import (
44
"path/filepath"

pkg/ui/common/component.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package common
2+
3+
import (
4+
tea "github.com/charmbracelet/bubbletea"
5+
)
6+
7+
// Common is a struct that contains the width and height of a component.
8+
type Common struct {
9+
Width, Height int
10+
}
11+
12+
type Component interface {
13+
SetSize(width, height int) tea.Cmd
14+
}

0 commit comments

Comments
 (0)