Skip to content

Commit 2f41a74

Browse files
committed
refactor
Signed-off-by: Ayush <mail@ayuch.dev>
1 parent 5adeda3 commit 2f41a74

File tree

8 files changed

+392
-403
lines changed

8 files changed

+392
-403
lines changed

internal/git/log.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,10 @@ func parseCommitLogs(output string) []CommitLog {
8383
lines := strings.Split(output, "\n")
8484

8585
for _, line := range lines {
86-
if strings.Contains(line, "<COMMIT>") {
87-
parts := strings.SplitN(line, "<COMMIT>", 2)
86+
lineWithNodeReplaced := strings.ReplaceAll(line, "*", "○")
87+
88+
if strings.Contains(lineWithNodeReplaced, "<COMMIT>") {
89+
parts := strings.SplitN(lineWithNodeReplaced, "<COMMIT>", 2)
8890
graph := parts[0]
8991
commitData := strings.SplitN(parts[1], "|", 3)
9092

@@ -97,8 +99,7 @@ func parseCommitLogs(output string) []CommitLog {
9799
})
98100
}
99101
} else {
100-
// This line is purely for drawing the graph.
101-
logs = append(logs, CommitLog{Graph: line})
102+
logs = append(logs, CommitLog{Graph: lineWithNodeReplaced})
102103
}
103104
}
104105
return logs

internal/tui/filetree.go

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,19 @@ import (
1010
// Node represents a file or directory within the file tree structure.
1111
type Node struct {
1212
name string
13-
status string // Git status prefix (e.g., "M ", "MM", "??"), only for file nodes.
14-
path string // Full path relative to the repo root
15-
isRenamed bool // Flag to indicate a renamed/copied file
13+
status string // Git status prefix (e.g., "M ", "??"), only for file nodes.
14+
path string // Full path relative to the repository root.
15+
isRenamed bool
1616
children []*Node
1717
}
1818

1919
// BuildTree parses the output of `git status --porcelain` to construct a file tree.
2020
func BuildTree(gitStatus string) *Node {
2121
root := &Node{name: "."}
22-
lines := strings.Split(strings.TrimSpace(gitStatus), "\n")
22+
23+
lines := strings.Split(gitStatus, "\n")
2324
if len(lines) == 1 && lines[0] == "" {
24-
return root // No changes.
25+
return root
2526
}
2627

2728
for _, line := range lines {
@@ -63,6 +64,11 @@ func BuildTree(gitStatus string) *Node {
6364
return root
6465
}
6566

67+
// Render traverses the tree and returns a slice of formatted strings for display.
68+
func (n *Node) Render(theme Theme) []string {
69+
return n.renderRecursive("", theme)
70+
}
71+
6672
// findChild searches for an immediate child node by name.
6773
func (n *Node) findChild(name string) *Node {
6874
for _, child := range n.children {
@@ -73,7 +79,7 @@ func (n *Node) findChild(name string) *Node {
7379
return nil
7480
}
7581

76-
// sort recursively sorts the children of a node.
82+
// sort recursively sorts the children of a node, placing directories before files.
7783
func (n *Node) sort() {
7884
if n.children == nil {
7985
return
@@ -82,7 +88,7 @@ func (n *Node) sort() {
8288
isDirI := len(n.children[i].children) > 0
8389
isDirJ := len(n.children[j].children) > 0
8490
if isDirI != isDirJ {
85-
return isDirI // Directories first.
91+
return isDirI
8692
}
8793
return n.children[i].name < n.children[j].name
8894
})
@@ -92,51 +98,48 @@ func (n *Node) sort() {
9298
}
9399
}
94100

95-
// compact recursively merges directories that contain only a single sub-directory.
101+
// compact recursively merges directories that contain only a single sub-directory
102+
// to create a more concise file tree.
96103
func (n *Node) compact() {
97104
if n.children == nil {
98105
return
99106
}
107+
108+
// Recursively compact children first.
100109
for _, child := range n.children {
101110
child.compact()
102111
}
112+
113+
// Do not compact the root node itself.
103114
if n.name == "." {
104115
return
105116
}
117+
118+
// If a directory has only one child and that child is also a directory, merge them.
106119
for len(n.children) == 1 && len(n.children[0].children) > 0 {
107120
child := n.children[0]
108121
n.name = filepath.Join(n.name, child.name)
109122
n.children = child.children
110123
}
111124
}
112125

113-
// Render traverses the tree and returns a slice of strings for display.
114-
func (n *Node) Render(theme Theme) []string {
115-
return n.renderRecursive("", theme)
116-
}
117-
118-
// renderRecursive creates raw, tab-delimited strings for the view to parse.
126+
// renderRecursive performs a depth-first traversal of the tree to generate
127+
// raw, tab-delimited strings for the view to parse and style.
119128
func (n *Node) renderRecursive(prefix string, theme Theme) []string {
120129
var lines []string
121-
for i, child := range n.children {
122-
connector := theme.Tree.Connector
123-
newPrefix := theme.Tree.Prefix
124-
if i == len(n.children)-1 {
125-
connector = theme.Tree.ConnectorLast
126-
newPrefix = theme.Tree.PrefixLast
127-
}
130+
for _, child := range n.children {
131+
newPrefix := prefix + theme.Tree.Prefix
128132

129133
if len(child.children) > 0 { // It's a directory
130-
// Format: "prefix\tconnector\tname"
131-
lines = append(lines, fmt.Sprintf("%s%s▼\t\t%s", prefix, connector, child.name))
132-
lines = append(lines, child.renderRecursive(prefix+newPrefix, theme)...)
133-
} else { // It's a file
134+
displayName := "▼ " + child.name
135+
lines = append(lines, fmt.Sprintf("%s\t\t%s", prefix, displayName))
136+
lines = append(lines, child.renderRecursive(newPrefix, theme)...)
137+
} else { // It's a file.
134138
displayName := child.name
135139
if child.isRenamed {
136140
displayName = child.path
137141
}
138-
// Format: "prefix\tconnector\tstatus\tname"
139-
lines = append(lines, fmt.Sprintf("%s%s\t%s\t%s", prefix, connector, child.status, displayName))
142+
lines = append(lines, fmt.Sprintf("%s\t%s\t%s", prefix, child.status, displayName))
140143
}
141144
}
142145
return lines

internal/tui/model.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func initialModel() Model {
3434
repoName, branchName, _ := gc.GetRepoInfo()
3535
initialContent := "Loading..."
3636

37-
// Create a slice to hold all our panels.
37+
// Create a slice to hold all UI panels.
3838
panels := make([]panel, totalPanels)
3939
for i := range panels {
4040
vp := viewport.New(0, 0)

internal/tui/panels.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
// Panel is an enumeration of all the panels in the UI.
1010
type Panel int
1111

12+
// Defines the available panels in the UI.
1213
const (
1314
MainPanel Panel = iota
1415
StatusPanel

internal/tui/theme.go

Lines changed: 58 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package tui
22

33
import "github.com/charmbracelet/lipgloss"
44

5+
// Palette defines a set of colors for a theme.
56
type Palette struct {
67
Black, Red, Green, Yellow, Blue, Magenta, Cyan, White,
78
BrightBlack, BrightRed, BrightGreen, BrightYellow, BrightBlue, BrightMagenta, BrightCyan, BrightWhite,
@@ -85,8 +86,6 @@ var Palettes = map[string]Palette{
8586

8687
// Theme represents the styles for different components of the UI.
8788
type Theme struct {
88-
ActivePanel lipgloss.Style
89-
InactivePanel lipgloss.Style
9089
ActiveTitle lipgloss.Style
9190
InactiveTitle lipgloss.Style
9291
NormalText lipgloss.Style
@@ -95,34 +94,24 @@ type Theme struct {
9594
HelpButton lipgloss.Style
9695
ScrollbarThumb lipgloss.Style
9796
SelectedLine lipgloss.Style
98-
99-
// Git status styles
100-
GitStaged lipgloss.Style
101-
GitUnstaged lipgloss.Style
102-
GitUntracked lipgloss.Style
103-
GitConflicted lipgloss.Style
104-
105-
// Branch styles
106-
BranchCurrent lipgloss.Style
107-
BranchDate lipgloss.Style
108-
109-
// Commit log styles
110-
CommitSHA lipgloss.Style
111-
CommitAuthor lipgloss.Style
112-
CommitMerge lipgloss.Style
113-
114-
// Stash styles
115-
StashName lipgloss.Style
116-
StashMessage lipgloss.Style
117-
118-
Tree TreeStyle
119-
97+
GitStaged lipgloss.Style
98+
GitUnstaged lipgloss.Style
99+
GitUntracked lipgloss.Style
100+
GitConflicted lipgloss.Style
101+
BranchCurrent lipgloss.Style
102+
BranchDate lipgloss.Style
103+
CommitSHA lipgloss.Style
104+
CommitAuthor lipgloss.Style
105+
CommitMerge lipgloss.Style
106+
GraphEdge lipgloss.Style
107+
GraphNode lipgloss.Style
108+
StashName lipgloss.Style
109+
StashMessage lipgloss.Style
120110
ActiveBorder BorderStyle
121111
InactiveBorder BorderStyle
112+
Tree TreeStyle
122113
}
123114

124-
const scrollThumb string = "▐"
125-
126115
// BorderStyle defines the characters and styles for a panel's border.
127116
type BorderStyle struct {
128117
Top string
@@ -136,63 +125,49 @@ type BorderStyle struct {
136125
Style lipgloss.Style
137126
}
138127

128+
// TreeStyle defines the characters used to render the file tree.
139129
type TreeStyle struct {
140-
Connector string
141-
ConnectorLast string
142-
Prefix string
143-
PrefixLast string
130+
Connector, ConnectorLast, Prefix, PrefixLast string
144131
}
145132

146-
// NewThemeFromPalette creates a Theme from a Palette.
147-
func NewThemeFromPalette(p Palette) Theme {
148-
return Theme{
149-
ActiveTitle: lipgloss.NewStyle().
150-
Foreground(lipgloss.Color(p.Bg)).
151-
Background(lipgloss.Color(p.BrightCyan)),
152-
InactiveTitle: lipgloss.NewStyle().
153-
Foreground(lipgloss.Color(p.Fg)).
154-
Background(lipgloss.Color(p.Black)),
155-
NormalText: lipgloss.NewStyle().
156-
Foreground(lipgloss.Color(p.Fg)),
157-
HelpTitle: lipgloss.NewStyle().
158-
Foreground(lipgloss.Color(p.Green)).
159-
Bold(true),
160-
HelpKey: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Yellow)),
161-
HelpButton: lipgloss.NewStyle().
162-
Foreground(lipgloss.Color(p.Bg)).
163-
Background(lipgloss.Color(p.Green)).
164-
Margin(0, 1),
165-
ScrollbarThumb: lipgloss.NewStyle().Foreground(lipgloss.Color(p.BrightGreen)),
166-
SelectedLine: lipgloss.NewStyle().
167-
Background(lipgloss.Color(p.DarkBlue)).
168-
Foreground(lipgloss.Color(p.BrightWhite)),
133+
const (
134+
scrollThumb = "▐"
135+
graphNode = "○"
136+
)
169137

170-
GitStaged: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Green)),
171-
GitUnstaged: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Red)),
172-
GitUntracked: lipgloss.NewStyle().Foreground(lipgloss.Color(p.BrightBlack)),
173-
GitConflicted: lipgloss.NewStyle().Foreground(lipgloss.Color(p.BrightRed)).Bold(true),
174-
175-
// Tree style
176-
Tree: TreeStyle{
177-
Connector: "├─",
178-
ConnectorLast: "└─",
179-
Prefix: "│ ",
180-
PrefixLast: " ",
181-
},
182-
183-
// Branch styles
184-
BranchCurrent: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Green)).Bold(true),
185-
BranchDate: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Yellow)),
186-
187-
// Commit log styles
188-
CommitSHA: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Yellow)),
189-
CommitAuthor: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Green)),
190-
CommitMerge: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Magenta)),
138+
// Themes holds all the available themes, generated from palettes.
139+
var Themes = map[string]Theme{}
191140

192-
// Stash styles
193-
StashName: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Yellow)),
194-
StashMessage: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Fg)),
141+
func init() {
142+
for name, p := range Palettes {
143+
Themes[name] = NewThemeFromPalette(p)
144+
}
145+
}
195146

147+
// NewThemeFromPalette creates a Theme from a given color Palette.
148+
func NewThemeFromPalette(p Palette) Theme {
149+
return Theme{
150+
ActiveTitle: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Bg)).Background(lipgloss.Color(p.BrightCyan)),
151+
InactiveTitle: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Fg)).Background(lipgloss.Color(p.Black)),
152+
NormalText: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Fg)),
153+
HelpTitle: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Green)).Bold(true),
154+
HelpKey: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Yellow)),
155+
HelpButton: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Bg)).Background(lipgloss.Color(p.Green)).Margin(0, 1),
156+
ScrollbarThumb: lipgloss.NewStyle().Foreground(lipgloss.Color(p.BrightGreen)),
157+
SelectedLine: lipgloss.NewStyle().Background(lipgloss.Color(p.DarkBlue)).Foreground(lipgloss.Color(p.BrightWhite)),
158+
GitStaged: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Green)),
159+
GitUnstaged: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Red)),
160+
GitUntracked: lipgloss.NewStyle().Foreground(lipgloss.Color(p.BrightBlack)),
161+
GitConflicted: lipgloss.NewStyle().Foreground(lipgloss.Color(p.BrightRed)).Bold(true),
162+
BranchCurrent: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Green)).Bold(true),
163+
BranchDate: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Yellow)),
164+
CommitSHA: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Yellow)),
165+
CommitAuthor: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Green)),
166+
CommitMerge: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Magenta)),
167+
GraphEdge: lipgloss.NewStyle().Foreground(lipgloss.Color(p.BrightBlack)),
168+
GraphNode: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Green)),
169+
StashName: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Yellow)),
170+
StashMessage: lipgloss.NewStyle().Foreground(lipgloss.Color(p.Fg)),
196171
ActiveBorder: BorderStyle{
197172
Top: "─", Bottom: "─", Left: "│", Right: "│",
198173
TopLeft: "╭", TopRight: "╮", BottomLeft: "╰", BottomRight: "╯",
@@ -203,15 +178,12 @@ func NewThemeFromPalette(p Palette) Theme {
203178
TopLeft: "╭", TopRight: "╮", BottomLeft: "╰", BottomRight: "╯",
204179
Style: lipgloss.NewStyle().Foreground(lipgloss.Color(p.BrightBlack)),
205180
},
206-
}
207-
}
208-
209-
// Themes holds all the available themes, generated from palettes.
210-
var Themes = map[string]Theme{}
211-
212-
func init() {
213-
for name, p := range Palettes {
214-
Themes[name] = NewThemeFromPalette(p)
181+
Tree: TreeStyle{
182+
Connector: "",
183+
ConnectorLast: "",
184+
Prefix: " ",
185+
PrefixLast: " ",
186+
},
215187
}
216188
}
217189

internal/tui/tui.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ func (a *App) watchGitDir() {
5252
gc := git.NewGitCommands()
5353
gitDir, err := gc.GetGitRepoPath()
5454
if err != nil {
55+
// Not in a git repo, no need to watch.
5556
return
5657
}
5758

@@ -78,11 +79,11 @@ func (a *App) watchGitDir() {
7879

7980
for {
8081
select {
81-
case _, ok := <-watcher.Events: // We don't need to inspect the event
82+
case _, ok := <-watcher.Events:
8283
if !ok {
8384
return
8485
}
85-
needsUpdate = true // Set to true on ANY event
86+
needsUpdate = true // Set flag on any event.
8687
case err, ok := <-watcher.Errors:
8788
if !ok {
8889
return

0 commit comments

Comments
 (0)