Skip to content

Commit d9ad4db

Browse files
authored
feat: link icon and metadata (#1171)
1 parent fd2abee commit d9ad4db

File tree

8 files changed

+90
-37
lines changed

8 files changed

+90
-37
lines changed

src/config/icon/icon.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ var Icons = map[string]Style{
108108
"ko": {Icon: "\uebc6", Color: "#9b59b6"}, // Printable Rune : ""
109109
"kt": {Icon: "\ue634", Color: "#2980b9"}, // Printable Rune : ""
110110
"less": {Icon: "\ue758", Color: "#3498db"}, // Printable Rune : ""
111+
"link_file": {Icon: "\uf481", Color: "NONE"}, // Printable Rune : ""
111112
"lock": {Icon: "\uf023", Color: "#f1c40f"}, // Printable Rune : ""
112113
"log": {Icon: "\uf18d", Color: "#7f8c8d"}, // Printable Rune : ""
113114
"lua": {Icon: "\ue620", Color: "#e74c3c"}, // Printable Rune : ""
@@ -418,6 +419,7 @@ var Folders = map[string]Style{
418419
// Item for Generic folder, with key "folder" is initialized in InitIcon()
419420
"hidden": {Icon: "\uf023", Color: "#75715e"}, // Hidden folder - Dark yellowish // Printable Rune : ""
420421
"node_modules": {Icon: "\ue5fa", Color: "#cb3837"}, // Node modules folder - Red // Printable Rune : ""
422+
"link_folder": {Icon: "\uf482", Color: "NONE"}, // link folder - None // Printable Rune : ""
421423

422424
"superfile": {Icon: "\U000f069d", Color: "#FF6F00"}, // Printable Rune : "󰚝"
423425
}

src/internal/common/icon_utils.go

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,11 @@ import (
77
"github.com/yorukot/superfile/src/config/icon"
88
)
99

10-
func GetElementIcon(file string, isDir bool, nerdFont bool) icon.Style {
11-
ext := strings.TrimPrefix(filepath.Ext(file), ".")
12-
name := file
13-
14-
if !nerdFont {
15-
return icon.Style{
16-
Icon: "",
17-
Color: Theme.FilePanelFG,
18-
}
19-
}
20-
21-
if isDir {
22-
resultIcon := icon.Folders["folder"]
23-
betterIcon, hasBetterIcon := icon.Folders[name]
24-
if hasBetterIcon {
25-
resultIcon = betterIcon
26-
}
27-
return resultIcon
10+
func getFileIcon(file string, isLink bool) icon.Style {
11+
if isLink {
12+
return icon.Icons["link_file"]
2813
}
14+
ext := strings.TrimPrefix(filepath.Ext(file), ".")
2915
// default icon for all files. try to find a better one though...
3016
resultIcon := icon.Icons["file"]
3117
// resolve aliased extensions
@@ -42,7 +28,7 @@ func GetElementIcon(file string, isDir bool, nerdFont bool) icon.Style {
4228
}
4329

4430
// now look for icons based on full names
45-
fullName := name
31+
fullName := file
4632

4733
fullName = strings.ToLower(fullName)
4834
fullAlias, hasFullAlias := icon.Aliases[fullName]
@@ -61,3 +47,26 @@ func GetElementIcon(file string, isDir bool, nerdFont bool) icon.Style {
6147
}
6248
return resultIcon
6349
}
50+
51+
func GetElementIcon(file string, isDir bool, isLink bool, nerdFont bool) icon.Style {
52+
if !nerdFont {
53+
return icon.Style{
54+
Icon: "",
55+
Color: Theme.FilePanelFG,
56+
}
57+
}
58+
59+
if isDir {
60+
if isLink {
61+
return icon.Folders["link_folder"]
62+
}
63+
resultIcon := icon.Folders["folder"]
64+
betterIcon, hasBetterIcon := icon.Folders[file]
65+
if hasBetterIcon {
66+
resultIcon = betterIcon
67+
}
68+
return resultIcon
69+
}
70+
71+
return getFileIcon(file, isLink)
72+
}

src/internal/common/icon_utils_test.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ func TestGetElementIcon(t *testing.T) {
1111
name string
1212
file string
1313
isDir bool
14+
isLink bool
1415
nerdFont bool
1516
expected icon.Style
1617
}{
1718
{
1819
name: "Non-nerdfont returns empty icon",
1920
file: "test.txt",
2021
isDir: false,
22+
isLink: false,
2123
nerdFont: false,
2224
expected: icon.Style{
2325
Icon: "",
@@ -28,48 +30,55 @@ func TestGetElementIcon(t *testing.T) {
2830
name: "Directory with nerd font",
2931
file: "folder",
3032
isDir: true,
33+
isLink: false,
3134
nerdFont: true,
3235
expected: icon.Folders["folder"],
3336
},
3437
{
3538
name: "File with known extension",
3639
file: "test.js",
3740
isDir: false,
41+
isLink: false,
3842
nerdFont: true,
3943
expected: icon.Icons["js"],
4044
},
4145
{
4246
name: "Full name takes priority over extension",
4347
file: "gulpfile.js",
4448
isDir: false,
49+
isLink: false,
4550
nerdFont: true,
4651
expected: icon.Icons["gulpfile.js"],
4752
},
4853
{
4954
name: ".git directory",
5055
file: ".git",
5156
isDir: true,
57+
isLink: false,
5258
nerdFont: true,
5359
expected: icon.Folders[".git"],
5460
},
5561
{
5662
name: "superfile directory",
5763
file: "superfile",
5864
isDir: true,
65+
isLink: false,
5966
nerdFont: true,
6067
expected: icon.Folders["superfile"],
6168
},
6269
{
6370
name: "package.json file",
6471
file: "package.json",
6572
isDir: false,
73+
isLink: false,
6674
nerdFont: true,
6775
expected: icon.Icons["package"],
6876
},
6977
{
7078
name: "File with unknown extension",
7179
file: "test.xyz",
7280
isDir: false,
81+
isLink: false,
7382
nerdFont: true,
7483
expected: icon.Style{
7584
Icon: icon.Icons["file"].Icon,
@@ -81,14 +90,31 @@ func TestGetElementIcon(t *testing.T) {
8190
name: "File with aliased name",
8291
file: "dockerfile",
8392
isDir: false,
93+
isLink: false,
8494
nerdFont: true,
8595
expected: icon.Icons["dockerfile"],
8696
},
97+
{
98+
name: "Link to Directory with nerd font",
99+
file: "folder",
100+
isDir: true,
101+
isLink: true,
102+
nerdFont: true,
103+
expected: icon.Folders["link_folder"],
104+
},
105+
{
106+
name: "Link to File",
107+
file: "test.js",
108+
isDir: false,
109+
isLink: true,
110+
nerdFont: true,
111+
expected: icon.Icons["link_file"],
112+
},
87113
}
88114

89115
for _, tt := range tests {
90116
t.Run(tt.name, func(t *testing.T) {
91-
result := GetElementIcon(tt.file, tt.isDir, tt.nerdFont)
117+
result := GetElementIcon(tt.file, tt.isDir, tt.isLink, tt.nerdFont)
92118
if result.Icon != tt.expected.Icon || result.Color != tt.expected.Color {
93119
t.Errorf("GetElementIcon() = %v, want %v", result, tt.expected)
94120
}

src/internal/common/string_function.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ func TruncateMiddleText(text string, maxChars int, tails string) string {
5858
return truncatedText
5959
}
6060

61-
func PrettierName(name string, width int, isDir bool, isSelected bool, bgColor lipgloss.Color) string {
62-
style := GetElementIcon(name, isDir, Config.Nerdfont)
61+
func PrettierName(name string, width int, isDir bool, isLink bool, isSelected bool, bgColor lipgloss.Color) string {
62+
style := GetElementIcon(name, isDir, isLink, Config.Nerdfont)
6363
if isSelected {
6464
return StringColorRender(lipgloss.Color(style.Color), bgColor).
6565
Background(bgColor).
@@ -73,16 +73,16 @@ func PrettierName(name string, width int, isDir bool, isSelected bool, bgColor l
7373
FilePanelStyle.Render(TruncateText(name, width, "..."))
7474
}
7575

76-
func PrettierDirectoryPreviewName(name string, isDir bool, bgColor lipgloss.Color) string {
77-
style := GetElementIcon(name, isDir, Config.Nerdfont)
76+
func PrettierDirectoryPreviewName(name string, isDir bool, isLink bool, bgColor lipgloss.Color) string {
77+
style := GetElementIcon(name, isDir, isLink, Config.Nerdfont)
7878
return StringColorRender(lipgloss.Color(style.Color), bgColor).
7979
Background(bgColor).
8080
Render(style.Icon+" ") +
8181
FilePanelStyle.Render(name)
8282
}
8383

84-
func ClipboardPrettierName(name string, width int, isDir bool, isSelected bool) string {
85-
style := GetElementIcon(filepath.Base(name), isDir, Config.Nerdfont)
84+
func ClipboardPrettierName(name string, width int, isDir bool, isLink bool, isSelected bool) string {
85+
style := GetElementIcon(filepath.Base(name), isDir, isLink, Config.Nerdfont)
8686
if isSelected {
8787
return StringColorRender(lipgloss.Color(style.Color), FooterBGColor).
8888
Background(FooterBGColor).

src/internal/model_render.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,12 @@ func (panel *filePanel) renderFileEntries(r *rendering.Renderer, mainPanelHeight
136136
// Calculate the actual prefix width for proper alignment
137137
prefixWidth := lipgloss.Width(cursor+" ") + lipgloss.Width(selectBox)
138138

139+
isLink := panel.element[i].info.Mode()&os.ModeSymlink != 0
139140
renderedName := common.PrettierName(
140141
panel.element[i].name,
141142
filePanelWidth-prefixWidth,
142143
panel.element[i].directory,
144+
isLink,
143145
isSelected,
144146
common.FilePanelBGColor,
145147
)
@@ -222,15 +224,16 @@ func (m *model) clipboardRender() string {
222224
// Last Entry we can render, but there are more that one left
223225
r.AddLines(strconv.Itoa(len(m.copyItems.items)-i) + " item left....")
224226
} else {
225-
fileInfo, err := os.Stat(m.copyItems.items[i])
227+
fileInfo, err := os.Lstat(m.copyItems.items[i])
226228
if err != nil {
227229
slog.Error("Clipboard render function get item state ", "error", err)
228230
}
229231
if !os.IsNotExist(err) {
232+
isLink := fileInfo.Mode()&os.ModeSymlink != 0
230233
// TODO : There is an inconsistency in parameter that is being passed,
231234
// and its name in ClipboardPrettierName function
232235
r.AddLines(common.ClipboardPrettierName(m.copyItems.items[i],
233-
utils.FooterWidth(m.fullWidth)-3, fileInfo.IsDir(), false))
236+
utils.FooterWidth(m.fullWidth)-3, fileInfo.IsDir(), isLink, false))
234237
}
235238
}
236239
}

src/internal/ui/metadata/const.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ const keyValueSpacing = " "
55
const keyValueSpacingLen = 1
66

77
const fileStatErrorMsg = "Cannot load file stats"
8-
const linkFileMsg = "This is a link file."
98
const linkFileBrokenMsg = "Link file is broken!"
109
const etFetchErrorMsg = "Errors while fetching metadata via exiftool"
1110

@@ -17,6 +16,7 @@ const keyPermissions = "Permissions"
1716
const keyMd5Checksum = "MD5Checksum"
1817
const keyOwner = "Owner"
1918
const keyGroup = "Group"
19+
const keyPath = "Path"
2020

2121
var sortPriority = map[string]int{ //nolint: gochecknoglobals // This is effectively const.
2222
keyName: 0,
@@ -26,4 +26,5 @@ var sortPriority = map[string]int{ //nolint: gochecknoglobals // This is effecti
2626
keyPermissions: 4,
2727
keyOwner: 5,
2828
keyGroup: 6,
29+
keyPath: 7,
2930
}

src/internal/ui/metadata/metadata.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,20 @@ func GetMetadata(filePath string, metadataFocussed bool, et *exiftool.Exiftool)
8080
return meta
8181
}
8282

83+
func getSymLinkMetaData(filePath string) Metadata {
84+
res := Metadata{
85+
filepath: filePath,
86+
}
87+
linkPath, symlinkErr := filepath.EvalSymlinks(filePath)
88+
if symlinkErr != nil {
89+
res.infoMsg = linkFileBrokenMsg
90+
} else {
91+
path := [2]string{keyPath, linkPath}
92+
res.data = append(res.data, path)
93+
}
94+
return res
95+
}
96+
8397
func getMetaDataUnsorted(filePath string, metadataFocussed bool, et *exiftool.Exiftool) Metadata {
8498
res := Metadata{
8599
filepath: filePath,
@@ -91,13 +105,7 @@ func getMetaDataUnsorted(filePath string, metadataFocussed bool, et *exiftool.Ex
91105
return res
92106
}
93107
if fileInfo.Mode()&os.ModeSymlink != 0 {
94-
_, symlinkErr := filepath.EvalSymlinks(filePath)
95-
if symlinkErr != nil {
96-
res.infoMsg = linkFileBrokenMsg
97-
} else {
98-
res.infoMsg = linkFileMsg
99-
}
100-
return res
108+
return getSymLinkMetaData(filePath)
101109
}
102110
// Add basic metadata information irrespective of what is fetched from exiftool
103111
// Note : we prioritize these while sorting Metadata

src/internal/ui/preview/model.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,11 @@ func renderDirectoryPreview(r *rendering.Renderer, itemPath string, previewHeigh
151151

152152
for i := 0; i < previewHeight && i < len(files); i++ {
153153
file := files[i]
154-
style := common.GetElementIcon(file.Name(), file.IsDir(), common.Config.Nerdfont)
154+
isLink := false
155+
if info, err := file.Info(); err == nil {
156+
isLink = info.Mode()&os.ModeSymlink != 0
157+
}
158+
style := common.GetElementIcon(file.Name(), file.IsDir(), isLink, common.Config.Nerdfont)
155159
res := lipgloss.NewStyle().Foreground(lipgloss.Color(style.Color)).Background(common.FilePanelBGColor).
156160
Render(style.Icon+" ") + common.FilePanelStyle.Render(file.Name())
157161
r.AddLines(res)

0 commit comments

Comments
 (0)