feat: / filters the account pane
CI / Checks (pull_request) Successful in 25s
PR title / Conventional title (pull_request) Successful in 1s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kebandClaude Opus 5.5 committed 2026-10-11 10:28:59 +02:00
1 parent 3913989cd1
commit 0a80073aad
7 files changed
+61 -62

No files matched your search

+33 -56
View File
@@ -2,7 +2,6 @@ package panes
import (
"fmt"
"strings"
"github.com/charmbracelet/x/ansi"
@@ -10,12 +9,10 @@ import (
)
// SourceDetail is the fourth pane on a provider's row: what's known about
// its account, one field per row, to move through and copy from.
// its account, one field per row, to move through, filter and copy from.
// It's a List, so / filters it like the other panes.
type SourceDetail struct {
title string
fields []Field
sel int
w, h int
List[Field]
}
// Field is a row: a value under a label. Info rows (loading, errors)
@@ -25,64 +22,44 @@ type Field struct {
Info bool
}
// Set shows fields under title, keeping the cursor where it was for the
// same title.
func NewSourceDetail() SourceDetail {
l := NewList("", Item[Field]{
ID: func(f Field) string { return f.Label + "\x00" + f.Value },
Filter: func(f Field) string { return f.Value },
// "/tenant" finds the tenant's rows.
FilterExtra: func(f Field) string { return f.Label },
Render: func(st styles.Styles, f Field, width int) string {
value := f.Value
if f.Info {
value = st.Muted.Render(value)
}
return ansi.Truncate(st.Label.Render(fmt.Sprintf("%-10s", f.Label))+" "+value, width, "…")
},
})
l.Empty = "nothing known about this account"
return SourceDetail{l}
}
// Set shows fields under title. Another title (another account) starts
// over, without a filter.
func (s *SourceDetail) Set(title string, fields []Field) {
if title != s.title {
s.sel = 0
if title != s.Title {
s.Reset()
s.Title = title
}
if len(fields) > 0 || len(s.Items()) > 0 {
s.SetItems(fields)
}
s.title, s.fields = title, fields
s.sel = max(0, min(s.sel, len(fields)-1))
}
// Shown reports whether there's anything to show.
func (s SourceDetail) Shown() bool { return len(s.fields) > 0 }
func (s *SourceDetail) SetSize(w, h int) { s.w, s.h = w, h }
func (s *SourceDetail) Move(delta int) { s.sel = max(0, min(s.sel+delta, len(s.fields)-1)) }
func (s *SourceDetail) Top() { s.sel = 0 }
func (s *SourceDetail) Bottom() { s.sel = max(0, len(s.fields)-1) }
func (s SourceDetail) Shown() bool { return len(s.Items()) > 0 }
// Selected is the field under the cursor, unless it's an info row.
func (s SourceDetail) Selected() (Field, bool) {
if s.sel >= len(s.fields) || s.fields[s.sel].Info {
f, ok := s.List.Selected()
if !ok || f.Info {
return Field{}, false
}
return s.fields[s.sel], true
}
func (s SourceDetail) View(st styles.Styles, focused bool) string {
inner := max(1, s.w-2)
rows := max(0, s.h-2)
offset := 0
if s.sel >= rows { // keep the cursor in view
offset = s.sel - rows + 1
}
var body []string
for i, f := range s.fields[min(offset, len(s.fields)):] {
if i >= rows {
break
}
value := f.Value
if f.Info {
value = st.Muted.Render(value)
}
line := " " + st.Label.Render(fmt.Sprintf("%-10s", f.Label)) + " " + value
line = ansi.Truncate(line, inner, "…")
if i+offset == s.sel {
pad := strings.Repeat(" ", max(0, inner-ansi.StringWidth(line)))
if focused {
line = st.Selected.Render(ansi.Strip(line) + pad)
} else {
line = st.SelectedBlurred.Render(ansi.Strip(line) + pad)
}
}
body = append(body, line)
}
footer := ""
if len(s.fields) > rows {
footer = fmt.Sprintf("%d of %d", s.sel+1, len(s.fields))
}
return Box(st, TitleStyle(st, focused).Render(s.title), footer, body, s.w, s.h, focused)
return f, true
}