Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion cmd/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,8 +408,11 @@ type groupedEvent struct {

// groupFilesByTopLevel collapses file entries into top-level directories
// and root files. "cmd/sync.go" + "cmd/init.go" become one entry "cmd/" with count=2.
// When a directory contains only one file, the full relative path is kept.
func groupFilesByTopLevel(files []syncer.FileEntry) []groupedEvent {
dirMap := make(map[string]*groupedEvent)
// Track the original filename for single-file groups.
dirFirstFile := make(map[string]string)
var rootFiles []groupedEvent
var dirOrder []string

Expand All @@ -429,14 +432,19 @@ func groupFilesByTopLevel(files []syncer.FileEntry) []groupedEvent {
g.bytes += f.Bytes
} else {
dirMap[dir] = &groupedEvent{name: dir, count: 1, bytes: f.Bytes}
dirFirstFile[dir] = f.Name
dirOrder = append(dirOrder, dir)
}
}
}

var out []groupedEvent
for _, dir := range dirOrder {
out = append(out, *dirMap[dir])
g := *dirMap[dir]
if g.count == 1 {
g.name = dirFirstFile[dir]
}
out = append(out, g)
}
out = append(out, rootFiles...)
return out
Expand Down
27 changes: 25 additions & 2 deletions internal/tui/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,14 +256,14 @@ func (m DashboardModel) renderEvent(evt SyncEvent) string {
ts := dimStyle.Render(evt.Time.Format("15:04:05"))
switch evt.Status {
case "synced":
name := padRight(evt.File, 30)
name := padRight(abbreviatePath(evt.File, 30), 30)
detail := ""
if evt.Size != "" {
detail = dimStyle.Render(fmt.Sprintf("%18s %s", evt.Size, evt.Duration.Truncate(100*time.Millisecond)))
}
return ts + " " + statusSynced.Render("✓") + " " + name + detail
case "error":
name := padRight(evt.File, 30)
name := padRight(abbreviatePath(evt.File, 30), 30)
return ts + " " + statusError.Render("✗") + " " + name + statusError.Render("error")
default:
return ts + " " + evt.File
Expand All @@ -285,6 +285,29 @@ func (m DashboardModel) filteredEvents() []SyncEvent {
return out
}

// abbreviatePath shortens a file path to fit within maxLen by replacing
// leading directory segments with their first letter.
// "internal/syncer/syncer.go" → "i/s/syncer.go"
func abbreviatePath(p string, maxLen int) string {
if len(p) <= maxLen {
return p
}
parts := strings.Split(p, "/")
if len(parts) <= 1 {
return p
}
// Shorten directory segments from the left, keep the filename intact.
for i := 0; i < len(parts)-1; i++ {
if len(parts[i]) > 1 {
parts[i] = parts[i][:1]
}
if len(strings.Join(parts, "/")) <= maxLen {
break
}
}
return strings.Join(parts, "/")
}

// padRight pads s with spaces to width n, truncating if necessary.
func padRight(s string, n int) string {
if len(s) >= n {
Expand Down