From 046b9051c3ce2cdb4dac94c51ced4ab7504781a7 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 28 Feb 2016 18:11:18 +0000 Subject: [PATCH] Add option for matching on previous command. Setting ZSH_AUTOSUGGEST_MATCH_PREV_CMD will enable the plug-in to be a bit more context aware when generating the suggestion, by matching the previously executed command against the command executed before the preferred suggestion. See src/config.zsh for an example. Also added testing for the suggestion computation. --- script/test.zsh | 139 ++++++++++++++++++++++++++++++++++++++++ src/config.zsh | 12 ++++ src/suggestion.zsh | 38 +++++++++-- zsh-autosuggestions.zsh | 50 +++++++++++++-- 4 files changed, 229 insertions(+), 10 deletions(-) diff --git a/script/test.zsh b/script/test.zsh index 45f3dbc..505741d 100755 --- a/script/test.zsh +++ b/script/test.zsh @@ -3,12 +3,151 @@ SCRIPT_DIR=$(dirname "$0") TEST_DIR=$SCRIPT_DIR/../test DIST_DIR=$SCRIPT_DIR/../ +TMPHIST_FILE=/tmp/zsh-autosuggestions-test-tmp-hist # Use stub.sh for stubbing/mocking source $TEST_DIR/stub-1.0.2.sh source $DIST_DIR/zsh-autosuggestions.zsh +#--------------------------------------------------------------------# +# Suggestions # +#--------------------------------------------------------------------# + +testSuggestionSimple() { + HISTSIZE=0 # Clear history + HISTSIZE=10 + + cat > $TMPHIST_FILE <<-EOH + one + two + three + four + five + EOH + echo >> $TMPHIST_FILE + + fc -R $TMPHIST_FILE + + rm $TMPHIST_FILE + + unset ZSH_AUTOSUGGEST_MATCH_PREV_CMD + + assertEquals \ + "Did not pick correct suggestion for prefix 'garbage'" \ + "" \ + "$(_zsh_autosuggest_suggestion garbage)" + + assertEquals \ + "Did not pick correct suggestion for prefix 'o'" \ + "one" \ + "$(_zsh_autosuggest_suggestion o)" + + assertEquals \ + "Did not pick correct suggestion for prefix 't'" \ + "three" \ + "$(_zsh_autosuggest_suggestion t)" + + assertEquals \ + "Did not pick correct suggestion for prefix 'tw'" \ + "two" \ + "$(_zsh_autosuggest_suggestion tw)" + + assertEquals \ + "Did not pick correct suggestion for prefix 'f'" \ + "five" \ + "$(_zsh_autosuggest_suggestion f)" + + assertEquals \ + "Did not pick correct suggestion for prefix 'fo'" \ + "four" \ + "$(_zsh_autosuggest_suggestion fo)" +} + +testSuggestionMatchPrevCmd() { + HISTSIZE=0 # Clear history + HISTSIZE=10 + + cat > $TMPHIST_FILE <<-EOH + one + two + three + four + five + EOH + echo >> $TMPHIST_FILE + + fc -R $TMPHIST_FILE + + rm $TMPHIST_FILE + + ZSH_AUTOSUGGEST_MATCH_PREV_CMD=1 + + stub_and_echo _zsh_autosuggest_prev_cmd "one" + + assertEquals \ + "Did not pick correct suggestion for prefix 'garbage' after 'one'" \ + "" \ + "$(_zsh_autosuggest_suggestion garbage)" + + assertEquals \ + "Did not pick correct suggestion for prefix 'o' after 'one'" \ + "one" \ + "$(_zsh_autosuggest_suggestion o)" + + assertEquals \ + "Did not pick correct suggestion for prefix 't' after 'one'" \ + "two" \ + "$(_zsh_autosuggest_suggestion t)" + + assertEquals \ + "Did not pick correct suggestion for prefix 'th' after 'one'" \ + "three" \ + "$(_zsh_autosuggest_suggestion th)" + + assertEquals \ + "Did not pick correct suggestion for prefix 'f' after 'one'" \ + "five" \ + "$(_zsh_autosuggest_suggestion f)" + + assertEquals \ + "Did not pick correct suggestion for prefix 'fo' after 'one" \ + "four" \ + "$(_zsh_autosuggest_suggestion fo)" + + stub_and_echo _zsh_autosuggest_prev_cmd "two" + + assertEquals \ + "Did not pick correct suggestion for prefix 'garbage' after 'two'" \ + "" \ + "$(_zsh_autosuggest_suggestion garbage)" + + assertEquals \ + "Did not pick correct suggestion for prefix 'o' after 'two'" \ + "one" \ + "$(_zsh_autosuggest_suggestion o)" + + assertEquals \ + "Did not pick correct suggestion for prefix 't' after 'two'" \ + "three" \ + "$(_zsh_autosuggest_suggestion t)" + + assertEquals \ + "Did not pick correct suggestion for prefix 'tw' after 'two'" \ + "two" \ + "$(_zsh_autosuggest_suggestion tw)" + + assertEquals \ + "Did not pick correct suggestion for prefix 'f' after 'two'" \ + "five" \ + "$(_zsh_autosuggest_suggestion f)" + + assertEquals \ + "Did not pick correct suggestion for prefix 'fo' after 'two" \ + "four" \ + "$(_zsh_autosuggest_suggestion fo)" +} + #--------------------------------------------------------------------# # Highlighting # #--------------------------------------------------------------------# diff --git a/src/config.zsh b/src/config.zsh index c8d6f0a..a79781d 100644 --- a/src/config.zsh +++ b/src/config.zsh @@ -8,6 +8,18 @@ # More info: http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Zle-Widgets ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8' +# Set this to enable matching the history entry preceding the suggestion +# against the previously executed command. +# For example, if your have just executed: +# pwd +# ls foo +# ls bar +# pwd +# And then you start typing 'ls', then the suggestion will be 'ls foo', +# rather than 'ls bar', as your most recently executed command (pwd) +# was succeeded by 'ls foo' on it's previous invocation. +unset ZSH_AUTOSUGGEST_MATCH_PREV_CMD + # Prefix to use when saving original versions of bound widgets ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig- diff --git a/src/suggestion.zsh b/src/suggestion.zsh index f8e5459..7ef2def 100644 --- a/src/suggestion.zsh +++ b/src/suggestion.zsh @@ -3,16 +3,44 @@ # Suggestion # #--------------------------------------------------------------------# +# Get the peviously executed command (hookable for testing) +_zsh_autosuggest_prev_cmd() { + echo -E "${history[$((HISTCMD-1))]}" +} + # Get a suggestion from history that matches a given prefix _zsh_autosuggest_suggestion() { local prefix="$(_zsh_autosuggest_escape_command_prefix "$1")" - # Get all history items (reversed) that match pattern $prefix* - local history_matches - history_matches=(${(j:\0:s:\0:)history[(R)$prefix*]}) + # Get all history event numbers (reversed) that correspond to history + # entries that match pattern $prefix* + local history_match_keys=(${(k)history[(R)$prefix*]}) - # Echo the first item that matches - echo -E "$history_matches[1]" + # By default we use the first history number (most recent history entry) + local history_key="$history_match_keys[1]" + + # If matching on the previous command is enabled ... + if (( ${+ZSH_AUTOSUGGEST_MATCH_PREV_CMD} )); then + # Get the previously executed command + local prev_cmd=$(_zsh_autosuggest_prev_cmd) + prev_cmd="$(_zsh_autosuggest_escape_command_prefix $prev_cmd)" + + # Iterate up to the first 200 history event numbers that match $prefix + for key in "${(@)history_match_keys[1,200]}"; do + # Stop if we ran out of history + [[ $key -gt 1 ]] || break + + # See if the history entry preceding the suggestion matches the + # previous command, and use it if it does + if [[ "${history[$((key - 1))]}" == $prev_cmd ]]; then + history_key=$key + break + fi + done + fi + + # Echo the matched history entry + echo -E "$history[$history_key]" } _zsh_autosuggest_escape_command_prefix() { diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index a02b3a2..45d60d1 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -34,6 +34,18 @@ # More info: http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Zle-Widgets ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8' +# Set this to enable matching the history entry preceding the suggestion +# against the previously executed command. +# For example, if your have just executed: +# pwd +# ls foo +# ls bar +# pwd +# And then you start typing 'ls', then the suggestion will be 'ls foo', +# rather than 'ls bar', as your most recently executed command (pwd) +# was succeeded by 'ls foo' on it's previous invocation. +unset ZSH_AUTOSUGGEST_MATCH_PREV_CMD + # Prefix to use when saving original versions of bound widgets ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig- @@ -289,16 +301,44 @@ zle -N autosuggest-clear _zsh_autosuggest_widget_clear # Suggestion # #--------------------------------------------------------------------# +# Get the peviously executed command (hookable for testing) +_zsh_autosuggest_prev_cmd() { + echo -E "${history[$((HISTCMD-1))]}" +} + # Get a suggestion from history that matches a given prefix _zsh_autosuggest_suggestion() { local prefix="$(_zsh_autosuggest_escape_command_prefix "$1")" - # Get all history items (reversed) that match pattern $prefix* - local history_matches - history_matches=(${(j:\0:s:\0:)history[(R)$prefix*]}) + # Get all history event numbers (reversed) that correspond to history + # entries that match pattern $prefix* + local history_match_keys=(${(k)history[(R)$prefix*]}) - # Echo the first item that matches - echo -E "$history_matches[1]" + # By default we use the first history number (most recent history entry) + local history_key="$history_match_keys[1]" + + # If matching on the previous command is enabled ... + if (( ${+ZSH_AUTOSUGGEST_MATCH_PREV_CMD} )); then + # Get the previously executed command + local prev_cmd=$(_zsh_autosuggest_prev_cmd) + prev_cmd="$(_zsh_autosuggest_escape_command_prefix $prev_cmd)" + + # Iterate up to the first 200 history event numbers that match $prefix + for key in "${(@)history_match_keys[1,200]}"; do + # Stop if we ran out of history + [[ $key -gt 1 ]] || break + + # See if the history entry preceding the suggestion matches the + # previous command, and use it if it does + if [[ "${history[$((key - 1))]}" == $prev_cmd ]]; then + history_key=$key + break + fi + done + fi + + # Echo the matched history entry + echo -E "$history[$history_key]" } _zsh_autosuggest_escape_command_prefix() {