From 5b108037a309929c8f6da21bdd9750245a02bc76 Mon Sep 17 00:00:00 2001 From: lorentzforces <25649139+lorentzforces@users.noreply.github.com> Date: Sun, 7 Apr 2024 08:35:46 -0500 Subject: [PATCH] Use symlink target for stat styling & icons (#566) (#1644) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * use symlink target for stat styling & icons (#566) Support LS_COLORS/dircolors use of "target" for symlinks to select icons and styles for them according to the target stat. This mainly applies to directories - file name-based styles will still be based on the name of the link, not its target. This mirrors `ls` behavior. * remove unnecessary debugging and factor out parsePair in color parsing * move conditional to switch block * clarify ln target behavior in docs * run gofmt on single file --------- Co-authored-by: Gökçehan Kara --- colors.go | 62 ++++++++++++++++++++++++++++++------------------------- doc.md | 2 ++ doc.txt | 12 ++++++++--- icons.go | 62 ++++++++++++++++++++++++++++++------------------------- lf.1 | 8 +++++++ 5 files changed, 87 insertions(+), 59 deletions(-) diff --git a/colors.go b/colors.go index 95204801..8558b3fa 100644 --- a/colors.go +++ b/colors.go @@ -10,10 +10,16 @@ import ( "github.com/gdamore/tcell/v2" ) -type styleMap map[string]tcell.Style +type styleMap struct { + styles map[string]tcell.Style + useLinkTarget bool +} func parseStyles() styleMap { - sm := make(styleMap) + sm := styleMap{ + styles: make(map[string]tcell.Style), + useLinkTarget: false, + } // Default values from dircolors // @@ -191,20 +197,12 @@ func (sm styleMap) parseFile(path string) { } for _, pair := range pairs { - key, val := pair[0], pair[1] - - key = replaceTilde(key) - - if filepath.IsAbs(key) { - key = filepath.Clean(key) - } - - sm[key] = applyAnsiCodes(val, tcell.StyleDefault) + sm.parsePair(pair) } } // This function parses $LS_COLORS environment variable. -func (sm styleMap) parseGNU(env string) { +func (sm *styleMap) parseGNU(env string) { for _, entry := range strings.Split(env, ":") { if entry == "" { continue @@ -217,16 +215,24 @@ func (sm styleMap) parseGNU(env string) { return } - key, val := pair[0], pair[1] + sm.parsePair(pair) + } +} - key = replaceTilde(key) +func (sm *styleMap) parsePair(pair []string) { + key, val := pair[0], pair[1] - if filepath.IsAbs(key) { - key = filepath.Clean(key) - } + key = replaceTilde(key) - sm[key] = applyAnsiCodes(val, tcell.StyleDefault) + if filepath.IsAbs(key) { + key = filepath.Clean(key) } + + if key == "ln" && val == "target" { + sm.useLinkTarget = true + } + + sm.styles[key] = applyAnsiCodes(val, tcell.StyleDefault) } // This function parses $LSCOLORS environment variable. @@ -267,17 +273,17 @@ func (sm styleMap) parseBSD(env string) { } for i, key := range colorNames { - sm[key] = getStyle(env[i*2], env[i*2+1]) + sm.styles[key] = getStyle(env[i*2], env[i*2+1]) } } func (sm styleMap) get(f *file) tcell.Style { - if val, ok := sm[f.path]; ok { + if val, ok := sm.styles[f.path]; ok { return val } if f.IsDir() { - if val, ok := sm[f.Name()+"/"]; ok { + if val, ok := sm.styles[f.Name()+"/"]; ok { return val } } @@ -285,7 +291,7 @@ func (sm styleMap) get(f *file) tcell.Style { var key string switch { - case f.linkState == working: + case f.linkState == working && !sm.useLinkTarget: key = "ln" case f.linkState == broken: key = "or" @@ -313,27 +319,27 @@ func (sm styleMap) get(f *file) tcell.Style { key = "ex" } - if val, ok := sm[key]; ok { + if val, ok := sm.styles[key]; ok { return val } - if val, ok := sm[f.Name()+"*"]; ok { + if val, ok := sm.styles[f.Name()+"*"]; ok { return val } - if val, ok := sm["*"+f.Name()]; ok { + if val, ok := sm.styles["*"+f.Name()]; ok { return val } - if val, ok := sm[filepath.Base(f.Name())+".*"]; ok { + if val, ok := sm.styles[filepath.Base(f.Name())+".*"]; ok { return val } - if val, ok := sm["*"+strings.ToLower(f.ext)]; ok { + if val, ok := sm.styles["*"+strings.ToLower(f.ext)]; ok { return val } - if val, ok := sm["fi"]; ok { + if val, ok := sm.styles["fi"]; ok { return val } diff --git a/doc.md b/doc.md index 92dee461..faf38c9f 100644 --- a/doc.md +++ b/doc.md @@ -1790,6 +1790,7 @@ You may instead divide it into multiple lines in between double quotes by escapi ex=01;32:\ " +The `ln` entry supports the special value `target`, which will use the link target to select a style. File name rules will still apply based on the link's name -- this mirrors GNU's `ls` and `dircolors` behavior. Having such a long variable definition in a shell configuration file might be undesirable. You may instead use the colors file (refer to the [CONFIGURATION section](https://github.com/gokcehan/lf/blob/master/doc.md#configuration)) for configuration. A sample colors file can be found at @@ -1802,6 +1803,7 @@ https://en.wikipedia.org/wiki/ANSI_escape_code Icons are configured using `LF_ICONS` environment variable or an icons file (refer to the [CONFIGURATION section](https://github.com/gokcehan/lf/blob/master/doc.md#configuration)). The variable uses the same syntax as `LS_COLORS/LF_COLORS`. Instead of colors, you should put a single characters as values of entries. +The `ln` entry supports the special value `target`, which will use the link target to select a icon. File name rules will still apply based on the link's name -- this mirrors GNU's `ls` and `dircolors` behavior. The icons file (refer to the [CONFIGURATION section](https://github.com/gokcehan/lf/blob/master/doc.md#configuration)) should consist of whitespace-separated pairs with a `#` character to start comments until the end of the line. Do not forget to add `set icons true` to your `lfrc` to see the icons. Default values are as follows given with their matching order in lf: diff --git a/doc.txt b/doc.txt index f932ec11..5910ce21 100644 --- a/doc.txt +++ b/doc.txt @@ -2026,8 +2026,11 @@ escaping newlines with backslashes as follows: ex=01;32:\ " -Having such a long variable definition in a shell configuration file -might be undesirable. You may instead use the colors file (refer to the +The ln entry supports the special value target, which will use the link +target to select a style. File name rules will still apply based on the +link's name -- this mirrors GNU's ls and dircolors behavior. Having such +a long variable definition in a shell configuration file might be +undesirable. You may instead use the colors file (refer to the CONFIGURATION section) for configuration. A sample colors file can be found at https://github.com/gokcehan/lf/blob/master/etc/colors.example You may also see the wiki page for ANSI escape codes @@ -2038,7 +2041,10 @@ ICONS Icons are configured using LF_ICONS environment variable or an icons file (refer to the CONFIGURATION section). The variable uses the same syntax as LS_COLORS/LF_COLORS. Instead of colors, you should put a -single characters as values of entries. The icons file (refer to the +single characters as values of entries. The ln entry supports the +special value target, which will use the link target to select a icon. +File name rules will still apply based on the link's name -- this +mirrors GNU's ls and dircolors behavior. The icons file (refer to the CONFIGURATION section) should consist of whitespace-separated pairs with a # character to start comments until the end of the line. Do not forget to add set icons true to your lfrc to see the icons. Default values are diff --git a/icons.go b/icons.go index ac4230fa..5583864a 100644 --- a/icons.go +++ b/icons.go @@ -7,10 +7,16 @@ import ( "strings" ) -type iconMap map[string]string +type iconMap struct { + icons map[string]string + useLinkTarget bool +} func parseIcons() iconMap { - im := make(iconMap) + im := iconMap{ + icons: make(map[string]string), + useLinkTarget: false, + } defaultIcons := []string{ "ln=l", @@ -44,7 +50,7 @@ func parseIcons() iconMap { return im } -func (im iconMap) parseFile(path string) { +func (im *iconMap) parseFile(path string) { log.Printf("reading file: %s", path) f, err := os.Open(path) @@ -61,19 +67,11 @@ func (im iconMap) parseFile(path string) { } for _, pair := range pairs { - key, val := pair[0], pair[1] - - key = replaceTilde(key) - - if filepath.IsAbs(key) { - key = filepath.Clean(key) - } - - im[key] = val + im.parsePair(pair) } } -func (im iconMap) parseEnv(env string) { +func (im *iconMap) parseEnv(env string) { for _, entry := range strings.Split(env, ":") { if entry == "" { continue @@ -86,25 +84,33 @@ func (im iconMap) parseEnv(env string) { return } - key, val := pair[0], pair[1] + im.parsePair(pair) + } +} - key = replaceTilde(key) +func (im *iconMap) parsePair(pair []string) { + key, val := pair[0], pair[1] - if filepath.IsAbs(key) { - key = filepath.Clean(key) - } + key = replaceTilde(key) - im[key] = val + if filepath.IsAbs(key) { + key = filepath.Clean(key) } + + if key == "ln" && val == "target" { + im.useLinkTarget = true + } + + im.icons[key] = val } func (im iconMap) get(f *file) string { - if val, ok := im[f.path]; ok { + if val, ok := im.icons[f.path]; ok { return val } if f.IsDir() { - if val, ok := im[f.Name()+"/"]; ok { + if val, ok := im.icons[f.Name()+"/"]; ok { return val } } @@ -112,7 +118,7 @@ func (im iconMap) get(f *file) string { var key string switch { - case f.linkState == working: + case f.linkState == working && !im.useLinkTarget: key = "ln" case f.linkState == broken: key = "or" @@ -140,27 +146,27 @@ func (im iconMap) get(f *file) string { key = "ex" } - if val, ok := im[key]; ok { + if val, ok := im.icons[key]; ok { return val } - if val, ok := im[f.Name()+"*"]; ok { + if val, ok := im.icons[f.Name()+"*"]; ok { return val } - if val, ok := im["*"+f.Name()]; ok { + if val, ok := im.icons["*"+f.Name()]; ok { return val } - if val, ok := im[filepath.Base(f.Name())+".*"]; ok { + if val, ok := im.icons[filepath.Base(f.Name())+".*"]; ok { return val } - if val, ok := im["*"+strings.ToLower(f.ext)]; ok { + if val, ok := im.icons["*"+strings.ToLower(f.ext)]; ok { return val } - if val, ok := im["fi"]; ok { + if val, ok := im.icons["fi"]; ok { return val } diff --git a/lf.1 b/lf.1 index 1d0c45e7..2bb32381 100644 --- a/lf.1 +++ b/lf.1 @@ -2363,6 +2363,10 @@ ex=01;32:\[rs] \f[R] .fi .PP +The \f[C]ln\f[R] entry supports the special value \f[C]target\f[R], +which will use the link target to select a style. +File name rules will still apply based on the link\[aq]s name -- this +mirrors GNU\[aq]s \f[C]ls\f[R] and \f[C]dircolors\f[R] behavior. Having such a long variable definition in a shell configuration file might be undesirable. You may instead use the colors file (refer to the CONFIGURATION @@ -2380,6 +2384,10 @@ section (https://github.com/gokcehan/lf/blob/master/doc.md#configuration)). The variable uses the same syntax as \f[C]LS_COLORS/LF_COLORS\f[R]. Instead of colors, you should put a single characters as values of entries. +The \f[C]ln\f[R] entry supports the special value \f[C]target\f[R], +which will use the link target to select a icon. +File name rules will still apply based on the link\[aq]s name -- this +mirrors GNU\[aq]s \f[C]ls\f[R] and \f[C]dircolors\f[R] behavior. The icons file (refer to the CONFIGURATION section (https://github.com/gokcehan/lf/blob/master/doc.md#configuration)) should consist of whitespace-separated pairs with a \f[C]#\f[R]