From ed8056c5e80c847709d7e76fbd634c2b8d61fd0d Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Thu, 16 Feb 2017 19:18:03 -0700 Subject: [PATCH] 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 }