diff --git a/CHANGELOG.md b/CHANGELOG.md index 3134f5f..f017be2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## v0.4.3 +- Avoid bell when accepting suggestions with `autosuggest-accept` (#228) +- Don't fetch suggestions after [up,down]-line-or-beginning-search (#227, #241) +- We are now running CI against new 5.5.1 version +- Fix partial-accept in vi mode (#188) +- Fix suggestion disappearing on fast movement after switching to `vicmd` mode (#290) +- Fix issue rotating through kill ring with `yank-pop` (#301) +- Fix issue creating new pty for async mode when previous pty is not properly cleaned up (#249) + ## v0.4.2 - Fix bug in zsh versions older than 5.0.8 (#296) - Officially support back to zsh v4.3.11 diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..945cec7 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,67 @@ +## Installation + +### Manual (Git Clone) + +1. Clone this repository somewhere on your machine. This guide will assume `~/.zsh/zsh-autosuggestions`. + + ```sh + git clone https://github.com/zsh-users/zsh-autosuggestions ~/.zsh/zsh-autosuggestions + ``` + +2. Add the following to your `.zshrc`: + + ```sh + source ~/.zsh/zsh-autosuggestions/zsh-autosuggestions.zsh + ``` + +3. Start a new terminal session. + +### Oh My Zsh + +1. Clone this repository into `$ZSH_CUSTOM/plugins` (by default `~/.oh-my-zsh/custom/plugins`) + + ```sh + git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions + ``` + +2. Add the plugin to the list of plugins for Oh My Zsh to load: + + ```sh + plugins=(zsh-autosuggestions) + ``` + +3. Start a new terminal session. + +### Arch Linux + +1. Install [`zsh-autosuggestions`](https://www.archlinux.org/packages/community/any/zsh-autosuggestions/) from the `community` repository. + + ```sh + pacman -S zsh-autosuggestions + ``` + + or, to use a package based on the `master` branch, install [`zsh-autosuggestions-git`](https://aur.archlinux.org/packages/zsh-autosuggestions-git/) from the [AUR](https://wiki.archlinux.org/index.php/Arch_User_Repository). + +2. Add the following to your `.zshrc`: + + ```sh + source /usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh + ``` + +3. Start a new terminal session. + +### macOS via Homebrew +1. Install the `zsh-autosuggestions` package using [Homebrew](https://brew.sh/). + + ```sh + brew install zsh-autosuggestions + ``` + +2. Add the following to your `.zshrc`: + + ```sh + source /usr/local/share/zsh-autosuggestions/zsh-autosuggestions.zsh + ``` + +3. Start a new terminal session. + diff --git a/LICENSE b/LICENSE index ad6594e..bcbc8b9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ Copyright (c) 2013 Thiago de Arruda -Copyright (c) 2016-2017 Eric Freese +Copyright (c) 2016-2018 Eric Freese Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/README.md b/README.md index a8ad5de..4ad07d8 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ _[Fish](http://fishshell.com/)-like fast/unobtrusive autosuggestions for zsh._ It suggests commands as you type, based on command history. +Requirements: Zsh v4.3.11 or later + [![CircleCI](https://circleci.com/gh/zsh-users/zsh-autosuggestions.svg?style=svg)](https://circleci.com/gh/zsh-users/zsh-autosuggestions) @@ -11,73 +13,8 @@ It suggests commands as you type, based on command history. ## Installation -Requirements: Zsh v4.3.11 or later +See [INSTALL.md](INSTALL.md). -### Manual - -1. Clone this repository somewhere on your machine. This guide will assume `~/.zsh/zsh-autosuggestions`. - - ```sh - git clone https://github.com/zsh-users/zsh-autosuggestions ~/.zsh/zsh-autosuggestions - ``` - -2. Add the following to your `.zshrc`: - - ```sh - source ~/.zsh/zsh-autosuggestions/zsh-autosuggestions.zsh - ``` - -3. Start a new terminal session. - - -### Oh My Zsh - -1. Clone this repository into `$ZSH_CUSTOM/plugins` (by default `~/.oh-my-zsh/custom/plugins`) - - ```sh - git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions - ``` - -2. Add the plugin to the list of plugins for Oh My Zsh to load: - - ```sh - plugins=(zsh-autosuggestions) - ``` - -3. Start a new terminal session. - -### Arch Linux - -1. Install [`zsh-autosuggestions`](https://www.archlinux.org/packages/community/any/zsh-autosuggestions/) from the `community` repository. - - ```sh - pacman -S zsh-autosuggestions - ``` - - or, to use a package based on the `master` branch, install [`zsh-autosuggestions-git`](https://aur.archlinux.org/packages/zsh-autosuggestions-git/) from the [AUR](https://wiki.archlinux.org/index.php/Arch_User_Repository). - -2. Add the following to your `.zshrc`: - - ```sh - source /usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh - ``` - -3. Start a new terminal session. - -### macOS via Homebrew -1. Install the `zsh-autosuggestions` package using [Homebrew](https://brew.sh/). - - ```sh - brew install zsh-autosuggestions - ``` - -2. Add the following to your `.zshrc`: - - ```sh - source /usr/local/share/zsh-autosuggestions/zsh-autosuggestions.zsh - ``` - -3. Start a new terminal session. ## Usage diff --git a/VERSION b/VERSION index 0eec13e..f87d474 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v0.4.2 +v0.4.3 diff --git a/circle.yml b/circle.yml index d0f7633..c804ae1 100644 --- a/circle.yml +++ b/circle.yml @@ -1,6 +1,6 @@ machine: environment: - ZSH_VERSIONS: zsh-dev/4.3.11 zsh/5.0.2 zsh/5.0.8 zsh/5.1.1 zsh/5.2 zsh/5.3.1 zsh/5.4.2 + ZSH_VERSIONS: zsh-dev/4.3.11 zsh/5.0.2 zsh/5.0.8 zsh/5.1.1 zsh/5.2 zsh/5.3.1 zsh/5.4.2 zsh/5.5.1 dependencies: pre: diff --git a/spec/async_spec.rb b/spec/async_spec.rb new file mode 100644 index 0000000..0bcbaa1 --- /dev/null +++ b/spec/async_spec.rb @@ -0,0 +1,41 @@ +context 'with asynchronous suggestions enabled' do + let(:options) { ["ZSH_AUTOSUGGEST_USE_ASYNC="] } + + describe '`up-line-or-beginning-search`' do + let(:before_sourcing) do + -> do + session. + run_command('autoload -U up-line-or-beginning-search'). + run_command('zle -N up-line-or-beginning-search'). + send_string('bindkey "'). + send_keys('C-v').send_keys('up'). + send_string('" up-line-or-beginning-search'). + send_keys('enter') + end + end + + it 'should show previous history entries' do + with_history( + 'echo foo', + 'echo bar', + 'echo baz' + ) do + session.clear_screen + 3.times { session.send_keys('up') } + wait_for { session.content }.to eq("echo foo") + end + end + end + + describe 'exiting a subshell' do + it 'should not cause error messages to be printed' do + session.run_command('$(exit)') + + sleep 1 + + expect(session.content).to eq('$(exit)') + end + end +end + + diff --git a/spec/integrations/vi_mode_spec.rb b/spec/integrations/vi_mode_spec.rb new file mode 100644 index 0000000..cf471b5 --- /dev/null +++ b/spec/integrations/vi_mode_spec.rb @@ -0,0 +1,67 @@ +describe 'when using vi mode' do + let(:before_sourcing) do + -> do + session.run_command('bindkey -v') + end + end + + describe 'moving the cursor after exiting insert mode' do + it 'should not clear the current suggestion' do + with_history('foobar foo') do + session. + send_string('foo'). + send_keys('escape'). + send_keys('h') + + wait_for { session.content }.to eq('foobar foo') + end + end + end + + describe '`vi-forward-word-end`' do + it 'should accept through the end of the current word' do + with_history('foobar foo') do + session. + send_string('foo'). + send_keys('escape'). + send_keys('e'). # vi-forward-word-end + send_keys('a'). # vi-add-next + send_string('baz') + + wait_for { session.content }.to eq('foobarbaz') + end + end + end + + describe '`vi-forward-word`' do + it 'should accept through the first character of the next word' do + with_history('foobar foo') do + session. + send_string('foo'). + send_keys('escape'). + send_keys('w'). # vi-forward-word + send_keys('a'). # vi-add-next + send_string('az') + + wait_for { session.content }.to eq('foobar faz') + end + end + end + + describe '`vi-find-next-char`' do + it 'should accept through the next occurrence of the character' do + with_history('foobar foo') do + session. + send_string('foo'). + send_keys('escape'). + send_keys('f'). # vi-find-next-char + send_keys('o'). + send_keys('a'). # vi-add-next + send_string('b') + + wait_for { session.content }.to eq('foobar fob') + end + end + end +end + diff --git a/spec/kill_ring_spec.rb b/spec/kill_ring_spec.rb new file mode 100644 index 0000000..4d0178f --- /dev/null +++ b/spec/kill_ring_spec.rb @@ -0,0 +1,23 @@ +context 'with some items in the kill ring' do + before do + session. + send_string('echo foo'). + send_keys('C-u'). + send_string('echo bar'). + send_keys('C-u') + end + + describe '`yank-pop`' do + it 'should cycle through all items in the kill ring' do + session.send_keys('C-y') + wait_for { session.content }.to eq('echo bar') + + session.send_keys('escape').send_keys('y') + wait_for { session.content }.to eq('echo foo') + + session.send_keys('escape').send_keys('y') + wait_for { session.content }.to eq('echo bar') + end + end +end + diff --git a/spec/terminal_session.rb b/spec/terminal_session.rb index 82705d3..2d9468f 100644 --- a/spec/terminal_session.rb +++ b/spec/terminal_session.rb @@ -77,6 +77,10 @@ class TerminalSession map(&:to_i) end + def attach! + tmux_command('attach-session') + end + private attr_reader :opts diff --git a/src/async.zsh b/src/async.zsh index 1d1e76d..9a0cfaa 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -23,7 +23,6 @@ _zsh_autosuggest_async_server() { # Silence any error messages exec 2>/dev/null - local strategy=$1 local last_pid while IFS='' read -r -d $'\0' query; do @@ -70,7 +69,7 @@ _zsh_autosuggest_async_pty_create() { fi # Fork a zpty process running the server function - zpty -b $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME "_zsh_autosuggest_async_server _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" + zpty -b $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME _zsh_autosuggest_async_server # Store the fd so we can remove the handler later if (( REPLY )); then @@ -84,13 +83,11 @@ _zsh_autosuggest_async_pty_create() { } _zsh_autosuggest_async_pty_destroy() { - if zpty -t $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null; then - # Remove the input handler - zle -F $_ZSH_AUTOSUGGEST_PTY_FD &>/dev/null + # Remove the input handler + zle -F $_ZSH_AUTOSUGGEST_PTY_FD &>/dev/null - # Destroy the zpty - zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null - fi + # Destroy the zpty + zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null } _zsh_autosuggest_async_pty_recreate() { diff --git a/src/bind.zsh b/src/bind.zsh index ed9a977..42a0dd0 100644 --- a/src/bind.zsh +++ b/src/bind.zsh @@ -106,7 +106,7 @@ _zsh_autosuggest_bind_widgets() { # Given the name of an original widget and args, invoke it, if it exists _zsh_autosuggest_invoke_original_widget() { # Do nothing unless called with at least one arg - (( $# )) || return + (( $# )) || return 0 local original_widget_name="$1" diff --git a/src/config.zsh b/src/config.zsh index ba694c0..597307f 100644 --- a/src/config.zsh +++ b/src/config.zsh @@ -21,6 +21,8 @@ ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( history-beginning-search-backward history-substring-search-up history-substring-search-down + up-line-or-beginning-search + down-line-or-beginning-search up-line-or-history down-line-or-history accept-line @@ -47,6 +49,8 @@ ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=( vi-forward-word-end vi-forward-blank-word vi-forward-blank-word-end + vi-find-next-char + vi-find-next-char-skip ) # Widgets that should be ignored (globbing supported but must be escaped) @@ -57,6 +61,7 @@ ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( set-local-history which-command yank + yank-pop ) # Max size of buffer to trigger autosuggestion. Leave undefined for no upper bound. diff --git a/src/widgets.zsh b/src/widgets.zsh index 8a31133..87bb62e 100644 --- a/src/widgets.zsh +++ b/src/widgets.zsh @@ -55,6 +55,7 @@ _zsh_autosuggest_modify() { # Don't fetch a new suggestion if there's more input to be read immediately if (( $PENDING > 0 )) || (( $KEYS_QUEUED_COUNT > 0 )); then + POSTDISPLAY="$orig_postdisplay" return $retval fi @@ -152,7 +153,7 @@ _zsh_autosuggest_execute() { # Partially accept the suggestion _zsh_autosuggest_partial_accept() { - local -i retval + local -i retval cursor_loc # Save the contents of the buffer so we can restore later if needed local original_buffer="$BUFFER" @@ -164,13 +165,19 @@ _zsh_autosuggest_partial_accept() { _zsh_autosuggest_invoke_original_widget $@ retval=$? + # Normalize cursor location across vi/emacs modes + cursor_loc=$CURSOR + if [[ "$KEYMAP" = "vicmd" ]]; then + cursor_loc=$((cursor_loc + 1)) + fi + # If we've moved past the end of the original buffer - if (( $CURSOR > $#original_buffer )); then + if (( $cursor_loc > $#original_buffer )); then # Set POSTDISPLAY to text right of the cursor - POSTDISPLAY="$RBUFFER" + POSTDISPLAY="${BUFFER[$(($cursor_loc + 1)),$#BUFFER]}" # Clip the buffer at the cursor - BUFFER="$LBUFFER" + BUFFER="${BUFFER[1,$cursor_loc]}" else # Restore the original buffer BUFFER="$original_buffer" diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index ccfe07e..1c3eab5 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -1,8 +1,8 @@ # Fish-like fast/unobtrusive autosuggestions for zsh. # https://github.com/zsh-users/zsh-autosuggestions -# v0.4.2 +# v0.4.3 # Copyright (c) 2013 Thiago de Arruda -# Copyright (c) 2016-2017 Eric Freese +# Copyright (c) 2016-2018 Eric Freese # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -57,6 +57,8 @@ ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( history-beginning-search-backward history-substring-search-up history-substring-search-down + up-line-or-beginning-search + down-line-or-beginning-search up-line-or-history down-line-or-history accept-line @@ -83,6 +85,8 @@ ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=( vi-forward-word-end vi-forward-blank-word vi-forward-blank-word-end + vi-find-next-char + vi-find-next-char-skip ) # Widgets that should be ignored (globbing supported but must be escaped) @@ -93,6 +97,7 @@ ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( set-local-history which-command yank + yank-pop ) # Max size of buffer to trigger autosuggestion. Leave undefined for no upper bound. @@ -238,7 +243,7 @@ _zsh_autosuggest_bind_widgets() { # Given the name of an original widget and args, invoke it, if it exists _zsh_autosuggest_invoke_original_widget() { # Do nothing unless called with at least one arg - (( $# )) || return + (( $# )) || return 0 local original_widget_name="$1" @@ -331,6 +336,7 @@ _zsh_autosuggest_modify() { # Don't fetch a new suggestion if there's more input to be read immediately if (( $PENDING > 0 )) || (( $KEYS_QUEUED_COUNT > 0 )); then + POSTDISPLAY="$orig_postdisplay" return $retval fi @@ -428,7 +434,7 @@ _zsh_autosuggest_execute() { # Partially accept the suggestion _zsh_autosuggest_partial_accept() { - local -i retval + local -i retval cursor_loc # Save the contents of the buffer so we can restore later if needed local original_buffer="$BUFFER" @@ -440,13 +446,19 @@ _zsh_autosuggest_partial_accept() { _zsh_autosuggest_invoke_original_widget $@ retval=$? + # Normalize cursor location across vi/emacs modes + cursor_loc=$CURSOR + if [[ "$KEYMAP" = "vicmd" ]]; then + cursor_loc=$((cursor_loc + 1)) + fi + # If we've moved past the end of the original buffer - if (( $CURSOR > $#original_buffer )); then + if (( $cursor_loc > $#original_buffer )); then # Set POSTDISPLAY to text right of the cursor - POSTDISPLAY="$RBUFFER" + POSTDISPLAY="${BUFFER[$(($cursor_loc + 1)),$#BUFFER]}" # Clip the buffer at the cursor - BUFFER="$LBUFFER" + BUFFER="${BUFFER[1,$cursor_loc]}" else # Restore the original buffer BUFFER="$original_buffer" @@ -589,7 +601,6 @@ _zsh_autosuggest_async_server() { # Silence any error messages exec 2>/dev/null - local strategy=$1 local last_pid while IFS='' read -r -d $'\0' query; do @@ -636,7 +647,7 @@ _zsh_autosuggest_async_pty_create() { fi # Fork a zpty process running the server function - zpty -b $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME "_zsh_autosuggest_async_server _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" + zpty -b $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME _zsh_autosuggest_async_server # Store the fd so we can remove the handler later if (( REPLY )); then @@ -650,13 +661,11 @@ _zsh_autosuggest_async_pty_create() { } _zsh_autosuggest_async_pty_destroy() { - if zpty -t $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null; then - # Remove the input handler - zle -F $_ZSH_AUTOSUGGEST_PTY_FD &>/dev/null + # Remove the input handler + zle -F $_ZSH_AUTOSUGGEST_PTY_FD &>/dev/null - # Destroy the zpty - zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null - fi + # Destroy the zpty + zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null } _zsh_autosuggest_async_pty_recreate() {