Files
MujicaUI/git/branch_list.go
T

123 lines
3.7 KiB
Go

package git
import (
"fmt"
"log/slog"
"github.com/egoist/mygo/ui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/icons"
"github.com/ZacharyZhang-NY/MujicaUI/theme"
)
// Branch is a local or remote branch.
type Branch struct {
// Name is unique; remote names carry the remote, as origin/main.
Name string
Remote bool
// Current marks the checked-out branch.
Current bool
// Upstream is the tracked remote branch, if any.
Upstream string
Ahead, Behind int
}
// GitAction is a row action of a version-control list.
type GitAction struct {
ID string
Label string
// Icon names an icon from icons/.
Icon string
}
// BranchListOptions configure a BranchList.
type BranchListOptions struct {
// Actions show on every row; Action returns their IDs.
Actions []GitAction
Status data.DataStatus
Error string
}
// BranchList shows branches with upstream and ahead/behind counts.
func BranchList(c *ui.Context, s *data.ListState[string], branches []Branch, opts BranchListOptions) GitListResult {
gitActionsCheck(opts.Actions)
var r GitListResult
v := data.List(c, s, len(branches), data.ListOptions[string]{
Key: func(i int) string { return branchKey(branches[i]) },
Label: func(i int) string { return branches[i].Name },
Status: opts.Status, Error: opts.Error, Empty: core.Msg(c, "branch_list.empty"),
}, func(i int) {
b := branches[i]
branchListRow(c, b)
gitRowActions(c, opts.Actions, b.Name, &r)
})
r.Element, r.changed, r.submitted = v.Element, v.Changed(), v.Submitted()
return r
}
func branchKey(b Branch) string {
if b.Name == "" {
panic("mujica: Branch needs a Name")
}
if b.Ahead < 0 || b.Behind < 0 {
panic("mujica: Branch Ahead and Behind must not be negative")
}
return b.Name
}
func branchListRow(c *ui.Context, b Branch) {
k := core.Tokens(c)
u := core.Density(c).Unit()
small := core.FontSize(c, theme.CaptionSize)
mark := ui.Box(c).Size(16, 16).Shrink(0).Center().Role(ui.RoleNone)
if b.Current {
mark.TextColor(k.AccentText).Children(func() { ui.Icon(c, icons.Must("check")).Size(14, 14) })
}
ui.Icon(c, icons.Must("git-branch")).Size(14, 14).Shrink(0).TextColor(k.TextMuted)
name := ui.Text(c, b.Name).SingleLine().MinWidth(0).Shrink(1)
if b.Current {
name.FontWeight(600)
ui.Text(c, core.Msg(c, "branch_list.current")).FontSize(small).TextColor(k.AccentText).Shrink(0)
}
ui.Spacer(c)
if b.Upstream != "" {
ui.Row(c).Gap(u).AlignItems(ui.Center).Shrink(0).TextColor(k.TextMuted).Children(func() {
ui.Text(c, b.Upstream).FontSize(small).SingleLine().MaxWidth(160)
if b.Ahead > 0 {
ui.Text(c, fmt.Sprintf("↑%d", b.Ahead)).FontSize(small).FontFeatures("tnum").Label(fmt.Sprintf(core.Msg(c, "branch_list.ahead"), b.Ahead))
}
if b.Behind > 0 {
ui.Text(c, fmt.Sprintf("↓%d", b.Behind)).FontSize(small).FontFeatures("tnum").Label(fmt.Sprintf(core.Msg(c, "branch_list.behind"), b.Behind))
}
})
}
}
// gitActionsCheck panics on incomplete or duplicate actions.
func gitActionsCheck(actions []GitAction) {
seen := map[string]bool{}
for _, a := range actions {
if a.ID == "" || a.Label == "" || a.Icon == "" {
panic("mujica: GitAction needs an ID, Label and Icon")
}
if seen[a.ID] {
panic("mujica: duplicate GitAction " + a.ID)
}
seen[a.ID] = true
icons.Must(a.Icon)
}
}
// gitRowActions shows row actions and records the one used.
func gitRowActions(c *ui.Context, actions []GitAction, item string, r *GitListResult) {
for _, a := range actions {
b := core.IconAction(c, icons.Must(a.Icon), a.Label).Tooltip(a.Label).Shrink(0)
if b.Clicked() && !b.IsDisabled() {
r.action, r.item = a.ID, item
slog.Debug("mujica: git list action", "action", a.ID, "item", item)
}
}
}