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/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/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/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/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..3ad35a8
--- /dev/null
+++ b/spec/widgets/enable_spec.rb
@@ -0,0 +1,42 @@
+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 hello') do
+ session.send_string('e')
+ sleep 1
+ 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
+
+ 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/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 a0a59d5..aa3f248 100644
--- a/src/widgets.zsh
+++ b/src/widgets.zsh
@@ -3,6 +3,30 @@
# 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
+
+ if [ $#BUFFER -gt 0 ]; then
+ _zsh_autosuggest_fetch
+ fi
+}
+
+# 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
@@ -15,6 +39,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 +53,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}
@@ -43,6 +75,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
@@ -142,7 +179,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
@@ -164,3 +201,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 05e1bb4..55d23b7 100644
--- a/zsh-autosuggestions.zsh
+++ b/zsh-autosuggestions.zsh
@@ -281,6 +281,30 @@ _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
+
+ if [ $#BUFFER -gt 0 ]; then
+ _zsh_autosuggest_fetch
+ fi
+}
+
+# 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
@@ -293,6 +317,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 +331,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}
@@ -321,6 +353,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
@@ -420,7 +457,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
@@ -442,6 +479,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 #