diff options
author | Daniel Zhang <wodesuck@gmail.com> | 2024-04-21 03:41:37 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-20 21:41:37 +0200 |
commit | 6cabb0e8e43c8a374fcb1e0d4225141f478ce212 (patch) | |
tree | 49fb95beca756580bf937a9e10d26dd5a3c54a96 | |
parent | ce1211641830029000f7f3c56444dc509487d6b2 (diff) |
Support icon color (#1674)
* Support icon color
* Rename variables
* Test for readArrays
* Add doc and examples for icon color
-rw-r--r-- | doc.md | 6 | ||||
-rw-r--r-- | doc.txt | 13 | ||||
-rw-r--r-- | etc/icons.example | 16 | ||||
-rw-r--r-- | etc/icons_colored.example | 377 | ||||
-rw-r--r-- | icons.go | 53 | ||||
-rw-r--r-- | misc.go | 31 | ||||
-rw-r--r-- | misc_test.go | 28 | ||||
-rw-r--r-- | ui.go | 9 |
8 files changed, 490 insertions, 43 deletions
@@ -1814,7 +1814,8 @@ Icons are configured using `LF_ICONS` environment variable or an icons file (ref 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. +The icons file (refer to the [CONFIGURATION section](https://github.com/gokcehan/lf/blob/master/doc.md#configuration)) should consist of whitespace-separated arrays with a `#` character to start comments until the end of the line. +Each line should contain 1-3 columns, first column is filetype or filename pattern, second column is the icon, third column is an optional icon color. If there is only one column, means to disable rule for this filetype or pattern. 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: @@ -1835,3 +1836,6 @@ Default values are as follows given with their matching order in lf: A sample icons file can be found at https://github.com/gokcehan/lf/blob/master/etc/icons.example + +A sample colored icons file can be found at +https://github.com/gokcehan/lf/blob/master/etc/icons_colored.example @@ -2057,9 +2057,13 @@ 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 -as follows given with their matching order in lf: +a # character to start comments until the end of the line. Each line +should contain 1-3 columns, first column is filetype or filename +pattern, second column is the icon, third column is an optional icon +color. If there is only one column, means to disable rule for this +filetype or pattern. 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: ln l or l @@ -2078,3 +2082,6 @@ as follows given with their matching order in lf: A sample icons file can be found at https://github.com/gokcehan/lf/blob/master/etc/icons.example + +A sample colored icons file can be found at +https://github.com/gokcehan/lf/blob/master/etc/icons_colored.example diff --git a/etc/icons.example b/etc/icons.example index 43dbe5d..4022691 100644 --- a/etc/icons.example +++ b/etc/icons.example @@ -35,6 +35,22 @@ sg g # SETGID ex # EXEC fi # FILE +# disable some default filetype icons, let them choose icon by filename +# ln # LINK +# or # ORPHAN +# tw # STICKY_OTHER_WRITABLE +# ow # OTHER_WRITABLE +# st # STICKY +# di # DIR +# pi # FIFO +# so # SOCK +# bd # BLK +# cd # CHR +# su # SETUID +# sg # SETGID +# ex # EXEC +# fi # FILE + # file extensions (vim-devicons) *.styl *.sass diff --git a/etc/icons_colored.example b/etc/icons_colored.example new file mode 100644 index 0000000..437e1b0 --- /dev/null +++ b/etc/icons_colored.example @@ -0,0 +1,377 @@ +# vim:ft=conf + +# These examples require Nerd Fonts or a compatible font to be used. +# See https://www.nerdfonts.com for more information. + +# default values from lf (with matching order) +# ln l # LINK +# or l # ORPHAN +# tw t # STICKY_OTHER_WRITABLE +# ow d # OTHER_WRITABLE +# st t # STICKY +# di d # DIR +# pi p # FIFO +# so s # SOCK +# bd b # BLK +# cd c # CHR +# su u # SETUID +# sg g # SETGID +# ex x # EXEC +# fi - # FILE + +# file types (with matching order) +ln # LINK +or # ORPHAN +tw t # STICKY_OTHER_WRITABLE +ow # OTHER_WRITABLE +st t # STICKY +di # DIR +pi p # FIFO +so s # SOCK +bd b # BLK +cd c # CHR +su u # SETUID +sg g # SETGID +ex # EXEC +fi # FILE + +# disable some default filetype icons, let them choose icon by filename +# ln # LINK +# or # ORPHAN +# tw # STICKY_OTHER_WRITABLE +# ow # OTHER_WRITABLE +# st # STICKY +# di # DIR +# pi # FIFO +# so # SOCK +# bd # BLK +# cd # CHR +# su # SETUID +# sg # SETGID +# ex # EXEC +# fi # FILE + +# file extensions (vim-devicons) +*.styl 00;38;2;141;193;73 +*.sass 00;38;2;245;83;133 +*.scss 00;38;2;245;83;133 +*.htm 00;38;2;227;76;38 +*.html 00;38;2;227;76;38 +*.slim 00;38;2;227;76;38 +*.haml 00;38;2;234;234;225 +*.ejs 00;38;2;203;203;65 +*.css 00;38;2;86;61;124 +*.less 00;38;2;86;61;124 +*.md 00;38;2;81;154;186 +*.mdx 00;38;2;81;154;186 +*.markdown 00;38;2;81;154;186 +*.rmd 00;38;2;81;154;186 +*.json 00;38;2;149;157;165 +*.webmanifest 00;38;2;241;224;90 +*.js 00;38;2;203;203;65 +*.mjs 00;38;2;241;224;90 +*.jsx 00;38;2;81;154;186 +*.rb 00;38;2;112;21;22 +*.gemspec 00;38;2;112;21;22 +*.rake 00;38;2;112;21;22 +*.php 00;38;2;160;116;196 +*.py 00;38;2;81;154;186 +*.pyc 00;38;2;81;154;186 +*.pyo 00;38;2;81;154;186 +*.pyd 00;38;2;81;154;186 +*.coffee 00;38;2;203;203;65 +*.mustache 00;38;2;227;121;51 +*.hbs 00;38;2;240;119;43 +*.conf 00;38;2;109;128;134 +*.ini 00;38;2;109;128;134 +*.yml 00;38;2;149;157;165 +*.yaml 00;38;2;149;157;165 +*.toml 00;38;2;109;128;134 +*.bat 00;38;2;193;241;46 +*.mk 00;38;2;109;128;134 +*.jpg 00;38;2;160;116;196 +*.jpeg 00;38;2;160;116;196 +*.bmp 00;38;2;160;116;196 +*.png 00;38;2;160;116;196 +*.webp 00;38;2;160;116;196 +*.gif 00;38;2;160;116;196 +*.ico 00;38;2;203;203;65 +*.twig 00;38;2;141;193;73 +*.cpp 00;38;2;81;154;186 +*.c++ 00;38;2;89;158;255 +*.cxx 00;38;2;81;154;186 +*.cc 00;38;2;81;154;186 +*.cp 00;38;2;81;154;186 +*.c 00;38;2;89;158;255 +*.cs 00;38;2;89;103;6 +*.h 00;38;2;160;116;196 +*.hh 00;38;2;160;116;196 +*.hpp 00;38;2;160;116;196 +*.hxx 00;38;2;160;116;196 +*.hs 00;38;2;160;116;196 +*.lhs 00;38;2;160;116;196 +*.nix 00;38;2;126;186;228 +*.lua 00;38;2;81;160;207 +*.java 00;38;2;204;62;68 +*.sh 00;38;2;137;224;81 +*.fish 00;38;2;137;224;81 +*.bash 00;38;2;137;224;81 +*.zsh 00;38;2;137;224;81 +*.ksh 00;38;2;137;224;81 +*.csh 00;38;2;137;224;81 +*.awk 00;38;2;137;224;81 +*.ps1 00;38;2;137;224;81 +*.ml λ 00;38;2;227;121;51 +*.mli λ 00;38;2;227;121;51 +*.diff 00;38;2;65;83;91 +*.db 00;38;2;218;216;216 +*.sql 00;38;2;218;216;216 +*.dump 00;38;2;218;216;216 +*.clj 00;38;2;141;193;73 +*.cljc 00;38;2;141;193;73 +*.cljs 00;38;2;81;154;186 +*.edn 00;38;2;81;154;186 +*.scala 00;38;2;204;62;68 +*.go 00;38;2;81;154;186 +*.dart 00;38;2;3;88;156 +*.xul 00;38;2;227;121;51 +*.sln 00;38;2;133;76;199 +*.suo 00;38;2;133;76;199 +*.pl 00;38;2;81;154;186 +*.pm 00;38;2;81;154;186 +*.t 00;38;2;81;154;186 +*.rss 00;38;2;251;157;59 +'*.f#' 00;38;2;81;154;186 +*.fsscript 00;38;2;81;154;186 +*.fsx 00;38;2;81;154;186 +*.fs 00;38;2;81;154;186 +*.fsi 00;38;2;81;154;186 +*.rs 00;38;2;222;165;132 +*.rlib 00;38;2;222;165;132 +*.d 00;38;2;66;120;25 +*.erl 00;38;2;184;57;152 +*.hrl 00;38;2;184;57;152 +*.ex 00;38;2;160;116;196 +*.exs 00;38;2;160;116;196 +*.eex 00;38;2;160;116;196 +*.leex 00;38;2;160;116;196 +*.heex 00;38;2;160;116;196 +*.vim 00;38;2;1;152;51 +*.ai 00;38;2;203;203;65 +*.psd 00;38;2;81;154;186 +*.psb 00;38;2;81;154;186 +*.ts 00;38;2;81;154;186 +*.tsx 00;38;2;81;154;186 +*.jl 00;38;2;162;112;186 +*.pp 00;38;2;255;166;26 +*.vue 00;38;2;141;193;73 +*.elm 00;38;2;81;154;186 +*.swift 00;38;2;227;121;51 +*.xcplayground 00;38;2;227;121;51 +*.tex 00;38;2;61;97;23 +*.r 00;38;2;53;138;91 +*.rproj 00;38;2;53;138;91 +*.sol 00;38;2;81;154;186 +*.pem 00;38;2;205;155;62 + +# file names (vim-devicons) (case-insensitive not supported in lf) +*gruntfile.coffee 00;38;2;227;121;51 +*gruntfile.js 00;38;2;227;121;51 +*gruntfile.ls 00;38;2;227;121;51 +*gulpfile.coffee 00;38;2;204;62;68 +*gulpfile.js 00;38;2;204;62;68 +*gulpfile.ls 00;38;2;204;62;68 +*mix.lock 00;38;2;160;116;196 +*dropbox 00;38;2;0;97;254 +*.ds_store 00;38;2;77;90;94 +*.gitconfig 00;38;2;65;83;91 +*.gitignore 00;38;2;65;83;91 +*.gitattributes 00;38;2;65;83;91 +*.gitlab-ci.yml 00;38;2;226;67;41 +*.bashrc 00;38;2;137;224;81 +*.zshrc 00;38;2;137;224;81 +*.zshenv 00;38;2;137;224;81 +*.zprofile 00;38;2;137;224;81 +*.vimrc 00;38;2;1;152;51 +*.gvimrc 00;38;2;1;152;51 +*_vimrc 00;38;2;1;152;51 +*_gvimrc 00;38;2;1;152;51 +*.bashprofile 00;38;2;137;224;81 +*favicon.ico 00;38;2;203;203;65 +*license 00;38;2;203;203;65 +*node_modules 00;38;2;232;39;75 +*react.jsx 00;38;2;81;154;186 +*procfile 00;38;2;160;116;196 +*dockerfile 00;38;2;81;154;186 +*docker-compose.yml 00;38;2;81;154;186 +*docker-compose.yaml 00;38;2;81;154;186 +*compose.yml 00;38;2;81;154;186 +*compose.yaml 00;38;2;81;154;186 +*rakefile 00;38;2;112;21;22 +*config.ru 00;38;2;112;21;22 +*gemfile 00;38;2;112;21;22 +*makefile 00;38;2;109;128;134 +*cmakelists.txt 00;38;2;109;128;134 +*robots.txt 00;38;2;109;128;134 + +# file names (case-sensitive adaptations) +*Gruntfile.coffee 00;38;2;227;121;51 +*Gruntfile.js 00;38;2;227;121;51 +*Gruntfile.ls 00;38;2;227;121;51 +*Gulpfile.coffee 00;38;2;204;62;68 +*Gulpfile.js 00;38;2;204;62;68 +*Gulpfile.ls 00;38;2;204;62;68 +*Dropbox 00;38;2;0;97;254 +*.DS_Store 00;38;2;193;241;46 +*LICENSE 00;38;2;203;203;65 +*React.jsx 00;38;2;81;154;186 +*Procfile 00;38;2;160;116;196 +*Dockerfile 00;38;2;81;154;186 +*Docker-compose.yml 00;38;2;81;154;186 +*Docker-compose.yaml 00;38;2;81;154;186 +*Rakefile 00;38;2;112;21;22 +*Gemfile 00;38;2;112;21;22 +*Makefile 00;38;2;109;128;134 +*CMakeLists.txt 00;38;2;109;128;134 + +# file patterns (vim-devicons) (patterns not supported in lf) +# .*jquery.*\.js$ 00;38;2;227;117;187 +# .*angular.*\.js$ 00;38;2;226;50;55 +# .*backbone.*\.js$ 00;38;2;0;113;181 +# .*require.*\.js$ 00;38;2;244;74;65 +# .*materialize.*\.js$ 00;38;2;238;110;115 +# .*materialize.*\.css$ 00;38;2;238;110;115 +# .*mootools.*\.js$ 00;38;2;236;236;236 +# .*vimrc.* 00;38;2;1;152;51 +# Vagrantfile$ 00;38;2;21;99;255 + +# file patterns (file name adaptations) +*jquery.min.js 00;38;2;227;117;187 +*angular.min.js 00;38;2;226;50;55 +*backbone.min.js 00;38;2;0;113;181 +*require.min.js 00;38;2;244;74;65 +*materialize.min.js 00;38;2;238;110;115 +*materialize.min.css 00;38;2;238;110;115 +*mootools.min.js 00;38;2;236;236;236 +*vimrc 00;38;2;1;152;51 +Vagrantfile 00;38;2;21;99;255 + +# archives or compressed (extensions from dircolors defaults) +*.tar +*.tgz +*.arc +*.arj +*.taz +*.lha +*.lz4 +*.lzh +*.lzma +*.tlz +*.txz +*.tzo +*.t7z +*.zip +*.z +*.dz +*.gz +*.lrz +*.lz +*.lzo +*.xz +*.zst +*.tzst +*.bz2 +*.bz +*.tbz +*.tbz2 +*.tz +*.deb +*.rpm +*.jar +*.war +*.ear +*.sar +*.rar +*.alz +*.ace +*.zoo +*.cpio +*.7z +*.rz +*.cab +*.wim +*.swm +*.dwm +*.esd + +# image formats (extensions from dircolors defaults) +*.jpg +*.jpeg +*.mjpg +*.mjpeg +*.gif +*.bmp +*.pbm +*.pgm +*.ppm +*.tga +*.xbm +*.xpm +*.tif +*.tiff +*.png +*.svg +*.svgz +*.mng +*.pcx +*.mov +*.mpg +*.mpeg +*.m2v +*.mkv +*.webm +*.ogm +*.mp4 +*.m4v +*.mp4v +*.vob +*.qt +*.nuv +*.wmv +*.asf +*.rm +*.rmvb +*.flc +*.avi +*.fli +*.flv +*.gl +*.dl +*.xcf +*.xwd +*.yuv +*.cgm +*.emf +*.ogv +*.ogx + +# audio formats (extensions from dircolors defaults) +*.aac +*.au +*.flac +*.m4a +*.mid +*.midi +*.mka +*.mp3 +*.mpc +*.ogg +*.ra +*.wav +*.oga +*.opus +*.spx +*.xspf + +# other formats +*.pdf 00;38;2;179;11;0 @@ -5,16 +5,32 @@ import ( "os" "path/filepath" "strings" + + "github.com/gdamore/tcell/v2" ) +type iconDef struct { + icon string + hasStyle bool + style tcell.Style +} + type iconMap struct { - icons map[string]string + icons map[string]iconDef useLinkTarget bool } +func iconWithoutStyle(icon string) iconDef { + return iconDef{icon, false, tcell.StyleDefault} +} + +func iconWithStyle(icon string, style tcell.Style) iconDef { + return iconDef{icon, true, style} +} + func parseIcons() iconMap { im := iconMap{ - icons: make(map[string]string), + icons: make(map[string]iconDef), useLinkTarget: false, } @@ -60,14 +76,14 @@ func (im *iconMap) parseFile(path string) { } defer f.Close() - pairs, err := readPairs(f) + arrs, err := readArrays(f, 1, 3) if err != nil { log.Printf("reading icons file: %s", err) return } - for _, pair := range pairs { - im.parsePair(pair) + for _, arr := range arrs { + im.parseArray(arr) } } @@ -84,12 +100,12 @@ func (im *iconMap) parseEnv(env string) { return } - im.parsePair(pair) + im.parseArray(pair) } } -func (im *iconMap) parsePair(pair []string) { - key, val := pair[0], pair[1] +func (im *iconMap) parseArray(arr []string) { + key := arr[0] key = replaceTilde(key) @@ -97,14 +113,23 @@ func (im *iconMap) parsePair(pair []string) { key = filepath.Clean(key) } - if key == "ln" && val == "target" { - im.useLinkTarget = true + switch len(arr) { + case 1: + delete(im.icons, key) + case 2: + icon := arr[1] + if key == "ln" && icon == "target" { + im.useLinkTarget = true + } else { + im.icons[key] = iconWithoutStyle(icon) + } + case 3: + icon, color := arr[1], arr[2] + im.icons[key] = iconWithStyle(icon, applyAnsiCodes(color, tcell.StyleDefault)) } - - im.icons[key] = val } -func (im iconMap) get(f *file) string { +func (im iconMap) get(f *file) iconDef { if val, ok := im.icons[f.path]; ok { return val } @@ -170,5 +195,5 @@ func (im iconMap) get(f *file) string { return val } - return " " + return iconWithoutStyle(" ") } @@ -152,12 +152,12 @@ func splitWord(s string) (word, rest string) { return } -// This function reads whitespace separated string pairs at each line. Single +// This function reads whitespace separated string arrays at each line. Single // or double quotes can be used to escape whitespaces. Hash characters can be // used to add a comment until the end of line. Leading and trailing space is // trimmed. Empty lines are skipped. -func readPairs(r io.Reader) ([][]string, error) { - var pairs [][]string +func readArrays(r io.Reader, min_cols, max_cols int) ([][]string, error) { + var arrays [][]string s := bufio.NewScanner(r) for s.Scan() { line := s.Text() @@ -182,7 +182,7 @@ func readPairs(r io.Reader) ([][]string, error) { } squote, dquote = false, false - pair := strings.FieldsFunc(line, func(r rune) bool { + arr := strings.FieldsFunc(line, func(r rune) bool { if r == '\'' && !dquote { squote = !squote } else if r == '"' && !squote { @@ -191,14 +191,17 @@ func readPairs(r io.Reader) ([][]string, error) { return !squote && !dquote && unicode.IsSpace(r) }) - if len(pair) != 2 { - return nil, fmt.Errorf("expected pair but found: %s", s.Text()) + if len(arr) < min_cols || len(arr) > max_cols { + if min_cols == max_cols { + return nil, fmt.Errorf("expected %d columns but found: %s", min_cols, s.Text()) + } + return nil, fmt.Errorf("expected %d~%d columns but found: %s", min_cols, max_cols, s.Text()) } - for i := 0; i < len(pair); i++ { + for i := 0; i < len(arr); i++ { squote, dquote = false, false - buf := make([]rune, 0, len(pair[i])) - for _, r := range pair[i] { + buf := make([]rune, 0, len(arr[i])) + for _, r := range arr[i] { if r == '\'' && !dquote { squote = !squote continue @@ -209,13 +212,17 @@ func readPairs(r io.Reader) ([][]string, error) { } buf = append(buf, r) } - pair[i] = string(buf) + arr[i] = string(buf) } - pairs = append(pairs, pair) + arrays = append(arrays, arr) } - return pairs, nil + return arrays, nil +} + +func readPairs(r io.Reader) ([][]string, error) { + return readArrays(r, 2, 2) } // This function converts a size in bytes to a human readable form using metric diff --git a/misc_test.go b/misc_test.go index 52b2628..f991c3a 100644 --- a/misc_test.go +++ b/misc_test.go @@ -217,23 +217,27 @@ func TestSplitWord(t *testing.T) { } } -func TestReadPairs(t *testing.T) { +func TestReadArrays(t *testing.T) { tests := []struct { - s string - exp [][]string + s string + min_cols int + max_cols int + exp [][]string }{ - {"foo bar", [][]string{{"foo", "bar"}}}, - {"foo bar ", [][]string{{"foo", "bar"}}}, - {" foo bar", [][]string{{"foo", "bar"}}}, - {" foo bar ", [][]string{{"foo", "bar"}}}, - {"foo bar#baz", [][]string{{"foo", "bar"}}}, - {"foo bar #baz", [][]string{{"foo", "bar"}}}, - {`'foo#baz' bar`, [][]string{{"foo#baz", "bar"}}}, - {`"foo#baz" bar`, [][]string{{"foo#baz", "bar"}}}, + {"foo bar", 2, 2, [][]string{{"foo", "bar"}}}, + {"foo bar ", 2, 2, [][]string{{"foo", "bar"}}}, + {" foo bar", 2, 2, [][]string{{"foo", "bar"}}}, + {" foo bar ", 2, 2, [][]string{{"foo", "bar"}}}, + {"foo bar#baz", 2, 2, [][]string{{"foo", "bar"}}}, + {"foo bar #baz", 2, 2, [][]string{{"foo", "bar"}}}, + {`'foo#baz' bar`, 2, 2, [][]string{{"foo#baz", "bar"}}}, + {`"foo#baz" bar`, 2, 2, [][]string{{"foo#baz", "bar"}}}, + {"foo bar baz", 3, 3, [][]string{{"foo", "bar", "baz"}}}, + {`"foo bar baz"`, 1, 1, [][]string{{"foo bar baz"}}}, } for _, test := range tests { - if got, _ := readPairs(strings.NewReader(test.s)); !reflect.DeepEqual(got, test.exp) { + if got, _ := readArrays(strings.NewReader(test.s), test.min_cols, test.max_cols); !reflect.DeepEqual(got, test.exp) { t.Errorf("at input '%v' expected '%v' but got '%v'", test.s, test.exp, got) } } @@ -450,8 +450,11 @@ func (win *win) printDir(ui *ui, dir *dir, context *dirContext, dirStyle *dirSty // leave space for displaying the tag s = append(s, ' ') + var icon iconDef + if gOpts.icons { - s = append(s, []rune(dirStyle.icons.get(f))...) + icon = dirStyle.icons.get(f) + s = append(s, []rune(icon.icon)...) s = append(s, ' ') } @@ -497,6 +500,10 @@ func (win *win) printDir(ui *ui, dir *dir, context *dirContext, dirStyle *dirSty styledFilename := fmt.Sprintf(cursorescapefmt, string(s)) win.print(ui.screen, lnwidth+1, i, st, styledFilename) + if icon.hasStyle && i != dir.pos { + win.print(ui.screen, lnwidth+2, i, icon.style, icon.icon) + } + tag, ok := context.tags[path] if ok { if i == dir.pos { |