Repository: gokcehan/lf
Pull request #1674 (2024-04-03 10:37:37)

Support icon color
Implement #928 #1520. Now you can add the third column in `~/.config/lf/icons` to set icon colors, fallback to filename color in `~/.config/lf/colors` if icon color is not specified.

Example:
```
*.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
```

<img width="552" alt="image" src="https://github.com/gokcehan/lf/assets/3124581/259cec94-c7b5-4573-a8a5-a9c3b5695356">

Also implement #992. You can remove icons by writting only one column in `~/.config/lf/icons`, for example:

```
ln             # LINK
or             # ORPHAN
ow             # OTHER_WRITABLE
di             # DIR
ex              # EXEC, disable excutable file icon, select icon by filename
``` 

wodesuck: @joelim-work Updated, check again pls. 🙏
gokcehan: Thank you very much @wodesuck for the patch and @joelim-work for the review. There is some conflict in this patch after #1644 is merged, and I wasn't sure how to resolve them. Can you please update the patch one more time? Sorry for the inconvenience as we have been holding many feature patches for a while due to the new release.
wodesuck: @gokcehan Conflict resolved.

Patch:
diff --git a/doc.md b/doc.md
index d84e7bd..4454f50 100644
--- a/doc.md
+++ b/doc.md
@@ -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
diff --git a/doc.txt b/doc.txt
index 64c41a6..b21d608 100644
--- a/doc.txt
+++ b/doc.txt
@@ -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
diff --git a/icons.go b/icons.go
index 5583864..963a7a2 100644
--- a/icons.go
+++ b/icons.go
@@ -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(" ")
 }
diff --git a/misc.go b/misc.go
index 63b162e..d877d64 100644
--- a/misc.go
+++ b/misc.go
@@ -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/ui.go b/ui.go
index 5aabf66..0a26c2e 100644
--- a/ui.go
+++ b/ui.go
@@ -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 {


Test patch:
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)
 		}
 	}