Add autosuggest-next and autosuggest-previous widgets; enhance async suggestion handling
This commit is contained in:
		
							parent
							
								
									85919cd1ff
								
							
						
					
					
						commit
						bf5d77a65f
					
				|  | @ -0,0 +1,2 @@ | ||||||
|  | --- | ||||||
|  | BUNDLE_PATH: "vendor/bundle" | ||||||
|  | @ -109,9 +109,11 @@ This plugin provides a few widgets that you can use with `bindkey`: | ||||||
| 2. `autosuggest-execute`: Accepts and executes the current suggestion. | 2. `autosuggest-execute`: Accepts and executes the current suggestion. | ||||||
| 3. `autosuggest-clear`: Clears the current suggestion. | 3. `autosuggest-clear`: Clears the current suggestion. | ||||||
| 4. `autosuggest-fetch`: Fetches a suggestion (works even when suggestions are disabled). | 4. `autosuggest-fetch`: Fetches a suggestion (works even when suggestions are disabled). | ||||||
| 5. `autosuggest-disable`: Disables suggestions. | 5. `autosuggest-next`: Cycles to the next available suggestion for the current buffer (wraps around). | ||||||
| 6. `autosuggest-enable`: Re-enables suggestions. | 6. `autosuggest-previous`: Cycles backward through the available suggestions (wraps around). | ||||||
| 7. `autosuggest-toggle`: Toggles between enabled/disabled suggestions. | 7. `autosuggest-disable`: Disables suggestions. | ||||||
|  | 8. `autosuggest-enable`: Re-enables suggestions. | ||||||
|  | 9. `autosuggest-toggle`: Toggles between enabled/disabled suggestions. | ||||||
| 
 | 
 | ||||||
| For example, this would bind <kbd>ctrl</kbd> + <kbd>space</kbd> to accept the current suggestion. | For example, this would bind <kbd>ctrl</kbd> + <kbd>space</kbd> to accept the current suggestion. | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,38 @@ | ||||||
|  | describe 'the `autosuggest-next` widget' do | ||||||
|  |   let(:options) { ['unset ZSH_AUTOSUGGEST_USE_ASYNC', 'ZSH_AUTOSUGGEST_STRATEGY=history'] } | ||||||
|  | 
 | ||||||
|  |   before do | ||||||
|  |     session.run_command('bindkey ^N autosuggest-next') | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it 'cycles through history suggestions and wraps around' do | ||||||
|  |     with_history do | ||||||
|  |       session.run_command('echo foo') | ||||||
|  |       session.run_command('echo bar') | ||||||
|  |       session.run_command('echo baz') | ||||||
|  |       session.clear_screen | ||||||
|  | 
 | ||||||
|  |       session.send_string('echo ') | ||||||
|  |       wait_for { session.content }.to eq('echo baz') | ||||||
|  | 
 | ||||||
|  |       session.send_keys('C-n') | ||||||
|  |       wait_for { session.content }.to eq('echo bar') | ||||||
|  | 
 | ||||||
|  |       session.send_keys('C-n') | ||||||
|  |       wait_for { session.content }.to eq('echo foo') | ||||||
|  | 
 | ||||||
|  |       session.send_keys('C-n') | ||||||
|  |       wait_for { session.content }.to eq('echo baz') | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it 'leaves the buffer untouched when no suggestions are available' do | ||||||
|  |     with_history do | ||||||
|  |       session.send_string('foo') | ||||||
|  |       wait_for { session.content }.to eq('foo') | ||||||
|  | 
 | ||||||
|  |       session.send_keys('C-n') | ||||||
|  |       wait_for { session.content }.to eq('foo') | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -0,0 +1,38 @@ | ||||||
|  | describe 'the `autosuggest-previous` widget' do | ||||||
|  |   let(:options) { ['unset ZSH_AUTOSUGGEST_USE_ASYNC', 'ZSH_AUTOSUGGEST_STRATEGY=history'] } | ||||||
|  | 
 | ||||||
|  |   before do | ||||||
|  |     session.run_command('bindkey ^P autosuggest-previous') | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it 'cycles backwards through history suggestions and wraps around' do | ||||||
|  |     with_history do | ||||||
|  |       session.run_command('echo foo') | ||||||
|  |       session.run_command('echo bar') | ||||||
|  |       session.run_command('echo baz') | ||||||
|  |       session.clear_screen | ||||||
|  | 
 | ||||||
|  |       session.send_string('echo ') | ||||||
|  |       wait_for { session.content }.to eq('echo baz') | ||||||
|  | 
 | ||||||
|  |       session.send_keys('C-p') | ||||||
|  |       wait_for { session.content }.to eq('echo foo') | ||||||
|  | 
 | ||||||
|  |       session.send_keys('C-p') | ||||||
|  |       wait_for { session.content }.to eq('echo bar') | ||||||
|  | 
 | ||||||
|  |       session.send_keys('C-p') | ||||||
|  |       wait_for { session.content }.to eq('echo baz') | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it 'leaves the buffer untouched when no suggestions are available' do | ||||||
|  |     with_history do | ||||||
|  |       session.send_string('foo') | ||||||
|  |       wait_for { session.content }.to eq('foo') | ||||||
|  | 
 | ||||||
|  |       session.send_keys('C-p') | ||||||
|  |       wait_for { session.content }.to eq('foo') | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -3,18 +3,13 @@ | ||||||
| # Async                                                              # | # Async                                                              # | ||||||
| #--------------------------------------------------------------------# | #--------------------------------------------------------------------# | ||||||
| 
 | 
 | ||||||
| _zsh_autosuggest_async_request() { | _zsh_autosuggest_async_cancel() { | ||||||
| 	zmodload zsh/system 2>/dev/null # For `$sysparams` |  | ||||||
| 
 |  | ||||||
| 	typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD _ZSH_AUTOSUGGEST_CHILD_PID |  | ||||||
| 
 |  | ||||||
| 	# If we've got a pending request, cancel it |  | ||||||
| 	if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then | 	if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then | ||||||
| 		# Close the file descriptor and remove the handler | 		# Close the file descriptor and remove the handler | ||||||
| 		builtin exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&- | 		builtin exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&- | ||||||
| 		zle -F $_ZSH_AUTOSUGGEST_ASYNC_FD | 		zle -F $_ZSH_AUTOSUGGEST_ASYNC_FD | ||||||
|  | 	fi | ||||||
| 
 | 
 | ||||||
| 		# We won't know the pid unless the user has zsh/system module installed |  | ||||||
| 	if [[ -n "$_ZSH_AUTOSUGGEST_CHILD_PID" ]]; then | 	if [[ -n "$_ZSH_AUTOSUGGEST_CHILD_PID" ]]; then | ||||||
| 		# Zsh will make a new process group for the child process only if job | 		# Zsh will make a new process group for the child process only if job | ||||||
| 		# control is enabled (MONITOR option) | 		# control is enabled (MONITOR option) | ||||||
|  | @ -29,7 +24,24 @@ _zsh_autosuggest_async_request() { | ||||||
| 			kill -TERM $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null | 			kill -TERM $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null | ||||||
| 		fi | 		fi | ||||||
| 	fi | 	fi | ||||||
| 	fi | 
 | ||||||
|  | 	_ZSH_AUTOSUGGEST_ASYNC_FD= | ||||||
|  | 	_ZSH_AUTOSUGGEST_CHILD_PID= | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | _zsh_autosuggest_async_request() { | ||||||
|  | 	zmodload zsh/system 2>/dev/null # For `$sysparams` | ||||||
|  | 
 | ||||||
|  | 	typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD _ZSH_AUTOSUGGEST_CHILD_PID | ||||||
|  | 
 | ||||||
|  | 	local buffer="$1" | ||||||
|  | 	local -i offset=${2:-0} | ||||||
|  | 
 | ||||||
|  | 	_zsh_autosuggest_async_cancel | ||||||
|  | 
 | ||||||
|  | 	typeset -g _ZSH_AUTOSUGGEST_PENDING_BUFFER _ZSH_AUTOSUGGEST_PENDING_OFFSET | ||||||
|  | 	_ZSH_AUTOSUGGEST_PENDING_BUFFER="$buffer" | ||||||
|  | 	_ZSH_AUTOSUGGEST_PENDING_OFFSET=$offset | ||||||
| 
 | 
 | ||||||
| 	# Fork a process to fetch a suggestion and open a pipe to read from it | 	# Fork a process to fetch a suggestion and open a pipe to read from it | ||||||
| 	builtin exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <( | 	builtin exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <( | ||||||
|  | @ -38,7 +50,7 @@ _zsh_autosuggest_async_request() { | ||||||
| 
 | 
 | ||||||
| 		# Fetch and print the suggestion | 		# Fetch and print the suggestion | ||||||
| 		local suggestion | 		local suggestion | ||||||
| 		_zsh_autosuggest_fetch_suggestion "$1" | 		_zsh_autosuggest_fetch_suggestion "$buffer" $offset | ||||||
| 		echo -nE "$suggestion" | 		echo -nE "$suggestion" | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
|  | @ -65,6 +77,22 @@ _zsh_autosuggest_async_response() { | ||||||
| 	if [[ -z "$2" || "$2" == "hup" ]]; then | 	if [[ -z "$2" || "$2" == "hup" ]]; then | ||||||
| 		# Read everything from the fd and give it as a suggestion | 		# Read everything from the fd and give it as a suggestion | ||||||
| 		IFS='' read -rd '' -u $1 suggestion | 		IFS='' read -rd '' -u $1 suggestion | ||||||
|  | 		if [[ -n "$suggestion" && "$suggestion" = "$BUFFER"* ]]; then | ||||||
|  | 			typeset -g _ZSH_AUTOSUGGEST_SUGGESTION_INDEX _ZSH_AUTOSUGGEST_LAST_PREFIX | ||||||
|  | 			if [[ -n "${_ZSH_AUTOSUGGEST_PENDING_BUFFER-}" && "$BUFFER" = "$_ZSH_AUTOSUGGEST_PENDING_BUFFER" ]]; then | ||||||
|  | 				_ZSH_AUTOSUGGEST_SUGGESTION_INDEX=${_ZSH_AUTOSUGGEST_PENDING_OFFSET:-0} | ||||||
|  | 				_ZSH_AUTOSUGGEST_LAST_PREFIX="$BUFFER" | ||||||
|  | 			else | ||||||
|  | 				# Fall back to updating state using the current buffer if it still | ||||||
|  | 				# matches the suggestion. | ||||||
|  | 				_ZSH_AUTOSUGGEST_SUGGESTION_INDEX=${_ZSH_AUTOSUGGEST_PENDING_OFFSET:-0} | ||||||
|  | 				_ZSH_AUTOSUGGEST_LAST_PREFIX="$BUFFER" | ||||||
|  | 			fi | ||||||
|  | 		elif [[ -z "$suggestion" ]]; then | ||||||
|  | 			typeset -g _ZSH_AUTOSUGGEST_SUGGESTION_INDEX _ZSH_AUTOSUGGEST_LAST_PREFIX | ||||||
|  | 			_ZSH_AUTOSUGGEST_SUGGESTION_INDEX=0 | ||||||
|  | 			_ZSH_AUTOSUGGEST_LAST_PREFIX="$BUFFER" | ||||||
|  | 		fi | ||||||
| 		zle autosuggest-suggest -- "$suggestion" | 		zle autosuggest-suggest -- "$suggestion" | ||||||
| 
 | 
 | ||||||
| 		# Close the fd | 		# Close the fd | ||||||
|  | @ -74,4 +102,9 @@ _zsh_autosuggest_async_response() { | ||||||
| 	# Always remove the handler | 	# Always remove the handler | ||||||
| 	zle -F "$1" | 	zle -F "$1" | ||||||
| 	_ZSH_AUTOSUGGEST_ASYNC_FD= | 	_ZSH_AUTOSUGGEST_ASYNC_FD= | ||||||
|  | 	_ZSH_AUTOSUGGEST_CHILD_PID= | ||||||
|  | 
 | ||||||
|  | 	typeset -g _ZSH_AUTOSUGGEST_PENDING_BUFFER _ZSH_AUTOSUGGEST_PENDING_OFFSET | ||||||
|  | 	unset _ZSH_AUTOSUGGEST_PENDING_BUFFER | ||||||
|  | 	_ZSH_AUTOSUGGEST_PENDING_OFFSET=0 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -8,20 +8,55 @@ | ||||||
| 
 | 
 | ||||||
| _zsh_autosuggest_fetch_suggestion() { | _zsh_autosuggest_fetch_suggestion() { | ||||||
| 	typeset -g suggestion | 	typeset -g suggestion | ||||||
|  | 	local prefix="$1" | ||||||
|  | 	local -i remaining_offset=${2:-0} | ||||||
| 	local -a strategies | 	local -a strategies | ||||||
| 	local strategy | 	local strategy | ||||||
|  | 	local reply_value | ||||||
|  | 	local -i strategy_count | ||||||
| 
 | 
 | ||||||
| 	# Ensure we are working with an array | 	# Ensure we are working with an array | ||||||
| 	strategies=(${=ZSH_AUTOSUGGEST_STRATEGY}) | 	strategies=(${=ZSH_AUTOSUGGEST_STRATEGY}) | ||||||
| 
 | 
 | ||||||
|  | 	# Reset global suggestion result | ||||||
|  | 	unset suggestion | ||||||
|  | 
 | ||||||
| 	for strategy in $strategies; do | 	for strategy in $strategies; do | ||||||
|  | 		reply_value= | ||||||
|  | 		REPLY= | ||||||
|  | 
 | ||||||
| 		# Try to get a suggestion from this strategy | 		# Try to get a suggestion from this strategy | ||||||
| 		_zsh_autosuggest_strategy_$strategy "$1" | 		_zsh_autosuggest_strategy_$strategy "$prefix" $remaining_offset | ||||||
| 
 | 
 | ||||||
| 		# Ensure the suggestion matches the prefix | 		# Ensure the suggestion matches the prefix | ||||||
| 		[[ "$suggestion" != "$1"* ]] && unset suggestion | 		if [[ "$suggestion" != "$prefix"* ]]; then | ||||||
|  | 			unset suggestion | ||||||
|  | 		fi | ||||||
| 
 | 
 | ||||||
| 		# Break once we've found a valid suggestion | 		# Break once we've found a valid suggestion | ||||||
| 		[[ -n "$suggestion" ]] && break | 		if [[ -n "$suggestion" ]]; then | ||||||
|  | 			break | ||||||
|  | 		fi | ||||||
|  | 
 | ||||||
|  | 		# Determine how many suggestions this strategy can offer so we can | ||||||
|  | 		# decrement the remaining offset before trying the next strategy. | ||||||
|  | 		reply_value="$REPLY" | ||||||
|  | 		if [[ -n "$reply_value" && "$reply_value" = <-> ]]; then | ||||||
|  | 			strategy_count=$reply_value | ||||||
|  | 		elif [[ -n "$reply_value" ]]; then | ||||||
|  | 			# Treat non-numeric replies as zero to avoid arithmetic errors | ||||||
|  | 			strategy_count=0 | ||||||
|  | 		else | ||||||
|  | 			# Preserve existing behaviour when the strategy doesn't report a count. | ||||||
|  | 			strategy_count=0 | ||||||
|  | 		fi | ||||||
|  | 
 | ||||||
|  | 		if (( remaining_offset > 0 && strategy_count > 0 )); then | ||||||
|  | 			if (( remaining_offset >= strategy_count )); then | ||||||
|  | 				remaining_offset=$((remaining_offset - strategy_count)) | ||||||
|  | 			else | ||||||
|  | 				remaining_offset=0 | ||||||
|  | 			fi | ||||||
|  | 		fi | ||||||
| 	done | 	done | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -101,7 +101,8 @@ _zsh_autosuggest_strategy_completion() { | ||||||
| 	setopt EXTENDED_GLOB | 	setopt EXTENDED_GLOB | ||||||
| 
 | 
 | ||||||
| 	typeset -g suggestion | 	typeset -g suggestion | ||||||
| 	local line REPLY | 	local -i offset=${2:-0} | ||||||
|  | 	local line | ||||||
| 
 | 
 | ||||||
| 	# Exit if we don't have completions | 	# Exit if we don't have completions | ||||||
| 	whence compdef >/dev/null || return | 	whence compdef >/dev/null || return | ||||||
|  | @ -134,4 +135,13 @@ _zsh_autosuggest_strategy_completion() { | ||||||
| 		# Destroy the pty | 		# Destroy the pty | ||||||
| 		zpty -d $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME | 		zpty -d $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	if [[ -n "$suggestion" ]]; then | ||||||
|  | 		REPLY=1 | ||||||
|  | 		if (( offset > 0 )); then | ||||||
|  | 			unset suggestion | ||||||
|  | 		fi | ||||||
|  | 	else | ||||||
|  | 		REPLY=0 | ||||||
|  | 	fi | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,4 +1,3 @@ | ||||||
| 
 |  | ||||||
| #--------------------------------------------------------------------# | #--------------------------------------------------------------------# | ||||||
| # History Suggestion Strategy                                        # | # History Suggestion Strategy                                        # | ||||||
| #--------------------------------------------------------------------# | #--------------------------------------------------------------------# | ||||||
|  | @ -13,20 +12,41 @@ _zsh_autosuggest_strategy_history() { | ||||||
| 	# Enable globbing flags so that we can use (#m) and (x~y) glob operator | 	# Enable globbing flags so that we can use (#m) and (x~y) glob operator | ||||||
| 	setopt EXTENDED_GLOB | 	setopt EXTENDED_GLOB | ||||||
| 
 | 
 | ||||||
|  | 	local raw_prefix="$1" | ||||||
|  | 	local -i offset=${2:-0} | ||||||
|  | 
 | ||||||
| 	# Escape backslashes and all of the glob operators so we can use | 	# Escape backslashes and all of the glob operators so we can use | ||||||
| 	# this string as a pattern to search the $history associative array. | 	# this string as a pattern to search the history list. | ||||||
| 	# - (#m) globbing flag enables setting references for match data | 	# - (#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 | 	# TODO: Use (b) flag when we can drop support for zsh older than v5.0.8 | ||||||
| 	local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" | 	local prefix="${raw_prefix//(#m)[\\*?\[\]<>()|^~#]/\\$MATCH}" | ||||||
| 
 | 
 | ||||||
| 	# Get the history items that match the prefix, excluding those that match | 	# Build the matcher, excluding entries that match the ignore pattern | ||||||
| 	# the ignore pattern |  | ||||||
| 	local pattern="$prefix*" | 	local pattern="$prefix*" | ||||||
| 	if [[ -n $ZSH_AUTOSUGGEST_HISTORY_IGNORE ]]; then | 	if [[ -n $ZSH_AUTOSUGGEST_HISTORY_IGNORE ]]; then | ||||||
| 		pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)" | 		pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)" | ||||||
| 	fi | 	fi | ||||||
| 
 | 
 | ||||||
| 	# Give the first history item matching the pattern as the suggestion | 	local fc_output | ||||||
| 	# - (r) subscript flag makes the pattern match on values | 	fc_output=$(builtin fc -ln 2>/dev/null) | ||||||
| 	typeset -g suggestion="${history[(r)$pattern]}" | 
 | ||||||
|  | 	local -a matches | ||||||
|  | 	matches=() | ||||||
|  | 
 | ||||||
|  | 	local line | ||||||
|  | 	for line in ${(f)fc_output}; do | ||||||
|  | 		if [[ "$line" == ${~pattern} ]]; then | ||||||
|  | 			matches=("$line" "${matches[@]}") | ||||||
|  | 		fi | ||||||
|  | 	done | ||||||
|  | 
 | ||||||
|  |     # no-op: keep vars local to this function to avoid global pollution | ||||||
|  | 
 | ||||||
|  | 	REPLY=$#matches | ||||||
|  | 
 | ||||||
|  | 	if (( offset < $#matches )); then | ||||||
|  | 		typeset -g suggestion="${matches[offset+1]}" | ||||||
|  | 	else | ||||||
|  | 		unset suggestion | ||||||
|  | 	fi | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -27,8 +27,11 @@ _zsh_autosuggest_strategy_match_prev_cmd() { | ||||||
| 	# Enable globbing flags so that we can use (#m) and (x~y) glob operator | 	# Enable globbing flags so that we can use (#m) and (x~y) glob operator | ||||||
| 	setopt EXTENDED_GLOB | 	setopt EXTENDED_GLOB | ||||||
| 
 | 
 | ||||||
|  | 	local raw_prefix="$1" | ||||||
|  | 	local -i offset=${2:-0} | ||||||
|  | 
 | ||||||
| 	# TODO: Use (b) flag when we can drop support for zsh older than v5.0.8 | 	# TODO: Use (b) flag when we can drop support for zsh older than v5.0.8 | ||||||
| 	local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" | 	local prefix="${raw_prefix//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" | ||||||
| 
 | 
 | ||||||
| 	# Get the history items that match the prefix, excluding those that match | 	# Get the history items that match the prefix, excluding those that match | ||||||
| 	# the ignore pattern | 	# the ignore pattern | ||||||
|  | @ -37,10 +40,9 @@ _zsh_autosuggest_strategy_match_prev_cmd() { | ||||||
| 		pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)" | 		pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)" | ||||||
| 	fi | 	fi | ||||||
| 
 | 
 | ||||||
| 	# Get all history event numbers that correspond to history | 	# Get all history event numbers that correspond to history entries that match the pattern | ||||||
| 	# entries that match the pattern |  | ||||||
| 	local history_match_keys | 	local history_match_keys | ||||||
| 	history_match_keys=(${(k)history[(R)$~pattern]}) | 	history_match_keys=(${(kOn)history[(R)$pattern]}) | ||||||
| 
 | 
 | ||||||
| 	# By default we use the first history number (most recent history entry) | 	# By default we use the first history number (most recent history entry) | ||||||
| 	local histkey="${history_match_keys[1]}" | 	local histkey="${history_match_keys[1]}" | ||||||
|  | @ -61,6 +63,27 @@ _zsh_autosuggest_strategy_match_prev_cmd() { | ||||||
| 		fi | 		fi | ||||||
| 	done | 	done | ||||||
| 
 | 
 | ||||||
| 	# Give back the matched history entry | 	local -a ordered_keys matches | ||||||
| 	typeset -g suggestion="$history[$histkey]" | 	if [[ -n "$histkey" ]]; then | ||||||
|  | 		ordered_keys=("$histkey") | ||||||
|  | 	fi | ||||||
|  | 
 | ||||||
|  | 	for key in "${(@)history_match_keys[1,200]}"; do | ||||||
|  | 		[[ -n "$key" ]] || continue | ||||||
|  | 		[[ "$key" = "$histkey" ]] && continue | ||||||
|  | 		ordered_keys+=("$key") | ||||||
|  | 	done | ||||||
|  | 
 | ||||||
|  | 	matches=() | ||||||
|  | 	for key in $ordered_keys; do | ||||||
|  | 		matches+=("${history[$key]}") | ||||||
|  | 	done | ||||||
|  | 
 | ||||||
|  | 	REPLY=$#matches | ||||||
|  | 
 | ||||||
|  | 	if (( offset < $#matches )); then | ||||||
|  | 		typeset -g suggestion="${matches[offset+1]}" | ||||||
|  | 	else | ||||||
|  | 		unset suggestion | ||||||
|  | 	fi | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										114
									
								
								src/widgets.zsh
								
								
								
								
							
							
						
						
									
										114
									
								
								src/widgets.zsh
								
								
								
								
							|  | @ -3,6 +3,11 @@ | ||||||
| # Autosuggest Widget Implementations                                 # | # Autosuggest Widget Implementations                                 # | ||||||
| #--------------------------------------------------------------------# | #--------------------------------------------------------------------# | ||||||
| 
 | 
 | ||||||
|  | typeset -gi _ZSH_AUTOSUGGEST_SUGGESTION_INDEX=0 | ||||||
|  | typeset -gi _ZSH_AUTOSUGGEST_PENDING_OFFSET=0 | ||||||
|  | typeset -g _ZSH_AUTOSUGGEST_PENDING_BUFFER | ||||||
|  | typeset -g _ZSH_AUTOSUGGEST_LAST_PREFIX | ||||||
|  | 
 | ||||||
| # Disable suggestions | # Disable suggestions | ||||||
| _zsh_autosuggest_disable() { | _zsh_autosuggest_disable() { | ||||||
| 	typeset -g _ZSH_AUTOSUGGEST_DISABLED | 	typeset -g _ZSH_AUTOSUGGEST_DISABLED | ||||||
|  | @ -18,6 +23,69 @@ _zsh_autosuggest_enable() { | ||||||
| 	fi | 	fi | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | _zsh_autosuggest_next() { | ||||||
|  | 	emulate -L zsh | ||||||
|  | 
 | ||||||
|  | 	# Do nothing if suggestions are disabled or there's no buffer to base suggestions on | ||||||
|  | 	if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )) || (( $#BUFFER == 0 )); then | ||||||
|  | 		return 0 | ||||||
|  | 	fi | ||||||
|  | 
 | ||||||
|  | 	local -i current_index=${_ZSH_AUTOSUGGEST_SUGGESTION_INDEX:-0} | ||||||
|  | 	local -i next_index=$((current_index + 1)) | ||||||
|  | 
 | ||||||
|  | 	# Fetch synchronously to avoid races | ||||||
|  | 	_zsh_autosuggest_fetch $next_index "sync" | ||||||
|  | 
 | ||||||
|  | 	# If no suggestion at next_index, wrap back to 0 | ||||||
|  | 	if (( _ZSH_AUTOSUGGEST_SUGGESTION_INDEX != next_index )); then | ||||||
|  | 		_zsh_autosuggest_fetch 0 "sync" | ||||||
|  | 	fi | ||||||
|  | 
 | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | _zsh_autosuggest_previous() { | ||||||
|  | 	emulate -L zsh | ||||||
|  | 
 | ||||||
|  | 	if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )) || (( $#BUFFER == 0 )); then | ||||||
|  | 		return 0 | ||||||
|  | 	fi | ||||||
|  | 
 | ||||||
|  | 	local -i current_index=${_ZSH_AUTOSUGGEST_SUGGESTION_INDEX:-0} | ||||||
|  | 
 | ||||||
|  | 	if (( current_index > 0 )); then | ||||||
|  | 		local -i prev_index=$((current_index - 1)) | ||||||
|  | 		_zsh_autosuggest_fetch $prev_index "sync" | ||||||
|  | 		return 0 | ||||||
|  | 	fi | ||||||
|  | 
 | ||||||
|  | 	# We are at the first suggestion; step forward until we can't to find the last entry. | ||||||
|  | 	_zsh_autosuggest_fetch 0 "sync" | ||||||
|  | 
 | ||||||
|  | 	if [[ -z "$POSTDISPLAY" ]]; then | ||||||
|  | 		return 0 | ||||||
|  | 	fi | ||||||
|  | 
 | ||||||
|  | 	local -i probe=0 | ||||||
|  | 	local -i last_index=0 | ||||||
|  | 
 | ||||||
|  | 	while true; do | ||||||
|  | 		(( probe++ )) | ||||||
|  | 		_zsh_autosuggest_fetch $probe "sync" | ||||||
|  | 
 | ||||||
|  | 		if (( _ZSH_AUTOSUGGEST_SUGGESTION_INDEX == probe )); then | ||||||
|  | 			last_index=$probe | ||||||
|  | 			continue | ||||||
|  | 		fi | ||||||
|  | 
 | ||||||
|  | 		break | ||||||
|  | 	done | ||||||
|  | 
 | ||||||
|  | 	_zsh_autosuggest_fetch $last_index "sync" | ||||||
|  | 
 | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
| # Toggle suggestions (enable/disable) | # Toggle suggestions (enable/disable) | ||||||
| _zsh_autosuggest_toggle() { | _zsh_autosuggest_toggle() { | ||||||
| 	if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )); then | 	if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )); then | ||||||
|  | @ -31,6 +99,8 @@ _zsh_autosuggest_toggle() { | ||||||
| _zsh_autosuggest_clear() { | _zsh_autosuggest_clear() { | ||||||
| 	# Remove the suggestion | 	# Remove the suggestion | ||||||
| 	POSTDISPLAY= | 	POSTDISPLAY= | ||||||
|  | 	_ZSH_AUTOSUGGEST_SUGGESTION_INDEX=0 | ||||||
|  | 	_ZSH_AUTOSUGGEST_LAST_PREFIX="$BUFFER" | ||||||
| 
 | 
 | ||||||
| 	_zsh_autosuggest_invoke_original_widget $@ | 	_zsh_autosuggest_invoke_original_widget $@ | ||||||
| } | } | ||||||
|  | @ -84,12 +154,50 @@ _zsh_autosuggest_modify() { | ||||||
| 
 | 
 | ||||||
| # Fetch a new suggestion based on what's currently in the buffer | # Fetch a new suggestion based on what's currently in the buffer | ||||||
| _zsh_autosuggest_fetch() { | _zsh_autosuggest_fetch() { | ||||||
|  | 	local -i offset=${1:-0} | ||||||
|  | 	local mode="${2:-auto}" | ||||||
|  | 	local use_async=0 | ||||||
|  | 
 | ||||||
|  | 	# Reset offset if the buffer changed since the last suggestion lookup | ||||||
|  | 	if [[ "$BUFFER" != "${_ZSH_AUTOSUGGEST_LAST_PREFIX-}" ]]; then | ||||||
|  | 		offset=0 | ||||||
|  | 	fi | ||||||
|  | 
 | ||||||
| 	if (( ${+ZSH_AUTOSUGGEST_USE_ASYNC} )); then | 	if (( ${+ZSH_AUTOSUGGEST_USE_ASYNC} )); then | ||||||
| 		_zsh_autosuggest_async_request "$BUFFER" | 		use_async=1 | ||||||
|  | 	fi | ||||||
|  | 
 | ||||||
|  | 	if [[ "$mode" == "sync" ]]; then | ||||||
|  | 		use_async=0 | ||||||
|  | 		# Cancel any pending async request so results don't race the sync fetch | ||||||
|  | 		_zsh_autosuggest_async_cancel | ||||||
|  | 	fi | ||||||
|  | 
 | ||||||
|  | 	_ZSH_AUTOSUGGEST_PENDING_OFFSET=$offset | ||||||
|  | 	_ZSH_AUTOSUGGEST_PENDING_BUFFER="$BUFFER" | ||||||
|  | 
 | ||||||
|  | 	if (( use_async )); then | ||||||
|  | 		_zsh_autosuggest_async_request "$BUFFER" $offset | ||||||
| 	else | 	else | ||||||
| 		local suggestion | 		local suggestion | ||||||
| 		_zsh_autosuggest_fetch_suggestion "$BUFFER" | 		_zsh_autosuggest_fetch_suggestion "$BUFFER" $offset | ||||||
|  | 
 | ||||||
|  | 		if [[ -n "$suggestion" ]]; then | ||||||
|  | 			_ZSH_AUTOSUGGEST_SUGGESTION_INDEX=$offset | ||||||
|  | 			_ZSH_AUTOSUGGEST_LAST_PREFIX="$BUFFER" | ||||||
|  | 		else | ||||||
|  | 			if (( offset > 0 )); then | ||||||
|  | 				_ZSH_AUTOSUGGEST_SUGGESTION_INDEX=0 | ||||||
|  | 			fi | ||||||
|  | 			# Ensure state tracks the current buffer even when no suggestion exists | ||||||
|  | 			_ZSH_AUTOSUGGEST_LAST_PREFIX="$BUFFER" | ||||||
|  | 		fi | ||||||
|  | 
 | ||||||
| 		_zsh_autosuggest_suggest "$suggestion" | 		_zsh_autosuggest_suggest "$suggestion" | ||||||
|  | 
 | ||||||
|  | 		# Clear pending markers for synchronous flow | ||||||
|  | 		unset _ZSH_AUTOSUGGEST_PENDING_BUFFER | ||||||
|  | 		_ZSH_AUTOSUGGEST_PENDING_OFFSET=0 | ||||||
| 	fi | 	fi | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -200,6 +308,8 @@ _zsh_autosuggest_partial_accept() { | ||||||
| 		clear | 		clear | ||||||
| 		fetch | 		fetch | ||||||
| 		suggest | 		suggest | ||||||
|  | 		next | ||||||
|  | 		previous | ||||||
| 		accept | 		accept | ||||||
| 		execute | 		execute | ||||||
| 		enable | 		enable | ||||||
|  |  | ||||||
|  | @ -267,6 +267,11 @@ _zsh_autosuggest_highlight_apply() { | ||||||
| # Autosuggest Widget Implementations                                 # | # Autosuggest Widget Implementations                                 # | ||||||
| #--------------------------------------------------------------------# | #--------------------------------------------------------------------# | ||||||
| 
 | 
 | ||||||
|  | typeset -gi _ZSH_AUTOSUGGEST_SUGGESTION_INDEX=0 | ||||||
|  | typeset -gi _ZSH_AUTOSUGGEST_PENDING_OFFSET=0 | ||||||
|  | typeset -g _ZSH_AUTOSUGGEST_PENDING_BUFFER | ||||||
|  | typeset -g _ZSH_AUTOSUGGEST_LAST_PREFIX | ||||||
|  | 
 | ||||||
| # Disable suggestions | # Disable suggestions | ||||||
| _zsh_autosuggest_disable() { | _zsh_autosuggest_disable() { | ||||||
| 	typeset -g _ZSH_AUTOSUGGEST_DISABLED | 	typeset -g _ZSH_AUTOSUGGEST_DISABLED | ||||||
|  | @ -282,6 +287,69 @@ _zsh_autosuggest_enable() { | ||||||
| 	fi | 	fi | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | _zsh_autosuggest_next() { | ||||||
|  | 	emulate -L zsh | ||||||
|  | 
 | ||||||
|  | 	# Do nothing if suggestions are disabled or there's no buffer to base suggestions on | ||||||
|  | 	if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )) || (( $#BUFFER == 0 )); then | ||||||
|  | 		return 0 | ||||||
|  | 	fi | ||||||
|  | 
 | ||||||
|  | 	local -i current_index=${_ZSH_AUTOSUGGEST_SUGGESTION_INDEX:-0} | ||||||
|  | 	local -i next_index=$((current_index + 1)) | ||||||
|  | 
 | ||||||
|  | 	# Fetch synchronously to avoid races | ||||||
|  | 	_zsh_autosuggest_fetch $next_index "sync" | ||||||
|  | 
 | ||||||
|  | 	# If no suggestion at next_index, wrap back to 0 | ||||||
|  | 	if (( _ZSH_AUTOSUGGEST_SUGGESTION_INDEX != next_index )); then | ||||||
|  | 		_zsh_autosuggest_fetch 0 "sync" | ||||||
|  | 	fi | ||||||
|  | 
 | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | _zsh_autosuggest_previous() { | ||||||
|  | 	emulate -L zsh | ||||||
|  | 
 | ||||||
|  | 	if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )) || (( $#BUFFER == 0 )); then | ||||||
|  | 		return 0 | ||||||
|  | 	fi | ||||||
|  | 
 | ||||||
|  | 	local -i current_index=${_ZSH_AUTOSUGGEST_SUGGESTION_INDEX:-0} | ||||||
|  | 
 | ||||||
|  | 	if (( current_index > 0 )); then | ||||||
|  | 		local -i prev_index=$((current_index - 1)) | ||||||
|  | 		_zsh_autosuggest_fetch $prev_index "sync" | ||||||
|  | 		return 0 | ||||||
|  | 	fi | ||||||
|  | 
 | ||||||
|  | 	# We are at the first suggestion; step forward until we can't to find the last entry. | ||||||
|  | 	_zsh_autosuggest_fetch 0 "sync" | ||||||
|  | 
 | ||||||
|  | 	if [[ -z "$POSTDISPLAY" ]]; then | ||||||
|  | 		return 0 | ||||||
|  | 	fi | ||||||
|  | 
 | ||||||
|  | 	local -i probe=0 | ||||||
|  | 	local -i last_index=0 | ||||||
|  | 
 | ||||||
|  | 	while true; do | ||||||
|  | 		(( probe++ )) | ||||||
|  | 		_zsh_autosuggest_fetch $probe "sync" | ||||||
|  | 
 | ||||||
|  | 		if (( _ZSH_AUTOSUGGEST_SUGGESTION_INDEX == probe )); then | ||||||
|  | 			last_index=$probe | ||||||
|  | 			continue | ||||||
|  | 		fi | ||||||
|  | 
 | ||||||
|  | 		break | ||||||
|  | 	done | ||||||
|  | 
 | ||||||
|  | 	_zsh_autosuggest_fetch $last_index "sync" | ||||||
|  | 
 | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
| # Toggle suggestions (enable/disable) | # Toggle suggestions (enable/disable) | ||||||
| _zsh_autosuggest_toggle() { | _zsh_autosuggest_toggle() { | ||||||
| 	if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )); then | 	if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )); then | ||||||
|  | @ -295,6 +363,8 @@ _zsh_autosuggest_toggle() { | ||||||
| _zsh_autosuggest_clear() { | _zsh_autosuggest_clear() { | ||||||
| 	# Remove the suggestion | 	# Remove the suggestion | ||||||
| 	POSTDISPLAY= | 	POSTDISPLAY= | ||||||
|  | 	_ZSH_AUTOSUGGEST_SUGGESTION_INDEX=0 | ||||||
|  | 	_ZSH_AUTOSUGGEST_LAST_PREFIX="$BUFFER" | ||||||
| 
 | 
 | ||||||
| 	_zsh_autosuggest_invoke_original_widget $@ | 	_zsh_autosuggest_invoke_original_widget $@ | ||||||
| } | } | ||||||
|  | @ -348,12 +418,50 @@ _zsh_autosuggest_modify() { | ||||||
| 
 | 
 | ||||||
| # Fetch a new suggestion based on what's currently in the buffer | # Fetch a new suggestion based on what's currently in the buffer | ||||||
| _zsh_autosuggest_fetch() { | _zsh_autosuggest_fetch() { | ||||||
|  | 	local -i offset=${1:-0} | ||||||
|  | 	local mode="${2:-auto}" | ||||||
|  | 	local use_async=0 | ||||||
|  | 
 | ||||||
|  | 	# Reset offset if the buffer changed since the last suggestion lookup | ||||||
|  | 	if [[ "$BUFFER" != "${_ZSH_AUTOSUGGEST_LAST_PREFIX-}" ]]; then | ||||||
|  | 		offset=0 | ||||||
|  | 	fi | ||||||
|  | 
 | ||||||
| 	if (( ${+ZSH_AUTOSUGGEST_USE_ASYNC} )); then | 	if (( ${+ZSH_AUTOSUGGEST_USE_ASYNC} )); then | ||||||
| 		_zsh_autosuggest_async_request "$BUFFER" | 		use_async=1 | ||||||
|  | 	fi | ||||||
|  | 
 | ||||||
|  | 	if [[ "$mode" == "sync" ]]; then | ||||||
|  | 		use_async=0 | ||||||
|  | 		# Cancel any pending async request so results don't race the sync fetch | ||||||
|  | 		_zsh_autosuggest_async_cancel | ||||||
|  | 	fi | ||||||
|  | 
 | ||||||
|  | 	_ZSH_AUTOSUGGEST_PENDING_OFFSET=$offset | ||||||
|  | 	_ZSH_AUTOSUGGEST_PENDING_BUFFER="$BUFFER" | ||||||
|  | 
 | ||||||
|  | 	if (( use_async )); then | ||||||
|  | 		_zsh_autosuggest_async_request "$BUFFER" $offset | ||||||
| 	else | 	else | ||||||
| 		local suggestion | 		local suggestion | ||||||
| 		_zsh_autosuggest_fetch_suggestion "$BUFFER" | 		_zsh_autosuggest_fetch_suggestion "$BUFFER" $offset | ||||||
|  | 
 | ||||||
|  | 		if [[ -n "$suggestion" ]]; then | ||||||
|  | 			_ZSH_AUTOSUGGEST_SUGGESTION_INDEX=$offset | ||||||
|  | 			_ZSH_AUTOSUGGEST_LAST_PREFIX="$BUFFER" | ||||||
|  | 		else | ||||||
|  | 			if (( offset > 0 )); then | ||||||
|  | 				_ZSH_AUTOSUGGEST_SUGGESTION_INDEX=0 | ||||||
|  | 			fi | ||||||
|  | 			# Ensure state tracks the current buffer even when no suggestion exists | ||||||
|  | 			_ZSH_AUTOSUGGEST_LAST_PREFIX="$BUFFER" | ||||||
|  | 		fi | ||||||
|  | 
 | ||||||
| 		_zsh_autosuggest_suggest "$suggestion" | 		_zsh_autosuggest_suggest "$suggestion" | ||||||
|  | 
 | ||||||
|  | 		# Clear pending markers for synchronous flow | ||||||
|  | 		unset _ZSH_AUTOSUGGEST_PENDING_BUFFER | ||||||
|  | 		_ZSH_AUTOSUGGEST_PENDING_OFFSET=0 | ||||||
| 	fi | 	fi | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -464,6 +572,8 @@ _zsh_autosuggest_partial_accept() { | ||||||
| 		clear | 		clear | ||||||
| 		fetch | 		fetch | ||||||
| 		suggest | 		suggest | ||||||
|  | 		next | ||||||
|  | 		previous | ||||||
| 		accept | 		accept | ||||||
| 		execute | 		execute | ||||||
| 		enable | 		enable | ||||||
|  | @ -596,7 +706,8 @@ _zsh_autosuggest_strategy_completion() { | ||||||
| 	setopt EXTENDED_GLOB | 	setopt EXTENDED_GLOB | ||||||
| 
 | 
 | ||||||
| 	typeset -g suggestion | 	typeset -g suggestion | ||||||
| 	local line REPLY | 	local -i offset=${2:-0} | ||||||
|  | 	local line | ||||||
| 
 | 
 | ||||||
| 	# Exit if we don't have completions | 	# Exit if we don't have completions | ||||||
| 	whence compdef >/dev/null || return | 	whence compdef >/dev/null || return | ||||||
|  | @ -629,8 +740,16 @@ _zsh_autosuggest_strategy_completion() { | ||||||
| 		# Destroy the pty | 		# Destroy the pty | ||||||
| 		zpty -d $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME | 		zpty -d $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME | ||||||
| 	} | 	} | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
|  | 	if [[ -n "$suggestion" ]]; then | ||||||
|  | 		REPLY=1 | ||||||
|  | 		if (( offset > 0 )); then | ||||||
|  | 			unset suggestion | ||||||
|  | 		fi | ||||||
|  | 	else | ||||||
|  | 		REPLY=0 | ||||||
|  | 	fi | ||||||
|  | } | ||||||
| #--------------------------------------------------------------------# | #--------------------------------------------------------------------# | ||||||
| # History Suggestion Strategy                                        # | # History Suggestion Strategy                                        # | ||||||
| #--------------------------------------------------------------------# | #--------------------------------------------------------------------# | ||||||
|  | @ -645,22 +764,43 @@ _zsh_autosuggest_strategy_history() { | ||||||
| 	# Enable globbing flags so that we can use (#m) and (x~y) glob operator | 	# Enable globbing flags so that we can use (#m) and (x~y) glob operator | ||||||
| 	setopt EXTENDED_GLOB | 	setopt EXTENDED_GLOB | ||||||
| 
 | 
 | ||||||
|  | 	local raw_prefix="$1" | ||||||
|  | 	local -i offset=${2:-0} | ||||||
|  | 
 | ||||||
| 	# Escape backslashes and all of the glob operators so we can use | 	# Escape backslashes and all of the glob operators so we can use | ||||||
| 	# this string as a pattern to search the $history associative array. | 	# this string as a pattern to search the history list. | ||||||
| 	# - (#m) globbing flag enables setting references for match data | 	# - (#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 | 	# TODO: Use (b) flag when we can drop support for zsh older than v5.0.8 | ||||||
| 	local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" | 	local prefix="${raw_prefix//(#m)[\\*?\[\]<>()|^~#]/\\$MATCH}" | ||||||
| 
 | 
 | ||||||
| 	# Get the history items that match the prefix, excluding those that match | 	# Build the matcher, excluding entries that match the ignore pattern | ||||||
| 	# the ignore pattern |  | ||||||
| 	local pattern="$prefix*" | 	local pattern="$prefix*" | ||||||
| 	if [[ -n $ZSH_AUTOSUGGEST_HISTORY_IGNORE ]]; then | 	if [[ -n $ZSH_AUTOSUGGEST_HISTORY_IGNORE ]]; then | ||||||
| 		pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)" | 		pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)" | ||||||
| 	fi | 	fi | ||||||
| 
 | 
 | ||||||
| 	# Give the first history item matching the pattern as the suggestion | 	local fc_output | ||||||
| 	# - (r) subscript flag makes the pattern match on values | 	fc_output=$(builtin fc -ln 2>/dev/null) | ||||||
| 	typeset -g suggestion="${history[(r)$pattern]}" | 
 | ||||||
|  | 	local -a matches | ||||||
|  | 	matches=() | ||||||
|  | 
 | ||||||
|  | 	local line | ||||||
|  | 	for line in ${(f)fc_output}; do | ||||||
|  | 		if [[ "$line" == ${~pattern} ]]; then | ||||||
|  | 			matches=("$line" "${matches[@]}") | ||||||
|  | 		fi | ||||||
|  | 	done | ||||||
|  | 
 | ||||||
|  |     # no-op: keep vars local to this function to avoid global pollution | ||||||
|  | 
 | ||||||
|  | 	REPLY=$#matches | ||||||
|  | 
 | ||||||
|  | 	if (( offset < $#matches )); then | ||||||
|  | 		typeset -g suggestion="${matches[offset+1]}" | ||||||
|  | 	else | ||||||
|  | 		unset suggestion | ||||||
|  | 	fi | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #--------------------------------------------------------------------# | #--------------------------------------------------------------------# | ||||||
|  | @ -691,8 +831,11 @@ _zsh_autosuggest_strategy_match_prev_cmd() { | ||||||
| 	# Enable globbing flags so that we can use (#m) and (x~y) glob operator | 	# Enable globbing flags so that we can use (#m) and (x~y) glob operator | ||||||
| 	setopt EXTENDED_GLOB | 	setopt EXTENDED_GLOB | ||||||
| 
 | 
 | ||||||
|  | 	local raw_prefix="$1" | ||||||
|  | 	local -i offset=${2:-0} | ||||||
|  | 
 | ||||||
| 	# TODO: Use (b) flag when we can drop support for zsh older than v5.0.8 | 	# TODO: Use (b) flag when we can drop support for zsh older than v5.0.8 | ||||||
| 	local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" | 	local prefix="${raw_prefix//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" | ||||||
| 
 | 
 | ||||||
| 	# Get the history items that match the prefix, excluding those that match | 	# Get the history items that match the prefix, excluding those that match | ||||||
| 	# the ignore pattern | 	# the ignore pattern | ||||||
|  | @ -701,10 +844,9 @@ _zsh_autosuggest_strategy_match_prev_cmd() { | ||||||
| 		pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)" | 		pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)" | ||||||
| 	fi | 	fi | ||||||
| 
 | 
 | ||||||
| 	# Get all history event numbers that correspond to history | 	# Get all history event numbers that correspond to history entries that match the pattern | ||||||
| 	# entries that match the pattern |  | ||||||
| 	local history_match_keys | 	local history_match_keys | ||||||
| 	history_match_keys=(${(k)history[(R)$~pattern]}) | 	history_match_keys=(${(kOn)history[(R)$pattern]}) | ||||||
| 
 | 
 | ||||||
| 	# By default we use the first history number (most recent history entry) | 	# By default we use the first history number (most recent history entry) | ||||||
| 	local histkey="${history_match_keys[1]}" | 	local histkey="${history_match_keys[1]}" | ||||||
|  | @ -725,8 +867,29 @@ _zsh_autosuggest_strategy_match_prev_cmd() { | ||||||
| 		fi | 		fi | ||||||
| 	done | 	done | ||||||
| 
 | 
 | ||||||
| 	# Give back the matched history entry | 	local -a ordered_keys matches | ||||||
| 	typeset -g suggestion="$history[$histkey]" | 	if [[ -n "$histkey" ]]; then | ||||||
|  | 		ordered_keys=("$histkey") | ||||||
|  | 	fi | ||||||
|  | 
 | ||||||
|  | 	for key in "${(@)history_match_keys[1,200]}"; do | ||||||
|  | 		[[ -n "$key" ]] || continue | ||||||
|  | 		[[ "$key" = "$histkey" ]] && continue | ||||||
|  | 		ordered_keys+=("$key") | ||||||
|  | 	done | ||||||
|  | 
 | ||||||
|  | 	matches=() | ||||||
|  | 	for key in $ordered_keys; do | ||||||
|  | 		matches+=("${history[$key]}") | ||||||
|  | 	done | ||||||
|  | 
 | ||||||
|  | 	REPLY=$#matches | ||||||
|  | 
 | ||||||
|  | 	if (( offset < $#matches )); then | ||||||
|  | 		typeset -g suggestion="${matches[offset+1]}" | ||||||
|  | 	else | ||||||
|  | 		unset suggestion | ||||||
|  | 	fi | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #--------------------------------------------------------------------# | #--------------------------------------------------------------------# | ||||||
|  | @ -738,21 +901,56 @@ _zsh_autosuggest_strategy_match_prev_cmd() { | ||||||
| 
 | 
 | ||||||
| _zsh_autosuggest_fetch_suggestion() { | _zsh_autosuggest_fetch_suggestion() { | ||||||
| 	typeset -g suggestion | 	typeset -g suggestion | ||||||
|  | 	local prefix="$1" | ||||||
|  | 	local -i remaining_offset=${2:-0} | ||||||
| 	local -a strategies | 	local -a strategies | ||||||
| 	local strategy | 	local strategy | ||||||
|  | 	local reply_value | ||||||
|  | 	local -i strategy_count | ||||||
| 
 | 
 | ||||||
| 	# Ensure we are working with an array | 	# Ensure we are working with an array | ||||||
| 	strategies=(${=ZSH_AUTOSUGGEST_STRATEGY}) | 	strategies=(${=ZSH_AUTOSUGGEST_STRATEGY}) | ||||||
| 
 | 
 | ||||||
|  | 	# Reset global suggestion result | ||||||
|  | 	unset suggestion | ||||||
|  | 
 | ||||||
| 	for strategy in $strategies; do | 	for strategy in $strategies; do | ||||||
|  | 		reply_value= | ||||||
|  | 		REPLY= | ||||||
|  | 
 | ||||||
| 		# Try to get a suggestion from this strategy | 		# Try to get a suggestion from this strategy | ||||||
| 		_zsh_autosuggest_strategy_$strategy "$1" | 		_zsh_autosuggest_strategy_$strategy "$prefix" $remaining_offset | ||||||
| 
 | 
 | ||||||
| 		# Ensure the suggestion matches the prefix | 		# Ensure the suggestion matches the prefix | ||||||
| 		[[ "$suggestion" != "$1"* ]] && unset suggestion | 		if [[ "$suggestion" != "$prefix"* ]]; then | ||||||
|  | 			unset suggestion | ||||||
|  | 		fi | ||||||
| 
 | 
 | ||||||
| 		# Break once we've found a valid suggestion | 		# Break once we've found a valid suggestion | ||||||
| 		[[ -n "$suggestion" ]] && break | 		if [[ -n "$suggestion" ]]; then | ||||||
|  | 			break | ||||||
|  | 		fi | ||||||
|  | 
 | ||||||
|  | 		# Determine how many suggestions this strategy can offer so we can | ||||||
|  | 		# decrement the remaining offset before trying the next strategy. | ||||||
|  | 		reply_value="$REPLY" | ||||||
|  | 		if [[ -n "$reply_value" && "$reply_value" = <-> ]]; then | ||||||
|  | 			strategy_count=$reply_value | ||||||
|  | 		elif [[ -n "$reply_value" ]]; then | ||||||
|  | 			# Treat non-numeric replies as zero to avoid arithmetic errors | ||||||
|  | 			strategy_count=0 | ||||||
|  | 		else | ||||||
|  | 			# Preserve existing behaviour when the strategy doesn't report a count. | ||||||
|  | 			strategy_count=0 | ||||||
|  | 		fi | ||||||
|  | 
 | ||||||
|  | 		if (( remaining_offset > 0 && strategy_count > 0 )); then | ||||||
|  | 			if (( remaining_offset >= strategy_count )); then | ||||||
|  | 				remaining_offset=$((remaining_offset - strategy_count)) | ||||||
|  | 			else | ||||||
|  | 				remaining_offset=0 | ||||||
|  | 			fi | ||||||
|  | 		fi | ||||||
| 	done | 	done | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -760,18 +958,13 @@ _zsh_autosuggest_fetch_suggestion() { | ||||||
| # Async                                                              # | # Async                                                              # | ||||||
| #--------------------------------------------------------------------# | #--------------------------------------------------------------------# | ||||||
| 
 | 
 | ||||||
| _zsh_autosuggest_async_request() { | _zsh_autosuggest_async_cancel() { | ||||||
| 	zmodload zsh/system 2>/dev/null # For `$sysparams` |  | ||||||
| 
 |  | ||||||
| 	typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD _ZSH_AUTOSUGGEST_CHILD_PID |  | ||||||
| 
 |  | ||||||
| 	# If we've got a pending request, cancel it |  | ||||||
| 	if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then | 	if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then | ||||||
| 		# Close the file descriptor and remove the handler | 		# Close the file descriptor and remove the handler | ||||||
| 		builtin exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&- | 		builtin exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&- | ||||||
| 		zle -F $_ZSH_AUTOSUGGEST_ASYNC_FD | 		zle -F $_ZSH_AUTOSUGGEST_ASYNC_FD | ||||||
|  | 	fi | ||||||
| 
 | 
 | ||||||
| 		# We won't know the pid unless the user has zsh/system module installed |  | ||||||
| 	if [[ -n "$_ZSH_AUTOSUGGEST_CHILD_PID" ]]; then | 	if [[ -n "$_ZSH_AUTOSUGGEST_CHILD_PID" ]]; then | ||||||
| 		# Zsh will make a new process group for the child process only if job | 		# Zsh will make a new process group for the child process only if job | ||||||
| 		# control is enabled (MONITOR option) | 		# control is enabled (MONITOR option) | ||||||
|  | @ -786,7 +979,24 @@ _zsh_autosuggest_async_request() { | ||||||
| 			kill -TERM $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null | 			kill -TERM $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null | ||||||
| 		fi | 		fi | ||||||
| 	fi | 	fi | ||||||
| 	fi | 
 | ||||||
|  | 	_ZSH_AUTOSUGGEST_ASYNC_FD= | ||||||
|  | 	_ZSH_AUTOSUGGEST_CHILD_PID= | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | _zsh_autosuggest_async_request() { | ||||||
|  | 	zmodload zsh/system 2>/dev/null # For `$sysparams` | ||||||
|  | 
 | ||||||
|  | 	typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD _ZSH_AUTOSUGGEST_CHILD_PID | ||||||
|  | 
 | ||||||
|  | 	local buffer="$1" | ||||||
|  | 	local -i offset=${2:-0} | ||||||
|  | 
 | ||||||
|  | 	_zsh_autosuggest_async_cancel | ||||||
|  | 
 | ||||||
|  | 	typeset -g _ZSH_AUTOSUGGEST_PENDING_BUFFER _ZSH_AUTOSUGGEST_PENDING_OFFSET | ||||||
|  | 	_ZSH_AUTOSUGGEST_PENDING_BUFFER="$buffer" | ||||||
|  | 	_ZSH_AUTOSUGGEST_PENDING_OFFSET=$offset | ||||||
| 
 | 
 | ||||||
| 	# Fork a process to fetch a suggestion and open a pipe to read from it | 	# Fork a process to fetch a suggestion and open a pipe to read from it | ||||||
| 	builtin exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <( | 	builtin exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <( | ||||||
|  | @ -795,7 +1005,7 @@ _zsh_autosuggest_async_request() { | ||||||
| 
 | 
 | ||||||
| 		# Fetch and print the suggestion | 		# Fetch and print the suggestion | ||||||
| 		local suggestion | 		local suggestion | ||||||
| 		_zsh_autosuggest_fetch_suggestion "$1" | 		_zsh_autosuggest_fetch_suggestion "$buffer" $offset | ||||||
| 		echo -nE "$suggestion" | 		echo -nE "$suggestion" | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
|  | @ -822,6 +1032,22 @@ _zsh_autosuggest_async_response() { | ||||||
| 	if [[ -z "$2" || "$2" == "hup" ]]; then | 	if [[ -z "$2" || "$2" == "hup" ]]; then | ||||||
| 		# Read everything from the fd and give it as a suggestion | 		# Read everything from the fd and give it as a suggestion | ||||||
| 		IFS='' read -rd '' -u $1 suggestion | 		IFS='' read -rd '' -u $1 suggestion | ||||||
|  | 		if [[ -n "$suggestion" && "$suggestion" = "$BUFFER"* ]]; then | ||||||
|  | 			typeset -g _ZSH_AUTOSUGGEST_SUGGESTION_INDEX _ZSH_AUTOSUGGEST_LAST_PREFIX | ||||||
|  | 			if [[ -n "${_ZSH_AUTOSUGGEST_PENDING_BUFFER-}" && "$BUFFER" = "$_ZSH_AUTOSUGGEST_PENDING_BUFFER" ]]; then | ||||||
|  | 				_ZSH_AUTOSUGGEST_SUGGESTION_INDEX=${_ZSH_AUTOSUGGEST_PENDING_OFFSET:-0} | ||||||
|  | 				_ZSH_AUTOSUGGEST_LAST_PREFIX="$BUFFER" | ||||||
|  | 			else | ||||||
|  | 				# Fall back to updating state using the current buffer if it still | ||||||
|  | 				# matches the suggestion. | ||||||
|  | 				_ZSH_AUTOSUGGEST_SUGGESTION_INDEX=${_ZSH_AUTOSUGGEST_PENDING_OFFSET:-0} | ||||||
|  | 				_ZSH_AUTOSUGGEST_LAST_PREFIX="$BUFFER" | ||||||
|  | 			fi | ||||||
|  | 		elif [[ -z "$suggestion" ]]; then | ||||||
|  | 			typeset -g _ZSH_AUTOSUGGEST_SUGGESTION_INDEX _ZSH_AUTOSUGGEST_LAST_PREFIX | ||||||
|  | 			_ZSH_AUTOSUGGEST_SUGGESTION_INDEX=0 | ||||||
|  | 			_ZSH_AUTOSUGGEST_LAST_PREFIX="$BUFFER" | ||||||
|  | 		fi | ||||||
| 		zle autosuggest-suggest -- "$suggestion" | 		zle autosuggest-suggest -- "$suggestion" | ||||||
| 
 | 
 | ||||||
| 		# Close the fd | 		# Close the fd | ||||||
|  | @ -831,6 +1057,11 @@ _zsh_autosuggest_async_response() { | ||||||
| 	# Always remove the handler | 	# Always remove the handler | ||||||
| 	zle -F "$1" | 	zle -F "$1" | ||||||
| 	_ZSH_AUTOSUGGEST_ASYNC_FD= | 	_ZSH_AUTOSUGGEST_ASYNC_FD= | ||||||
|  | 	_ZSH_AUTOSUGGEST_CHILD_PID= | ||||||
|  | 
 | ||||||
|  | 	typeset -g _ZSH_AUTOSUGGEST_PENDING_BUFFER _ZSH_AUTOSUGGEST_PENDING_OFFSET | ||||||
|  | 	unset _ZSH_AUTOSUGGEST_PENDING_BUFFER | ||||||
|  | 	_ZSH_AUTOSUGGEST_PENDING_OFFSET=0 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #--------------------------------------------------------------------# | #--------------------------------------------------------------------# | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue