Skip to content

Commit 8078494

Browse files
authored
Merge pull request #15 from agentic-utils/feat/responsive-width
feat: fill the terminal width and redraw on resize
2 parents bc5e47b + d9d1920 commit 8078494

2 files changed

Lines changed: 68 additions & 12 deletions

File tree

main.go

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
146146
if m.listHeight < 3 {
147147
m.listHeight = 3
148148
}
149-
return m, nil
149+
// Clear so a shrink doesn't leave wider stale rows behind.
150+
return m, tea.ClearScreen
150151

151152
case tea.MouseMsg:
152153
// Determine if mouse is in preview area (below list + separator)
@@ -261,8 +262,8 @@ func (m model) View() string {
261262

262263
var b strings.Builder
263264

264-
// Table width: 2 + 16 + 2 + 22 + 2 + 34 + 2 + 5 + 2 + 4 + 2 + 6 = 99
265-
tableWidth := 99
265+
// The list spans the full terminal width; TOPIC flexes to fill it.
266+
tableWidth := m.width
266267

267268
// Title line with help right-aligned
268269
title := fmt.Sprintf("ccs · claude code search · %s", version)
@@ -310,7 +311,8 @@ func (m model) View() string {
310311
previewHeight := m.height - listHeight - 6 // 6 for title + search + blank + header + borders
311312

312313
// Column headers
313-
b.WriteString(fmt.Sprintf(" \033[90m%-16s %-22s %-34s %5s %4s %6s\033[0m\n", "DATE", "PROJECT", "TOPIC", "MSGS", "HITS", "SIZE"))
314+
b.WriteString(fmt.Sprintf(" \033[90m%-*s %-*s %-*s %*s %*s %*s\033[0m\n",
315+
colDate, "DATE", colProject, "PROJECT", m.topicColWidth(), "TOPIC", colMsgs, "MSGS", colHits, "HITS", colSize, "SIZE"))
314316
b.WriteString(strings.Repeat("─", m.width))
315317
b.WriteString("\n")
316318

@@ -352,16 +354,35 @@ func (m model) View() string {
352354
return b.String()
353355
}
354356

357+
// Fixed list column widths. TOPIC is the flex column - it absorbs the rest of
358+
// the terminal width (see topicColWidth).
359+
const (
360+
colDate = 16
361+
colProject = 22
362+
colMsgs = 5
363+
colHits = 4
364+
colSize = 6
365+
colGap = 2 // spaces between columns
366+
listIndent = 2 // leading " " / "> " on each row
367+
numGaps = 5
368+
)
369+
370+
// topicColWidth flexes the TOPIC column to fill the terminal width.
371+
func (m model) topicColWidth() int {
372+
used := listIndent + colDate + colProject + colMsgs + colHits + colSize + numGaps*colGap
373+
if w := m.width - used; w > 10 {
374+
return w
375+
}
376+
return 10
377+
}
378+
355379
func (m model) formatListItem(item listItem, selected bool) string {
356380
ts := formatTimestamp(item.conv.LastTimestamp)
357381
project := item.conv.Cwd
358382
if idx := strings.LastIndex(project, "/"); idx >= 0 {
359383
project = project[idx+1:]
360384
}
361-
// Truncate project name to fit column
362-
if len(project) > 22 {
363-
project = project[:19] + "..."
364-
}
385+
project = truncate(project, colProject)
365386

366387
// Mark only user-set custom titles. Claude auto-generates an ai-title for
367388
// almost every session, so marking any title would flag nearly every row;
@@ -372,7 +393,8 @@ func (m model) formatListItem(item listItem, selected bool) string {
372393
if item.conv.IsCustomTitle {
373394
topic = "✎ " + topic
374395
}
375-
topic = truncate(topic, 34)
396+
tw := m.topicColWidth()
397+
topic = truncate(topic, tw)
376398

377399
// Message count
378400
msgs := len(item.conv.Messages)
@@ -393,10 +415,11 @@ func (m model) formatListItem(item listItem, selected bool) string {
393415

394416
// Format: date | project | topic | msgs | hits | size (aligned columns)
395417
if selected {
396-
return fmt.Sprintf("%-16s %-22s %-34s %5d %4d %6s", ts, project, topic, msgs, hits, size)
418+
return fmt.Sprintf("%-*s %-*s %-*s %*d %*d %*s",
419+
colDate, ts, colProject, project, tw, topic, colMsgs, msgs, colHits, hits, colSize, size)
397420
}
398-
return fmt.Sprintf("\033[90m%-16s\033[0m \033[1;33m%-22s\033[0m %-34s %5d \033[36m%4d\033[0m \033[35m%6s\033[0m",
399-
ts, project, topic, msgs, hits, size)
421+
return fmt.Sprintf("\033[90m%-*s\033[0m \033[1;33m%-*s\033[0m %-*s %*d \033[36m%*d\033[0m \033[35m%*s\033[0m",
422+
colDate, ts, colProject, project, tw, topic, colMsgs, msgs, colHits, hits, colSize, size)
400423
}
401424

402425
// buildPreviewLines builds the scrollable message lines of a conversation

main_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,7 @@ func TestFormatListItemNamedSessionMarker(t *testing.T) {
785785
Messages: []Message{{Role: "user", Text: "just a first message"}},
786786
}}
787787
m := initialModel([]listItem{custom, aiTitled, fallback}, "", nil)
788+
m.width = 120 // give TOPIC room so the title isn't truncated
788789

789790
if got := m.formatListItem(custom, false); !strings.Contains(got, "✎ Refactor auth flow") {
790791
t.Errorf("user-set custom title should show the marker, got %q", got)
@@ -797,6 +798,38 @@ func TestFormatListItemNamedSessionMarker(t *testing.T) {
797798
}
798799
}
799800

801+
func TestTopicColWidthFlexes(t *testing.T) {
802+
fixed := listIndent + colDate + colProject + colMsgs + colHits + colSize + numGaps*colGap
803+
for _, w := range []int{80, 120, 200} {
804+
m := model{width: w}
805+
if got, want := m.topicColWidth(), w-fixed; got != want {
806+
t.Errorf("topicColWidth(width=%d) = %d, want %d", w, got, want)
807+
}
808+
}
809+
// Tiny terminal clamps to a minimum rather than going negative.
810+
if got := (model{width: 20}).topicColWidth(); got != 10 {
811+
t.Errorf("topicColWidth(width=20) = %d, want 10 (min)", got)
812+
}
813+
}
814+
815+
func TestFormatListItemFillsWidth(t *testing.T) {
816+
item := listItem{conv: Conversation{
817+
SessionID: "s",
818+
LastTimestamp: "2024-01-15T10:30:00Z",
819+
Size: 1 << 20,
820+
Messages: []Message{{Role: "user", Text: "hi"}},
821+
}}
822+
for _, w := range []int{80, 120, 200} {
823+
m := initialModel([]listItem{item}, "", nil)
824+
m.width = w
825+
// Selected row has no ANSI codes; its width + the 2-char row prefix
826+
// added by View should fill the terminal exactly.
827+
if got := len(m.formatListItem(item, true)); got != w-listIndent {
828+
t.Errorf("width=%d: row length = %d, want %d", w, got, w-listIndent)
829+
}
830+
}
831+
}
832+
800833
func TestUpdateKeyboardNavigation(t *testing.T) {
801834
items := []listItem{
802835
{conv: Conversation{SessionID: "test-1"}, searchText: "first"},

0 commit comments

Comments
 (0)