diff --git a/docs/highlighters/files.md b/docs/highlighters/files.md new file mode 100644 index 0000000..9ca5687 --- /dev/null +++ b/docs/highlighters/files.md @@ -0,0 +1,49 @@ +zsh-syntax-highlighting / highlighters / files +---------------------------------------------- + +This is the `files` highlighter, that highlights existing files appearing on the +command line. + + +### Quickstart + +If you are happy with your `LS_COLORS`, simply add the following line to your +`.zshrc` after sourcing `zsh-syntax-highlighting.zsh`: + +```zsh +zsh_highlight_files_extract_ls_colors +``` + + +### Configuration + +Files are colored according to the associative arrays `ZSH_HIGHLIGHT_FILE_TYPES` +and `ZSH_HIGHLIGHT_FILE_PATTERNS`. The values of `ZSH_HIGHLIGHT_FILE_TYPES` are +color specifications as in `ZSH_HIGHLIGHT_STYLES`, and the keys define which +file types are highlighted according to that style (following `LS_COLORS`): + +* `fi` - ordinary files +* `di` - directories +* `ln` - symbolic links +* `pi` - pipes +* `so` - sockets +* `bd` - block devices +* `cd` - character devices +* `or` - broken symlinks +* `ex` - executable files +* `su` - files that have the suid bit set +* `sg` - files that have the sgid bit set +* `ow` - files that are world-writable +* `tw` - files that are world-writable and sticky +* `lp` - if set, the path-component of a filename is highlighted using this style + +If a file would be highlighted `fi`, then it can be highlighted according to the +filename using `ZSH_HIGHLIGHT_FILE_PATTERNS` instead. The keys of this +associative array are arbitrary glob patterns; the values are color +specifications. For instance, if have `setopt extended_glob` and you write + +```zsh +ZSH_HIGHLIGHT_FILE_PATTERNS[(#i)*.jpe#g]=red,bold +``` + +then the files `foo.jpg` and `bar.jPeG` will be colored red and bold. diff --git a/highlighters/files/files-highlighter.zsh b/highlighters/files/files-highlighter.zsh new file mode 100644 index 0000000..eebcfab --- /dev/null +++ b/highlighters/files/files-highlighter.zsh @@ -0,0 +1,192 @@ +# Highlighter for zsh-syntax-highlighting that highlights filenames + +typeset -gA ZSH_HIGHLIGHT_FILE_TYPES ZSH_HIGHLIGHT_FILE_PATTERNS + +# Convert an ANSI escape sequence color into zle_highlight format (man 1 zshzle) +_zsh_highlight_highlighter_files_ansi_to_zle() { + local match mbegin mend seq + local var=$1; shift + for seq in "${(@s.:.)1}"; do + seq=${seq#(#b)(*)=} + (( $#match )) || continue + _zsh_highlight_highlighter_files_ansi_to_zle1 $seq $var\[$match[1]\] + done +} + +_zsh_highlight_highlighter_files_ansi_to_zle1() { + emulate -L zsh + setopt local_options extended_glob + + local -a sgrs match + local back mbegin mend fgbg hex + integer sgr arg arg2 col r g b + local str=$1 + + while [ -n "$str" ]; do + back=${str##(#b)([[:digit:]]##)} + back=${back#;} + (( $#match )) || return 1 + + sgr=$match; unset match + case $sgr in + 0) ;; + 1) sgrs+=(bold) ;; + 4) sgrs+=(underline) ;; + 7) sgrs+=(standout) ;; + <30-37>) sgrs+=(fg=$(( $sgr - 30 ))) ;; + <40-47>) sgrs+=(bg=$(( $sgr - 40 ))) ;; + 38|48) + (( sgr == 38 )) && fgbg=fg || fgbg=bg + # 38;5;n means paletted color + # 38;2;r;g;b means truecolor + back=${back##(#b)([[:digit:]]##)} + back=${back#;} + (( $#match )) || return 1 + arg=$match; unset match + case $arg in + 5) back=${back##(#b)([[:digit:]]##)} + back=${back#;} + (( $#match )) || return 1 + arg2=$match; unset match + sgrs+=($fgbg=$arg2) ;; + 2) back=${back##(#b)([[:digit:]]##);([[:digit:]]##);([[:digit:]]##)} + back=${back#;} + (( $#match == 3 )) || return 1 + printf -v hex \#%02X%02X%02X $match + unset match + sgrs+=($fgbg=$hex) ;; + *) return 1 ;; + esac ;; + *) return 1 ;; + esac + + str=$back + done + + if [[ -n "$2" ]]; then + eval $2='${(j.,.)sgrs}' + else + print -- ${(j.,.)sgrs} + fi +} + +# Extract ZSH_HIGHLIGHT_FILE_TYPES and ZSH_HIGHLIGHT_FILE_PATTERNS from LS_COLORS +zsh_highlight_files_extract_ls_colors() { + local -A ls_colors + _zsh_highlight_highlighter_files_ansi_to_zle ls_colors $LS_COLORS + for key val in ${(kv)ls_colors}; do + case $key in + di|fi|ln|pi|so|bd|cd|or|ex|su|sg|ow|tw) + ZSH_HIGHLIGHT_FILE_TYPES[$key]=$val ;; + *) ZSH_HIGHLIGHT_FILE_PATTERNS[$key]=$val ;; + esac + done +} + +# Perform simple filename expansion without globbing and without generating +# errors +_zsh_highlight_highlighter_files_fn_expand() { + local fn=$1 + local match expandable tail + local -a mbegin mend + if [[ $fn = (#b)(\~[^/]#)(*) ]]; then + expandable=$match[1] + tail=$match[2] + # Try expanding expandable + (: $~expandable) >&/dev/null && expandable=$~expandable + print -- $expandable$tail + else + print -- $fn + fi +} + +# Whether the highlighter should be called or not. +_zsh_highlight_highlighter_files_predicate() { + _zsh_highlight_buffer_modified +} + +# Syntax highlighting function. +_zsh_highlight_highlighter_files_paint() { + emulate -L zsh + setopt localoptions extended_glob + + zmodload -F zsh/stat b:zstat + + local buf=$BUFFER word basename word_subst col type mode + local -a words=(${(z)buf}) + local -A statdata + integer start=0 curword=0 numwords=$#words len end + + while (( curword++ < numwords )); do + col="" + word=$words[1] + words=("${(@)words:1}") + len=$#buf + buf="${buf/#[^$word[1]]#}" # strip whitespace + start+=$(( len - $#buf )) + end=$(( start + $#word )) + + word_subst=$(_zsh_highlight_highlighter_files_fn_expand $word) + + if ! zstat -H statdata -Ls -- "$word_subst" 2>/dev/null; then + start=$end + buf=${buf[$#word+1,-1]} + continue + fi + mode=$statdata[mode] + type=$mode[1] + basename=$word:t + [[ $word[-1] = '/' ]] && basename=$basename/ + + # Color by file type + case $type in + d) [[ ${mode[9,10]} = wt ]] \ + && col=$ZSH_HIGHLIGHT_FILE_TYPES[tw] \ + || col=$ZSH_HIGHLIGHT_FILE_TYPES[di];; + l) [[ -e "$word_subst" ]] \ + && col=$ZSH_HIGHLIGHT_FILE_TYPES[ln] \ + || col=$ZSH_HIGHLIGHT_FILE_TYPES[or];; + p) col=$ZSH_HIGHLIGHT_FILE_TYPES[pi];; + b) col=$ZSH_HIGHLIGHT_FILE_TYPES[bd];; + c) col=$ZSH_HIGHLIGHT_FILE_TYPES[cd];; + s) col=$ZSH_HIGHLIGHT_FILE_TYPES[so];; + esac + + # Regular file: more special cases + if [[ -z "$col" ]]; then + if [[ $mode[4] = s ]]; then + col=$ZSH_HIGHLIGHT_FILE_TYPES[su] # setuid root + elif [[ $mode[7] = s ]]; then + col=$ZSH_HIGHLIGHT_FILE_TYPES[sg] # setgid root + elif [[ $mode[4] = x ]]; then + col=$ZSH_HIGHLIGHT_FILE_TYPES[ex] # Executable + fi + fi + + # Regular file: check file patterns + if [[ -z "$col" ]]; then + for key val in ${(kv)ZSH_HIGHLIGHT_FILE_PATTERNS}; do + if [[ $basename = $~key ]]; then + col=$val + break + fi + done + fi + + # Just a regular file + if [[ -z "$col" ]]; then + col=$ZSH_HIGHLIGHT_FILE_TYPES[fi] + fi + + if [[ -n "$col" ]]; then + if (( end > start + $#basename && ${+ZSH_HIGHLIGHT_FILE_TYPES[lp]} )); then + region_highlight+=("$start $(( end - $#basename )) $ZSH_HIGHLIGHT_FILE_TYPES[lp]") + fi + region_highlight+=("$(( end - $#basename )) $end $col") + fi + + start=$end + buf=${buf[$#word+1,-1]} + done +} + diff --git a/highlighters/files/files.md b/highlighters/files/files.md new file mode 120000 index 0000000..0cd103f --- /dev/null +++ b/highlighters/files/files.md @@ -0,0 +1 @@ +../../docs/highlighters/files.md \ No newline at end of file