zsh-users / zsh-autosuggestions

Fish-like autosuggestions for zsh
MIT License
31.11k stars 1.86k forks source link

Autosuggest eats buffer by ctrl+w #363

Open jsirex opened 6 years ago

jsirex commented 6 years ago

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)
ericfreese commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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:

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 commented 5 years ago

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 commented 5 years ago

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.

macdems commented 4 years ago

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

zsh-autosuggestions.patch.txt

iloveitaly commented 4 years ago

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!

vincentbernat commented 4 years ago

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.

macdems commented 4 years ago

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

macdems commented 4 years ago

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

artem-nefedov commented 1 year ago

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.

trinitronx commented 4 months ago

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! 🤷

aculich commented 2 months ago

@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