Feature: Allow cycling throw history in the autocompletion

This commit is contained in:
Marcelo Lima 2018-11-04 21:29:58 +01:00
parent fa5d9c0ff5
commit 47173c140c
12 changed files with 282 additions and 38 deletions

View File

@ -84,6 +84,8 @@ This plugin provides a few widgets that you can use with `bindkey`:
5. `autosuggest-disable`: Disables suggestions.
6. `autosuggest-enable`: Re-enables suggestions.
7. `autosuggest-toggle`: Toggles between enabled/disabled suggestions.
8. `autosuggest-next`: Suggests the next older entry from history.
9. `autosuggest-previous`: Suggests the next newer entry from history.
For example, this would bind <kbd>ctrl</kbd> + <kbd>space</kbd> to accept the current suggestion.

View File

@ -1,7 +1,7 @@
describe 'a suggestion for a given prefix' do
let(:history_strategy) { '_zsh_autosuggest_strategy_history() { suggestion="history" }' }
let(:foobar_strategy) { '_zsh_autosuggest_strategy_foobar() { [[ "foobar baz" = $1* ]] && suggestion="foobar baz" }' }
let(:foobaz_strategy) { '_zsh_autosuggest_strategy_foobaz() { [[ "foobaz bar" = $1* ]] && suggestion="foobaz bar" }' }
let(:foobar_strategy) { '_zsh_autosuggest_strategy_foobar() { [[ "foobar baz" = $2* ]] && suggestion="foobar baz" }' }
let(:foobaz_strategy) { '_zsh_autosuggest_strategy_foobaz() { [[ "foobaz bar" = $2* ]] && suggestion="foobaz bar" }' }
let(:options) { [ history_strategy ] }

63
spec/widgets/next_spec.rb Normal file
View File

@ -0,0 +1,63 @@
describe 'the `autosuggest-next` widget' do
context 'when suggestions are disabled' do
before do
session.
run_command('bindkey ^B autosuggest-disable').
run_command('bindkey ^K autosuggest-next').
send_keys('C-b')
end
it 'will fetch and display a suggestion' do
with_history('echo hello', 'echo world', 'echo joe') do
session.send_string('echo h')
sleep 1
expect(session.content).to eq('echo h')
session.send_keys('C-k')
wait_for { session.content }.to eq('echo hello')
session.send_string('e')
wait_for { session.content }.to eq('echo hello')
end
end
end
context 'invoked on a populated history' do
before do
session.
run_command('bindkey ^K autosuggest-next')
end
it 'will cycle, fetch, and display a suggestion' do
with_history('echo hello', 'echo world', 'echo joe') do
session.send_string('echo')
sleep 1
expect(session.content).to eq('echo joe')
session.send_keys('C-k')
wait_for { session.content }.to eq('echo world')
session.send_keys('C-k')
wait_for { session.content }.to eq('echo hello')
session.send_keys('C-k')
wait_for { session.content }.to eq('echo hello')
end
end
end
context 'when async mode is enabled' do
let(:options) { ['ZSH_AUTOSUGGEST_USE_ASYNC=true', 'ZSH_AUTOSUGGEST_STRATEGY=history'] }
before do
session.
run_command('bindkey ^K autosuggest-next')
end
it 'will cycle, fetch, and display a suggestion' do
with_history('echo hello', 'echo world', 'echo joe') do
session.send_string('echo')
sleep 1
expect(session.content).to eq('echo joe')
session.send_keys('C-k')
wait_for { session.content }.to eq('echo world')
session.send_keys('C-k')
wait_for { session.content }.to eq('echo hello')
session.send_keys('C-k')
wait_for { session.content }.to eq('echo hello')
end
end
end
end

View File

@ -0,0 +1,77 @@
describe 'the `autosuggest-previous` widget' do
context 'when suggestions are disabled' do
before do
session.
run_command('bindkey ^B autosuggest-disable').
run_command('bindkey ^J autosuggest-previous').
send_keys('C-b')
end
it 'will fetch and display a suggestion' do
with_history('echo hello', 'echo world', 'echo joe') do
session.send_string('echo h')
sleep 1
expect(session.content).to eq('echo h')
session.send_keys('C-j')
wait_for { session.content }.to eq('echo hello')
session.send_string('e')
wait_for { session.content }.to eq('echo hello')
end
end
end
context 'invoked on a populated history' do
before do
session.
run_command('bindkey ^K autosuggest-next').
run_command('bindkey ^J autosuggest-previous')
end
it 'will cycle, fetch, and display a suggestion' do
with_history('echo hello', 'echo world', 'echo joe') do
session.send_string('echo')
sleep 1
expect(session.content).to eq('echo joe')
session.send_keys('C-k')
wait_for { session.content }.to eq('echo world')
session.send_keys('C-k')
wait_for { session.content }.to eq('echo hello')
session.send_keys('C-k')
wait_for { session.content }.to eq('echo hello')
session.send_keys('C-j')
wait_for { session.content }.to eq('echo world')
session.send_keys('C-j')
wait_for { session.content }.to eq('echo joe')
session.send_keys('C-j')
wait_for { session.content }.to eq('echo joe')
end
end
end
context 'when async mode is enabled' do
let(:options) { ['ZSH_AUTOSUGGEST_USE_ASYNC=true', 'ZSH_AUTOSUGGEST_STRATEGY=history'] }
before do
session.
run_command('bindkey ^K autosuggest-next').
run_command('bindkey ^J autosuggest-previous')
end
it 'will cycle, fetch, and display a suggestion' do
with_history('echo hello', 'echo world', 'echo joe') do
session.send_string('echo')
sleep 1
expect(session.content).to eq('echo joe')
session.send_keys('C-k')
wait_for { session.content }.to eq('echo world')
session.send_keys('C-k')
wait_for { session.content }.to eq('echo hello')
session.send_keys('C-k')
wait_for { session.content }.to eq('echo hello')
session.send_keys('C-j')
wait_for { session.content }.to eq('echo world')
session.send_keys('C-j')
wait_for { session.content }.to eq('echo joe')
session.send_keys('C-j')
wait_for { session.content }.to eq('echo joe')
end
end
end
end

View File

@ -34,9 +34,10 @@ _zsh_autosuggest_async_request() {
echo $sysparams[pid]
# Fetch and print the suggestion
local capped_history_index
local suggestion
_zsh_autosuggest_fetch_suggestion "$1"
echo -nE "$suggestion"
_zsh_autosuggest_fetch_suggestion "$@"
echo -nE "$capped_history_index" "$suggestion"
)
# Read the pid from the child process
@ -52,7 +53,15 @@ _zsh_autosuggest_async_request() {
_zsh_autosuggest_async_response() {
if [[ -z "$2" || "$2" == "hup" ]]; then
# Read everything from the fd and give it as a suggestion
zle autosuggest-suggest -- "$(cat <&$1)"
local raw_input=`cat <&$1`
# Break up the output
# - (z) split into words using shell parsing to find the words
local input=(${(z)raw_input})
local capped_history_index="${input[1]}"
local suggestion="${input[2,-1]}"
zle autosuggest-suggest -- "$capped_history_index" "$suggestion"
# Close the fd
exec {1}<&-

View File

@ -76,7 +76,7 @@ _zsh_autosuggest_bind_widget() {
_zsh_autosuggest_bind_widgets() {
emulate -L zsh
local widget
local widget
local ignore_widgets
ignore_widgets=(

View File

@ -7,6 +7,7 @@
#
_zsh_autosuggest_fetch_suggestion() {
typeset -g capped_history_index
typeset -g suggestion
local -a strategies
local strategy
@ -16,7 +17,7 @@ _zsh_autosuggest_fetch_suggestion() {
for strategy in $strategies; do
# Try to get a suggestion from this strategy
_zsh_autosuggest_strategy_$strategy "$1"
_zsh_autosuggest_strategy_$strategy "$@"
# Break once we've found a suggestion
[[ -n "$suggestion" ]] && break

View File

@ -75,6 +75,10 @@ _zsh_autosuggest_strategy_completion() {
typeset -g suggestion
local line REPLY
# Ignore index parameter, since it does not apply to this strategy
typeset -g capped_history_index=1
shift
# Exit if we don't have completions
whence compdef >/dev/null || return

View File

@ -2,8 +2,8 @@
#--------------------------------------------------------------------#
# History Suggestion Strategy #
#--------------------------------------------------------------------#
# Suggests the most recent history item that matches the given
# prefix.
# Suggests the history item that matches the given prefix and history
# index
#
_zsh_autosuggest_strategy_history() {
@ -13,13 +13,20 @@ _zsh_autosuggest_strategy_history() {
# Enable globbing flags so that we can use (#m)
setopt EXTENDED_GLOB
# Extract the paramenters for this function
typeset -g capped_history_index="${1}"
local query="${2}"
# 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}"
local prefix="${query//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}"
# Get the history items that match
# - (r) subscript flag makes the pattern match on values
typeset -g suggestion="${history[(r)${prefix}*]}"
# - (R) subscript flag makes the pattern match on values
# - (k) returns the entry indices instead of values
local suggestions=(${(k)history[(R)$prefix*]})
(( capped_history_index > $#suggestions )) && capped_history_index=${#suggestions}
typeset -g suggestion="${history[${suggestions[${capped_history_index}]}]}"
}

View File

@ -27,8 +27,12 @@ _zsh_autosuggest_strategy_match_prev_cmd() {
# Enable globbing flags so that we can use (#m)
setopt EXTENDED_GLOB
# Extract the paramenters for this function
typeset -g capped_history_index="${1}"
local query="${2}"
# TODO: Use (b) flag when we can drop support for zsh older than v5.0.8
local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}"
local prefix="${query//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}"
# Get all history event numbers that correspond to history
# entries that match pattern $prefix*
@ -36,13 +40,13 @@ _zsh_autosuggest_strategy_match_prev_cmd() {
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]}"
local histkey="${history_match_keys[capped_history_index]}"
# Get the previously executed command
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
for key in "${(@)history_match_keys[capped_history_index,200]}"; do
# Stop if we ran out of history
[[ $key -gt 1 ]] || break

View File

@ -31,6 +31,7 @@ _zsh_autosuggest_toggle() {
_zsh_autosuggest_clear() {
# Remove the suggestion
unset POSTDISPLAY
history_index=1
_zsh_autosuggest_invoke_original_widget $@
}
@ -50,6 +51,7 @@ _zsh_autosuggest_modify() {
# Clear suggestion while waiting for next one
unset POSTDISPLAY
history_index=1
# Original widget may modify the buffer
_zsh_autosuggest_invoke_original_widget $@
@ -93,14 +95,31 @@ _zsh_autosuggest_modify() {
return $retval
}
# Navigate to the next suggestion in the suggestion list
_zsh_autosuggest_next() {
history_index=$(( history_index + 1 ))
_zsh_autosuggest_fetch
}
# Navigate to the previous suggestion in the suggestion list
_zsh_autosuggest_previous() {
(( history_index > 1 )) && history_index=$(( history_index - 1 ))
_zsh_autosuggest_fetch
}
# Fetch a new suggestion based on what's currently in the buffer
_zsh_autosuggest_fetch() {
if ! (( history_index > 0 )); then
history_index=1
fi
if [[ -n "${ZSH_AUTOSUGGEST_USE_ASYNC+x}" ]]; then
_zsh_autosuggest_async_request "$BUFFER"
_zsh_autosuggest_async_request "$history_index" "$BUFFER"
else
local suggestion
_zsh_autosuggest_fetch_suggestion "$BUFFER"
_zsh_autosuggest_suggest "$suggestion"
local capped_history_index
_zsh_autosuggest_fetch_suggestion "$history_index" "$BUFFER"
_zsh_autosuggest_suggest "$capped_history_index" "$suggestion"
fi
}
@ -108,12 +127,15 @@ _zsh_autosuggest_fetch() {
_zsh_autosuggest_suggest() {
emulate -L zsh
local suggestion="$1"
local capped_history_index="$1"
local suggestion="$2"
if [[ -n "$suggestion" ]] && (( $#BUFFER )); then
POSTDISPLAY="${suggestion#$BUFFER}"
history_index="${capped_history_index}"
else
unset POSTDISPLAY
history_index=1
fi
}
@ -134,6 +156,7 @@ _zsh_autosuggest_accept() {
# Remove the suggestion
unset POSTDISPLAY
history_index=1
# Move the cursor to the end of the buffer
CURSOR=${#BUFFER}
@ -149,6 +172,7 @@ _zsh_autosuggest_execute() {
# Remove the suggestion
unset POSTDISPLAY
history_index=1
# Call the original `accept-line` to handle syntax highlighting or
# other potential custom behavior
@ -190,7 +214,7 @@ _zsh_autosuggest_partial_accept() {
return $retval
}
for action in clear modify fetch suggest accept partial_accept execute enable disable toggle; do
for action in clear modify fetch suggest accept partial_accept execute enable disable toggle next previous; do
eval "_zsh_autosuggest_widget_$action() {
local -i retval
@ -215,3 +239,5 @@ zle -N autosuggest-execute _zsh_autosuggest_widget_execute
zle -N autosuggest-enable _zsh_autosuggest_widget_enable
zle -N autosuggest-disable _zsh_autosuggest_widget_disable
zle -N autosuggest-toggle _zsh_autosuggest_widget_toggle
zle -N autosuggest-next _zsh_autosuggest_widget_next
zle -N autosuggest-previous _zsh_autosuggest_widget_previous

View File

@ -186,7 +186,7 @@ _zsh_autosuggest_bind_widget() {
_zsh_autosuggest_bind_widgets() {
emulate -L zsh
local widget
local widget
local ignore_widgets
ignore_widgets=(
@ -287,6 +287,7 @@ _zsh_autosuggest_toggle() {
_zsh_autosuggest_clear() {
# Remove the suggestion
unset POSTDISPLAY
history_index=1
_zsh_autosuggest_invoke_original_widget $@
}
@ -306,6 +307,7 @@ _zsh_autosuggest_modify() {
# Clear suggestion while waiting for next one
unset POSTDISPLAY
history_index=1
# Original widget may modify the buffer
_zsh_autosuggest_invoke_original_widget $@
@ -349,14 +351,31 @@ _zsh_autosuggest_modify() {
return $retval
}
# Navigate to the next suggestion in the suggestion list
_zsh_autosuggest_next() {
history_index=$(( history_index + 1 ))
_zsh_autosuggest_fetch
}
# Navigate to the previous suggestion in the suggestion list
_zsh_autosuggest_previous() {
(( history_index > 1 )) && history_index=$(( history_index - 1 ))
_zsh_autosuggest_fetch
}
# Fetch a new suggestion based on what's currently in the buffer
_zsh_autosuggest_fetch() {
if ! (( history_index > 0 )); then
history_index=1
fi
if [[ -n "${ZSH_AUTOSUGGEST_USE_ASYNC+x}" ]]; then
_zsh_autosuggest_async_request "$BUFFER"
_zsh_autosuggest_async_request "$history_index" "$BUFFER"
else
local suggestion
_zsh_autosuggest_fetch_suggestion "$BUFFER"
_zsh_autosuggest_suggest "$suggestion"
local capped_history_index
_zsh_autosuggest_fetch_suggestion "$history_index" "$BUFFER"
_zsh_autosuggest_suggest "$capped_history_index" "$suggestion"
fi
}
@ -364,12 +383,15 @@ _zsh_autosuggest_fetch() {
_zsh_autosuggest_suggest() {
emulate -L zsh
local suggestion="$1"
local capped_history_index="$1"
local suggestion="$2"
if [[ -n "$suggestion" ]] && (( $#BUFFER )); then
POSTDISPLAY="${suggestion#$BUFFER}"
history_index="${capped_history_index}"
else
unset POSTDISPLAY
history_index=1
fi
}
@ -390,6 +412,7 @@ _zsh_autosuggest_accept() {
# Remove the suggestion
unset POSTDISPLAY
history_index=1
# Move the cursor to the end of the buffer
CURSOR=${#BUFFER}
@ -405,6 +428,7 @@ _zsh_autosuggest_execute() {
# Remove the suggestion
unset POSTDISPLAY
history_index=1
# Call the original `accept-line` to handle syntax highlighting or
# other potential custom behavior
@ -446,7 +470,7 @@ _zsh_autosuggest_partial_accept() {
return $retval
}
for action in clear modify fetch suggest accept partial_accept execute enable disable toggle; do
for action in clear modify fetch suggest accept partial_accept execute enable disable toggle next previous; do
eval "_zsh_autosuggest_widget_$action() {
local -i retval
@ -471,6 +495,8 @@ zle -N autosuggest-execute _zsh_autosuggest_widget_execute
zle -N autosuggest-enable _zsh_autosuggest_widget_enable
zle -N autosuggest-disable _zsh_autosuggest_widget_disable
zle -N autosuggest-toggle _zsh_autosuggest_widget_toggle
zle -N autosuggest-next _zsh_autosuggest_widget_next
zle -N autosuggest-previous _zsh_autosuggest_widget_previous
#--------------------------------------------------------------------#
# Completion Suggestion Strategy #
@ -548,6 +574,10 @@ _zsh_autosuggest_strategy_completion() {
typeset -g suggestion
local line REPLY
# Ignore index parameter, since it does not apply to this strategy
typeset -g capped_history_index=1
shift
# Exit if we don't have completions
whence compdef >/dev/null || return
@ -579,8 +609,8 @@ _zsh_autosuggest_strategy_completion() {
#--------------------------------------------------------------------#
# History Suggestion Strategy #
#--------------------------------------------------------------------#
# Suggests the most recent history item that matches the given
# prefix.
# Suggests the history item that matches the given prefix and history
# index
#
_zsh_autosuggest_strategy_history() {
@ -590,15 +620,22 @@ _zsh_autosuggest_strategy_history() {
# Enable globbing flags so that we can use (#m)
setopt EXTENDED_GLOB
# Extract the paramenters for this function
typeset -g capped_history_index="${1}"
local query="${2}"
# 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}"
local prefix="${query//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}"
# Get the history items that match
# - (r) subscript flag makes the pattern match on values
typeset -g suggestion="${history[(r)${prefix}*]}"
# - (R) subscript flag makes the pattern match on values
# - (k) returns the entry indices instead of values
local suggestions=(${(k)history[(R)$prefix*]})
(( capped_history_index > $#suggestions )) && capped_history_index=${#suggestions}
typeset -g suggestion="${history[${suggestions[${capped_history_index}]}]}"
}
#--------------------------------------------------------------------#
@ -629,8 +666,12 @@ _zsh_autosuggest_strategy_match_prev_cmd() {
# Enable globbing flags so that we can use (#m)
setopt EXTENDED_GLOB
# Extract the paramenters for this function
typeset -g capped_history_index="${1}"
local query="${2}"
# TODO: Use (b) flag when we can drop support for zsh older than v5.0.8
local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}"
local prefix="${query//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}"
# Get all history event numbers that correspond to history
# entries that match pattern $prefix*
@ -638,13 +679,13 @@ _zsh_autosuggest_strategy_match_prev_cmd() {
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]}"
local histkey="${history_match_keys[capped_history_index]}"
# Get the previously executed command
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
for key in "${(@)history_match_keys[capped_history_index,200]}"; do
# Stop if we ran out of history
[[ $key -gt 1 ]] || break
@ -668,6 +709,7 @@ _zsh_autosuggest_strategy_match_prev_cmd() {
#
_zsh_autosuggest_fetch_suggestion() {
typeset -g capped_history_index
typeset -g suggestion
local -a strategies
local strategy
@ -677,7 +719,7 @@ _zsh_autosuggest_fetch_suggestion() {
for strategy in $strategies; do
# Try to get a suggestion from this strategy
_zsh_autosuggest_strategy_$strategy "$1"
_zsh_autosuggest_strategy_$strategy "$@"
# Break once we've found a suggestion
[[ -n "$suggestion" ]] && break
@ -719,9 +761,10 @@ _zsh_autosuggest_async_request() {
echo $sysparams[pid]
# Fetch and print the suggestion
local capped_history_index
local suggestion
_zsh_autosuggest_fetch_suggestion "$1"
echo -nE "$suggestion"
_zsh_autosuggest_fetch_suggestion "$@"
echo -nE "$capped_history_index" "$suggestion"
)
# Read the pid from the child process
@ -737,7 +780,15 @@ _zsh_autosuggest_async_request() {
_zsh_autosuggest_async_response() {
if [[ -z "$2" || "$2" == "hup" ]]; then
# Read everything from the fd and give it as a suggestion
zle autosuggest-suggest -- "$(cat <&$1)"
local raw_input=`cat <&$1`
# Break up the output
# - (z) split into words using shell parsing to find the words
local input=(${(z)raw_input})
local capped_history_index="${input[1]}"
local suggestion="${input[2,-1]}"
zle autosuggest-suggest -- "$capped_history_index" "$suggestion"
# Close the fd
exec {1}<&-