Skip to content

Commit 92d1496

Browse files
committed
add path shortening algorithm in case the current file browser path doesn't fit in the title
1 parent 64cd5db commit 92d1496

2 files changed

Lines changed: 121 additions & 1 deletion

File tree

internal/ui/file_browser/file_browser.go

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,22 @@ func (fileBrowser *FileBrowserComponent) SetSelectedSnapshot(snapshot *data.Snap
485485

486486
func (fileBrowser *FileBrowserComponent) Refresh() {
487487
fileBrowser.showMessage(status_message.NewInfoStatusMessage("Refreshing..."))
488-
title := fmt.Sprintf("Path: %s", fileBrowser.path)
488+
489+
_, _, width, _ := fileBrowser.tableContainer.GetLayout().GetRect()
490+
if width == 0 {
491+
width = 80
492+
}
493+
// title is " Path: Path: <path> "
494+
// theme.CreateTitleText adds 2 spaces.
495+
// Box adds borders (2 chars).
496+
// "Path: " is 6 chars.
497+
// So available is width - 2 (borders) - 2 (spaces) - 6 (prefix) = width - 10.
498+
maxWidth := width - 10
499+
if maxWidth < 20 {
500+
maxWidth = 20
501+
}
502+
503+
title := fmt.Sprintf("Path: %s", fileBrowser.truncatePath(fileBrowser.path, maxWidth))
489504
fileBrowser.tableContainer.SetTitle(title)
490505

491506
entries, err := fileBrowser.computeTableEntries()
@@ -499,6 +514,48 @@ func (fileBrowser *FileBrowserComponent) Refresh() {
499514
fileBrowser.showMessage(status_message.NewInfoStatusMessage(""))
500515
}
501516

517+
func (fileBrowser *FileBrowserComponent) truncatePath(path string, maxWidth int) string {
518+
if len([]rune(path)) <= maxWidth {
519+
return path
520+
}
521+
522+
separator := string(os.PathSeparator)
523+
parts := strings.Split(path, separator)
524+
525+
if len(parts) <= 1 {
526+
runes := []rune(path)
527+
if len(runes) > maxWidth && maxWidth > 3 {
528+
return "..." + string(runes[len(runes)-maxWidth+3:])
529+
}
530+
return path
531+
}
532+
533+
// Try shortening parts from left to right, except the last one
534+
for i := 0; i < len(parts)-1; i++ {
535+
if parts[i] == "" || parts[i] == "." || parts[i] == ".." {
536+
continue
537+
}
538+
539+
runes := []rune(parts[i])
540+
if len(runes) > 1 {
541+
parts[i] = string(runes[0]) + "…"
542+
newPath := strings.Join(parts, separator)
543+
if len([]rune(newPath)) <= maxWidth {
544+
return newPath
545+
}
546+
}
547+
}
548+
549+
// If still too long, truncate the resulting path with ellipsis at the beginning
550+
finalPath := strings.Join(parts, separator)
551+
finalRunes := []rune(finalPath)
552+
if len(finalRunes) > maxWidth && maxWidth > 3 {
553+
return "..." + string(finalRunes[len(finalRunes)-maxWidth+3:])
554+
}
555+
556+
return finalPath
557+
}
558+
502559
func (fileBrowser *FileBrowserComponent) selectFileEntry(newSelection *data.FileBrowserEntry) {
503560
if fileBrowser.GetSelection() == newSelection || (fileBrowser.GetSelection() != nil && newSelection != nil && fileBrowser.GetSelection().GetRealPath() == newSelection.GetRealPath()) {
504561
return
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package file_browser
2+
3+
import (
4+
"os"
5+
"strings"
6+
"testing"
7+
)
8+
9+
func TestTruncatePath(t *testing.T) {
10+
fb := &FileBrowserComponent{}
11+
sep := string(os.PathSeparator)
12+
13+
tests := []struct {
14+
name string
15+
path string
16+
maxWidth int
17+
want string
18+
}{
19+
{
20+
name: "Short path, no truncation",
21+
path: "/home/user",
22+
maxWidth: 20,
23+
want: "/home/user",
24+
},
25+
{
26+
name: "Long path, shorten components",
27+
path: "/home/markus/projects/zfs-file-history",
28+
maxWidth: 25,
29+
want: "...m…/p…/zfs-file-history",
30+
},
31+
{
32+
name: "Very long path, shorten components and ellipsis",
33+
path: "/home/markus/projects/zfs-file-history",
34+
maxWidth: 15,
35+
want: "...file-history",
36+
},
37+
{
38+
name: "Long path, shortening fits exactly",
39+
path: "/home/markus/projects/zfs-file-history",
40+
maxWidth: 26,
41+
want: "/h…/m…/p…/zfs-file-history",
42+
},
43+
{
44+
name: "Path with empty components (leading slash)",
45+
path: "/a/b/c/d/e/f/g/h",
46+
maxWidth: 10,
47+
want: "...e/f/g/h",
48+
},
49+
}
50+
51+
for _, tt := range tests {
52+
t.Run(tt.name, func(t *testing.T) {
53+
// replace / with os.PathSeparator in test cases for cross-platform
54+
path := strings.ReplaceAll(tt.path, "/", sep)
55+
want := strings.ReplaceAll(tt.want, "/", sep)
56+
57+
got := fb.truncatePath(path, tt.maxWidth)
58+
if got != want {
59+
t.Errorf("truncatePath() = %v, want %v", got, want)
60+
}
61+
})
62+
}
63+
}

0 commit comments

Comments
 (0)