Trakt lists: right-align the numbers

The grey number of every entry ("1.2k watching", "on 18k lists") stood
right after its title, so they did not line up. The rows are now padded
to the list's width on every draw where it changed, putting the numbers
against the right edge; a title too long for that keeps the number right
after it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 23:20:59 +02:00
co-authored by Claude Opus 5.5
parent 68c87bc3cd
commit 9b8d59bd48
4 changed files with 85 additions and 8 deletions
+2 -1
View File
@@ -4,7 +4,8 @@
- **Trending, most watched and anticipated** movies and series from Trakt: with an empty search
box, Movies and Series show trending now, most watched this week, anticipated, popular and (movies)
the US box office; `[` `]` switch lists, `r` reloads. Titles in the library are ticked, the others
the US box office, each title with its number ("1.2k watching", "on 18k lists", "$48M last
weekend") right-aligned in grey; `[` `]` switch lists, `r` reloads. Titles in the library are ticked, the others
can be added and searched like search results. Needs a free Trakt client ID, set in the new Trakt
section of the Config view (`trakt.client_id`, `ARR_TUI_TRAKT_CLIENT_ID`).
Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

+42
View File
@@ -8,6 +8,8 @@ import (
"time"
"github.com/gdamore/tcell/v2"
"git.stinnesbeck.com/nils/arr-tui/internal/radarr"
)
func TestTraktListsInTheSearchView(t *testing.T) {
@@ -86,3 +88,43 @@ func TestTraktListsInTheSearchView(t *testing.T) {
t.Errorf("search row %q", main)
}
}
func TestListNumbersAreRightAligned(t *testing.T) {
sim := tcell.NewSimulationScreen("")
if err := sim.Init(); err != nil {
t.Fatal(err)
}
defer sim.Fini()
sim.SetSize(60, 6)
a := newTestApp(t)
v := a.searches[0]
a.trakt.Reconfigure("id")
v.discover[0] = &discovered{at: time.Now(),
results: []result{movieResult{Movie: radarr.Movie{Title: "Dune", Year: 2021}}, movieResult{Movie: radarr.Movie{Title: "A Much Longer Title", Year: 2024}}},
notes: []string{"1.2k watching", "5 watching"}}
v.showList()
ends := func(width int) []int {
sim.Clear()
l := notesList{v.list, v}
l.SetRect(0, 0, width, 6)
l.Draw(sim)
var out []int
for y := 1; y <= 2; y++ {
row := strings.TrimRight(rowText(sim, y, 0, width), " │")
out = append(out, len([]rune(row)))
if !strings.HasSuffix(row, "watching") {
t.Errorf("row %d does not end with its number: %q", y, row)
}
}
return out
}
if e := ends(60); e[0] != e[1] {
t.Errorf("numbers end in columns %v, want the same", e)
}
// After a resize they line up at the new edge.
if e := ends(44); e[0] != e[1] || e[0] > 44 {
t.Errorf("after resize: numbers end in columns %v", e)
}
}
+41 -7
View File
@@ -36,6 +36,7 @@ type searchView struct {
results []result
notes []string // per result, a Trakt list's number ("1.2k watching"); nil for searches
placeholder string // shown when there are no results
notesWidth int // list width the rows were laid out for
timer *time.Timer
cancel context.CancelFunc
gen int
@@ -71,7 +72,7 @@ func newSearchView(app *App, be backend) *searchView {
left := tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(v.input, 1, 0, true).
AddItem(v.list, 0, 1, false)
AddItem(notesList{v.list, v}, 0, 1, false)
v.root = tview.NewFlex().
AddItem(left, 0, 1, true).
AddItem(v.details, 0, 1, false)
@@ -279,12 +280,10 @@ func (v *searchView) activate() {
// render rebuilds the list, selecting index sel.
func (v *searchView) render(sel int) {
v.list.Clear()
for i, r := range v.results {
label := r.label()
if i < len(v.notes) && v.notes[i] != "" {
label += " [gray]" + tview.Escape(v.notes[i]) + "[-]"
}
v.list.AddItem(label, "", 0, nil)
_, _, width, _ := v.list.GetInnerRect()
v.notesWidth = width
for i := range v.results {
v.list.AddItem(v.itemText(i, width), "", 0, nil)
}
if len(v.results) == 0 {
if v.placeholder != "" {
@@ -298,6 +297,41 @@ func (v *searchView) render(sel int) {
v.showDetails(sel)
}
// itemText is the row of result i in a list width columns wide: the label,
// and a Trakt list's number (if any) in grey against the right edge, so the
// numbers of all rows line up. A label too long for that gets the number
// right after it.
func (v *searchView) itemText(i, width int) string {
label := v.results[i].label()
if i >= len(v.notes) || v.notes[i] == "" {
return label
}
note := tview.Escape(v.notes[i])
gap := width - tview.TaggedStringWidth(label) - tview.TaggedStringWidth(note)
if gap < 2 {
gap = 2
}
return label + strings.Repeat(" ", gap) + "[gray]" + note + "[-]"
}
// notesList draws the results list, first laying the rows out again when the
// width changed (the numbers of a Trakt list sit against the right edge).
type notesList struct {
*tview.List
v *searchView
}
func (n notesList) Draw(screen tcell.Screen) {
v := n.v
if _, _, width, _ := v.list.GetInnerRect(); width != v.notesWidth && len(v.notes) > 0 {
v.notesWidth = width
for i := range v.results {
v.list.SetItemText(i, v.itemText(i, width), "")
}
}
n.List.Draw(screen)
}
func (v *searchView) showDetails(i int) {
if i < 0 || i >= len(v.results) {
v.details.Clear()