From af671fb406360bafba1d91173e2956c76912dec4 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Thu, 19 Jan 2017 00:55:27 -0700 Subject: [PATCH 01/70] Add ruby settings to editor config --- .editorconfig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.editorconfig b/.editorconfig index 51c4765..b40bc96 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,3 +8,7 @@ indent_size = 4 [*.md] indent_style = space + +[*.rb] +indent_style = space +indent_size = 2 From e6591d5de05dc51848e6680d6a009d23610a888e Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Thu, 19 Jan 2017 22:33:17 -0700 Subject: [PATCH 02/70] Add RSpec for high-level integration testing --- .rspec | 2 ++ .ruby-version | 1 + Gemfile | 4 ++++ Gemfile.lock | 29 +++++++++++++++++++++++++++++ spec/spec_helper.rb | 13 +++++++++++++ 5 files changed, 49 insertions(+) create mode 100644 .rspec create mode 100644 .ruby-version create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 spec/spec_helper.rb diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..83e16f8 --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--color +--require spec_helper diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..2bf1c1c --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.3.1 diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..bb4eb7f --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +gem 'rspec' +gem 'rspec-wait' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..5ad7873 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,29 @@ +GEM + remote: https://rubygems.org/ + specs: + diff-lcs (1.3) + rspec (3.5.0) + rspec-core (~> 3.5.0) + rspec-expectations (~> 3.5.0) + rspec-mocks (~> 3.5.0) + rspec-core (3.5.4) + rspec-support (~> 3.5.0) + rspec-expectations (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-mocks (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-support (3.5.0) + rspec-wait (0.0.9) + rspec (>= 3, < 4) + +PLATFORMS + ruby + +DEPENDENCIES + rspec + rspec-wait + +BUNDLED WITH + 1.12.5 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..38e3f56 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,13 @@ +require 'rspec/wait' + +RSpec.configure do |config| + config.expect_with :rspec do |expectations| + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + config.mock_with :rspec do |mocks| + mocks.verify_partial_doubles = true + end + + config.wait_timeout = 2 +end From 07a6768fcb3c1d1696f48238fc033d38ebf6f377 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Thu, 19 Jan 2017 22:33:31 -0700 Subject: [PATCH 03/70] Add TerminalSession helper for managing a tmux session --- spec/spec_helper.rb | 1 + spec/terminal_session.rb | 55 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 spec/terminal_session.rb diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 38e3f56..b3882d7 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,4 +1,5 @@ require 'rspec/wait' +require 'terminal_session' RSpec.configure do |config| config.expect_with :rspec do |expectations| diff --git a/spec/terminal_session.rb b/spec/terminal_session.rb new file mode 100644 index 0000000..63eaf0d --- /dev/null +++ b/spec/terminal_session.rb @@ -0,0 +1,55 @@ +require 'securerandom' + +class TerminalSession + def initialize(width: 80, height: 24, prompt: '', term: 'xterm-256color') + tmux_command("new-session -d -x #{width} -y #{height} 'PS1=#{prompt} TERM=#{term} zsh -f'") + end + + def run_command(command) + send_string(command) + send_keys('enter') + end + + def send_string(str) + tmux_command("send-keys -t 0 -l '#{str.gsub("'", "\\'")}'") + end + + def send_keys(*keys) + tmux_command("send-keys -t 0 #{keys.join(' ')}") + end + + def content(esc_seqs: false) + cmd = 'capture-pane -p -t 0' + cmd += ' -e' if esc_seqs + tmux_command(cmd).strip + end + + def clear + send_keys('C-l') + end + + def destroy + tmux_command('kill-session') + end + + def cursor + tmux_command("display-message -t 0 -p '\#{cursor_x},\#{cursor_y}'"). + strip. + split(','). + map(&:to_i) + end + + private + + def socket_name + @socket_name ||= SecureRandom.hex(6) + end + + def tmux_command(cmd) + out = `tmux -u -L #{socket_name} #{cmd}` + + raise('tmux error') unless $?.success? + + out + end +end From c22ab0e399c66305b880a1a9e0b4c4b35d72eaac Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Thu, 19 Jan 2017 00:59:06 -0700 Subject: [PATCH 04/70] Implement suggestion integration tests in RSpec + tmux --- spec/strategies/default_spec.rb | 132 ++++++++++++++++++++++++ spec/strategies/match_prev_cmd_spec.rb | 60 +++++++++++ test/strategies/default_test.zsh | 56 ---------- test/strategies/match_prev_cmd_test.zsh | 74 ------------- test/strategies_test.zsh | 102 ------------------ 5 files changed, 192 insertions(+), 232 deletions(-) create mode 100644 spec/strategies/default_spec.rb create mode 100644 spec/strategies/match_prev_cmd_spec.rb delete mode 100755 test/strategies/default_test.zsh delete mode 100755 test/strategies/match_prev_cmd_test.zsh delete mode 100644 test/strategies_test.zsh diff --git a/spec/strategies/default_spec.rb b/spec/strategies/default_spec.rb new file mode 100644 index 0000000..36a1221 --- /dev/null +++ b/spec/strategies/default_spec.rb @@ -0,0 +1,132 @@ +describe 'default strategy' do + let(:session) { TerminalSession.new } + + before do + session.run_command('source zsh-autosuggestions.zsh') + session.run_command('fc -p') + session.clear + end + + after do + session.destroy + end + + context 'with some simple history entries' do + before do + session.run_command('ls foo') + session.run_command('ls bar') + + session.clear + end + + it 'suggests nothing if there is no match' do + session.send_string('ls q') + wait_for { session.content }.to eq('ls q') + end + + it 'suggests the most recent matching history item' do + session.send_string('ls') + wait_for { session.content }.to eq('ls bar') + end + end + + xcontext 'with a multiline hist entry' do + before do + session.send_string('echo "') + session.send_keys('enter') + session.send_string('"') + session.send_keys('enter') + + session.clear + end + + it do + session.send_keys('e') + wait_for { session.content }.to eq "echo \"\n\"" + end + end + + context 'with a hist entry with a backslash' do + before do + session.run_command('echo "hello\nworld"') + session.clear + end + + it do + session.send_string('echo "hello\\') + wait_for { session.content }.to eq('echo "hello\nworld"') + end + end + + context 'with a hist entry with double backslashes' do + before do + session.run_command('echo "\\\\"') + session.clear + end + + it do + session.send_string('echo "\\\\') + wait_for { session.content }.to eq('echo "\\\\"') + end + end + + context 'with a hist entry with a tilde' do + before do + session.run_command('ls ~/foo') + session.clear + end + + it do + session.send_string('ls ~') + wait_for { session.content }.to eq('ls ~/foo') + end + + context 'with extended_glob set' do + before do + session.run_command('setopt local_options extended_glob') + session.clear + end + + it do + session.send_string('ls ~') + wait_for { session.content }.to eq('ls ~/foo') + end + end + end + + context 'with a hist entry with parentheses' do + before do + session.run_command('echo "$(ls foo)"') + session.clear + end + + it do + session.send_string('echo "$(') + wait_for { session.content }.to eq('echo "$(ls foo)"') + end + end + + context 'with a hist entry with square brackets' do + before do + session.run_command('echo "$history[123]"') + session.clear + end + + it do + session.send_string('echo "$history[') + wait_for { session.content }.to eq('echo "$history[123]"') + end + end + + context 'with a hist entry with pound sign' do + before do + session.run_command('echo "#yolo"') + session.clear + end + + it do + session.send_string('echo "#') + wait_for { session.content }.to eq('echo "#yolo"') + end + end +end diff --git a/spec/strategies/match_prev_cmd_spec.rb b/spec/strategies/match_prev_cmd_spec.rb new file mode 100644 index 0000000..e038fc7 --- /dev/null +++ b/spec/strategies/match_prev_cmd_spec.rb @@ -0,0 +1,60 @@ +describe 'match_prev_cmd strategy' do + let(:session) { TerminalSession.new } + + before do + session.run_command('source zsh-autosuggestions.zsh') + session.run_command('ZSH_AUTOSUGGEST_STRATEGY=match_prev_cmd') + session.run_command('fc -p') + session.clear + end + + after do + session.destroy + end + + context 'with some history entries' do + before do + session.run_command('echo what') + session.run_command('ls foo') + session.run_command('echo what') + session.run_command('ls bar') + session.run_command('ls baz') + + session.clear + end + + it 'suggests nothing if prefix does not match' do + session.send_string('ls q') + wait_for { session.content }.to eq('ls q') + end + + it 'suggests the most recent matching history item' do + session.send_string('ls') + wait_for { session.content }.to eq('ls baz') + end + + it 'suggests the most recent after the previous command' do + session.run_command('echo what') + session.clear + + session.send_string('ls') + wait_for { session.content }.to eq('ls bar') + end + end + + context 'with a multiline hist entry' do + before do + session.send_string('echo "') + session.send_keys('enter') + session.send_string('"') + session.send_keys('enter') + + session.clear + end + + it do + session.send_keys('e') + wait_for { session.content }.to eq "echo \"\n\"" + end + end +end diff --git a/test/strategies/default_test.zsh b/test/strategies/default_test.zsh deleted file mode 100755 index f5200e6..0000000 --- a/test/strategies/default_test.zsh +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env zsh - -source "${0:a:h}/../test_helper.zsh" - -oneTimeSetUp() { - source_autosuggestions -} - -testNoMatch() { - set_history <<-'EOF' - ls foo - ls bar - EOF - - assertSuggestion \ - 'foo' \ - '' - - assertSuggestion \ - 'ls q' \ - '' -} - -testBasicMatches() { - set_history <<-'EOF' - ls foo - ls bar - EOF - - assertSuggestion \ - 'ls f' \ - 'ls foo' - - assertSuggestion \ - 'ls b' \ - 'ls bar' -} - -testMostRecentMatch() { - set_history <<-'EOF' - ls foo - cd bar - ls baz - cd quux - EOF - - assertSuggestion \ - 'ls' \ - 'ls baz' - - assertSuggestion \ - 'cd' \ - 'cd quux' -} - -run_tests "$0" diff --git a/test/strategies/match_prev_cmd_test.zsh b/test/strategies/match_prev_cmd_test.zsh deleted file mode 100755 index bf3fc64..0000000 --- a/test/strategies/match_prev_cmd_test.zsh +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env zsh - -source "${0:a:h}/../test_helper.zsh" - -oneTimeSetUp() { - source_autosuggestions - - ZSH_AUTOSUGGEST_STRATEGY=match_prev_cmd -} - -testNoMatch() { - set_history <<-'EOF' - ls foo - ls bar - EOF - - assertSuggestion \ - 'foo' \ - '' - - assertSuggestion \ - 'ls q' \ - '' -} - -testBasicMatches() { - set_history <<-'EOF' - ls foo - ls bar - EOF - - assertSuggestion \ - 'ls f' \ - 'ls foo' - - assertSuggestion \ - 'ls b' \ - 'ls bar' -} - -testMostRecentMatch() { - set_history <<-'EOF' - ls foo - cd bar - ls baz - cd quux - EOF - - assertSuggestion \ - 'ls' \ - 'ls baz' - - assertSuggestion \ - 'cd' \ - 'cd quux' -} - -testMatchMostRecentAfterPreviousCmd() { - set_history <<-'EOF' - echo what - ls foo - ls bar - echo what - ls baz - ls quux - echo what - EOF - - assertSuggestion \ - 'ls' \ - 'ls baz' -} - -run_tests "$0" diff --git a/test/strategies_test.zsh b/test/strategies_test.zsh deleted file mode 100644 index 0a937f4..0000000 --- a/test/strategies_test.zsh +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env zsh - -source "${0:a:h}/test_helper.zsh" - -oneTimeSetUp() { - source_autosuggestions -} - -assertBackslashSuggestion() { - set_history <<-'EOF' - echo "hello\nworld" - EOF - - assertSuggestion \ - 'echo "hello\' \ - 'echo "hello\nworld"' -} - -assertDoubleBackslashSuggestion() { - set_history <<-'EOF' - echo "\\" - EOF - - assertSuggestion \ - 'echo "\\' \ - 'echo "\\"' -} - -assertTildeSuggestion() { - set_history <<-'EOF' - cd ~/something - EOF - - assertSuggestion \ - 'cd' \ - 'cd ~/something' - - assertSuggestion \ - 'cd ~' \ - 'cd ~/something' - - assertSuggestion \ - 'cd ~/s' \ - 'cd ~/something' -} - -assertTildeSuggestionWithExtendedGlob() { - setopt local_options extended_glob - - assertTildeSuggestion -} - -assertParenthesesSuggestion() { - set_history <<-'EOF' - echo "$(ls foo)" - EOF - - assertSuggestion \ - 'echo "$(' \ - 'echo "$(ls foo)"' -} - -assertSquareBracketsSuggestion() { - set_history <<-'EOF' - echo "$history[123]" - EOF - - assertSuggestion \ - 'echo "$history[' \ - 'echo "$history[123]"' -} - -assertHashSuggestion() { - set_history <<-'EOF' - echo "#yolo" - EOF - - assertSuggestion \ - 'echo "#' \ - 'echo "#yolo"' -} - -testSpecialCharsForAllStrategies() { - local strategies - strategies=( - "default" - "match_prev_cmd" - ) - - for s in $strategies; do - ZSH_AUTOSUGGEST_STRATEGY="$s" - - assertBackslashSuggestion - assertDoubleBackslashSuggestion - assertTildeSuggestion - assertTildeSuggestionWithExtendedGlob - assertParenthesesSuggestion - assertSquareBracketsSuggestion - done -} - -run_tests "$0" From 48501198874fa93f2ef8e05c9238dfddc2cc2636 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Thu, 19 Jan 2017 00:59:26 -0700 Subject: [PATCH 05/70] Add separate test task for RSpec --- Makefile | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index fde3691..27d02db 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ PLUGIN_TARGET := zsh-autosuggestions.zsh SHUNIT2 := $(VENDOR_DIR)/shunit2/2.1.6 STUB_SH := $(VENDOR_DIR)/stub.sh/stub.sh -TEST_PREREQS := \ +UNIT_TEST_PREREQS := \ $(SHUNIT2) \ $(STUB_SH) @@ -43,5 +43,10 @@ clean: rm $(PLUGIN_TARGET) .PHONY: test -test: all $(TEST_PREREQS) - script/test_runner.zsh $(TESTS) +test: rspec unit_test + +unit_test: all $(UNIT_TEST_PREREQS) + script/test_runner.zsh $(UNIT_TESTS) + +rspec: all + bundle exec rspec $(RSPEC_TESTS) From debbffc79a3d1a068c2be7e151abf4a7a1afab64 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Thu, 19 Jan 2017 22:29:26 -0700 Subject: [PATCH 06/70] Add rspec test around accepting suggestions --- spec/widgets/accept_spec.rb | 162 ++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 spec/widgets/accept_spec.rb diff --git a/spec/widgets/accept_spec.rb b/spec/widgets/accept_spec.rb new file mode 100644 index 0000000..42e1e99 --- /dev/null +++ b/spec/widgets/accept_spec.rb @@ -0,0 +1,162 @@ +describe 'accept widget' do + let(:session) { TerminalSession.new } + + before do + session.run_command('source zsh-autosuggestions.zsh') + session.run_command(select_keymap) + session.run_command('fc -p') + session.run_command('echo hello world') + + session.clear + + session.send_string('echo') + wait_for { session.content }.to start_with('echo') + end + + after do + session.destroy + end + + describe 'emacs keymap' do + let(:select_keymap) { 'bindkey -e' } + + context 'forward-char' do + subject { session.send_keys('right') } + + context 'when the cursor is at the end of the buffer' do + it 'accepts the suggestion' do + expect { subject }.to change { session.content(esc_seqs: true) }.to('echo hello world') + end + + it 'moves the cursor to the end of the buffer' do + expect { subject }.to change { session.cursor }.from([4, 0]).to([16, 0]) + end + end + + context 'when the cursor is not at the end of the buffer' do + before { 2.times { session.send_keys('left') } } + + it 'does not accept the suggestion' do + expect { subject }.not_to change { session.content(esc_seqs: true) } + end + + it 'moves the cursor forward one character' do + expect { subject }.to change { session.cursor }.from([2, 0]).to([3, 0]) + end + end + end + + context 'end-of-line' do + subject { session.send_keys('C-e') } + + context 'when the cursor is at the end of the buffer' do + it 'accepts the suggestion' do + expect { subject }.to change { session.content(esc_seqs: true) }.to('echo hello world') + end + + it 'moves the cursor to the end of the buffer' do + expect { subject }.to change { session.cursor }.from([4, 0]).to([16, 0]) + end + end + + context 'when the cursor is not at the end of the buffer' do + before { 2.times { session.send_keys('left') } } + + it 'does not accept the suggestion' do + expect { subject }.not_to change { session.content(esc_seqs: true) } + end + + it 'moves the cursor to the end of the line' do + expect { subject }.to change { session.cursor }.from([2, 0]).to([4, 0]) + end + end + end + end + + describe 'vi keymap' do + let(:select_keymap) { 'bindkey -v' } + + before { session.send_keys('escape') } + + context 'vi-forward-char' do + subject { session.send_keys('l') } + + context 'when the cursor is at the end of the buffer' do + it 'accepts the suggestion' do + expect { subject }.to change { session.content(esc_seqs: true) }.to('echo hello world') + end + + it 'moves the cursor to the end of the buffer' do + wait_for { session.cursor }.to eq([3, 0]) + expect { subject }.to change { session.cursor }.from([3, 0]).to([15, 0]) + end + end + + context 'when the cursor is not at the end of the buffer' do + before { 2.times { session.send_keys('h') } } + + it 'does not accept the suggestion' do + expect { subject }.not_to change { session.content(esc_seqs: true) } + end + + it 'moves the cursor forward one character' do + expect { subject }.to change { session.cursor }.from([1, 0]).to([2, 0]) + end + end + end + + context 'vi-end-of-line' do + subject { session.send_keys('$') } + + context 'when the cursor is at the end of the buffer' do + it 'accepts the suggestion' do + expect { subject }.to change { session.content(esc_seqs: true) }.to('echo hello world') + end + + it 'moves the cursor to the end of the buffer' do + wait_for { session.cursor }.to eq([3, 0]) + expect { subject }.to change { session.cursor }.from([3, 0]).to([15, 0]) + end + end + + context 'when the cursor is not at the end of the buffer' do + before { 2.times { session.send_keys('h') } } + + it 'does not accept the suggestion' do + expect { subject }.not_to change { session.content(esc_seqs: true) } + end + + it 'moves the cursor to the end of the line' do + expect { subject }.to change { session.cursor }.from([1, 0]).to([3, 0]) + end + end + end + + context 'vi-add-eol' do + subject { session.send_keys('A') } + + context 'when the cursor is at the end of the buffer' do + it 'accepts the suggestion' do + expect { subject }.to change { session.content(esc_seqs: true) }.to('echo hello world') + end + + it 'moves the cursor to the end of the buffer' do + wait_for { session.cursor }.to eq([3, 0]) + expect { subject }.to change { session.cursor }.from([3, 0]).to([16, 0]) + end + end + + context 'when the cursor is not at the end of the buffer' do + before { 2.times { session.send_keys('h') } } + + it 'does not accept the suggestion' do + expect { subject }.not_to change { session.content(esc_seqs: true) } + end + + it 'moves the cursor to the end of the line' do + expect { subject }.to change { session.cursor }.from([1, 0]).to([4, 0]) + end + end + end + end +end From ab8f29522522007fa22266538af7ef19e2331fa6 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 19 Jul 2016 21:04:18 -0600 Subject: [PATCH 07/70] First pass at async functionality --- Makefile | 4 +- src/async.zsh | 53 ++++++++++++++++++ src/config.zsh | 3 ++ src/setup.zsh | 10 ++++ src/start.zsh | 1 - src/suggestion.zsh | 21 -------- src/util.zsh | 11 ++++ src/widgets.zsh | 28 +++++++--- zsh-autosuggestions.zsh | 115 +++++++++++++++++++++++++++++++--------- 9 files changed, 191 insertions(+), 55 deletions(-) create mode 100644 src/async.zsh create mode 100644 src/setup.zsh delete mode 100644 src/suggestion.zsh create mode 100644 src/util.zsh diff --git a/Makefile b/Makefile index 27d02db..2ea2763 100644 --- a/Makefile +++ b/Makefile @@ -2,13 +2,15 @@ SRC_DIR := ./src VENDOR_DIR := ./vendor SRC_FILES := \ + $(SRC_DIR)/setup.zsh \ $(SRC_DIR)/config.zsh \ + $(SRC_DIR)/util.zsh \ $(SRC_DIR)/deprecated.zsh \ $(SRC_DIR)/bind.zsh \ $(SRC_DIR)/highlight.zsh \ $(SRC_DIR)/widgets.zsh \ - $(SRC_DIR)/suggestion.zsh \ $(SRC_DIR)/strategies/*.zsh \ + $(SRC_DIR)/async.zsh \ $(SRC_DIR)/start.zsh HEADER_FILES := \ diff --git a/src/async.zsh b/src/async.zsh new file mode 100644 index 0000000..a87fd05 --- /dev/null +++ b/src/async.zsh @@ -0,0 +1,53 @@ + +#--------------------------------------------------------------------# +# Async # +#--------------------------------------------------------------------# + +_zsh_autosuggest_async_fetch_suggestion() { + local strategy_function="_zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" + local prefix="$(_zsh_autosuggest_escape_command "$1")" + + # Send the suggestion command to the pty to fetch a suggestion + zpty -w -n $ZSH_AUTOSUGGEST_PTY_NAME "$strategy_function '$prefix'"$'\0' +} + +_zsh_autosuggest_async_suggestion_worker() { + local last_pid + + while read -d $'\0' cmd; do + # Kill last bg process + kill -KILL $last_pid &>/dev/null + + # Run suggestion search in the background + print -n -- "$(eval "$cmd")"$'\0' & + + # Save the bg process's id so we can kill later + last_pid=$! + done +} + +_zsh_autosuggest_async_suggestion_ready() { + # while zpty -rt $ZSH_AUTOSUGGEST_PTY_NAME suggestion 2>/dev/null; do + while read -u $_ZSH_AUTOSUGGEST_PTY_FD -d $'\0' suggestion; do + zle _autosuggest-show-suggestion "${suggestion//$'\r'$'\n'/$'\n'}" + done +} + +# Recreate the pty to get a fresh list of history events +_zsh_autosuggest_async_recreate_pty() { + typeset -g _ZSH_AUTOSUGGEST_PTY_FD + + # Kill the old pty + if [ -n "$_ZSH_AUTOSUGGEST_PTY_FD" ]; then + zle -F $_ZSH_AUTOSUGGEST_PTY_FD + zpty -d $ZSH_AUTOSUGGEST_PTY_NAME &>/dev/null + fi + + # Start a new pty + typeset -h REPLY + zpty -b $ZSH_AUTOSUGGEST_PTY_NAME _zsh_autosuggest_async_suggestion_worker + _ZSH_AUTOSUGGEST_PTY_FD=$REPLY + zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_suggestion_ready +} + +add-zsh-hook precmd _zsh_autosuggest_async_recreate_pty diff --git a/src/config.zsh b/src/config.zsh index f519f6f..68d3b32 100644 --- a/src/config.zsh +++ b/src/config.zsh @@ -11,6 +11,9 @@ ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8' # Prefix to use when saving original versions of bound widgets ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig- +# Pty name for calculating autosuggestions asynchronously +ZSH_AUTOSUGGEST_PTY_NAME=zsh_autosuggest_pty + ZSH_AUTOSUGGEST_STRATEGY=default # Widgets that clear the suggestion diff --git a/src/setup.zsh b/src/setup.zsh new file mode 100644 index 0000000..c74489f --- /dev/null +++ b/src/setup.zsh @@ -0,0 +1,10 @@ + +#--------------------------------------------------------------------# +# Setup # +#--------------------------------------------------------------------# + +# Precmd hooks for initializing the library and starting pty's +autoload -Uz add-zsh-hook + +# Asynchronous suggestions are generated in a pty +zmodload zsh/zpty diff --git a/src/start.zsh b/src/start.zsh index 54f5bb8..5314e1f 100644 --- a/src/start.zsh +++ b/src/start.zsh @@ -9,5 +9,4 @@ _zsh_autosuggest_start() { _zsh_autosuggest_bind_widgets } -autoload -Uz add-zsh-hook add-zsh-hook precmd _zsh_autosuggest_start diff --git a/src/suggestion.zsh b/src/suggestion.zsh deleted file mode 100644 index 31a9f76..0000000 --- a/src/suggestion.zsh +++ /dev/null @@ -1,21 +0,0 @@ - -#--------------------------------------------------------------------# -# Suggestion # -#--------------------------------------------------------------------# - -# Delegate to the selected strategy to determine a suggestion -_zsh_autosuggest_suggestion() { - local escaped_prefix="$(_zsh_autosuggest_escape_command "$1")" - local strategy_function="_zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" - - if [ -n "$functions[$strategy_function]" ]; then - echo -E "$($strategy_function "$escaped_prefix")" - fi -} - -_zsh_autosuggest_escape_command() { - setopt localoptions EXTENDED_GLOB - - # Escape special chars in the string (requires EXTENDED_GLOB) - echo -E "${1//(#m)[\\()\[\]|*?~]/\\$MATCH}" -} diff --git a/src/util.zsh b/src/util.zsh new file mode 100644 index 0000000..1f55d36 --- /dev/null +++ b/src/util.zsh @@ -0,0 +1,11 @@ + +#--------------------------------------------------------------------# +# Utility Functions # +#--------------------------------------------------------------------# + +_zsh_autosuggest_escape_command() { + setopt localoptions EXTENDED_GLOB + + # Escape special chars in the string (requires EXTENDED_GLOB) + echo -E "${1//(#m)[\"\'\\()\[\]|*?~]/\\$MATCH}" +} diff --git a/src/widgets.zsh b/src/widgets.zsh index 57f378e..0dfb3f0 100644 --- a/src/widgets.zsh +++ b/src/widgets.zsh @@ -19,7 +19,7 @@ _zsh_autosuggest_modify() { local orig_buffer="$BUFFER" local orig_postdisplay="$POSTDISPLAY" - # Clear suggestion while original widget runs + # Clear suggestion while waiting for next one unset POSTDISPLAY # Original widget may modify the buffer @@ -33,18 +33,12 @@ _zsh_autosuggest_modify() { fi # Get a new suggestion if the buffer is not empty after modification - local suggestion if [ $#BUFFER -gt 0 ]; then if [ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" -o $#BUFFER -lt "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]; then - suggestion="$(_zsh_autosuggest_suggestion "$BUFFER")" + _zsh_autosuggest_async_fetch_suggestion "$BUFFER" fi fi - # Add the suggestion to the POSTDISPLAY - if [ -n "$suggestion" ]; then - POSTDISPLAY="${suggestion#$BUFFER}" - fi - return $retval } @@ -133,3 +127,21 @@ done zle -N autosuggest-accept _zsh_autosuggest_widget_accept zle -N autosuggest-clear _zsh_autosuggest_widget_clear zle -N autosuggest-execute _zsh_autosuggest_widget_execute + +_zsh_autosuggest_show_suggestion() { + local suggestion=$1 + + _zsh_autosuggest_highlight_reset + + if [ -n "$suggestion" ]; then + POSTDISPLAY="${suggestion#$BUFFER}" + else + unset POSTDISPLAY + fi + + _zsh_autosuggest_highlight_apply + + zle -R +} + +zle -N _autosuggest-show-suggestion _zsh_autosuggest_show_suggestion diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 3761efe..34d7771 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -25,6 +25,16 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. +#--------------------------------------------------------------------# +# Setup # +#--------------------------------------------------------------------# + +# Precmd hooks for initializing the library and starting pty's +autoload -Uz add-zsh-hook + +# Asynchronous suggestions are generated in a pty +zmodload zsh/zpty + #--------------------------------------------------------------------# # Global Configuration Variables # #--------------------------------------------------------------------# @@ -37,6 +47,9 @@ ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8' # Prefix to use when saving original versions of bound widgets ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig- +# Pty name for calculating autosuggestions asynchronously +ZSH_AUTOSUGGEST_PTY_NAME=zsh_autosuggest_pty + ZSH_AUTOSUGGEST_STRATEGY=default # Widgets that clear the suggestion @@ -87,6 +100,17 @@ ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( # Max size of buffer to trigger autosuggestion. Leave undefined for no upper bound. ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE= +#--------------------------------------------------------------------# +# Utility Functions # +#--------------------------------------------------------------------# + +_zsh_autosuggest_escape_command() { + setopt localoptions EXTENDED_GLOB + + # Escape special chars in the string (requires EXTENDED_GLOB) + echo -E "${1//(#m)[\"\'\\()\[\]|*?~]/\\$MATCH}" +} + #--------------------------------------------------------------------# # Handle Deprecated Variables/Widgets # #--------------------------------------------------------------------# @@ -260,7 +284,7 @@ _zsh_autosuggest_modify() { local orig_buffer="$BUFFER" local orig_postdisplay="$POSTDISPLAY" - # Clear suggestion while original widget runs + # Clear suggestion while waiting for next one unset POSTDISPLAY # Original widget may modify the buffer @@ -274,18 +298,12 @@ _zsh_autosuggest_modify() { fi # Get a new suggestion if the buffer is not empty after modification - local suggestion if [ $#BUFFER -gt 0 ]; then if [ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" -o $#BUFFER -lt "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]; then - suggestion="$(_zsh_autosuggest_suggestion "$BUFFER")" + _zsh_autosuggest_async_fetch_suggestion "$BUFFER" fi fi - # Add the suggestion to the POSTDISPLAY - if [ -n "$suggestion" ]; then - POSTDISPLAY="${suggestion#$BUFFER}" - fi - return $retval } @@ -375,26 +393,23 @@ zle -N autosuggest-accept _zsh_autosuggest_widget_accept zle -N autosuggest-clear _zsh_autosuggest_widget_clear zle -N autosuggest-execute _zsh_autosuggest_widget_execute -#--------------------------------------------------------------------# -# Suggestion # -#--------------------------------------------------------------------# +_zsh_autosuggest_show_suggestion() { + local suggestion=$1 -# Delegate to the selected strategy to determine a suggestion -_zsh_autosuggest_suggestion() { - local escaped_prefix="$(_zsh_autosuggest_escape_command "$1")" - local strategy_function="_zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" + _zsh_autosuggest_highlight_reset - if [ -n "$functions[$strategy_function]" ]; then - echo -E "$($strategy_function "$escaped_prefix")" + if [ -n "$suggestion" ]; then + POSTDISPLAY="${suggestion#$BUFFER}" + else + unset POSTDISPLAY fi + + _zsh_autosuggest_highlight_apply + + zle -R } -_zsh_autosuggest_escape_command() { - setopt localoptions EXTENDED_GLOB - - # Escape special chars in the string (requires EXTENDED_GLOB) - echo -E "${1//(#m)[\\()\[\]|*?~]/\\$MATCH}" -} +zle -N _autosuggest-show-suggestion _zsh_autosuggest_show_suggestion #--------------------------------------------------------------------# # Default Suggestion Strategy # @@ -459,6 +474,59 @@ _zsh_autosuggest_strategy_match_prev_cmd() { echo -E "$history[$histkey]" } +#--------------------------------------------------------------------# +# Async # +#--------------------------------------------------------------------# + +_zsh_autosuggest_async_fetch_suggestion() { + local strategy_function="_zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" + local prefix="$(_zsh_autosuggest_escape_command "$1")" + + # Send the suggestion command to the pty to fetch a suggestion + zpty -w -n $ZSH_AUTOSUGGEST_PTY_NAME "$strategy_function '$prefix'"$'\0' +} + +_zsh_autosuggest_async_suggestion_worker() { + local last_pid + + while read -d $'\0' cmd; do + # Kill last bg process + kill -KILL $last_pid &>/dev/null + + # Run suggestion search in the background + print -n -- "$(eval "$cmd")"$'\0' & + + # Save the bg process's id so we can kill later + last_pid=$! + done +} + +_zsh_autosuggest_async_suggestion_ready() { + # while zpty -rt $ZSH_AUTOSUGGEST_PTY_NAME suggestion 2>/dev/null; do + while read -u $_ZSH_AUTOSUGGEST_PTY_FD -d $'\0' suggestion; do + zle _autosuggest-show-suggestion "${suggestion//$'\r'$'\n'/$'\n'}" + done +} + +# Recreate the pty to get a fresh list of history events +_zsh_autosuggest_async_recreate_pty() { + typeset -g _ZSH_AUTOSUGGEST_PTY_FD + + # Kill the old pty + if [ -n "$_ZSH_AUTOSUGGEST_PTY_FD" ]; then + zle -F $_ZSH_AUTOSUGGEST_PTY_FD + zpty -d $ZSH_AUTOSUGGEST_PTY_NAME &>/dev/null + fi + + # Start a new pty + typeset -h REPLY + zpty -b $ZSH_AUTOSUGGEST_PTY_NAME _zsh_autosuggest_async_suggestion_worker + _ZSH_AUTOSUGGEST_PTY_FD=$REPLY + zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_suggestion_ready +} + +add-zsh-hook precmd _zsh_autosuggest_async_recreate_pty + #--------------------------------------------------------------------# # Start # #--------------------------------------------------------------------# @@ -469,5 +537,4 @@ _zsh_autosuggest_start() { _zsh_autosuggest_bind_widgets } -autoload -Uz add-zsh-hook add-zsh-hook precmd _zsh_autosuggest_start From e72c2d87e54b819dd085694888f0448d03f488c0 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 24 Jan 2017 19:53:26 -0700 Subject: [PATCH 08/70] add a bunch of comments --- src/async.zsh | 15 ++++++++++++++- zsh-autosuggestions.zsh | 15 ++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/async.zsh b/src/async.zsh index a87fd05..c4632d8 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -11,6 +11,7 @@ _zsh_autosuggest_async_fetch_suggestion() { zpty -w -n $ZSH_AUTOSUGGEST_PTY_NAME "$strategy_function '$prefix'"$'\0' } +# Pty is spawned running this function _zsh_autosuggest_async_suggestion_worker() { local last_pid @@ -26,6 +27,9 @@ _zsh_autosuggest_async_suggestion_worker() { done } +# Called when new data is ready to be read from the pty +# First arg will be fd ready for reading +# Second arg will be passed in case of error _zsh_autosuggest_async_suggestion_ready() { # while zpty -rt $ZSH_AUTOSUGGEST_PTY_NAME suggestion 2>/dev/null; do while read -u $_ZSH_AUTOSUGGEST_PTY_FD -d $'\0' suggestion; do @@ -39,14 +43,23 @@ _zsh_autosuggest_async_recreate_pty() { # Kill the old pty if [ -n "$_ZSH_AUTOSUGGEST_PTY_FD" ]; then + # Remove the input handler zle -F $_ZSH_AUTOSUGGEST_PTY_FD + + # Destroy the pty zpty -d $ZSH_AUTOSUGGEST_PTY_NAME &>/dev/null fi - # Start a new pty + # REPLY stores the fd to read from typeset -h REPLY + + # Start a new pty running the server function zpty -b $ZSH_AUTOSUGGEST_PTY_NAME _zsh_autosuggest_async_suggestion_worker + + # Store the fd so we can destroy this pty later _ZSH_AUTOSUGGEST_PTY_FD=$REPLY + + # Set up input handler from the pty zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_suggestion_ready } diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 34d7771..248687f 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -486,6 +486,7 @@ _zsh_autosuggest_async_fetch_suggestion() { zpty -w -n $ZSH_AUTOSUGGEST_PTY_NAME "$strategy_function '$prefix'"$'\0' } +# Pty is spawned running this function _zsh_autosuggest_async_suggestion_worker() { local last_pid @@ -501,6 +502,9 @@ _zsh_autosuggest_async_suggestion_worker() { done } +# Called when new data is ready to be read from the pty +# First arg will be fd ready for reading +# Second arg will be passed in case of error _zsh_autosuggest_async_suggestion_ready() { # while zpty -rt $ZSH_AUTOSUGGEST_PTY_NAME suggestion 2>/dev/null; do while read -u $_ZSH_AUTOSUGGEST_PTY_FD -d $'\0' suggestion; do @@ -514,14 +518,23 @@ _zsh_autosuggest_async_recreate_pty() { # Kill the old pty if [ -n "$_ZSH_AUTOSUGGEST_PTY_FD" ]; then + # Remove the input handler zle -F $_ZSH_AUTOSUGGEST_PTY_FD + + # Destroy the pty zpty -d $ZSH_AUTOSUGGEST_PTY_NAME &>/dev/null fi - # Start a new pty + # REPLY stores the fd to read from typeset -h REPLY + + # Start a new pty running the server function zpty -b $ZSH_AUTOSUGGEST_PTY_NAME _zsh_autosuggest_async_suggestion_worker + + # Store the fd so we can destroy this pty later _ZSH_AUTOSUGGEST_PTY_FD=$REPLY + + # Set up input handler from the pty zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_suggestion_ready } From 0308ed797e1e100d7e091376efdba16e63f0a1ce Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 24 Jan 2017 19:55:38 -0700 Subject: [PATCH 09/70] Rename worker to server --- src/async.zsh | 4 ++-- zsh-autosuggestions.zsh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/async.zsh b/src/async.zsh index c4632d8..b330ccf 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -12,7 +12,7 @@ _zsh_autosuggest_async_fetch_suggestion() { } # Pty is spawned running this function -_zsh_autosuggest_async_suggestion_worker() { +_zsh_autosuggest_async_suggestion_server() { local last_pid while read -d $'\0' cmd; do @@ -54,7 +54,7 @@ _zsh_autosuggest_async_recreate_pty() { typeset -h REPLY # Start a new pty running the server function - zpty -b $ZSH_AUTOSUGGEST_PTY_NAME _zsh_autosuggest_async_suggestion_worker + zpty -b $ZSH_AUTOSUGGEST_PTY_NAME _zsh_autosuggest_async_suggestion_server # Store the fd so we can destroy this pty later _ZSH_AUTOSUGGEST_PTY_FD=$REPLY diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 248687f..2b0465a 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -487,7 +487,7 @@ _zsh_autosuggest_async_fetch_suggestion() { } # Pty is spawned running this function -_zsh_autosuggest_async_suggestion_worker() { +_zsh_autosuggest_async_suggestion_server() { local last_pid while read -d $'\0' cmd; do @@ -529,7 +529,7 @@ _zsh_autosuggest_async_recreate_pty() { typeset -h REPLY # Start a new pty running the server function - zpty -b $ZSH_AUTOSUGGEST_PTY_NAME _zsh_autosuggest_async_suggestion_worker + zpty -b $ZSH_AUTOSUGGEST_PTY_NAME _zsh_autosuggest_async_suggestion_server # Store the fd so we can destroy this pty later _ZSH_AUTOSUGGEST_PTY_FD=$REPLY From fba20b042e89fac0932e01c08a41602e77158ca2 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 24 Jan 2017 19:56:34 -0700 Subject: [PATCH 10/70] Use %1 instead of tracking pid --- src/async.zsh | 7 +------ zsh-autosuggestions.zsh | 7 +------ 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/async.zsh b/src/async.zsh index b330ccf..91c3587 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -13,17 +13,12 @@ _zsh_autosuggest_async_fetch_suggestion() { # Pty is spawned running this function _zsh_autosuggest_async_suggestion_server() { - local last_pid - while read -d $'\0' cmd; do # Kill last bg process - kill -KILL $last_pid &>/dev/null + kill -KILL %1 &>/dev/null # Run suggestion search in the background print -n -- "$(eval "$cmd")"$'\0' & - - # Save the bg process's id so we can kill later - last_pid=$! done } diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 2b0465a..f99953b 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -488,17 +488,12 @@ _zsh_autosuggest_async_fetch_suggestion() { # Pty is spawned running this function _zsh_autosuggest_async_suggestion_server() { - local last_pid - while read -d $'\0' cmd; do # Kill last bg process - kill -KILL $last_pid &>/dev/null + kill -KILL %1 &>/dev/null # Run suggestion search in the background print -n -- "$(eval "$cmd")"$'\0' & - - # Save the bg process's id so we can kill later - last_pid=$! done } From e33eb570c461e4f0c97d7ce1f2c33a15f2a200b7 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 24 Jan 2017 19:58:06 -0700 Subject: [PATCH 11/70] Send only the prefix to the suggestion server --- src/async.zsh | 11 ++++------- src/strategies/default.zsh | 4 +++- zsh-autosuggestions.zsh | 15 +++++++-------- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/async.zsh b/src/async.zsh index 91c3587..2e23fb6 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -4,21 +4,18 @@ #--------------------------------------------------------------------# _zsh_autosuggest_async_fetch_suggestion() { - local strategy_function="_zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" - local prefix="$(_zsh_autosuggest_escape_command "$1")" - - # Send the suggestion command to the pty to fetch a suggestion - zpty -w -n $ZSH_AUTOSUGGEST_PTY_NAME "$strategy_function '$prefix'"$'\0' + # Send the prefix to the pty to fetch a suggestion + zpty -w -n $ZSH_AUTOSUGGEST_PTY_NAME $1 $'\0' } # Pty is spawned running this function _zsh_autosuggest_async_suggestion_server() { - while read -d $'\0' cmd; do + while read -d $'\0' prefix; do # Kill last bg process kill -KILL %1 &>/dev/null # Run suggestion search in the background - print -n -- "$(eval "$cmd")"$'\0' & + echo -n -E "$(_zsh_autosuggest_strategy_default "$prefix")"$'\0' & done } diff --git a/src/strategies/default.zsh b/src/strategies/default.zsh index 29333f5..a242ee0 100644 --- a/src/strategies/default.zsh +++ b/src/strategies/default.zsh @@ -7,5 +7,7 @@ # _zsh_autosuggest_strategy_default() { - fc -lnrm "$1*" 1 2>/dev/null | head -n 1 + setopt localoptions EXTENDED_GLOB + + fc -lnrm "${1//(#m)[\\()\[\]|*?~]/\\$MATCH}*" 1 2>/dev/null | head -n 1 } diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index f99953b..f85aac6 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -419,7 +419,9 @@ zle -N _autosuggest-show-suggestion _zsh_autosuggest_show_suggestion # _zsh_autosuggest_strategy_default() { - fc -lnrm "$1*" 1 2>/dev/null | head -n 1 + setopt localoptions EXTENDED_GLOB + + fc -lnrm "${1//(#m)[\\()\[\]|*?~]/\\$MATCH}*" 1 2>/dev/null | head -n 1 } #--------------------------------------------------------------------# @@ -479,21 +481,18 @@ _zsh_autosuggest_strategy_match_prev_cmd() { #--------------------------------------------------------------------# _zsh_autosuggest_async_fetch_suggestion() { - local strategy_function="_zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" - local prefix="$(_zsh_autosuggest_escape_command "$1")" - - # Send the suggestion command to the pty to fetch a suggestion - zpty -w -n $ZSH_AUTOSUGGEST_PTY_NAME "$strategy_function '$prefix'"$'\0' + # Send the prefix to the pty to fetch a suggestion + zpty -w -n $ZSH_AUTOSUGGEST_PTY_NAME $1 $'\0' } # Pty is spawned running this function _zsh_autosuggest_async_suggestion_server() { - while read -d $'\0' cmd; do + while read -d $'\0' prefix; do # Kill last bg process kill -KILL %1 &>/dev/null # Run suggestion search in the background - print -n -- "$(eval "$cmd")"$'\0' & + echo -n -E "$(_zsh_autosuggest_strategy_default "$prefix")"$'\0' & done } From 5c891afd48e3c27faaff86f988816b11fe499dde Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 24 Jan 2017 19:58:25 -0700 Subject: [PATCH 12/70] Reset zsh options inside pty (from zsh-async) --- src/async.zsh | 2 ++ zsh-autosuggestions.zsh | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/async.zsh b/src/async.zsh index 2e23fb6..566a81a 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -10,6 +10,8 @@ _zsh_autosuggest_async_fetch_suggestion() { # Pty is spawned running this function _zsh_autosuggest_async_suggestion_server() { + emulate -R zsh + while read -d $'\0' prefix; do # Kill last bg process kill -KILL %1 &>/dev/null diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index f85aac6..d8f1103 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -487,6 +487,8 @@ _zsh_autosuggest_async_fetch_suggestion() { # Pty is spawned running this function _zsh_autosuggest_async_suggestion_server() { + emulate -R zsh + while read -d $'\0' prefix; do # Kill last bg process kill -KILL %1 &>/dev/null From b530b0c99679c00a5234c25f0adfa0d70a522900 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 24 Jan 2017 19:58:52 -0700 Subject: [PATCH 13/70] Use `zpty -r` with pattern matching to fetch suggestion --- src/async.zsh | 8 ++++---- zsh-autosuggestions.zsh | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/async.zsh b/src/async.zsh index 566a81a..1535297 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -25,10 +25,10 @@ _zsh_autosuggest_async_suggestion_server() { # First arg will be fd ready for reading # Second arg will be passed in case of error _zsh_autosuggest_async_suggestion_ready() { - # while zpty -rt $ZSH_AUTOSUGGEST_PTY_NAME suggestion 2>/dev/null; do - while read -u $_ZSH_AUTOSUGGEST_PTY_FD -d $'\0' suggestion; do - zle _autosuggest-show-suggestion "${suggestion//$'\r'$'\n'/$'\n'}" - done + local suggestion + + zpty -rt $ZSH_AUTOSUGGEST_PTY_NAME suggestion '*'$'\0' 2>/dev/null + zle _autosuggest-show-suggestion "${suggestion%$'\0'}" } # Recreate the pty to get a fresh list of history events diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index d8f1103..34c1f90 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -502,10 +502,10 @@ _zsh_autosuggest_async_suggestion_server() { # First arg will be fd ready for reading # Second arg will be passed in case of error _zsh_autosuggest_async_suggestion_ready() { - # while zpty -rt $ZSH_AUTOSUGGEST_PTY_NAME suggestion 2>/dev/null; do - while read -u $_ZSH_AUTOSUGGEST_PTY_FD -d $'\0' suggestion; do - zle _autosuggest-show-suggestion "${suggestion//$'\r'$'\n'/$'\n'}" - done + local suggestion + + zpty -rt $ZSH_AUTOSUGGEST_PTY_NAME suggestion '*'$'\0' 2>/dev/null + zle _autosuggest-show-suggestion "${suggestion%$'\0'}" } # Recreate the pty to get a fresh list of history events From 0337005eb05de7f9f99b1fbb42b8da72eddbf5ff Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 24 Jan 2017 21:58:17 -0700 Subject: [PATCH 14/70] Disable word splitting while reading to preserve whitespace --- src/async.zsh | 4 ++-- zsh-autosuggestions.zsh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/async.zsh b/src/async.zsh index 1535297..0e29a3f 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -5,14 +5,14 @@ _zsh_autosuggest_async_fetch_suggestion() { # Send the prefix to the pty to fetch a suggestion - zpty -w -n $ZSH_AUTOSUGGEST_PTY_NAME $1 $'\0' + zpty -w -n $ZSH_AUTOSUGGEST_PTY_NAME "${1}"$'\0' } # Pty is spawned running this function _zsh_autosuggest_async_suggestion_server() { emulate -R zsh - while read -d $'\0' prefix; do + while IFS='' read -r -d $'\0' prefix; do # Kill last bg process kill -KILL %1 &>/dev/null diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 34c1f90..ff3f0ac 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -482,14 +482,14 @@ _zsh_autosuggest_strategy_match_prev_cmd() { _zsh_autosuggest_async_fetch_suggestion() { # Send the prefix to the pty to fetch a suggestion - zpty -w -n $ZSH_AUTOSUGGEST_PTY_NAME $1 $'\0' + zpty -w -n $ZSH_AUTOSUGGEST_PTY_NAME "${1}"$'\0' } # Pty is spawned running this function _zsh_autosuggest_async_suggestion_server() { emulate -R zsh - while read -d $'\0' prefix; do + while IFS='' read -r -d $'\0' prefix; do # Kill last bg process kill -KILL %1 &>/dev/null From e5a5b0c1e0cbd2a8eb3f30543e8891b8ca61d392 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 24 Jan 2017 22:27:09 -0700 Subject: [PATCH 15/70] Output only newlines in the pty --- src/async.zsh | 3 +++ zsh-autosuggestions.zsh | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/async.zsh b/src/async.zsh index 0e29a3f..18dbb9d 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -12,6 +12,9 @@ _zsh_autosuggest_async_fetch_suggestion() { _zsh_autosuggest_async_suggestion_server() { emulate -R zsh + # Output only newlines (not carriage return + newline) + stty -onlcr + while IFS='' read -r -d $'\0' prefix; do # Kill last bg process kill -KILL %1 &>/dev/null diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index ff3f0ac..449e08d 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -489,6 +489,9 @@ _zsh_autosuggest_async_fetch_suggestion() { _zsh_autosuggest_async_suggestion_server() { emulate -R zsh + # Output only newlines (not carriage return + newline) + stty -onlcr + while IFS='' read -r -d $'\0' prefix; do # Kill last bg process kill -KILL %1 &>/dev/null From ab2742537f248f6c807535db7fa121157c593990 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 24 Jan 2017 22:27:47 -0700 Subject: [PATCH 16/70] Quote the suggestion to support sh_split_word option --- src/widgets.zsh | 2 +- zsh-autosuggestions.zsh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widgets.zsh b/src/widgets.zsh index 0dfb3f0..c6162b3 100644 --- a/src/widgets.zsh +++ b/src/widgets.zsh @@ -129,7 +129,7 @@ zle -N autosuggest-clear _zsh_autosuggest_widget_clear zle -N autosuggest-execute _zsh_autosuggest_widget_execute _zsh_autosuggest_show_suggestion() { - local suggestion=$1 + local suggestion="$1" _zsh_autosuggest_highlight_reset diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 449e08d..246e96d 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -394,7 +394,7 @@ zle -N autosuggest-clear _zsh_autosuggest_widget_clear zle -N autosuggest-execute _zsh_autosuggest_widget_execute _zsh_autosuggest_show_suggestion() { - local suggestion=$1 + local suggestion="$1" _zsh_autosuggest_highlight_reset From b3208b08af4e40fbc6a040b0ad9455f33c236f3b Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 24 Jan 2017 22:48:30 -0700 Subject: [PATCH 17/70] Pass the chosen strategy into the suggestion server pty --- src/async.zsh | 6 ++++-- zsh-autosuggestions.zsh | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/async.zsh b/src/async.zsh index 18dbb9d..1d2ae87 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -15,12 +15,14 @@ _zsh_autosuggest_async_suggestion_server() { # Output only newlines (not carriage return + newline) stty -onlcr + local strategy=$1 + while IFS='' read -r -d $'\0' prefix; do # Kill last bg process kill -KILL %1 &>/dev/null # Run suggestion search in the background - echo -n -E "$(_zsh_autosuggest_strategy_default "$prefix")"$'\0' & + echo -n -E "$($strategy "$prefix")"$'\0' & done } @@ -51,7 +53,7 @@ _zsh_autosuggest_async_recreate_pty() { typeset -h REPLY # Start a new pty running the server function - zpty -b $ZSH_AUTOSUGGEST_PTY_NAME _zsh_autosuggest_async_suggestion_server + zpty -b $ZSH_AUTOSUGGEST_PTY_NAME "_zsh_autosuggest_async_suggestion_server _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" # Store the fd so we can destroy this pty later _ZSH_AUTOSUGGEST_PTY_FD=$REPLY diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 246e96d..2dc5141 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -492,12 +492,14 @@ _zsh_autosuggest_async_suggestion_server() { # Output only newlines (not carriage return + newline) stty -onlcr + local strategy=$1 + while IFS='' read -r -d $'\0' prefix; do # Kill last bg process kill -KILL %1 &>/dev/null # Run suggestion search in the background - echo -n -E "$(_zsh_autosuggest_strategy_default "$prefix")"$'\0' & + echo -n -E "$($strategy "$prefix")"$'\0' & done } @@ -528,7 +530,7 @@ _zsh_autosuggest_async_recreate_pty() { typeset -h REPLY # Start a new pty running the server function - zpty -b $ZSH_AUTOSUGGEST_PTY_NAME _zsh_autosuggest_async_suggestion_server + zpty -b $ZSH_AUTOSUGGEST_PTY_NAME "_zsh_autosuggest_async_suggestion_server _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" # Store the fd so we can destroy this pty later _ZSH_AUTOSUGGEST_PTY_FD=$REPLY From 8e06a54b1c6b53442d6fa47e1614c4b144fbd734 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 24 Jan 2017 22:49:21 -0700 Subject: [PATCH 18/70] Add test for string with "\n" in it --- spec/strategies/default_spec.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spec/strategies/default_spec.rb b/spec/strategies/default_spec.rb index 36a1221..1b6a4a2 100644 --- a/spec/strategies/default_spec.rb +++ b/spec/strategies/default_spec.rb @@ -46,6 +46,20 @@ describe 'default strategy' do end end + context 'with a newline hist entry' do + before do + session.send_string('echo "\n"') + session.send_keys('enter') + + session.clear + end + + it do + session.send_keys('e') + wait_for { session.content }.to eq 'echo "\n"' + end + end + context 'with a hist entry with a backslash' do before do session.run_command('echo "hello\nworld"') From 0305908adf1f9ee703d545cc18ef77c34092d410 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 24 Jan 2017 23:04:07 -0700 Subject: [PATCH 19/70] Revert `fc` usage in calculating suggestion As far as I know, `fc` makes it impossible to tell whether history items used an actual newline character or the string "\n". Pulling from the `$history` array gives a more accurate representation of the actual command that was run. --- spec/strategies/default_spec.rb | 2 +- src/strategies/default.zsh | 9 ++++++++- zsh-autosuggestions.zsh | 9 ++++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/spec/strategies/default_spec.rb b/spec/strategies/default_spec.rb index 1b6a4a2..3987c7e 100644 --- a/spec/strategies/default_spec.rb +++ b/spec/strategies/default_spec.rb @@ -30,7 +30,7 @@ describe 'default strategy' do end end - xcontext 'with a multiline hist entry' do + context 'with a multiline hist entry' do before do session.send_string('echo "') session.send_keys('enter') diff --git a/src/strategies/default.zsh b/src/strategies/default.zsh index a242ee0..4246b78 100644 --- a/src/strategies/default.zsh +++ b/src/strategies/default.zsh @@ -9,5 +9,12 @@ _zsh_autosuggest_strategy_default() { setopt localoptions EXTENDED_GLOB - fc -lnrm "${1//(#m)[\\()\[\]|*?~]/\\$MATCH}*" 1 2>/dev/null | head -n 1 + local prefix="${1//(#m)[\\()\[\]|*?~]/\\$MATCH}" + + # Get the keys of the history items that match + local -a histkeys + histkeys=(${(k)history[(r)$prefix*]}) + + # Echo the value of the first key + echo -E "${history[$histkeys[1]]}" } diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 2dc5141..a438c65 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -421,7 +421,14 @@ zle -N _autosuggest-show-suggestion _zsh_autosuggest_show_suggestion _zsh_autosuggest_strategy_default() { setopt localoptions EXTENDED_GLOB - fc -lnrm "${1//(#m)[\\()\[\]|*?~]/\\$MATCH}*" 1 2>/dev/null | head -n 1 + local prefix="${1//(#m)[\\()\[\]|*?~]/\\$MATCH}" + + # Get the keys of the history items that match + local -a histkeys + histkeys=(${(k)history[(r)$prefix*]}) + + # Echo the value of the first key + echo -E "${history[$histkeys[1]]}" } #--------------------------------------------------------------------# From 50e6832b8c5f5b3aff9da8f7aaf5a0b836f27368 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 24 Jan 2017 23:05:50 -0700 Subject: [PATCH 20/70] Escape the prefix passed into the match_prev_cmd strategy --- src/strategies/match_prev_cmd.zsh | 2 +- zsh-autosuggestions.zsh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/strategies/match_prev_cmd.zsh b/src/strategies/match_prev_cmd.zsh index bf8bdd9..632a24b 100644 --- a/src/strategies/match_prev_cmd.zsh +++ b/src/strategies/match_prev_cmd.zsh @@ -21,7 +21,7 @@ # `HIST_EXPIRE_DUPS_FIRST`. _zsh_autosuggest_strategy_match_prev_cmd() { - local prefix="$1" + local prefix="${1//(#m)[\\()\[\]|*?~]/\\$MATCH}" # Get all history event numbers that correspond to history # entries that match pattern $prefix* diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index a438c65..b72f3e2 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -453,7 +453,7 @@ _zsh_autosuggest_strategy_default() { # `HIST_EXPIRE_DUPS_FIRST`. _zsh_autosuggest_strategy_match_prev_cmd() { - local prefix="$1" + local prefix="${1//(#m)[\\()\[\]|*?~]/\\$MATCH}" # Get all history event numbers that correspond to history # entries that match pattern $prefix* From 21d9eda5dd0fdf74afeb9d452735e4a0c14b7a61 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 24 Jan 2017 23:59:38 -0700 Subject: [PATCH 21/70] Wrap suggestion fetch command in parens to actually run in background --- src/async.zsh | 2 +- zsh-autosuggestions.zsh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/async.zsh b/src/async.zsh index 1d2ae87..ddc07a5 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -22,7 +22,7 @@ _zsh_autosuggest_async_suggestion_server() { kill -KILL %1 &>/dev/null # Run suggestion search in the background - echo -n -E "$($strategy "$prefix")"$'\0' & + (echo -n -E "$($strategy "$prefix")"$'\0') & done } diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index b72f3e2..bcee1ee 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -506,7 +506,7 @@ _zsh_autosuggest_async_suggestion_server() { kill -KILL %1 &>/dev/null # Run suggestion search in the background - echo -n -E "$($strategy "$prefix")"$'\0' & + (echo -n -E "$($strategy "$prefix")"$'\0') & done } From 54e1eee924be6b11db297301d6f95d3d3ddba32b Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Wed, 25 Jan 2017 00:00:13 -0700 Subject: [PATCH 22/70] Optimize case where manually typing in a suggestion --- src/widgets.zsh | 11 +++++++++++ zsh-autosuggestions.zsh | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/widgets.zsh b/src/widgets.zsh index c6162b3..66eef58 100644 --- a/src/widgets.zsh +++ b/src/widgets.zsh @@ -26,6 +26,17 @@ _zsh_autosuggest_modify() { _zsh_autosuggest_invoke_original_widget $@ retval=$? + # Optimize if manually typing in the suggestion + if [ $#BUFFER -gt $#orig_buffer ]; then + local added=${BUFFER#$orig_buffer} + + # If the string added matches the beginning of the postdisplay + if [ "$added" = "${orig_postdisplay:0:$#added}" ]; then + POSTDISPLAY="${orig_postdisplay:$#added}" + return $retval + fi + fi + # Don't fetch a new suggestion if the buffer hasn't changed if [ "$BUFFER" = "$orig_buffer" ]; then POSTDISPLAY="$orig_postdisplay" diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index bcee1ee..266cdff 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -291,6 +291,17 @@ _zsh_autosuggest_modify() { _zsh_autosuggest_invoke_original_widget $@ retval=$? + # Optimize if manually typing in the suggestion + if [ $#BUFFER -gt $#orig_buffer ]; then + local added=${BUFFER#$orig_buffer} + + # If the string added matches the beginning of the postdisplay + if [ "$added" = "${orig_postdisplay:0:$#added}" ]; then + POSTDISPLAY="${orig_postdisplay:$#added}" + return $retval + fi + fi + # Don't fetch a new suggestion if the buffer hasn't changed if [ "$BUFFER" = "$orig_buffer" ]; then POSTDISPLAY="$orig_postdisplay" From 6c5cd4233189fb67872a58d21c1368fd1d6d1c9b Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Wed, 25 Jan 2017 00:00:53 -0700 Subject: [PATCH 23/70] Go back to tracking last pid because `kill %1` didn't seem to be working --- src/async.zsh | 5 ++++- zsh-autosuggestions.zsh | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/async.zsh b/src/async.zsh index ddc07a5..f0dbc2c 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -16,13 +16,16 @@ _zsh_autosuggest_async_suggestion_server() { stty -onlcr local strategy=$1 + local last_pid while IFS='' read -r -d $'\0' prefix; do # Kill last bg process - kill -KILL %1 &>/dev/null + kill -KILL $last_pid &>/dev/null # Run suggestion search in the background (echo -n -E "$($strategy "$prefix")"$'\0') & + + last_pid=$! done } diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 266cdff..a6cbe05 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -511,13 +511,16 @@ _zsh_autosuggest_async_suggestion_server() { stty -onlcr local strategy=$1 + local last_pid while IFS='' read -r -d $'\0' prefix; do # Kill last bg process - kill -KILL %1 &>/dev/null + kill -KILL $last_pid &>/dev/null # Run suggestion search in the background (echo -n -E "$($strategy "$prefix")"$'\0') & + + last_pid=$! done } From 2dbd261989cfaf813e0aa7311cb8a0bed83bfb13 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Thu, 26 Jan 2017 16:04:46 -0700 Subject: [PATCH 24/70] Allow configuring of zsh binary to run integration tests against --- spec/terminal_session.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/terminal_session.rb b/spec/terminal_session.rb index 63eaf0d..ca06343 100644 --- a/spec/terminal_session.rb +++ b/spec/terminal_session.rb @@ -1,8 +1,10 @@ require 'securerandom' class TerminalSession + ZSH_BIN = ENV['TEST_ZSH_BIN'] || 'zsh' + def initialize(width: 80, height: 24, prompt: '', term: 'xterm-256color') - tmux_command("new-session -d -x #{width} -y #{height} 'PS1=#{prompt} TERM=#{term} zsh -f'") + tmux_command("new-session -d -x #{width} -y #{height} 'PS1=#{prompt} TERM=#{term} #{ZSH_BIN} -f'") end def run_command(command) From 3f57198d0791dc6a176aab887673dbe6c276abaa Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Thu, 26 Jan 2017 16:11:01 -0700 Subject: [PATCH 25/70] Only bind widgets once, on initial sourcing --- src/start.zsh | 2 ++ zsh-autosuggestions.zsh | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/start.zsh b/src/start.zsh index 5314e1f..ab60afe 100644 --- a/src/start.zsh +++ b/src/start.zsh @@ -5,6 +5,8 @@ # Start the autosuggestion widgets _zsh_autosuggest_start() { + add-zsh-hook -d precmd _zsh_autosuggest_start + _zsh_autosuggest_check_deprecated_config _zsh_autosuggest_bind_widgets } diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index a6cbe05..c635630 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -568,6 +568,8 @@ add-zsh-hook precmd _zsh_autosuggest_async_recreate_pty # Start the autosuggestion widgets _zsh_autosuggest_start() { + add-zsh-hook -d precmd _zsh_autosuggest_start + _zsh_autosuggest_check_deprecated_config _zsh_autosuggest_bind_widgets } From 78ba07179a38584ed6756307f329d64da03b0638 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Thu, 26 Jan 2017 16:35:33 -0700 Subject: [PATCH 26/70] Add feature detection Checks whether `zpty` gives a file descriptor, which was not the case in older versions of zsh. Based on https://github.com/mafredri/zsh-async/blob/a4b2f81c966a00eeb38876d505d97423cc8addfd/async.zsh#L395-L401 --- Makefile | 1 + src/features.zsh | 19 +++++++++++++++++++ src/start.zsh | 1 + zsh-autosuggestions.zsh | 20 ++++++++++++++++++++ 4 files changed, 41 insertions(+) create mode 100644 src/features.zsh diff --git a/Makefile b/Makefile index 2ea2763..63d8020 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ SRC_FILES := \ $(SRC_DIR)/setup.zsh \ $(SRC_DIR)/config.zsh \ $(SRC_DIR)/util.zsh \ + $(SRC_DIR)/features.zsh \ $(SRC_DIR)/deprecated.zsh \ $(SRC_DIR)/bind.zsh \ $(SRC_DIR)/highlight.zsh \ diff --git a/src/features.zsh b/src/features.zsh new file mode 100644 index 0000000..cd960e5 --- /dev/null +++ b/src/features.zsh @@ -0,0 +1,19 @@ + +#--------------------------------------------------------------------# +# Feature Detection # +#--------------------------------------------------------------------# + +_zsh_autosuggest_feature_detect() { + typeset -g _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD + typeset -h REPLY + + zpty $ZSH_AUTOSUGGEST_PTY_NAME : + + if (( REPLY )); then + _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=1 + else + _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=0 + fi + + zpty -d $ZSH_AUTOSUGGEST_PTY_NAME +} diff --git a/src/start.zsh b/src/start.zsh index ab60afe..0633da3 100644 --- a/src/start.zsh +++ b/src/start.zsh @@ -7,6 +7,7 @@ _zsh_autosuggest_start() { add-zsh-hook -d precmd _zsh_autosuggest_start + _zsh_autosuggest_feature_detect _zsh_autosuggest_check_deprecated_config _zsh_autosuggest_bind_widgets } diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index c635630..0d17ef7 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -111,6 +111,25 @@ _zsh_autosuggest_escape_command() { echo -E "${1//(#m)[\"\'\\()\[\]|*?~]/\\$MATCH}" } +#--------------------------------------------------------------------# +# Feature Detection # +#--------------------------------------------------------------------# + +_zsh_autosuggest_feature_detect() { + typeset -g _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD + typeset -h REPLY + + zpty $ZSH_AUTOSUGGEST_PTY_NAME : + + if (( REPLY )); then + _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=1 + else + _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=0 + fi + + zpty -d $ZSH_AUTOSUGGEST_PTY_NAME +} + #--------------------------------------------------------------------# # Handle Deprecated Variables/Widgets # #--------------------------------------------------------------------# @@ -570,6 +589,7 @@ add-zsh-hook precmd _zsh_autosuggest_async_recreate_pty _zsh_autosuggest_start() { add-zsh-hook -d precmd _zsh_autosuggest_start + _zsh_autosuggest_feature_detect _zsh_autosuggest_check_deprecated_config _zsh_autosuggest_bind_widgets } From f33b605a63b3d64357fb2f390d07349de9d85013 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Thu, 26 Jan 2017 16:37:42 -0700 Subject: [PATCH 27/70] Move async initialization into `start` function to keep in one place --- src/async.zsh | 2 -- src/start.zsh | 3 +++ zsh-autosuggestions.zsh | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/async.zsh b/src/async.zsh index f0dbc2c..81b3268 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -64,5 +64,3 @@ _zsh_autosuggest_async_recreate_pty() { # Set up input handler from the pty zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_suggestion_ready } - -add-zsh-hook precmd _zsh_autosuggest_async_recreate_pty diff --git a/src/start.zsh b/src/start.zsh index 0633da3..7a1725e 100644 --- a/src/start.zsh +++ b/src/start.zsh @@ -10,6 +10,9 @@ _zsh_autosuggest_start() { _zsh_autosuggest_feature_detect _zsh_autosuggest_check_deprecated_config _zsh_autosuggest_bind_widgets + + _zsh_autosuggest_async_recreate_pty + add-zsh-hook precmd _zsh_autosuggest_async_recreate_pty } add-zsh-hook precmd _zsh_autosuggest_start diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 0d17ef7..efdf6d2 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -579,8 +579,6 @@ _zsh_autosuggest_async_recreate_pty() { zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_suggestion_ready } -add-zsh-hook precmd _zsh_autosuggest_async_recreate_pty - #--------------------------------------------------------------------# # Start # #--------------------------------------------------------------------# @@ -592,6 +590,9 @@ _zsh_autosuggest_start() { _zsh_autosuggest_feature_detect _zsh_autosuggest_check_deprecated_config _zsh_autosuggest_bind_widgets + + _zsh_autosuggest_async_recreate_pty + add-zsh-hook precmd _zsh_autosuggest_async_recreate_pty } add-zsh-hook precmd _zsh_autosuggest_start From 16666da4884a334ccb4c457c63be3f9d0fa00fec Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Thu, 26 Jan 2017 16:50:19 -0700 Subject: [PATCH 28/70] Handle versions of zsh where zpty does not set REPLY to fd of opened pty Based on https://github.com/mafredri/zsh-async/blob/e702ec4697ab6469b8948d599768227304d146b1/async.zsh#L400-L406 --- src/async.zsh | 16 ++++++++++++++-- zsh-autosuggestions.zsh | 16 ++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/async.zsh b/src/async.zsh index 81b3268..eadd9c7 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -52,14 +52,26 @@ _zsh_autosuggest_async_recreate_pty() { zpty -d $ZSH_AUTOSUGGEST_PTY_NAME &>/dev/null fi - # REPLY stores the fd to read from + # With newer versions of zsh, REPLY stores the fd to read from typeset -h REPLY + # If we won't get a fd back from zpty, try to guess it + if [ $_ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD -eq 0 ]; then + integer -l zptyfd + exec {zptyfd}>&1 # Open a new file descriptor (above 10). + exec {zptyfd}>&- # Close it so it's free to be used by zpty. + fi + # Start a new pty running the server function zpty -b $ZSH_AUTOSUGGEST_PTY_NAME "_zsh_autosuggest_async_suggestion_server _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" # Store the fd so we can destroy this pty later - _ZSH_AUTOSUGGEST_PTY_FD=$REPLY + if (( REPLY )); then + _ZSH_AUTOSUGGEST_PTY_FD=$REPLY + else + _ZSH_AUTOSUGGEST_PTY_FD=$zptyfd + fi + # Set up input handler from the pty zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_suggestion_ready diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index efdf6d2..64bfd34 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -566,14 +566,26 @@ _zsh_autosuggest_async_recreate_pty() { zpty -d $ZSH_AUTOSUGGEST_PTY_NAME &>/dev/null fi - # REPLY stores the fd to read from + # With newer versions of zsh, REPLY stores the fd to read from typeset -h REPLY + # If we won't get a fd back from zpty, try to guess it + if [ $_ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD -eq 0 ]; then + integer -l zptyfd + exec {zptyfd}>&1 # Open a new file descriptor (above 10). + exec {zptyfd}>&- # Close it so it's free to be used by zpty. + fi + # Start a new pty running the server function zpty -b $ZSH_AUTOSUGGEST_PTY_NAME "_zsh_autosuggest_async_suggestion_server _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" # Store the fd so we can destroy this pty later - _ZSH_AUTOSUGGEST_PTY_FD=$REPLY + if (( REPLY )); then + _ZSH_AUTOSUGGEST_PTY_FD=$REPLY + else + _ZSH_AUTOSUGGEST_PTY_FD=$zptyfd + fi + # Set up input handler from the pty zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_suggestion_ready From 40bb2e78046f715eac8ff4a0f5a60ad4513cd1b0 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Thu, 26 Jan 2017 17:00:56 -0700 Subject: [PATCH 29/70] little cleanup --- src/async.zsh | 3 +-- zsh-autosuggestions.zsh | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/async.zsh b/src/async.zsh index eadd9c7..3e61a0b 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -65,14 +65,13 @@ _zsh_autosuggest_async_recreate_pty() { # Start a new pty running the server function zpty -b $ZSH_AUTOSUGGEST_PTY_NAME "_zsh_autosuggest_async_suggestion_server _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" - # Store the fd so we can destroy this pty later + # Store the fd so we can remove the handler later if (( REPLY )); then _ZSH_AUTOSUGGEST_PTY_FD=$REPLY else _ZSH_AUTOSUGGEST_PTY_FD=$zptyfd fi - # Set up input handler from the pty zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_suggestion_ready } diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 64bfd34..d6b6377 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -579,14 +579,13 @@ _zsh_autosuggest_async_recreate_pty() { # Start a new pty running the server function zpty -b $ZSH_AUTOSUGGEST_PTY_NAME "_zsh_autosuggest_async_suggestion_server _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" - # Store the fd so we can destroy this pty later + # Store the fd so we can remove the handler later if (( REPLY )); then _ZSH_AUTOSUGGEST_PTY_FD=$REPLY else _ZSH_AUTOSUGGEST_PTY_FD=$zptyfd fi - # Set up input handler from the pty zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_suggestion_ready } From 89dd69d517161f84c7b5858ae7bfde81026d8836 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 27 Jan 2017 14:06:16 -0700 Subject: [PATCH 30/70] Add pry gem for debugging support --- Gemfile | 1 + Gemfile.lock | 10 +++++++++- spec/spec_helper.rb | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index bb4eb7f..c8090be 100644 --- a/Gemfile +++ b/Gemfile @@ -2,3 +2,4 @@ source 'https://rubygems.org' gem 'rspec' gem 'rspec-wait' +gem 'pry' diff --git a/Gemfile.lock b/Gemfile.lock index 5ad7873..75b649b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,13 @@ GEM remote: https://rubygems.org/ specs: + coderay (1.1.1) diff-lcs (1.3) + method_source (0.8.2) + pry (0.10.4) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) rspec (3.5.0) rspec-core (~> 3.5.0) rspec-expectations (~> 3.5.0) @@ -17,13 +23,15 @@ GEM rspec-support (3.5.0) rspec-wait (0.0.9) rspec (>= 3, < 4) + slop (3.6.0) PLATFORMS ruby DEPENDENCIES + pry rspec rspec-wait BUNDLED WITH - 1.12.5 + 1.13.6 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b3882d7..2b8eb13 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,4 @@ +require 'pry' require 'rspec/wait' require 'terminal_session' From c3425870f1bb6b0e8109d9808392f55ff6122fab Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 27 Jan 2017 14:07:06 -0700 Subject: [PATCH 31/70] Wait for the terminal.clear to go through before continuing Prevents some flakiness in tests --- spec/strategies/default_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/strategies/default_spec.rb b/spec/strategies/default_spec.rb index 3987c7e..e4bdb2f 100644 --- a/spec/strategies/default_spec.rb +++ b/spec/strategies/default_spec.rb @@ -5,6 +5,8 @@ describe 'default strategy' do session.run_command('source zsh-autosuggestions.zsh') session.run_command('fc -p') session.clear + + wait_for { session.content }.to eq('') end after do From e3eb286ea213dbcac4f9c22f37ba084dd9e22024 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 27 Jan 2017 15:18:26 -0700 Subject: [PATCH 32/70] Lots of little async cleanups --- src/async.zsh | 66 +++++++++------ src/config.zsh | 3 + src/start.zsh | 6 +- src/strategies/default.zsh | 4 +- src/strategies/match_prev_cmd.zsh | 4 +- src/widgets.zsh | 48 ++++++----- zsh-autosuggestions.zsh | 131 ++++++++++++++++++------------ 7 files changed, 160 insertions(+), 102 deletions(-) diff --git a/src/async.zsh b/src/async.zsh index 3e61a0b..fd81bca 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -3,13 +3,8 @@ # Async # #--------------------------------------------------------------------# -_zsh_autosuggest_async_fetch_suggestion() { - # Send the prefix to the pty to fetch a suggestion - zpty -w -n $ZSH_AUTOSUGGEST_PTY_NAME "${1}"$'\0' -} - # Pty is spawned running this function -_zsh_autosuggest_async_suggestion_server() { +_zsh_autosuggest_async_server() { emulate -R zsh # Output only newlines (not carriage return + newline) @@ -18,40 +13,37 @@ _zsh_autosuggest_async_suggestion_server() { local strategy=$1 local last_pid - while IFS='' read -r -d $'\0' prefix; do + while IFS='' read -r -d $'\0' query; do # Kill last bg process kill -KILL $last_pid &>/dev/null # Run suggestion search in the background - (echo -n -E "$($strategy "$prefix")"$'\0') & + ( + local suggestion + _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY "$query" + echo -n -E "$suggestion"$'\0' + ) & last_pid=$! done } +_zsh_autosuggest_async_request() { + # Send the query to the pty to fetch a suggestion + zpty -w -n $ZSH_AUTOSUGGEST_PTY_NAME "${1}"$'\0' +} + # Called when new data is ready to be read from the pty # First arg will be fd ready for reading # Second arg will be passed in case of error -_zsh_autosuggest_async_suggestion_ready() { +_zsh_autosuggest_async_response() { local suggestion zpty -rt $ZSH_AUTOSUGGEST_PTY_NAME suggestion '*'$'\0' 2>/dev/null - zle _autosuggest-show-suggestion "${suggestion%$'\0'}" + zle autosuggest-suggest "${suggestion%$'\0'}" } -# Recreate the pty to get a fresh list of history events -_zsh_autosuggest_async_recreate_pty() { - typeset -g _ZSH_AUTOSUGGEST_PTY_FD - - # Kill the old pty - if [ -n "$_ZSH_AUTOSUGGEST_PTY_FD" ]; then - # Remove the input handler - zle -F $_ZSH_AUTOSUGGEST_PTY_FD - - # Destroy the pty - zpty -d $ZSH_AUTOSUGGEST_PTY_NAME &>/dev/null - fi - +_zsh_autosuggest_async_pty_create() { # With newer versions of zsh, REPLY stores the fd to read from typeset -h REPLY @@ -63,7 +55,7 @@ _zsh_autosuggest_async_recreate_pty() { fi # Start a new pty running the server function - zpty -b $ZSH_AUTOSUGGEST_PTY_NAME "_zsh_autosuggest_async_suggestion_server _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" + zpty -b $ZSH_AUTOSUGGEST_PTY_NAME "_zsh_autosuggest_async_server _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" # Store the fd so we can remove the handler later if (( REPLY )); then @@ -73,5 +65,29 @@ _zsh_autosuggest_async_recreate_pty() { fi # Set up input handler from the pty - zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_suggestion_ready + zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_response +} + +_zsh_autosuggest_async_pty_destroy() { + if [ -n "$_ZSH_AUTOSUGGEST_PTY_FD" ]; then + # Remove the input handler + zle -F $_ZSH_AUTOSUGGEST_PTY_FD + + # Destroy the pty + zpty -d $ZSH_AUTOSUGGEST_PTY_NAME &>/dev/null + fi +} + +_zsh_autosuggest_async_pty_recreate() { + _zsh_autosuggest_async_pty_destroy + _zsh_autosuggest_async_pty_create +} + +_zsh_autosuggest_async_start() { + typeset -g _ZSH_AUTOSUGGEST_PTY_FD + + _zsh_autosuggest_async_pty_create + + # We recreate the pty to get a fresh list of history events + add-zsh-hook precmd _zsh_autosuggest_async_pty_recreate } diff --git a/src/config.zsh b/src/config.zsh index 68d3b32..29806ea 100644 --- a/src/config.zsh +++ b/src/config.zsh @@ -63,3 +63,6 @@ ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( # Max size of buffer to trigger autosuggestion. Leave undefined for no upper bound. ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE= + +# Use asynchronous mode by default. Unset this variable to use sync mode. +ZSH_AUTOSUGGEST_USE_ASYNC= diff --git a/src/start.zsh b/src/start.zsh index 7a1725e..49af555 100644 --- a/src/start.zsh +++ b/src/start.zsh @@ -11,8 +11,10 @@ _zsh_autosuggest_start() { _zsh_autosuggest_check_deprecated_config _zsh_autosuggest_bind_widgets - _zsh_autosuggest_async_recreate_pty - add-zsh-hook precmd _zsh_autosuggest_async_recreate_pty + if [ -n "${ZSH_AUTOSUGGEST_USE_ASYNC+x}" ]; then + _zsh_autosuggest_async_start + fi } +# Start the autosuggestion widgets on the next precmd add-zsh-hook precmd _zsh_autosuggest_start diff --git a/src/strategies/default.zsh b/src/strategies/default.zsh index 4246b78..bbd5e59 100644 --- a/src/strategies/default.zsh +++ b/src/strategies/default.zsh @@ -15,6 +15,6 @@ _zsh_autosuggest_strategy_default() { local -a histkeys histkeys=(${(k)history[(r)$prefix*]}) - # Echo the value of the first key - echo -E "${history[$histkeys[1]]}" + # Give back the value of the first key + suggestion="${history[$histkeys[1]]}" } diff --git a/src/strategies/match_prev_cmd.zsh b/src/strategies/match_prev_cmd.zsh index 632a24b..ee26346 100644 --- a/src/strategies/match_prev_cmd.zsh +++ b/src/strategies/match_prev_cmd.zsh @@ -47,6 +47,6 @@ _zsh_autosuggest_strategy_match_prev_cmd() { fi done - # Echo the matched history entry - echo -E "$history[$histkey]" + # Give back the matched history entry + suggestion="$history[$histkey]" } diff --git a/src/widgets.zsh b/src/widgets.zsh index 66eef58..0f0520a 100644 --- a/src/widgets.zsh +++ b/src/widgets.zsh @@ -46,13 +46,35 @@ _zsh_autosuggest_modify() { # 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 -lt "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]; then - _zsh_autosuggest_async_fetch_suggestion "$BUFFER" + _zsh_autosuggest_fetch fi fi return $retval } +# Fetch a new suggestion based on what's currently in the buffer +_zsh_autosuggest_fetch() { + if zpty -t "$ZSH_AUTOSUGGEST_PTY_NAME" &>/dev/null; then + _zsh_autosuggest_async_request "$BUFFER" + else + local suggestion + _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY "$BUFFER" + _zsh_autosuggest_suggest "$suggestion" + fi +} + +# Offer a suggestion +_zsh_autosuggest_suggest() { + local suggestion="$1" + + if [ -n "$suggestion" ]; then + POSTDISPLAY="${suggestion#$BUFFER}" + else + unset POSTDISPLAY + fi +} + # Accept the entire suggestion _zsh_autosuggest_accept() { local -i max_cursor_pos=$#BUFFER @@ -120,7 +142,7 @@ _zsh_autosuggest_partial_accept() { return $retval } -for action in clear modify accept partial_accept execute; do +for action in clear modify fetch suggest accept partial_accept execute; do eval "_zsh_autosuggest_widget_$action() { local -i retval @@ -131,28 +153,14 @@ for action in clear modify accept partial_accept execute; do _zsh_autosuggest_highlight_apply + zle -R + return \$retval }" done +zle -N autosuggest-fetch _zsh_autosuggest_widget_fetch +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 - -_zsh_autosuggest_show_suggestion() { - local suggestion="$1" - - _zsh_autosuggest_highlight_reset - - if [ -n "$suggestion" ]; then - POSTDISPLAY="${suggestion#$BUFFER}" - else - unset POSTDISPLAY - fi - - _zsh_autosuggest_highlight_apply - - zle -R -} - -zle -N _autosuggest-show-suggestion _zsh_autosuggest_show_suggestion diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index d6b6377..5d56502 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -100,6 +100,9 @@ ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( # Max size of buffer to trigger autosuggestion. Leave undefined for no upper bound. ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE= +# Use asynchronous mode by default. Unset this variable to use sync mode. +ZSH_AUTOSUGGEST_USE_ASYNC= + #--------------------------------------------------------------------# # Utility Functions # #--------------------------------------------------------------------# @@ -330,13 +333,35 @@ _zsh_autosuggest_modify() { # 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 -lt "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]; then - _zsh_autosuggest_async_fetch_suggestion "$BUFFER" + _zsh_autosuggest_fetch fi fi return $retval } +# Fetch a new suggestion based on what's currently in the buffer +_zsh_autosuggest_fetch() { + if zpty -t "$ZSH_AUTOSUGGEST_PTY_NAME" &>/dev/null; then + _zsh_autosuggest_async_request "$BUFFER" + else + local suggestion + _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY "$BUFFER" + _zsh_autosuggest_suggest "$suggestion" + fi +} + +# Offer a suggestion +_zsh_autosuggest_suggest() { + local suggestion="$1" + + if [ -n "$suggestion" ]; then + POSTDISPLAY="${suggestion#$BUFFER}" + else + unset POSTDISPLAY + fi +} + # Accept the entire suggestion _zsh_autosuggest_accept() { local -i max_cursor_pos=$#BUFFER @@ -404,7 +429,7 @@ _zsh_autosuggest_partial_accept() { return $retval } -for action in clear modify accept partial_accept execute; do +for action in clear modify fetch suggest accept partial_accept execute; do eval "_zsh_autosuggest_widget_$action() { local -i retval @@ -415,32 +440,18 @@ for action in clear modify accept partial_accept execute; do _zsh_autosuggest_highlight_apply + zle -R + return \$retval }" done +zle -N autosuggest-fetch _zsh_autosuggest_widget_fetch +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 -_zsh_autosuggest_show_suggestion() { - local suggestion="$1" - - _zsh_autosuggest_highlight_reset - - if [ -n "$suggestion" ]; then - POSTDISPLAY="${suggestion#$BUFFER}" - else - unset POSTDISPLAY - fi - - _zsh_autosuggest_highlight_apply - - zle -R -} - -zle -N _autosuggest-show-suggestion _zsh_autosuggest_show_suggestion - #--------------------------------------------------------------------# # Default Suggestion Strategy # #--------------------------------------------------------------------# @@ -457,8 +468,8 @@ _zsh_autosuggest_strategy_default() { local -a histkeys histkeys=(${(k)history[(r)$prefix*]}) - # Echo the value of the first key - echo -E "${history[$histkeys[1]]}" + # Give back the value of the first key + suggestion="${history[$histkeys[1]]}" } #--------------------------------------------------------------------# @@ -509,21 +520,16 @@ _zsh_autosuggest_strategy_match_prev_cmd() { fi done - # Echo the matched history entry - echo -E "$history[$histkey]" + # Give back the matched history entry + suggestion="$history[$histkey]" } #--------------------------------------------------------------------# # Async # #--------------------------------------------------------------------# -_zsh_autosuggest_async_fetch_suggestion() { - # Send the prefix to the pty to fetch a suggestion - zpty -w -n $ZSH_AUTOSUGGEST_PTY_NAME "${1}"$'\0' -} - # Pty is spawned running this function -_zsh_autosuggest_async_suggestion_server() { +_zsh_autosuggest_async_server() { emulate -R zsh # Output only newlines (not carriage return + newline) @@ -532,40 +538,37 @@ _zsh_autosuggest_async_suggestion_server() { local strategy=$1 local last_pid - while IFS='' read -r -d $'\0' prefix; do + while IFS='' read -r -d $'\0' query; do # Kill last bg process kill -KILL $last_pid &>/dev/null # Run suggestion search in the background - (echo -n -E "$($strategy "$prefix")"$'\0') & + ( + local suggestion + _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY "$query" + echo -n -E "$suggestion"$'\0' + ) & last_pid=$! done } +_zsh_autosuggest_async_request() { + # Send the query to the pty to fetch a suggestion + zpty -w -n $ZSH_AUTOSUGGEST_PTY_NAME "${1}"$'\0' +} + # Called when new data is ready to be read from the pty # First arg will be fd ready for reading # Second arg will be passed in case of error -_zsh_autosuggest_async_suggestion_ready() { +_zsh_autosuggest_async_response() { local suggestion zpty -rt $ZSH_AUTOSUGGEST_PTY_NAME suggestion '*'$'\0' 2>/dev/null - zle _autosuggest-show-suggestion "${suggestion%$'\0'}" + zle autosuggest-suggest "${suggestion%$'\0'}" } -# Recreate the pty to get a fresh list of history events -_zsh_autosuggest_async_recreate_pty() { - typeset -g _ZSH_AUTOSUGGEST_PTY_FD - - # Kill the old pty - if [ -n "$_ZSH_AUTOSUGGEST_PTY_FD" ]; then - # Remove the input handler - zle -F $_ZSH_AUTOSUGGEST_PTY_FD - - # Destroy the pty - zpty -d $ZSH_AUTOSUGGEST_PTY_NAME &>/dev/null - fi - +_zsh_autosuggest_async_pty_create() { # With newer versions of zsh, REPLY stores the fd to read from typeset -h REPLY @@ -577,7 +580,7 @@ _zsh_autosuggest_async_recreate_pty() { fi # Start a new pty running the server function - zpty -b $ZSH_AUTOSUGGEST_PTY_NAME "_zsh_autosuggest_async_suggestion_server _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" + zpty -b $ZSH_AUTOSUGGEST_PTY_NAME "_zsh_autosuggest_async_server _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" # Store the fd so we can remove the handler later if (( REPLY )); then @@ -587,7 +590,31 @@ _zsh_autosuggest_async_recreate_pty() { fi # Set up input handler from the pty - zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_suggestion_ready + zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_response +} + +_zsh_autosuggest_async_pty_destroy() { + if [ -n "$_ZSH_AUTOSUGGEST_PTY_FD" ]; then + # Remove the input handler + zle -F $_ZSH_AUTOSUGGEST_PTY_FD + + # Destroy the pty + zpty -d $ZSH_AUTOSUGGEST_PTY_NAME &>/dev/null + fi +} + +_zsh_autosuggest_async_pty_recreate() { + _zsh_autosuggest_async_pty_destroy + _zsh_autosuggest_async_pty_create +} + +_zsh_autosuggest_async_start() { + typeset -g _ZSH_AUTOSUGGEST_PTY_FD + + _zsh_autosuggest_async_pty_create + + # We recreate the pty to get a fresh list of history events + add-zsh-hook precmd _zsh_autosuggest_async_pty_recreate } #--------------------------------------------------------------------# @@ -602,8 +629,10 @@ _zsh_autosuggest_start() { _zsh_autosuggest_check_deprecated_config _zsh_autosuggest_bind_widgets - _zsh_autosuggest_async_recreate_pty - add-zsh-hook precmd _zsh_autosuggest_async_recreate_pty + if [ -n "${ZSH_AUTOSUGGEST_USE_ASYNC+x}" ]; then + _zsh_autosuggest_async_start + fi } +# Start the autosuggestion widgets on the next precmd add-zsh-hook precmd _zsh_autosuggest_start From 2c465a932a3cac2cbdfe9defa7ca8640e48cb33b Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sun, 29 Jan 2017 10:39:07 -0700 Subject: [PATCH 33/70] Rename async pty name config var --- src/async.zsh | 8 ++++---- src/config.zsh | 6 +++--- src/features.zsh | 4 ++-- src/widgets.zsh | 2 +- zsh-autosuggestions.zsh | 20 ++++++++++---------- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/async.zsh b/src/async.zsh index fd81bca..40de015 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -30,7 +30,7 @@ _zsh_autosuggest_async_server() { _zsh_autosuggest_async_request() { # Send the query to the pty to fetch a suggestion - zpty -w -n $ZSH_AUTOSUGGEST_PTY_NAME "${1}"$'\0' + zpty -w -n $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME "${1}"$'\0' } # Called when new data is ready to be read from the pty @@ -39,7 +39,7 @@ _zsh_autosuggest_async_request() { _zsh_autosuggest_async_response() { local suggestion - zpty -rt $ZSH_AUTOSUGGEST_PTY_NAME suggestion '*'$'\0' 2>/dev/null + zpty -rt $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME suggestion '*'$'\0' 2>/dev/null zle autosuggest-suggest "${suggestion%$'\0'}" } @@ -55,7 +55,7 @@ _zsh_autosuggest_async_pty_create() { fi # Start a new pty running the server function - zpty -b $ZSH_AUTOSUGGEST_PTY_NAME "_zsh_autosuggest_async_server _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" + zpty -b $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME "_zsh_autosuggest_async_server _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" # Store the fd so we can remove the handler later if (( REPLY )); then @@ -74,7 +74,7 @@ _zsh_autosuggest_async_pty_destroy() { zle -F $_ZSH_AUTOSUGGEST_PTY_FD # Destroy the pty - zpty -d $ZSH_AUTOSUGGEST_PTY_NAME &>/dev/null + zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null fi } diff --git a/src/config.zsh b/src/config.zsh index 29806ea..a9f02e6 100644 --- a/src/config.zsh +++ b/src/config.zsh @@ -11,9 +11,6 @@ ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8' # Prefix to use when saving original versions of bound widgets ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig- -# Pty name for calculating autosuggestions asynchronously -ZSH_AUTOSUGGEST_PTY_NAME=zsh_autosuggest_pty - ZSH_AUTOSUGGEST_STRATEGY=default # Widgets that clear the suggestion @@ -66,3 +63,6 @@ ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE= # Use asynchronous mode by default. Unset this variable to use sync mode. ZSH_AUTOSUGGEST_USE_ASYNC= + +# Pty name for calculating autosuggestions asynchronously +ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_pty diff --git a/src/features.zsh b/src/features.zsh index cd960e5..9198085 100644 --- a/src/features.zsh +++ b/src/features.zsh @@ -7,7 +7,7 @@ _zsh_autosuggest_feature_detect() { typeset -g _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD typeset -h REPLY - zpty $ZSH_AUTOSUGGEST_PTY_NAME : + zpty $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME : if (( REPLY )); then _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=1 @@ -15,5 +15,5 @@ _zsh_autosuggest_feature_detect() { _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=0 fi - zpty -d $ZSH_AUTOSUGGEST_PTY_NAME + zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME } diff --git a/src/widgets.zsh b/src/widgets.zsh index 0f0520a..bbf9c3f 100644 --- a/src/widgets.zsh +++ b/src/widgets.zsh @@ -55,7 +55,7 @@ _zsh_autosuggest_modify() { # Fetch a new suggestion based on what's currently in the buffer _zsh_autosuggest_fetch() { - if zpty -t "$ZSH_AUTOSUGGEST_PTY_NAME" &>/dev/null; then + if zpty -t "$ZSH_AUTOSUGGEST_ASYNC_PTY_NAME" &>/dev/null; then _zsh_autosuggest_async_request "$BUFFER" else local suggestion diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 5d56502..d0003e2 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -47,9 +47,6 @@ ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8' # Prefix to use when saving original versions of bound widgets ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig- -# Pty name for calculating autosuggestions asynchronously -ZSH_AUTOSUGGEST_PTY_NAME=zsh_autosuggest_pty - ZSH_AUTOSUGGEST_STRATEGY=default # Widgets that clear the suggestion @@ -103,6 +100,9 @@ ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE= # Use asynchronous mode by default. Unset this variable to use sync mode. ZSH_AUTOSUGGEST_USE_ASYNC= +# Pty name for calculating autosuggestions asynchronously +ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_pty + #--------------------------------------------------------------------# # Utility Functions # #--------------------------------------------------------------------# @@ -122,7 +122,7 @@ _zsh_autosuggest_feature_detect() { typeset -g _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD typeset -h REPLY - zpty $ZSH_AUTOSUGGEST_PTY_NAME : + zpty $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME : if (( REPLY )); then _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=1 @@ -130,7 +130,7 @@ _zsh_autosuggest_feature_detect() { _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=0 fi - zpty -d $ZSH_AUTOSUGGEST_PTY_NAME + zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME } #--------------------------------------------------------------------# @@ -342,7 +342,7 @@ _zsh_autosuggest_modify() { # Fetch a new suggestion based on what's currently in the buffer _zsh_autosuggest_fetch() { - if zpty -t "$ZSH_AUTOSUGGEST_PTY_NAME" &>/dev/null; then + if zpty -t "$ZSH_AUTOSUGGEST_ASYNC_PTY_NAME" &>/dev/null; then _zsh_autosuggest_async_request "$BUFFER" else local suggestion @@ -555,7 +555,7 @@ _zsh_autosuggest_async_server() { _zsh_autosuggest_async_request() { # Send the query to the pty to fetch a suggestion - zpty -w -n $ZSH_AUTOSUGGEST_PTY_NAME "${1}"$'\0' + zpty -w -n $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME "${1}"$'\0' } # Called when new data is ready to be read from the pty @@ -564,7 +564,7 @@ _zsh_autosuggest_async_request() { _zsh_autosuggest_async_response() { local suggestion - zpty -rt $ZSH_AUTOSUGGEST_PTY_NAME suggestion '*'$'\0' 2>/dev/null + zpty -rt $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME suggestion '*'$'\0' 2>/dev/null zle autosuggest-suggest "${suggestion%$'\0'}" } @@ -580,7 +580,7 @@ _zsh_autosuggest_async_pty_create() { fi # Start a new pty running the server function - zpty -b $ZSH_AUTOSUGGEST_PTY_NAME "_zsh_autosuggest_async_server _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" + zpty -b $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME "_zsh_autosuggest_async_server _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY" # Store the fd so we can remove the handler later if (( REPLY )); then @@ -599,7 +599,7 @@ _zsh_autosuggest_async_pty_destroy() { zle -F $_ZSH_AUTOSUGGEST_PTY_FD # Destroy the pty - zpty -d $ZSH_AUTOSUGGEST_PTY_NAME &>/dev/null + zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null fi } From 5151adfe400c8a0ccefe7a4f440ae0fb6f5f67a0 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sun, 29 Jan 2017 10:40:05 -0700 Subject: [PATCH 34/70] Make TerminalSession#clear block until the screen is cleared --- spec/terminal_session.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/terminal_session.rb b/spec/terminal_session.rb index ca06343..ee0bca1 100644 --- a/spec/terminal_session.rb +++ b/spec/terminal_session.rb @@ -28,6 +28,7 @@ class TerminalSession def clear send_keys('C-l') + sleep(0.1) until content == '' end def destroy From 51e8755634a5ad3651ab3758d898065d769960eb Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sun, 29 Jan 2017 10:42:28 -0700 Subject: [PATCH 35/70] TerminalSession methods return self to support chaining --- spec/terminal_session.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/terminal_session.rb b/spec/terminal_session.rb index ee0bca1..733728b 100644 --- a/spec/terminal_session.rb +++ b/spec/terminal_session.rb @@ -10,14 +10,20 @@ class TerminalSession def run_command(command) send_string(command) send_keys('enter') + + self end def send_string(str) tmux_command("send-keys -t 0 -l '#{str.gsub("'", "\\'")}'") + + self end def send_keys(*keys) tmux_command("send-keys -t 0 #{keys.join(' ')}") + + self end def content(esc_seqs: false) @@ -29,6 +35,8 @@ class TerminalSession def clear send_keys('C-l') sleep(0.1) until content == '' + + self end def destroy From 98f926d53d4982490e04c8f2b8bb9a24e4afb676 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sun, 29 Jan 2017 10:43:00 -0700 Subject: [PATCH 36/70] Clean up TerminalSession constructor a bit --- spec/terminal_session.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/spec/terminal_session.rb b/spec/terminal_session.rb index 733728b..bbadb9a 100644 --- a/spec/terminal_session.rb +++ b/spec/terminal_session.rb @@ -3,8 +3,17 @@ require 'securerandom' class TerminalSession ZSH_BIN = ENV['TEST_ZSH_BIN'] || 'zsh' - def initialize(width: 80, height: 24, prompt: '', term: 'xterm-256color') - tmux_command("new-session -d -x #{width} -y #{height} 'PS1=#{prompt} TERM=#{term} #{ZSH_BIN} -f'") + def initialize(opts = {}) + opts = { + width: 80, + height: 24, + prompt: '', + term: 'xterm-256color', + zsh_bin: ZSH_BIN + }.merge(opts) + + cmd="PS1=#{opts[:prompt]} TERM=#{opts[:term]} #{ZSH_BIN} -f" + tmux_command("new-session -d -x #{opts[:width]} -y #{opts[:height]} '#{cmd}'") end def run_command(command) From 64e7ec5bf8ce1d0650eb7e7f0764b6de91db1033 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sun, 29 Jan 2017 10:43:20 -0700 Subject: [PATCH 37/70] Rename internal term session method --- spec/terminal_session.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/terminal_session.rb b/spec/terminal_session.rb index bbadb9a..8eeac32 100644 --- a/spec/terminal_session.rb +++ b/spec/terminal_session.rb @@ -61,12 +61,12 @@ class TerminalSession private - def socket_name - @socket_name ||= SecureRandom.hex(6) + def tmux_socket_name + @tmux_socket_name ||= SecureRandom.hex(6) end def tmux_command(cmd) - out = `tmux -u -L #{socket_name} #{cmd}` + out = `tmux -u -L #{tmux_socket_name} #{cmd}` raise('tmux error') unless $?.success? From 38eb7cdafdc7264706e804e78e997f64379152c5 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Thu, 16 Feb 2017 19:07:41 -0700 Subject: [PATCH 38/70] Update license date --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index ee52ee2..ad6594e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ Copyright (c) 2013 Thiago de Arruda -Copyright (c) 2016 Eric Freese +Copyright (c) 2016-2017 Eric Freese Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation From ed8056c5e80c847709d7e76fbd634c2b8d61fd0d Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Thu, 16 Feb 2017 19:18:03 -0700 Subject: [PATCH 39/70] Lots of async changes --- .gitmodules | 6 - .rspec | 1 + .rubocop.yml | 30 ++++ Makefile | 23 +-- script/test_runner.zsh | 54 ------- spec/integrations/client_zpty_spec.rb | 11 ++ spec/multi_line_spec.rb | 13 ++ spec/options/async_zpty_name_spec.rb | 15 ++ spec/options/buffer_max_size_spec.rb | 30 ++++ spec/options/highlight_style_spec.rb | 7 + spec/options/original_widget_prefix_spec.rb | 7 + spec/options/strategy_spec.rb | 20 +++ spec/options/use_async_spec.rb | 7 + spec/options/widget_lists_spec.rb | 72 +++++++++ spec/spec_helper.rb | 35 +++++ spec/special_characters_spec.rb | 55 +++++++ spec/strategies/default_spec.rb | 146 +----------------- spec/strategies/match_prev_cmd_spec.rb | 65 ++------ spec/terminal_session.rb | 4 +- spec/widgets/accept_spec.rb | 162 -------------------- src/async.zsh | 24 ++- src/strategies/default.zsh | 19 ++- src/widgets.zsh | 2 +- test/bind_test.zsh | 45 ------ test/highlight_test.zsh | 73 --------- test/suggestion_test.zsh | 46 ------ test/test_helper.zsh | 60 -------- test/widgets/accept_test.zsh | 161 ------------------- test/widgets/clear_test.zsh | 77 ---------- test/widgets/execute_test.zsh | 26 ---- test/widgets/modify_test.zsh | 88 ----------- test/widgets/partial_accept_test.zsh | 84 ---------- vendor/shunit2 | 1 - vendor/stub.sh | 1 - zsh-autosuggestions.zsh | 47 ++++-- 35 files changed, 384 insertions(+), 1133 deletions(-) delete mode 100644 .gitmodules create mode 100644 .rubocop.yml delete mode 100755 script/test_runner.zsh create mode 100644 spec/integrations/client_zpty_spec.rb create mode 100644 spec/multi_line_spec.rb create mode 100644 spec/options/async_zpty_name_spec.rb create mode 100644 spec/options/buffer_max_size_spec.rb create mode 100644 spec/options/highlight_style_spec.rb create mode 100644 spec/options/original_widget_prefix_spec.rb create mode 100644 spec/options/strategy_spec.rb create mode 100644 spec/options/use_async_spec.rb create mode 100644 spec/options/widget_lists_spec.rb create mode 100644 spec/special_characters_spec.rb delete mode 100644 spec/widgets/accept_spec.rb delete mode 100644 test/bind_test.zsh delete mode 100644 test/highlight_test.zsh delete mode 100644 test/suggestion_test.zsh delete mode 100644 test/test_helper.zsh delete mode 100644 test/widgets/accept_test.zsh delete mode 100644 test/widgets/clear_test.zsh delete mode 100644 test/widgets/execute_test.zsh delete mode 100644 test/widgets/modify_test.zsh delete mode 100644 test/widgets/partial_accept_test.zsh delete mode 160000 vendor/shunit2 delete mode 160000 vendor/stub.sh diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index b45eb46..0000000 --- a/.gitmodules +++ /dev/null @@ -1,6 +0,0 @@ -[submodule "vendor/shunit2"] - path = vendor/shunit2 - url = https://github.com/kward/shunit2 -[submodule "vendor/stub.sh"] - path = vendor/stub.sh - url = https://github.com/ericfreese/stub.sh diff --git a/.rspec b/.rspec index 83e16f8..43ae203 100644 --- a/.rspec +++ b/.rspec @@ -1,2 +1,3 @@ --color --require spec_helper +--format documentation diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..9e0792f --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,30 @@ +# Rails: +# Enabled: true + +AllCops: + TargetRubyVersion: 2.3 + Include: + - '**/Rakefile' + - '**/config.ru' + - '**/Gemfile' + +Metrics/LineLength: + Max: 120 + +Style/Documentation: + Enabled: false + +Style/DotPosition: + EnforcedStyle: trailing + +Style/FrozenStringLiteralComment: + Enabled: false + +Style/Lambda: + Enabled: false + +Style/MultilineMethodCallIndentation: + EnforcedStyle: indented + +Style/TrailingUnderscoreVariable: + Enabled: false diff --git a/Makefile b/Makefile index 63d8020..c7aaf1d 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,4 @@ SRC_DIR := ./src -VENDOR_DIR := ./vendor SRC_FILES := \ $(SRC_DIR)/setup.zsh \ @@ -22,34 +21,16 @@ HEADER_FILES := \ PLUGIN_TARGET := zsh-autosuggestions.zsh -SHUNIT2 := $(VENDOR_DIR)/shunit2/2.1.6 -STUB_SH := $(VENDOR_DIR)/stub.sh/stub.sh - -UNIT_TEST_PREREQS := \ - $(SHUNIT2) \ - $(STUB_SH) - all: $(PLUGIN_TARGET) $(PLUGIN_TARGET): $(HEADER_FILES) $(SRC_FILES) cat $(HEADER_FILES) | sed -e 's/^/# /g' > $@ cat $(SRC_FILES) >> $@ -$(SHUNIT2): - git submodule update --init vendor/shunit2 - -$(STUB_SH): - git submodule update --init vendor/stub.sh - .PHONY: clean clean: rm $(PLUGIN_TARGET) .PHONY: test -test: rspec unit_test - -unit_test: all $(UNIT_TEST_PREREQS) - script/test_runner.zsh $(UNIT_TESTS) - -rspec: all - bundle exec rspec $(RSPEC_TESTS) +test: all + bundle exec rspec $(TESTS) diff --git a/script/test_runner.zsh b/script/test_runner.zsh deleted file mode 100755 index 0ff4173..0000000 --- a/script/test_runner.zsh +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env zsh - -DIR="${0:a:h}" -ROOT_DIR="$DIR/.." -TEST_DIR="$ROOT_DIR/test" - -header() { - local message="$1" - - cat <<-EOF - -#====================================================================# -# $message -#====================================================================# - EOF -} - -# ZSH binary to use -local zsh_bin="zsh" - -while getopts ":z:" opt; do - case $opt in - z) - zsh_bin="$OPTARG" - ;; - \?) - echo "Invalid option: -$OPTARG" >&2 - exit 1 - ;; - :) - echo "Option -$OPTARG requires an argument" >&2 - exit 1 - ;; - esac -done - -shift $((OPTIND -1)) - -# Test suites to run -local -a tests -if [ $#@ -gt 0 ]; then - tests=($@) -else - tests=($TEST_DIR/**/*_test.zsh) -fi - -local -i retval=0 - -for suite in $tests; do - header "${suite#"$ROOT_DIR/"}" - "$zsh_bin" -f "$suite" || retval=$? -done - -exit $retval diff --git a/spec/integrations/client_zpty_spec.rb b/spec/integrations/client_zpty_spec.rb new file mode 100644 index 0000000..f294a1c --- /dev/null +++ b/spec/integrations/client_zpty_spec.rb @@ -0,0 +1,11 @@ +describe 'a running zpty command' do + it 'is not affected by running zsh-autosuggestions' do + session.run_command('zmodload zsh/zpty') + session.run_command('zpty -b kitty cat') + session.run_command('zpty -w kitty cat') + sleep 1 + session.run_command('zpty -r kitty') + + wait_for(session.content).to end_with("\ncat") + end +end diff --git a/spec/multi_line_spec.rb b/spec/multi_line_spec.rb new file mode 100644 index 0000000..4ff2ae1 --- /dev/null +++ b/spec/multi_line_spec.rb @@ -0,0 +1,13 @@ +describe 'a multi-line suggestion' do + it 'should be displayed on multiple lines' do + with_history(-> { + session.send_string('echo "') + session.send_keys('enter') + session.send_string('"') + session.send_keys('enter') + }) do + session.send_keys('e') + wait_for { session.content }.to eq("echo \"\n\"") + end + end +end diff --git a/spec/options/async_zpty_name_spec.rb b/spec/options/async_zpty_name_spec.rb new file mode 100644 index 0000000..c768f54 --- /dev/null +++ b/spec/options/async_zpty_name_spec.rb @@ -0,0 +1,15 @@ +describe 'the zpty for async suggestions' do + it 'is created with the default name' do + session.run_command('zpty -t zsh_autosuggest_pty &>/dev/null; echo $?') + wait_for { session.content }.to end_with("\n0") + end + + context 'when ZSH_AUTOSUGGEST_ASYNC_PTY_NAME is set' do + let(:options) { ['ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=foo_pty'] } + + it 'is created with the specified name' do + session.run_command('zpty -t foo_pty &>/dev/null; echo $?') + wait_for { session.content }.to end_with("\n0") + end + end +end diff --git a/spec/options/buffer_max_size_spec.rb b/spec/options/buffer_max_size_spec.rb new file mode 100644 index 0000000..29ca8bc --- /dev/null +++ b/spec/options/buffer_max_size_spec.rb @@ -0,0 +1,30 @@ +describe 'a suggestion' do + let(:term_opts) { { width: 200 } } + let(:long_command) { "echo #{'a' * 100}" } + + around do |example| + with_history(long_command) { example.run } + end + + it 'is provided for any buffer length' do + session.send_string(long_command[0...-1]) + wait_for { session.content }.to eq(long_command) + end + + context 'when ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE is specified' do + let(:buffer_max_size) { 10 } + let(:options) { ["ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=#{buffer_max_size}"] } + + it 'is provided when the buffer is shorter than the specified length' do + session.send_string(long_command[0...(buffer_max_size - 1)]) + wait_for { session.content }.to eq(long_command) + end + + it 'is provided when the buffer is equal to the specified length' do + session.send_string(long_command[0...(buffer_max_size)]) + wait_for { session.content }.to eq(long_command) + end + + it 'is not provided when the buffer is longer than the specified length' + end +end diff --git a/spec/options/highlight_style_spec.rb b/spec/options/highlight_style_spec.rb new file mode 100644 index 0000000..a7e39b3 --- /dev/null +++ b/spec/options/highlight_style_spec.rb @@ -0,0 +1,7 @@ +describe 'a displayed suggestion' do + it 'is shown in the default style' + + describe 'when ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE is set to a zle_highlight string' do + it 'is shown in the specified style' + end +end diff --git a/spec/options/original_widget_prefix_spec.rb b/spec/options/original_widget_prefix_spec.rb new file mode 100644 index 0000000..a4b6e98 --- /dev/null +++ b/spec/options/original_widget_prefix_spec.rb @@ -0,0 +1,7 @@ +describe 'an original zle widget' do + context 'is accessible with the default prefix' + + context 'when ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX is set' do + it 'is accessible with the specified prefix' + end +end diff --git a/spec/options/strategy_spec.rb b/spec/options/strategy_spec.rb new file mode 100644 index 0000000..c9f01e1 --- /dev/null +++ b/spec/options/strategy_spec.rb @@ -0,0 +1,20 @@ +describe 'a suggestion for a given prefix' do + let(:options) { ['_zsh_autosuggest_strategy_default() { suggestion="echo foo" }'] } + + it 'is determined by calling the default strategy function' do + session.send_string('e') + wait_for { session.content }.to eq('echo foo') + end + + context 'when ZSH_AUTOSUGGEST_STRATEGY is set' do + let(:options) { [ + '_zsh_autosuggest_strategy_custom() { suggestion="echo foo" }', + 'ZSH_AUTOSUGGEST_STRATEGY=custom' + ] } + + it 'is determined by calling the specified strategy function' do + session.send_string('e') + wait_for { session.content }.to eq('echo foo') + end + end +end diff --git a/spec/options/use_async_spec.rb b/spec/options/use_async_spec.rb new file mode 100644 index 0000000..8b9ebab --- /dev/null +++ b/spec/options/use_async_spec.rb @@ -0,0 +1,7 @@ +describe 'suggestion fetching' do + it 'is performed asynchronously' + + context 'when ZSH_AUTOSUGGEST_USE_ASYNC is unset' do + it 'is performed synchronously' + end +end diff --git a/spec/options/widget_lists_spec.rb b/spec/options/widget_lists_spec.rb new file mode 100644 index 0000000..3e03acf --- /dev/null +++ b/spec/options/widget_lists_spec.rb @@ -0,0 +1,72 @@ +describe 'a zle widget' do + let(:before_sourcing) { -> { session.run_command('my-widget() {}; zle -N my-widget; bindkey ^B my-widget') } } + + context 'when added to ZSH_AUTOSUGGEST_ACCEPT_WIDGETS' do + let(:options) { ['ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=(my-widget)'] } + + it 'accepts the suggestion when invoked' do + with_history('echo hello') do + session.send_string('e') + wait_for { session.content }.to eq('echo hello') + session.send_keys('C-b') + wait_for { session.content(esc_seqs: true) }.to eq('echo hello') + end + end + end + + context 'when added to ZSH_AUTOSUGGEST_CLEAR_WIDGETS' do + let(:options) { ['ZSH_AUTOSUGGEST_CLEAR_WIDGETS=(my-widget)'] } + + it 'clears the suggestion when invoked' do + with_history('echo hello') do + session.send_string('e') + wait_for { session.content }.to eq('echo hello') + session.send_keys('C-b') + wait_for { session.content }.to eq('e') + end + end + end + + context 'when added to ZSH_AUTOSUGGEST_EXECUTE_WIDGETS' do + let(:options) { ['ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=(my-widget)'] } + + it 'executes the suggestion when invoked' do + with_history('echo hello') do + session.send_string('e') + wait_for { session.content }.to eq('echo hello') + session.send_keys('C-b') + wait_for { session.content }.to end_with("\nhello") + end + end + end +end + +describe 'a zle widget that moves the cursor forward' do + let(:before_sourcing) { -> { session.run_command('my-widget() { zle forward-char }; zle -N my-widget; bindkey ^B my-widget') } } + + context 'when added to ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS' do + let(:options) { ['ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=(my-widget)'] } + + it 'accepts the suggestion as far as the cursor is moved when invoked' do + with_history('echo hello') do + session.send_string('e') + wait_for { session.content }.to start_with('echo hello') + session.send_keys('C-b') + wait_for { session.content(esc_seqs: true) }.to match(/\Aec\e\[[0-9]+mho hello/) + end + end + end +end + +describe 'a builtin zle widget' do + let(:widget) { 'beep' } + + context 'when added to ZSH_AUTOSUGGEST_IGNORE_WIDGETS' do + let(:options) { ["ZSH_AUTOSUGGEST_IGNORE_WIDGETS=(#{widget})"] } + + it 'should not be wrapped with an autosuggest widget' do + session.run_command("echo $widgets[#{widget}]") + wait_for { session.content }.to end_with("\nbuiltin") + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2b8eb13..64115d2 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,6 +2,39 @@ require 'pry' require 'rspec/wait' require 'terminal_session' +RSpec.shared_context 'terminal session' do + let(:term_opts) { {} } + let(:session) { TerminalSession.new(term_opts) } + let(:before_sourcing) { -> {} } + let(:options) { [] } + + around do |example| + before_sourcing.call + + session.run_command((['source zsh-autosuggestions.zsh'] + options).join('; ')) + session.clear_screen + + example.run + + session.destroy + end + + def with_history(*commands, &block) + session.run_command('fc -p') + + commands.each do |c| + c.respond_to?(:call) ? c.call : session.run_command(c) + end + + session.clear_screen + + yield block + + session.send_keys('C-c') + session.run_command('fc -P') + end +end + RSpec.configure do |config| config.expect_with :rspec do |expectations| expectations.include_chain_clauses_in_custom_matcher_descriptions = true @@ -12,4 +45,6 @@ RSpec.configure do |config| end config.wait_timeout = 2 + + config.include_context 'terminal session' end diff --git a/spec/special_characters_spec.rb b/spec/special_characters_spec.rb new file mode 100644 index 0000000..21f681b --- /dev/null +++ b/spec/special_characters_spec.rb @@ -0,0 +1,55 @@ +describe 'a special character in the buffer' do + it 'should be treated like any other character' do + with_history('echo "hello*"', 'echo "hello."') do + session.send_string('echo "hello*') + wait_for { session.content }.to eq('echo "hello*"') + end + + with_history('echo "hello?"', 'echo "hello."') do + session.send_string('echo "hello?') + wait_for { session.content }.to eq('echo "hello?"') + end + + with_history('echo "hello\nworld"') do + session.send_string('echo "hello\\') + wait_for { session.content }.to eq('echo "hello\nworld"') + end + + with_history('echo "\\\\"') do + session.send_string('echo "\\\\') + wait_for { session.content }.to eq('echo "\\\\"') + end + + with_history('echo ~/foo') do + session.send_string('echo ~') + wait_for { session.content }.to eq('echo ~/foo') + end + + with_history('echo "$(ls foo)"') do + session.send_string('echo "$(') + wait_for { session.content }.to eq('echo "$(ls foo)"') + end + + with_history('echo "$history[123]"') do + session.send_string('echo "$history[') + wait_for { session.content }.to eq('echo "$history[123]"') + session.send_string('123]') + wait_for { session.content }.to eq('echo "$history[123]"') + end + + with_history('echo "#yolo"') do + session.send_string('echo "#') + wait_for { session.content }.to eq('echo "#yolo"') + end + + with_history('echo "#foo"', 'echo $#abc') do + session.send_string('echo "#') + wait_for { session.content }.to eq('echo "#foo"') + end + + with_history('echo "^A"', 'echo "^B"') do + session.send_string('echo "^A') + wait_for { session.content }.to eq('echo "^A"') + end + end +end diff --git a/spec/strategies/default_spec.rb b/spec/strategies/default_spec.rb index e4bdb2f..94f3450 100644 --- a/spec/strategies/default_spec.rb +++ b/spec/strategies/default_spec.rb @@ -1,148 +1,8 @@ -describe 'default strategy' do - let(:session) { TerminalSession.new } - - before do - session.run_command('source zsh-autosuggestions.zsh') - session.run_command('fc -p') - session.clear - - wait_for { session.content }.to eq('') - end - - after do - session.destroy - end - - context 'with some simple history entries' do - before do - session.run_command('ls foo') - session.run_command('ls bar') - - session.clear - end - - it 'suggests nothing if there is no match' do - session.send_string('ls q') - wait_for { session.content }.to eq('ls q') - end - - it 'suggests the most recent matching history item' do +describe 'the default suggestion strategy' do + it 'suggests the last matching history entry' do + with_history('ls foo', 'ls bar', 'echo baz') do session.send_string('ls') wait_for { session.content }.to eq('ls bar') end end - - context 'with a multiline hist entry' do - before do - session.send_string('echo "') - session.send_keys('enter') - session.send_string('"') - session.send_keys('enter') - - session.clear - end - - it do - session.send_keys('e') - wait_for { session.content }.to eq "echo \"\n\"" - end - end - - context 'with a newline hist entry' do - before do - session.send_string('echo "\n"') - session.send_keys('enter') - - session.clear - end - - it do - session.send_keys('e') - wait_for { session.content }.to eq 'echo "\n"' - end - end - - context 'with a hist entry with a backslash' do - before do - session.run_command('echo "hello\nworld"') - session.clear - end - - it do - session.send_string('echo "hello\\') - wait_for { session.content }.to eq('echo "hello\nworld"') - end - end - - context 'with a hist entry with double backslashes' do - before do - session.run_command('echo "\\\\"') - session.clear - end - - it do - session.send_string('echo "\\\\') - wait_for { session.content }.to eq('echo "\\\\"') - end - end - - context 'with a hist entry with a tilde' do - before do - session.run_command('ls ~/foo') - session.clear - end - - it do - session.send_string('ls ~') - wait_for { session.content }.to eq('ls ~/foo') - end - - context 'with extended_glob set' do - before do - session.run_command('setopt local_options extended_glob') - session.clear - end - - it do - session.send_string('ls ~') - wait_for { session.content }.to eq('ls ~/foo') - end - end - end - - context 'with a hist entry with parentheses' do - before do - session.run_command('echo "$(ls foo)"') - session.clear - end - - it do - session.send_string('echo "$(') - wait_for { session.content }.to eq('echo "$(ls foo)"') - end - end - - context 'with a hist entry with square brackets' do - before do - session.run_command('echo "$history[123]"') - session.clear - end - - it do - session.send_string('echo "$history[') - wait_for { session.content }.to eq('echo "$history[123]"') - end - end - - context 'with a hist entry with pound sign' do - before do - session.run_command('echo "#yolo"') - session.clear - end - - it do - session.send_string('echo "#') - wait_for { session.content }.to eq('echo "#yolo"') - end - end end diff --git a/spec/strategies/match_prev_cmd_spec.rb b/spec/strategies/match_prev_cmd_spec.rb index e038fc7..21be712 100644 --- a/spec/strategies/match_prev_cmd_spec.rb +++ b/spec/strategies/match_prev_cmd_spec.rb @@ -1,60 +1,17 @@ -describe 'match_prev_cmd strategy' do - let(:session) { TerminalSession.new } - - before do - session.run_command('source zsh-autosuggestions.zsh') - session.run_command('ZSH_AUTOSUGGEST_STRATEGY=match_prev_cmd') - session.run_command('fc -p') - session.clear - end - - after do - session.destroy - end - - context 'with some history entries' do - before do - session.run_command('echo what') - session.run_command('ls foo') - session.run_command('echo what') - session.run_command('ls bar') - session.run_command('ls baz') - - session.clear - end - - it 'suggests nothing if prefix does not match' do - session.send_string('ls q') - wait_for { session.content }.to eq('ls q') - end - - it 'suggests the most recent matching history item' do - session.send_string('ls') - wait_for { session.content }.to eq('ls baz') - end - - it 'suggests the most recent after the previous command' do - session.run_command('echo what') - session.clear +describe 'the match_prev_cmd strategy' do + let(:options) { ['ZSH_AUTOSUGGEST_STRATEGY=match_prev_cmd'] } + it 'suggests the last matching history entry after the previous command' do + with_history( + 'echo what', + 'ls foo', + 'echo what', + 'ls bar', + 'ls baz', + 'echo what' + ) do session.send_string('ls') wait_for { session.content }.to eq('ls bar') end end - - context 'with a multiline hist entry' do - before do - session.send_string('echo "') - session.send_keys('enter') - session.send_string('"') - session.send_keys('enter') - - session.clear - end - - it do - session.send_keys('e') - wait_for { session.content }.to eq "echo \"\n\"" - end - end end diff --git a/spec/terminal_session.rb b/spec/terminal_session.rb index 8eeac32..cf4ee84 100644 --- a/spec/terminal_session.rb +++ b/spec/terminal_session.rb @@ -41,7 +41,7 @@ class TerminalSession tmux_command(cmd).strip end - def clear + def clear_screen send_keys('C-l') sleep(0.1) until content == '' @@ -68,7 +68,7 @@ class TerminalSession def tmux_command(cmd) out = `tmux -u -L #{tmux_socket_name} #{cmd}` - raise('tmux error') unless $?.success? + raise("tmux error running: '#{cmd}'") unless $?.success? out end diff --git a/spec/widgets/accept_spec.rb b/spec/widgets/accept_spec.rb deleted file mode 100644 index 42e1e99..0000000 --- a/spec/widgets/accept_spec.rb +++ /dev/null @@ -1,162 +0,0 @@ -describe 'accept widget' do - let(:session) { TerminalSession.new } - - before do - session.run_command('source zsh-autosuggestions.zsh') - session.run_command(select_keymap) - session.run_command('fc -p') - session.run_command('echo hello world') - - session.clear - - session.send_string('echo') - wait_for { session.content }.to start_with('echo') - end - - after do - session.destroy - end - - describe 'emacs keymap' do - let(:select_keymap) { 'bindkey -e' } - - context 'forward-char' do - subject { session.send_keys('right') } - - context 'when the cursor is at the end of the buffer' do - it 'accepts the suggestion' do - expect { subject }.to change { session.content(esc_seqs: true) }.to('echo hello world') - end - - it 'moves the cursor to the end of the buffer' do - expect { subject }.to change { session.cursor }.from([4, 0]).to([16, 0]) - end - end - - context 'when the cursor is not at the end of the buffer' do - before { 2.times { session.send_keys('left') } } - - it 'does not accept the suggestion' do - expect { subject }.not_to change { session.content(esc_seqs: true) } - end - - it 'moves the cursor forward one character' do - expect { subject }.to change { session.cursor }.from([2, 0]).to([3, 0]) - end - end - end - - context 'end-of-line' do - subject { session.send_keys('C-e') } - - context 'when the cursor is at the end of the buffer' do - it 'accepts the suggestion' do - expect { subject }.to change { session.content(esc_seqs: true) }.to('echo hello world') - end - - it 'moves the cursor to the end of the buffer' do - expect { subject }.to change { session.cursor }.from([4, 0]).to([16, 0]) - end - end - - context 'when the cursor is not at the end of the buffer' do - before { 2.times { session.send_keys('left') } } - - it 'does not accept the suggestion' do - expect { subject }.not_to change { session.content(esc_seqs: true) } - end - - it 'moves the cursor to the end of the line' do - expect { subject }.to change { session.cursor }.from([2, 0]).to([4, 0]) - end - end - end - end - - describe 'vi keymap' do - let(:select_keymap) { 'bindkey -v' } - - before { session.send_keys('escape') } - - context 'vi-forward-char' do - subject { session.send_keys('l') } - - context 'when the cursor is at the end of the buffer' do - it 'accepts the suggestion' do - expect { subject }.to change { session.content(esc_seqs: true) }.to('echo hello world') - end - - it 'moves the cursor to the end of the buffer' do - wait_for { session.cursor }.to eq([3, 0]) - expect { subject }.to change { session.cursor }.from([3, 0]).to([15, 0]) - end - end - - context 'when the cursor is not at the end of the buffer' do - before { 2.times { session.send_keys('h') } } - - it 'does not accept the suggestion' do - expect { subject }.not_to change { session.content(esc_seqs: true) } - end - - it 'moves the cursor forward one character' do - expect { subject }.to change { session.cursor }.from([1, 0]).to([2, 0]) - end - end - end - - context 'vi-end-of-line' do - subject { session.send_keys('$') } - - context 'when the cursor is at the end of the buffer' do - it 'accepts the suggestion' do - expect { subject }.to change { session.content(esc_seqs: true) }.to('echo hello world') - end - - it 'moves the cursor to the end of the buffer' do - wait_for { session.cursor }.to eq([3, 0]) - expect { subject }.to change { session.cursor }.from([3, 0]).to([15, 0]) - end - end - - context 'when the cursor is not at the end of the buffer' do - before { 2.times { session.send_keys('h') } } - - it 'does not accept the suggestion' do - expect { subject }.not_to change { session.content(esc_seqs: true) } - end - - it 'moves the cursor to the end of the line' do - expect { subject }.to change { session.cursor }.from([1, 0]).to([3, 0]) - end - end - end - - context 'vi-add-eol' do - subject { session.send_keys('A') } - - context 'when the cursor is at the end of the buffer' do - it 'accepts the suggestion' do - expect { subject }.to change { session.content(esc_seqs: true) }.to('echo hello world') - end - - it 'moves the cursor to the end of the buffer' do - wait_for { session.cursor }.to eq([3, 0]) - expect { subject }.to change { session.cursor }.from([3, 0]).to([16, 0]) - end - end - - context 'when the cursor is not at the end of the buffer' do - before { 2.times { session.send_keys('h') } } - - it 'does not accept the suggestion' do - expect { subject }.not_to change { session.content(esc_seqs: true) } - end - - it 'moves the cursor to the end of the line' do - expect { subject }.to change { session.cursor }.from([1, 0]).to([4, 0]) - end - end - end - end -end diff --git a/src/async.zsh b/src/async.zsh index 40de015..da70b80 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -3,10 +3,20 @@ # Async # #--------------------------------------------------------------------# -# Pty is spawned running this function +# Zpty process is spawned running this function _zsh_autosuggest_async_server() { emulate -R zsh + # There is a bug in zpty module (fixed in zsh/master) by which a + # zpty that exits will kill all zpty processes that were forked + # before it. Here we set up a zsh exit hook to SIGKILL the zpty + # process immediately, before it has a chance to kill any other + # zpty processes. + zshexit() { + kill -KILL $$ + sleep 1 # Block for long enough for the signal to come through + } + # Output only newlines (not carriage return + newline) stty -onlcr @@ -29,7 +39,7 @@ _zsh_autosuggest_async_server() { } _zsh_autosuggest_async_request() { - # Send the query to the pty to fetch a suggestion + # Write the query to the zpty process to fetch a suggestion zpty -w -n $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME "${1}"$'\0' } @@ -37,10 +47,12 @@ _zsh_autosuggest_async_request() { # First arg will be fd ready for reading # Second arg will be passed in case of error _zsh_autosuggest_async_response() { + setopt LOCAL_OPTIONS EXTENDED_GLOB + local suggestion zpty -rt $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME suggestion '*'$'\0' 2>/dev/null - zle autosuggest-suggest "${suggestion%$'\0'}" + zle autosuggest-suggest "${suggestion%%$'\0'##}" } _zsh_autosuggest_async_pty_create() { @@ -54,7 +66,7 @@ _zsh_autosuggest_async_pty_create() { exec {zptyfd}>&- # Close it so it's free to be used by zpty. fi - # Start a new pty running the server function + # 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" # Store the fd so we can remove the handler later @@ -64,7 +76,7 @@ _zsh_autosuggest_async_pty_create() { _ZSH_AUTOSUGGEST_PTY_FD=$zptyfd fi - # Set up input handler from the pty + # Set up input handler from the zpty zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_response } @@ -73,7 +85,7 @@ _zsh_autosuggest_async_pty_destroy() { # Remove the input handler zle -F $_ZSH_AUTOSUGGEST_PTY_FD - # Destroy the pty + # Destroy the zpty zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null fi } diff --git a/src/strategies/default.zsh b/src/strategies/default.zsh index bbd5e59..60c0494 100644 --- a/src/strategies/default.zsh +++ b/src/strategies/default.zsh @@ -7,14 +7,19 @@ # _zsh_autosuggest_strategy_default() { - setopt localoptions EXTENDED_GLOB + # Reset options to defaults and enable LOCAL_OPTIONS + emulate -L zsh - local prefix="${1//(#m)[\\()\[\]|*?~]/\\$MATCH}" + # Enable globbing flags so that we can use (#m) + setopt EXTENDED_GLOB - # Get the keys of the history items that match - local -a histkeys - histkeys=(${(k)history[(r)$prefix*]}) + # Escape backslashes and all of the glob operators so we can use + # this string as a pattern to search the $history associative array. + # - (#m) globbing flag enables setting references for match data + local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" + + # Get the history items that match + # - (r) subscript flag makes the pattern match on values + suggestion="${history[(r)$prefix*]}" - # Give back the value of the first key - suggestion="${history[$histkeys[1]]}" } diff --git a/src/widgets.zsh b/src/widgets.zsh index bbf9c3f..3031066 100644 --- a/src/widgets.zsh +++ b/src/widgets.zsh @@ -45,7 +45,7 @@ _zsh_autosuggest_modify() { # 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 -lt "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]; then + if [ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" -o $#BUFFER -le "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]; then _zsh_autosuggest_fetch fi fi diff --git a/test/bind_test.zsh b/test/bind_test.zsh deleted file mode 100644 index f73ea7f..0000000 --- a/test/bind_test.zsh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env zsh - -source "${0:a:h}/test_helper.zsh" - -oneTimeSetUp() { - source_autosuggestions -} - -testInvokeOriginalWidgetDefined() { - stub_and_eval \ - zle \ - 'return 1' - - _zsh_autosuggest_invoke_original_widget 'self-insert' - - assertEquals \ - '1' \ - "$?" - - assertTrue \ - 'zle was not invoked' \ - 'stub_called zle' - - restore zle -} - -testInvokeOriginalWidgetUndefined() { - stub_and_eval \ - zle \ - 'return 1' - - _zsh_autosuggest_invoke_original_widget 'some-undefined-widget' - - assertEquals \ - '0' \ - "$?" - - assertFalse \ - 'zle was invoked' \ - 'stub_called zle' - - restore zle -} - -run_tests "$0" diff --git a/test/highlight_test.zsh b/test/highlight_test.zsh deleted file mode 100644 index 7268af8..0000000 --- a/test/highlight_test.zsh +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env zsh - -source "${0:a:h}/test_helper.zsh" - -oneTimeSetUp() { - source_autosuggestions -} - -testHighlightDefaultStyle() { - assertEquals \ - 'fg=8' \ - "$ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE" -} - -testHighlightApplyWithSuggestion() { - local orig_style=ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE - ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=4' - - BUFFER='ec' - POSTDISPLAY='ho hello' - region_highlight=('0 2 fg=1') - - _zsh_autosuggest_highlight_apply - - assertEquals \ - 'highlight did not use correct style' \ - "0 2 fg=1 2 10 $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE" \ - "$region_highlight" - - assertEquals \ - 'higlight was not saved to be removed later' \ - "2 10 $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE" \ - "$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT" - - ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE=orig_style -} - -testHighlightApplyWithoutSuggestion() { - BUFFER='echo hello' - POSTDISPLAY='' - region_highlight=('0 4 fg=1') - - _zsh_autosuggest_highlight_apply - - assertEquals \ - 'region_highlight was modified' \ - '0 4 fg=1' \ - "$region_highlight" - - assertNull \ - 'last highlight region was not cleared' \ - "$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT" -} - -testHighlightReset() { - BUFFER='ec' - POSTDISPLAY='ho hello' - region_highlight=('0 1 fg=1' '2 10 fg=8' '1 2 fg=1') - _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT='2 10 fg=8' - - _zsh_autosuggest_highlight_reset - - assertEquals \ - 'last highlight region was not removed' \ - '0 1 fg=1 1 2 fg=1' \ - "$region_highlight" - - assertNull \ - 'last highlight variable was not cleared' \ - "$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT" -} - -run_tests "$0" diff --git a/test/suggestion_test.zsh b/test/suggestion_test.zsh deleted file mode 100644 index fc6330d..0000000 --- a/test/suggestion_test.zsh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env zsh - -source "${0:a:h}/test_helper.zsh" - -oneTimeSetUp() { - source_autosuggestions -} - -testEscapeCommand() { - assertEquals \ - 'Did not escape single backslash' \ - '\\' \ - "$(_zsh_autosuggest_escape_command '\')" - - assertEquals \ - 'Did not escape two backslashes' \ - '\\\\' \ - "$(_zsh_autosuggest_escape_command '\\')" - - assertEquals \ - 'Did not escape parentheses' \ - '\(\)' \ - "$(_zsh_autosuggest_escape_command '()')" - - assertEquals \ - 'Did not escape square brackets' \ - '\[\]' \ - "$(_zsh_autosuggest_escape_command '[]')" - - assertEquals \ - 'Did not escape pipe' \ - '\|' \ - "$(_zsh_autosuggest_escape_command '|')" - - assertEquals \ - 'Did not escape star' \ - '\*' \ - "$(_zsh_autosuggest_escape_command '*')" - - assertEquals \ - 'Did not escape question mark' \ - '\?' \ - "$(_zsh_autosuggest_escape_command '?')" -} - -run_tests "$0" diff --git a/test/test_helper.zsh b/test/test_helper.zsh deleted file mode 100644 index 7e7dbc0..0000000 --- a/test/test_helper.zsh +++ /dev/null @@ -1,60 +0,0 @@ -DIR="${0:a:h}" -ROOT_DIR="$DIR/.." -VENDOR_DIR="$ROOT_DIR/vendor" - -# Use stub.sh for stubbing/mocking -source "$VENDOR_DIR/stub.sh/stub.sh" - -#--------------------------------------------------------------------# -# Helper Functions # -#--------------------------------------------------------------------# - -# Source the autosuggestions plugin file -source_autosuggestions() { - source "$ROOT_DIR/zsh-autosuggestions.zsh" -} - -# Set history list from stdin -set_history() { - # Make a tmp file in shunit's tmp dir - local tmp=$(mktemp "$SHUNIT_TMPDIR/hist.XXX") - - # Write from stdin to the tmp file - > "$tmp" - - # Write an extra line to simulate history active mode - # See https://github.com/zsh-users/zsh/blob/ca3bc0d95d7deab4f5381f12b15047de748c0814/Src/hist.c#L69-L82 - echo >> "$tmp" - - # Clear history and re-read from the tmp file - fc -P; fc -p; fc -R "$tmp" - - rm "$tmp" -} - -# Should be called at the bottom of every test suite file -# Pass in the name of the test script ($0) for shunit -run_tests() { - local test_script="$1" - shift - - # Required for shunit to work with zsh - setopt localoptions shwordsplit - SHUNIT_PARENT="$test_script" - - source "$VENDOR_DIR/shunit2/2.1.6/src/shunit2" -} - -#--------------------------------------------------------------------# -# Custom Assertions # -#--------------------------------------------------------------------# - -assertSuggestion() { - local prefix="$1" - local expected_suggestion="$2" - - assertEquals \ - "Did not get correct suggestion for prefix:<$prefix> using strategy <$ZSH_AUTOSUGGEST_STRATEGY>" \ - "$expected_suggestion" \ - "$(_zsh_autosuggest_suggestion "$prefix")" -} diff --git a/test/widgets/accept_test.zsh b/test/widgets/accept_test.zsh deleted file mode 100644 index f126091..0000000 --- a/test/widgets/accept_test.zsh +++ /dev/null @@ -1,161 +0,0 @@ -#!/usr/bin/env zsh - -source "${0:a:h}/../test_helper.zsh" - -oneTimeSetUp() { - source_autosuggestions -} - -setUp() { - BUFFER='' - POSTDISPLAY='' - CURSOR=0 - KEYMAP='main' -} - -tearDown() { - restore _zsh_autosuggest_invoke_original_widget -} - -testCursorAtEnd() { - BUFFER='echo' - POSTDISPLAY=' hello' - CURSOR=4 - - stub _zsh_autosuggest_invoke_original_widget - - _zsh_autosuggest_accept 'original-widget' - - assertTrue \ - 'original widget not invoked' \ - 'stub_called _zsh_autosuggest_invoke_original_widget' - - assertEquals \ - 'BUFFER was not modified' \ - 'echo hello' \ - "$BUFFER" - - assertEquals \ - 'POSTDISPLAY was not cleared' \ - '' \ - "$POSTDISPLAY" -} - -testCursorNotAtEnd() { - BUFFER='echo' - POSTDISPLAY=' hello' - CURSOR=2 - - stub _zsh_autosuggest_invoke_original_widget - - _zsh_autosuggest_accept 'original-widget' - - assertTrue \ - 'original widget not invoked' \ - 'stub_called _zsh_autosuggest_invoke_original_widget' - - assertEquals \ - 'BUFFER was modified' \ - 'echo' \ - "$BUFFER" - - assertEquals \ - 'POSTDISPLAY was modified' \ - ' hello' \ - "$POSTDISPLAY" -} - -testViCursorAtEnd() { - BUFFER='echo' - POSTDISPLAY=' hello' - CURSOR=3 - KEYMAP='vicmd' - - stub _zsh_autosuggest_invoke_original_widget - - _zsh_autosuggest_accept 'original-widget' - - assertTrue \ - 'original widget not invoked' \ - 'stub_called _zsh_autosuggest_invoke_original_widget' - - assertEquals \ - 'BUFFER was not modified' \ - 'echo hello' \ - "$BUFFER" - - assertEquals \ - 'POSTDISPLAY was not cleared' \ - '' \ - "$POSTDISPLAY" -} - -testViCursorNotAtEnd() { - BUFFER='echo' - POSTDISPLAY=' hello' - CURSOR=2 - KEYMAP='vicmd' - - stub _zsh_autosuggest_invoke_original_widget - - _zsh_autosuggest_accept 'original-widget' - - assertTrue \ - 'original widget not invoked' \ - 'stub_called _zsh_autosuggest_invoke_original_widget' - - assertEquals \ - 'BUFFER was modified' \ - 'echo' \ - "$BUFFER" - - assertEquals \ - 'POSTDISPLAY was modified' \ - ' hello' \ - "$POSTDISPLAY" -} - -testRetval() { - stub_and_eval \ - _zsh_autosuggest_invoke_original_widget \ - 'return 1' - - _zsh_autosuggest_widget_accept 'original-widget' - - assertEquals \ - 'Did not return correct value from original widget' \ - '1' \ - "$?" -} - -testWidget() { - stub _zsh_autosuggest_highlight_reset - stub _zsh_autosuggest_accept - stub _zsh_autosuggest_highlight_apply - - # Call the function pointed to by the widget since we can't call - # the widget itself when zle is not active - ${widgets[autosuggest-accept]#*:} 'original-widget' - - assertTrue \ - 'autosuggest-accept widget does not exist' \ - 'zle -l autosuggest-accept' - - assertTrue \ - 'highlight_reset was not called' \ - 'stub_called _zsh_autosuggest_highlight_reset' - - assertTrue \ - 'widget function was not called' \ - 'stub_called _zsh_autosuggest_accept' - - assertTrue \ - 'highlight_apply was not called' \ - 'stub_called _zsh_autosuggest_highlight_apply' - - restore _zsh_autosuggest_highlight_reset - restore _zsh_autosuggest_accept - restore _zsh_autosuggest_highlight_apply -} - -run_tests "$0" diff --git a/test/widgets/clear_test.zsh b/test/widgets/clear_test.zsh deleted file mode 100644 index f0500c5..0000000 --- a/test/widgets/clear_test.zsh +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env zsh - -source "${0:a:h}/../test_helper.zsh" - -oneTimeSetUp() { - source_autosuggestions -} - -setUp() { - BUFFER='' - POSTDISPLAY='' -} - -tearDown() { - restore _zsh_autosuggest_invoke_original_widget -} - -testClear() { - BUFFER='ec' - POSTDISPLAY='ho hello' - - _zsh_autosuggest_clear 'original-widget' - - assertEquals \ - 'BUFFER was modified' \ - 'ec' \ - "$BUFFER" - - assertNull \ - 'POSTDISPLAY was not cleared' \ - "$POSTDISPLAY" -} - -testRetval() { - stub_and_eval \ - _zsh_autosuggest_invoke_original_widget \ - 'return 1' - - _zsh_autosuggest_widget_clear 'original-widget' - - assertEquals \ - 'Did not return correct value from original widget' \ - '1' \ - "$?" -} - -testWidget() { - stub _zsh_autosuggest_highlight_reset - stub _zsh_autosuggest_clear - stub _zsh_autosuggest_highlight_apply - - # Call the function pointed to by the widget since we can't call - # the widget itself when zle is not active - ${widgets[autosuggest-clear]#*:} 'original-widget' - - assertTrue \ - 'autosuggest-clear widget does not exist' \ - 'zle -l autosuggest-clear' - - assertTrue \ - 'highlight_reset was not called' \ - 'stub_called _zsh_autosuggest_highlight_reset' - - assertTrue \ - 'widget function was not called' \ - 'stub_called _zsh_autosuggest_clear' - - assertTrue \ - 'highlight_apply was not called' \ - 'stub_called _zsh_autosuggest_highlight_apply' - - restore _zsh_autosuggest_highlight_reset - restore _zsh_autosuggest_clear - restore _zsh_autosuggest_highlight_apply -} - -run_tests "$0" diff --git a/test/widgets/execute_test.zsh b/test/widgets/execute_test.zsh deleted file mode 100644 index cb346db..0000000 --- a/test/widgets/execute_test.zsh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env zsh - -source "${0:a:h}/../test_helper.zsh" - -oneTimeSetUp() { - source_autosuggestions -} - -tearDown() { - restore _zsh_autosuggest_invoke_original_widget -} - -testRetval() { - stub_and_eval \ - _zsh_autosuggest_invoke_original_widget \ - 'return 1' - - _zsh_autosuggest_widget_execute 'original-widget' - - assertEquals \ - 'Did not return correct value from original widget' \ - '1' \ - "$?" -} - -run_tests "$0" diff --git a/test/widgets/modify_test.zsh b/test/widgets/modify_test.zsh deleted file mode 100644 index 7ed6346..0000000 --- a/test/widgets/modify_test.zsh +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env zsh - -source "${0:a:h}/../test_helper.zsh" - -oneTimeSetUp() { - source_autosuggestions -} - -setUp() { - BUFFER='' - POSTDISPLAY='' - ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE='' -} - -tearDown() { - restore _zsh_autosuggest_invoke_original_widget - restore _zsh_autosuggest_suggestion -} - -testModify() { - stub_and_eval \ - _zsh_autosuggest_invoke_original_widget \ - 'BUFFER+="e"' - - stub_and_echo \ - _zsh_autosuggest_suggestion \ - 'echo hello' - - _zsh_autosuggest_modify 'original-widget' - - assertTrue \ - 'original widget not invoked' \ - 'stub_called _zsh_autosuggest_invoke_original_widget' - - assertEquals \ - 'BUFFER was not modified' \ - 'e' \ - "$BUFFER" - - assertEquals \ - 'POSTDISPLAY does not contain suggestion' \ - 'cho hello' \ - "$POSTDISPLAY" -} - -testModifyBufferTooLarge() { - - ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE='20' - - stub_and_eval \ - _zsh_autosuggest_invoke_original_widget \ - 'BUFFER+="012345678901234567890"' - - stub_and_echo \ - _zsh_autosuggest_suggestion \ - '012345678901234567890123456789' - - _zsh_autosuggest_modify 'original-widget' - - assertTrue \ - 'original widget not invoked' \ - 'stub_called _zsh_autosuggest_invoke_original_widget' - - assertEquals \ - 'BUFFER was not modified' \ - '012345678901234567890' \ - "$BUFFER" - - assertEquals \ - 'POSTDISPLAY does not contain suggestion' \ - '' \ - "$POSTDISPLAY" -} - -testRetval() { - stub_and_eval \ - _zsh_autosuggest_invoke_original_widget \ - 'return 1' - - _zsh_autosuggest_widget_modify 'original-widget' - - assertEquals \ - 'Did not return correct value from original widget' \ - '1' \ - "$?" -} - -run_tests "$0" diff --git a/test/widgets/partial_accept_test.zsh b/test/widgets/partial_accept_test.zsh deleted file mode 100644 index 60c78a6..0000000 --- a/test/widgets/partial_accept_test.zsh +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env zsh - -source "${0:a:h}/../test_helper.zsh" - -oneTimeSetUp() { - source_autosuggestions -} - -setUp() { - BUFFER='' - POSTDISPLAY='' - CURSOR=0 -} - -tearDown() { - restore _zsh_autosuggest_invoke_original_widget -} - -testCursorMovesOutOfBuffer() { - BUFFER='ec' - POSTDISPLAY='ho hello' - CURSOR=1 - - stub_and_eval \ - _zsh_autosuggest_invoke_original_widget \ - 'CURSOR=5; LBUFFER="echo "; RBUFFER="hello"' - - _zsh_autosuggest_partial_accept 'original-widget' - - assertTrue \ - 'original widget not invoked' \ - 'stub_called _zsh_autosuggest_invoke_original_widget' - - assertEquals \ - 'BUFFER was not modified correctly' \ - 'echo ' \ - "$BUFFER" - - assertEquals \ - 'POSTDISPLAY was not modified correctly' \ - 'hello' \ - "$POSTDISPLAY" -} - -testCursorStaysInBuffer() { - BUFFER='echo hello' - POSTDISPLAY=' world' - CURSOR=1 - - stub_and_eval \ - _zsh_autosuggest_invoke_original_widget \ - 'CURSOR=5; LBUFFER="echo "; RBUFFER="hello"' - - _zsh_autosuggest_partial_accept 'original-widget' - - assertTrue \ - 'original widget not invoked' \ - 'stub_called _zsh_autosuggest_invoke_original_widget' - - assertEquals \ - 'BUFFER was modified' \ - 'echo hello' \ - "$BUFFER" - - assertEquals \ - 'POSTDISPLAY was modified' \ - ' world' \ - "$POSTDISPLAY" -} - -testRetval() { - stub_and_eval \ - _zsh_autosuggest_invoke_original_widget \ - 'return 1' - - _zsh_autosuggest_widget_partial_accept 'original-widget' - - assertEquals \ - 'Did not return correct value from original widget' \ - '1' \ - "$?" -} - -run_tests "$0" diff --git a/vendor/shunit2 b/vendor/shunit2 deleted file mode 160000 index 46973db..0000000 --- a/vendor/shunit2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 46973db9df87bd5fdadea74cb472a99f212a0d3a diff --git a/vendor/stub.sh b/vendor/stub.sh deleted file mode 160000 index bd6f3c4..0000000 --- a/vendor/stub.sh +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bd6f3c4666cd2a702e388e09d77b8543a1f6b672 diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index d0003e2..d21bfd5 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -2,7 +2,7 @@ # https://github.com/zsh-users/zsh-autosuggestions # v0.3.3 # Copyright (c) 2013 Thiago de Arruda -# Copyright (c) 2016 Eric Freese +# Copyright (c) 2016-2017 Eric Freese # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation @@ -332,7 +332,7 @@ _zsh_autosuggest_modify() { # 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 -lt "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]; then + if [ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" -o $#BUFFER -le "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]; then _zsh_autosuggest_fetch fi fi @@ -460,16 +460,21 @@ zle -N autosuggest-execute _zsh_autosuggest_widget_execute # _zsh_autosuggest_strategy_default() { - setopt localoptions EXTENDED_GLOB + # Reset options to defaults and enable LOCAL_OPTIONS + emulate -L zsh - local prefix="${1//(#m)[\\()\[\]|*?~]/\\$MATCH}" + # Enable globbing flags so that we can use (#m) + setopt EXTENDED_GLOB - # Get the keys of the history items that match - local -a histkeys - histkeys=(${(k)history[(r)$prefix*]}) + # Escape backslashes and all of the glob operators so we can use + # this string as a pattern to search the $history associative array. + # - (#m) globbing flag enables setting references for match data + local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" + + # Get the history items that match + # - (r) subscript flag makes the pattern match on values + suggestion="${history[(r)$prefix*]}" - # Give back the value of the first key - suggestion="${history[$histkeys[1]]}" } #--------------------------------------------------------------------# @@ -528,10 +533,20 @@ _zsh_autosuggest_strategy_match_prev_cmd() { # Async # #--------------------------------------------------------------------# -# Pty is spawned running this function +# Zpty process is spawned running this function _zsh_autosuggest_async_server() { emulate -R zsh + # There is a bug in zpty module (fixed in zsh/master) by which a + # zpty that exits will kill all zpty processes that were forked + # before it. Here we set up a zsh exit hook to SIGKILL the zpty + # process immediately, before it has a chance to kill any other + # zpty processes. + zshexit() { + kill -KILL $$ + sleep 1 # Block for long enough for the signal to come through + } + # Output only newlines (not carriage return + newline) stty -onlcr @@ -554,7 +569,7 @@ _zsh_autosuggest_async_server() { } _zsh_autosuggest_async_request() { - # Send the query to the pty to fetch a suggestion + # Write the query to the zpty process to fetch a suggestion zpty -w -n $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME "${1}"$'\0' } @@ -562,10 +577,12 @@ _zsh_autosuggest_async_request() { # First arg will be fd ready for reading # Second arg will be passed in case of error _zsh_autosuggest_async_response() { + setopt LOCAL_OPTIONS EXTENDED_GLOB + local suggestion zpty -rt $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME suggestion '*'$'\0' 2>/dev/null - zle autosuggest-suggest "${suggestion%$'\0'}" + zle autosuggest-suggest "${suggestion%%$'\0'##}" } _zsh_autosuggest_async_pty_create() { @@ -579,7 +596,7 @@ _zsh_autosuggest_async_pty_create() { exec {zptyfd}>&- # Close it so it's free to be used by zpty. fi - # Start a new pty running the server function + # 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" # Store the fd so we can remove the handler later @@ -589,7 +606,7 @@ _zsh_autosuggest_async_pty_create() { _ZSH_AUTOSUGGEST_PTY_FD=$zptyfd fi - # Set up input handler from the pty + # Set up input handler from the zpty zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_response } @@ -598,7 +615,7 @@ _zsh_autosuggest_async_pty_destroy() { # Remove the input handler zle -F $_ZSH_AUTOSUGGEST_PTY_FD - # Destroy the pty + # Destroy the zpty zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null fi } From 9feac573c932a2598bd406b54aed633d199282fe Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Thu, 16 Feb 2017 19:27:32 -0700 Subject: [PATCH 40/70] Do not show any error output from async zpty server process --- src/async.zsh | 4 ++++ zsh-autosuggestions.zsh | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/async.zsh b/src/async.zsh index da70b80..60055b9 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -20,6 +20,10 @@ _zsh_autosuggest_async_server() { # Output only newlines (not carriage return + newline) stty -onlcr + + # Silence any error messages + exec 2>/dev/null + local strategy=$1 local last_pid diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index d21bfd5..1492f19 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -550,6 +550,10 @@ _zsh_autosuggest_async_server() { # Output only newlines (not carriage return + newline) stty -onlcr + + # Silence any error messages + exec 2>/dev/null + local strategy=$1 local last_pid From 06fca77ffb7d85c8b83d5daa591e3f30e646f1d5 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Thu, 16 Feb 2017 20:12:04 -0700 Subject: [PATCH 41/70] Readme updates for v0.4.0 --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3a5c3f3..85cac70 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,10 @@ Widgets that modify the buffer and are not found in any of these arrays will fet Set `ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE` to an integer value to disable autosuggestion for large buffers. The default is unset, which means that autosuggestion will be tried for any buffer size. Recommended value is 20. This can be useful when pasting large amount of text in the terminal, to avoid triggering autosuggestion for too long strings. +### Disable Asynchronous Mode + +As of `v0.4.0`, suggestions are fetched asynchronously using the `zsh/zpty` module. To disable this behavior and fall back to fetching suggestions synchronously, unset the `ZSH_AUTOSUGGEST_USE_ASYNC` variable. + ### Key Bindings @@ -154,9 +158,9 @@ Pull requests are welcome! If you send a pull request, please: ### Testing -Testing is performed with [`shunit2`](https://github.com/kward/shunit2) (v2.1.6). Documentation can be found [here](http://shunit2.googlecode.com/svn/trunk/source/2.1/doc/shunit2.html). +Tests are written in ruby using the [`rspec`](http://rspec.info/) framework. They use [`tmux`](https://tmux.github.io/) to drive a pseudoterminal, sending simulated keystrokes and making assertions on the terminal content. -The test script lives at `script/test_runner.zsh`. To run the tests, run `make test`. +Test files live in `spec/`. To run the tests, run `make test`. To run a specific test, run `TESTS=spec/some_spec.rb make test`. You can also specify a `zsh` binary to use by setting the `TEST_ZSH_BIN` environment variable (ex: `TEST_ZSH_BIN=/bin/zsh make test`). ## License From c959408305ff34b0d40f254ace5adb7e7e64ef3e Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 17 Feb 2017 15:33:09 -0700 Subject: [PATCH 42/70] Only wait a max of 2 seconds for content to match after clearing screen --- spec/terminal_session.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/spec/terminal_session.rb b/spec/terminal_session.rb index cf4ee84..275a90b 100644 --- a/spec/terminal_session.rb +++ b/spec/terminal_session.rb @@ -43,7 +43,12 @@ class TerminalSession def clear_screen send_keys('C-l') - sleep(0.1) until content == '' + + i = 0 + until content == opts[:prompt] || i > 20 do + sleep(0.1) + i = i + 1 + end self end From c4bfd8e2c6cf20a5b074b2fcc708d4a7bec8b9f2 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 17 Feb 2017 15:51:50 -0700 Subject: [PATCH 43/70] Need to prevent zpty feature detection from HUPing existing zptys --- spec/integrations/client_zpty_spec.rb | 13 ++++++------- src/features.zsh | 2 +- zsh-autosuggestions.zsh | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/spec/integrations/client_zpty_spec.rb b/spec/integrations/client_zpty_spec.rb index f294a1c..fb7bbeb 100644 --- a/spec/integrations/client_zpty_spec.rb +++ b/spec/integrations/client_zpty_spec.rb @@ -1,11 +1,10 @@ describe 'a running zpty command' do - it 'is not affected by running zsh-autosuggestions' do - session.run_command('zmodload zsh/zpty') - session.run_command('zpty -b kitty cat') - session.run_command('zpty -w kitty cat') - sleep 1 - session.run_command('zpty -r kitty') + let(:before_sourcing) { -> { session.run_command('zmodload zsh/zpty && zpty -b kitty cat') } } - wait_for(session.content).to end_with("\ncat") + it 'is not affected by running zsh-autosuggestions' do + sleep 1 # Give a little time for precmd hooks to run + session.run_command('zpty -t kitty; echo $?') + + wait_for(session.content).to end_with("\n0") end end diff --git a/src/features.zsh b/src/features.zsh index 9198085..35dfcc3 100644 --- a/src/features.zsh +++ b/src/features.zsh @@ -7,7 +7,7 @@ _zsh_autosuggest_feature_detect() { typeset -g _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD typeset -h REPLY - zpty $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME : + zpty $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME '{ zshexit() { kill -KILL $$; sleep 1 } }' if (( REPLY )); then _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=1 diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 1492f19..f33c3d2 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -122,7 +122,7 @@ _zsh_autosuggest_feature_detect() { typeset -g _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD typeset -h REPLY - zpty $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME : + zpty $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME '{ zshexit() { kill -KILL $$; sleep 1 } }' if (( REPLY )); then _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=1 From 938144530cd9cafa9a334d1e0b42458f3a3250e2 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 17 Feb 2017 16:01:07 -0700 Subject: [PATCH 44/70] Fix tests --- spec/terminal_session.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/terminal_session.rb b/spec/terminal_session.rb index 275a90b..a089d27 100644 --- a/spec/terminal_session.rb +++ b/spec/terminal_session.rb @@ -12,7 +12,9 @@ class TerminalSession zsh_bin: ZSH_BIN }.merge(opts) - cmd="PS1=#{opts[:prompt]} TERM=#{opts[:term]} #{ZSH_BIN} -f" + @opts = opts + + cmd="PS1=\"#{opts[:prompt]}\" TERM=#{opts[:term]} #{ZSH_BIN} -f" tmux_command("new-session -d -x #{opts[:width]} -y #{opts[:height]} '#{cmd}'") end @@ -66,6 +68,8 @@ class TerminalSession private + attr_reader :opts + def tmux_socket_name @tmux_socket_name ||= SecureRandom.hex(6) end From 23ef16c297fd17eec9688ff2c3d11bcdab416671 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 17 Feb 2017 18:26:34 -0700 Subject: [PATCH 45/70] Do not show suggestions if the buffer is empty --- src/widgets.zsh | 2 +- zsh-autosuggestions.zsh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widgets.zsh b/src/widgets.zsh index 3031066..a0a59d5 100644 --- a/src/widgets.zsh +++ b/src/widgets.zsh @@ -68,7 +68,7 @@ _zsh_autosuggest_fetch() { _zsh_autosuggest_suggest() { local suggestion="$1" - if [ -n "$suggestion" ]; then + if [ -n "$suggestion" ] && [ $#BUFFER -gt 0 ]; then POSTDISPLAY="${suggestion#$BUFFER}" else unset POSTDISPLAY diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index f33c3d2..ed631c3 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -355,7 +355,7 @@ _zsh_autosuggest_fetch() { _zsh_autosuggest_suggest() { local suggestion="$1" - if [ -n "$suggestion" ]; then + if [ -n "$suggestion" ] && [ $#BUFFER -gt 0 ]; then POSTDISPLAY="${suggestion#$BUFFER}" else unset POSTDISPLAY From 0c940e70f2eb931c856875bcb15f47b35f37cbec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20H=C3=B6ltje?= Date: Sat, 5 Nov 2016 01:40:14 -0400 Subject: [PATCH 46/70] Don't bind any zle-* methods It seems like all the zle-* methods are special and shouldn't be monkeyed with. Specifically `zle-isearch-update` and friends. Binding that widget caused `history-incremental-pattern-search` to stop working. Fixes zsh-users/zsh-syntax-highlighting#387 --- src/bind.zsh | 2 +- zsh-autosuggestions.zsh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bind.zsh b/src/bind.zsh index 49763e8..9729873 100644 --- a/src/bind.zsh +++ b/src/bind.zsh @@ -53,7 +53,7 @@ _zsh_autosuggest_bind_widgets() { ignore_widgets=( .\* _\* - zle-line-\* + zle-\* autosuggest-\* $ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX\* $ZSH_AUTOSUGGEST_IGNORE_WIDGETS diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index ed631c3..8bfe6db 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -223,7 +223,7 @@ _zsh_autosuggest_bind_widgets() { ignore_widgets=( .\* _\* - zle-line-\* + zle-\* autosuggest-\* $ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX\* $ZSH_AUTOSUGGEST_IGNORE_WIDGETS From dcce973287c181bad0d54c85590b5b3999221823 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 17 Feb 2017 18:45:46 -0700 Subject: [PATCH 47/70] Remove support for long-deprecated options These options have been deprecated for over a year. --- Makefile | 1 - src/deprecated.zsh | 36 ------------------------------------ src/start.zsh | 1 - zsh-autosuggestions.zsh | 37 ------------------------------------- 4 files changed, 75 deletions(-) delete mode 100644 src/deprecated.zsh diff --git a/Makefile b/Makefile index c7aaf1d..51d1b0c 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,6 @@ SRC_FILES := \ $(SRC_DIR)/config.zsh \ $(SRC_DIR)/util.zsh \ $(SRC_DIR)/features.zsh \ - $(SRC_DIR)/deprecated.zsh \ $(SRC_DIR)/bind.zsh \ $(SRC_DIR)/highlight.zsh \ $(SRC_DIR)/widgets.zsh \ diff --git a/src/deprecated.zsh b/src/deprecated.zsh deleted file mode 100644 index bcf0d74..0000000 --- a/src/deprecated.zsh +++ /dev/null @@ -1,36 +0,0 @@ - -#--------------------------------------------------------------------# -# Handle Deprecated Variables/Widgets # -#--------------------------------------------------------------------# - -_zsh_autosuggest_deprecated_warning() { - >&2 echo "zsh-autosuggestions: $@" -} - -_zsh_autosuggest_check_deprecated_config() { - if [ -n "$AUTOSUGGESTION_HIGHLIGHT_COLOR" ]; then - _zsh_autosuggest_deprecated_warning "AUTOSUGGESTION_HIGHLIGHT_COLOR is deprecated. Use ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE instead." - [ -z "$ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE" ] && ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE=$AUTOSUGGESTION_HIGHLIGHT_STYLE - unset AUTOSUGGESTION_HIGHLIGHT_STYLE - fi - - if [ -n "$AUTOSUGGESTION_HIGHLIGHT_CURSOR" ]; then - _zsh_autosuggest_deprecated_warning "AUTOSUGGESTION_HIGHLIGHT_CURSOR is deprecated." - unset AUTOSUGGESTION_HIGHLIGHT_CURSOR - fi - - if [ -n "$AUTOSUGGESTION_ACCEPT_RIGHT_ARROW" ]; then - _zsh_autosuggest_deprecated_warning "AUTOSUGGESTION_ACCEPT_RIGHT_ARROW is deprecated. The right arrow now accepts the suggestion by default." - unset AUTOSUGGESTION_ACCEPT_RIGHT_ARROW - fi -} - -_zsh_autosuggest_deprecated_start_widget() { - _zsh_autosuggest_deprecated_warning "The autosuggest-start widget is deprecated. For more info, see the README at https://github.com/zsh-users/zsh-autosuggestions." - zle -D autosuggest-start - eval "zle-line-init() { - $(echo $functions[${widgets[zle-line-init]#*:}] | sed -e 's/zle autosuggest-start//g') - }" -} - -zle -N autosuggest-start _zsh_autosuggest_deprecated_start_widget diff --git a/src/start.zsh b/src/start.zsh index 49af555..a982f2d 100644 --- a/src/start.zsh +++ b/src/start.zsh @@ -8,7 +8,6 @@ _zsh_autosuggest_start() { add-zsh-hook -d precmd _zsh_autosuggest_start _zsh_autosuggest_feature_detect - _zsh_autosuggest_check_deprecated_config _zsh_autosuggest_bind_widgets if [ -n "${ZSH_AUTOSUGGEST_USE_ASYNC+x}" ]; then diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 8bfe6db..3ece082 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -133,42 +133,6 @@ _zsh_autosuggest_feature_detect() { zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME } -#--------------------------------------------------------------------# -# Handle Deprecated Variables/Widgets # -#--------------------------------------------------------------------# - -_zsh_autosuggest_deprecated_warning() { - >&2 echo "zsh-autosuggestions: $@" -} - -_zsh_autosuggest_check_deprecated_config() { - if [ -n "$AUTOSUGGESTION_HIGHLIGHT_COLOR" ]; then - _zsh_autosuggest_deprecated_warning "AUTOSUGGESTION_HIGHLIGHT_COLOR is deprecated. Use ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE instead." - [ -z "$ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE" ] && ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE=$AUTOSUGGESTION_HIGHLIGHT_STYLE - unset AUTOSUGGESTION_HIGHLIGHT_STYLE - fi - - if [ -n "$AUTOSUGGESTION_HIGHLIGHT_CURSOR" ]; then - _zsh_autosuggest_deprecated_warning "AUTOSUGGESTION_HIGHLIGHT_CURSOR is deprecated." - unset AUTOSUGGESTION_HIGHLIGHT_CURSOR - fi - - if [ -n "$AUTOSUGGESTION_ACCEPT_RIGHT_ARROW" ]; then - _zsh_autosuggest_deprecated_warning "AUTOSUGGESTION_ACCEPT_RIGHT_ARROW is deprecated. The right arrow now accepts the suggestion by default." - unset AUTOSUGGESTION_ACCEPT_RIGHT_ARROW - fi -} - -_zsh_autosuggest_deprecated_start_widget() { - _zsh_autosuggest_deprecated_warning "The autosuggest-start widget is deprecated. For more info, see the README at https://github.com/zsh-users/zsh-autosuggestions." - zle -D autosuggest-start - eval "zle-line-init() { - $(echo $functions[${widgets[zle-line-init]#*:}] | sed -e 's/zle autosuggest-start//g') - }" -} - -zle -N autosuggest-start _zsh_autosuggest_deprecated_start_widget - #--------------------------------------------------------------------# # Widget Helpers # #--------------------------------------------------------------------# @@ -647,7 +611,6 @@ _zsh_autosuggest_start() { add-zsh-hook -d precmd _zsh_autosuggest_start _zsh_autosuggest_feature_detect - _zsh_autosuggest_check_deprecated_config _zsh_autosuggest_bind_widgets if [ -n "${ZSH_AUTOSUGGEST_USE_ASYNC+x}" ]; then From 39ca3dac45696a1d7ab3affe08a6498c661ccad9 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 17 Feb 2017 22:07:48 -0700 Subject: [PATCH 48/70] Use a different name for feature detection zpty So that it doesn't conflict when the file is sourced again --- src/features.zsh | 4 ++-- zsh-autosuggestions.zsh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/features.zsh b/src/features.zsh index 35dfcc3..9c6a3ae 100644 --- a/src/features.zsh +++ b/src/features.zsh @@ -7,7 +7,7 @@ _zsh_autosuggest_feature_detect() { typeset -g _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD typeset -h REPLY - zpty $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME '{ zshexit() { kill -KILL $$; sleep 1 } }' + zpty zsh_autosuggest_feature_detect '{ zshexit() { kill -KILL $$; sleep 1 } }' if (( REPLY )); then _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=1 @@ -15,5 +15,5 @@ _zsh_autosuggest_feature_detect() { _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=0 fi - zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME + zpty -d zsh_autosuggest_feature_detect } diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 3ece082..dc7d1a7 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -122,7 +122,7 @@ _zsh_autosuggest_feature_detect() { typeset -g _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD typeset -h REPLY - zpty $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME '{ zshexit() { kill -KILL $$; sleep 1 } }' + zpty zsh_autosuggest_feature_detect '{ zshexit() { kill -KILL $$; sleep 1 } }' if (( REPLY )); then _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=1 @@ -130,7 +130,7 @@ _zsh_autosuggest_feature_detect() { _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=0 fi - zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME + zpty -d zsh_autosuggest_feature_detect } #--------------------------------------------------------------------# From a0fcd81ce19947339aea3b05ec57d6680ba99548 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 17 Feb 2017 22:40:23 -0700 Subject: [PATCH 49/70] Destroy zpty on load if it already exists --- src/async.zsh | 6 +++--- zsh-autosuggestions.zsh | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/async.zsh b/src/async.zsh index 60055b9..be331ba 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -85,9 +85,9 @@ _zsh_autosuggest_async_pty_create() { } _zsh_autosuggest_async_pty_destroy() { - if [ -n "$_ZSH_AUTOSUGGEST_PTY_FD" ]; then + if zpty -t $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null; then # Remove the input handler - zle -F $_ZSH_AUTOSUGGEST_PTY_FD + zle -F $_ZSH_AUTOSUGGEST_PTY_FD &>/dev/null # Destroy the zpty zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null @@ -102,7 +102,7 @@ _zsh_autosuggest_async_pty_recreate() { _zsh_autosuggest_async_start() { typeset -g _ZSH_AUTOSUGGEST_PTY_FD - _zsh_autosuggest_async_pty_create + _zsh_autosuggest_async_pty_recreate # We recreate the pty to get a fresh list of history events add-zsh-hook precmd _zsh_autosuggest_async_pty_recreate diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index dc7d1a7..2e612ca 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -579,9 +579,9 @@ _zsh_autosuggest_async_pty_create() { } _zsh_autosuggest_async_pty_destroy() { - if [ -n "$_ZSH_AUTOSUGGEST_PTY_FD" ]; then + if zpty -t $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null; then # Remove the input handler - zle -F $_ZSH_AUTOSUGGEST_PTY_FD + zle -F $_ZSH_AUTOSUGGEST_PTY_FD &>/dev/null # Destroy the zpty zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null @@ -596,7 +596,7 @@ _zsh_autosuggest_async_pty_recreate() { _zsh_autosuggest_async_start() { typeset -g _ZSH_AUTOSUGGEST_PTY_FD - _zsh_autosuggest_async_pty_create + _zsh_autosuggest_async_pty_recreate # We recreate the pty to get a fresh list of history events add-zsh-hook precmd _zsh_autosuggest_async_pty_recreate From 75e850577d0b85d39217066139fafff1ba0376df Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 17 Feb 2017 22:50:11 -0700 Subject: [PATCH 50/70] Gracefully handle being sourced multiple times Should fix #126 --- src/bind.zsh | 1 + zsh-autosuggestions.zsh | 1 + 2 files changed, 2 insertions(+) diff --git a/src/bind.zsh b/src/bind.zsh index 9729873..e2ca5c9 100644 --- a/src/bind.zsh +++ b/src/bind.zsh @@ -16,6 +16,7 @@ _zsh_autosuggest_bind_widget() { # User-defined widget user:*) + zle -l "$prefix$widget" && zle -N "$widget" ${widgets[$prefix$widget]#*:} zle -N $prefix$widget ${widgets[$widget]#*:} ;; diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 2e612ca..df4f4a5 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -150,6 +150,7 @@ _zsh_autosuggest_bind_widget() { # User-defined widget user:*) + zle -l "$prefix$widget" && zle -N "$widget" ${widgets[$prefix$widget]#*:} zle -N $prefix$widget ${widgets[$widget]#*:} ;; From 4321fc097c408f6a8dfab8acf36d4706adb35386 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 17 Feb 2017 23:20:04 -0700 Subject: [PATCH 51/70] We need to bind on every precmd to ensure we wrap other wrappers Specifically, highlighting breaks if our widgets are wrapped by z-syn-h widgets. --- src/start.zsh | 2 -- zsh-autosuggestions.zsh | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/start.zsh b/src/start.zsh index a982f2d..c5ac120 100644 --- a/src/start.zsh +++ b/src/start.zsh @@ -5,8 +5,6 @@ # Start the autosuggestion widgets _zsh_autosuggest_start() { - add-zsh-hook -d precmd _zsh_autosuggest_start - _zsh_autosuggest_feature_detect _zsh_autosuggest_bind_widgets diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index df4f4a5..4018b0f 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -609,8 +609,6 @@ _zsh_autosuggest_async_start() { # Start the autosuggestion widgets _zsh_autosuggest_start() { - add-zsh-hook -d precmd _zsh_autosuggest_start - _zsh_autosuggest_feature_detect _zsh_autosuggest_bind_widgets From 255359dbb8c7ec3f3437cf29a54e0869e38d9713 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sat, 18 Feb 2017 10:35:30 -0700 Subject: [PATCH 52/70] Use `+=` to be a bit more true to the spec language --- spec/options/widget_lists_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/options/widget_lists_spec.rb b/spec/options/widget_lists_spec.rb index 3e03acf..c62196d 100644 --- a/spec/options/widget_lists_spec.rb +++ b/spec/options/widget_lists_spec.rb @@ -2,7 +2,7 @@ describe 'a zle widget' do let(:before_sourcing) { -> { session.run_command('my-widget() {}; zle -N my-widget; bindkey ^B my-widget') } } context 'when added to ZSH_AUTOSUGGEST_ACCEPT_WIDGETS' do - let(:options) { ['ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=(my-widget)'] } + let(:options) { ['ZSH_AUTOSUGGEST_ACCEPT_WIDGETS+=(my-widget)'] } it 'accepts the suggestion when invoked' do with_history('echo hello') do @@ -15,7 +15,7 @@ describe 'a zle widget' do end context 'when added to ZSH_AUTOSUGGEST_CLEAR_WIDGETS' do - let(:options) { ['ZSH_AUTOSUGGEST_CLEAR_WIDGETS=(my-widget)'] } + let(:options) { ['ZSH_AUTOSUGGEST_CLEAR_WIDGETS+=(my-widget)'] } it 'clears the suggestion when invoked' do with_history('echo hello') do @@ -28,7 +28,7 @@ describe 'a zle widget' do end context 'when added to ZSH_AUTOSUGGEST_EXECUTE_WIDGETS' do - let(:options) { ['ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=(my-widget)'] } + let(:options) { ['ZSH_AUTOSUGGEST_EXECUTE_WIDGETS+=(my-widget)'] } it 'executes the suggestion when invoked' do with_history('echo hello') do From c70d685d154a83258c83536639940aecab974088 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sat, 18 Feb 2017 11:12:10 -0700 Subject: [PATCH 53/70] Clean up widget list spec --- spec/options/widget_lists_spec.rb | 49 +++++++++++++++---------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/spec/options/widget_lists_spec.rb b/spec/options/widget_lists_spec.rb index c62196d..eefc057 100644 --- a/spec/options/widget_lists_spec.rb +++ b/spec/options/widget_lists_spec.rb @@ -1,8 +1,9 @@ describe 'a zle widget' do - let(:before_sourcing) { -> { session.run_command('my-widget() {}; zle -N my-widget; bindkey ^B my-widget') } } + let(:widget) { 'my-widget' } + let(:before_sourcing) { -> { session.run_command("#{widget}() {}; zle -N #{widget}; bindkey ^B #{widget}") } } context 'when added to ZSH_AUTOSUGGEST_ACCEPT_WIDGETS' do - let(:options) { ['ZSH_AUTOSUGGEST_ACCEPT_WIDGETS+=(my-widget)'] } + let(:options) { ["ZSH_AUTOSUGGEST_ACCEPT_WIDGETS+=(#{widget})"] } it 'accepts the suggestion when invoked' do with_history('echo hello') do @@ -15,7 +16,7 @@ describe 'a zle widget' do end context 'when added to ZSH_AUTOSUGGEST_CLEAR_WIDGETS' do - let(:options) { ['ZSH_AUTOSUGGEST_CLEAR_WIDGETS+=(my-widget)'] } + let(:options) { ["ZSH_AUTOSUGGEST_CLEAR_WIDGETS+=(#{widget})"] } it 'clears the suggestion when invoked' do with_history('echo hello') do @@ -28,7 +29,7 @@ describe 'a zle widget' do end context 'when added to ZSH_AUTOSUGGEST_EXECUTE_WIDGETS' do - let(:options) { ['ZSH_AUTOSUGGEST_EXECUTE_WIDGETS+=(my-widget)'] } + let(:options) { ["ZSH_AUTOSUGGEST_EXECUTE_WIDGETS+=(#{widget})"] } it 'executes the suggestion when invoked' do with_history('echo hello') do @@ -39,34 +40,30 @@ describe 'a zle widget' do end end end -end - -describe 'a zle widget that moves the cursor forward' do - let(:before_sourcing) { -> { session.run_command('my-widget() { zle forward-char }; zle -N my-widget; bindkey ^B my-widget') } } - - context 'when added to ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS' do - let(:options) { ['ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=(my-widget)'] } - - it 'accepts the suggestion as far as the cursor is moved when invoked' do - with_history('echo hello') do - session.send_string('e') - wait_for { session.content }.to start_with('echo hello') - session.send_keys('C-b') - wait_for { session.content(esc_seqs: true) }.to match(/\Aec\e\[[0-9]+mho hello/) - end - end - end -end - -describe 'a builtin zle widget' do - let(:widget) { 'beep' } context 'when added to ZSH_AUTOSUGGEST_IGNORE_WIDGETS' do let(:options) { ["ZSH_AUTOSUGGEST_IGNORE_WIDGETS=(#{widget})"] } it 'should not be wrapped with an autosuggest widget' do session.run_command("echo $widgets[#{widget}]") - wait_for { session.content }.to end_with("\nbuiltin") + wait_for { session.content }.to end_with("\nuser:#{widget}") + end + end + + context 'that moves the cursor forward' do + before { session.run_command("#{widget}() { zle forward-char }") } + + context 'when added to ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS' do + let(:options) { ["ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=(#{widget})"] } + + it 'accepts the suggestion as far as the cursor is moved when invoked' do + with_history('echo hello') do + session.send_string('e') + wait_for { session.content }.to start_with('echo hello') + session.send_keys('C-b') + wait_for { session.content(esc_seqs: true) }.to match(/\Aec\e\[[0-9]+mho hello/) + end + end end end end From 2cd99e64b70cfe940ed4692eb5c6186ba255a7be Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sat, 18 Feb 2017 11:12:55 -0700 Subject: [PATCH 54/70] Add a test for modifying widget list vars after sourcing plugin --- spec/options/widget_lists_spec.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spec/options/widget_lists_spec.rb b/spec/options/widget_lists_spec.rb index eefc057..00441ee 100644 --- a/spec/options/widget_lists_spec.rb +++ b/spec/options/widget_lists_spec.rb @@ -67,3 +67,18 @@ describe 'a zle widget' do end end end + +describe 'a modification to the widget lists' do + let(:widget) { 'my-widget' } + let(:before_sourcing) { -> { session.run_command("#{widget}() {}; zle -N #{widget}; bindkey ^B #{widget}") } } + before { session.run_command("ZSH_AUTOSUGGEST_ACCEPT_WIDGETS+=(#{widget})") } + + it 'takes effect on the next cmd line' do + with_history('echo hello') do + session.send_string('e') + wait_for { session.content }.to eq('echo hello') + session.send_keys('C-b') + wait_for { session.content(esc_seqs: true) }.to eq('echo hello') + end + end +end From e3fa4e4904fede78390152e9a1cc6f9f2ec9b1cb Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sat, 18 Feb 2017 10:47:53 -0700 Subject: [PATCH 55/70] Don't do anything but re-bind widgets on each precmd There's no need to re-run feature detection or async_start on every precmd. Just do those once. --- src/start.zsh | 8 ++++++++ zsh-autosuggestions.zsh | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/src/start.zsh b/src/start.zsh index c5ac120..2304fb3 100644 --- a/src/start.zsh +++ b/src/start.zsh @@ -5,9 +5,17 @@ # Start the autosuggestion widgets _zsh_autosuggest_start() { + add-zsh-hook -d precmd _zsh_autosuggest_start + _zsh_autosuggest_feature_detect _zsh_autosuggest_bind_widgets + # Re-bind widgets on every precmd to ensure we wrap other wrappers. + # Specifically, highlighting breaks if our widgets are wrapped by + # zsh-syntax-highlighting widgets. This also allows modifications + # to the widget list variables to take effect on the next precmd. + add-zsh-hook precmd _zsh_autosuggest_bind_widgets + if [ -n "${ZSH_AUTOSUGGEST_USE_ASYNC+x}" ]; then _zsh_autosuggest_async_start fi diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 4018b0f..2726661 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -609,9 +609,17 @@ _zsh_autosuggest_async_start() { # Start the autosuggestion widgets _zsh_autosuggest_start() { + add-zsh-hook -d precmd _zsh_autosuggest_start + _zsh_autosuggest_feature_detect _zsh_autosuggest_bind_widgets + # Re-bind widgets on every precmd to ensure we wrap other wrappers. + # Specifically, highlighting breaks if our widgets are wrapped by + # zsh-syntax-highlighting widgets. This also allows modifications + # to the widget list variables to take effect on the next precmd. + add-zsh-hook precmd _zsh_autosuggest_bind_widgets + if [ -n "${ZSH_AUTOSUGGEST_USE_ASYNC+x}" ]; then _zsh_autosuggest_async_start fi From 4afbbbaddad1c84b5ae26077a8cf23177e1aa2db Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sat, 18 Feb 2017 11:25:27 -0700 Subject: [PATCH 56/70] We only need to run the feature detection if starting async --- src/async.zsh | 1 + src/features.zsh | 2 +- src/start.zsh | 1 - zsh-autosuggestions.zsh | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/async.zsh b/src/async.zsh index be331ba..81ea5cf 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -102,6 +102,7 @@ _zsh_autosuggest_async_pty_recreate() { _zsh_autosuggest_async_start() { typeset -g _ZSH_AUTOSUGGEST_PTY_FD + _zsh_autosuggest_feature_detect_zpty_returns_fd _zsh_autosuggest_async_pty_recreate # We recreate the pty to get a fresh list of history events diff --git a/src/features.zsh b/src/features.zsh index 9c6a3ae..7a5248f 100644 --- a/src/features.zsh +++ b/src/features.zsh @@ -3,7 +3,7 @@ # Feature Detection # #--------------------------------------------------------------------# -_zsh_autosuggest_feature_detect() { +_zsh_autosuggest_feature_detect_zpty_returns_fd() { typeset -g _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD typeset -h REPLY diff --git a/src/start.zsh b/src/start.zsh index 2304fb3..6fa8ce9 100644 --- a/src/start.zsh +++ b/src/start.zsh @@ -7,7 +7,6 @@ _zsh_autosuggest_start() { add-zsh-hook -d precmd _zsh_autosuggest_start - _zsh_autosuggest_feature_detect _zsh_autosuggest_bind_widgets # Re-bind widgets on every precmd to ensure we wrap other wrappers. diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 2726661..4df6b2c 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -118,7 +118,7 @@ _zsh_autosuggest_escape_command() { # Feature Detection # #--------------------------------------------------------------------# -_zsh_autosuggest_feature_detect() { +_zsh_autosuggest_feature_detect_zpty_returns_fd() { typeset -g _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD typeset -h REPLY @@ -597,6 +597,7 @@ _zsh_autosuggest_async_pty_recreate() { _zsh_autosuggest_async_start() { typeset -g _ZSH_AUTOSUGGEST_PTY_FD + _zsh_autosuggest_feature_detect_zpty_returns_fd _zsh_autosuggest_async_pty_recreate # We recreate the pty to get a fresh list of history events @@ -611,7 +612,6 @@ _zsh_autosuggest_async_start() { _zsh_autosuggest_start() { add-zsh-hook -d precmd _zsh_autosuggest_start - _zsh_autosuggest_feature_detect _zsh_autosuggest_bind_widgets # Re-bind widgets on every precmd to ensure we wrap other wrappers. From 48a21bf79eb329f48a7db3f928a99c0333c4e623 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sat, 18 Feb 2017 11:27:55 -0700 Subject: [PATCH 57/70] [cleanup] Remove an extra newline --- src/async.zsh | 1 - zsh-autosuggestions.zsh | 1 - 2 files changed, 2 deletions(-) diff --git a/src/async.zsh b/src/async.zsh index 81ea5cf..3c3cf84 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -20,7 +20,6 @@ _zsh_autosuggest_async_server() { # Output only newlines (not carriage return + newline) stty -onlcr - # Silence any error messages exec 2>/dev/null diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 4df6b2c..0a33006 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -515,7 +515,6 @@ _zsh_autosuggest_async_server() { # Output only newlines (not carriage return + newline) stty -onlcr - # Silence any error messages exec 2>/dev/null From c9a51e0c4c604ba7406a4d1ac9fd9164090a3776 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sat, 18 Feb 2017 16:51:53 -0700 Subject: [PATCH 58/70] Handle dashes at the beginning of commands --- spec/special_characters_spec.rb | 5 +++++ spec/terminal_session.rb | 2 +- src/async.zsh | 2 +- zsh-autosuggestions.zsh | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/spec/special_characters_spec.rb b/spec/special_characters_spec.rb index 21f681b..ce7810b 100644 --- a/spec/special_characters_spec.rb +++ b/spec/special_characters_spec.rb @@ -51,5 +51,10 @@ describe 'a special character in the buffer' do session.send_string('echo "^A') wait_for { session.content }.to eq('echo "^A"') end + + with_history('-foo() {}') do + session.send_string('-') + wait_for { session.content }.to eq('-foo() {}') + end end end diff --git a/spec/terminal_session.rb b/spec/terminal_session.rb index a089d27..8cbbac0 100644 --- a/spec/terminal_session.rb +++ b/spec/terminal_session.rb @@ -26,7 +26,7 @@ class TerminalSession end def send_string(str) - tmux_command("send-keys -t 0 -l '#{str.gsub("'", "\\'")}'") + tmux_command("send-keys -t 0 -l -- '#{str.gsub("'", "\\'")}'") self end diff --git a/src/async.zsh b/src/async.zsh index 3c3cf84..124c9ac 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -55,7 +55,7 @@ _zsh_autosuggest_async_response() { local suggestion zpty -rt $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME suggestion '*'$'\0' 2>/dev/null - zle autosuggest-suggest "${suggestion%%$'\0'##}" + zle autosuggest-suggest -- "${suggestion%%$'\0'##}" } _zsh_autosuggest_async_pty_create() { diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 0a33006..34ca080 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -550,7 +550,7 @@ _zsh_autosuggest_async_response() { local suggestion zpty -rt $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME suggestion '*'$'\0' 2>/dev/null - zle autosuggest-suggest "${suggestion%%$'\0'##}" + zle autosuggest-suggest -- "${suggestion%%$'\0'##}" } _zsh_autosuggest_async_pty_create() { From 468b7403e933af1cc2d08356340a48e5b2fb45e1 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sun, 26 Feb 2017 14:18:22 -0700 Subject: [PATCH 59/70] Test should be passing block to RSpec wait_for Fixes flaky test --- spec/integrations/client_zpty_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/integrations/client_zpty_spec.rb b/spec/integrations/client_zpty_spec.rb index fb7bbeb..8f1550e 100644 --- a/spec/integrations/client_zpty_spec.rb +++ b/spec/integrations/client_zpty_spec.rb @@ -5,6 +5,6 @@ describe 'a running zpty command' do sleep 1 # Give a little time for precmd hooks to run session.run_command('zpty -t kitty; echo $?') - wait_for(session.content).to end_with("\n0") + wait_for { session.content }.to end_with("\n0") end end From 39762ecd971de53daa0784a57c27fdf8254722e8 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sun, 26 Feb 2017 14:19:09 -0700 Subject: [PATCH 60/70] Set up circle ci --- .editorconfig | 4 ++++ Makefile | 1 + README.md | 2 ++ circle.yml | 12 ++++++++++++ 4 files changed, 19 insertions(+) create mode 100644 circle.yml diff --git a/.editorconfig b/.editorconfig index b40bc96..ddabb17 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,3 +12,7 @@ indent_style = space [*.rb] indent_style = space indent_size = 2 + +[*.yml] +indent_style = space +indent_size = 2 diff --git a/Makefile b/Makefile index 51d1b0c..d5d162c 100644 --- a/Makefile +++ b/Makefile @@ -32,4 +32,5 @@ clean: .PHONY: test test: all + @test -n "$$TEST_ZSH_BIN" && echo "Testing zsh binary: $(TEST_ZSH_BIN)" || true bundle exec rspec $(TESTS) diff --git a/README.md b/README.md index 85cac70..9b363da 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. +[![CircleCI](https://circleci.com/gh/zsh-users/zsh-autosuggestions.svg?style=svg)](https://circleci.com/gh/zsh-users/zsh-autosuggestions) + diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..5e3a6f6 --- /dev/null +++ b/circle.yml @@ -0,0 +1,12 @@ +machine: + environment: + ZSH_VERSIONS: 5.0.8 5.1.1 5.2 5.3.1 + +dependencies: + pre: + - for v in $(echo $ZSH_VERSIONS | awk "{ for (i=$((1+CIRCLE_NODE_INDEX));i<=NF;i+=$CIRCLE_NODE_TOTAL) print \$i }"); do wget https://sourceforge.net/projects/zsh/files/zsh/$v/zsh-$v.tar.gz && tar xzf zsh-$v.tar.gz && cd zsh-$v && ./configure && sudo make install || exit 1; done + +test: + override: + - for v in $(echo $ZSH_VERSIONS | awk "{ for (i=$((1+CIRCLE_NODE_INDEX));i<=NF;i+=$CIRCLE_NODE_TOTAL) print \$i }"); do TEST_ZSH_BIN=/usr/local/bin/zsh-$v make test || exit 1; done: + parallel: true From ce362248faf82a73cd47707b7a228bc2485e58d9 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 28 Feb 2017 11:12:23 -0700 Subject: [PATCH 61/70] Use pry-byebug instead of pry for more functionality --- Gemfile | 2 +- Gemfile.lock | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index c8090be..8b5deec 100644 --- a/Gemfile +++ b/Gemfile @@ -2,4 +2,4 @@ source 'https://rubygems.org' gem 'rspec' gem 'rspec-wait' -gem 'pry' +gem 'pry-byebug' diff --git a/Gemfile.lock b/Gemfile.lock index 75b649b..63ee778 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,7 @@ GEM remote: https://rubygems.org/ specs: + byebug (9.0.5) coderay (1.1.1) diff-lcs (1.3) method_source (0.8.2) @@ -8,6 +9,9 @@ GEM coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) + pry-byebug (3.4.0) + byebug (~> 9.0) + pry (~> 0.10) rspec (3.5.0) rspec-core (~> 3.5.0) rspec-expectations (~> 3.5.0) @@ -29,7 +33,7 @@ PLATFORMS ruby DEPENDENCIES - pry + pry-byebug rspec rspec-wait From 502fb4a174a7625cb5c3e5a0b0a3cebfc223df08 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 28 Feb 2017 11:12:50 -0700 Subject: [PATCH 62/70] Make tmux_socket_name public so you can access easily from binding.pry Can attach while tests are stopped with `tmux -L attach` --- spec/terminal_session.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/terminal_session.rb b/spec/terminal_session.rb index 8cbbac0..3f8ca69 100644 --- a/spec/terminal_session.rb +++ b/spec/terminal_session.rb @@ -18,6 +18,10 @@ class TerminalSession tmux_command("new-session -d -x #{opts[:width]} -y #{opts[:height]} '#{cmd}'") end + def tmux_socket_name + @tmux_socket_name ||= SecureRandom.hex(6) + end + def run_command(command) send_string(command) send_keys('enter') @@ -70,10 +74,6 @@ class TerminalSession attr_reader :opts - def tmux_socket_name - @tmux_socket_name ||= SecureRandom.hex(6) - end - def tmux_command(cmd) out = `tmux -u -L #{tmux_socket_name} #{cmd}` From ea505b01e52731e2d6023d5f191dbaea1652c9e2 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 28 Feb 2017 11:13:29 -0700 Subject: [PATCH 63/70] Add a spec for unlisted widgets fetching a new suggestion --- spec/options/widget_lists_spec.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/spec/options/widget_lists_spec.rb b/spec/options/widget_lists_spec.rb index 00441ee..c207c0c 100644 --- a/spec/options/widget_lists_spec.rb +++ b/spec/options/widget_lists_spec.rb @@ -66,6 +66,19 @@ describe 'a zle widget' do end end end + + context 'that modifies the buffer' do + before { session.run_command("#{widget}() { BUFFER=\"foo\" }") } + + context 'when not added to any of the widget lists' do + it 'modifies the buffer and fetches a new suggestion' do + with_history('foobar') do + session.send_keys('C-b') + wait_for { session.content }.to eq('foobar') + end + end + end + end end describe 'a modification to the widget lists' do From c52c428793fc4ea2326b9ad0860853959b0d2954 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 28 Feb 2017 11:14:16 -0700 Subject: [PATCH 64/70] Fix issues with widgets wrapped by other plugins Puts in a better fix for #126 and related issues. --- spec/integrations/wrapped_widget_spec.rb | 39 +++++++++++++++++++++++ src/bind.zsh | 40 +++++++++++++++++++----- zsh-autosuggestions.zsh | 40 +++++++++++++++++++----- 3 files changed, 105 insertions(+), 14 deletions(-) create mode 100644 spec/integrations/wrapped_widget_spec.rb diff --git a/spec/integrations/wrapped_widget_spec.rb b/spec/integrations/wrapped_widget_spec.rb new file mode 100644 index 0000000..61dfc2d --- /dev/null +++ b/spec/integrations/wrapped_widget_spec.rb @@ -0,0 +1,39 @@ +describe 'a wrapped widget' do + let(:widget) { 'backward-delete-char' } + + context 'initialized before sourcing the plugin' do + let(:before_sourcing) do + -> do + session. + run_command("_orig_#{widget}() { zle .#{widget} }"). + run_command("zle -N orig-#{widget} _orig_#{widget}"). + run_command("#{widget}-magic() { zle orig-#{widget}; BUFFER+=b }"). + run_command("zle -N #{widget} #{widget}-magic") + end + end + + it 'executes the custom behavior and the built-in behavior' do + with_history('foobar', 'foodar') do + session.send_string('food').send_keys('C-h') + wait_for { session.content }.to eq('foobar') + end + end + end + + context 'initialized after sourcing the plugin' do + before do + session. + run_command("zle -N orig-#{widget} ${widgets[#{widget}]#*:}"). + run_command("#{widget}-magic() { zle orig-#{widget}; BUFFER+=b }"). + run_command("zle -N #{widget} #{widget}-magic"). + clear_screen + end + + it 'executes the custom behavior and the built-in behavior' do + with_history('foobar', 'foodar') do + session.send_string('food').send_keys('C-h') + wait_for { session.content }.to eq('foobar') + end + end + end +end diff --git a/src/bind.zsh b/src/bind.zsh index e2ca5c9..d8629f1 100644 --- a/src/bind.zsh +++ b/src/bind.zsh @@ -3,12 +3,34 @@ # Widget Helpers # #--------------------------------------------------------------------# +_zsh_autosuggest_incr_bind_count() { + if ((${+_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]})); then + ((_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]++)) + else + _ZSH_AUTOSUGGEST_BIND_COUNTS[$1]=1 + fi + + bind_count=$_ZSH_AUTOSUGGEST_BIND_COUNTS[$1] +} + +_zsh_autosuggest_get_bind_count() { + if ((${+_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]})); then + bind_count=$_ZSH_AUTOSUGGEST_BIND_COUNTS[$1] + else + bind_count=0 + fi +} + # Bind a single widget to an autosuggest widget, saving a reference to the original widget _zsh_autosuggest_bind_widget() { + typeset -gA _ZSH_AUTOSUGGEST_BIND_COUNTS + local widget=$1 local autosuggest_action=$2 local prefix=$ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX + local -i bind_count + # Save a reference to the original widget case $widgets[$widget] in # Already bound @@ -16,34 +38,38 @@ _zsh_autosuggest_bind_widget() { # User-defined widget user:*) - zle -l "$prefix$widget" && zle -N "$widget" ${widgets[$prefix$widget]#*:} - zle -N $prefix$widget ${widgets[$widget]#*:} + _zsh_autosuggest_incr_bind_count $widget + zle -N $prefix${bind_count}-$widget ${widgets[$widget]#*:} ;; # Built-in widget builtin) + _zsh_autosuggest_incr_bind_count $widget eval "_zsh_autosuggest_orig_${(q)widget}() { zle .${(q)widget} }" - zle -N $prefix$widget _zsh_autosuggest_orig_$widget + zle -N $prefix${bind_count}-$widget _zsh_autosuggest_orig_$widget ;; # Completion widget completion:*) - eval "zle -C $prefix${(q)widget} ${${(s.:.)widgets[$widget]}[2,3]}" + _zsh_autosuggest_incr_bind_count $widget + eval "zle -C $prefix${bind_count}-${(q)widget} ${${(s.:.)widgets[$widget]}[2,3]}" ;; esac + _zsh_autosuggest_get_bind_count $widget + # Pass the original widget's name explicitly into the autosuggest # function. Use this passed in widget name to call the original # widget instead of relying on the $WIDGET variable being set # correctly. $WIDGET cannot be trusted because other plugins call # zle without the `-w` flag (e.g. `zle self-insert` instead of # `zle self-insert -w`). - eval "_zsh_autosuggest_bound_${(q)widget}() { - _zsh_autosuggest_widget_$autosuggest_action $prefix${(q)widget} \$@ + eval "_zsh_autosuggest_bound_${bind_count}_${(q)widget}() { + _zsh_autosuggest_widget_$autosuggest_action $prefix$bind_count-${(q)widget} \$@ }" # Create the bound widget - zle -N $widget _zsh_autosuggest_bound_$widget + zle -N $widget _zsh_autosuggest_bound_${bind_count}_$widget } # Map all configured widgets to the right autosuggest widgets diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 34ca080..05e1bb4 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -137,12 +137,34 @@ _zsh_autosuggest_feature_detect_zpty_returns_fd() { # Widget Helpers # #--------------------------------------------------------------------# +_zsh_autosuggest_incr_bind_count() { + if ((${+_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]})); then + ((_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]++)) + else + _ZSH_AUTOSUGGEST_BIND_COUNTS[$1]=1 + fi + + bind_count=$_ZSH_AUTOSUGGEST_BIND_COUNTS[$1] +} + +_zsh_autosuggest_get_bind_count() { + if ((${+_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]})); then + bind_count=$_ZSH_AUTOSUGGEST_BIND_COUNTS[$1] + else + bind_count=0 + fi +} + # Bind a single widget to an autosuggest widget, saving a reference to the original widget _zsh_autosuggest_bind_widget() { + typeset -gA _ZSH_AUTOSUGGEST_BIND_COUNTS + local widget=$1 local autosuggest_action=$2 local prefix=$ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX + local -i bind_count + # Save a reference to the original widget case $widgets[$widget] in # Already bound @@ -150,34 +172,38 @@ _zsh_autosuggest_bind_widget() { # User-defined widget user:*) - zle -l "$prefix$widget" && zle -N "$widget" ${widgets[$prefix$widget]#*:} - zle -N $prefix$widget ${widgets[$widget]#*:} + _zsh_autosuggest_incr_bind_count $widget + zle -N $prefix${bind_count}-$widget ${widgets[$widget]#*:} ;; # Built-in widget builtin) + _zsh_autosuggest_incr_bind_count $widget eval "_zsh_autosuggest_orig_${(q)widget}() { zle .${(q)widget} }" - zle -N $prefix$widget _zsh_autosuggest_orig_$widget + zle -N $prefix${bind_count}-$widget _zsh_autosuggest_orig_$widget ;; # Completion widget completion:*) - eval "zle -C $prefix${(q)widget} ${${(s.:.)widgets[$widget]}[2,3]}" + _zsh_autosuggest_incr_bind_count $widget + eval "zle -C $prefix${bind_count}-${(q)widget} ${${(s.:.)widgets[$widget]}[2,3]}" ;; esac + _zsh_autosuggest_get_bind_count $widget + # Pass the original widget's name explicitly into the autosuggest # function. Use this passed in widget name to call the original # widget instead of relying on the $WIDGET variable being set # correctly. $WIDGET cannot be trusted because other plugins call # zle without the `-w` flag (e.g. `zle self-insert` instead of # `zle self-insert -w`). - eval "_zsh_autosuggest_bound_${(q)widget}() { - _zsh_autosuggest_widget_$autosuggest_action $prefix${(q)widget} \$@ + eval "_zsh_autosuggest_bound_${bind_count}_${(q)widget}() { + _zsh_autosuggest_widget_$autosuggest_action $prefix$bind_count-${(q)widget} \$@ }" # Create the bound widget - zle -N $widget _zsh_autosuggest_bound_$widget + zle -N $widget _zsh_autosuggest_bound_${bind_count}_$widget } # Map all configured widgets to the right autosuggest widgets From e1959d0f617f8a070623eeec1b5d9d1ec35ba40a Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 28 Feb 2017 11:18:21 -0700 Subject: [PATCH 65/70] 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 66/70] 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 67/70] 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) From 83129dd796b19cf888dd32aad84753fb728eead9 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 14 Apr 2017 08:48:54 -0600 Subject: [PATCH 68/70] Make asynchronous suggestions disabled by default While they are still experimental --- README.md | 4 ++-- spec/options/async_zpty_name_spec.rb | 24 ++++++++++++++---------- spec/options/use_async_spec.rb | 6 +++--- src/config.zsh | 3 --- zsh-autosuggestions.zsh | 3 --- 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 0d3c833..ab5c82c 100644 --- a/README.md +++ b/README.md @@ -94,9 +94,9 @@ Widgets that modify the buffer and are not found in any of these arrays will fet Set `ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE` to an integer value to disable autosuggestion for large buffers. The default is unset, which means that autosuggestion will be tried for any buffer size. Recommended value is 20. This can be useful when pasting large amount of text in the terminal, to avoid triggering autosuggestion for too long strings. -### Disable Asynchronous Mode +### Enable Asynchronous Mode -As of `v0.4.0`, suggestions are fetched asynchronously using the `zsh/zpty` module. To disable this behavior and fall back to fetching suggestions synchronously, unset the `ZSH_AUTOSUGGEST_USE_ASYNC` variable. +As of `v0.4.0`, suggestions can be fetched asynchronously using the `zsh/zpty` module. To enable this behavior, set the `ZSH_AUTOSUGGEST_USE_ASYNC` variable (it can be set to anything). ### Key Bindings diff --git a/spec/options/async_zpty_name_spec.rb b/spec/options/async_zpty_name_spec.rb index c768f54..407ee70 100644 --- a/spec/options/async_zpty_name_spec.rb +++ b/spec/options/async_zpty_name_spec.rb @@ -1,15 +1,19 @@ -describe 'the zpty for async suggestions' do - it 'is created with the default name' do - session.run_command('zpty -t zsh_autosuggest_pty &>/dev/null; echo $?') - wait_for { session.content }.to end_with("\n0") - end +context 'when async suggestions are enabled' do + let(:options) { ["ZSH_AUTOSUGGEST_USE_ASYNC="] } - context 'when ZSH_AUTOSUGGEST_ASYNC_PTY_NAME is set' do - let(:options) { ['ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=foo_pty'] } - - it 'is created with the specified name' do - session.run_command('zpty -t foo_pty &>/dev/null; echo $?') + describe 'the zpty for async suggestions' do + it 'is created with the default name' do + session.run_command('zpty -t zsh_autosuggest_pty &>/dev/null; echo $?') wait_for { session.content }.to end_with("\n0") end + + context 'when ZSH_AUTOSUGGEST_ASYNC_PTY_NAME is set' do + let(:options) { super() + ['ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=foo_pty'] } + + it 'is created with the specified name' do + session.run_command('zpty -t foo_pty &>/dev/null; echo $?') + wait_for { session.content }.to end_with("\n0") + end + end end end diff --git a/spec/options/use_async_spec.rb b/spec/options/use_async_spec.rb index 8b9ebab..420dcc3 100644 --- a/spec/options/use_async_spec.rb +++ b/spec/options/use_async_spec.rb @@ -1,7 +1,7 @@ describe 'suggestion fetching' do - it 'is performed asynchronously' + it 'is performed synchronously' - context 'when ZSH_AUTOSUGGEST_USE_ASYNC is unset' do - it 'is performed synchronously' + context 'when ZSH_AUTOSUGGEST_USE_ASYNC is set' do + it 'is performed asynchronously' end end diff --git a/src/config.zsh b/src/config.zsh index a9f02e6..c7fc55a 100644 --- a/src/config.zsh +++ b/src/config.zsh @@ -61,8 +61,5 @@ ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( # Max size of buffer to trigger autosuggestion. Leave undefined for no upper bound. ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE= -# Use asynchronous mode by default. Unset this variable to use sync mode. -ZSH_AUTOSUGGEST_USE_ASYNC= - # Pty name for calculating autosuggestions asynchronously ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_pty diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 55d23b7..bdc9bcc 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -97,9 +97,6 @@ ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( # Max size of buffer to trigger autosuggestion. Leave undefined for no upper bound. ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE= -# Use asynchronous mode by default. Unset this variable to use sync mode. -ZSH_AUTOSUGGEST_USE_ASYNC= - # Pty name for calculating autosuggestions asynchronously ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_pty From 281ed9bbf73fd77b3d95e6c42940e37d313d91f6 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 14 Apr 2017 09:04:59 -0600 Subject: [PATCH 69/70] v0.4.0 changelog updates --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50a1e0a..ac0f8e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## v0.4.0 +- High-level integration tests using RSpec and tmux +- Add continuous integration with Circle CI +- Experimental support for asynchronous suggestions (#) +- Fix problems with multi-line suggestions (#) +- Optimize case where manually typing in suggestion +- Avoid wrapping any zle-* widgets (#) +- Remove support for deprecated options from v0.0.x +- Handle history entries that begin with dashes (#) +- Gracefully handle being sourced multiple times (#126) +- Add enable/disable/toggle widgets to disable/enable suggestions (#219) + + ## v0.3.3 - Switch from $history array to fc builtin for better performance with large HISTFILEs (#164) - Fix tilde handling when extended_glob is set (#168) From 14179d869d75f4d6b866ca72c5c349fb238c634e Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 14 Apr 2017 09:05:25 -0600 Subject: [PATCH 70/70] Bump version --- VERSION | 2 +- zsh-autosuggestions.zsh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 600e6fd..fb7a04c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v0.3.3 +v0.4.0 diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index bdc9bcc..0d389ec 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -1,6 +1,6 @@ # Fish-like fast/unobtrusive autosuggestions for zsh. # https://github.com/zsh-users/zsh-autosuggestions -# v0.3.3 +# v0.4.0 # Copyright (c) 2013 Thiago de Arruda # Copyright (c) 2016-2017 Eric Freese #