From 5853b80d0f868257b0688290407dceb1e18f44f2 Mon Sep 17 00:00:00 2001 From: Alexander Slavschik Date: Tue, 7 Apr 2026 13:35:44 +0200 Subject: [PATCH 1/5] fix (colors): add option to colorize dotfiles and dotfolders --- colors.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/colors.go b/colors.go index cb4cf3bc9..41b2b51c2 100644 --- a/colors.go +++ b/colors.go @@ -264,6 +264,14 @@ func (sm styleMap) get(f *file) tcell.Style { } if val, ok := sm.styles[key]; ok { + if key == "di" && strings.HasPrefix(f.Name(), ".") { + if dotval, ok2 := sm.styles[".*/"]; ok2 { + return dotval + } + if dotval, ok2 := sm.styles[".*"]; ok2 { + return dotval + } + } return val } @@ -279,6 +287,12 @@ func (sm styleMap) get(f *file) tcell.Style { return val } + if strings.HasPrefix(f.Name(), ".") { + if val, ok := sm.styles[".*"]; ok { + return val + } + } + if val, ok := sm.styles["*"+strings.ToLower(f.ext)]; ok { return val } From 2f17cde0854ef9f67ce7cce44fb9cc9b47e29c66 Mon Sep 17 00:00:00 2001 From: Alexander Slavschik Date: Tue, 7 Apr 2026 16:30:35 +0200 Subject: [PATCH 2/5] colors: use hd/hf type codes for hidden dirs and files --- colors.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/colors.go b/colors.go index 41b2b51c2..7db487a3e 100644 --- a/colors.go +++ b/colors.go @@ -265,10 +265,7 @@ func (sm styleMap) get(f *file) tcell.Style { if val, ok := sm.styles[key]; ok { if key == "di" && strings.HasPrefix(f.Name(), ".") { - if dotval, ok2 := sm.styles[".*/"]; ok2 { - return dotval - } - if dotval, ok2 := sm.styles[".*"]; ok2 { + if dotval, ok2 := sm.styles["hd"]; ok2 { return dotval } } @@ -288,7 +285,7 @@ func (sm styleMap) get(f *file) tcell.Style { } if strings.HasPrefix(f.Name(), ".") { - if val, ok := sm.styles[".*"]; ok { + if val, ok := sm.styles["hf"]; ok { return val } } From 8c24cb12f1d4359dd63f9a8fcc6aec2e597d66f5 Mon Sep 17 00:00:00 2001 From: Alexander Slavschik Date: Wed, 8 Apr 2026 16:53:44 +0200 Subject: [PATCH 3/5] refactor(colors): replace direct hidden file check with isHidden function --- colors.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colors.go b/colors.go index 7db487a3e..fa78c913e 100644 --- a/colors.go +++ b/colors.go @@ -264,7 +264,7 @@ func (sm styleMap) get(f *file) tcell.Style { } if val, ok := sm.styles[key]; ok { - if key == "di" && strings.HasPrefix(f.Name(), ".") { + if key == "di" && isHidden(f.FileInfo, filepath.Dir(f.path), gOpts.hiddenfiles) { if dotval, ok2 := sm.styles["hd"]; ok2 { return dotval } @@ -284,7 +284,7 @@ func (sm styleMap) get(f *file) tcell.Style { return val } - if strings.HasPrefix(f.Name(), ".") { + if isHidden(f.FileInfo, filepath.Dir(f.path), gOpts.hiddenfiles) { if val, ok := sm.styles["hf"]; ok { return val } From c5c86dc69b6f8f4e39275d881a5d6f73ce0eb4ec Mon Sep 17 00:00:00 2001 From: Alexander Slavschik Date: Wed, 15 Apr 2026 09:41:55 +0200 Subject: [PATCH 4/5] feat: add hiddendirfmt and hiddenfilefmt options for custom hidden item colors --- CHANGELOG.md | 1 + colors.go | 26 ++++++++++++++++++++++---- doc.md | 8 ++++++++ doc.txt | 9 +++++++++ eval.go | 4 ++++ lf.1 | 11 +++++++++++ opts.go | 4 ++++ 7 files changed, 59 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98c7e911a..100a4943c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), - A new option `borderstyle` is added to control whether `drawbox` draws an outline, separators, or both (#2445). This also replaces the existing `roundbox` option. - The `loading...` message delay of 100 milliseconds for file previews is now applied to directories as well (#2410). - `lf` will now automatically change to the parent directory if the current directory no longer exists and the `watch` option is enabled (#2424). +- New options `hiddendirfmt` and `hiddenfilefmt` are added to colour hidden directories and files separately from `di` and `fi` (#2492). ### Fixed diff --git a/colors.go b/colors.go index fa78c913e..4538b8dba 100644 --- a/colors.go +++ b/colors.go @@ -221,6 +221,24 @@ func (sm styleMap) parseBSD(env string) { } } +// parseFmtStyle converts an escape-sequence option value like +// "\033[3;38;2;146;131;116m" to a tcell.Style. Returns false for empty or +// malformed input so callers can fall back to the default style. +func parseFmtStyle(fmtStr string) (tcell.Style, bool) { + if fmtStr == "" { + return tcell.StyleDefault, false + } + s, ok := strings.CutPrefix(fmtStr, "\033[") + if !ok { + return tcell.StyleDefault, false + } + s, ok = strings.CutSuffix(s, "m") + if !ok { + return tcell.StyleDefault, false + } + return applySGR(s, tcell.StyleDefault), true +} + func (sm styleMap) get(f *file) tcell.Style { if val, ok := sm.styles[f.path]; ok { return val @@ -265,8 +283,8 @@ func (sm styleMap) get(f *file) tcell.Style { if val, ok := sm.styles[key]; ok { if key == "di" && isHidden(f.FileInfo, filepath.Dir(f.path), gOpts.hiddenfiles) { - if dotval, ok2 := sm.styles["hd"]; ok2 { - return dotval + if st, ok := parseFmtStyle(gOpts.hiddendirfmt); ok { + return st } } return val @@ -285,8 +303,8 @@ func (sm styleMap) get(f *file) tcell.Style { } if isHidden(f.FileInfo, filepath.Dir(f.path), gOpts.hiddenfiles) { - if val, ok := sm.styles["hf"]; ok { - return val + if st, ok := parseFmtStyle(gOpts.hiddenfilefmt); ok { + return st } } diff --git a/doc.md b/doc.md index 72f30b052..9c6f95077 100644 --- a/doc.md +++ b/doc.md @@ -955,6 +955,14 @@ Show hidden files. On Unix systems, hidden files are determined by the value of `hiddenfiles`. On Windows, files with hidden attributes are also considered hidden files. +## hiddendirfmt (string) (default ``), hiddenfilefmt (string) (default ``) + +Format strings for colouring hidden directories and files. +When set to a non-empty value, they override the normal `di` and `fi` colours for entries considered hidden (see `hiddenfiles`). +Hidden detection on Windows also respects the filesystem hidden attribute. +Leave empty to disable and fall back to the regular colour lookup. +Example: `set hiddendirfmt "\033[3;38;2;146;131;116m"`. + ## hiddenfiles ([]string) (default `.*` for Unix and `` for Windows) List of hidden file glob patterns. diff --git a/doc.txt b/doc.txt index 48fc1b484..43df165ab 100644 --- a/doc.txt +++ b/doc.txt @@ -1031,6 +1031,15 @@ Show hidden files. On Unix systems, hidden files are determined by the value of hiddenfiles. On Windows, files with hidden attributes are also considered hidden files. +hiddendirfmt (string) (default ``), hiddenfilefmt (string) (default ``) + +Format strings for colouring hidden directories and files. When set to +a non-empty value, they override the normal di and fi colours for +entries considered hidden (see hiddenfiles). Hidden detection on +Windows also respects the filesystem hidden attribute. Leave empty to +disable and fall back to the regular colour lookup. Example: set +hiddendirfmt "\033[3;38;2;146;131;116m". + hiddenfiles ([]string) (default .* for Unix and `` for Windows) List of hidden file glob patterns. Patterns can be given as relative or diff --git a/eval.go b/eval.go index 51b0e84b5..22c89ad40 100644 --- a/eval.go +++ b/eval.go @@ -250,6 +250,10 @@ func (e *setExpr) eval(app *app, _ []string) { return } gOpts.findlen = n + case "hiddendirfmt": + gOpts.hiddendirfmt = e.val + case "hiddenfilefmt": + gOpts.hiddenfilefmt = e.val case "hiddenfiles": toks := strings.Split(e.val, ":") for _, s := range toks { diff --git a/lf.1 b/lf.1 index 9e84bb9d9..82baec77f 100644 --- a/lf.1 +++ b/lf.1 @@ -298,6 +298,8 @@ filesep string (default \(dq\(rsn\(dq) filtermethod string (default \(aqtext\(aq) findlen int (default 1) hidden bool (default false) +hiddendirfmt string (default \(aq\(aq) +hiddenfilefmt string (default \(aq\(aq) hiddenfiles []string (default \(aq.*\(aq for Unix and \(aq\(aq for Windows) history bool (default true) icons bool (default false) @@ -922,6 +924,15 @@ On Unix systems, hidden files are determined by the value of \f[CR]hiddenfiles\f[R]. On Windows, files with hidden attributes are also considered hidden files. +.SS hiddendirfmt (string) (default \(ga\(ga), hiddenfilefmt (string) (default \(ga\(ga) +Format strings for colouring hidden directories and files. +When set to a non-empty value, they override the normal \f[CR]di\f[R] +and \f[CR]fi\f[R] colours for entries considered hidden (see +\f[CR]hiddenfiles\f[R]). +Hidden detection on Windows also respects the filesystem hidden +attribute. +Leave empty to disable and fall back to the regular colour lookup. +Example: \f[CR]set hiddendirfmt \(dq\(rs033[3;38;2;146;131;116m\(dq\f[R]. .SS hiddenfiles ([]string) (default \f[CR].*\f[R] for Unix and \(ga\(ga for Windows) List of hidden file glob patterns. Patterns can be given as relative or absolute paths. diff --git a/opts.go b/opts.go index 3140253b3..71d535b03 100644 --- a/opts.go +++ b/opts.go @@ -102,6 +102,8 @@ var gOpts struct { filtermethod searchMethod findlen int hidden bool + hiddendirfmt string + hiddenfilefmt string hiddenfiles []string history bool icons bool @@ -245,6 +247,8 @@ func init() { gOpts.filtermethod = textSearch gOpts.findlen = 1 gOpts.hidden = false + gOpts.hiddendirfmt = "" + gOpts.hiddenfilefmt = "" gOpts.hiddenfiles = gDefaultHiddenFiles gOpts.history = true gOpts.icons = false From 042cbcb92e03e93873f49fddd5b42d4b4c9170d9 Mon Sep 17 00:00:00 2001 From: Alexander Slavschik Date: Mon, 20 Apr 2026 12:38:48 +0200 Subject: [PATCH 5/5] refactor: consolidate hiddendirfmt/hiddenfilefmt into single hiddenfmt option Replace separate hiddendirfmt and hiddenfilefmt options with a single hiddenfmt option, and move the hidden colour logic from colors.go into printDir in ui.go per reviewer feedback on #2492. --- CHANGELOG.md | 2 +- colors.go | 28 ---------------------------- doc.md | 9 +++++---- eval.go | 6 ++---- opts.go | 6 ++---- ui.go | 7 ++++++- 6 files changed, 16 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 100a4943c..3942b6470 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), - A new option `borderstyle` is added to control whether `drawbox` draws an outline, separators, or both (#2445). This also replaces the existing `roundbox` option. - The `loading...` message delay of 100 milliseconds for file previews is now applied to directories as well (#2410). - `lf` will now automatically change to the parent directory if the current directory no longer exists and the `watch` option is enabled (#2424). -- New options `hiddendirfmt` and `hiddenfilefmt` are added to colour hidden directories and files separately from `di` and `fi` (#2492). +- A new option `hiddenfmt` is added to colour hidden files and directories (#2492). ### Fixed diff --git a/colors.go b/colors.go index 4538b8dba..3e22a626c 100644 --- a/colors.go +++ b/colors.go @@ -221,23 +221,6 @@ func (sm styleMap) parseBSD(env string) { } } -// parseFmtStyle converts an escape-sequence option value like -// "\033[3;38;2;146;131;116m" to a tcell.Style. Returns false for empty or -// malformed input so callers can fall back to the default style. -func parseFmtStyle(fmtStr string) (tcell.Style, bool) { - if fmtStr == "" { - return tcell.StyleDefault, false - } - s, ok := strings.CutPrefix(fmtStr, "\033[") - if !ok { - return tcell.StyleDefault, false - } - s, ok = strings.CutSuffix(s, "m") - if !ok { - return tcell.StyleDefault, false - } - return applySGR(s, tcell.StyleDefault), true -} func (sm styleMap) get(f *file) tcell.Style { if val, ok := sm.styles[f.path]; ok { @@ -282,11 +265,6 @@ func (sm styleMap) get(f *file) tcell.Style { } if val, ok := sm.styles[key]; ok { - if key == "di" && isHidden(f.FileInfo, filepath.Dir(f.path), gOpts.hiddenfiles) { - if st, ok := parseFmtStyle(gOpts.hiddendirfmt); ok { - return st - } - } return val } @@ -302,12 +280,6 @@ func (sm styleMap) get(f *file) tcell.Style { return val } - if isHidden(f.FileInfo, filepath.Dir(f.path), gOpts.hiddenfiles) { - if st, ok := parseFmtStyle(gOpts.hiddenfilefmt); ok { - return st - } - } - if val, ok := sm.styles["*"+strings.ToLower(f.ext)]; ok { return val } diff --git a/doc.md b/doc.md index 9c6f95077..489fbfcb2 100644 --- a/doc.md +++ b/doc.md @@ -274,6 +274,7 @@ The following options can be used to customize the behavior of lf: filtermethod string (default 'text') findlen int (default 1) hidden bool (default false) + hiddenfmt string (default '') hiddenfiles []string (default '.*' for Unix and '' for Windows) history bool (default true) icons bool (default false) @@ -955,13 +956,13 @@ Show hidden files. On Unix systems, hidden files are determined by the value of `hiddenfiles`. On Windows, files with hidden attributes are also considered hidden files. -## hiddendirfmt (string) (default ``), hiddenfilefmt (string) (default ``) +## hiddenfmt (string) (default ``) -Format strings for colouring hidden directories and files. -When set to a non-empty value, they override the normal `di` and `fi` colours for entries considered hidden (see `hiddenfiles`). +Format string for colouring hidden files and directories. +When set to a non-empty value, it overrides the normal colour for entries considered hidden (see `hiddenfiles`). Hidden detection on Windows also respects the filesystem hidden attribute. Leave empty to disable and fall back to the regular colour lookup. -Example: `set hiddendirfmt "\033[3;38;2;146;131;116m"`. +Example: `set hiddenfmt "\033[3;38;2;146;131;116m"`. ## hiddenfiles ([]string) (default `.*` for Unix and `` for Windows) diff --git a/eval.go b/eval.go index 22c89ad40..a3a7b8346 100644 --- a/eval.go +++ b/eval.go @@ -250,10 +250,8 @@ func (e *setExpr) eval(app *app, _ []string) { return } gOpts.findlen = n - case "hiddendirfmt": - gOpts.hiddendirfmt = e.val - case "hiddenfilefmt": - gOpts.hiddenfilefmt = e.val + case "hiddenfmt": + gOpts.hiddenfmt = e.val case "hiddenfiles": toks := strings.Split(e.val, ":") for _, s := range toks { diff --git a/opts.go b/opts.go index 71d535b03..70c1e0e8a 100644 --- a/opts.go +++ b/opts.go @@ -102,8 +102,7 @@ var gOpts struct { filtermethod searchMethod findlen int hidden bool - hiddendirfmt string - hiddenfilefmt string + hiddenfmt string hiddenfiles []string history bool icons bool @@ -247,8 +246,7 @@ func init() { gOpts.filtermethod = textSearch gOpts.findlen = 1 gOpts.hidden = false - gOpts.hiddendirfmt = "" - gOpts.hiddenfilefmt = "" + gOpts.hiddenfmt = "" gOpts.hiddenfiles = gDefaultHiddenFiles gOpts.history = true gOpts.icons = false diff --git a/ui.go b/ui.go index 770d50017..3c5c096c2 100644 --- a/ui.go +++ b/ui.go @@ -313,7 +313,12 @@ func (win *win) printDir(ui *ui, dir *dir, context *dirContext, dirStyle *dirSty visualSelections := dir.visualSelections() for i, f := range dir.files[beg:end] { - st := dirStyle.colors.get(f) + var st tcell.Style + if gOpts.hiddenfmt != "" && isHidden(f, dir.path, gOpts.hiddenfiles) { + st = parseEscapeSequence(gOpts.hiddenfmt) + } else { + st = dirStyle.colors.get(f) + } if lnwidth > 0 { var ln string