esync

Directory watching and remote syncing
Log | Files | Refs | README | LICENSE

commit e2e330cf6e9bbc943596dfb5947ee55ab9033e4a
parent 0da912f9c96725bff615d4fbb08ffdd1b44e6467
Author: Erik Loualiche <eloualiche@users.noreply.github.com>
Date:   Tue,  3 Mar 2026 15:32:45 -0600

Merge pull request #8 from LouLouLibs/feat/tui-single-file-full-path

feat: show full path for single-file directory groups in TUI
Diffstat:
Mcmd/sync.go | 10+++++++++-
Minternal/tui/dashboard.go | 27+++++++++++++++++++++++++--
2 files changed, 34 insertions(+), 3 deletions(-)

diff --git a/cmd/sync.go b/cmd/sync.go @@ -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 @@ -429,6 +432,7 @@ 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) } } @@ -436,7 +440,11 @@ func groupFilesByTopLevel(files []syncer.FileEntry) []groupedEvent { 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 diff --git a/internal/tui/dashboard.go b/internal/tui/dashboard.go @@ -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 @@ -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 {