8 Commits
Author SHA1 Message Date
simonfrankenandClaude Sonnet 5.5 90ea9cc241 Add shift+arrows for collapse/expand all
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 20:30:14 +02:00
simonfrankenandClaude Sonnet 5.5 e4340fb35e Show h/l, j/k and H/L in the key legend
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 20:29:47 +02:00
simonfrankenandClaude Sonnet 5.5 6cb7bd6967 Add H/L as aliases for collapse/expand all
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 20:27:27 +02:00
simonfrankenandClaude Sonnet 5.5 17151a578d Split hidden issue counts by level
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 20:21:00 +02:00
simonfrankenandClaude Sonnet 5.5 aec4ec6e3b Show failed loads as errors in the header
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 20:17:27 +02:00
simonfrankenandClaude Sonnet 5.5 e5fdc875af Count failed loads as danger in hidden issues
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 20:14:47 +02:00
simonfrankenandClaude Sonnet 5.5 af6d03a5b1 Mark untracked directories that are git repositories
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 20:12:13 +02:00
simonfrankenandClaude Sonnet 5.5 69d0f9b704 Don't count failed loads as loading; explain missing gitlink
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 20:09:59 +02:00
13 changed files with 142 additions and 42 deletions
+6 -4
View File
@@ -53,15 +53,17 @@ everyone else's checkout.
**Context.** Only the first level is shown by default, so nested problems **Context.** Only the first level is shown by default, so nested problems
would be invisible. would be invisible.
**Decision.** Collapsed parents show `·k` (issues in all descendants), colored **Decision.** Collapsed parents show `·k` (issues in all descendants), one
by the worst one. Problems in a child do **not** mark the parent itself as count per level: red for danger, then yellow for warn (amended: a single
`changed`. count in the worst color made 1 error + 1 warning look like 2 errors; the
status column grew from 14 to 16 for the second count). Problems in a child
do **not** mark the parent itself as `changed`.
## D8 — Collapsed by default, branch-first navigation ## D8 — Collapsed by default, branch-first navigation
**Decision.** Only the first level is visible. `→` expands and enters the **Decision.** Only the first level is visible. `→` expands and enters the
first child, `←` collapses and goes to the parent, `space` toggles, first child, `←` collapses and goes to the parent, `space` toggles,
`ctrl+→/←` expand/collapse all (see [ux-guide.md](ux-guide.md#navigation)). `shift+→/←`, `L`/`H` (or `ctrl+→/←`) expand/collapse all (see [ux-guide.md](ux-guide.md#navigation)).
`←` on an expanded node collapses it in place, so a node can be closed `←` on an expanded node collapses it in place, so a node can be closed
without leaving it. Remembered expansion is kept on collapse, but reset by without leaving it. Remembered expansion is kept on collapse, but reset by
"collapse all". "collapse all".
+2 -1
View File
@@ -34,7 +34,8 @@ commit hashes. It doesn't answer the questions users actually have:
- **F5** A collapsed submodule shows the number of its **direct** children, - **F5** A collapsed submodule shows the number of its **direct** children,
e.g. `[2]`. e.g. `[2]`.
- **F6** A collapsed submodule shows the number of issues **hidden** in all - **F6** A collapsed submodule shows the number of issues **hidden** in all
its descendants (`·2`), colored by the most severe hidden issue. its descendants (`·1 ·2`), one count per level: errors in red, then
warnings in yellow.
- **F7** Expansion state of descendants is remembered when a parent is - **F7** Expansion state of descendants is remembered when a parent is
collapsed and restored when it is expanded again (except after collapsed and restored when it is expanded again (except after
"collapse all", which resets it). "collapse all", which resets it).
+5 -3
View File
@@ -72,7 +72,7 @@ truth for what counts as a problem.
| `conflict` | Merge conflict in the worktree | Danger | | `conflict` | Merge conflict in the worktree | Danger |
| `missing` | Not initialized; directory missing | Warn | | `missing` | Not initialized; directory missing | Warn |
| `off pin` | Checkout differs from pin | Warn | | `off pin` | Checkout differs from pin | Warn |
| `changed` | Uncommitted changes; untracked files | Warn | | `changed` | Uncommitted changes; untracked files (worded "untracked repository" if all of them are git repositories) | Warn |
Rules: Rules:
@@ -96,8 +96,10 @@ Rules:
### Hidden issues ### Hidden issues
`Submodule.HiddenIssues()` counts the issues of **all** loaded descendants `Submodule.HiddenIssues()` counts the issues of **all** loaded descendants
and returns the most severe level. A collapsed submodule shows this as `·k` per level (`Hidden{Warn, Danger}`). A collapsed submodule shows them as `·k`
in that level's color, so a danger can never hide inside a collapsed subtree. per level, red first, then yellow, so a danger can never hide inside a
collapsed subtree and a warning is never mistaken for one.
A descendant whose load failed (`error`) counts as one danger.
## Resolving a pin to a ref ## Resolving a pin to a ref
+14 -9
View File
@@ -26,7 +26,7 @@
my_submodule_3 unpushed origin/main ↑1 │ Pinned b7c8d9e Hotfix null pointer my_submodule_3 unpushed origin/main ↑1 │ Pinned b7c8d9e Hotfix null pointer
libs/ │ Checkout b7c8d9e Hotfix null pointer libs/ │ Checkout b7c8d9e Hotfix null pointer
logging origin/main ↓3 │ Tip 6f5e4d3 Release 2.1.0 logging origin/main ↓3 │ Tip 6f5e4d3 Release 2.1.0
↑/↓ move • ←/→ collapse/expand • space toggle • u checkout pin • … • ? help • q quit ↑↓/jk move • ←→/hl fold • ⇧←→/HL fold all • space toggle • ? help • q quit
``` ```
| Area | Height / width | | Area | Height / width |
@@ -47,7 +47,7 @@ indent ▸ name [n] status ref delta
| indent | 2 per depth | | | indent | 2 per depth | |
| toggle | 3 | `▸` collapsed, `▾` expanded, blank for leaves | | toggle | 3 | `▸` collapsed, `▾` expanded, blank for leaves |
| name | fits the widest visible name | name, `[n]` = direct children (collapsed only) | | name | fits the widest visible name | name, `[n]` = direct children (collapsed only) |
| status | 14 | issue word, `+n`, `·k` hidden issues; muted `in sync` without issues; spinner while loading | | status | 16 | issue word, `+n`, `·k` hidden issues per level (red, then yellow); muted `in sync` without issues; spinner while loading |
| ref | widest ref, max 22 | resolved ref; muted short SHA if unresolved | | ref | widest ref, max 22 | resolved ref; muted short SHA if unresolved |
| delta | 6 | `↑n`, `↓n`, `↑n↓m` | | delta | 6 | `↑n`, `↓n`, `↑n↓m` |
@@ -91,7 +91,9 @@ Changes 5 files · 2M 1A 1D 1R · +37 −12
meaning, the color only supports it (N4). meaning, the color only supports it (N4).
- Letters and colors: `!` red bold, `M` yellow, `A` green, `D` red, `R` - Letters and colors: `!` red bold, `M` yellow, `A` green, `D` red, `R`
cyan, `?` muted. Line counts: green `+n`, red `−n`; `bin` for binary cyan, `?` muted. Line counts: green `+n`, red `−n`; `bin` for binary
files, `submodule` for a moved nested pin, nothing for untracked files. files, `submodule` for a moved nested pin, `repository` for an untracked
directory that is a git repository itself (cloned, never added as a
submodule), nothing for other untracked files.
- Order: kind (`!`, `M`, `A`, `D`, `R`, `?`), then changed lines, largest - Order: kind (`!`, `M`, `A`, `D`, `R`, `?`), then changed lines, largest
first, because the biggest edits are the ones people remember. first, because the biggest edits are the ones people remember.
- Budget: 10 files. Every kind that occurs gets at least one line, the - Budget: 10 files. Every kind that occurs gets at least one line, the
@@ -118,10 +120,12 @@ Changes 5 files · 2M 1A 1D 1R · +37 −12
- Untracked directories are shown as one entry (`tmp/`). - Untracked directories are shown as one entry (`tmp/`).
### Header ### Header
`path branch ↑↓ · n submodules · <count> <issue>… · n pin staged · n loading · <fetch>` `path branch ↑↓ · n submodules · n error · <count> <issue>… · n pin staged · n loading · <fetch>`
Issue counts are ordered by severity (unpushed, conflict, missing, off pin, Issue counts are ordered by severity (unpushed, conflict, missing, off pin,
changed), each in its level color. Zero counts are omitted. A detached changed), each in its level color. Zero counts are omitted. `n loading`
counts only submodules whose status is still being loaded; ones whose load
failed are counted separately as `n error` (red, before the issue counts). A detached
superproject shows `(detached)` as its branch. superproject shows `(detached)` as its branch.
`<fetch>` on the right: `<fetch>` on the right:
@@ -179,8 +183,8 @@ colors in components.
| `space` | Toggle expand/collapse of the selected submodule | | `space` | Toggle expand/collapse of the selected submodule |
| `→` / `l` | Collapsed with children: expand and select the first child. Expanded: select the first child. Leaf: nothing | | `→` / `l` | Collapsed with children: expand and select the first child. Expanded: select the first child. Leaf: nothing |
| `←` / `h` | Expanded: collapse it (stays selected). Otherwise: collapse the parent and select it. Top level: nothing | | `←` / `h` | Expanded: collapse it (stays selected). Otherwise: collapse the parent and select it. Top level: nothing |
| `ctrl+→` | Expand all; then follow the first child from the selection down to a submodule without children | | `L` / `shift+→` / `ctrl+→` | Expand all; then follow the first child from the selection down to a submodule without children |
| `ctrl+←` | Collapse all (resets remembered expansion); select the top-level ancestor | | `H` / `shift+←` / `ctrl+←` | Collapse all (resets remembered expansion); select the top-level ancestor |
| `home` / `g`, `end` / `G` | First / last submodule | | `home` / `g`, `end` / `G` | First / last submodule |
| `pgup`, `pgdn` | Move by one screen | | `pgup`, `pgdn` | Move by one screen |
| `?` | Toggle full help | | `?` | Toggle full help |
@@ -205,5 +209,6 @@ is selected, headers above it are shown too.
## Accessibility and terminals ## Accessibility and terminals
- `SUBMARINE_ASCII=1`: ASCII symbols (`^ v > v . *`) instead of Unicode. - `SUBMARINE_ASCII=1`: ASCII symbols (`^ v > v . *`) instead of Unicode.
- Some terminals or multiplexers capture `ctrl+←/→`; letter aliases can be - Many setups capture `ctrl+←/→` (macOS Mission Control, some terminals and
added if needed. multiplexers), so expand/collapse all also work as `shift+→/←` and as `L` / `H`, the
shifted vim keys for `→` / `←` (for terminals that don't report shift+arrows).
+4 -1
View File
@@ -158,7 +158,7 @@ func status(ctx context.Context, in info) (*submodule.Status, error) {
} }
pin := parseGitlink(out) pin := parseGitlink(out)
if pin == "" { if pin == "" {
return nil, errors.New("no gitlink in the superproject index") return nil, errors.New("listed in .gitmodules, but has no gitlink in the superproject index (stale entry?)")
} }
out, _, err = try(ctx, in.superDir, "ls-tree", "HEAD", "--", in.relPath) out, _, err = try(ctx, in.superDir, "ls-tree", "HEAD", "--", in.relPath)
if err != nil { if err != nil {
@@ -387,6 +387,9 @@ func changes(ctx context.Context, dir string, entries []entry) (*submodule.Chang
c.Added += n.added c.Added += n.added
c.Removed += n.removed c.Removed += n.removed
} }
if e.kind == submodule.ChangeUntracked && strings.HasSuffix(e.path, "/") {
f.Repo = initialized(filepath.Join(dir, filepath.FromSlash(e.path)))
}
if e.kind != submodule.ChangeRemoved { if e.kind != submodule.ChangeRemoved {
if fi, err := os.Stat(filepath.Join(dir, filepath.FromSlash(e.path))); err == nil { if fi, err := os.Stat(filepath.Join(dir, filepath.FromSlash(e.path))); err == nil {
f.Modified = fi.ModTime() f.Modified = fi.ModTime()
+1
View File
@@ -132,6 +132,7 @@ type FileChange struct {
Removed int // removed lines Removed int // removed lines
Binary bool // line counts are unknown Binary bool // line counts are unknown
Submodule bool // a nested submodule pointer changed Submodule bool // a nested submodule pointer changed
Repo bool // an untracked directory that is a git repository itself
Modified time.Time // modification time of the file; zero if it doesn't exist Modified time.Time // modification time of the file; zero if it doesn't exist
} }
+37 -7
View File
@@ -55,21 +55,36 @@ func (s *Status) Issues() []Issue {
case WorktreeModified: case WorktreeModified:
out = append(out, Issue{IssueChanged, LevelWarn, "uncommitted changes"}) out = append(out, Issue{IssueChanged, LevelWarn, "uncommitted changes"})
case WorktreeUntracked: case WorktreeUntracked:
out = append(out, Issue{IssueChanged, LevelWarn, "untracked files"}) out = append(out, Issue{IssueChanged, LevelWarn, s.untrackedText()})
} }
return out return out
} }
// HiddenIssues counts the issues of all loaded descendants and returns the // Hidden counts issues below a submodule per level.
// most severe level among them. type Hidden struct{ Warn, Danger int }
func (m *Submodule) HiddenIssues() (count int, worst Level) {
// Total is the number of issues of all levels.
func (h Hidden) Total() int { return h.Warn + h.Danger }
// HiddenIssues counts the issues of all loaded descendants per level. A
// descendant whose load failed counts as one danger, since its state is
// unknown.
func (m *Submodule) HiddenIssues() Hidden {
var h Hidden
Walk(m.Children, func(c *Submodule) { Walk(m.Children, func(c *Submodule) {
if c.Status == nil && c.Err != nil {
h.Danger++
}
for _, is := range c.Status.Issues() { for _, is := range c.Status.Issues() {
count++ switch is.Level {
worst = max(worst, is.Level) case LevelDanger:
h.Danger++
case LevelWarn:
h.Warn++
}
} }
}) })
return count, worst return h
} }
// Walk calls fn for every submodule in depth-first order. // Walk calls fn for every submodule in depth-first order.
@@ -79,3 +94,18 @@ func Walk(mods []*Submodule, fn func(*Submodule)) {
Walk(m.Children, fn) Walk(m.Children, fn)
} }
} }
// untrackedText words the untracked-files issue: "untracked repository" if
// everything untracked is a git repository of its own, e.g. a clone that was
// never added as a submodule.
func (s *Status) untrackedText() string {
if s.Changes == nil || s.Changes.Total != len(s.Changes.Files) {
return "untracked files"
}
for _, f := range s.Changes.Files {
if f.Kind != ChangeUntracked || !f.Repo {
return "untracked files"
}
}
return "untracked repository"
}
+31 -3
View File
@@ -1,6 +1,7 @@
package submodule package submodule
import ( import (
"errors"
"slices" "slices"
"testing" "testing"
) )
@@ -45,8 +46,35 @@ func TestHiddenIssues(t *testing.T) {
loading := &Submodule{} loading := &Submodule{}
root := &Submodule{Status: ok, Children: []*Submodule{child, {Status: ok}, loading}} root := &Submodule{Status: ok, Children: []*Submodule{child, {Status: ok}, loading}}
count, worst := root.HiddenIssues() if got := root.HiddenIssues(); got != (Hidden{Warn: 1, Danger: 1}) {
if count != 2 || worst != LevelDanger { t.Errorf("HiddenIssues() = %+v; want 1 warn, 1 danger", got)
t.Errorf("HiddenIssues() = %d, %v; want 2, LevelDanger", count, worst) }
}
func TestUntrackedRepositoryWording(t *testing.T) {
repo := FileChange{Path: "lib/x/", Kind: ChangeUntracked, Repo: true}
file := FileChange{Path: "a.txt", Kind: ChangeUntracked}
for _, c := range []struct {
files []FileChange
want string
}{
{[]FileChange{repo}, "untracked repository"},
{[]FileChange{repo, file}, "untracked files"},
{[]FileChange{file}, "untracked files"},
} {
s := &Status{Worktree: WorktreeUntracked, Changes: &Changes{Total: len(c.files), Files: c.files}}
got := s.Issues()
if len(got) == 0 || got[len(got)-1].Detail != c.want {
t.Errorf("%v: got %v, want %q", c.files, got, c.want)
}
}
}
func TestHiddenIssuesCountLoadErrors(t *testing.T) {
bad := &Submodule{Err: errors.New("boom")}
loading := &Submodule{}
parent := &Submodule{Children: []*Submodule{bad, loading}}
if got := parent.HiddenIssues(); got != (Hidden{Danger: 1}) {
t.Errorf("got %+v; want 1 danger", got)
} }
} }
+2
View File
@@ -163,6 +163,8 @@ func (m Model) fileLines(files []submodule.FileChange) []string {
switch { switch {
case f.Submodule: case f.Submodule:
r.counts = muted.Render("submodule") r.counts = muted.Render("submodule")
case f.Repo:
r.counts = muted.Render("repository")
case f.Binary: case f.Binary:
r.counts = muted.Render("bin") r.counts = muted.Render("bin")
default: default:
+10 -2
View File
@@ -138,8 +138,16 @@ func (m Model) lines(s *submodule.Submodule) []string {
out = append(out, "") out = append(out, "")
if n := len(s.Children); n > 0 { if n := len(s.Children); n > 0 {
children := fmt.Sprintf("%d direct", n) children := fmt.Sprintf("%d direct", n)
if k, worst := s.HiddenIssues(); k > 0 { h := s.HiddenIssues()
children += t.LevelStyle(worst, lipgloss.NewStyle()).Render(fmt.Sprintf(" · %d issue(s) below", k)) var parts []string
if h.Danger > 0 {
parts = append(parts, t.LevelStyle(submodule.LevelDanger, lipgloss.NewStyle()).Render(fmt.Sprintf("%d error(s)", h.Danger)))
}
if h.Warn > 0 {
parts = append(parts, t.LevelStyle(submodule.LevelWarn, lipgloss.NewStyle()).Render(fmt.Sprintf("%d warning(s)", h.Warn)))
}
if len(parts) > 0 {
children += muted.Render(" · ") + strings.Join(parts, muted.Render(", ")) + muted.Render(" below")
} }
out = append(out, label("Children")+children) out = append(out, label("Children")+children)
} }
+8 -1
View File
@@ -36,13 +36,17 @@ func View(t theme.Theme, repo *submodule.Repo, fetch Fetch, spinner string, widt
} }
// count submodules per issue kind (most severe first), plus staged pins // count submodules per issue kind (most severe first), plus staged pins
var total, loading, staged int var total, loading, errored, staged int
perKind := map[submodule.IssueKind]int{} perKind := map[submodule.IssueKind]int{}
level := map[submodule.IssueKind]submodule.Level{} level := map[submodule.IssueKind]submodule.Level{}
submodule.Walk(repo.Submodules, func(s *submodule.Submodule) { submodule.Walk(repo.Submodules, func(s *submodule.Submodule) {
total++ total++
if s.Status == nil { if s.Status == nil {
if s.Err == nil {
loading++ loading++
} else {
errored++
}
return return
} }
if s.Status.PinStaged { if s.Status.PinStaged {
@@ -56,6 +60,9 @@ func View(t theme.Theme, repo *submodule.Repo, fetch Fetch, spinner string, widt
sep := seg(t.Muted, " · ") sep := seg(t.Muted, " · ")
mid := seg(t.Muted, fmt.Sprintf("%d submodules", total)) mid := seg(t.Muted, fmt.Sprintf("%d submodules", total))
if errored > 0 {
mid += sep + t.LevelStyle(submodule.LevelDanger, bar).Render(fmt.Sprintf("%d error", errored))
}
for _, k := range []submodule.IssueKind{ for _, k := range []submodule.IssueKind{
submodule.IssueUnpushed, submodule.IssueConflict, submodule.IssueMissing, submodule.IssueUnpushed, submodule.IssueConflict, submodule.IssueMissing,
submodule.IssueOffPin, submodule.IssueChanged, submodule.IssueOffPin, submodule.IssueChanged,
+8 -5
View File
@@ -12,13 +12,13 @@ type keyMap struct {
func defaultKeys() keyMap { func defaultKeys() keyMap {
return keyMap{ return keyMap{
Up: key.NewBinding(key.WithKeys("up", "k"), key.WithHelp("↑/↓", "move")), Up: key.NewBinding(key.WithKeys("up", "k"), key.WithHelp("↑/k", "up")),
Down: key.NewBinding(key.WithKeys("down", "j"), key.WithHelp("↓/j", "down")), Down: key.NewBinding(key.WithKeys("down", "j"), key.WithHelp("↓/j", "down")),
Left: key.NewBinding(key.WithKeys("left", "h"), key.WithHelp("←/→", "collapse/expand")), Left: key.NewBinding(key.WithKeys("left", "h"), key.WithHelp("←/h", "collapse, parent")),
Right: key.NewBinding(key.WithKeys("right", "l"), key.WithHelp("→/l", "expand, first child")), Right: key.NewBinding(key.WithKeys("right", "l"), key.WithHelp("→/l", "expand, first child")),
Toggle: key.NewBinding(key.WithKeys(" "), key.WithHelp("space", "toggle")), Toggle: key.NewBinding(key.WithKeys(" "), key.WithHelp("space", "toggle")),
CollapseAll: key.NewBinding(key.WithKeys("ctrl+left"), key.WithHelp("ctrl+←", "collapse all")), CollapseAll: key.NewBinding(key.WithKeys("shift+left", "ctrl+left", "H"), key.WithHelp("H/⇧←", "collapse all")),
ExpandAll: key.NewBinding(key.WithKeys("ctrl+right"), key.WithHelp("ctrl+→", "expand all")), ExpandAll: key.NewBinding(key.WithKeys("shift+right", "ctrl+right", "L"), key.WithHelp("L/⇧→", "expand all")),
Home: key.NewBinding(key.WithKeys("home", "g"), key.WithHelp("home/g", "first")), Home: key.NewBinding(key.WithKeys("home", "g"), key.WithHelp("home/g", "first")),
End: key.NewBinding(key.WithKeys("end", "G"), key.WithHelp("end/G", "last")), End: key.NewBinding(key.WithKeys("end", "G"), key.WithHelp("end/G", "last")),
PageUp: key.NewBinding(key.WithKeys("pgup"), key.WithHelp("pgup", "page up")), PageUp: key.NewBinding(key.WithKeys("pgup"), key.WithHelp("pgup", "page up")),
@@ -35,7 +35,10 @@ func defaultKeys() keyMap {
} }
func (k keyMap) ShortHelp() []key.Binding { func (k keyMap) ShortHelp() []key.Binding {
return []key.Binding{k.Up, k.Left, k.Toggle, k.Update, k.Bump, k.Fetch, k.Help, k.Quit} move := key.NewBinding(key.WithKeys("up"), key.WithHelp("↑↓/jk", "move"))
fold := key.NewBinding(key.WithKeys("left"), key.WithHelp("←→/hl", "fold"))
all := key.NewBinding(key.WithKeys("H"), key.WithHelp("⇧←→/HL", "fold all"))
return []key.Binding{move, fold, all, k.Toggle, k.Help, k.Quit}
} }
func (k keyMap) FullHelp() [][]key.Binding { func (k keyMap) FullHelp() [][]key.Binding {
+11 -3
View File
@@ -15,7 +15,7 @@ import (
) )
const ( const (
statusWidth = 14 // "unpushed +1 ·2" statusWidth = 16 // "unpushed +1 ·2 ·3"
deltaWidth = 6 deltaWidth = 6
maxRefWidth = 22 maxRefWidth = 22
) )
@@ -414,11 +414,19 @@ func (m Model) nodeLine(r row, nameColW, refW int) string {
default: default:
status = seg(t.Muted, theme.InSync) status = seg(t.Muted, theme.InSync)
} }
if k, worst := s.HiddenIssues(); collapsed && k > 0 { if h := s.HiddenIssues(); collapsed && h.Total() > 0 {
for _, c := range []struct {
level submodule.Level
n int
}{{submodule.LevelDanger, h.Danger}, {submodule.LevelWarn, h.Warn}} {
if c.n == 0 {
continue
}
if status != "" { if status != "" {
status += pad(1) status += pad(1)
} }
status += t.LevelStyle(worst, base).Render(fmt.Sprintf("%s%d", t.Glyphs.Problems, k)) status += t.LevelStyle(c.level, base).Render(fmt.Sprintf("%s%d", t.Glyphs.Problems, c.n))
}
} }
status = ansi.Truncate(status, statusWidth, "") status = ansi.Truncate(status, statusWidth, "")
status += pad(statusWidth - lipgloss.Width(status)) status += pad(statusWidth - lipgloss.Width(status))