zsh-autosuggestions icon indicating copy to clipboard operation
zsh-autosuggestions copied to clipboard

Autosuggest eats buffer by ctrl+w

Open jsirex opened this issue 7 years ago • 13 comments

Given command: echo first second I want to cut two words first second by typing CTRL+WW .

But then when I try to paste it with CTRL+Y I'm getting only one word: first.

I'm using oh-my-zsh:

plugins=(aws backup git extract knife knife_ssh mercurial mvn vagrant bundler gem rake rvm thor debian sudo kitchen docker-compose emacs berkshelf power-save terraform systemd zsh-autosuggestions)

jsirex avatar Jul 30 '18 11:07 jsirex

I pushed a failing spec to fixes/kill-multiple-words

multiple words killed with `backward-kill-word`
  can be yanked back with `yank` (FAILED - 1)

Failures:

  1) multiple words killed with `backward-kill-word` can be yanked back with `yank`
     Failure/Error: wait_for { session.content }.to eq('echo first second')
     
       expected: "echo first second"
            got: "echo first"
     
       (compared using ==)
     # /usr/local/bundle/gems/rspec-wait-0.0.9/lib/rspec/wait/handler.rb:13:in `block (2 levels) in handle_matcher'
     # /usr/local/bundle/gems/rspec-wait-0.0.9/lib/rspec/wait/handler.rb:10:in `loop'
     # /usr/local/bundle/gems/rspec-wait-0.0.9/lib/rspec/wait/handler.rb:10:in `block in handle_matcher'
     # /usr/local/bundle/gems/rspec-wait-0.0.9/lib/rspec/wait/handler.rb:9:in `handle_matcher'
     # /usr/local/bundle/gems/rspec-wait-0.0.9/lib/rspec/wait/target.rb:30:in `block in to'
     # /usr/local/bundle/gems/rspec-wait-0.0.9/lib/rspec/wait/target.rb:44:in `block in with_wait'
     # /usr/local/bundle/gems/rspec-wait-0.0.9/lib/rspec/wait.rb:28:in `with_wait'
     # /usr/local/bundle/gems/rspec-wait-0.0.9/lib/rspec/wait/target.rb:44:in `with_wait'
     # /usr/local/bundle/gems/rspec-wait-0.0.9/lib/rspec/wait/target.rb:30:in `to'
     # ./spec/integrations/kill_word_spec.rb:12:in `block (2 levels) in <top (required)>'
     # ./spec/spec_helper.rb:19:in `block (2 levels) in <top (required)>'
     # /usr/local/bundle/gems/rspec-wait-0.0.9/lib/rspec/wait.rb:46:in `block (2 levels) in <top (required)>'
     # ------------------
     # --- Caused by: ---
     # Timeout::Error:
     #   execution expired
     #   /usr/local/bundle/gems/rspec-wait-0.0.9/lib/rspec/wait/handler.rb:16:in `sleep'

Finished in 2.15 seconds (files took 0.24763 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/integrations/kill_word_spec.rb:10 # multiple words killed with `backward-kill-word` can be yanked back with `yank`

ericfreese avatar Dec 22 '18 06:12 ericfreese

Looks like this behavior can be reproduced by simply wrapping backward-kill-word in a user-defined widget.

% my-backward-kill-word() { zle backward-kill-word }
% zle -N my-backward-kill-word
% bindkey ^W my-backward-kill-word

ericfreese avatar Dec 22 '18 06:12 ericfreese

The functionality where cut text is concatenated to be yanked in combined form later seems to be dependent on the two built-in cutting widgets running one directly after the other.

Some interesting places in the upstream code:

  • The backward-kill-word has flag ZLE_KILL which flags it as a "kill" type command.
  • There's functionality here that checks if the last widget executed had the ZLE_KILL flag set. If it didn't, it will start a new buffer and thus not concatenate the text cut this time with the text cut last time.
  • lastcmd flags are reset to zero here when the user-defined widget finishes running.

So the flow when using my-backward-kill-word twice is something like:

  1. backward-kill-word built in finishes: from now on lastcmd was a kill command
  2. my-backward-kill-word widget finishes: from now on lastcmd was not a kill command
  3. backward-kill-word built in finishes: from now on lastcmd was a kill command
  4. my-backward-kill-word widget finishes: from now on lastcmd was not a kill command

Because the kill commands aren't coming one after another, the text is not concatenated for yanking.

Compare with the flow when using built-in backward-kill-word:

  1. backward-kill-word built in finishes: from now on lastcmd was a kill command
  2. backward-kill-word built in finishes: from now on lastcmd was a kill command

Not sure how this could be fixed. Next step is probably an email to the mailing list.

ericfreese avatar Dec 22 '18 08:12 ericfreese

Actually... there's a pretty easy fix if you're ok with not fetching suggestions after backward-kill-word is executed.

Add backward-kill-word to the list of ignore widgets in your zshrc:

# After sourcing zsh-autosuggestions.zsh
ZSH_AUTOSUGGEST_IGNORE_WIDGETS+=(backward-kill-word)

ericfreese avatar Dec 22 '18 08:12 ericfreese

Also found this related issue in zsh-syntax-highlighting: https://github.com/zsh-users/zsh-syntax-highlighting/issues/150

Looks like it hasn't been merged yet, but there's a commit https://github.com/danielshahaf/zsh-syntax-highlighting/commit/bfa71c983fa6c3b43cc657276223410123d5c145 that uses zle -f (only available in zsh >=5.2) to set the "kill" flag on the user-defined widgets wrapping the builtin kill widgets.

ericfreese avatar Dec 23 '18 16:12 ericfreese

Here is a patch based on @ericfreese comment and the solution he has linked

zsh-autosuggestions.patch.txt

macdems avatar Feb 05 '20 08:02 macdems

I ran into this issue on and @ericfreese's hack worked for me. Would be great to get a long term fix for this merged in. Thanks for all of the great work on this plugin!

iloveitaly avatar Jun 11 '20 14:06 iloveitaly

The issue with the proposed workaround is that it leaves the suggestion while this is not valid.

@macdems Maybe you could start a pull request with your suggestion? I think you need to somehow put the list of widgets into a specific variable, like this is done for ZSH_AUTOSUGGEST_IGNORE_WIDGETS. If you don't have time, I can do that for you.

vincentbernat avatar Jun 27 '20 21:06 vincentbernat

@vincentbernat I can do this, but next week the earliest (no I am almost fully offline with no access to any computer).

macdems avatar Jun 28 '20 10:06 macdems

I have created a pull request. In my system yanking does not leave the suggestion, so it also seems to solve #526.

macdems avatar Jul 08 '20 09:07 macdems

I have created a pull request. In my system yanking does not leave the suggestion, so it also seems to solve #526.

I tried applying this patch on top of upstream. Ctrl-w Ctrl-y is still broken for me after that.

artem-nefedov avatar Apr 18 '23 20:04 artem-nefedov

Has anyone gotten the normal behavior of select-word-style bash to actually work while zsh-autosuggestions is enabled?

I've been seeing broken behavior where Ctrl+w will gobble up the entire line always into the cut buffer, no matter that WORDCHARS had been set to (e.g. so as to not include spaces). Then Ctrl+y shows what is there by "yanking" (pasting) that entire line out, not just the last word. When I use the ZSH_AUTOSUGGEST_IGNORE_WIDGETS workaround, and disable select-word-style bash, then normal Zsh behavior for backward-kill-word is restored where one word at a time can be deleted backwards via Ctrl+w, then pasted back with Ctrl+y. Yet, then I can't set it up like bash word behavior because Zsh defaults to not include / and ' separators as "words".

I've tried out the change in PR #551, but behavior is still the same unless I add backward-kill-word, and backward-kill-word-match to the ZSH_AUTOSUGGEST_IGNORE_WIDGETS array, and disable select-word-style bash entirely. So my choices seem to be: use zsh-autosuggestions but be forced to have Zsh default Ctrl+w word behavior, OR disable zsh-autosuggestions and then get select-word-style bash behavior working.

The only way I've been able to get normal select-word-style bash behavior is to not load zsh-autosuggestions plugin at all! 🤷

trinitronx avatar Jun 03 '24 09:06 trinitronx

@trinitronx I just tried out PR #551 with zsh 5.9 and it works for me, though it also need a little fix for yank-pop as noted by @vincentbernat which I've added in another PR #795

aculich avatar Jul 14 '24 20:07 aculich