Skip to content

Commit 5807661

Browse files
authored
Merge pull request #28 from adinhodovic/better-project-truncate
fix: Improve project truncation
2 parents 94d1a98 + ec7e29b commit 5807661

5 files changed

Lines changed: 92 additions & 8 deletions

File tree

internal/ui/details.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
uiutil "github.com/adinhodovic/ai-dash/internal/ui/util"
1313
)
1414

15+
const relatedProjectW = 12
16+
1517
func detailPaneSectionHeights(termHeight int) (summary, detail, related int) {
1618
bodyH := uilayout.PaneBodyHeight(uilayout.BottomPaneHeight(termHeight))
1719
// Layout: summaryLabel(1) + summaryText(1) + divider(1) + detailTable + divider(1) + relatedLabel(1) + relatedTable
@@ -55,7 +57,7 @@ func (m *Model) resizeRelatedTable(filtered []session.Session) {
5557
_, _, relatedH := detailPaneSectionHeights(m.height)
5658
m.relatedTable.SetColumns([]table.Column{
5759
{Title: "Tool", Width: 7},
58-
{Title: "Project", Width: 12},
60+
{Title: "Project", Width: relatedProjectW},
5961
{Title: "Relation", Width: 8},
6062
{Title: "Summary", Width: max(8, width-35)},
6163
})
@@ -84,7 +86,7 @@ func (m *Model) syncRelatedTable(filtered []session.Session) {
8486
rows,
8587
table.Row{
8688
candidate.Tool,
87-
uiutil.CleanProjectName(candidate.Project),
89+
uiutil.TruncateProject(candidate.Project, relatedProjectW),
8890
relation,
8991
candidate.Summary,
9092
},

internal/ui/overview.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func (m *Model) resizeOverviewTable(filtered []session.Session) {
4646
}
4747
rows = append(rows, table.Row{
4848
uiutil.TimeAgo(ps.last),
49-
uiutil.Truncate(uiutil.CleanProjectName(k), nameW),
49+
uiutil.TruncateProject(k, nameW),
5050
countStr,
5151
})
5252
m.projectPaths = append(m.projectPaths, k)

internal/ui/sessions.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ func (m *Model) resizeTable(filtered []session.Session) {
1616
}
1717
// Subtract pane border (2) for inner width; header join (1) for height.
1818
tableW := max(40, width-2)
19-
summaryW := max(16, tableW-63)
19+
projectW, summaryW := sessionColumnWidths(tableW)
2020
height := max(2, uilayout.PaneBodyHeight(uilayout.BottomPaneHeight(m.height))-1)
2121
m.sessionTable.SetColumns([]table.Column{
2222
{Title: m.sortHeader("Last Active", session.SortUpdated), Width: 14},
2323
{Title: m.sortHeader("Tool", session.SortTool), Width: 8},
2424
{Title: m.sortHeader("Status", session.SortStatus), Width: 11},
25-
{Title: m.sortHeader("Project", session.SortProject), Width: 20},
25+
{Title: m.sortHeader("Project", session.SortProject), Width: projectW},
2626
{Title: m.sortHeader("Summary", session.SortSummary), Width: summaryW},
2727
})
2828
m.sessionTable.SetWidth(tableW)
@@ -32,20 +32,31 @@ func (m *Model) resizeTable(filtered []session.Session) {
3232

3333
func (m *Model) syncTable(filtered []session.Session) {
3434
rows := make([]table.Row, 0, len(filtered))
35-
summaryWidth := max(16, m.sessionTable.Width()-63)
35+
projectW, summaryW := sessionColumnWidths(m.sessionTable.Width())
3636
for _, s := range filtered {
3737
status := uiutil.SessionStatusLabel(s)
3838
rows = append(rows, table.Row{
3939
uiutil.TimeAgo(uiutil.LastActive(s)),
4040
uiutil.Capitalize(s.Tool),
4141
theme.StatusStyle(status).Render(uiutil.TruncateForCell(status, 11)),
42-
uiutil.TruncateForCell(uiutil.CleanProjectName(s.Project), 20),
43-
uiutil.TruncateForCell(uiutil.CleanSummary(s.Summary), summaryWidth),
42+
uiutil.TruncateProject(s.Project, projectW),
43+
uiutil.TruncateForCell(uiutil.CleanSummary(s.Summary), summaryW),
4444
})
4545
}
4646
m.sessionTable.SetRows(rows)
4747
}
4848

49+
func sessionColumnWidths(tableW int) (int, int) {
50+
const fixed = 14 + 8 + 11
51+
const cellPad = 5 * 2
52+
const minSummaryW = 16
53+
available := max(36, tableW-fixed-cellPad)
54+
projectW := min(36, max(24, available/3))
55+
projectW = min(projectW, available-minSummaryW)
56+
summaryW := max(minSummaryW, available-projectW)
57+
return projectW, summaryW
58+
}
59+
4960
func (m *Model) resizeSourceTable() {
5061
width := max(40, m.width*70/100-6)
5162
m.sourceTable.SetColumns([]table.Column{

internal/ui/util/util.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,57 @@ func TruncateForCell(value string, width int) string {
4343
return Truncate(value, width)
4444
}
4545

46+
func TruncateProject(value string, width int) string {
47+
value = CleanProjectName(value)
48+
if width <= 0 || lo.RuneLength(value) <= width {
49+
return value
50+
}
51+
if !strings.Contains(value, "/") {
52+
return Truncate(value, width)
53+
}
54+
return truncatePathTail(value, width)
55+
}
56+
57+
func truncatePathTail(value string, width int) string {
58+
const marker = ".../"
59+
60+
prefix := pathPrefix(value)
61+
budget := width - lo.RuneLength(prefix) - lo.RuneLength(marker)
62+
if budget < 4 {
63+
return truncateMiddle(value, width)
64+
}
65+
66+
return prefix + marker + lo.Substring(value, -budget, uint(budget))
67+
}
68+
69+
func pathPrefix(value string) string {
70+
if strings.HasPrefix(value, "~/") {
71+
return "~/"
72+
}
73+
if strings.HasPrefix(value, "/") {
74+
return "/"
75+
}
76+
idx := strings.Index(value, "/")
77+
if idx >= 0 {
78+
return value[:idx+1]
79+
}
80+
return ""
81+
}
82+
83+
func truncateMiddle(value string, width int) string {
84+
runes := []rune(value)
85+
if width <= 0 || len(runes) <= width {
86+
return value
87+
}
88+
if width <= 1 {
89+
return string(runes[:width])
90+
}
91+
keep := width - 1
92+
left := keep / 2
93+
right := keep - left
94+
return string(runes[:left]) + "~" + string(runes[len(runes)-right:])
95+
}
96+
4697
var homeDir, _ = os.UserHomeDir()
4798

4899
func ShortenPath(value string) string {

internal/ui/util/util_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,26 @@ func TestTruncateForCell(t *testing.T) {
3737
}
3838
}
3939

40+
func TestTruncateProjectPreservesPathTail(t *testing.T) {
41+
tests := []struct {
42+
value string
43+
width int
44+
want string
45+
}{
46+
{"~/oss/very-long-project-name", 18, "~/.../project-name"},
47+
{"/var/tmp/very-long-project-name", 16, "/.../roject-name"},
48+
{"team/very-long-project-name", 18, "team/.../ject-name"},
49+
{"very-long-project-name", 10, "very-long~"},
50+
{"short", 10, "short"},
51+
}
52+
for _, tt := range tests {
53+
got := TruncateProject(tt.value, tt.width)
54+
if got != tt.want {
55+
t.Errorf("TruncateProject(%q, %d) = %q, want %q", tt.value, tt.width, got, tt.want)
56+
}
57+
}
58+
}
59+
4060
func TestCleanProjectName(t *testing.T) {
4161
home := homeDir
4262
tests := []struct {

0 commit comments

Comments
 (0)