diff --git a/CHANGELOG.md b/CHANGELOG.md index fb15b88..3134f5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v0.4.2 +- Fix bug in zsh versions older than 5.0.8 (#296) +- Officially support back to zsh v4.3.11 + ## v0.4.1 - Switch to [[ and (( conditionals instead of [ (#257) - Avoid warnnestedvar warnings with `typeset -g` (#275) diff --git a/Makefile b/Makefile index 5402b7f..d5d162c 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ SRC_DIR := ./src SRC_FILES := \ $(SRC_DIR)/setup.zsh \ $(SRC_DIR)/config.zsh \ + $(SRC_DIR)/util.zsh \ $(SRC_DIR)/features.zsh \ $(SRC_DIR)/bind.zsh \ $(SRC_DIR)/highlight.zsh \ diff --git a/README.md b/README.md index 8aedb31..6a6ae1e 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ It suggests commands as you type, based on command history. ## Installation +Requirements: Zsh v4.3.11 or later + ### Manual 1. Clone this repository somewhere on your machine. This guide will assume `~/.zsh/zsh-autosuggestions`. diff --git a/VERSION b/VERSION index 5aff472..0eec13e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v0.4.1 +v0.4.2 diff --git a/circle.yml b/circle.yml index 5e3a6f6..d0f7633 100644 --- a/circle.yml +++ b/circle.yml @@ -1,12 +1,12 @@ machine: environment: - ZSH_VERSIONS: 5.0.8 5.1.1 5.2 5.3.1 + ZSH_VERSIONS: zsh-dev/4.3.11 zsh/5.0.2 zsh/5.0.8 zsh/5.1.1 zsh/5.2 zsh/5.3.1 zsh/5.4.2 dependencies: pre: - - for v in $(echo $ZSH_VERSIONS | awk "{ for (i=$((1+CIRCLE_NODE_INDEX));i<=NF;i+=$CIRCLE_NODE_TOTAL) print \$i }"); do wget https://sourceforge.net/projects/zsh/files/zsh/$v/zsh-$v.tar.gz && tar xzf zsh-$v.tar.gz && cd zsh-$v && ./configure && sudo make install || exit 1; done + - for v in $(echo $ZSH_VERSIONS | awk "{ for (i=$((1+CIRCLE_NODE_INDEX));i<=NF;i+=$CIRCLE_NODE_TOTAL) print \$i }"); do wget https://sourceforge.net/projects/zsh/files/$v/zsh-${v#*/}.tar.gz && tar xzf zsh-${v#*/}.tar.gz && pushd zsh-${v#*/} && ./configure && sudo make install && popd || exit 1; done test: override: - - for v in $(echo $ZSH_VERSIONS | awk "{ for (i=$((1+CIRCLE_NODE_INDEX));i<=NF;i+=$CIRCLE_NODE_TOTAL) print \$i }"); do TEST_ZSH_BIN=/usr/local/bin/zsh-$v make test || exit 1; done: + - for v in $(echo $ZSH_VERSIONS | awk "{ for (i=$((1+CIRCLE_NODE_INDEX));i<=NF;i+=$CIRCLE_NODE_TOTAL) print \$i }"); do TEST_ZSH_BIN=/usr/local/bin/zsh-${v#*/} make test || exit 1; done: parallel: true diff --git a/src/strategies/default.zsh b/src/strategies/default.zsh index 17bf65d..0e85fb5 100644 --- a/src/strategies/default.zsh +++ b/src/strategies/default.zsh @@ -7,9 +7,19 @@ # _zsh_autosuggest_strategy_default() { - local prefix="$1" + # Reset options to defaults and enable LOCAL_OPTIONS + emulate -L zsh + + # Enable globbing flags so that we can use (#m) + setopt EXTENDED_GLOB + + # Escape backslashes and all of the glob operators so we can use + # this string as a pattern to search the $history associative array. + # - (#m) globbing flag enables setting references for match data + # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8 + local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" # Get the history items that match # - (r) subscript flag makes the pattern match on values - typeset -g suggestion="${history[(r)${(b)prefix}*]}" + typeset -g suggestion="${history[(r)${prefix}*]}" } diff --git a/src/strategies/match_prev_cmd.zsh b/src/strategies/match_prev_cmd.zsh index 8a84728..f76d3c1 100644 --- a/src/strategies/match_prev_cmd.zsh +++ b/src/strategies/match_prev_cmd.zsh @@ -21,18 +21,25 @@ # `HIST_EXPIRE_DUPS_FIRST`. _zsh_autosuggest_strategy_match_prev_cmd() { - local prefix="$1" + # Reset options to defaults and enable LOCAL_OPTIONS + emulate -L zsh + + # Enable globbing flags so that we can use (#m) + setopt EXTENDED_GLOB + + # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8 + local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" # Get all history event numbers that correspond to history # entries that match pattern $prefix* local history_match_keys - history_match_keys=(${(k)history[(R)${(b)prefix}*]}) + history_match_keys=(${(k)history[(R)$prefix*]}) # By default we use the first history number (most recent history entry) local histkey="${history_match_keys[1]}" # Get the previously executed command - local prev_cmd="${history[$((HISTCMD-1))]}" + local prev_cmd="$(_zsh_autosuggest_escape_command "${history[$((HISTCMD-1))]}")" # Iterate up to the first 200 history event numbers that match $prefix for key in "${(@)history_match_keys[1,200]}"; do diff --git a/src/util.zsh b/src/util.zsh new file mode 100644 index 0000000..1f55d36 --- /dev/null +++ b/src/util.zsh @@ -0,0 +1,11 @@ + +#--------------------------------------------------------------------# +# Utility Functions # +#--------------------------------------------------------------------# + +_zsh_autosuggest_escape_command() { + setopt localoptions EXTENDED_GLOB + + # Escape special chars in the string (requires EXTENDED_GLOB) + echo -E "${1//(#m)[\"\'\\()\[\]|*?~]/\\$MATCH}" +} diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index fadbac7..ccfe07e 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -1,6 +1,6 @@ # Fish-like fast/unobtrusive autosuggestions for zsh. # https://github.com/zsh-users/zsh-autosuggestions -# v0.4.1 +# v0.4.2 # Copyright (c) 2013 Thiago de Arruda # Copyright (c) 2016-2017 Eric Freese # @@ -101,6 +101,17 @@ ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE= # Pty name for calculating autosuggestions asynchronously ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_pty +#--------------------------------------------------------------------# +# Utility Functions # +#--------------------------------------------------------------------# + +_zsh_autosuggest_escape_command() { + setopt localoptions EXTENDED_GLOB + + # Escape special chars in the string (requires EXTENDED_GLOB) + echo -E "${1//(#m)[\"\'\\()\[\]|*?~]/\\$MATCH}" +} + #--------------------------------------------------------------------# # Feature Detection # #--------------------------------------------------------------------# @@ -478,11 +489,21 @@ zle -N autosuggest-toggle _zsh_autosuggest_widget_toggle # _zsh_autosuggest_strategy_default() { - local prefix="$1" + # Reset options to defaults and enable LOCAL_OPTIONS + emulate -L zsh + + # Enable globbing flags so that we can use (#m) + setopt EXTENDED_GLOB + + # Escape backslashes and all of the glob operators so we can use + # this string as a pattern to search the $history associative array. + # - (#m) globbing flag enables setting references for match data + # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8 + local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" # Get the history items that match # - (r) subscript flag makes the pattern match on values - typeset -g suggestion="${history[(r)${(b)prefix}*]}" + typeset -g suggestion="${history[(r)${prefix}*]}" } #--------------------------------------------------------------------# @@ -507,18 +528,25 @@ _zsh_autosuggest_strategy_default() { # `HIST_EXPIRE_DUPS_FIRST`. _zsh_autosuggest_strategy_match_prev_cmd() { - local prefix="$1" + # Reset options to defaults and enable LOCAL_OPTIONS + emulate -L zsh + + # Enable globbing flags so that we can use (#m) + setopt EXTENDED_GLOB + + # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8 + local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" # Get all history event numbers that correspond to history # entries that match pattern $prefix* local history_match_keys - history_match_keys=(${(k)history[(R)${(b)prefix}*]}) + history_match_keys=(${(k)history[(R)$prefix*]}) # By default we use the first history number (most recent history entry) local histkey="${history_match_keys[1]}" # Get the previously executed command - local prev_cmd="${history[$((HISTCMD-1))]}" + local prev_cmd="$(_zsh_autosuggest_escape_command "${history[$((HISTCMD-1))]}")" # Iterate up to the first 200 history event numbers that match $prefix for key in "${(@)history_match_keys[1,200]}"; do