refactor: one copy of the UI's repeated helpers
CI / Checks (pull_request) Successful in 36s
PR title / Conventional title (pull_request) Successful in 0s

- Text inputs come from newTextInput; the link and folder prompts share one width.
- One starred-first sort for sources, vaults and secrets; accountOf where the account was cut from the label by hand.
- One helper each for the reveal and clipboard countdowns' tick and seconds, the "Open with" choices, the vaults-loaded count, and Go to's and the panes' tabs.
- The details pane builds its label prefix, indent, note and source once; list rows mark matches with MarkMatches.
- The release page opens through openPage, so it now says it opened, as other pages do.
- The cache's account filter is built once per load or save, not per key.
This commit is contained in:
keb committed 2026-10-15 08:54:47 +02:00
1 parent b1eda53d5d
commit 4d601bb82f
18 files changed
+171 -217

No files matched your search

+17
View File
@@ -69,3 +69,20 @@ func TitleStyle(st styles.Styles, focused bool) lipgloss.Style {
}
return st.TitleBlurred
}
// Tabs draws tab names for a title in style ts, the current one
// underlined and the rest muted.
func Tabs(st styles.Styles, ts lipgloss.Style, names []string, current int) string {
var out string
for i, name := range names {
if i > 0 {
out += ts.Render(" - ")
}
if i == current {
out += ts.Underline(true).Render(name)
} else {
out += st.Muted.Render(name)
}
}
return out
}
+27 -21
View File
@@ -198,20 +198,31 @@ func carryStyle(lines []string) []string {
return lines
}
var valueIndent = strings.Repeat(" ", 12)
// fieldLabel starts a detail line: the label, padded so values line up
// at valueIndent.
func fieldLabel(st styles.Styles, label string) string {
return " " + st.Label.Render(fmt.Sprintf("%-10s", label)) + " "
}
// lines renders the body, wrapped to the pane width.
func (d Detail) lines(st styles.Styles) []string {
inner := max(1, d.w-3)
var out []string
field := func(label, value string) {
prefix := " " + st.Label.Render(fmt.Sprintf("%-10s", label)) + " "
indent := strings.Repeat(" ", 12)
wrapped := carryStyle(strings.Split(ansi.Hardwrap(value, max(1, inner-11), true), "\n"))
out = append(out, prefix+wrapped[0])
out = append(out, fieldLabel(st, label)+wrapped[0])
for _, w := range wrapped[1:] {
out = append(out, indent+w)
out = append(out, valueIndent+w)
}
}
muted := st.Muted.Render
note := func() {
if d.note != "" {
field("note", st.Favourite.Render(d.note))
}
}
if d.container == nil {
return []string{muted(" Select a vault or scope to browse its secrets.")}
@@ -221,30 +232,27 @@ func (d Detail) lines(st styles.Styles) []string {
if containerLabel == "" {
containerLabel = "vault"
}
source := d.Icons.Prefix(c.Source.Kind) + cmp.Or(c.Source.ProviderLabel, string(c.Source.Kind)) + " / " + c.Source.Name
if d.secret == nil {
field(containerLabel, c.Name)
if d.note != "" {
field("note", st.Favourite.Render(d.note))
}
note()
if c.Detail != "" {
field("", muted(c.Detail))
}
field("source", d.Icons.Prefix(c.Source.Kind)+cmp.Or(c.Source.ProviderLabel, string(c.Source.Kind))+" / "+c.Source.Name)
field("source", source)
out = append(out, "", muted(" Select a secret to see its details."))
return out
}
s := *d.secret
field("name", s.Name)
if d.note != "" {
field("note", st.Favourite.Render(d.note))
}
note()
field(containerLabel, c.Name)
if c.Source.Kind != provider.KindAzure && c.Detail != "" {
field("", muted(c.Detail))
}
field("source", d.Icons.Prefix(c.Source.Kind)+cmp.Or(c.Source.ProviderLabel, string(c.Source.Kind))+" / "+c.Source.Name)
field("source", source)
if s.Version != "" {
field("version", s.Version)
}
@@ -308,14 +316,12 @@ func (d Detail) lines(st styles.Styles) []string {
case provider.TypeCertificate:
label, hidden, reveal = "cert", "", "show the certificate"
}
indent := strings.Repeat(" ", 12)
out = append(out, "")
switch d.state {
case ValueHidden:
if hidden != "" {
field(label, hidden)
out = append(out, indent+hints(reveal))
out = append(out, valueIndent+hints(reveal))
} else {
field(label, hints(reveal))
}
@@ -332,9 +338,9 @@ func (d Detail) lines(st styles.Styles) []string {
for j, w := range strings.Split(ansi.Wrap(text, max(1, inner-11-len(lead)), ""), "\n") {
w = lead + w
if i == 0 && j == 0 {
out = append(out, " "+st.Label.Render(fmt.Sprintf("%-10s", label))+" "+st.Error.Render(w))
out = append(out, fieldLabel(st, label)+st.Error.Render(w))
} else {
out = append(out, strings.Repeat(" ", 12)+st.Error.Render(w))
out = append(out, valueIndent+st.Error.Render(w))
}
}
}
@@ -345,10 +351,10 @@ func (d Detail) lines(st styles.Styles) []string {
for _, line := range lines {
for _, w := range strings.Split(ansi.Hardwrap(line, max(1, inner-11), true), "\n") {
if first {
out = append(out, " "+st.Label.Render(fmt.Sprintf("%-10s", label))+" "+st.Value.Render(w))
out = append(out, fieldLabel(st, label)+st.Value.Render(w))
first = false
} else {
out = append(out, indent+st.Value.Render(w))
out = append(out, valueIndent+st.Value.Render(w))
}
}
}
@@ -362,7 +368,7 @@ func (d Detail) lines(st styles.Styles) []string {
if d.hidesIn != "" {
more = append(more, "hides in "+d.hidesIn)
}
out = append(out, indent+hints("hide", more...))
out = append(out, valueIndent+hints("hide", more...))
}
return append(out, d.versionLines(st)...)
}
@@ -383,7 +389,7 @@ func (d Detail) versionLines(st styles.Styles) []string {
head += muted(" · " + d.VersionsKey + " to browse")
}
}
out := []string{"", " " + st.Label.Render(fmt.Sprintf("%-10s", "versions")) + " " + head}
out := []string{"", fieldLabel(st, "versions") + head}
current := d.versions[0].Version
for i, v := range d.versions {
if i == maxVersionLines {
+3 -26
View File
@@ -338,17 +338,7 @@ func (l List[T]) View(st styles.Styles, focused bool, spin string) string {
ts := TitleStyle(st, focused)
title := ts.Render(l.Title)
if len(l.Tabs) > 1 {
for i, tab := range l.Tabs {
sep := " - "
if i == 0 {
sep = " "
}
if i == l.Tab {
title += ts.Render(sep) + ts.Underline(true).Render(tab)
} else {
title += ts.Render(sep) + st.Muted.Render(tab)
}
}
title += ts.Render(" ") + Tabs(st, ts, l.Tabs, l.Tab)
}
if q := l.input.Value(); q != "" && !l.filtering {
title += ts.Render(" /" + q)
@@ -379,26 +369,13 @@ func (l List[T]) View(st styles.Styles, focused bool, spin string) string {
default:
for i := l.offset; i < len(l.visible) && i < l.offset+rows; i++ {
line := l.item.Render(st, l.items[l.visible[i]], inner-1)
plain := ansi.Strip(line)
hits := l.highlight(i, plain)
line = " " + MarkMatches(line, l.highlight(i, ansi.Strip(line)), st.Match)
if i == l.cursor {
base := st.SelectedBlurred
if focused {
base = st.Selected
}
text := " " + line
if len(hits) > 0 {
for j := range hits {
hits[j]++ // leading space
}
text = lipgloss.StyleRanges(text, matchRanges(" "+plain, hits, st.Match)...)
}
line = Highlight(ansi.Truncate(text, inner, "…"), base, inner)
} else if len(hits) > 0 {
// Style just the matches, keeping the row's own colours.
line = " " + lipgloss.StyleRanges(line, matchRanges(plain, hits, st.Match)...)
} else {
line = " " + line
line = Highlight(ansi.Truncate(line, inner, "…"), base, inner)
}
body = append(body, line)
}