Compare commits

..

8 Commits

Author SHA1 Message Date
Eric Freese
3391962a15 Clear async global variables on successful response
If _ZSH_AUTOSUGGEST_ASYNC_FD is not cleared, something else may open the
file descriptor pointed to by it. And then the next call into
_zsh_autosuggest_async_request will close it causing trouble.

It seems like good practice to clean up _ZSH_AUTOSUGGEST_CHILD_PID as
well, though it's not directly causing any known problems at the moment.

I was able to produce errors with ZSH_AUTOSUGGEST_MANUAL_REBIND active
and sourcing a file async-widget-setup.zsh after the first precmd with
the plugin active. If manual rebind is not active or if the widgets are
created before the first precmd, then zsh-autosuggestions wraps the
widgets and for some reason we don't seem to get any fd conflicts.

The .zshrc:

```
ZSH_AUTOSUGGEST_MANUAL_REBIND=true
source zsh-autosuggestions.zsh
```

and async-widget-setup.zsh:

```
function async-widget-fork() {
	exec {fd}< <(echo foo)
	zle -M "opened: $fd, autosuggest fd: $_ZSH_AUTOSUGGEST_ASYNC_FD"
}

function async-widget-read() {
	zle -M "reading from $fd: $(cat <&$fd)"
}

zle -N async-widget-fork
zle -N async-widget-read

bindkey ^A async-widget-fork
bindkey ^B async-widget-read
```

Then run `ZDOTDIR=$PWD zsh` and run `source async-widget-setup.zsh`. At
the next prompt, type one character e.g. "a" to trigger an async
request/response cycle. This leaves _ZSH_AUTOSUGGEST_ASYNC_FD set to the
stale file descriptor number. Then press ^A to activate the fork. This
will set the fd parameter to the same number as
_ZSH_AUTOSUGGEST_ASYNC_FD. Then type another character e.g. "a" to
trigger an async request. This will print a "No handler installed" error
and close the file descriptor pointed to by both
_ZSH_AUTOSUGGEST_ASYNC_FD and fd. Pressing ^B at this point will fail to
read with a "bad file descriptor" error.
2023-08-25 22:05:57 -06:00
Eric Freese
61257de667 Avoid accessing unset param when zsh/system is not available 2023-08-25 16:29:02 -06:00
Eric Freese
84c7cfaa89 Prevent parameter value being interpreted as kill flag 2023-08-25 16:29:02 -06:00
Eric Freese
6259d46d36 Move check for MONITOR directly after fork
This should properly handle the edge case where the MONITOR option is
changed between forking and killing.
2023-08-25 16:28:18 -06:00
Eric Freese
b350b900ce Prevent suggestion being treated as an option for echo 2023-08-25 16:27:29 -06:00
Eric Freese
e386de0247 cleanup: Remove unnecessary/inconsistent quoting from fd arg 2023-08-25 16:27:29 -06:00
Eric Freese
2b730c9634 cleanup: More precisely check number of args instead of second arg value 2023-08-25 16:27:29 -06:00
Eric Freese
35ed23efe8 cleanup: Use simpler arithmetic expression 2023-08-25 16:27:28 -06:00
10 changed files with 61 additions and 90 deletions

2
.gitignore vendored
View File

@ -1,2 +0,0 @@
# zsh word code files
*.zwc

View File

@ -1,12 +1,5 @@
# Changelog
## v0.7.1
- Clear POSTDISPLAY instead of unsetting (#634)
- Always reset async file descriptor after consuming it (#630)
- Always use builtin `exec` (#628)
- Add `history-beginning-search-*-end` widgets to clear widget list (#619)
- Switch CI from Circle CI to GitHub Actions
## v0.7.0
- Enable asynchronous mode by default (#498)
- No longer wrap user widgets starting with `autosuggest-` prefix (#496)

View File

@ -3,21 +3,19 @@
* [Packages](#packages)
* [Antigen](#antigen)
* [Oh My Zsh](#oh-my-zsh)
* [HomeBrew](#homebrew)
* [Manual](#manual-git-clone)
## Packages
| System | Package |
| ------------- | ------------- |
| Alpine Linux | [zsh-autosuggestions](https://pkgs.alpinelinux.org/packages?name=zsh-autosuggestions) |
| Debian / Ubuntu | [zsh-autosuggestions OBS repository](https://software.opensuse.org/download.html?project=shells%3Azsh-users%3Azsh-autosuggestions&package=zsh-autosuggestions) |
| Fedora / CentOS / RHEL / Scientific Linux | [zsh-autosuggestions OBS repository](https://software.opensuse.org/download.html?project=shells%3Azsh-users%3Azsh-autosuggestions&package=zsh-autosuggestions) |
| OpenSUSE / SLE | [zsh-autosuggestions OBS repository](https://software.opensuse.org/download.html?project=shells%3Azsh-users%3Azsh-autosuggestions&package=zsh-autosuggestions) |
| Arch Linux / Manjaro / Antergos / Hyperbola | [zsh-autosuggestions](https://www.archlinux.org/packages/zsh-autosuggestions), [zsh-autosuggestions-git](https://aur.archlinux.org/packages/zsh-autosuggestions-git) |
| NixOS | [zsh-autosuggestions](https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/zs/zsh-autosuggestions/package.nix) |
| NixOS | [zsh-autosuggestions](https://github.com/NixOS/nixpkgs/blob/master/pkgs/shells/zsh/zsh-autosuggestions/default.nix) |
| Void Linux | [zsh-autosuggestions](https://github.com/void-linux/void-packages/blob/master/srcpkgs/zsh-autosuggestions/template) |
| Mac OS | [homebrew](https://github.com/Homebrew/homebrew-core/blob/master/Formula/z/zsh-autosuggestions.rb) |
| Mac OS | [homebrew](https://github.com/Homebrew/homebrew-core/blob/master/Formula/zsh-autosuggestions.rb) |
| NetBSD | [pkgsrc](http://ftp.netbsd.org/pub/pkgsrc/current/pkgsrc/shells/zsh-autosuggestions/README.html) |
## Antigen
@ -49,21 +47,6 @@
3. Start a new terminal session.
## Homebrew
1. Install command:
```sh
brew install zsh-autosuggestions
```
2. To activate the autosuggestions, add the following at the end of your .zshrc:
```sh
source $(brew --prefix)/share/zsh-autosuggestions/zsh-autosuggestions.zsh
```
3. Start a new terminal session.
## Manual (Git Clone)
1. Clone this repository somewhere on your machine. This guide will assume `~/.zsh/zsh-autosuggestions`.

View File

@ -1,4 +1,4 @@
SRC_DIR := ./src
SRC_DIR := ./src
SRC_FILES := \
$(SRC_DIR)/config.zsh \

View File

@ -6,6 +6,7 @@ It suggests commands as you type based on history and completions.
Requirements: Zsh v4.3.11 or later
[![CircleCI](https://img.shields.io/circleci/build/github/zsh-users/zsh-autosuggestions.svg)](https://circleci.com/gh/zsh-users/zsh-autosuggestions)
[![Chat on Gitter](https://img.shields.io/gitter/room/zsh-users/zsh-autosuggestions.svg)](https://gitter.im/zsh-users/zsh-autosuggestions)
<a href="https://asciinema.org/a/37390" target="_blank"><img src="https://asciinema.org/a/37390.png" width="400" /></a>
@ -52,7 +53,7 @@ For more info, read the Character Highlighting section of the zsh manual: `man z
`ZSH_AUTOSUGGEST_STRATEGY` is an array that specifies how suggestions should be generated. The strategies in the array are tried successively until a suggestion is found. There are currently three built-in strategies to choose from:
- `history`: Chooses the most recent match from history.
- `completion`: Chooses a suggestion based on what tab-completion would suggest. (requires `zpty` module, which is included with zsh since 4.0.1)
- `completion`: Chooses a suggestion based on what tab-completion would suggest. (requires `zpty` module)
- `match_prev_cmd`: Like `history`, but 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`.
For example, setting `ZSH_AUTOSUGGEST_STRATEGY=(history completion)` will first try to find a suggestion from your history, but, if it can't find a match, will find a suggestion from the completion engine.

View File

@ -1 +1 @@
v0.7.1
v0.7.0

View File

@ -11,35 +11,24 @@ _zsh_autosuggest_async_request() {
# If we've got a pending request, cancel it
if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then
# Close the file descriptor and remove the handler
builtin exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&-
exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&-
zle -F $_ZSH_AUTOSUGGEST_ASYNC_FD
# We won't know the pid unless the user has zsh/system module installed
if [[ -n "$_ZSH_AUTOSUGGEST_CHILD_PID" ]]; then
# Zsh will make a new process group for the child process only if job
# control is enabled (MONITOR option)
if [[ -o MONITOR ]]; then
# Send the signal to the process group to kill any processes that may
# have been forked by the suggestion strategy
kill -TERM -$_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null
else
# Kill just the child process since it wasn't placed in a new process
# group. If the suggestion strategy forked any child processes they may
# be orphaned and left behind.
kill -TERM $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null
fi
if (( _ZSH_AUTOSUGGEST_CHILD_PID )); then
kill -TERM -- $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null
fi
fi
# Fork a process to fetch a suggestion and open a pipe to read from it
builtin exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <(
# Tell parent process our pid
echo $sysparams[pid]
exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <(
# Tell parent process our pid if we can
echo ${sysparams[pid]:-}
# Fetch and print the suggestion
local suggestion
_zsh_autosuggest_fetch_suggestion "$1"
echo -nE "$suggestion"
echo -nE - "$suggestion"
)
# There's a weird bug here where ^C stops working unless we force a fork
@ -50,6 +39,15 @@ _zsh_autosuggest_async_request() {
# Read the pid from the child process
read _ZSH_AUTOSUGGEST_CHILD_PID <&$_ZSH_AUTOSUGGEST_ASYNC_FD
# Zsh will make a new process group for the child process only if job
# control is enabled (MONITOR option)
if [[ -o MONITOR ]]; then
# If we need to kill the background process in the future, we'll send
# SIGTERM to the process group to kill any processes that may have been
# forked by the suggestion strategy
_ZSH_AUTOSUGGEST_CHILD_PID=${_ZSH_AUTOSUGGEST_CHILD_PID:+-$_ZSH_AUTOSUGGEST_CHILD_PID}
fi
# When the fd is readable, call the response handler
zle -F "$_ZSH_AUTOSUGGEST_ASYNC_FD" _zsh_autosuggest_async_response
}
@ -60,18 +58,20 @@ _zsh_autosuggest_async_request() {
_zsh_autosuggest_async_response() {
emulate -L zsh
typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD _ZSH_AUTOSUGGEST_CHILD_PID
local suggestion
if [[ -z "$2" || "$2" == "hup" ]]; then
if [[ $# == 1 || "$2" == "hup" ]]; then
# Read everything from the fd and give it as a suggestion
IFS='' read -rd '' -u $1 suggestion
zle autosuggest-suggest -- "$suggestion"
# Close the fd
builtin exec {1}<&-
exec {1}<&-
_ZSH_AUTOSUGGEST_ASYNC_FD=
_ZSH_AUTOSUGGEST_ASYNC_PID=
fi
# Always remove the handler
zle -F "$1"
_ZSH_AUTOSUGGEST_ASYNC_FD=
zle -F $1
}

View File

@ -28,8 +28,6 @@ typeset -g ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig-
history-search-backward
history-beginning-search-forward
history-beginning-search-backward
history-beginning-search-forward-end
history-beginning-search-backward-end
history-substring-search-up
history-substring-search-down
up-line-or-beginning-search

View File

@ -30,7 +30,7 @@ _zsh_autosuggest_toggle() {
# Clear the suggestion
_zsh_autosuggest_clear() {
# Remove the suggestion
POSTDISPLAY=
unset POSTDISPLAY
_zsh_autosuggest_invoke_original_widget $@
}
@ -47,7 +47,7 @@ _zsh_autosuggest_modify() {
local orig_postdisplay="$POSTDISPLAY"
# Clear suggestion while waiting for next one
POSTDISPLAY=
unset POSTDISPLAY
# Original widget may modify the buffer
_zsh_autosuggest_invoke_original_widget $@
@ -102,7 +102,7 @@ _zsh_autosuggest_suggest() {
if [[ -n "$suggestion" ]] && (( $#BUFFER )); then
POSTDISPLAY="${suggestion#$BUFFER}"
else
POSTDISPLAY=
unset POSTDISPLAY
fi
}
@ -128,7 +128,7 @@ _zsh_autosuggest_accept() {
BUFFER="$BUFFER$POSTDISPLAY"
# Remove the suggestion
POSTDISPLAY=
unset POSTDISPLAY
# Run the original widget before manually moving the cursor so that the
# cursor movement doesn't make the widget do something unexpected
@ -151,7 +151,7 @@ _zsh_autosuggest_execute() {
BUFFER="$BUFFER$POSTDISPLAY"
# Remove the suggestion
POSTDISPLAY=
unset POSTDISPLAY
# Call the original `accept-line` to handle syntax highlighting or
# other potential custom behavior

View File

@ -1,6 +1,6 @@
# Fish-like fast/unobtrusive autosuggestions for zsh.
# https://github.com/zsh-users/zsh-autosuggestions
# v0.7.1
# v0.7.0
# Copyright (c) 2013 Thiago de Arruda
# Copyright (c) 2016-2021 Eric Freese
#
@ -54,8 +54,6 @@ typeset -g ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig-
history-search-backward
history-beginning-search-forward
history-beginning-search-backward
history-beginning-search-forward-end
history-beginning-search-backward-end
history-substring-search-up
history-substring-search-down
up-line-or-beginning-search
@ -294,7 +292,7 @@ _zsh_autosuggest_toggle() {
# Clear the suggestion
_zsh_autosuggest_clear() {
# Remove the suggestion
POSTDISPLAY=
unset POSTDISPLAY
_zsh_autosuggest_invoke_original_widget $@
}
@ -311,7 +309,7 @@ _zsh_autosuggest_modify() {
local orig_postdisplay="$POSTDISPLAY"
# Clear suggestion while waiting for next one
POSTDISPLAY=
unset POSTDISPLAY
# Original widget may modify the buffer
_zsh_autosuggest_invoke_original_widget $@
@ -366,7 +364,7 @@ _zsh_autosuggest_suggest() {
if [[ -n "$suggestion" ]] && (( $#BUFFER )); then
POSTDISPLAY="${suggestion#$BUFFER}"
else
POSTDISPLAY=
unset POSTDISPLAY
fi
}
@ -392,7 +390,7 @@ _zsh_autosuggest_accept() {
BUFFER="$BUFFER$POSTDISPLAY"
# Remove the suggestion
POSTDISPLAY=
unset POSTDISPLAY
# Run the original widget before manually moving the cursor so that the
# cursor movement doesn't make the widget do something unexpected
@ -415,7 +413,7 @@ _zsh_autosuggest_execute() {
BUFFER="$BUFFER$POSTDISPLAY"
# Remove the suggestion
POSTDISPLAY=
unset POSTDISPLAY
# Call the original `accept-line` to handle syntax highlighting or
# other potential custom behavior
@ -768,35 +766,24 @@ _zsh_autosuggest_async_request() {
# If we've got a pending request, cancel it
if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then
# Close the file descriptor and remove the handler
builtin exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&-
exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&-
zle -F $_ZSH_AUTOSUGGEST_ASYNC_FD
# We won't know the pid unless the user has zsh/system module installed
if [[ -n "$_ZSH_AUTOSUGGEST_CHILD_PID" ]]; then
# Zsh will make a new process group for the child process only if job
# control is enabled (MONITOR option)
if [[ -o MONITOR ]]; then
# Send the signal to the process group to kill any processes that may
# have been forked by the suggestion strategy
kill -TERM -$_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null
else
# Kill just the child process since it wasn't placed in a new process
# group. If the suggestion strategy forked any child processes they may
# be orphaned and left behind.
kill -TERM $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null
fi
if (( _ZSH_AUTOSUGGEST_CHILD_PID )); then
kill -TERM -- $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null
fi
fi
# Fork a process to fetch a suggestion and open a pipe to read from it
builtin exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <(
# Tell parent process our pid
echo $sysparams[pid]
exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <(
# Tell parent process our pid if we can
echo ${sysparams[pid]:-}
# Fetch and print the suggestion
local suggestion
_zsh_autosuggest_fetch_suggestion "$1"
echo -nE "$suggestion"
echo -nE - "$suggestion"
)
# There's a weird bug here where ^C stops working unless we force a fork
@ -807,6 +794,15 @@ _zsh_autosuggest_async_request() {
# Read the pid from the child process
read _ZSH_AUTOSUGGEST_CHILD_PID <&$_ZSH_AUTOSUGGEST_ASYNC_FD
# Zsh will make a new process group for the child process only if job
# control is enabled (MONITOR option)
if [[ -o MONITOR ]]; then
# If we need to kill the background process in the future, we'll send
# SIGTERM to the process group to kill any processes that may have been
# forked by the suggestion strategy
_ZSH_AUTOSUGGEST_CHILD_PID=${_ZSH_AUTOSUGGEST_CHILD_PID:+-$_ZSH_AUTOSUGGEST_CHILD_PID}
fi
# When the fd is readable, call the response handler
zle -F "$_ZSH_AUTOSUGGEST_ASYNC_FD" _zsh_autosuggest_async_response
}
@ -817,20 +813,22 @@ _zsh_autosuggest_async_request() {
_zsh_autosuggest_async_response() {
emulate -L zsh
typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD _ZSH_AUTOSUGGEST_CHILD_PID
local suggestion
if [[ -z "$2" || "$2" == "hup" ]]; then
if [[ $# == 1 || "$2" == "hup" ]]; then
# Read everything from the fd and give it as a suggestion
IFS='' read -rd '' -u $1 suggestion
zle autosuggest-suggest -- "$suggestion"
# Close the fd
builtin exec {1}<&-
exec {1}<&-
_ZSH_AUTOSUGGEST_ASYNC_FD=
_ZSH_AUTOSUGGEST_ASYNC_PID=
fi
# Always remove the handler
zle -F "$1"
_ZSH_AUTOSUGGEST_ASYNC_FD=
zle -F $1
}
#--------------------------------------------------------------------#