diff --git a/zsh-lint b/zsh-lint index 1130945..1ecface 100755 --- a/zsh-lint +++ b/zsh-lint @@ -7,11 +7,11 @@ local IFS=$' \n' # Run as script? if [[ "$0" != zsh-lint || -n "$ZSH_SCRIPT" ]]; then - # Handle $0 according to the Zsh Plugin Standard: - # http://z-shell.github.io/ZSH-TOP-100/Zsh-Plugin-Standard.html - 0="${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}" - 0="${${(M)0##/*}:-$PWD/$0}" - fpath+=( "$0:h" ) + # Handle $0 according to the Zsh Plugin Standard: + # http://z-shell.github.io/ZSH-TOP-100/Zsh-Plugin-Standard.html + 0="${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}" + 0="${${(M)0##/*}:-$PWD/$0}" + fpath+=( "$0:h" ) fi # Already autoloaded in *.plugin.zsh, here it's for Zshelldoc @@ -27,82 +27,90 @@ local -a ZSHLINT_DEBUG_MSGS /zsh-lint-dbg-array() { ZSHLINT_DEBUG_MSGS+=( "$@" ); } # Print-out the debug messages /zsh-lint-dbg-print-out() { - ZSHLINT_DEBUG_MSGS=( "${ZSHLINT_DEBUG_MSGS[@]//$(( ${#zshrc} - 5 ))}" ) - local -a lines - lines=( "${(f@)zshrc}" ) - ZSHLINT_DEBUG_MSGS=( "${ZSHLINT_DEBUG_MSGS[@]//${#lines}}" ) - { local ver="$(<${ZI[BIN_DIR]}/.git/refs/heads/main)" } 2>/dev/null - [[ -z "$ver" ]] && ver="unknown (no .git/refs/heads/main)" || ver="${ver[1,7]}" - ZSHLINT_DEBUG_MSGS=( "${ZSHLINT_DEBUG_MSGS[@]//$ver}" ) - { local ver="$(<$ZSHLINT_REPO_DIR/.git/refs/heads/main)" } 2>/dev/null - [[ -z "$ver" ]] && ver="unknown (no .git/refs/heads/main)" || ver="${ver[1,7]}" - ZSHLINT_DEBUG_MSGS=( "${ZSHLINT_DEBUG_MSGS[@]//$ver}" ) - print -rl -- "${ZSHLINT_DEBUG_MSGS[@]}"; + ZSHLINT_DEBUG_MSGS=( "${ZSHLINT_DEBUG_MSGS[@]//$(( ${#zshrc} - 5 ))}" ) + local -a lines + lines=( "${(f@)zshrc}" ) + ZSHLINT_DEBUG_MSGS=( "${ZSHLINT_DEBUG_MSGS[@]//${#lines}}" ) + + { local ver="$(<${ZI[BIN_DIR]}/.git/refs/heads/main)" } 2>/dev/null + [[ -z "$ver" ]] && ver="unknown (no .git/refs/heads/main)" || ver="${ver[1,7]}" + ZSHLINT_DEBUG_MSGS=( "${ZSHLINT_DEBUG_MSGS[@]//$ver}" ) + + { local ver="$(<$ZSHLINT_REPO_DIR/.git/refs/heads/main)" } 2>/dev/null + [[ -z "$ver" ]] && ver="unknown (no .git/refs/heads/main)" || ver="${ver[1,7]}" + ZSHLINT_DEBUG_MSGS=( "${ZSHLINT_DEBUG_MSGS[@]//$ver}" ) + print -rl -- "${ZSHLINT_DEBUG_MSGS[@]}"; } local -a ice_order nval_ices ice_order=( - wait lucid silent service svn proto from teleid as id-as depth cloneopts - ver has if load unload blockf pick bpick src notify mv cp atinit atclone - atload atpull make run-atpull nocd cloneonly trackbinds bindmap multisrc - compile nocompile nocompletions reset-prompt - # Include all additional ices – after stripping them from the possible: '' - ${(@s.|.)${ZI_EXTS[ice-mods]//\'\'/}} + wait lucid silent service svn proto from teleid as id-as depth cloneopts + ver has if load unload blockf pick bpick src notify mv cp atinit atclone + atload atpull make run-atpull nocd cloneonly trackbinds bindmap multisrc + compile nocompile nocompletions reset-prompt + # Include all additional ices – after + # stripping them from the possible: '' + ${(@s.|.)${ZINIT_EXTS[ice-mods]//\'\'/}} ) nval_ices=( - blockf silent lucid trackbinds cloneonly nocd run-atpull - nocompletions svn - # Include only those additional ices, don't have the '' in their name, i.e. aren't designed to hold value - ${(@)${(@s.|.)ZI_EXTS[ice-mods]}:#*\'\'*} + blockf silent lucid trackbinds cloneonly nocd run-atpull + nocompletions svn + # Include only those additional ices, + # don't have the '' in their name, i.e. + # aren't designed to hold value + ${(@)${(@s.|.)ZINIT_EXTS[ice-mods]}:#*\'\'*} ) # Initial debug message /zsh-lint-dbg "ZSH version: $ZSH_VERSION ($ZSH_PATCHLEVEL), machine: $CPUTYPE $VENDOR $OSTYPE, LANG: $LANG" if [[ ${+ZI} = 1 ]]; then - /zsh-lint-dbg "ZI version: , declare-zshrc version: " - /zsh-lint-dbg "ZI BIN_DIR: ${ZI[BIN_DIR]/$HOME/~}, HOME_DIR: ${ZI[HOME_DIR]/$HOME/~}, PLUGINS_DIR: ${ZI[PLUGINS_DIR]/$HOME/~}, COMPLETIONS_DIR: ${ZI[COMPLETIONS_DIR]/$HOME/~}" + /zsh-lint-dbg "ZI version: , declare-zshrc version: " + /zsh-lint-dbg "ZI BIN_DIR: ${ZI[BIN_DIR]/$HOME/~}, HOME_DIR: ${ZI[HOME_DIR]/$HOME/~}, PLUGINS_DIR: ${ZI[PLUGINS_DIR]/$HOME/~}, COMPLETIONS_DIR: ${ZI[COMPLETIONS_DIR]/$HOME/~}" else - /zsh-lint-dbg "${ZUI[RED]}ZI not loaded${ZUI[FMT_END]}" + /zsh-lint-dbg "${ZUI[RED]}ZI not loaded${ZUI[FMT_END]}" fi /zsh-lint-dbg "" -/zsh-lint-dbg-array "Active plugins:" "${ZI_REGISTERED_PLUGINS[@]/(#s)/ }" +/zsh-lint-dbg-array "Active plugins:" "${ZINIT_REGISTERED_PLUGINS[@]/(#s)/ }" /zsh-lint-dbg "" +zsh-lint-error() { - [[ -n "$OPT_QUIET" && "$1" != \!* ]] && return 0 - local opt="${(M)1:#(-r|-rl|-l|-rnl|-rln|-n|-nl|-rn|-nr)}" - [[ -n "$opt" ]] && shift - print -r $opt -- "$fg[red]ERROR$reset_color:" "$@" + [[ -n "$OPT_QUIET" && "$1" != \!* ]] && return 0 + local opt="${(M)1:#(-r|-rl|-l|-rnl|-rln|-n|-nl|-rn|-nr)}" + [[ -n "$opt" ]] && shift + print -r $opt -- "$fg[red]ERROR$reset_color:" "$@" } +zsh-lint-warn() { - [[ -n "$OPT_QUIET" && "$1" != \!* ]] && return 0 - local opt="${(M)1:#(-r|-rl|-l|-rnl|-rln|-n|-nl|-rn|-nr)}" - [[ -n "$opt" ]] && shift - print -r $opt -- "$fg[red]WARNING$reset_color:" "$@" + [[ -n "$OPT_QUIET" && "$1" != \!* ]] && return 0 + local opt="${(M)1:#(-r|-rl|-l|-rnl|-rln|-n|-nl|-rn|-nr)}" + [[ -n "$opt" ]] && shift + print -r $opt -- "$fg[red]WARNING$reset_color:" "$@" } +zsh-lint-info() { - [[ -n "$OPT_QUIET" && "$1" != \!* ]] && return 0 - local opt="${(M)1:#(-r|-rl|-l|-rnl|-rln|-n|-nl|-rn|-nr)}" - [[ -n "$opt" ]] && shift - print -r $opt -- ${fg[yellow]}${${(M)1#-}:+INFO: }"${1#-}" "${@[2,-1]}"$reset_color + [[ -n "$OPT_QUIET" && "$1" != \!* ]] && return 0 + local opt="${(M)1:#(-r|-rl|-l|-rnl|-rln|-n|-nl|-rn|-nr)}" + [[ -n "$opt" ]] && shift + print -r $opt -- ${fg[yellow]}${${(M)1#-}:+INFO: }"${1#-}" "${@[2,-1]}"$reset_color } +zsh-lint-msg() { - [[ -n "$OPT_QUIET" && "$1" != \!* ]] && return 0 - local opt="${(M)1:#(-r|-rl|-l|-rnl|-rln|-n|-nl|-rn|-nr)}" - [[ -n "$opt" ]] && shift - local -A map - map=( - RE "${fg[red]:-x}" GR "${fg[green]:-x}" - YE "${fg[yellow]:-x}" BL "${fg[blue]:-x}" - CY "${fg[cyan]:-x}" MA "${fg[magenta]:-x}" - RS "${reset_color:-x}" - ) - print -r $opt -- "${@//(#m)[A-Z][A-Z]/${${map[$MATCH]:-$MATCH}:#x}}"$reset_color + [[ -n "$OPT_QUIET" && "$1" != \!* ]] && return 0 + + local opt="${(M)1:#(-r|-rl|-l|-rnl|-rln|-n|-nl|-rn|-nr)}" + [[ -n "$opt" ]] && shift + + local -A map + map=( + RE "${fg[red]:-x}" GR "${fg[green]:-x}" + YE "${fg[yellow]:-x}" BL "${fg[blue]:-x}" + CY "${fg[cyan]:-x}" MA "${fg[magenta]:-x}" + RS "${reset_color:-x}" + ) + + print -r $opt -- "${@//(#m)[A-Z][A-Z]/${${map[$MATCH]:-$MATCH}:#x}}"$reset_color } # @@ -112,15 +120,19 @@ fi local -A TOKEN_TYPES TOKEN_TYPES=( + # Precommand + 'builtin' 1 'command' 1 'exec' 1 'nocorrect' 1 'noglob' 1 'pkexec' 1 + # Control flow # Tokens that at "command position" are followed by a command + $'\x7b' 2 # { $'\x28' 2 # ( '()' 2 @@ -134,10 +146,13 @@ TOKEN_TYPES=( 'time' 2 'coproc' 2 '!' 2 + # Command separators + '|' 3 '||' 3 '&&' 3 + '|&' 4 '&!' 4 '&|' 4 @@ -149,11 +164,21 @@ TOKEN_TYPES=( # Parse options # -local -a OPT_HELP OPT_VERBOSE OPT_QUIET OPT_NOANSI OPT_DISABLE OPT_ENABLE OPT_TOGGLE OPT_ADD OPT_ADD_FLAG OPT_PURGE OPT_OUT +local -a OPT_HELP OPT_VERBOSE OPT_QUIET OPT_NOANSI \ + OPT_DISABLE OPT_ENABLE OPT_TOGGLE OPT_ADD \ + OPT_ADD_FLAG OPT_PURGE OPT_OUT local -A opthash -zparseopts -E -D -A opthash h=OPT_HELP -help=OPT_HELP v=OPT_VERBOSE -verbose=OPT_VERBOSE q=OPT_QUIET -quiet=OPT_QUIET \ -n=OPT_NOANSI -noansi=OPT_NOANSI o:=OPT_OUT -out:=OPT_OUT DD+:=OPT_DISABLE -zp-disable+:=OPT_DISABLE EE+:=OPT_ENABLE -zp-enable+:=OPT_ENABLE \ -TT+:=OPT_TOGGLE -zp-toggle+:=OPT_TOGGLE AA+:=OPT_ADD -zp-add+=OPT_ADD_FLAG PP+:=OPT_PURGE -zp-purge+:=OPT_PURGE || { echo "Improper options given, see help (-h/--help)"; return 1; } +zparseopts -E -D -A opthash h=OPT_HELP -help=OPT_HELP \ + v=OPT_VERBOSE -verbose=OPT_VERBOSE \ + q=OPT_QUIET -quiet=OPT_QUIET \ + n=OPT_NOANSI -noansi=OPT_NOANSI \ + o:=OPT_OUT -out:=OPT_OUT \ + DD+:=OPT_DISABLE -zp-disable+:=OPT_DISABLE \ + EE+:=OPT_ENABLE -zp-enable+:=OPT_ENABLE \ + TT+:=OPT_TOGGLE -zp-toggle+:=OPT_TOGGLE \ + AA+:=OPT_ADD -zp-add+=OPT_ADD_FLAG \ + PP+:=OPT_PURGE -zp-purge+:=OPT_PURGE || \ + { echo "Improper options given, see help (-h/--help)"; return 1; } integer i l local -a mbegin mend match @@ -184,7 +209,7 @@ zshrc_path=${~zshrc_in_path} local token prev_token spaces prev_spaces next_token next_spaces # Command detection -integer at_command=1 in_zi=0 +integer at_command=1 in_zinit=0 # Functions local fun_name @@ -202,7 +227,7 @@ local -A features call_tree rev_call_tree funs local -a known_functions sourced_files # Text and commands data -integer was_zi=0 pre_block_end=0 post_block_begin=0 +integer was_zinit=0 pre_block_end=0 post_block_begin=0 local -A cmd3 local -a cmdlist integer coidx=1 @@ -244,8 +269,9 @@ local -A theme # exists in other projects, is an autoload function, and is kept # in separate file "@zsh-lint-process-buffer". .zsh-lint-tokenize-zsh-rc() { - @zsh-lint-process-buffer "$zshrc" 1 -} # }}} + @zsh-lint-process-buffer "$zshrc" 1 +} +# }}} # FUNCTION: .zsh-lint_verify_tokenization {{{ # To large extent verifies if tokenization was correct. # Also removes the test-tokens added to input zshrc (this @@ -255,42 +281,47 @@ local -A theme # existence in the structures confirms correct parsing # of the preceding text, i.e. the whole zshrc). .zsh-lint_verify_tokenization() { - local lasta="${ZSHLINT_PB_WORDS[-1]}" lastb="${ZSHLINT_PB_ALL[-2]}" - if [[ "$lasta" != "test" || "$lastb" != "test" ]]; then - return 1 - fi - ZSHLINT_PB_WORDS[-1]=() - ZSHLINT_PB_SPACES[-1]=() - ZSHLINT_PB_SPACES[-1]="${ZSHLINT_PB_SPACES[-1]%$'\n'}" - ZSHLINT_PB_ALL[-2,-1]=() - ZSHLINT_PB_ALL[-1]="${ZSHLINT_PB_ALL[-1]%$'\n'}" - return 0 -} # }}} + local lasta="${ZSHLINT_PB_WORDS[-1]}" lastb="${ZSHLINT_PB_ALL[-2]}" + + if [[ "$lasta" != "test" || "$lastb" != "test" ]]; then + return 1 + fi + + ZSHLINT_PB_WORDS[-1]=() + ZSHLINT_PB_SPACES[-1]=() + ZSHLINT_PB_SPACES[-1]="${ZSHLINT_PB_SPACES[-1]%$'\n'}" + ZSHLINT_PB_ALL[-2,-1]=() + ZSHLINT_PB_ALL[-1]="${ZSHLINT_PB_ALL[-1]%$'\n'}" + + return 0 +} +# }}} # FUNCTION: .zsh-lint_tokenization_failed {{{ # Outputs a message that zshrc didn't parse, and # includes information what can be a possible cause. .zsh-lint_tokenization_failed() { - +zsh-lint-error "Failed to parse zshrc. Possible causes:" - +zsh-lint-info "" - +zsh-lint-info "1. Zsh <= 5.4.2 doesn't parse closing parenthesis ')' for '\$('" - +zsh-lint-info " if it is at other line, not at the same line as '\$('. So:" - +zsh-lint-info "" - +zsh-lint-info " \$(ls -1 | perl -alne 'echo foo')" - +zsh-lint-info "" - +zsh-lint-info " will parse correctly, while:" - +zsh-lint-info "" - +zsh-lint-info " \$(ls -1 | perl -alne 'echo foo'" - +zsh-lint-info " )" - +zsh-lint-info "" - +zsh-lint-info " will not. A more sophisticated not-parsing example:" - +zsh-lint-info "" - +zsh-lint-info " asmcmds+=(\${(o)\$(ls -1 | perl -alne 'echo foo'" - +zsh-lint-info " )})" - +zsh-lint-info "" - +zsh-lint-info "2. A regular syntax error may exist, try: zcompile .zshrc, or" - +zsh-lint-info " start a zsh session and see if there are error messages." - +zsh-lint-info "" -} # }}} + +zsh-lint-error "Failed to parse zshrc. Possible causes:" + +zsh-lint-info "" + +zsh-lint-info "1. Zsh <= 5.4.2 doesn't parse closing parenthesis ')' for '\$('" + +zsh-lint-info " if it is at other line, not at the same line as '\$('. So:" + +zsh-lint-info "" + +zsh-lint-info " \$(ls -1 | perl -alne 'echo foo')" + +zsh-lint-info "" + +zsh-lint-info " will parse correctly, while:" + +zsh-lint-info "" + +zsh-lint-info " \$(ls -1 | perl -alne 'echo foo'" + +zsh-lint-info " )" + +zsh-lint-info "" + +zsh-lint-info " will not. A more sophisticated not-parsing example:" + +zsh-lint-info "" + +zsh-lint-info " asmcmds+=(\${(o)\$(ls -1 | perl -alne 'echo foo'" + +zsh-lint-info " )})" + +zsh-lint-info "" + +zsh-lint-info "2. A regular syntax error may exist, try: zcompile .zshrc, or" + +zsh-lint-info " start a zsh session and see if there are error messages." + +zsh-lint-info "" +} +# }}} # FUNCTION: .zsh-lint-process-zsh-rc {{{ # Parses tokens of the loaded zshrc and detects: # - functions @@ -302,660 +333,757 @@ local -A theme # - $ZSHLINT_PB_WORDS - tokens # - $ZSHLINT_PB_SPACES - spaces in front of each token, +1 at the end .zsh-lint-process-zsh-rc() { - integer i j size="${#ZSHLINT_PB_WORDS}" - for (( i=1; i<=size; ++ i )); do - token="${ZSHLINT_PB_WORDS[i]}" - spaces="${ZSHLINT_PB_SPACES[i]}" - next_token="${ZSHLINT_PB_WORDS[i+1]}" - next_spaces="${ZSHLINT_PB_SPACES[i+1]}" - cur_fun=0 prev_fun=0 descentff=0 descentfa=0 - nested_fun=0 prev_nested_fun=0 - (( next_fun )) && { next_fun=0 cur_fun=1 prev_fun=0 anon_depth=-1; } - (( next_nested_fun )) && { next_nested_fun=0 nested_fun=1 prev_nested_fun=0; } - # Explicit future-function - if [[ "$token" = "function"(|$'\r') && ( "$fun_depth" -lt 0 ) && ( $anon_depth -lt 0 ) ]]; then - next_fun=1 cur_fun=0 prev_fun=0 anon_depth=-1 - # Detect top-level prev-function differentiating from anonymous function - elif [[ "$token" = "()"(|$'\r') && ( "$fun_depth" -lt 0 ) && ( $anon_depth -lt 0 ) ]]; then - if [[ "$spaces" = *$'\n'* || -z "$prev_token" || "${TOKEN_TYPES[$prev_token]}" = [1234] ]]; then - next_fun=0 cur_fun=0 prev_fun=0 anon_depth=$depth - else - next_fun=0 cur_fun=0 prev_fun=1 anon_depth=-1 - fi - # Must be a nested future-function - elif [[ "$token" = "function"(|$'\r') ]]; then - next_nested_fun=1 nested_fun=0 prev_nested_fun=0 - # Is it a nested prev-function? - elif [[ "$token" = "()"(|$'\r') && "$nested_fun" -eq 0 && "$depth" -gt "$fun_stack_depths[-1]" ]]; then - if [[ "$spaces" != *$'\n'* && -n "$prev_token" && "${TOKEN_TYPES[$prev_token]}" != [1234] ]]; then - next_nested_fun=0 nested_fun=0 prev_nested_fun=1 - fi - elif [[ "$token" = "{"(|$'\r') ]]; then - (( ++ depth )) - elif [[ "$token" = "}"(|$'\r') ]]; then - (( -- depth )) - fi - # Check if any final function-flag is raised - if (( cur_fun )); then - fun_name="${token%$'\r'}" - fun_depth="$depth" - fun_stack_depths+=( "$depth" ) - elif (( prev_fun )); then - fun_name="${prev_token%$'\r'}" - fun_depth="$depth" - fun_stack_depths+=( "$depth" ) - fi - # Track nested functions - if (( nested_fun + prev_nested_fun )); then - fun_stack_depths+=( "$depth" ) - fi - # Ascent to function - skip '{' - if (( fun_depth >= 0 && depth == (fun_depth + 1) )) && [[ "$token" = "{"(|$'\r') ]]; then - : - # In function - elif (( fun_depth >= 0 && depth > fun_depth )); then - if [[ "$token" != [[:blank:]]#\#* ]]; then - # : # do something with a non-comment function token - funs[$fun_name]+="${spaces}${token}" - fi - # Handle descent from nested function - if (( ${#fun_stack_depths} > 0 && depth == fun_stack_depths[-1] && prev_depth == fun_stack_depths[-1] + 1 )); then - fun_stack_depths[-1]=() - fi - # In anonymous-function - elif (( anon_depth >= 0 && depth > anon_depth )); then - if (( ${#fun_stack_depths} > 0 && depth == fun_stack_depths[-1] && prev_depth == fun_stack_depths[-1] + 1 )); then - fun_stack_depths[-1]=() - fi - # Descent from function - skip '}' - elif (( fun_depth >= 0 && depth == fun_depth && prev_depth == fun_depth + 1 )); then - descentff=1 - # Descent from anon - elif (( anon_depth >= 0 && depth == anon_depth && prev_depth == anon_depth + 1 )); then - descentfa=1 - fi - # Anon function in top-level - if (( anon_depth >= 0 && fun_depth < 0 )); then - [[ "$token" != [[:blank:]]#\#* ]] && preamble+="${spaces}${token}" - fi - ### Detect function call - # Check for introduction of ZI call - if [[ "$spaces" = *$'\n'* || -z "$prev_token" || "${TOKEN_TYPES[$prev_token]}" = [1234] ]]; then - if [[ "$spaces" != [[:blank:]]#"\\"(|$'\r')$'\n'[[:blank:]]# ]]; then - at_command=1 - [[ "$token" != [[:blank:]]#\#* ]] && in_zi=0 || { (( in_zi )) && in_zi=1; } - fi - (( in_zi )) && { - (( 2*i - 1 - post_block_begin > 7*4 )) && { /zsh-lint-dbg "WARNING: many non-ZI commands in ZI block"; } - # A new command (i.e. $spaces has a new line, etc.) - move the post-block pointer - post_block_begin=2*i-1 - } - fi - # Command token - if (( at_command )); then - at_command=0 - # ZI call not in function - if [[ "$cur_fun" -eq 0 && "$next_token" != "()"(|$'\r') && "$fun_stack_depths[-1]" -le "0" ]]; then - if [[ "$token" = (zi|zpl) || ( "$token" = ":" && "$next_token" = (zi|zpl) ) ]]; then - in_zi=1 - (( was_zi == 0 )) && { - # Initially point the pre-block at the previous token - # (the result will be zero if zi command is first) - pre_block_end=2*(i-1) - for (( j=i-1; j >= 1; j -- )); do - # Include also some preceding comments - [[ "${ZSHLINT_PB_WORDS[j]}" != [[:blank:]]#\#* || "${ZSHLINT_PB_SPACES[j+1]}" = *$'\n'*$'\n'* ]] && { pre_block_end=2*j; break; } - done - } - was_zi=1 - # Revive at_command mode if disabled zi command - [[ "$token" = ":" ]] && at_command=1 + integer i j size="${#ZSHLINT_PB_WORDS}" + + for (( i=1; i<=size; ++ i )); do + token="${ZSHLINT_PB_WORDS[i]}" + spaces="${ZSHLINT_PB_SPACES[i]}" + next_token="${ZSHLINT_PB_WORDS[i+1]}" + next_spaces="${ZSHLINT_PB_SPACES[i+1]}" + + cur_fun=0 prev_fun=0 descentff=0 descentfa=0 + nested_fun=0 prev_nested_fun=0 + + (( next_fun )) && { next_fun=0 cur_fun=1 prev_fun=0 anon_depth=-1; } + (( next_nested_fun )) && { next_nested_fun=0 nested_fun=1 prev_nested_fun=0; } + + # Explicit future-function + if [[ "$token" = "function"(|$'\r') && ( "$fun_depth" -lt 0 ) && ( $anon_depth -lt 0 ) ]]; then + next_fun=1 cur_fun=0 prev_fun=0 anon_depth=-1 + # Detect top-level prev-function differentiating from anonymous function + elif [[ "$token" = "()"(|$'\r') && ( "$fun_depth" -lt 0 ) && ( $anon_depth -lt 0 ) ]]; then + if [[ "$spaces" = *$'\n'* || -z "$prev_token" || "${TOKEN_TYPES[$prev_token]}" = [1234] ]]; then + next_fun=0 cur_fun=0 prev_fun=0 anon_depth=$depth + else + next_fun=0 cur_fun=0 prev_fun=1 anon_depth=-1 + fi + # Must be a nested future-function + elif [[ "$token" = "function"(|$'\r') ]]; then + next_nested_fun=1 nested_fun=0 prev_nested_fun=0 + # Is it a nested prev-function? + elif [[ "$token" = "()"(|$'\r') && "$nested_fun" -eq 0 && "$depth" -gt "$fun_stack_depths[-1]" ]]; then + if [[ "$spaces" != *$'\n'* && -n "$prev_token" && "${TOKEN_TYPES[$prev_token]}" != [1234] ]]; then + next_nested_fun=0 nested_fun=0 prev_nested_fun=1 + fi + elif [[ "$token" = "{"(|$'\r') ]]; then + (( ++ depth )) + elif [[ "$token" = "}"(|$'\r') ]]; then + (( -- depth )) fi - fi - # Prepare call-tree extraction - # Search for this possible function ($token) in current script - local tokenEx="${(q)name}/${(q)token}" - local found="${known_functions[(r)$tokenEx]}" candidate="" last_candidate="" - integer nth=1 - if [[ -z "$found" ]]; then - # Search for other scripts having this possible function - tokenEx="*/${(q)token}" - while (( 1 )); do - candidate="${known_functions[(rn:nth:)$tokenEx]}" - if [[ -n "$candidate" ]]; then - last_candidate="$candidate" - found="${sourced_files[(r)*${candidate:h}*]}" - [[ -n "$found" ]] && break - else - break - fi - (( ++ nth )) - done - found="$last_candidate" - fi - if [[ -z "$fun_name" ]]; then - local needle="${(q)name}/zsd_script_body" - else - local needle="${(q)name}/${(q)fun_name}" - fi - # Extract call-tree and reversed call-tree, and also features - if [[ "$cur_fun" -eq 0 && "$next_token" != "()" && -n "$found" && "$fun_stack_depths[-1]" -le "0" ]]; then - if [[ -z "$fun_name" ]]; then - [[ "${call_tree[${(q)name}/zsd_script_body]}" != *[[:blank:]]"${(q)found}"[[:blank:]]* ]] && { - call_tree[${(q)name}/zsd_script_body]+=" ${(q)found} " - } - [[ "${rev_call_tree[${(q)found}]}" != *[[:blank:]]"$needle"[[:blank:]]* ]] && { - rev_call_tree[${(q)found}]+=" ${(q)name}/zsd_script_body " - } - else - [[ "${call_tree[${(q)name}/${(q)fun_name}]}" != *[[:blank:]]"${(q)found}"[[:blank:]]* ]] && { - call_tree[${(q)name}/${(q)fun_name}]+=" ${(q)found} " - } - [[ ${rev_call_tree[${(q)found}]} != *[[:blank:]]"$needle"[[:blank:]]* ]] && { - rev_call_tree[${(q)found}]+=" ${(q)name}/${(q)fun_name} " - } + + # Check if any final function-flag is raised + if (( cur_fun )); then + fun_name="${token%$'\r'}" + fun_depth="$depth" + fun_stack_depths+=( "$depth" ) + elif (( prev_fun )); then + fun_name="${prev_token%$'\r'}" + fun_depth="$depth" + fun_stack_depths+=( "$depth" ) fi - fi - if [[ "$cur_fun" -eq 0 && "$next_token" != "()" && "$fun_stack_depths[-1]" -le "0" ]]; then - # Features - if [[ "$token" = ${(~j:|:)feature_list} ]]; then - [[ -z "$fun_name" ]] && local fkey="zsd_script_body" || local fkey="$fun_name" - [[ "${features[$fkey]}" != *[[:blank:]]"$token"[[:blank:]]* ]] && features[$fkey]+=" $token " - [[ "$token" = "source" ]] && sourced_files+=( "$next_token" ) + + # Track nested functions + if (( nested_fun + prev_nested_fun )); then + fun_stack_depths+=( "$depth" ) fi - fi - fi - # Late disable of anonymous function - if (( descentfa )); then - anon_depth=-1 - # Late disable of normal function - elif (( descentff )); then - fun_name="" - fun_depth=-1 - fun_stack_depths[-1]=() - # No-function text gathering - elif (( next_fun == 0 && cur_fun == 0 && prev_fun == 0 && anon_depth < 0 && fun_depth < 0 )); then - if [[ "$next_token" != "()"(|$'\r') || "$next_spaces" = *$'\n'* || "${TOKEN_TYPES[$token]}" = [34] ]]; then - [[ "$token" != [[:blank:]]#\#* ]] && : # do something with script-body token - fi - fi - # History of state - prev_depth="$depth" - prev_token="$token" - prev_spaces="$spaces" - done - # If ZI command ended the zshrc, detect it here - (( in_zi )) && post_block_begin=2*i-1 - if (( pre_block_end == 0 )); then - # No zi commands -> pre-block ends at - # the last token in the file - (( post_block_begin == 0 )) && pre_block_end=2*(i-1) - else - j=0+(pre_block_end/2) - # Soft command-separator? - if [[ "${TOKEN_TYPES[${ZSHLINT_PB_WORDS[j]}]}" = "3" ]]; then - integer found=0 - for (( j=(pre_block_end/2)-1; j>=1; -- j )); do - token="${ZSHLINT_PB_WORDS[j]}" - spaces="${ZSHLINT_PB_SPACES[j]}" - next_token="${ZSHLINT_PB_WORDS[j+1]}" - next_spaces="${ZSHLINT_PB_SPACES[j+1]}" - (( j >= 2 )) && prev_token="${ZSHLINT_PB_WORDS[j-1]}" || prev_token="" - if [[ "${TOKEN_TYPES[$token]}" = "4" ]]; then - found=1 - pre_block_end=2*j - break - # Soft command separator can have spaces after it - elif [[ "$spaces" = *$'\n'* && "${TOKEN_TYPES[$prev_token]}" != "3" ]]; then - pre_block_end=2*(j-1) - found=1 - break + + # Ascent to function - skip '{' + if (( fun_depth >= 0 && depth == (fun_depth + 1) )) && [[ "$token" = "{"(|$'\r') ]]; then + : + # In function + elif (( fun_depth >= 0 && depth > fun_depth )); then + if [[ "$token" != [[:blank:]]#\#* ]]; then + # : # do something with a non-comment function token + funs[$fun_name]+="${spaces}${token}" + fi + # Handle descent from nested function + if (( ${#fun_stack_depths} > 0 && depth == fun_stack_depths[-1] && prev_depth == fun_stack_depths[-1] + 1 )); then + fun_stack_depths[-1]=() + fi + # In anonymous-function + elif (( anon_depth >= 0 && depth > anon_depth )); then + if (( ${#fun_stack_depths} > 0 && depth == fun_stack_depths[-1] && prev_depth == fun_stack_depths[-1] + 1 )); then + fun_stack_depths[-1]=() + fi + # Descent from function - skip '}' + elif (( fun_depth >= 0 && depth == fun_depth && prev_depth == fun_depth + 1 )); then + descentff=1 + # Descent from anon + elif (( anon_depth >= 0 && depth == anon_depth && prev_depth == anon_depth + 1 )); then + descentfa=1 fi - done - # Didn't found, and therefore reached beginning of file - (( found == 0 )) && pre_block_end=0 - fi - fi - if (( post_block_begin == 0 )); then - post_block_begin=2*i-1 - else - # Detect not related trailing comments - for (( j=(post_block_begin+1)/2-1; j >= 1; -- j )); do - [[ "${ZSHLINT_PB_WORDS[j]}" = [[:blank:]]#\#* && "${ZSHLINT_PB_WORDS[j]}" != [[:blank:]]#\#[[:blank:]]#zi* ]] && { - post_block_begin=2*j-1 - } || break - done - # Detect if compinit follows - integer count=$(( size-(post_block_begin+1)/2 )) - local next_token_2 - (( count = (count > 15) ? 15 : count )) - for (( j=(post_block_begin+1)/2; count > 0; ++ j, -- count )); do - token="${ZSHLINT_PB_WORDS[j]}" - next_token="${ZSHLINT_PB_WORDS[j+1]}" - next_token_2="${ZSHLINT_PB_WORDS[j+2]}" - next_spaces="${ZSHLINT_PB_SPACES[j+1]}" - if [[ "$token" = "autoload" && ( "$next_token" = "compinit" || "$next_token_2" = "compinit" ) ]]; then - [[ "$next_token" = "compinit" ]] && { post_block_begin=2*(j+2)-1; j+=1; count=count-1; } - [[ "$next_token_2" = "compinit" ]] && { post_block_begin=2*(j+3)-1; j+=2; count=count-2; } - elif [[ "$token" = "compinit" && ( "$next_spaces" = *$'\n'* || "${TOKEN_TYPES[$next_token]}" = 4 ) ]]; then - # TODO: soft-connected following tokens can be also included - post_block_begin=2*(j+1)-1 - break - fi + + # Anon function in top-level + if (( anon_depth >= 0 && fun_depth < 0 )); then + [[ "$token" != [[:blank:]]#\#* ]] && preamble+="${spaces}${token}" + fi + + ### Detect function call + # Check for introduction of ZI call + if [[ "$spaces" = *$'\n'* || -z "$prev_token" || "${TOKEN_TYPES[$prev_token]}" = [1234] ]]; then + if [[ "$spaces" != [[:blank:]]#"\\"(|$'\r')$'\n'[[:blank:]]# ]]; then + at_command=1 + [[ "$token" != [[:blank:]]#\#* ]] && in_zinit=0 || { (( in_zinit )) && in_zinit=1; } + fi + (( in_zinit )) && { + (( 2*i - 1 - post_block_begin > 7*4 )) && { /zsh-lint-dbg "WARNING: many non-ZI commands in ZI block"; } + # A new command (i.e. $spaces has a new line, etc.) - move the post-block pointer + post_block_begin=2*i-1 + } + fi + + # Command token + if (( at_command )); then + at_command=0 + # ZI call not in function + if [[ "$cur_fun" -eq 0 && "$next_token" != "()"(|$'\r') && "$fun_stack_depths[-1]" -le "0" ]]; then + if [[ "$token" = (zi|zinit|zpl) || ( "$token" = ":" && "$next_token" = (zi|zinit|zpl) ) ]]; then + in_zinit=1 + (( was_zinit == 0 )) && { + # Initially point the pre-block at the previous token + # (the result will be zero if zi command is first) + pre_block_end=2*(i-1) + for (( j=i-1; j >= 1; j -- )); do + # Include also some preceding comments + [[ "${ZSHLINT_PB_WORDS[j]}" != [[:blank:]]#\#* || "${ZSHLINT_PB_SPACES[j+1]}" = *$'\n'*$'\n'* ]] && { pre_block_end=2*j; break; } + done + } + was_zinit=1 + + # Revive at_command mode if disabled zi command + [[ "$token" = ":" ]] && at_command=1 + fi + fi + + # Prepare call-tree extraction + # Search for this possible function ($token) in current script + local tokenEx="${(q)name}/${(q)token}" + local found="${known_functions[(r)$tokenEx]}" candidate="" last_candidate="" + integer nth=1 + if [[ -z "$found" ]]; then + # Search for other scripts having this possible function + tokenEx="*/${(q)token}" + while (( 1 )); do + candidate="${known_functions[(rn:nth:)$tokenEx]}" + if [[ -n "$candidate" ]]; then + last_candidate="$candidate" + found="${sourced_files[(r)*${candidate:h}*]}" + [[ -n "$found" ]] && break + else + break + fi + (( ++ nth )) + done + found="$last_candidate" + fi + if [[ -z "$fun_name" ]]; then + local needle="${(q)name}/zsd_script_body" + else + local needle="${(q)name}/${(q)fun_name}" + fi + + # Extract call-tree and reversed call-tree, and also features + if [[ "$cur_fun" -eq 0 && "$next_token" != "()" && -n "$found" && "$fun_stack_depths[-1]" -le "0" ]]; then + if [[ -z "$fun_name" ]]; then + [[ "${call_tree[${(q)name}/zsd_script_body]}" != *[[:blank:]]"${(q)found}"[[:blank:]]* ]] && { + call_tree[${(q)name}/zsd_script_body]+=" ${(q)found} " + } + [[ "${rev_call_tree[${(q)found}]}" != *[[:blank:]]"$needle"[[:blank:]]* ]] && { + rev_call_tree[${(q)found}]+=" ${(q)name}/zsd_script_body " + } + else + [[ "${call_tree[${(q)name}/${(q)fun_name}]}" != *[[:blank:]]"${(q)found}"[[:blank:]]* ]] && { + call_tree[${(q)name}/${(q)fun_name}]+=" ${(q)found} " + } + [[ ${rev_call_tree[${(q)found}]} != *[[:blank:]]"$needle"[[:blank:]]* ]] && { + rev_call_tree[${(q)found}]+=" ${(q)name}/${(q)fun_name} " + } + fi + fi + + if [[ "$cur_fun" -eq 0 && "$next_token" != "()" && "$fun_stack_depths[-1]" -le "0" ]]; then + # Features + if [[ "$token" = ${(~j:|:)feature_list} ]]; then + [[ -z "$fun_name" ]] && local fkey="zsd_script_body" || local fkey="$fun_name" + [[ "${features[$fkey]}" != *[[:blank:]]"$token"[[:blank:]]* ]] && features[$fkey]+=" $token " + [[ "$token" = "source" ]] && sourced_files+=( "$next_token" ) + fi + fi + fi + + # Late disable of anonymous function + if (( descentfa )); then + anon_depth=-1 + # Late disable of normal function + elif (( descentff )); then + fun_name="" + fun_depth=-1 + fun_stack_depths[-1]=() + # No-function text gathering + elif (( next_fun == 0 && cur_fun == 0 && prev_fun == 0 && anon_depth < 0 && fun_depth < 0 )); then + if [[ "$next_token" != "()"(|$'\r') || "$next_spaces" = *$'\n'* || "${TOKEN_TYPES[$token]}" = [34] ]]; then + [[ "$token" != [[:blank:]]#\#* ]] && : # do something with script-body token + fi + fi + + # History of state + prev_depth="$depth" + prev_token="$token" + prev_spaces="$spaces" done - fi + + # If zi command ended the zshrc, detect it here + (( in_zinit )) && post_block_begin=2*i-1 + + if (( pre_block_end == 0 )); then + # No zi commands -> pre-block ends at + # the last token in the file + (( post_block_begin == 0 )) && pre_block_end=2*(i-1) + else + j=0+(pre_block_end/2) + + # Soft command-separator? + if [[ "${TOKEN_TYPES[${ZSHLINT_PB_WORDS[j]}]}" = "3" ]]; then + integer found=0 + for (( j=(pre_block_end/2)-1; j>=1; -- j )); do + token="${ZSHLINT_PB_WORDS[j]}" + spaces="${ZSHLINT_PB_SPACES[j]}" + next_token="${ZSHLINT_PB_WORDS[j+1]}" + next_spaces="${ZSHLINT_PB_SPACES[j+1]}" + (( j >= 2 )) && prev_token="${ZSHLINT_PB_WORDS[j-1]}" || prev_token="" + + if [[ "${TOKEN_TYPES[$token]}" = "4" ]]; then + found=1 + pre_block_end=2*j + break + # Soft command separator can have spaces after it + elif [[ "$spaces" = *$'\n'* && "${TOKEN_TYPES[$prev_token]}" != "3" ]]; then + pre_block_end=2*(j-1) + found=1 + break + fi + done + + # Didn't found, and therefore reached beginning of file + (( found == 0 )) && pre_block_end=0 + fi + fi + + if (( post_block_begin == 0 )); then + post_block_begin=2*i-1 + else + # Detect not related trailing comments + for (( j=(post_block_begin+1)/2-1; j >= 1; -- j )); do + [[ "${ZSHLINT_PB_WORDS[j]}" = [[:blank:]]#\#* && "${ZSHLINT_PB_WORDS[j]}" != [[:blank:]]#\#[[:blank:]]#zi* ]] && { + post_block_begin=2*j-1 + } || break + done + + # Detect if compinit follows + integer count=$(( size-(post_block_begin+1)/2 )) + local next_token_2 + (( count = (count > 15) ? 15 : count )) + for (( j=(post_block_begin+1)/2; count > 0; ++ j, -- count )); do + token="${ZSHLINT_PB_WORDS[j]}" + next_token="${ZSHLINT_PB_WORDS[j+1]}" + next_token_2="${ZSHLINT_PB_WORDS[j+2]}" + next_spaces="${ZSHLINT_PB_SPACES[j+1]}" + + if [[ "$token" = "autoload" && ( "$next_token" = "compinit" || "$next_token_2" = "compinit" ) ]]; then + [[ "$next_token" = "compinit" ]] && { post_block_begin=2*(j+2)-1; j+=1; count=count-1; } + [[ "$next_token_2" = "compinit" ]] && { post_block_begin=2*(j+3)-1; j+=2; count=count-2; } + elif [[ "$token" = "compinit" && ( "$next_spaces" = *$'\n'* || "${TOKEN_TYPES[$next_token]}" = 4 ) ]]; then + # TODO: soft-connected following tokens can be also included + post_block_begin=2*(j+1)-1 + break + fi + done + fi } # }}} # FUNCTION: .zsh-lint-process-zi-commands {{{ -# Processes block with zi commands established earlier in .zsh-lint-process-zsh-rc() and generates $cmdlist array -# which holds serialized hashes of every zi invocation, mixed-in additional (external) commands, comments. +# Processes block with zi commands established earlier +# in .zsh-lint-process-zsh-rc() and generates $cmdlist array +# which holds serialized hashes of every zi invocation, +# mixed-in additional (external) commands, comments. # # Uses parameters filled by @zsh-lint-process-buffer: # - $ZSHLINT_PB_WORDS - tokens # - $ZSHLINT_PB_SPACES - spaces in front of each token, +1 at the end .zsh-lint-process-zi-commands() { - integer i - cmd3=() - # Reset parameters used in .zsh-lint-process-zsh-rc - prev_depth=0 prev_token="" prev_spaces="" - # From start to end of zi block - # (pre_block_end/2)+1 - from the one space-token element after the pre-block - # (post_block_begin+1)/2-1 - to one space-token element before the post-block - # (post_block_begin+1)/2 - to the next, first element after last zi command - for (( i=(pre_block_end/2)+1; i<=(post_block_begin+1)/2-1; ++ i )); do - token="${ZSHLINT_PB_WORDS[i]}" - spaces="${ZSHLINT_PB_SPACES[i]}" - next_token="${ZSHLINT_PB_WORDS[i+1]}" - next_spaces="${ZSHLINT_PB_SPACES[i+1]}" - # New command? - if [[ "$spaces" = *$'\n'* || -z "$prev_token" || "${TOKEN_TYPES[$prev_token]}" = [1234] ]]; then - # Remember trailing spaces even though they might - # get overwritten later, if the single command is - # multi-line, i.e. if it uses \-line endings. The - # last stored spaces are the command's spaces. - if [[ "${TOKEN_TYPES[$prev_token]}" = [34] ]]; then - # When command ends with explicit command separator, - # then remember white spaces after that separator - cmd3[aspaces]="$spaces" - elif [[ "$spaces" != (|$'\r')$'\n' ]]; then - # Remember spaces if they're not single newline, and - # if what follows isn't a comment. So basically spaces - # between commands are remembered. Spaces before comments - # are not (but comment-remembering handles this) - if [[ "$token" != [[:blank:]]#\#* ]]; then - cmd3[aspaces]="$spaces" + integer i + cmd3=() + + # Reset parameters used in .zsh-lint-process-zsh-rc + prev_depth=0 prev_token="" prev_spaces="" + + # From start to end of zi block + # (pre_block_end/2)+1 - from the one space-token element after the pre-block + # (post_block_begin+1)/2-1 - to one space-token element before the post-block + # (post_block_begin+1)/2 - to the next, first element after last zi command + for (( i=(pre_block_end/2)+1; i<=(post_block_begin+1)/2-1; ++ i )); do + token="${ZSHLINT_PB_WORDS[i]}" + spaces="${ZSHLINT_PB_SPACES[i]}" + next_token="${ZSHLINT_PB_WORDS[i+1]}" + next_spaces="${ZSHLINT_PB_SPACES[i+1]}" + + # New command? + if [[ "$spaces" = *$'\n'* || -z "$prev_token" || "${TOKEN_TYPES[$prev_token]}" = [1234] ]]; then + # Remember trailing spaces even though they might + # get overwritten later, if the single command is + # multi-line, i.e. if it uses \-line endings. The + # last stored spaces are the command's spaces. + if [[ "${TOKEN_TYPES[$prev_token]}" = [34] ]]; then + # When command ends with explicit command separator, + # then remember white spaces after that separator + cmd3[aspaces]="$spaces" + elif [[ "$spaces" != (|$'\r')$'\n' ]]; then + # Remember spaces if they're not single newline, and + # if what follows isn't a comment. So basically spaces + # between commands are remembered. Spaces before comments + # are not (but comment-remembering handles this) + if [[ "$token" != [[:blank:]]#\#* ]]; then + cmd3[aspaces]="$spaces" + else + unset 'cmd3[aspaces]' + fi + else + unset 'cmd3[aspaces]' + fi + + if [[ "$spaces" = [[:blank:]]#"\\"(|$'\r')$'\n'[[:blank:]]# ]]; then + cmd3[\\-break_$in_zinit]="$spaces" + else + in_zinit=1 + + if [[ "${cmd3[c]}" = "zi" && "${cmd3[sub]}" = "ice" ]]; then + cmdlist[coidx+1]="${(j: :)${(qkv)cmd3[@]}}" + /zsh-lint-dbg "Stored /$coidx-cmd: ${cmdlist[coidx+1]}" + elif [[ "${cmd3[c]}" = "zi" && "${cmd3[sub]}" != "ice" ]]; then + cmdlist[coidx+2]="${(j: :)${(qkv)cmd3[@]}}" + /zsh-lint-dbg "Stored /$coidx-cmd: ${cmdlist[coidx+2]}" + coidx+=3 + else + # Don't increase coidx untill we get some command. + # This causes preceding comments to accumulate. + if [[ -n "${cmd3[c]}" ]]; then + # Custom command + cmdlist[coidx+2]="${(j: :)${(qkv)cmd3[@]}}" + /zsh-lint-dbg "Stored /$coidx-cmd: ${cmdlist[coidx+2]}" + coidx+=3 + fi + fi + + cmd3=() + fi + fi + + # Command token + if [[ "$token" = [[:blank:]]#\#* ]]; then + if (( in_zinit == 1 )); then + cmdlist[coidx]="${cmdlist[coidx]%%[[:space:]]##}" + cmdlist[coidx]+="$spaces$token$next_spaces" + else + cmd3[comment]="$spaces$token" + fi + elif (( in_zinit == 1 )); then + [[ "$token" = (zi|zinit|zpl) ]] && cmd3[c]="zi" || cmd3[c]="$token" + # Postpone current state to next token + [[ "$token" = ":" ]] && { cmd3[disabled]=1; (( in_zinit -- )); } + elif (( in_zinit == 2 )); then + if [[ "${cmd3[c]}" = "zi" ]]; then + [[ "$token" = (ice|load|light|snippet|cdclear|\ +cdreplay|env-whitelist|create) + ]] && { + cmd3[sub]="$token" + # We get another ice-command while a previous ice-command + # is still being pointed to by coidx? -> progress coidx + [[ "$token" = ice && -n "${cmdlist[coidx+1]}" ]] && \ + coidx+=3 + ((1)) + } || \ + { cmd3[sub]="unkn"; cmd3[unkn]="$token"; } + else + cmd3[arg_1]="$token" + cmd3[spaces_1]="$spaces" + fi else - unset 'cmd3[aspaces]' + if [[ "${cmd3[sub]}" = "ice" ]]; then + # Also dequote the ice modifier + [[ "$token" = (#b)(${(~j:|:)ice_order})(?)(?)(*)(?) || \ + "$token" = (#b)(${(~j:|:)ice_order})(*) + ]] && { + local key="${match[1]}" string="${match[2]#[:=]}${match[3]}${match[4]}${match[5]}" + local second="${match[2]}" third="${match[3]}" last="${match[5]}" + cmd3[${${(M)own_order#0}:+${in_zinit}_}$key]="${string//(#b)(:|=|)([\"\']|\$\'|)(*)[\"\']/${match[3]}}" + # Remember also the quoting style, if the quoting + # is a recognized one + [[ ( -n "$second" && "$second" = "$last" ) || \ + "$second" = (:|=)* # Via the star this handles both matchings + ]] && \ + cmd3[${${(M)own_order#0}:+${in_zinit}_}${key}_style]="${second[1]}" || { + [[ "$second" = '$' && "$third" = "'" ]] && \ + cmd3[${${(M)own_order#0}:+${in_zinit}_}${key}_style]="\$'" + } + ((1)) + } || { + cmd3[custom_$(( in_zinit - 2 ))]="$token" + cmd3[spaces_$(( in_zinit - 2 ))]="$spaces" + } + elif [[ "${cmd3[sub]}" = (load|light) ]]; then + if (( in_zinit == 4 )); then + cmd3[url]+="/$token" + elif (( ${+cmd3[url]} == 0 )); then + cmd3[url]="$token" + else + cmd3[custom_$(( in_zinit - 2 ))]="$token" + cmd3[spaces_$(( in_zinit - 2 ))]="$spaces" + fi + elif [[ "${cmd3[sub]}" = "snippet" ]]; then + if [[ "$token" = "-f" ]]; then + cmd3[-f]="1" + elif [[ "$token" = "--command" ]]; then + cmd3[--command]="1" + elif [[ ${+cmd3[url]} != 1 && "${TOKEN_TYPES[$token]}" != [34] ]]; then + cmd3[url]="$token" + else + cmd3[custom_$(( in_zinit - 2 ))]="$token" + cmd3[spaces_$(( in_zinit - 2 ))]="$spaces" + fi + elif [[ "${cmd3[sub]}" = "cdclear" ]]; then + [[ "$token" = "-q" ]] && cmd3[-q]="1" || { + cmd3[custom_$(( in_zinit - 2 ))]="$token" + cmd3[spaces_$(( in_zinit - 2 ))]="$spaces" + } + elif [[ "${cmd3[sub]}" = "cdreplay" ]]; then + [[ "$token" = "-q" ]] && cmd3[-q]="1" || { + cmd3[custom_$(( in_zinit - 2 ))]="$token" + cmd3[spaces_$(( in_zinit - 2 ))]="$spaces" + } + elif [[ "${cmd3[sub]}" = "unkn" ]]; then + # Argument following "zi ..." + cmd3[arg_$(( in_zinit - 2 ))]="$token" + else + # Argument following " ..." + cmd3[arg_$(( in_zinit - 1 ))]="$token" + cmd3[spaces_$(( in_zinit - 1 ))]="$spaces" + fi fi - else - unset 'cmd3[aspaces]' - fi - if [[ "$spaces" = [[:blank:]]#"\\"(|$'\r')$'\n'[[:blank:]]# ]]; then - cmd3[\\-break_$in_zi]="$spaces" - else - in_zi=1 + + # Advance deeper into "zi ..." if it wasn't comment + #[[ "$token" != [[:blank:]]#\#* ]] && in_zinit+=1 + in_zinit+=1 + + # History of state + prev_depth="$depth" + prev_token="$token" + prev_spaces="$spaces" + done + + if [[ -n "${cmd3[c]}" ]];then if [[ "${cmd3[c]}" = "zi" && "${cmd3[sub]}" = "ice" ]]; then - cmdlist[coidx+1]="${(j: :)${(qkv)cmd3[@]}}" - /zsh-lint-dbg "Stored /$coidx-cmd: ${cmdlist[coidx+1]}" + cmdlist[coidx+1]="${(j: :)${(qkv)cmd3[@]}}" + /zsh-lint-dbg "Stored /$coidx-cmd: ${cmdlist[coidx+1]}" elif [[ "${cmd3[c]}" = "zi" && "${cmd3[sub]}" != "ice" ]]; then - cmdlist[coidx+2]="${(j: :)${(qkv)cmd3[@]}}" - /zsh-lint-dbg "Stored /$coidx-cmd: ${cmdlist[coidx+2]}" - coidx+=3 - else - # Don't increase coidx untill we get some command. - # This causes preceding comments to accumulate. - if [[ -n "${cmd3[c]}" ]]; then - # Custom command + cmdlist[coidx+2]="${(j: :)${(qkv)cmd3[@]}}" + /zsh-lint-dbg "Stored /$coidx-cmd: ${cmdlist[coidx+2]}" + elif [[ "${cmd3[c]}" != "zi" ]]; then cmdlist[coidx+2]="${(j: :)${(qkv)cmd3[@]}}" /zsh-lint-dbg "Stored /$coidx-cmd: ${cmdlist[coidx+2]}" - coidx+=3 - fi fi - cmd3=() - fi - fi - # Command token - if [[ "$token" = [[:blank:]]#\#* ]]; then - if (( in_zi == 1 )); then - cmdlist[coidx]="${cmdlist[coidx]%%[[:space:]]##}" - cmdlist[coidx]+="$spaces$token$next_spaces" - else - cmd3[comment]="$spaces$token" - fi - elif (( in_zi == 1 )); then - [[ "$token" = (zi|zpl) ]] && cmd3[c]="zi" || cmd3[c]="$token" - # Postpone current state to next token - [[ "$token" = ":" ]] && { cmd3[disabled]=1; (( in_zi -- )); } - elif (( in_zi == 2 )); then - if [[ "${cmd3[c]}" = "zi" ]]; then - [[ "$token" = (ice|load|light|snippet|cdclear|cdreplay|env-whitelist|create) ]] && { - cmd3[sub]="$token" - # We get another ice-command while a previous ice-command - # is still being pointed to by coidx? -> progress coidx - [[ "$token" = ice && -n "${cmdlist[coidx+1]}" ]] && coidx+=3 - ((1)) - } || { cmd3[sub]="unkn"; cmd3[unkn]="$token"; } - else - cmd3[arg_1]="$token" - cmd3[spaces_1]="$spaces" - fi + /zsh-lint-dbg "ZI block in zshrc has $(( (coidx + 2) / 3 )) commands (not counting \`zi ice ...')" else - if [[ "${cmd3[sub]}" = "ice" ]]; then - # Also dequote the ice modifier - [[ "$token" = (#b)(${(~j:|:)ice_order})(?)(?)(*)(?) || "$token" = (#b)(${(~j:|:)ice_order})(*) ]] && { - local key="${match[1]}" string="${match[2]#[:=]}${match[3]}${match[4]}${match[5]}" - local second="${match[2]}" third="${match[3]}" last="${match[5]}" - cmd3[${${(M)own_order#0}:+${in_zi}_}$key]="${string//(#b)(:|=|)([\"\']|\$\'|)(*)[\"\']/${match[3]}}" - # Remember also the quoting style, if the quoting is a recognized one - [[ ( -n "$second" && "$second" = "$last" ) || "$second" = (:|=)* # Via the star this handles both matchings ]] && cmd3[${${(M)own_order#0}:+${in_zi}_}${key}_style]="${second[1]}" || { - [[ "$second" = '$' && "$third" = "'" ]] && cmd3[${${(M)own_order#0}:+${in_zi}_}${key}_style]="\$'" - } - ((1)) - } || { - cmd3[custom_$(( in_zi - 2 ))]="$token" - cmd3[spaces_$(( in_zi - 2 ))]="$spaces" - } - elif [[ "${cmd3[sub]}" = (load|light) ]]; then - if (( in_zi == 4 )); then - cmd3[url]+="/$token" - elif (( ${+cmd3[url]} == 0 )); then - cmd3[url]="$token" - else - cmd3[custom_$(( in_zi - 2 ))]="$token" - cmd3[spaces_$(( in_zi - 2 ))]="$spaces" - fi - elif [[ "${cmd3[sub]}" = "snippet" ]]; then - if [[ "$token" = "-f" ]]; then - cmd3[-f]="1" - elif [[ "$token" = "--command" ]]; then - cmd3[--command]="1" - elif [[ ${+cmd3[url]} != 1 && "${TOKEN_TYPES[$token]}" != [34] ]]; then - cmd3[url]="$token" - else - cmd3[custom_$(( in_zi - 2 ))]="$token" - cmd3[spaces_$(( in_zi - 2 ))]="$spaces" - fi - elif [[ "${cmd3[sub]}" = "cdclear" ]]; then - [[ "$token" = "-q" ]] && cmd3[-q]="1" || { - cmd3[custom_$(( in_zi - 2 ))]="$token" - cmd3[spaces_$(( in_zi - 2 ))]="$spaces" - } - elif [[ "${cmd3[sub]}" = "cdreplay" ]]; then - [[ "$token" = "-q" ]] && cmd3[-q]="1" || { - cmd3[custom_$(( in_zi - 2 ))]="$token" - cmd3[spaces_$(( in_zi - 2 ))]="$spaces" - } - elif [[ "${cmd3[sub]}" = "unkn" ]]; then - # Argument following "zi ..." - cmd3[arg_$(( in_zi - 2 ))]="$token" - else - # Argument following " ..." - cmd3[arg_$(( in_zi - 1 ))]="$token" - cmd3[spaces_$(( in_zi - 1 ))]="$spaces" - fi - fi - # Advance deeper into "zi ..." if it wasn't comment - #[[ "$token" != [[:blank:]]#\#* ]] && in_zi+=1 - in_zi+=1 - # History of state - prev_depth="$depth" - prev_token="$token" - prev_spaces="$spaces" - done - if [[ -n "${cmd3[c]}" ]];then - if [[ "${cmd3[c]}" = "zi" && "${cmd3[sub]}" = "ice" ]]; then - cmdlist[coidx+1]="${(j: :)${(qkv)cmd3[@]}}" - /zsh-lint-dbg "Stored /$coidx-cmd: ${cmdlist[coidx+1]}" - elif [[ "${cmd3[c]}" = "zi" && "${cmd3[sub]}" != "ice" ]]; then - cmdlist[coidx+2]="${(j: :)${(qkv)cmd3[@]}}" - /zsh-lint-dbg "Stored /$coidx-cmd: ${cmdlist[coidx+2]}" - elif [[ "${cmd3[c]}" != "zi" ]]; then - cmdlist[coidx+2]="${(j: :)${(qkv)cmd3[@]}}" - /zsh-lint-dbg "Stored /$coidx-cmd: ${cmdlist[coidx+2]}" + /zsh-lint-dbg "ZI block in zshrc has $(( (coidx-1) / 3 )) commands (not counting \`zi ice ...')" fi - /zsh-lint-dbg "ZI block in zshrc has $(( (coidx + 2) / 3 )) commands (not counting \`zi ice ...')" - else - /zsh-lint-dbg "ZI block in zshrc has $(( (coidx-1) / 3 )) commands (not counting \`zi ice ...')" - fi - /zsh-lint-dbg "" -} # }}} + + /zsh-lint-dbg "" +} +# }}} # FUNCTION: .zsh-lint_compose {{{ -# Constructs text with zi commands, optionally including original Zshrc blocks (in order to create full .zshrc). Can skip comments (via $1). +# Constructs text with zi commands, optionally +# including original Zshrc blocks (in order to create +# full .zshrc). Can skip comments (via $1). # # $1 - 0 or 1 - whether to include comments # $2 - 0 or 1 - whether to generate complete zshrc # # $reply - lines of created code .zsh-lint_compose() { - local with_comments="$1" whole="$2" own_quoting="$3" - local buf="" tmp comment key - integer i size=$(( ${#cmdlist} / 3 )) - local -a cmd keys - local -A ice_cmd main_cmd - [[ "$whole" = "1" ]] && { - if (( pre_block_end > 0 )); then - buf="${(j::)ZSHLINT_PB_ALL[1,pre_block_end+1]}" - else - buf="" - fi - } - for (( i=1; i<=size; ++ i )); do - cmd=( "${(@)cmdlist[(i-1)*3+1,i*3]}" ) - comment="${cmd[1]}" - ice_cmd=() main_cmd=() - [[ -n "${cmd[2]}" ]] && ice_cmd=( "${(z@)cmd[2]}" ) - [[ -n "${cmd[3]}" ]] && main_cmd=( "${(z@)cmd[3]}" ) - ice_cmd=( "${(Qkv)ice_cmd[@]}" ) - main_cmd=( "${(Qkv)main_cmd[@]}" ) - # A horrible thing with $(<...) omitting trailing newlines - [[ ${#buf} -gt 0 && ${buf[-1]} != $'\n' && $i -eq 1 ]] && buf+=$'\n' - # Protection against previous command having trailing spaces before $'\n' - local nl=$'\n' - comment="${comment/[[:blank:]]##$nl/$nl}" - # Comment - [[ "$with_comments" = "1" && -n "$comment" ]] && { - (( i == 1 )) && buf+="${comment##[[:space:]]##}" || buf+="${comment#$nl}" - } - # ICE - # Something more than [c]=zi [sub]=ice [comment]=comment - if (( ${#ice_cmd} > 2 && ${+ice_cmd[comment]} == 0 || ${#ice_cmd} > 3 && ${+ice_cmd[comment]} == 1 )); then - (( ice_cmd[disabled] || main_cmd[disabled] )) && buf+=": " - buf+="zi${ice_cmd[\\-break_2]:- }ice" - if (( own_order )); then - for tmp in "${ice_order[@]}"; do - .zsh-lint_the_ice_case "$tmp" "$tmp" - done - else - keys=( "${(kon)ice_cmd[@]}" ) - for key in "${keys[@]}"; do - [[ "$key" != <->_* ]] && continue - .zsh-lint_the_ice_case "${key#<->_}" "$key" - done - fi - # Put tokens other than ice-mod at the - # end, in order, with spaces - keys=( "${(kon)ice_cmd[@]}" ) - for key in "${keys[@]}"; do - [[ "$key" != custom_* ]] && continue - buf+="${ice_cmd[spaces_${key#custom_}]}${ice_cmd[$key]}" - done - [[ "$with_comments" = "1" && -n "${ice_cmd[comment]}" ]] && buf+="${ice_cmd[comment]}" - if [[ ${#ice_cmd} -gt 0 || "$i" -lt "$size" ]]; then - (( ${+ice_cmd[aspaces]} )) && buf+="${ice_cmd[aspaces]}" || buf+=$'\n' - fi - fi - # COMMAND - if [[ "${#main_cmd}" -ge 1 ]]; then - if [[ "${main_cmd[c]}" = (zi|zplg|zpl) ]]; then - (( ice_cmd[disabled] || main_cmd[disabled] )) && buf+=": " - if [[ "${main_cmd[sub]}" = (load|light|snippet|cdclear|cdreplay|env-whitelist|create) ]]; then - buf+="zi${main_cmd[\\-break_2]:- }${main_cmd[sub]}" - [[ "${main_cmd[--command]}" = "1" ]] && buf+=" --command" - [[ "${main_cmd[-f]}" = "1" ]] && buf+=" -f" - [[ "${main_cmd[-q]}" = "1" ]] && buf+=" -q" - [[ -n "${main_cmd[url]}" ]] && buf+=" ${main_cmd[url]}" - [[ "$with_comments" = "1" && -n "${main_cmd[comment]}" ]] && buf+="${main_cmd[comment]}" - keys=( "${(kon)main_cmd[@]}" ) - for key in "${keys[@]}"; do - [[ "$key" != arg_* ]] && continue - buf+="${main_cmd[\\-break_$(( ${key#arg_} + 1 ))]:- }${main_cmd[$key]}" - done - if [[ "$i" -lt "$size" ]]; then - (( ${+main_cmd[aspaces]} )) && buf+="${main_cmd[aspaces]}" || buf+=$'\n' - fi + local with_comments="$1" whole="$2" own_quoting="$3" + local buf="" tmp comment key + integer i size=$(( ${#cmdlist} / 3 )) + local -a cmd keys + local -A ice_cmd main_cmd + + [[ "$whole" = "1" ]] && { + if (( pre_block_end > 0 )); then + buf="${(j::)ZSHLINT_PB_ALL[1,pre_block_end+1]}" else - # Unknown subcommand - local text="${main_cmd[c]}" - [[ -n "${main_cmd[unkn]}" ]] && text+=" ${main_cmd[unkn]}" - keys=( "${(kon)main_cmd[@]}" ) - for key in "${keys[@]}"; do - [[ "$key" != arg_* ]] && continue - text+=" ${main_cmd[$key]}" - done - buf+="$text" - if [[ "$i" -lt "$size" ]]; then - (( ${+main_cmd[aspaces]} )) && buf+="${main_cmd[aspaces]}" || buf+=$'\n' - fi + buf="" fi - else - local text="${main_cmd[c]}" - keys=( "${(kon)main_cmd[@]}" ) - for key in "${keys[@]}"; do - [[ "$key" != arg_* ]] && continue - text+="${main_cmd[spaces_${key#arg_}]}${main_cmd[$key]}" - done - buf+="$text" - if [[ "$i" -lt "$size" ]]; then - (( ${+main_cmd[aspaces]} )) && buf+="${main_cmd[aspaces]}" || buf+=$'\n' + } + + for (( i=1; i<=size; ++ i )); do + cmd=( "${(@)cmdlist[(i-1)*3+1,i*3]}" ) + comment="${cmd[1]}" + ice_cmd=() main_cmd=() + [[ -n "${cmd[2]}" ]] && ice_cmd=( "${(z@)cmd[2]}" ) + [[ -n "${cmd[3]}" ]] && main_cmd=( "${(z@)cmd[3]}" ) + + ice_cmd=( "${(Qkv)ice_cmd[@]}" ) + main_cmd=( "${(Qkv)main_cmd[@]}" ) + + # A horrible thing with $(<...) omitting trailing newlines + [[ ${#buf} -gt 0 && ${buf[-1]} != $'\n' && $i -eq 1 ]] && buf+=$'\n' + + # Protection against previous command having trailing spaces before $'\n' + local nl=$'\n' + comment="${comment/[[:blank:]]##$nl/$nl}" + + # Comment + [[ "$with_comments" = "1" && -n "$comment" ]] && { + (( i == 1 )) && buf+="${comment##[[:space:]]##}" || buf+="${comment#$nl}" + } + + # ICE + # Something more than [c]=zi [sub]=ice [comment]=comment + if (( ${#ice_cmd} > 2 && ${+ice_cmd[comment]} == 0 || ${#ice_cmd} > 3 && ${+ice_cmd[comment]} == 1 )); then + (( ice_cmd[disabled] || main_cmd[disabled] )) && buf+=": " + buf+="zi${ice_cmd[\\-break_2]:- }ice" + if (( own_order )); then + for tmp in "${ice_order[@]}"; do + .zsh-lint_the_ice_case "$tmp" "$tmp" + done + else + keys=( "${(kon)ice_cmd[@]}" ) + for key in "${keys[@]}"; do + [[ "$key" != <->_* ]] && continue + .zsh-lint_the_ice_case "${key#<->_}" "$key" + done + fi + + # Put tokens other than ice-mod at the + # end, in order, with spaces + keys=( "${(kon)ice_cmd[@]}" ) + for key in "${keys[@]}"; do + [[ "$key" != custom_* ]] && continue + buf+="${ice_cmd[spaces_${key#custom_}]}${ice_cmd[$key]}" + done + + [[ "$with_comments" = "1" && -n "${ice_cmd[comment]}" ]] && buf+="${ice_cmd[comment]}" + if [[ ${#ice_cmd} -gt 0 || "$i" -lt "$size" ]]; then + (( ${+ice_cmd[aspaces]} )) && buf+="${ice_cmd[aspaces]}" || buf+=$'\n' + fi + fi + + # COMMAND + if [[ "${#main_cmd}" -ge 1 ]]; then + if [[ "${main_cmd[c]}" = (zi|zplg|zpl) ]]; then + (( ice_cmd[disabled] || main_cmd[disabled] )) && buf+=": " + if [[ "${main_cmd[sub]}" = (load|light|snippet|cdclear|\ +cdreplay|env-whitelist|create) ]]; then + buf+="zi${main_cmd[\\-break_2]:- }${main_cmd[sub]}" + [[ "${main_cmd[--command]}" = "1" ]] && buf+=" --command" + [[ "${main_cmd[-f]}" = "1" ]] && buf+=" -f" + [[ "${main_cmd[-q]}" = "1" ]] && buf+=" -q" + [[ -n "${main_cmd[url]}" ]] && buf+=" ${main_cmd[url]}" + [[ "$with_comments" = "1" && -n "${main_cmd[comment]}" ]] && buf+="${main_cmd[comment]}" + + keys=( "${(kon)main_cmd[@]}" ) + for key in "${keys[@]}"; do + [[ "$key" != arg_* ]] && continue + buf+="${main_cmd[\\-break_$(( ${key#arg_} + 1 ))]:- }${main_cmd[$key]}" + done + + if [[ "$i" -lt "$size" ]]; then + (( ${+main_cmd[aspaces]} )) && buf+="${main_cmd[aspaces]}" || buf+=$'\n' + fi + else + # Unknown subcommand + local text="${main_cmd[c]}" + [[ -n "${main_cmd[unkn]}" ]] && text+=" ${main_cmd[unkn]}" + keys=( "${(kon)main_cmd[@]}" ) + for key in "${keys[@]}"; do + [[ "$key" != arg_* ]] && continue + text+=" ${main_cmd[$key]}" + done + buf+="$text" + if [[ "$i" -lt "$size" ]]; then + (( ${+main_cmd[aspaces]} )) && buf+="${main_cmd[aspaces]}" || buf+=$'\n' + fi + fi + else + local text="${main_cmd[c]}" + keys=( "${(kon)main_cmd[@]}" ) + for key in "${keys[@]}"; do + [[ "$key" != arg_* ]] && continue + text+="${main_cmd[spaces_${key#arg_}]}${main_cmd[$key]}" + done + buf+="$text" + if [[ "$i" -lt "$size" ]]; then + (( ${+main_cmd[aspaces]} )) && buf+="${main_cmd[aspaces]}" || buf+=$'\n' + fi + fi fi - fi + done + + if (( pre_block_end+1 != post_block_begin )); then + [[ "$whole" = "1" ]] && buf+="${(j::)ZSHLINT_PB_ALL[post_block_begin,-1]}" fi - done - if (( pre_block_end+1 != post_block_begin )); then - [[ "$whole" = "1" ]] && buf+="${(j::)ZSHLINT_PB_ALL[post_block_begin,-1]}" - fi - reply=( "${(@f)buf}" ) -} # }}} + + reply=( "${(@f)buf}" ) +} +# }}} # FUNCTION: .zsh-lint_save {{{ -# Called when [Save] pressed. Composes full .zshrc, performs the save, outputs status message. +# Called when [Save] pressed. Composes full .zshrc, +# performs the save, outputs status message. # $1 - destination path .zsh-lint_save() { - local dest="$1" - integer ret - .zsh-lint_compose 1 1 "$own_quoting" - builtin print -rl -- "${reply[@]}" >| "$dest" - ret=$? - (( !ret )) && \ - /zsh-lint-dbg "Saved to: \`$dest'" || /zsh-lint-dbg "Couldn't save to: \`$dest'" - return $ret -} # }}} + local dest="$1" + integer ret + .zsh-lint_compose 1 1 "$own_quoting" + builtin print -rl -- "${reply[@]}" >| "$dest" + ret=$? + (( !ret )) && \ + /zsh-lint-dbg "Saved to: \`$dest'" || \ + /zsh-lint-dbg "Couldn't save to: \`$dest'" + return $ret +} +# }}} # # Backend functions # # FUNCTION: .zsh-lint_update_main_cmd {{{ -# Updates main entry in given ($3) command packet. There is single command packet per zi invocation and it -# contains main command, optionally ICE command, and also optionally a preceding comment. +# Updates main entry in given ($3) command packet. There +# is single command packet per zi invocation and it +# contains main command, optionally ICE command, and also +# optionally a preceding comment. # # $1 - key to update in the main command # $2 - data to store under the key # $3 - index of command pack to alter .zsh-lint_update_main_cmd() { - local key="$1" data="$2" idx="$3" - local -a cmd - local -A main_cmd - cmd=( "${(@)cmdlist[(idx-1)*3+1,idx*3]}" ) - if [[ -n "${cmd[3]}" ]]; then - main_cmd=( "${(z@)cmd[3]}" ) - main_cmd=( "${(Qkv)main_cmd[@]}" ) - fi - if [[ "$data" = "delete" ]]; then - unset "main_cmd[$key]" - elif [[ "$data" = "toggle" ]]; then - main_cmd[$key]=$(( !main_cmd[$key] )) - else - main_cmd[$key]="$data" - fi - cmdlist[idx*3]="${(j: :)${(qkv)main_cmd[@]}}" - return 0 -} # }}} + local key="$1" data="$2" idx="$3" + local -a cmd + local -A main_cmd + + cmd=( "${(@)cmdlist[(idx-1)*3+1,idx*3]}" ) + if [[ -n "${cmd[3]}" ]]; then + main_cmd=( "${(z@)cmd[3]}" ) + main_cmd=( "${(Qkv)main_cmd[@]}" ) + fi + if [[ "$data" = "delete" ]]; then + unset "main_cmd[$key]" + elif [[ "$data" = "toggle" ]]; then + main_cmd[$key]=$(( !main_cmd[$key] )) + else + main_cmd[$key]="$data" + fi + + cmdlist[idx*3]="${(j: :)${(qkv)main_cmd[@]}}" + return 0 +} +# }}} # FUNCTION: .zsh-lint_update_ice_cmd {{{ -# Updates ICE entry in given ($3) command packet. There is single command packet per zi invocation and it -# contains main command, optionally ICE command, and also optionally a preceding comment. +# Updates ICE entry in given ($3) command packet. There +# is single command packet per zi invocation and it +# contains main command, optionally ICE command, and also +# optionally a preceding comment. # # $1 - key to update in the ice command # $2 - data to store under the key # $3 - index of command pack to alter .zsh-lint_update_ice_cmd() { - local key="$1" data="$2" idx="$3" - local -a cmd - local -A ice_cmd - cmd=( "${(@)cmdlist[(idx-1)*3+1,idx*3]}" ) - if [[ -n "${cmd[2]}" ]]; then - ice_cmd=( "${(z@)cmd[2]}" ) - ice_cmd=( "${(Qkv)ice_cmd[@]}" ) - fi - if [[ "$data" = "delete" ]]; then - unset "ice_cmd[$key]" - elif [[ "$data" = "toggle" ]]; then - ice_cmd[$key]=$(( !ice_cmd[$key] )) - else - ice_cmd[$key]="$data" - fi - local -a other - other=( "${(@)ice_cmd[(I)(${(~j:|:)ice_order})]}" ) - if (( ${#other} == 0 )); then - ice_cmd=( comment "${ice_cmd[comment]}" ) - else - ice_cmd+=( c "zi" sub "ice" ) - fi - cmdlist[idx*3-1]="${(j: :)${(qkv)ice_cmd[@]}}" -} # }}} -# FUNCTION: .zsh-lint_search_zi {{{ -.zsh-lint_search_zi() { - local input="$1" - integer idx size - local -a cmd - local -A main_cmd - size=${#cmdlist} - for (( idx=1; idx*3 <= size; ++ idx )); do + local key="$1" data="$2" idx="$3" + local -a cmd + local -A ice_cmd + cmd=( "${(@)cmdlist[(idx-1)*3+1,idx*3]}" ) - [[ -z "${cmd[3]}" ]] && continue - main_cmd=( "${(z@)cmd[3]}" ) - main_cmd=( "${(Qkv)main_cmd[@]}" ) - [[ "${main_cmd[c]}" = zi && "${main_cmd[sub]}" = (load|light|snippet) && "${main_cmd[url]}" = ${~input} ]] && reply+=( "$idx" ) - done - +zsh-lint-info "-Found ${#reply} element(s)" -} # }}} + if [[ -n "${cmd[2]}" ]]; then + ice_cmd=( "${(z@)cmd[2]}" ) + ice_cmd=( "${(Qkv)ice_cmd[@]}" ) + fi + if [[ "$data" = "delete" ]]; then + unset "ice_cmd[$key]" + elif [[ "$data" = "toggle" ]]; then + ice_cmd[$key]=$(( !ice_cmd[$key] )) + else + ice_cmd[$key]="$data" + fi + + local -a other + other=( "${(@)ice_cmd[(I)(${(~j:|:)ice_order})]}" ) + if (( ${#other} == 0 )); then + ice_cmd=( comment "${ice_cmd[comment]}" ) + else + ice_cmd+=( c "zi" sub "ice" ) + fi + + cmdlist[idx*3-1]="${(j: :)${(qkv)ice_cmd[@]}}" +} +# }}} +# FUNCTION: .zsh-lint_search_zinit {{{ +.zsh-lint_search_zinit() { + local input="$1" + integer idx size + local -a cmd + local -A main_cmd + + size=${#cmdlist} + + for (( idx=1; idx*3 <= size; ++ idx )); do + cmd=( "${(@)cmdlist[(idx-1)*3+1,idx*3]}" ) + [[ -z "${cmd[3]}" ]] && continue + + main_cmd=( "${(z@)cmd[3]}" ) + main_cmd=( "${(Qkv)main_cmd[@]}" ) + + [[ "${main_cmd[c]}" = zi && \ + "${main_cmd[sub]}" = (load|light|snippet) && \ + "${main_cmd[url]}" = ${~input} + ]] && \ + reply+=( "$idx" ) + done + +zsh-lint-info "-Found ${#reply} element(s)" +} +# }}} # FUNCTION .zsh-lint_the_ice_case {{{ -# A little hackish function - it operates almost only on outside-scope parameters. It is a "backend" ice-mod -# processing during composing - it processes the given ice mod in $tmp, whether with or without the preceding order number. +# A little hackish function - it operates almost only on +# outside-scope parameters. It is a "backend" ice-mod +# processing during composing - it processes the given +# ice mod in $tmp, whether with or without the preceding +# order number. .zsh-lint_the_ice_case() { - local name="$1" tmp="$2" - case "$tmp" in - ((<->_|)(${(~j:|:)${ice_order:|nval_ices}})) - if (( !own_quoting )); then - [[ -z "${ice_cmd[$tmp]}" && ${+ice_cmd[$tmp]} -eq 1 ]] && { buf+="${ice_cmd[\\-break_${(M)tmp#[0-9]}]:- }$name"; continue; } - [[ -z "${ice_cmd[$tmp]}" ]] && continue - local apos="'" - local quoted="${ice_cmd[${tmp}_style]}${ice_cmd[$tmp]}${${${(M)ice_cmd[${tmp}_style]:#\$\'}:+$apos}:-${ice_cmd[${tmp}_style]:#[:=]}}" - buf+="${ice_cmd[\\-break_${(M)tmp#[0-9]}]:- }$name$quoted" - else - [[ -z "${ice_cmd[$tmp]}" && ${+ice_cmd[$tmp]} -eq 1 ]] && { buf+="${main_cmd[\\-break_${(M)tmp#[0-9]}]:- }$tmp"; continue; } - [[ -z "${ice_cmd[$tmp]}" ]] && continue - # Also quote the ice modifier - local opt1="\"${ice_cmd[$tmp]}\"" - local opt2="'${ice_cmd[$tmp]}'" - [[ "${${opt1/\$ZPFX/}/\$/}" != "${opt1/\$ZPFX/}" || "${opt1/\\\!/}" != "$opt1" ]] && \ - buf+="${ice_cmd[\\-break_${(M)tmp#[0-9]}]:- }$name$opt2" || buf+="${ice_cmd[\\-break_${(M)tmp#[0-9]}]:- }$name$opt1" - fi - ;; - ((<->_|)(${(~j:|:)nval_ices})) - (( ${+ice_cmd[$tmp]} == 0 )) && continue - buf+="${ice_cmd[\\-break_${(M)tmp#[0-9]}]:- }$name" - ;; - esac + local name="$1" tmp="$2" + case "$tmp" in + ((<->_|)(${(~j:|:)${ice_order:|nval_ices}})) + if (( !own_quoting )); then + [[ -z "${ice_cmd[$tmp]}" && ${+ice_cmd[$tmp]} -eq 1 ]] && \ + { buf+="${ice_cmd[\\-break_${(M)tmp#[0-9]}]:- }$name"; continue; } + [[ -z "${ice_cmd[$tmp]}" ]] && continue + local apos="'" + local quoted="${ice_cmd[${tmp}_style]}${ice_cmd[$tmp]}${${${(M)ice_cmd[${tmp}_style]:#\$\'}:+$apos}:-${ice_cmd[${tmp}_style]:#[:=]}}" + buf+="${ice_cmd[\\-break_${(M)tmp#[0-9]}]:- }$name$quoted" + else + [[ -z "${ice_cmd[$tmp]}" && ${+ice_cmd[$tmp]} -eq 1 ]] && { buf+="${main_cmd[\\-break_${(M)tmp#[0-9]}]:- }$tmp"; continue; } + [[ -z "${ice_cmd[$tmp]}" ]] && continue + # Also quote the ice modifier + local opt1="\"${ice_cmd[$tmp]}\"" + local opt2="'${ice_cmd[$tmp]}'" + [[ "${${opt1/\$ZPFX/}/\$/}" != "${opt1/\$ZPFX/}" \ + # || "${opt1/\\\!/}" != "$opt1" + ]] && \ + buf+="${ice_cmd[\\-break_${(M)tmp#[0-9]}]:- }$name$opt2" || \ + buf+="${ice_cmd[\\-break_${(M)tmp#[0-9]}]:- }$name$opt1" + fi + ;; + ((<->_|)(${(~j:|:)nval_ices})) + (( ${+ice_cmd[$tmp]} == 0 )) && continue + buf+="${ice_cmd[\\-break_${(M)tmp#[0-9]}]:- }$name" + ;; + esac } # }}} # Utility functions @@ -965,175 +1093,226 @@ local -A theme # The function checks if $1 contains $3 unquoted, i.e. not within # ", ' or preceded by an (properly unquoted, of course) backslash .zsh-lint_has_unquoted() { - local workbuf="$1" needle="$2" qu_char result_str sep=$'|' - integer idx beg_idx has_sq=0 has_dq=0 has_bs=0 - while [[ $workbuf = (#b)([^\"\'\\]#)(([\"\'])|[\\](*))(*) ]]; do - [[ -z "$qu_char" && -n "${match[1]}" ]] && result_str+="$sep${match[1]}" - [[ -n ${match[4]} ]] && { - idx=${mbegin[2]} - # Optionally skip 1 quoted char - this implements the handling of the backslash quoting – when skipping the - # character we block it from further processing, so e.g. " will go unprocessed, or another backslash, etc. - if [[ $qu_char = \' ]]; then - workbuf=${match[4]} - else - [[ ${match[4]} != \\* ]] && has_bs=1 - idx=1 - workbuf=${match[4]:1} - fi - } || { - idx=${mbegin[2]} - workbuf=${match[5]} - # Toggle quoting - [[ ( ${match[2]} = \" && $qu_char != \' ) || ( ${match[2]} = \' && $qu_char != \" ) ]] && { - [[ $qu_char = [\"\'] ]] && { - # End of quoting - # Detected a single- or double-quoting at indices beg_idx,id (including the quotation characters) - [[ $qu_char = \" ]] && { - # Detected a double-quoting at indices beg_idx,idx - } - # The end-of-quoting proper algorithm action - qu_char= - ((1)) - } || { - # Beginning of quoting - beg_idx=itmp - # The beginning-of-quoting proper algorithm action - qu_char=${match[2]} - [[ "$qu_char" = \' ]] && has_sq=1 - [[ "$qu_char" = \" ]] && has_dq=1 + local workbuf="$1" needle="$2" qu_char result_str sep=$'|' + integer idx beg_idx has_sq=0 has_dq=0 has_bs=0 + while [[ $workbuf = (#b)([^\"\'\\]#)(([\"\'])|[\\](*))(*) ]]; do + [[ -z "$qu_char" && -n "${match[1]}" ]] && result_str+="$sep${match[1]}" + [[ -n ${match[4]} ]] && { + idx=${mbegin[2]} + # Optionally skip 1 quoted char - this implements the + # handling of the backslash quoting – when skipping the + # character we block it from further processing, so e.g. + # " will go unprocessed, or another backslash, etc. + if [[ $qu_char = \' ]]; then + workbuf=${match[4]} + else + [[ ${match[4]} != \\* ]] && has_bs=1 + idx=1 + workbuf=${match[4]:1} + fi + } || { + idx=${mbegin[2]} + workbuf=${match[5]} + # Toggle quoting + [[ ( ${match[2]} = \" && $qu_char != \' ) || ( ${match[2]} = \' && $qu_char != \" ) ]] && { + [[ $qu_char = [\"\'] ]] && { + # End of quoting + # Detected a single- or double-quoting at indices beg_idx,idx + # (including the quotation characters) + [[ $qu_char = \" ]] && { + # Detected a double-quoting at indices beg_idx,idx + } + # The end-of-quoting proper algorithm action + qu_char= + ((1)) + } || { + # Beginning of quoting + beg_idx=itmp + # The beginning-of-quoting proper algorithm action + qu_char=${match[2]} + [[ "$qu_char" = \' ]] && has_sq=1 + [[ "$qu_char" = \" ]] && has_dq=1 + } } - } - } + } done [[ $qu_char = [\"\'] ]] && { - # Detected a final single- or double-quoting at indices beg_idx,-1 - [[ $qu_char = \" ]] && { - # Detected a final double-quoting at indices beg_idx,-1 - } - ((1)) + # Detected a final single- or double-quoting at indices beg_idx,-1 + [[ $qu_char = \" ]] && { + # Detected a final double-quoting at indices beg_idx,-1 + } + ((1)) } || { - [[ -n "$workbuf" ]] && result_str+="$sep$workbuf" + [[ -n "$workbuf" ]] && result_str+="$sep$workbuf" } + # TODO: Full \, ' and " handling - [[ ( $needle = \' && $has_sq = 1 ) || ( $needle = \" && $has_dq = 1 ) || ( $needle = \\ && $has_bs = 1 ) || $result_str = *$needle* ]] -} # }}} + [[ ( $needle = \' && $has_sq = 1 ) || \ + ( $needle = \" && $has_dq = 1 ) || \ + ( $needle = \\ && $has_bs = 1 ) || \ + $result_str = *$needle* ]] +} +# }}} # FUNCTION: .zsh-lint_util_swap {{{ -# Swaps two variables given by name. Uses (P) substitution flag, can swap e.g. hash entries. For example: -# local -A hash_arr=( a b c d ) -# .zsh-lint_util_swap 'hash_arr[a]' 'hash_arr[b]' +# Swaps two variables given by name. Uses (P) substitution +# flag, can swap e.g. hash entries. For example: +# local -A hash_arr=( a b c d ) +# .zsh-lint_util_swap 'hash_arr[a]' 'hash_arr[b]' # # $1 - name of first variable to swap # $2 - name of second variable to swap .zsh-lint_util_swap() { - local __var_name1="$1" __var_name2="$2" __tmp - integer set1="${(P)+__var_name1}" set2="${(P)+__var_name2}" - if (( set1 && set2 )); then - tmp="${(P)__var_name1}" - : ${(P)__var_name1::=${(P)__var_name2}} - : ${(P)__var_name2::=$tmp} - elif (( set1 && !set2 )); then - : ${(P)__var_name2::=${(P)__var_name1}} - unset $__var_name1 - elif (( !set1 && set2 )); then - : ${(P)__var_name1::=${(P)__var_name2}} - unset $__var_name2 - fi -} # }}} + local __var_name1="$1" __var_name2="$2" __tmp + integer set1="${(P)+__var_name1}" set2="${(P)+__var_name2}" + + if (( set1 && set2 )); then + tmp="${(P)__var_name1}" + : ${(P)__var_name1::=${(P)__var_name2}} + : ${(P)__var_name2::=$tmp} + elif (( set1 && !set2 )); then + : ${(P)__var_name2::=${(P)__var_name1}} + unset $__var_name1 + elif (( !set1 && set2 )); then + : ${(P)__var_name1::=${(P)__var_name2}} + unset $__var_name2 + fi +} +# }}} # FUNCTION: .zsh-lint_action_move_up {{{ -# Moves given instance ($2) up, i.e. swaps current and previous instance. Using "instance" here means: ZUI's -# module instance representing single zi command (possibly preceded with "zi ice ..." invocation) -# by the design of Crasis. Instance = invocation of a generator with "module_idx" "instance_idx" arguments. +# Moves given instance ($2) up, i.e. swaps current and +# previous instance. Using "instance" here means: ZUI's +# module instance representing single zi command +# (possibly preceded with "zi ice ..." invocation) +# by the design of Crasis. Instance = invocation of a +# generator with "module_idx" "instance_idx" arguments. # # $1 - instance index .zsh-lint_action_move_up() { - local ice="$1" t - local -a cmd1 cmd2 - (( (ice-1-1)*3+1 > ${#cmdlist} || (ice-1)*3 > ${#cmdlist} || (ice-1)*3+1 > ${#cmdlist} || ice*3 > ${#cmdlist} )) && return 1 - cmd1=( "${(@)cmdlist[(ice-1-1)*3+1,(ice-1)*3]}" ) - cmd2=( "${(@)cmdlist[(ice-1)*3+1,ice*3]}" ) - cmdlist[(ice-1-1)*3+1,(ice-1)*3]=( "${cmd2[@]}" ) - cmdlist[(ice-1)*3+1,ice*3]=( "${cmd1[@]}" ) - return 0 + local ice="$1" t + + local -a cmd1 cmd2 + + (( (ice-1-1)*3+1 > ${#cmdlist} || (ice-1)*3 > ${#cmdlist} || (ice-1)*3+1 > ${#cmdlist} || ice*3 > ${#cmdlist} )) && return 1 + + cmd1=( "${(@)cmdlist[(ice-1-1)*3+1,(ice-1)*3]}" ) + cmd2=( "${(@)cmdlist[(ice-1)*3+1,ice*3]}" ) + + cmdlist[(ice-1-1)*3+1,(ice-1)*3]=( "${cmd2[@]}" ) + cmdlist[(ice-1)*3+1,ice*3]=( "${cmd1[@]}" ) + + return 0 } # }}} # FUNCTION: .zsh-lint_action_move_down {{{ -# Moves given instance ($2) down, i.e. swaps current and next instance. Using "instance" here means: ZUI's -# module instance representing single zi command (possibly preceded with "zi ice ..." invocation) -# by the design of Crasis. Instance = invocation of a generator with "module_idx" "instance_idx" arguments. +# Moves given instance ($2) down, i.e. swaps current and +# next instance. Using "instance" here means: ZUI's +# module instance representing single zi command +# (possibly preceded with "zi ice ..." invocation) +# by the design of Crasis. Instance = invocation of a +# generator with "module_idx" "instance_idx" arguments. # # $1 - instance index .zsh-lint_action_move_down() { - local ice="$1" t - local -a cmd1 cmd2 - (( (ice-1)*3+1 > ${#cmdlist} || ice*3 > ${#cmdlist} || (ice-1+1)*3+1 > ${#cmdlist} || (ice+1)*3 > ${#cmdlist} )) && return 1 - cmd1=( "${(@)cmdlist[(ice-1)*3+1,ice*3]}" ) - cmd2=( "${(@)cmdlist[(ice-1+1)*3+1,(ice+1)*3]}" ) - cmdlist[(ice-1)*3+1,ice*3]=( "${cmd2[@]}" ) - cmdlist[(ice-1+1)*3+1,(ice+1)*3]=( "${cmd1[@]}" ) - return 0 -} # }}} + local ice="$1" t + + local -a cmd1 cmd2 + + (( (ice-1)*3+1 > ${#cmdlist} || ice*3 > ${#cmdlist} || (ice-1+1)*3+1 > ${#cmdlist} || (ice+1)*3 > ${#cmdlist} )) && return 1 + + cmd1=( "${(@)cmdlist[(ice-1)*3+1,ice*3]}" ) + cmd2=( "${(@)cmdlist[(ice-1+1)*3+1,(ice+1)*3]}" ) + + cmdlist[(ice-1)*3+1,ice*3]=( "${cmd2[@]}" ) + cmdlist[(ice-1+1)*3+1,(ice+1)*3]=( "${cmd1[@]}" ) + + return 0 +} +# }}} # FUNCTION: .zsh-lint_action_add_snippet {{{ -# Adds snippet to $cmdlist. So it creates a packet with "zi snippet ..." main command. +# Adds snippet to $cmdlist. So it creates a packet with +# "zi snippet ..." main command. .zsh-lint_action_add_snippet() { - local url="${ZUI[my_tfield3_data]}" - local -A main_cmd - [[ -z "$url" || "$url" = \<*\> ]] && { -zui_std_stalog "" "" "" "WARNING: " "" "Aborted, please enter a proper snippet URL"; return; } - [[ "$url" != /* && "$url" != (http|ftp|https|OMZ|PZT):* ]] && -zui_std_stalog "" "" "" "WARNING: " "" "Snippet isn't absolute path or URL" - main_cmd[c]="zi" - main_cmd[sub]="snippet" - main_cmd[url]="$url" - /zsh-lint-dbg "Prepending snippet (data: ${(kv)main_cmd})" - cmdlist[1,0]=( "" "" "${(j: :)${(qkv)main_cmd[@]}}" ) -} # }}} + local url="${ZUI[my_tfield3_data]}" + local -A main_cmd + + [[ -z "$url" || "$url" = \<*\> ]] && { -zui_std_stalog "" "" "" "WARNING: " "" "Aborted, please enter a proper snippet URL"; return; } + [[ "$url" != /* && "$url" != (http|ftp|https|OMZ|PZT):* ]] && -zui_std_stalog "" "" "" "WARNING: " "" "Snippet isn't absolute path or URL" + + main_cmd[c]="zi" + main_cmd[sub]="snippet" + main_cmd[url]="$url" + + /zsh-lint-dbg "Prepending snippet (data: ${(kv)main_cmd})" + + cmdlist[1,0]=( "" "" "${(j: :)${(qkv)main_cmd[@]}}" ) +} +# }}} # FUNCTION: .zsh-lint_action_add_plugin {{{ # Adds plugin to $cmdlist. So it creates a packet with # "zi load/light ..." main command. Ice command can be # added by editing actions. .zsh-lint_action_add_plugin() { - local url="${ZUI[my_tfield3_data]}" - local -A main_cmd - [[ -z "$url" ]] && { /zsh-lint-dbg "WARNING: Plugin addition aborted, please provide a proper plugin ID"; return 1; } - [[ "$url" != [a-zA-Z0-9-]##/[a-zA-Z0-9-]## ]] && /zsh-lint-dbg "WARNING: Plugin ID isn't of the form \`user/plugin'" - main_cmd[c]="zi" - main_cmd[sub]="load" - main_cmd[url]="$url" - /zsh-lint-dbg "Prepending plugin (data: ${(kv)main_cmd})" - cmdlist[1,0]=( "" "" "${(j: :)${(qkv)main_cmd[@]}}" ) -} # }}} + local url="${ZUI[my_tfield3_data]}" + local -A main_cmd + + [[ -z "$url" ]] && { /zsh-lint-dbg "WARNING: Plugin addition aborted, please provide a proper plugin ID"; return 1; } + [[ "$url" != [a-zA-Z0-9-]##/[a-zA-Z0-9-]## ]] && /zsh-lint-dbg "WARNING: Plugin ID isn't of the form \`user/plugin'" + + main_cmd[c]="zi" + main_cmd[sub]="load" + main_cmd[url]="$url" + + /zsh-lint-dbg "Prepending plugin (data: ${(kv)main_cmd})" + + cmdlist[1,0]=( "" "" "${(j: :)${(qkv)main_cmd[@]}}" ) +} +# }}} # FUNCTION: .zsh-lint_action_reload {{{ -# Ran at startup. Performs full zshrc processing, recognizes other and zi-related zshrc parts, it regenerates the +# Ran at startup. Performs full zshrc processing, recognizes +# other and zi-related zshrc parts, it regenerates the # backend model-structure, the $cmdlist array of hashes. .zsh-lint_action_reload() { - local id="$1" - float -F 2 sum=0.0 - local -a stats - reply=() - /zsh-lint-dbg "Loading \`$zshrc_path'..." - zshrc="$(<$zshrc_path)"$'\ntest' - /zsh-lint-dbg "Read bytes ( lines)" - /zsh-lint-dbg "" - +zsh-lint-info "-Parsing zshrc..." - cmdlist=() - coidx=1 - stats=() - [[ -n "$id" ]] && { /zsh-lint-dbg "[Reload] pressed"; /zsh-lint-dbg ""; } - typeset -F 2 SECONDS=0 - .zsh-lint-tokenize-zsh-rc - .zsh-lint_verify_tokenization || { .zsh-lint_tokenization_failed; return 1; } - stats+=( "Tokenization time: $SECONDS seconds" ) - sum+=SECONDS - SECONDS=0 - .zsh-lint-process-zsh-rc - stats+=( "Main processing time: $SECONDS seconds" ) - sum+=SECONDS - SECONDS=0 - .zsh-lint-process-zi-commands - stats+=( "ZI-commands processing time: $SECONDS seconds" ) - sum+=SECONDS - /zsh-lint-dbg-array "${stats[@]}" "Total time: $sum seconds" "" - /zsh-lint-dbg "Read \`$zshrc_path' (time: $sum sec)" - return 0 -} # }}} + local id="$1" + float -F 2 sum=0.0 + local -a stats + + reply=() + + /zsh-lint-dbg "Loading \`$zshrc_path'..." + zshrc="$(<$zshrc_path)"$'\ntest' + /zsh-lint-dbg "Read bytes ( lines)" + /zsh-lint-dbg "" + + +zsh-lint-info "-Parsing zshrc..." + cmdlist=() + coidx=1 + stats=() + + [[ -n "$id" ]] && { /zsh-lint-dbg "[Reload] pressed"; /zsh-lint-dbg ""; } + + typeset -F 2 SECONDS=0 + .zsh-lint-tokenize-zsh-rc + .zsh-lint_verify_tokenization || { .zsh-lint_tokenization_failed; return 1; } + stats+=( "Tokenization time: $SECONDS seconds" ) + sum+=SECONDS + + SECONDS=0 + .zsh-lint-process-zsh-rc + stats+=( "Main processing time: $SECONDS seconds" ) + sum+=SECONDS + + SECONDS=0 + .zsh-lint-process-zi-commands + stats+=( "ZI-commands processing time: $SECONDS seconds" ) + sum+=SECONDS + + /zsh-lint-dbg-array "${stats[@]}" "Total time: $sum seconds" "" + + /zsh-lint-dbg "Read \`$zshrc_path' (time: $sum sec)" + + return 0 +} +# }}} # # The exposed via options actions @@ -1141,140 +1320,168 @@ local -A theme # FUNCTION: .zsh-lint_action_disable {{{ .zsh-lint_action_disable() { - local input="$1" idx - reply=() - .zsh-lint_search_zi "$input" - for idx in "${reply[@]}"; do - (( idx*3 > ${#cmdlist} || idx <= 0 )) && { /zsh-lint-dbg "[disable] IMPROPER INDEX {$idx}/{${#cmdlist}}"; continue; } - .zsh-lint_update_ice_cmd disabled 1 "$idx" - .zsh-lint_update_main_cmd disabled 1 "$idx" - done - return 0 -} # }}} + local input="$1" idx + reply=() + .zsh-lint_search_zinit "$input" + for idx in "${reply[@]}"; do + (( idx*3 > ${#cmdlist} || idx <= 0 )) && \ + { /zsh-lint-dbg "[disable] IMPROPER INDEX {$idx}/{${#cmdlist}}"; continue; } + .zsh-lint_update_ice_cmd disabled 1 "$idx" + .zsh-lint_update_main_cmd disabled 1 "$idx" + done + return 0 +} +# }}} # FUNCTION: .zsh-lint_action_enable {{{ .zsh-lint_action_enable() { - local input="$1" idx - reply=() - .zsh-lint_search_zi "$input" - for idx in "${reply[@]}"; do - (( idx*3 > ${#cmdlist} || idx <= 0 )) && { /zsh-lint-dbg "[enable] IMPROPER INDEX {$idx}/{${#cmdlist}}"; continue; } - .zsh-lint_update_ice_cmd disabled 0 "$idx" - .zsh-lint_update_main_cmd disabled 0 "$idx" - done - return 0 + local input="$1" idx + reply=() + .zsh-lint_search_zinit "$input" + for idx in "${reply[@]}"; do + (( idx*3 > ${#cmdlist} || idx <= 0 )) && \ + { /zsh-lint-dbg "[enable] IMPROPER INDEX {$idx}/{${#cmdlist}}"; continue; } + .zsh-lint_update_ice_cmd disabled 0 "$idx" + .zsh-lint_update_main_cmd disabled 0 "$idx" + done + return 0 } # }}} # FUNCTION: .zsh-lint_action_toggle {{{ .zsh-lint_action_toggle() { - local input="$1" idx - reply=() - .zsh-lint_search_zi "$input" - for idx in "${reply[@]}"; do - (( idx*3 > ${#cmdlist} || idx <= 0 )) && { /zsh-lint-dbg "[toggle] IMPROPER INDEX {$idx}/{${#cmdlist}}"; continue; } - .zsh-lint_update_ice_cmd disabled 'toggle' "$idx" - .zsh-lint_update_main_cmd disabled 'toggle' "$idx" - done - return 0 -} # }}} + local input="$1" idx + reply=() + .zsh-lint_search_zinit "$input" + for idx in "${reply[@]}"; do + (( idx*3 > ${#cmdlist} || idx <= 0 )) && \ + { /zsh-lint-dbg "[toggle] IMPROPER INDEX {$idx}/{${#cmdlist}}"; continue; } + .zsh-lint_update_ice_cmd disabled 'toggle' "$idx" + .zsh-lint_update_main_cmd disabled 'toggle' "$idx" + done + return 0 +} +# }}} # FUNCTION: .zsh-lint_action_purge {{{ -# Finds and removes entries from $cmdlist $1 - pattern to be searched with +# Finds and removes entries from $cmdlist +# $1 - pattern to be searched with .zsh-lint_action_purge() { - local input="$1" idx - reply=() - .zsh-lint_search_zi "$input" - for idx in "${(Oa)reply[@]}"; do - (( idx*3 > ${#cmdlist} || idx <= 0 )) && { /zsh-lint-dbg "[purge] IMPROPER INDEX {$idx}/{${#cmdlist}}"; continue; } - cmdlist[(idx-1)*3+1,idx*3]=() - done - return 0 -} # }}} + local input="$1" idx + reply=() + .zsh-lint_search_zinit "$input" + for idx in "${(Oa)reply[@]}"; do + (( idx*3 > ${#cmdlist} || idx <= 0 )) && \ + { /zsh-lint-dbg "[purge] IMPROPER INDEX {$idx}/{${#cmdlist}}"; continue; } + cmdlist[(idx-1)*3+1,idx*3]=() + done + return 0 +} +# }}} # FUNCTION: .zsh-lint_action_add {{{ -# Example call: zsh-lint --zp-add [ wait'0' lucid pick'/dev/null' ] z-shell/null +# Example call: +# zsh-lint --zp-add [ wait'0' lucid pick'/dev/null' ] z-shell/null .zsh-lint_action_add() { - local -a args ices plugspec - integer idx - args=( "$@" ) - # Quoted ices as single string? In general it's the first method of - # invocation used by -AA short-style option. - # -> impose the uniform input type - if [[ "$1" = (#b)\[(*)\](*) ]]; then - ices=( ${(z@)match[1]} ) - args[1]=( ${ices[@]} ) - [[ -n "${match[2]}" && "${match[2]}" != [[:space:]]## ]] && \ - { plugspec="${match[2]//((#s)[[:space:]]##|[[:space:]]##(#e))/}" - (( ${#args} > ${#ices} )) && +zsh-lint-warn "Ignoring: ${args[${#ices}+1,-1]}" - ((1)) - } || \ - { plugspec="${2//((#s)[[:space:]]##|[[:space:]]##(#e))/}" - (( ${#args} > ${#ices}+1 )) && +zsh-lint-warn "Ignoring: ${args[${#ices}+2,-1]}" - } - args[${#ices}+1,-1]=() - elif [[ "$1" = (#b)[[:space:]]#\[(*) ]]; then - [[ -n "${match[1]}" && "${match[1]}" != [[:space:]]## ]] && { - args[1]="${match[1]}" - } || { - args[1]=() - } - else - plugspec="${1//((#s)[[:space:]]##|[[:space:]]##(#e))/}" - (( ${#args} > 1 )) && +zsh-lint-warn "Ignoring: ${args[2,-1]}" - args=() - fi - local -a tmp - if [[ -z "$plugspec" ]]; then - # Also decode possible mistakes/ignorances - if [[ 0 != ${idx::=${args[(I)(#b)(*)\](*)]}} ]]; then - if [[ -n "${match[1]}" && "${match[1]}" != [[:space:]]## ]]; then - args[idx]="${match[1]//((#s)[[:space:]]##|[[:space:]]##(#e))/}" - else - args[idx]=() - idx+=-1 - fi - if [[ -n "${match[2]}" && "${match[2]}" != [[:space:]]## ]]; then - plugspec="${match[2]//((#s)[[:space:]]##|[[:space:]]##(#e))/}" - (( ${#args} > idx )) && +zsh-lint-warn "Ignoring: ${args[idx+1,-1]}" - else - plugspec="${args[idx+1]//((#s)[[:space:]]##|[[:space:]]##(#e))/}" - (( ${#args} > idx+1 )) && +zsh-lint-warn "Ignoring: ${args[idx+2,-1]}" - fi - args[idx+1,-1]=() + local -a args ices plugspec + integer idx + args=( "$@" ) + # Quoted ices as single string? In general it's the first method of + # invocation used by -AA short-style option. + # -> impose the uniform input type + if [[ "$1" = (#b)\[(*)\](*) ]]; then + ices=( ${(z@)match[1]} ) + args[1]=( ${ices[@]} ) + [[ -n "${match[2]}" && "${match[2]}" != [[:space:]]## ]] && \ + { plugspec="${match[2]//((#s)[[:space:]]##|[[:space:]]##(#e))/}" + (( ${#args} > ${#ices} )) && +zsh-lint-warn "Ignoring: ${args[${#ices}+1,-1]}" + ((1)) + } || \ + { plugspec="${2//((#s)[[:space:]]##|[[:space:]]##(#e))/}" + (( ${#args} > ${#ices}+1 )) && +zsh-lint-warn "Ignoring: ${args[${#ices}+2,-1]}" + } + args[${#ices}+1,-1]=() + elif [[ "$1" = (#b)[[:space:]]#\[(*) ]]; then + [[ -n "${match[1]}" && "${match[1]}" != [[:space:]]## ]] && { + args[1]="${match[1]}" + } || { + args[1]=() + } + else + plugspec="${1//((#s)[[:space:]]##|[[:space:]]##(#e))/}" + (( ${#args} > 1 )) && +zsh-lint-warn "Ignoring: ${args[2,-1]}" + args=() fi - fi - [[ -z "$plugspec" ]] && { +zsh-lint-error "No plugin or snippet URL given, aborting"; return 1; } - # Parse ice-modifiers and build-up the command - local -A cmd3_ice cmd3_main - local icestr - cmd3_ice=( c zi sub ice ) - cmd3_main=( c zi sub load url "$plugspec" ) - for icestr in ${args[@]}; do - [[ "$icestr" = (#b)(${(~j:|:)ice_order})(?)(?)(*)(?) || "$icestr" = (#b)(${(~j:|:)ice_order})(*) ]] && { - local key="${match[1]}" string="${match[2]#[:=]}${match[3]}${match[4]}${match[5]}" - local second="${match[2]}" third="${match[3]}" last="${match[5]}" - cmd3_ice[$key]="${string//(#b)(:|=|)([\"\']|\$\'|)(*)[\"\']/${match[3]}}" - # Remember also the quoting style, if the quoting - # is a recognized one - [[ ( -n "$second" && "$second" = "$last" ) || "$second" = (:|=)* ]] && cmd3_ice[${key}_style]="${second[1]}" || if [[ "$second" = '$' && "$third" = "'" ]]; then - cmd3_ice[${key}_style]="\$'" - # Contains an unquoted "? - elif .zsh-lint_has_unquoted "$string" \"; then - cmd3_ice[${key}_style]=\' - elif .zsh-lint_has_unquoted "$string" \'; then - cmd3_ice[${key}_style]=\" - elif .zsh-lint_has_unquoted \ - "${string//\$(\{|)ZPFX(\}|)/}" \$ - then - cmd3_ice[${key}_style]=\' - else - cmd3_ice[${key}_style]=\" + + local -a tmp + if [[ -z "$plugspec" ]]; then + # Also decode possible mistakes/ignorances + if [[ 0 != ${idx::=${args[(I)(#b)(*)\](*)]}} ]]; then + if [[ -n "${match[1]}" && "${match[1]}" != [[:space:]]## ]]; then + args[idx]="${match[1]//((#s)[[:space:]]##|[[:space:]]##(#e))/}" + else + args[idx]=() + idx+=-1 + fi + if [[ -n "${match[2]}" && "${match[2]}" != [[:space:]]## ]]; then + plugspec="${match[2]//((#s)[[:space:]]##|[[:space:]]##(#e))/}" + (( ${#args} > idx )) && +zsh-lint-warn "Ignoring: ${args[idx+1,-1]}" + else + plugspec="${args[idx+1]//((#s)[[:space:]]##|[[:space:]]##(#e))/}" + (( ${#args} > idx+1 )) && +zsh-lint-warn "Ignoring: ${args[idx+2,-1]}" + fi + args[idx+1,-1]=() fi - } - done - cmdlist+=( "" "${(j: :)${(qkv)cmd3_ice[@]}}" "${(j: :)${(qkv)cmd3_main[@]}}" ) -} # }}} + fi + + [[ -z "$plugspec" ]] && \ + { +zsh-lint-error "No plugin or snippet URL given, aborting"; return 1; } + + # Parse ice-modifiers and build-up the command + local -A cmd3_ice cmd3_main + local icestr + cmd3_ice=( c zi sub ice ) + cmd3_main=( c zi sub load url "$plugspec" ) + for icestr in ${args[@]}; do + [[ "$icestr" = (#b)(${(~j:|:)ice_order})(?)(?)(*)(?) || \ + "$icestr" = (#b)(${(~j:|:)ice_order})(*) + ]] && { + local key="${match[1]}" string="${match[2]#[:=]}${match[3]}${match[4]}${match[5]}" + local second="${match[2]}" third="${match[3]}" last="${match[5]}" + cmd3_ice[$key]="${string//(#b)(:|=|)([\"\']|\$\'|)(*)[\"\']/${match[3]}}" + # Remember also the quoting style, if the quoting + # is a recognized one + [[ ( -n "$second" && "$second" = "$last" ) || \ + "$second" = (:|=)* # Via the star this handles both matchings + ]] && \ + cmd3_ice[${key}_style]="${second[1]}" || \ + if [[ "$second" = '$' && "$third" = "'" ]]; then + cmd3_ice[${key}_style]="\$'" + # Contains an unquoted "? + elif .zsh-lint_has_unquoted "$string" \"; then + cmd3_ice[${key}_style]=\' + elif .zsh-lint_has_unquoted "$string" \'; then + cmd3_ice[${key}_style]=\" + elif .zsh-lint_has_unquoted \ + "${string//\$(\{|)ZPFX(\}|)/}" \$ + then + cmd3_ice[${key}_style]=\' + else + cmd3_ice[${key}_style]=\" + fi + } + done + + cmdlist+=( "" "${(j: :)${(qkv)cmd3_ice[@]}}" "${(j: :)${(qkv)cmd3_main[@]}}" ) +} +# }}} + [[ -n "$OPT_DISABLE$OPT_ENABLE$OPT_TOGGLE$OPT_ADD$OPT_ADD_FLAG$OPT_PURGE" ]] && { - +zsh-lint-msg -rl ${OPT_DISABLE:+GRDisableRS: ${(j:, :)OPT_DISABLE}} ${OPT_ENABLE:+GREnableRS: ${(j:, :)OPT_ENABLE}} ${OPT_TOGGLE:+GRToggleRS: ${(j:, :)OPT_TOGGLE}} \ - ${${:-$OPT_ADD$OPT_ADD_FLAG}:+GRAddRS: ${(j:, :)OPT_ADD}${${OPT_ADD:+${OPT_ADD_FLAG:+, }}}}${OPT_ADD_FLAG:+${(j: :)@}} ${OPT_PURGE:+REPurgeRS: ${(j:, :)OPT_PURGE}} + +zsh-lint-msg -rl \ + ${OPT_DISABLE:+GRDisableRS: ${(j:, :)OPT_DISABLE}} \ + ${OPT_ENABLE:+GREnableRS: ${(j:, :)OPT_ENABLE}} \ + ${OPT_TOGGLE:+GRToggleRS: ${(j:, :)OPT_TOGGLE}} \ + ${${:-$OPT_ADD$OPT_ADD_FLAG}:+GRAddRS: ${(j:, :)OPT_ADD}${${OPT_ADD:+${OPT_ADD_FLAG:+, }}}}\ +${OPT_ADD_FLAG:+${(j: :)@}} \ + ${OPT_PURGE:+REPurgeRS: ${(j:, :)OPT_PURGE}} } + [[ -n "$OPT_HELP" ]] && { .zsh-lint-usage; return 0; } # @@ -1285,30 +1492,31 @@ local -A theme .zsh-lint_action_reload || return 1 for input in "${OPT_DISABLE[@]}"; do - .zsh-lint_action_disable "$input" + .zsh-lint_action_disable "$input" done for input in "${OPT_ENABLE[@]}"; do - .zsh-lint_action_enable "$input" + .zsh-lint_action_enable "$input" done for input in "${OPT_TOGGLE[@]}"; do - .zsh-lint_action_toggle "$input" + .zsh-lint_action_toggle "$input" done for input in "${OPT_ADD[@]}"; do - .zsh-lint_action_add "$input" + .zsh-lint_action_add "$input" done # Allow free, unquoted style of plugin specification [[ -n "$OPT_ADD_FLAG" ]] && .zsh-lint_action_add "$@" for input in "${OPT_PURGE[@]}"; do - .zsh-lint_action_purge "$input" + .zsh-lint_action_purge "$input" done local dest="${${OPT_OUT[1]:+${OPT_OUT[1]}}:-${zshrc_path}_gen}" -.zsh-lint_save "$dest" && +zsh-lint-info -"Saved the modified zshrc to: ${dest/(#s)$HOME/~}" || \ -+zsh-lint-error "Couldn't save the modified zshrc to: ${dest/(#s)$HOME/~}" #/zsh-lint-dbg-print-out +.zsh-lint_save "$dest" && \ + +zsh-lint-info -"Saved the modified zshrc to: ${dest/(#s)$HOME/~}" || \ + +zsh-lint-error "Couldn't save the modified zshrc to: ${dest/(#s)$HOME/~}" #/zsh-lint-dbg-print-out return 0