From e1959d0f617f8a070623eeec1b5d9d1ec35ba40a Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 28 Feb 2017 11:18:21 -0700 Subject: [PATCH 1/3] Put in a general fix for #219 - Handling input from `zle -U` Depends on patch to ZSH from workers/40702: http://www.zsh.org/mla/workers/2017/msg00414.html --- spec/integrations/zle_input_stack_spec.rb | 24 +++++++++++++++++++++++ src/widgets.zsh | 8 ++++++++ zsh-autosuggestions.zsh | 8 ++++++++ 3 files changed, 40 insertions(+) create mode 100644 spec/integrations/zle_input_stack_spec.rb diff --git a/spec/integrations/zle_input_stack_spec.rb b/spec/integrations/zle_input_stack_spec.rb new file mode 100644 index 0000000..8a2c990 --- /dev/null +++ b/spec/integrations/zle_input_stack_spec.rb @@ -0,0 +1,24 @@ +describe 'using `zle -U`' do + let(:before_sourcing) do + -> do + session. + run_command('_zsh_autosuggest_strategy_test() { sleep 1; _zsh_autosuggest_strategy_default "$1" }'). + run_command('foo() { zle -U - "echo hello" }; zle -N foo; bindkey ^B foo') + end + end + + let(:options) { ['unset ZSH_AUTOSUGGEST_USE_ASYNC', 'ZSH_AUTOSUGGEST_STRATEGY=test'] } + + # TODO: This is only possible with the $KEYS_QUEUED_COUNT widget parameter, coming soon... + xit 'does not fetch a suggestion for every inserted character' do + session.send_keys('C-b') + wait_for { session.content }.to eq('echo hello') + end + + it 'shows a suggestion when the widget completes' do + with_history('echo hello world') do + session.send_keys('C-b') + wait_for { session.content(esc_seqs: true) }.to match(/\Aecho hello\e\[[0-9]+m world/) + end + end +end diff --git a/src/widgets.zsh b/src/widgets.zsh index a0a59d5..7f15a27 100644 --- a/src/widgets.zsh +++ b/src/widgets.zsh @@ -15,6 +15,9 @@ _zsh_autosuggest_clear() { _zsh_autosuggest_modify() { local -i retval + # Only added to zsh very recently + local -i KEYS_QUEUED_COUNT + # Save the contents of the buffer/postdisplay local orig_buffer="$BUFFER" local orig_postdisplay="$POSTDISPLAY" @@ -26,6 +29,11 @@ _zsh_autosuggest_modify() { _zsh_autosuggest_invoke_original_widget $@ retval=$? + # Don't fetch a new suggestion if there's more input to be read immediately + if [[ $PENDING > 0 ]] || [[ $KEYS_QUEUED_COUNT > 0 ]]; then + return $retval + fi + # Optimize if manually typing in the suggestion if [ $#BUFFER -gt $#orig_buffer ]; then local added=${BUFFER#$orig_buffer} diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 05e1bb4..2b41154 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -293,6 +293,9 @@ _zsh_autosuggest_clear() { _zsh_autosuggest_modify() { local -i retval + # Only added to zsh very recently + local -i KEYS_QUEUED_COUNT + # Save the contents of the buffer/postdisplay local orig_buffer="$BUFFER" local orig_postdisplay="$POSTDISPLAY" @@ -304,6 +307,11 @@ _zsh_autosuggest_modify() { _zsh_autosuggest_invoke_original_widget $@ retval=$? + # Don't fetch a new suggestion if there's more input to be read immediately + if [[ $PENDING > 0 ]] || [[ $KEYS_QUEUED_COUNT > 0 ]]; then + return $retval + fi + # Optimize if manually typing in the suggestion if [ $#BUFFER -gt $#orig_buffer ]; then local added=${BUFFER#$orig_buffer} From 7d4a1d9a4a6b471bf148782cccea30fdf946e5e9 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 3 Mar 2017 11:59:30 -0700 Subject: [PATCH 2/3] Add enable/disable/toggle widgets to disable suggestion functionality [GitHub #219] Intended to be helpful for folks using bracketed-paste-magic and other widgets that use `zle -U`. --- README.md | 6 +++++- spec/widgets/disable_spec.rb | 19 +++++++++++++++++++ spec/widgets/enable_spec.rb | 21 +++++++++++++++++++++ spec/widgets/fetch_spec.rb | 24 ++++++++++++++++++++++++ spec/widgets/toggle_spec.rb | 26 ++++++++++++++++++++++++++ src/widgets.zsh | 31 ++++++++++++++++++++++++++++++- zsh-autosuggestions.zsh | 31 ++++++++++++++++++++++++++++++- 7 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 spec/widgets/disable_spec.rb create mode 100644 spec/widgets/enable_spec.rb create mode 100644 spec/widgets/fetch_spec.rb create mode 100644 spec/widgets/toggle_spec.rb diff --git a/README.md b/README.md index 9b363da..0d3c833 100644 --- a/README.md +++ b/README.md @@ -101,11 +101,15 @@ As of `v0.4.0`, suggestions are fetched asynchronously using the `zsh/zpty` modu ### Key Bindings -This plugin provides three widgets that you can use with `bindkey`: +This plugin provides a few widgets that you can use with `bindkey`: 1. `autosuggest-accept`: Accepts the current suggestion. 2. `autosuggest-execute`: Accepts and executes the current suggestion. 3. `autosuggest-clear`: Clears the current suggestion. +4. `autosuggest-fetch`: Fetches a suggestion (works even when suggestions are disabled). +5. `autosuggest-disable`: Disables suggestions. +6. `autosuggest-enable`: Re-enables suggestions. +7. `autosuggest-toggle`: Toggles between enabled/disabled suggestions. For example, this would bind ctrl + space to accept the current suggestion. diff --git a/spec/widgets/disable_spec.rb b/spec/widgets/disable_spec.rb new file mode 100644 index 0000000..b387a59 --- /dev/null +++ b/spec/widgets/disable_spec.rb @@ -0,0 +1,19 @@ +describe 'the `autosuggest-disable` widget' do + before do + session.run_command('bindkey ^B autosuggest-disable') + end + + it 'disables suggestions and clears the suggestion' do + with_history('echo hello') do + session.send_string('echo') + wait_for { session.content }.to eq('echo hello') + + session.send_keys('C-b') + wait_for { session.content }.to eq('echo') + + session.send_string(' h') + sleep 1 + expect(session.content).to eq('echo h') + end + end +end diff --git a/spec/widgets/enable_spec.rb b/spec/widgets/enable_spec.rb new file mode 100644 index 0000000..0322406 --- /dev/null +++ b/spec/widgets/enable_spec.rb @@ -0,0 +1,21 @@ +describe 'the `autosuggest-enable` widget' do + before do + session. + run_command('typeset -g _ZSH_AUTOSUGGEST_DISABLED'). + run_command('bindkey ^B autosuggest-enable') + end + + it 'enables suggestions and fetches a suggestion' do + with_history('echo world', 'echo hello') do + session.send_string('echo') + sleep 1 + expect(session.content).to eq('echo') + + session.send_keys('C-b') + wait_for { session.content }.to eq('echo hello') + + session.send_string(' w') + wait_for { session.content }.to eq('echo world') + end + end +end diff --git a/spec/widgets/fetch_spec.rb b/spec/widgets/fetch_spec.rb new file mode 100644 index 0000000..eb8f2ba --- /dev/null +++ b/spec/widgets/fetch_spec.rb @@ -0,0 +1,24 @@ +describe 'the `autosuggest-fetch` widget' do + context 'when suggestions are disabled' do + before do + session. + run_command('bindkey ^B autosuggest-disable'). + run_command('bindkey ^F autosuggest-fetch'). + send_keys('C-b') + end + + it 'will fetch and display a suggestion' do + with_history('echo hello') do + session.send_string('echo h') + sleep 1 + expect(session.content).to eq('echo h') + + session.send_keys('C-f') + wait_for { session.content }.to eq('echo hello') + + session.send_string('e') + wait_for { session.content }.to eq('echo hello') + end + end + end +end diff --git a/spec/widgets/toggle_spec.rb b/spec/widgets/toggle_spec.rb new file mode 100644 index 0000000..8f9f3c3 --- /dev/null +++ b/spec/widgets/toggle_spec.rb @@ -0,0 +1,26 @@ +describe 'the `autosuggest-toggle` widget' do + before do + session.run_command('bindkey ^B autosuggest-toggle') + end + + it 'toggles suggestions' do + with_history('echo world', 'echo hello') do + session.send_string('echo') + wait_for { session.content }.to eq('echo hello') + + session.send_keys('C-b') + wait_for { session.content }.to eq('echo') + + session.send_string(' h') + sleep 1 + expect(session.content).to eq('echo h') + + session.send_keys('C-b') + wait_for { session.content }.to eq('echo hello') + + session.send_keys('C-h') + session.send_string('w') + wait_for { session.content }.to eq('echo world') + end + end +end diff --git a/src/widgets.zsh b/src/widgets.zsh index 7f15a27..2bcedc4 100644 --- a/src/widgets.zsh +++ b/src/widgets.zsh @@ -3,6 +3,27 @@ # Autosuggest Widget Implementations # #--------------------------------------------------------------------# +# Disable suggestions +_zsh_autosuggest_disable() { + typeset -g _ZSH_AUTOSUGGEST_DISABLED + _zsh_autosuggest_clear +} + +# Enable suggestions +_zsh_autosuggest_enable() { + unset _ZSH_AUTOSUGGEST_DISABLED + _zsh_autosuggest_fetch +} + +# Toggle suggestions (enable/disable) +_zsh_autosuggest_toggle() { + if [ -n "${_ZSH_AUTOSUGGEST_DISABLED+x}" ]; then + _zsh_autosuggest_enable + else + _zsh_autosuggest_disable + fi +} + # Clear the suggestion _zsh_autosuggest_clear() { # Remove the suggestion @@ -51,6 +72,11 @@ _zsh_autosuggest_modify() { return $retval fi + # Bail out if suggestions are disabled + if [ -n "${_ZSH_AUTOSUGGEST_DISABLED+x}" ]; then + return $? + fi + # Get a new suggestion if the buffer is not empty after modification if [ $#BUFFER -gt 0 ]; then if [ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" -o $#BUFFER -le "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]; then @@ -150,7 +176,7 @@ _zsh_autosuggest_partial_accept() { return $retval } -for action in clear modify fetch suggest accept partial_accept execute; do +for action in clear modify fetch suggest accept partial_accept execute enable disable toggle; do eval "_zsh_autosuggest_widget_$action() { local -i retval @@ -172,3 +198,6 @@ zle -N autosuggest-suggest _zsh_autosuggest_widget_suggest zle -N autosuggest-accept _zsh_autosuggest_widget_accept zle -N autosuggest-clear _zsh_autosuggest_widget_clear zle -N autosuggest-execute _zsh_autosuggest_widget_execute +zle -N autosuggest-enable _zsh_autosuggest_widget_enable +zle -N autosuggest-disable _zsh_autosuggest_widget_disable +zle -N autosuggest-toggle _zsh_autosuggest_widget_toggle diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 2b41154..96b69a9 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -281,6 +281,27 @@ _zsh_autosuggest_highlight_apply() { # Autosuggest Widget Implementations # #--------------------------------------------------------------------# +# Disable suggestions +_zsh_autosuggest_disable() { + typeset -g _ZSH_AUTOSUGGEST_DISABLED + _zsh_autosuggest_clear +} + +# Enable suggestions +_zsh_autosuggest_enable() { + unset _ZSH_AUTOSUGGEST_DISABLED + _zsh_autosuggest_fetch +} + +# Toggle suggestions (enable/disable) +_zsh_autosuggest_toggle() { + if [ -n "${_ZSH_AUTOSUGGEST_DISABLED+x}" ]; then + _zsh_autosuggest_enable + else + _zsh_autosuggest_disable + fi +} + # Clear the suggestion _zsh_autosuggest_clear() { # Remove the suggestion @@ -329,6 +350,11 @@ _zsh_autosuggest_modify() { return $retval fi + # Bail out if suggestions are disabled + if [ -n "${_ZSH_AUTOSUGGEST_DISABLED+x}" ]; then + return $? + fi + # Get a new suggestion if the buffer is not empty after modification if [ $#BUFFER -gt 0 ]; then if [ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" -o $#BUFFER -le "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]; then @@ -428,7 +454,7 @@ _zsh_autosuggest_partial_accept() { return $retval } -for action in clear modify fetch suggest accept partial_accept execute; do +for action in clear modify fetch suggest accept partial_accept execute enable disable toggle; do eval "_zsh_autosuggest_widget_$action() { local -i retval @@ -450,6 +476,9 @@ zle -N autosuggest-suggest _zsh_autosuggest_widget_suggest zle -N autosuggest-accept _zsh_autosuggest_widget_accept zle -N autosuggest-clear _zsh_autosuggest_widget_clear zle -N autosuggest-execute _zsh_autosuggest_widget_execute +zle -N autosuggest-enable _zsh_autosuggest_widget_enable +zle -N autosuggest-disable _zsh_autosuggest_widget_disable +zle -N autosuggest-toggle _zsh_autosuggest_widget_toggle #--------------------------------------------------------------------# # Default Suggestion Strategy # From a2f0ffb12270505fe60a0890a850dd51d17ee635 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sat, 4 Mar 2017 17:04:04 -0500 Subject: [PATCH 3/3] Enabling suggestions should not fetch a suggestion if buffer is empty --- .../bracketed_paste_magic_spec.rb | 36 +++++++++++++++++++ spec/terminal_session.rb | 7 ++++ spec/widgets/enable_spec.rb | 31 +++++++++++++--- src/widgets.zsh | 5 ++- zsh-autosuggestions.zsh | 5 ++- 5 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 spec/integrations/bracketed_paste_magic_spec.rb diff --git a/spec/integrations/bracketed_paste_magic_spec.rb b/spec/integrations/bracketed_paste_magic_spec.rb new file mode 100644 index 0000000..f01b0e0 --- /dev/null +++ b/spec/integrations/bracketed_paste_magic_spec.rb @@ -0,0 +1,36 @@ +describe 'pasting using bracketed-paste-magic' do + let(:before_sourcing) do + -> do + session. + run_command('autoload -Uz bracketed-paste-magic'). + run_command('zle -N bracketed-paste bracketed-paste-magic') + end + end + + context 'with suggestions disabled while pasting' do + before do + session. + run_command('bpm_init() { zle autosuggest-disable }'). + run_command('bpm_finish() { zle autosuggest-enable }'). + run_command('zstyle :bracketed-paste-magic paste-init bpm_init'). + run_command('zstyle :bracketed-paste-magic paste-finish bpm_finish') + end + + it 'does not show an incorrect suggestion' do + with_history('echo hello') do + session.paste_string("echo #{'a' * 60}") + sleep 1 + expect(session.content).to eq("echo #{'a' * 60}") + end + end + + it 'shows a suggestion after a non-modifying keystroke' do + with_history('echo hello') do + session.paste_string('echo') + sleep 1 + session.send_keys('left') + wait_for { session.content }.to eq('echo hello') + end + end + end +end diff --git a/spec/terminal_session.rb b/spec/terminal_session.rb index 3f8ca69..82705d3 100644 --- a/spec/terminal_session.rb +++ b/spec/terminal_session.rb @@ -41,6 +41,13 @@ class TerminalSession self end + def paste_string(str) + tmux_command("set-buffer -- '#{str}'") + tmux_command("paste-buffer -dpr -t 0") + + self + end + def content(esc_seqs: false) cmd = 'capture-pane -p -t 0' cmd += ' -e' if esc_seqs diff --git a/spec/widgets/enable_spec.rb b/spec/widgets/enable_spec.rb index 0322406..3ad35a8 100644 --- a/spec/widgets/enable_spec.rb +++ b/spec/widgets/enable_spec.rb @@ -6,16 +6,37 @@ describe 'the `autosuggest-enable` widget' do end it 'enables suggestions and fetches a suggestion' do - with_history('echo world', 'echo hello') do - session.send_string('echo') + with_history('echo hello') do + session.send_string('e') sleep 1 - expect(session.content).to eq('echo') + expect(session.content).to eq('e') session.send_keys('C-b') + session.send_string('c') wait_for { session.content }.to eq('echo hello') + end + end - session.send_string(' w') - wait_for { session.content }.to eq('echo world') + context 'invoked on an empty buffer' do + it 'does not fetch a suggestion' do + with_history('echo hello') do + session.send_keys('C-b') + sleep 1 + expect(session.content).to eq('') + end + end + end + + context 'invoked on a non-empty buffer' do + it 'fetches a suggestion' do + with_history('echo hello') do + session.send_string('e') + sleep 1 + expect(session.content).to eq('e') + + session.send_keys('C-b') + wait_for { session.content }.to eq('echo hello') + end end end end diff --git a/src/widgets.zsh b/src/widgets.zsh index 2bcedc4..aa3f248 100644 --- a/src/widgets.zsh +++ b/src/widgets.zsh @@ -12,7 +12,10 @@ _zsh_autosuggest_disable() { # Enable suggestions _zsh_autosuggest_enable() { unset _ZSH_AUTOSUGGEST_DISABLED - _zsh_autosuggest_fetch + + if [ $#BUFFER -gt 0 ]; then + _zsh_autosuggest_fetch + fi } # Toggle suggestions (enable/disable) diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 96b69a9..55d23b7 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -290,7 +290,10 @@ _zsh_autosuggest_disable() { # Enable suggestions _zsh_autosuggest_enable() { unset _ZSH_AUTOSUGGEST_DISABLED - _zsh_autosuggest_fetch + + if [ $#BUFFER -gt 0 ]; then + _zsh_autosuggest_fetch + fi } # Toggle suggestions (enable/disable)