diff --git a/CHANGELOG.md b/CHANGELOG.md index 608d17f..50a1e0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v0.3.3 +- Switch from $history array to fc builtin for better performance with large HISTFILEs (#164) +- Fix tilde handling when extended_glob is set (#168) +- Add config option for maximum buffer length to fetch suggestions for (#178) +- Add config option for list of widgets to ignore (#184) +- Don't fetch a new suggestion unless a modification widget actually modifies the buffer (#183) + ## v0.3.2 - Test runner now supports running specific tests and choosing zsh binary - Return code from original widget is now correctly passed through (#135) diff --git a/README.md b/README.md index bc6de0f..3a5c3f3 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ Set `ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE` to configure the style that the suggestion Set `ZSH_AUTOSUGGEST_STRATEGY` to choose the strategy for generating suggestions. There are currently two to choose from: - `default`: Chooses the most recent match. -- `match_prev_cmd`: Chooses the most recent match whose preceding history item matches the most recently executed command ([more info](src/strategies/match_prev_cmd.zsh)). +- `match_prev_cmd`: Chooses the most recent match whose preceding history item matches the most recently executed command ([more info](src/strategies/match_prev_cmd.zsh)). Note that this strategy won't work as expected with ZSH options that don't preserve the history order such as `HIST_IGNORE_ALL_DUPS` or `HIST_EXPIRE_DUPS_FIRST`. ### Widget Mapping @@ -80,12 +80,19 @@ This plugin works by triggering custom behavior when certain [zle widgets](http: - `ZSH_AUTOSUGGEST_ACCEPT_WIDGETS`: Widgets in this array will accept the suggestion when invoked. - `ZSH_AUTOSUGGEST_EXECUTE_WIDGETS`: Widgets in this array will execute the suggestion when invoked. - `ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS`: Widgets in this array will partially accept the suggestion when invoked. +- `ZSH_AUTOSUGGEST_IGNORE_WIDGETS`: Widgets in this array will not trigger any custom behavior. -Widgets not in any of these lists will update the suggestion when invoked. +Widgets that modify the buffer and are not found in any of these arrays will fetch a new suggestion after they are invoked. **Note:** A widget shouldn't belong to more than one of the above arrays. +### Disabling suggestion for large buffers + +Set `ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE` to an integer value to disable autosuggestion for large buffers. The default is unset, which means that autosuggestion will be tried for any buffer size. Recommended value is 20. +This can be useful when pasting large amount of text in the terminal, to avoid triggering autosuggestion for too long strings. + + ### Key Bindings This plugin provides three widgets that you can use with `bindkey`: diff --git a/VERSION b/VERSION index 7becae1..600e6fd 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v0.3.2 +v0.3.3 diff --git a/src/bind.zsh b/src/bind.zsh index 384ae08..49763e8 100644 --- a/src/bind.zsh +++ b/src/bind.zsh @@ -47,10 +47,20 @@ _zsh_autosuggest_bind_widget() { # Map all configured widgets to the right autosuggest widgets _zsh_autosuggest_bind_widgets() { - local widget; + local widget + local ignore_widgets + + ignore_widgets=( + .\* + _\* + zle-line-\* + autosuggest-\* + $ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX\* + $ZSH_AUTOSUGGEST_IGNORE_WIDGETS + ) # Find every widget we might want to bind and bind it appropriately - for widget in ${${(f)"$(builtin zle -la)"}:#(.*|_*|orig-*|autosuggest-*|$ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX*|zle-line-*|run-help|which-command|beep|set-local-history|yank)}; do + for widget in ${${(f)"$(builtin zle -la)"}:#${(j:|:)~ignore_widgets}}; do if [ ${ZSH_AUTOSUGGEST_CLEAR_WIDGETS[(r)$widget]} ]; then _zsh_autosuggest_bind_widget $widget clear elif [ ${ZSH_AUTOSUGGEST_ACCEPT_WIDGETS[(r)$widget]} ]; then diff --git a/src/config.zsh b/src/config.zsh index 73d98fe..f519f6f 100644 --- a/src/config.zsh +++ b/src/config.zsh @@ -47,3 +47,16 @@ ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=( vi-forward-blank-word vi-forward-blank-word-end ) + +# Widgets that should be ignored (globbing supported but must be escaped) +ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( + orig-\* + beep + run-help + set-local-history + which-command + yank +) + +# Max size of buffer to trigger autosuggestion. Leave undefined for no upper bound. +ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE= diff --git a/src/strategies/default.zsh b/src/strategies/default.zsh index 42da24e..29333f5 100644 --- a/src/strategies/default.zsh +++ b/src/strategies/default.zsh @@ -7,12 +7,5 @@ # _zsh_autosuggest_strategy_default() { - local prefix="$1" - - # Get the keys of the history items that match - local -a histkeys - histkeys=(${(k)history[(r)$prefix*]}) - - # Echo the value of the first key - echo -E "${history[$histkeys[1]]}" + fc -lnrm "$1*" 1 2>/dev/null | head -n 1 } diff --git a/src/strategies/match_prev_cmd.zsh b/src/strategies/match_prev_cmd.zsh index e71957e..bf8bdd9 100644 --- a/src/strategies/match_prev_cmd.zsh +++ b/src/strategies/match_prev_cmd.zsh @@ -16,6 +16,9 @@ # will be 'ls foo' rather than 'ls bar' because your most recently # executed command (pwd) was previously followed by 'ls foo'. # +# Note that this strategy won't work as expected with ZSH options that don't +# preserve the history order such as `HIST_IGNORE_ALL_DUPS` or +# `HIST_EXPIRE_DUPS_FIRST`. _zsh_autosuggest_strategy_match_prev_cmd() { local prefix="$1" diff --git a/src/suggestion.zsh b/src/suggestion.zsh index 0a7ca1e..31a9f76 100644 --- a/src/suggestion.zsh +++ b/src/suggestion.zsh @@ -17,5 +17,5 @@ _zsh_autosuggest_escape_command() { setopt localoptions EXTENDED_GLOB # Escape special chars in the string (requires EXTENDED_GLOB) - echo -E "${1//(#m)[\\()\[\]|*?]/\\$MATCH}" + echo -E "${1//(#m)[\\()\[\]|*?~]/\\$MATCH}" } diff --git a/src/widgets.zsh b/src/widgets.zsh index ee1129f..57f378e 100644 --- a/src/widgets.zsh +++ b/src/widgets.zsh @@ -15,24 +15,34 @@ _zsh_autosuggest_clear() { _zsh_autosuggest_modify() { local -i retval + # Save the contents of the buffer/postdisplay + local orig_buffer="$BUFFER" + local orig_postdisplay="$POSTDISPLAY" + # Clear suggestion while original widget runs unset POSTDISPLAY - # Original widget modifies the buffer + # Original widget may modify the buffer _zsh_autosuggest_invoke_original_widget $@ retval=$? + # Don't fetch a new suggestion if the buffer hasn't changed + if [ "$BUFFER" = "$orig_buffer" ]; then + POSTDISPLAY="$orig_postdisplay" + return $retval + fi + # Get a new suggestion if the buffer is not empty after modification local suggestion if [ $#BUFFER -gt 0 ]; then - suggestion="$(_zsh_autosuggest_suggestion "$BUFFER")" + if [ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" -o $#BUFFER -lt "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]; then + suggestion="$(_zsh_autosuggest_suggestion "$BUFFER")" + fi fi # Add the suggestion to the POSTDISPLAY if [ -n "$suggestion" ]; then POSTDISPLAY="${suggestion#$BUFFER}" - else - unset POSTDISPLAY fi return $retval diff --git a/test/strategies_test.zsh b/test/strategies_test.zsh index 50d0a24..0a937f4 100644 --- a/test/strategies_test.zsh +++ b/test/strategies_test.zsh @@ -44,6 +44,12 @@ assertTildeSuggestion() { 'cd ~/something' } +assertTildeSuggestionWithExtendedGlob() { + setopt local_options extended_glob + + assertTildeSuggestion +} + assertParenthesesSuggestion() { set_history <<-'EOF' echo "$(ls foo)" @@ -87,6 +93,7 @@ testSpecialCharsForAllStrategies() { assertBackslashSuggestion assertDoubleBackslashSuggestion assertTildeSuggestion + assertTildeSuggestionWithExtendedGlob assertParenthesesSuggestion assertSquareBracketsSuggestion done diff --git a/test/widgets/modify_test.zsh b/test/widgets/modify_test.zsh index 4dfd30d..7ed6346 100644 --- a/test/widgets/modify_test.zsh +++ b/test/widgets/modify_test.zsh @@ -9,6 +9,7 @@ oneTimeSetUp() { setUp() { BUFFER='' POSTDISPLAY='' + ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE='' } tearDown() { @@ -42,6 +43,35 @@ testModify() { "$POSTDISPLAY" } +testModifyBufferTooLarge() { + + ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE='20' + + stub_and_eval \ + _zsh_autosuggest_invoke_original_widget \ + 'BUFFER+="012345678901234567890"' + + stub_and_echo \ + _zsh_autosuggest_suggestion \ + '012345678901234567890123456789' + + _zsh_autosuggest_modify 'original-widget' + + assertTrue \ + 'original widget not invoked' \ + 'stub_called _zsh_autosuggest_invoke_original_widget' + + assertEquals \ + 'BUFFER was not modified' \ + '012345678901234567890' \ + "$BUFFER" + + assertEquals \ + 'POSTDISPLAY does not contain suggestion' \ + '' \ + "$POSTDISPLAY" +} + testRetval() { stub_and_eval \ _zsh_autosuggest_invoke_original_widget \ diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 4e3c62e..3761efe 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.3.2 +# v0.3.3 # Copyright (c) 2013 Thiago de Arruda # Copyright (c) 2016 Eric Freese # @@ -74,6 +74,19 @@ ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=( vi-forward-blank-word-end ) +# Widgets that should be ignored (globbing supported but must be escaped) +ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( + orig-\* + beep + run-help + set-local-history + which-command + yank +) + +# Max size of buffer to trigger autosuggestion. Leave undefined for no upper bound. +ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE= + #--------------------------------------------------------------------# # Handle Deprecated Variables/Widgets # #--------------------------------------------------------------------# @@ -158,10 +171,20 @@ _zsh_autosuggest_bind_widget() { # Map all configured widgets to the right autosuggest widgets _zsh_autosuggest_bind_widgets() { - local widget; + local widget + local ignore_widgets + + ignore_widgets=( + .\* + _\* + zle-line-\* + autosuggest-\* + $ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX\* + $ZSH_AUTOSUGGEST_IGNORE_WIDGETS + ) # Find every widget we might want to bind and bind it appropriately - for widget in ${${(f)"$(builtin zle -la)"}:#(.*|_*|orig-*|autosuggest-*|$ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX*|zle-line-*|run-help|which-command|beep|set-local-history|yank)}; do + for widget in ${${(f)"$(builtin zle -la)"}:#${(j:|:)~ignore_widgets}}; do if [ ${ZSH_AUTOSUGGEST_CLEAR_WIDGETS[(r)$widget]} ]; then _zsh_autosuggest_bind_widget $widget clear elif [ ${ZSH_AUTOSUGGEST_ACCEPT_WIDGETS[(r)$widget]} ]; then @@ -233,24 +256,34 @@ _zsh_autosuggest_clear() { _zsh_autosuggest_modify() { local -i retval + # Save the contents of the buffer/postdisplay + local orig_buffer="$BUFFER" + local orig_postdisplay="$POSTDISPLAY" + # Clear suggestion while original widget runs unset POSTDISPLAY - # Original widget modifies the buffer + # Original widget may modify the buffer _zsh_autosuggest_invoke_original_widget $@ retval=$? + # Don't fetch a new suggestion if the buffer hasn't changed + if [ "$BUFFER" = "$orig_buffer" ]; then + POSTDISPLAY="$orig_postdisplay" + return $retval + fi + # Get a new suggestion if the buffer is not empty after modification local suggestion if [ $#BUFFER -gt 0 ]; then - suggestion="$(_zsh_autosuggest_suggestion "$BUFFER")" + if [ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" -o $#BUFFER -lt "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]; then + suggestion="$(_zsh_autosuggest_suggestion "$BUFFER")" + fi fi # Add the suggestion to the POSTDISPLAY if [ -n "$suggestion" ]; then POSTDISPLAY="${suggestion#$BUFFER}" - else - unset POSTDISPLAY fi return $retval @@ -360,7 +393,7 @@ _zsh_autosuggest_escape_command() { setopt localoptions EXTENDED_GLOB # Escape special chars in the string (requires EXTENDED_GLOB) - echo -E "${1//(#m)[\\()\[\]|*?]/\\$MATCH}" + echo -E "${1//(#m)[\\()\[\]|*?~]/\\$MATCH}" } #--------------------------------------------------------------------# @@ -371,14 +404,7 @@ _zsh_autosuggest_escape_command() { # _zsh_autosuggest_strategy_default() { - local prefix="$1" - - # Get the keys of the history items that match - local -a histkeys - histkeys=(${(k)history[(r)$prefix*]}) - - # Echo the value of the first key - echo -E "${history[$histkeys[1]]}" + fc -lnrm "$1*" 1 2>/dev/null | head -n 1 } #--------------------------------------------------------------------# @@ -398,6 +424,9 @@ _zsh_autosuggest_strategy_default() { # will be 'ls foo' rather than 'ls bar' because your most recently # executed command (pwd) was previously followed by 'ls foo'. # +# Note that this strategy won't work as expected with ZSH options that don't +# preserve the history order such as `HIST_IGNORE_ALL_DUPS` or +# `HIST_EXPIRE_DUPS_FIRST`. _zsh_autosuggest_strategy_match_prev_cmd() { local prefix="$1"