Skip to content

colors: support for hidden files and folders#2492

Open
slavshik wants to merge 5 commits intogokcehan:masterfrom
slavshik:master
Open

colors: support for hidden files and folders#2492
slavshik wants to merge 5 commits intogokcehan:masterfrom
slavshik:master

Conversation

@slavshik
Copy link
Copy Markdown

@slavshik slavshik commented Apr 7, 2026

Closes #2493

Summary

Add support for .* and .*/ as special color patterns that match any dotfile or dotfolder respectively.

Problem: lf's color lookup works by constructing a lookup key from the filename (e.g. for .gitconfig it checks .gitconfig*, *.gitconfig, etc.) and doing an exact map lookup. This makes it impossible to write a single pattern that matches all dotfiles — there is no key that would be generated for every file starting with ..

Solution: Two targeted checks added to get() in colors.go:

  1. For dotdirs (where key == "di"): before returning the di style, check if .*/ or .* is in the styles map and return that instead.
  2. For plain dotfiles (no special type match): after the name prefix/suffix/extension checks, check if .* is in the styles map before falling back to fi.

Priority behavior:

  • Exact path match and explicit name/ patterns still win (highest priority)
  • Special type codes (ln, ex, tw, ow, st, etc.) are unaffected — only di and plain files are overridden
  • Name-based patterns (.gitconfig*, *.conf) take precedence over .*
  • .* applies to both dotfiles and dotdirs (as fallback when .*/ is not set)

Example usage in colors file:

.*/     2;33    # dotfolders — dim yellow
.*      2;37    # dotfiles   — dim white
Screenshot 2026-04-07 at 13 38 25

@CatsDeservePets
Copy link
Copy Markdown
Collaborator

CatsDeservePets commented Apr 7, 2026

Regarding dotfiles, this is what the man page of dir_colors says:

       *extension color-sequence
              Specifies the color used for any file that ends in extension.

        .extension color-sequence
              Same as *.extension.  Specifies the color used for any file that ends in .extension.  Note that the period is included in  the
              extension,  which  makes  it  impossible to specify an extension not starting with a period, such as ~ for emacs backup files.
              This form should be considered obsolete.

At least for colors, lf tries to match GNU's $LS_COLORS matching.
I also noticed, that our matching isn't perfect as we test for extensions rather than suffixes, which can lead to problems with files like test.jpg.~1~.

How do your patterns look like when used inside $LS_COLORS and ls?

Also worth mentioning is that we explicitly don't support real globbing due to performance implications.

@slavshik
Copy link
Copy Markdown
Author

slavshik commented Apr 7, 2026

Good point.

To avoid any ambiguity with the $LS_COLORS format, I've updated the implementation to use dedicated type codes instead — hd (hidden directory) and hf (hidden file) — following the same convention as di, fi, ex, etc.:

hd      2;33    # hidden dir
hf      2;37    # hidden file

This approach:

  • Has no overlap with any $LS_COLORS / dir_colors semantics
  • Fits naturally alongside the existing type codes
  • Keeps the implementation simple — just two strings.HasPrefix(f.Name(), ".") checks, no globbing

@slavshik slavshik changed the title colors: support .* and .*/ patterns for dotfiles and dotfolders colors: support for hidden files and folders Apr 7, 2026
Copy link
Copy Markdown
Collaborator

@CatsDeservePets CatsDeservePets left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure about this. I admit this is a rather simple fix. However, this seems like a pretty specific use-case.

Do you know about other tools that support this? I am kinda against inventing a new syntax just for this. However, I do get that you probably don't want to add dozens of filenames to your config, to work around this.

Something else to consider: lf has special logic to determine hidden files on Windows. If we were to add this, you should probably consider using isHidden.

Also: What about icons?

Not sure how related that is, but there are long standing issues about executable icons overwriting others. I compared joshuto, eza, lsd and lf. In terms of colouring, their behaviour seems to match (i.e. following LS_COLORS/ls in terms of precedence). For icons there is no de facto standard as this was not considered back then. However, besides lf, name and suffix matches always seem to take precedence over file type matches.

I actually considered changing the matching priority for icons, so names and suffixes match first. I only wanted to do that for icons, not colours, as this seems to be the way, other established tools do it.
Just wanted to mention that. If you are interested in this, you can read more about this topic and proposed other solutions here:

Changing the matching priority and your feature request are different but also related topics, so I wanted to link this here.

And as always, I would also like to hear @joelim-work's opinion on this PR.

@joelim-work
Copy link
Copy Markdown
Collaborator

The rules for matching files in lf is based on LS_COLORS, which AFAIK does not have a way to specify hidden files. So then this essentially becomes a feature request, but then it has to be evaluated as to whether it can be accepted or not. Apart from hidden files, users may wish to match based on other criteria such as globs, regexes, whether the owner is root, etc. - the possibilities are endless, so where do you draw the line?

@slavshik
Copy link
Copy Markdown
Author

slavshik commented Apr 8, 2026

@CatsDeservePets, @joelim-work Thank you for the feedbacks!

Regarding the origin of this feature idea – I've seen it in nvim oil plugin and found it nice:

Screenshot 2026-04-08 at 16 54 25

I've tried to use isHidden instead and yeah, it works perfectly fine! At least in my system (macOS).
8c24cb1

and my config for hiddens looks like this:

# dotfiles and dotfolders (pale/italic gruvbox Comment gray)
hd      3;38;2;146;131;116    # hidden dir
hf      3;38;2;146;131;116    # hidden file

@CatsDeservePets
Copy link
Copy Markdown
Collaborator

CatsDeservePets commented Apr 9, 2026

@CatsDeservePets, @joelim-work Thank you for the feedbacks!

Regarding the origin of this feature idea – I've seen it in nvim oil plugin and found it nice:

Screenshot 2026-04-08 at 16 54 25

I see I see. I do admit that I kinda like the look. Its what GUI file managers like Explorer or Finder do as well.

I've tried to use isHidden instead and yeah, it works perfectly fine! At least in my system (macOS). 8c24cb1

I'd be surprised if it didn't. Most importantly, it also respects the hidden file attribute on Windows.

and my config for hiddens looks like this:

# dotfiles and dotfolders (pale/italic gruvbox Comment gray)
hd      3;38;2;146;131;116    # hidden dir
hf      3;38;2;146;131;116    # hidden file

I tested it by putting these into my $LS_COLORS env var. It worked fine inside lf.
However, something I did consider is how other tools react to these custom types.
GNU ls shows the following error message and refuses to show any colour at all:

$ ls -l --color 
ls: unrecognized prefix: ‘hd’
ls: unparsable value for LS_COLORS environment variable

So I don't think this is the right approach. Something I could imagine is a new option like hiddenfmt which overrides the colour of hidden files if not empty. This should be done inside printDir. However, that would also probably mean more computation.

So overall, I see the appeal of such an option but I am not sure how many people would actually use it.

@slavshik
Copy link
Copy Markdown
Author

So I don't think this is the right approach. Something I could imagine is a new option like hiddenfmt which overrides the colour of hidden files if not empty.

Good call — hd/hf as LS_COLORS keys is a leaky abstraction. I went with your suggestion, but kept the dir/file split as two separate options since it's a zero-cost generalisation of hiddenfmt:

  • hiddendirfmt — overrides di for hidden directories
  • hiddenfilefmt — overrides fi for hidden files

Both default to "" (no override), take an ANSI escape string exactly like cursoractivefmt / copyfmt, and are applied in colors.go:get() after isHidden is confirmed. Nothing lf-specific ever needs to live in $LS_COLORS / $LF_COLORS anymore — the hd/hf styleMap keys are gone.

Example config:

set hiddendirfmt  "\033[3;38;2;146;131;116m"
set hiddenfilefmt "\033[3;38;2;146;131;116m"

See c5c86dc

@CatsDeservePets
Copy link
Copy Markdown
Collaborator

Personally, I still think this should live inside printDir. I also not sure whether there should be different options for files and directories. I guess for people not using icons it might be useful to tell them apart, but it could also lead to people demanding more and more different types.
I'd do something rather simple like this:

@@ -321,7 +321,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, f.path, gOpts.hiddenfiles) {
+           st = parseEscapeSequence(gOpts.hiddenfmt)
+       } else {
+           st = dirStyle.colors.get(f)
+       }
 
        if lnwidth > 0 {
            var ln string

But the bigger question remains: Should it be merged?

…t 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 gokcehan#2492.
@slavshik
Copy link
Copy Markdown
Author

Hey @CatsDeservePets
I've added changes you've suggested. Now it's just one hiddenfmt and it works perfectly fine for me. Whether you merge it or not is up to you.

@CatsDeservePets
Copy link
Copy Markdown
Collaborator

CatsDeservePets commented Apr 22, 2026

Hey @CatsDeservePets I've added changes you've suggested. Now it's just one hiddenfmt and it works perfectly fine for me. Whether you merge it or not is up to you.

I don't want to decide this without @joelim-work. However, I think he might say that this is something that could be added using:

@joelim-work
Copy link
Copy Markdown
Collaborator

Hi @slavshik, @CatsDeservePets

Thank you for your work and interest on this. I agree that the current syntax (based on LS_COLORS) is limited, and that there is no way to specify hidden files otherwise. However, I still think this is a somewhat niche use case (toggling hidden files is generally sufficient for most users), and actually there was a similar feature request for customizing the appearance of duplicate copied files in #1739 but it was rejected.

If there is significant demand for this from other users, then I will reconsider. But for now you can just manually specify each name directly (i.e. .git/, .DS_Store, etc.). Although this approach doesn't scale well, adding yet another fmt option to the printDir function also doesn't scale well from a codebase maintenance perspective. The example colors file has over 100 entries, so adding another 10-50 entries for hidden files shouldn't cause any issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: support for hidden files and dirs dimming

3 participants