Compare commits

...

130 Commits

Author SHA1 Message Date
Eric Freese
9f9237ab8a v0.4.1 2017-11-28 10:07:49 -07:00
Eric Freese
29257230fe Add missing issue/pr numbers from last version 2017-11-28 10:07:49 -07:00
Kaleb Elwert
940e10a691 Fix conditionals to use [[ and (( rather than [
This fixes a small issue in src/widgets.zsh which makes it so if you
alias [ to g[ (as is done in prezto if the gnu-utility module is loaded)
autosuggestions would fail.

The documentation for GNU test mentions that -o and -a should be avoided
if possible because it's not very clear. Also, with zsh and [[ -o
actually tests if an option is set, which makes this option even more
confusing.
2017-11-27 08:31:41 -07:00
Eric Freese
9f1f322979 Update comment about KEYS_QUEUED_COUNT support
Now that patch has released
2017-09-27 15:04:42 -06:00
Eric Freese
680ce21f26 Merge pull request #275 from zsh-users/fixes/warn_nested_var_opt
Use typeset -g to avoid warnnestedvar warnings
2017-09-27 15:02:15 -06:00
Eric Freese
256293cbb6 Use typeset -g to avoid warnnestedvar warnings
Fixes github issue #271
2017-09-27 13:24:06 -06:00
Eric Freese
977e70e21b Merge pull request #270 from ssiegel/fix-match_prev_cmd
Simplify escaping of pattern and fix match_prev_cmd strategy
2017-09-26 08:33:14 -06:00
Eric Freese
218acf2fbe Merge branch 'fixes/match_prev_cmd_special_chars' into develop 2017-09-26 08:30:43 -06:00
Eric Freese
0681a1c121 Remove flaky test that doesn't really matter that much
Seems like this would happen on some machines but not on others. Not
sure exactly what's going on, but this is such an edge case I'm just
going to remove the test.
2017-09-26 08:23:00 -06:00
Eric Freese
9e3f1bd359 Use https protocol instead of git in README instructions
https should work more broadly for folks
2017-09-26 07:56:29 -06:00
Eric Freese
ae4c344e82 Merge pull request #267 from isaacwhanson/fixes/match_prev_cmd_special_chars
escape pattern-matching chars on $prefix for match_prev_cmd strategy
2017-09-12 09:19:17 -06:00
Stefan Siegel
7f8ff2867c Simplify escaping of pattern and fix match_prev_cmd strategy
Maybe this is also a fix for #247, #248 and #258. Supersedes #267.

Testcase:
Using match_prev_cmd strategy and with these lines in history:
echo '1^'
echo '2^'
echo '1^'

type:
echo       (unexpected suggestion echo '1^' instead of echo '2^')
echo '1^1  (wrong suggestion echo '1^1echo '1^')
echo '1^#  (error "bad math expression")
2017-09-10 04:35:19 +02:00
Eric Freese
33b91a9dea Merge pull request #268 from thomas-mcdonald/patch-1
replace tabs in Rubocop config with spaces
2017-08-22 19:02:15 -06:00
Thomas McDonald
802f53a10a replace tabs in Rubocop config with spaces
YAML does not permit tabs and many parsers will error when loading a tab-indented file - http://www.yaml.org/faq.html
2017-08-20 23:36:18 +01:00
Isaac W Hanson
5e4487ed4a escape pattern-matching chars on $prefix for match_prev_cmd strategy 2017-08-18 16:00:42 -04:00
Eric Freese
9e110406fa Add test for special characters with match_prev_cmd strategy
Github #247 and #258
2017-08-18 11:18:07 -06:00
Alexander Neumann
1915e28882 Add 'emacs-forward-word'
This commit adds the 'emacs-forward-word' widget to the list of partial
accept widgets.
2017-08-18 11:14:21 -06:00
Eric Freese
d6d9a46981 Merge pull request #261 from alonhar/patch-1
Update README.md
2017-07-30 09:30:48 +02:00
Alon Harel
706499838c Update README.md 2017-07-24 21:19:07 +03:00
Eric Freese
e304365745 Merge pull request #256 from sumnerevans/patch-1
Added installation instructions for AUR, Homebrew
2017-07-08 09:58:35 +02:00
Sumner Evans
4e72f7a91e Added installation instructions for AUR, Homebrew 2017-07-07 09:17:46 -06:00
Eric Freese
2cb6eb6e29 Merge pull request #218 from zsh-users/develop
v0.4.0
2017-05-10 15:18:07 -06:00
Eric Freese
14179d869d Bump version 2017-05-10 15:05:43 -06:00
Eric Freese
281ed9bbf7 v0.4.0 changelog updates 2017-05-10 15:05:43 -06:00
Eric Freese
83129dd796 Make asynchronous suggestions disabled by default
While they are still experimental
2017-04-14 08:48:54 -06:00
Eric Freese
40b96f6cfd Merge pull request #223 from zsh-users/fixes/bpm_async_fix
Fixes/bpm async fix
2017-03-05 12:59:19 -05:00
Eric Freese
a2f0ffb122 Enabling suggestions should not fetch a suggestion if buffer is empty 2017-03-05 12:53:13 -05:00
Eric Freese
7d4a1d9a4a 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`.
2017-03-03 18:43:17 -05:00
Eric Freese
e1959d0f61 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
2017-03-03 18:43:17 -05:00
Eric Freese
c52c428793 Fix issues with widgets wrapped by other plugins
Puts in a better fix for #126 and related issues.
2017-03-03 18:43:10 -05:00
Eric Freese
ea505b01e5 Add a spec for unlisted widgets fetching a new suggestion 2017-03-03 18:43:10 -05:00
Eric Freese
502fb4a174 Make tmux_socket_name public so you can access easily from binding.pry
Can attach while tests are stopped with `tmux -L <socket_name> attach`
2017-03-03 18:43:10 -05:00
Eric Freese
ce362248fa Use pry-byebug instead of pry for more functionality 2017-03-03 18:43:10 -05:00
Eric Freese
cb93366d0e Merge pull request #221 from zsh-users/circle_ci_setup
Set up Circle CI
2017-02-26 14:36:15 -07:00
Eric Freese
39762ecd97 Set up circle ci 2017-02-26 14:26:41 -07:00
Eric Freese
468b7403e9 Test should be passing block to RSpec wait_for
Fixes flaky test
2017-02-26 14:18:22 -07:00
Eric Freese
c9a51e0c4c Handle dashes at the beginning of commands 2017-02-18 16:51:53 -07:00
Eric Freese
48a21bf79e [cleanup] Remove an extra newline 2017-02-18 11:27:55 -07:00
Eric Freese
4afbbbadda We only need to run the feature detection if starting async 2017-02-18 11:25:27 -07:00
Eric Freese
e3fa4e4904 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.
2017-02-18 11:15:53 -07:00
Eric Freese
2cd99e64b7 Add a test for modifying widget list vars after sourcing plugin 2017-02-18 11:15:53 -07:00
Eric Freese
c70d685d15 Clean up widget list spec 2017-02-18 11:12:10 -07:00
Eric Freese
255359dbb8 Use += to be a bit more true to the spec language 2017-02-18 10:35:30 -07:00
Eric Freese
4321fc097c 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.
2017-02-17 23:20:04 -07:00
Eric Freese
75e850577d Gracefully handle being sourced multiple times
Should fix #126
2017-02-17 23:19:55 -07:00
Eric Freese
a0fcd81ce1 Destroy zpty on load if it already exists 2017-02-17 22:47:28 -07:00
Eric Freese
39ca3dac45 Use a different name for feature detection zpty
So that it doesn't conflict when the file is sourced again
2017-02-17 22:07:48 -07:00
Eric Freese
dcce973287 Remove support for long-deprecated options
These options have been deprecated for over a year.
2017-02-17 18:45:46 -07:00
Christian Höltje
0c940e70f2 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
2017-02-17 18:32:52 -07:00
Eric Freese
a109f52fe4 Merge pull request #180 from zsh-users/features/async
Asynchronous suggestions
2017-02-17 18:28:54 -07:00
Eric Freese
23ef16c297 Do not show suggestions if the buffer is empty 2017-02-17 18:26:34 -07:00
Eric Freese
938144530c Fix tests 2017-02-17 16:01:07 -07:00
Eric Freese
c4bfd8e2c6 Need to prevent zpty feature detection from HUPing existing zptys 2017-02-17 15:51:50 -07:00
Eric Freese
c959408305 Only wait a max of 2 seconds for content to match after clearing screen 2017-02-17 15:33:09 -07:00
Eric Freese
06fca77ffb Readme updates for v0.4.0 2017-02-16 20:12:04 -07:00
Eric Freese
9feac573c9 Do not show any error output from async zpty server process 2017-02-16 19:27:32 -07:00
Eric Freese
ed8056c5e8 Lots of async changes 2017-02-16 19:19:30 -07:00
Eric Freese
38eb7cdafd Update license date 2017-02-16 19:07:41 -07:00
Eric Freese
64e7ec5bf8 Rename internal term session method 2017-01-29 10:43:20 -07:00
Eric Freese
98f926d53d Clean up TerminalSession constructor a bit 2017-01-29 10:43:00 -07:00
Eric Freese
51e8755634 TerminalSession methods return self to support chaining 2017-01-29 10:42:28 -07:00
Eric Freese
5151adfe40 Make TerminalSession#clear block until the screen is cleared 2017-01-29 10:40:05 -07:00
Eric Freese
2c465a932a Rename async pty name config var 2017-01-29 10:39:07 -07:00
Eric Freese
e3eb286ea2 Lots of little async cleanups 2017-01-27 15:18:26 -07:00
Eric Freese
c3425870f1 Wait for the terminal.clear to go through before continuing
Prevents some flakiness in tests
2017-01-27 14:07:06 -07:00
Eric Freese
89dd69d517 Add pry gem for debugging support 2017-01-27 14:06:37 -07:00
Eric Freese
40bb2e7804 little cleanup 2017-01-26 17:00:56 -07:00
Eric Freese
16666da488 Handle versions of zsh where zpty does not set REPLY to fd of opened pty
Based on e702ec4697/async.zsh (L400-L406)
2017-01-26 16:50:19 -07:00
Eric Freese
f33b605a63 Move async initialization into start function to keep in one place 2017-01-26 16:40:34 -07:00
Eric Freese
78ba07179a Add feature detection
Checks whether `zpty` gives a file descriptor, which was not the case in
older versions of zsh.

Based on a4b2f81c96/async.zsh (L395-L401)
2017-01-26 16:40:34 -07:00
Eric Freese
3f57198d07 Only bind widgets once, on initial sourcing 2017-01-26 16:23:27 -07:00
Eric Freese
2dbd261989 Allow configuring of zsh binary to run integration tests against 2017-01-26 16:04:46 -07:00
Eric Freese
6c5cd42331 Go back to tracking last pid because kill %1 didn't seem to be working 2017-01-25 00:00:53 -07:00
Eric Freese
54e1eee924 Optimize case where manually typing in a suggestion 2017-01-25 00:00:13 -07:00
Eric Freese
21d9eda5dd Wrap suggestion fetch command in parens to actually run in background 2017-01-24 23:59:38 -07:00
Eric Freese
50e6832b8c Escape the prefix passed into the match_prev_cmd strategy 2017-01-24 23:06:41 -07:00
Eric Freese
0305908adf 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.
2017-01-24 23:04:07 -07:00
Eric Freese
8e06a54b1c Add test for string with "\n" in it 2017-01-24 22:49:21 -07:00
Eric Freese
b3208b08af Pass the chosen strategy into the suggestion server pty 2017-01-24 22:48:30 -07:00
Eric Freese
ab2742537f Quote the suggestion to support sh_split_word option 2017-01-24 22:27:47 -07:00
Eric Freese
e5a5b0c1e0 Output only newlines in the pty 2017-01-24 22:27:09 -07:00
Eric Freese
0337005eb0 Disable word splitting while reading to preserve whitespace 2017-01-24 21:59:22 -07:00
Eric Freese
b530b0c996 Use zpty -r with pattern matching to fetch suggestion 2017-01-24 20:01:30 -07:00
Eric Freese
5c891afd48 Reset zsh options inside pty (from zsh-async) 2017-01-24 20:01:27 -07:00
Eric Freese
e33eb570c4 Send only the prefix to the suggestion server 2017-01-24 20:01:11 -07:00
Eric Freese
fba20b042e Use %1 instead of tracking pid 2017-01-24 20:00:50 -07:00
Eric Freese
0308ed797e Rename worker to server 2017-01-24 20:00:34 -07:00
Eric Freese
e72c2d87e5 add a bunch of comments 2017-01-24 19:53:59 -07:00
Eric Freese
ab8f295225 First pass at async functionality 2017-01-24 19:45:11 -07:00
Eric Freese
debbffc79a Add rspec test around accepting suggestions 2017-01-19 22:38:19 -07:00
Eric Freese
4850119887 Add separate test task for RSpec 2017-01-19 22:38:19 -07:00
Eric Freese
c22ab0e399 Implement suggestion integration tests in RSpec + tmux 2017-01-19 22:38:19 -07:00
Eric Freese
07a6768fcb Add TerminalSession helper for managing a tmux session 2017-01-19 22:38:19 -07:00
Eric Freese
e6591d5de0 Add RSpec for high-level integration testing 2017-01-19 22:33:17 -07:00
Eric Freese
af671fb406 Add ruby settings to editor config 2017-01-19 01:03:24 -07:00
Eric Freese
fedc22e9bb Merge pull request #169 from zsh-users/develop
v0.3.3
2016-10-17 07:45:49 -06:00
Eric Freese
9cfaf5d342 v0.3.3 2016-10-17 07:45:09 -06:00
Eric Freese
9333f0653f Update changelog for v0.3.3 2016-10-17 07:43:56 -06:00
Eric Freese
b377c39d0e Only fetch a new suggestion if buffer has changed 2016-07-31 20:10:22 -06:00
Eric Freese
a44aa59321 Remove unnecessary reset of POSTDISPLAy 2016-07-31 20:09:26 -06:00
Eric Freese
25f4afb058 Add ZSH_AUTOSUGGEST_IGNORE_WIDGETS array 2016-07-31 19:35:30 -06:00
Eric Freese
472394681e Merge pull request #178 from lbolla/issue141-develop
Disable autosuggest if buffer is too large
2016-07-18 10:28:45 -06:00
Lorenzo Bolla
a9c8efa048 Update README 2016-07-18 16:06:50 +01:00
Lorenzo Bolla
cdf56a3305 Include result of make 2016-07-18 10:56:21 +01:00
Lorenzo Bolla
2450c95d8a Rename and document new config var 2016-07-18 10:55:19 +01:00
Lorenzo Bolla
7b81eb79b8 Disable autosuggest if buffer is too large
Make buffer max size configurable, defaulted to infinity
2016-07-18 08:56:00 +01:00
Eric Freese
63816c5da8 Fix #164: Use fc builtin instead of $history array for lookup
According to a few tests, the `fc` builtin appears to be quite a bit
faster than searching through the `$history` associative array when
dealing with large history files (500K+).
2016-06-10 13:52:24 -06:00
Eric Freese
b4b3a82ee3 Fix #168 and #130: Escape tildes when fetching suggestions 2016-06-10 13:51:20 -06:00
Eric Freese
85ea943789 Merge pull request #163 from tsdh/develop
Document caveats of match_prev_cmd strategy
2016-05-28 13:34:00 -06:00
Tassilo Horn
ca70612d3c Document caveats of match_prev_cmd strategy
This strategy relies on the history being exactly in the order in which
commands have been entered.  Therefore, options like suppressing
duplicates or expiring duplicates first will lead to unexpected
suggestions.
2016-05-28 21:23:35 +02:00
Eric Freese
62cdba4e0f Merge pull request #142 from zsh-users/v0.3.x
v0.3.2
2016-05-28 08:28:18 -06:00
Eric Freese
cce68de46d v0.3.2 2016-05-28 08:27:08 -06:00
Eric Freese
4a2d9f9049 Fix Makefile to not create symbolic link after PR #156 was merged 2016-05-28 08:26:24 -06:00
Eric Freese
7c688ec20c Add note to readme about PRs going to develop branch 2016-05-28 08:23:53 -06:00
Eric Freese
96eb0fae77 Changelog updates for v0.3.2 2016-05-28 08:18:52 -06:00
Eric Freese
5e5bfdb659 Merge pull request #156 from adamkruszewski/v0.3.x
Adjust plugin.zsh file to run on zsh 5.1 in mSYS2.
2016-05-12 08:53:20 -06:00
adamkruszewski
0a6c34947c Adjust plugin.zsh file to run on zsh 5.1 in mSYS2. 2016-05-11 17:02:41 +02:00
Eric Freese
e87bc74654 Fix 118: Clear suggestion before original widget to fix completions
See PR #149
2016-04-25 14:42:09 -06:00
Eric Freese
964773aa75 Use array indices for forward compatibility
See issue #152
2016-04-25 14:26:37 -06:00
Eric Freese
945c660856 Fix #152 by escaping widget names inside evals
Solves problems when dealing with widget names with irregular
characters such as those that come from `opp.zsh`.
2016-04-25 14:26:35 -06:00
Eric Freese
011d8bdfd1 Refactor to remove prev cmd function and simplify escaping 2016-04-25 14:19:26 -06:00
Eric Freese
c5f57da2b8 Pull duplicated test logic from strategies into single test file 2016-04-22 14:14:29 -06:00
Eric Freese
c477db2696 Remove unused test variables from Makefile 2016-04-15 13:41:41 -06:00
Eric Freese
d7001f2c34 Fix readme test script reference. 2016-04-15 13:40:18 -06:00
Eric Freese
6d6e7820f3 Fix #143: Add vi-add-eol to list of accept widgets. 2016-04-15 13:37:50 -06:00
Eric Freese
9bef50fac1 Merge pull request #140 from zsh-users/fixes/bound_widget_return_code
Keep track of return value from original widget (#135)
2016-04-14 11:18:01 -06:00
Eric Freese
1d4f7e157e Keep track of return value from original widget (#135) 2016-04-06 17:13:04 -06:00
Eric Freese
2acf25e065 Formatting 2016-03-15 09:24:13 -06:00
Eric Freese
ba7109169d Clean up tests with setUp and tearDown functions 2016-03-15 09:05:55 -06:00
Eric Freese
a28e72e84a Test Runner now supports running specific tests and choosing zsh bin 2016-03-15 09:04:17 -06:00
61 changed files with 1556 additions and 1024 deletions

View File

@@ -8,3 +8,11 @@ indent_size = 4
[*.md]
indent_style = space
[*.rb]
indent_style = space
indent_size = 2
[*.yml]
indent_style = space
indent_size = 2

6
.gitmodules vendored
View File

@@ -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

3
.rspec Normal file
View File

@@ -0,0 +1,3 @@
--color
--require spec_helper
--format documentation

30
.rubocop.yml Normal file
View File

@@ -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

1
.ruby-version Normal file
View File

@@ -0,0 +1 @@
2.3.1

View File

@@ -1,5 +1,40 @@
# Changelog
## v0.4.1
- Switch to [[ and (( conditionals instead of [ (#257)
- Avoid warnnestedvar warnings with `typeset -g` (#275)
- Replace tabs with spaces in yaml (#268)
- Clean up and fix escaping of special characters (#267)
- Add `emacs-forward-word` to default list of partial accept widgets (#246)
## v0.4.0
- High-level integration tests using RSpec and tmux
- Add continuous integration with Circle CI
- Experimental support for asynchronous suggestions (#170)
- Fix problems with multi-line suggestions (#225)
- Optimize case where manually typing in suggestion
- Avoid wrapping any zle-* widgets (#206)
- 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)
- Add config option for maximum buffer length to fetch suggestions for (#178)
- Add config option for list of widgets to ignore (#184)
- Don't fetch a new suggestion unless a modification widget actually modifies the buffer (#183)
## v0.3.2
- Test runner now supports running specific tests and choosing zsh binary
- Return code from original widget is now correctly passed through (#135)
- Add `vi-add-eol` to list of accept widgets (#143)
- Escapes widget names within evals to fix problems with irregular widget names (#152)
- Plugin now clears suggestion while within a completion menu (#149)
- .plugin file no longer relies on symbolic link support, fixing issues on Windows (#156)
## v0.3.1
- Fixes issue with `vi-next-char` not accepting suggestion (#137).

5
Gemfile Normal file
View File

@@ -0,0 +1,5 @@
source 'https://rubygems.org'
gem 'rspec'
gem 'rspec-wait'
gem 'pry-byebug'

41
Gemfile.lock Normal file
View File

@@ -0,0 +1,41 @@
GEM
remote: https://rubygems.org/
specs:
byebug (9.0.5)
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)
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)
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)
slop (3.6.0)
PLATFORMS
ruby
DEPENDENCIES
pry-byebug
rspec
rspec-wait
BUNDLED WITH
1.13.6

View File

@@ -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

View File

@@ -1,15 +1,14 @@
SRC_DIR := ./src
TEST_DIR := ./script
VENDOR_DIR := ./vendor
SRC_FILES := \
$(SRC_DIR)/setup.zsh \
$(SRC_DIR)/config.zsh \
$(SRC_DIR)/deprecated.zsh \
$(SRC_DIR)/features.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 := \
@@ -19,41 +18,18 @@ HEADER_FILES := \
LICENSE
PLUGIN_TARGET := zsh-autosuggestions.zsh
OH_MY_ZSH_LINK_TARGET := zsh-autosuggestions.plugin.zsh
ALL_TARGETS := \
$(PLUGIN_TARGET) \
$(OH_MY_ZSH_LINK_TARGET)
SHUNIT2 := $(VENDOR_DIR)/shunit2/2.1.6
STUB_SH := $(VENDOR_DIR)/stub.sh/stub.sh
TEST_PREREQS := \
$(SHUNIT2) \
$(STUB_SH)
TEST_FILES := \
$(TEST_DIR)/**/*.zsh
all: $(ALL_TARGETS)
all: $(PLUGIN_TARGET)
$(PLUGIN_TARGET): $(HEADER_FILES) $(SRC_FILES)
cat $(HEADER_FILES) | sed -e 's/^/# /g' > $@
cat $(SRC_FILES) >> $@
$(OH_MY_ZSH_LINK_TARGET): $(PLUGIN_TARGET)
ln -s $(PLUGIN_TARGET) $@
$(SHUNIT2):
git submodule update --init vendor/shunit2
$(STUB_SH):
git submodule update --init vendor/stub.sh
.PHONY: clean
clean:
rm $(ALL_TARGETS)
rm $(PLUGIN_TARGET)
.PHONY: test
test: all $(TEST_PREREQS)
script/test_runner.zsh
test: all
@test -n "$$TEST_ZSH_BIN" && echo "Testing zsh binary: $(TEST_ZSH_BIN)" || true
bundle exec rspec $(TESTS)

View File

@@ -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)
<a href="https://asciinema.org/a/37390" target="_blank"><img src="https://asciinema.org/a/37390.png" width="400" /></a>
@@ -14,7 +16,7 @@ It suggests commands as you type, based on command history.
1. Clone this repository somewhere on your machine. This guide will assume `~/.zsh/zsh-autosuggestions`.
```sh
git clone git://github.com/zsh-users/zsh-autosuggestions ~/.zsh/zsh-autosuggestions
git clone https://github.com/zsh-users/zsh-autosuggestions ~/.zsh/zsh-autosuggestions
```
2. Add the following to your `.zshrc`:
@@ -31,7 +33,7 @@ It suggests commands as you type, based on command history.
1. Clone this repository into `$ZSH_CUSTOM/plugins` (by default `~/.oh-my-zsh/custom/plugins`)
```sh
git clone git://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions
git clone https://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions
```
2. Add the plugin to the list of plugins for Oh My Zsh to load:
@@ -42,6 +44,39 @@ It suggests commands as you type, based on command history.
3. Start a new terminal session.
### Arch Linux via the AUR
1. Install the [`zsh-autosuggestions`](https://aur.archlinux.org/packages/zsh-autosuggestions/) or the [`zsh-autosuggestions-git`](https://aur.archlinux.org/packages/zsh-autosuggestions-git/) packages from the [AUR](https://wiki.archlinux.org/index.php/Arch_User_Repository).
```sh
pacaur -S zsh-autosuggestions
```
or
```
pacaur -S zsh-autosuggestions-git
```
2. Add the following to your `.zshrc`:
```sh
source /usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh
```
3. Start a new terminal session.
### macOS via Homebrew
1. Install the `zsh-autosuggestions` package using [Homebrew](https://brew.sh/).
```sh
brew install zsh-autosuggestions
```
2. Add the following to your `.zshrc`:
```sh
source /usr/local/share/zsh-autosuggestions/zsh-autosuggestions.zsh
```
3. Start a new terminal session.
## Usage
@@ -69,7 +104,7 @@ Set `ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE` to configure the style that the suggestion
Set `ZSH_AUTOSUGGEST_STRATEGY` to choose the strategy for generating suggestions. There are currently two to choose from:
- `default`: Chooses the most recent match.
- `match_prev_cmd`: Chooses the most recent match whose preceding history item matches the most recently executed command ([more info](src/strategies/match_prev_cmd.zsh)).
- `match_prev_cmd`: Chooses the most recent match whose preceding history item matches the most recently executed command ([more info](src/strategies/match_prev_cmd.zsh)). Note that this strategy won't work as expected with ZSH options that don't preserve the history order such as `HIST_IGNORE_ALL_DUPS` or `HIST_EXPIRE_DUPS_FIRST`.
### Widget Mapping
@@ -80,19 +115,34 @@ This plugin works by triggering custom behavior when certain [zle widgets](http:
- `ZSH_AUTOSUGGEST_ACCEPT_WIDGETS`: Widgets in this array will accept the suggestion when invoked.
- `ZSH_AUTOSUGGEST_EXECUTE_WIDGETS`: Widgets in this array will execute the suggestion when invoked.
- `ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS`: Widgets in this array will partially accept the suggestion when invoked.
- `ZSH_AUTOSUGGEST_IGNORE_WIDGETS`: Widgets in this array will not trigger any custom behavior.
Widgets not in any of these lists will update the suggestion when invoked.
Widgets that modify the buffer and are not found in any of these arrays will fetch a new suggestion after they are invoked.
**Note:** A widget shouldn't belong to more than one of the above arrays.
### Disabling suggestion for large buffers
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.
### Enable Asynchronous Mode
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
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 <kbd>ctrl</kbd> + <kbd>space</kbd> to accept the current suggestion.
@@ -139,6 +189,7 @@ Edit the source files in `src/`. Run `make` to build `zsh-autosuggestions.zsh` f
Pull requests are welcome! If you send a pull request, please:
- Request to merge into the `develop` branch (*NOT* `master`)
- Match the existing coding conventions.
- Include helpful comments to keep the barrier-to-entry low for people new to the project.
- Write tests that cover your code as much as possible.
@@ -146,9 +197,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.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

View File

@@ -1 +1 @@
v0.3.1
v0.4.1

12
circle.yml Normal file
View File

@@ -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

View File

@@ -1,29 +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
}
local -a tests
# Test suites to run
tests=($TEST_DIR/**/*_test.zsh)
local retval=0
for suite in $tests; do
header "${suite#"$TEST_DIR"}"
zsh -f "$suite" || retval=$?
done
exit retval

View File

@@ -0,0 +1,27 @@
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
end
end

View File

@@ -0,0 +1,10 @@
describe 'a running zpty command' do
let(:before_sourcing) { -> { session.run_command('zmodload zsh/zpty && zpty -b kitty cat') } }
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

View File

@@ -0,0 +1,13 @@
describe 'rebinding [' do
context 'initialized before sourcing the plugin' do
before do
session.run_command("function [ { $commands[\\[] \"$@\" }")
session.clear_screen
end
it 'executes the custom behavior and the built-in behavior' do
session.send_string('asdf')
wait_for { session.content }.to eq('asdf')
end
end
end

View File

@@ -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

View File

@@ -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

13
spec/multi_line_spec.rb Normal file
View File

@@ -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

View File

@@ -0,0 +1,19 @@
context 'when async suggestions are enabled' do
let(:options) { ["ZSH_AUTOSUGGEST_USE_ASYNC="] }
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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,7 @@
describe 'suggestion fetching' do
it 'is performed synchronously'
context 'when ZSH_AUTOSUGGEST_USE_ASYNC is set' do
it 'is performed asynchronously'
end
end

View File

@@ -0,0 +1,97 @@
describe 'a zle widget' do
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+=(#{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+=(#{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+=(#{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
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("\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
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
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

50
spec/spec_helper.rb Normal file
View File

@@ -0,0 +1,50 @@
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
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.wait_timeout = 2
config.include_context 'terminal session'
end

View File

@@ -0,0 +1,12 @@
require 'strategies/special_characters_helper'
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
include_examples 'special characters'
end

View File

@@ -0,0 +1,21 @@
require 'strategies/special_characters_helper'
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
include_examples 'special characters'
end

View File

@@ -0,0 +1,62 @@
shared_examples 'special characters' do
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
with_history('-foo() {}') do
session.send_string('-')
wait_for { session.content }.to eq('-foo() {}')
end
end
end
end

91
spec/terminal_session.rb Normal file
View File

@@ -0,0 +1,91 @@
require 'securerandom'
class TerminalSession
ZSH_BIN = ENV['TEST_ZSH_BIN'] || 'zsh'
def initialize(opts = {})
opts = {
width: 80,
height: 24,
prompt: '',
term: 'xterm-256color',
zsh_bin: ZSH_BIN
}.merge(opts)
@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
def tmux_socket_name
@tmux_socket_name ||= SecureRandom.hex(6)
end
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 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
tmux_command(cmd).strip
end
def clear_screen
send_keys('C-l')
i = 0
until content == opts[:prompt] || i > 20 do
sleep(0.1)
i = i + 1
end
self
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
attr_reader :opts
def tmux_command(cmd)
out = `tmux -u -L #{tmux_socket_name} #{cmd}`
raise("tmux error running: '#{cmd}'") unless $?.success?
out
end
end

View File

@@ -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

View File

@@ -0,0 +1,42 @@
describe 'the `autosuggest-enable` widget' do
before do
session.
run_command('typeset -g _ZSH_AUTOSUGGEST_DISABLED').
run_command('bindkey ^B autosuggest-enable')
end
it 'enables suggestions and fetches a suggestion' do
with_history('echo hello') do
session.send_string('e')
sleep 1
expect(session.content).to eq('e')
session.send_keys('C-b')
session.send_string('c')
wait_for { session.content }.to eq('echo hello')
end
end
context 'invoked on an empty buffer' do
it 'does not fetch a suggestion' do
with_history('echo hello') do
session.send_keys('C-b')
sleep 1
expect(session.content).to eq('')
end
end
end
context 'invoked on a non-empty buffer' do
it 'fetches a suggestion' do
with_history('echo hello') do
session.send_string('e')
sleep 1
expect(session.content).to eq('e')
session.send_keys('C-b')
wait_for { session.content }.to eq('echo hello')
end
end
end
end

View File

@@ -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

View File

@@ -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

109
src/async.zsh Normal file
View File

@@ -0,0 +1,109 @@
#--------------------------------------------------------------------#
# Async #
#--------------------------------------------------------------------#
# 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
# Silence any error messages
exec 2>/dev/null
local strategy=$1
local last_pid
while IFS='' read -r -d $'\0' query; do
# Kill last bg process
kill -KILL $last_pid &>/dev/null
# Run suggestion search in the background
(
local suggestion
_zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY "$query"
echo -n -E "$suggestion"$'\0'
) &
last_pid=$!
done
}
_zsh_autosuggest_async_request() {
# Write the query to the zpty process to fetch a suggestion
zpty -w -n $ZSH_AUTOSUGGEST_ASYNC_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_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'##}"
}
_zsh_autosuggest_async_pty_create() {
# 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 )); 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
# 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
if (( REPLY )); then
_ZSH_AUTOSUGGEST_PTY_FD=$REPLY
else
_ZSH_AUTOSUGGEST_PTY_FD=$zptyfd
fi
# Set up input handler from the zpty
zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_response
}
_zsh_autosuggest_async_pty_destroy() {
if zpty -t $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null; then
# Remove the input handler
zle -F $_ZSH_AUTOSUGGEST_PTY_FD &>/dev/null
# Destroy the zpty
zpty -d $ZSH_AUTOSUGGEST_ASYNC_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_feature_detect_zpty_returns_fd
_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
}

View File

@@ -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
typeset -gi bind_count=$_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]
}
_zsh_autosuggest_get_bind_count() {
if ((${+_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]})); then
typeset -gi bind_count=$_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]
else
typeset -gi 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,48 +38,63 @@ _zsh_autosuggest_bind_widget() {
# User-defined widget
user:*)
zle -N $prefix$widget ${widgets[$widget]#*:}
_zsh_autosuggest_incr_bind_count $widget
zle -N $prefix${bind_count}-$widget ${widgets[$widget]#*:}
;;
# Built-in widget
builtin)
eval "_zsh_autosuggest_orig_$widget() { zle .$widget }"
zle -N $prefix$widget _zsh_autosuggest_orig_$widget
_zsh_autosuggest_incr_bind_count $widget
eval "_zsh_autosuggest_orig_${(q)widget}() { zle .${(q)widget} }"
zle -N $prefix${bind_count}-$widget _zsh_autosuggest_orig_$widget
;;
# Completion widget
completion:*)
eval "zle -C $prefix$widget ${${widgets[$widget]#*:}/:/ }"
_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_$widget() {
_zsh_autosuggest_widget_$autosuggest_action $prefix$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
_zsh_autosuggest_bind_widgets() {
local widget;
local widget
local ignore_widgets
ignore_widgets=(
.\*
_\*
zle-\*
autosuggest-\*
$ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX\*
$ZSH_AUTOSUGGEST_IGNORE_WIDGETS
)
# Find every widget we might want to bind and bind it appropriately
for widget in ${${(f)"$(builtin zle -la)"}:#(.*|_*|orig-*|autosuggest-*|$ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX*|zle-line-*|run-help|which-command|beep|set-local-history|yank)}; do
if [ ${ZSH_AUTOSUGGEST_CLEAR_WIDGETS[(r)$widget]} ]; then
for widget in ${${(f)"$(builtin zle -la)"}:#${(j:|:)~ignore_widgets}}; do
if [[ -n ${ZSH_AUTOSUGGEST_CLEAR_WIDGETS[(r)$widget]} ]]; then
_zsh_autosuggest_bind_widget $widget clear
elif [ ${ZSH_AUTOSUGGEST_ACCEPT_WIDGETS[(r)$widget]} ]; then
elif [[ -n ${ZSH_AUTOSUGGEST_ACCEPT_WIDGETS[(r)$widget]} ]]; then
_zsh_autosuggest_bind_widget $widget accept
elif [ ${ZSH_AUTOSUGGEST_EXECUTE_WIDGETS[(r)$widget]} ]; then
elif [[ -n ${ZSH_AUTOSUGGEST_EXECUTE_WIDGETS[(r)$widget]} ]]; then
_zsh_autosuggest_bind_widget $widget execute
elif [ ${ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS[(r)$widget]} ]; then
elif [[ -n ${ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS[(r)$widget]} ]]; then
_zsh_autosuggest_bind_widget $widget partial_accept
else
# Assume any unspecified widget might modify the buffer
@@ -69,13 +106,13 @@ _zsh_autosuggest_bind_widgets() {
# Given the name of an original widget and args, invoke it, if it exists
_zsh_autosuggest_invoke_original_widget() {
# Do nothing unless called with at least one arg
[ $# -gt 0 ] || return
(( $# )) || return
local original_widget_name="$1"
shift
if [ $widgets[$original_widget_name] ]; then
if (( ${+widgets[$original_widget_name]} )); then
zle $original_widget_name -- $@
fi
}

View File

@@ -32,6 +32,7 @@ ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=(
end-of-line
vi-forward-char
vi-end-of-line
vi-add-eol
)
# Widgets that accept the entire suggestion and execute it
@@ -41,8 +42,25 @@ ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=(
# Widgets that accept the suggestion as far as the cursor moves
ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=(
forward-word
emacs-forward-word
vi-forward-word
vi-forward-word-end
vi-forward-blank-word
vi-forward-blank-word-end
)
# Widgets that should be ignored (globbing supported but must be escaped)
ZSH_AUTOSUGGEST_IGNORE_WIDGETS=(
orig-\*
beep
run-help
set-local-history
which-command
yank
)
# Max size of buffer to trigger autosuggestion. Leave undefined for no upper bound.
ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=
# Pty name for calculating autosuggestions asynchronously
ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_pty

View File

@@ -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

19
src/features.zsh Normal file
View File

@@ -0,0 +1,19 @@
#--------------------------------------------------------------------#
# Feature Detection #
#--------------------------------------------------------------------#
_zsh_autosuggest_feature_detect_zpty_returns_fd() {
typeset -g _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD
typeset -h REPLY
zpty zsh_autosuggest_feature_detect '{ zshexit() { kill -KILL $$; sleep 1 } }'
if (( REPLY )); then
_ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=1
else
_ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=0
fi
zpty -d zsh_autosuggest_feature_detect
}

View File

@@ -7,7 +7,7 @@
_zsh_autosuggest_highlight_reset() {
typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
if [ -n "$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT" ]; then
if [[ -n "$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT" ]]; then
region_highlight=("${(@)region_highlight:#$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT}")
unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
fi
@@ -17,8 +17,8 @@ _zsh_autosuggest_highlight_reset() {
_zsh_autosuggest_highlight_apply() {
typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
if [ $#POSTDISPLAY -gt 0 ]; then
_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT="$#BUFFER $(($#BUFFER + $#POSTDISPLAY)) $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE"
if (( $#POSTDISPLAY )); then
typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT="$#BUFFER $(($#BUFFER + $#POSTDISPLAY)) $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE"
region_highlight+=("$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT")
else
unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT

10
src/setup.zsh Normal file
View File

@@ -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

View File

@@ -5,9 +5,20 @@
# Start the autosuggestion widgets
_zsh_autosuggest_start() {
_zsh_autosuggest_check_deprecated_config
add-zsh-hook -d precmd _zsh_autosuggest_start
_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
}
autoload -Uz add-zsh-hook
# Start the autosuggestion widgets on the next precmd
add-zsh-hook precmd _zsh_autosuggest_start

View File

@@ -7,12 +7,9 @@
#
_zsh_autosuggest_strategy_default() {
local prefix="$(_zsh_autosuggest_escape_command "$1")"
local prefix="$1"
# 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]]}"
# Get the history items that match
# - (r) subscript flag makes the pattern match on values
typeset -g suggestion="${history[(r)${(b)prefix}*]}"
}

View File

@@ -16,21 +16,23 @@
# will be 'ls foo' rather than 'ls bar' because your most recently
# executed command (pwd) was previously followed by 'ls foo'.
#
# Note that this strategy won't work as expected with ZSH options that don't
# preserve the history order such as `HIST_IGNORE_ALL_DUPS` or
# `HIST_EXPIRE_DUPS_FIRST`.
_zsh_autosuggest_strategy_match_prev_cmd() {
local prefix="$(_zsh_autosuggest_escape_command "$1")"
local prefix="$1"
# Get all history event numbers that correspond to history
# entries that match pattern $prefix*
local history_match_keys
history_match_keys=(${(k)history[(R)$prefix*]})
history_match_keys=(${(k)history[(R)${(b)prefix}*]})
# By default we use the first history number (most recent history entry)
local histkey="${history_match_keys[1]}"
# Get the previously executed command
local prev_cmd="$(_zsh_autosuggest_prev_command)"
prev_cmd="$(_zsh_autosuggest_escape_command "$prev_cmd")"
local prev_cmd="${history[$((HISTCMD-1))]}"
# Iterate up to the first 200 history event numbers that match $prefix
for key in "${(@)history_match_keys[1,200]}"; do
@@ -45,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
typeset -g suggestion="$history[$histkey]"
}

View File

@@ -1,26 +0,0 @@
#--------------------------------------------------------------------#
# Suggestion #
#--------------------------------------------------------------------#
# Delegate to the selected strategy to determine a suggestion
_zsh_autosuggest_suggestion() {
local prefix="$1"
local strategy_function="_zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY"
if [ -n "$functions[$strategy_function]" ]; then
echo -E "$($strategy_function "$prefix")"
fi
}
_zsh_autosuggest_escape_command() {
setopt localoptions EXTENDED_GLOB
# Escape special chars in the string (requires EXTENDED_GLOB)
echo -E "${1//(#m)[\\()\[\]|*?]/\\$MATCH}"
}
# Get the previously executed command
_zsh_autosuggest_prev_command() {
echo -E "${history[$((HISTCMD-1))]}"
}

View File

@@ -3,6 +3,30 @@
# Autosuggest Widget Implementations #
#--------------------------------------------------------------------#
# Disable suggestions
_zsh_autosuggest_disable() {
typeset -g _ZSH_AUTOSUGGEST_DISABLED
_zsh_autosuggest_clear
}
# Enable suggestions
_zsh_autosuggest_enable() {
unset _ZSH_AUTOSUGGEST_DISABLED
if (( $#BUFFER )); then
_zsh_autosuggest_fetch
fi
}
# Toggle suggestions (enable/disable)
_zsh_autosuggest_toggle() {
if [[ -n "${_ZSH_AUTOSUGGEST_DISABLED+x}" ]]; then
_zsh_autosuggest_enable
else
_zsh_autosuggest_disable
fi
}
# Clear the suggestion
_zsh_autosuggest_clear() {
# Remove the suggestion
@@ -13,17 +37,75 @@ _zsh_autosuggest_clear() {
# Modify the buffer and get a new suggestion
_zsh_autosuggest_modify() {
# Original widget modifies the buffer
_zsh_autosuggest_invoke_original_widget $@
local -i retval
# Get a new suggestion if the buffer is not empty after modification
local suggestion
if [ $#BUFFER -gt 0 ]; then
suggestion="$(_zsh_autosuggest_suggestion "$BUFFER")"
# Only available in zsh >= 5.4
local -i KEYS_QUEUED_COUNT
# Save the contents of the buffer/postdisplay
local orig_buffer="$BUFFER"
local orig_postdisplay="$POSTDISPLAY"
# Clear suggestion while waiting for next one
unset POSTDISPLAY
# Original widget may modify the buffer
_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
# Add the suggestion to the POSTDISPLAY
if [ -n "$suggestion" ]; then
# Optimize if manually typing in the suggestion
if (( $#BUFFER > $#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"
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 > 0 )); then
if [[ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]] || (( $#BUFFER <= $ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE )); then
_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_ASYNC_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" ]] && (( $#BUFFER )); then
POSTDISPLAY="${suggestion#$BUFFER}"
else
unset POSTDISPLAY
@@ -36,12 +118,12 @@ _zsh_autosuggest_accept() {
# When vicmd keymap is active, the cursor can't move all the way
# to the end of the buffer
if [ "$KEYMAP" = "vicmd" ]; then
if [[ "$KEYMAP" = "vicmd" ]]; then
max_cursor_pos=$((max_cursor_pos - 1))
fi
# Only accept if the cursor is at the end of the buffer
if [ $CURSOR -eq $max_cursor_pos ]; then
if [[ $CURSOR = $max_cursor_pos ]]; then
# Add the suggestion to the buffer
BUFFER="$BUFFER$POSTDISPLAY"
@@ -70,6 +152,8 @@ _zsh_autosuggest_execute() {
# Partially accept the suggestion
_zsh_autosuggest_partial_accept() {
local -i retval
# Save the contents of the buffer so we can restore later if needed
local original_buffer="$BUFFER"
@@ -78,9 +162,10 @@ _zsh_autosuggest_partial_accept() {
# Original widget moves the cursor
_zsh_autosuggest_invoke_original_widget $@
retval=$?
# If we've moved past the end of the original buffer
if [ $CURSOR -gt $#original_buffer ]; then
if (( $CURSOR > $#original_buffer )); then
# Set POSTDISPLAY to text right of the cursor
POSTDISPLAY="$RBUFFER"
@@ -90,16 +175,32 @@ _zsh_autosuggest_partial_accept() {
# Restore the original buffer
BUFFER="$original_buffer"
fi
return $retval
}
for action in clear modify 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
_zsh_autosuggest_highlight_reset
_zsh_autosuggest_$action \$@
retval=\$?
_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
zle -N autosuggest-enable _zsh_autosuggest_widget_enable
zle -N autosuggest-disable _zsh_autosuggest_widget_disable
zle -N autosuggest-toggle _zsh_autosuggest_widget_toggle

View File

@@ -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"

View File

@@ -1,114 +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'
}
testBackslash() {
set_history <<-'EOF'
echo "hello\nworld"
EOF
assertSuggestion \
'echo "hello\' \
'echo "hello\nworld"'
}
testDoubleBackslash() {
set_history <<-'EOF'
echo "\\"
EOF
assertSuggestion \
'echo "\\' \
'echo "\\"'
}
testTilde() {
set_history <<-'EOF'
cd ~/something
EOF
assertSuggestion \
'cd' \
'cd ~/something'
assertSuggestion \
'cd ~' \
'cd ~/something'
assertSuggestion \
'cd ~/s' \
'cd ~/something'
}
testParentheses() {
set_history <<-'EOF'
echo "$(ls foo)"
EOF
assertSuggestion \
'echo "$(' \
'echo "$(ls foo)"'
}
testSquareBrackets() {
set_history <<-'EOF'
echo "$history[123]"
EOF
assertSuggestion \
'echo "$history[' \
'echo "$history[123]"'
}
run_tests "$0"

View File

@@ -1,132 +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'
}
testBackslash() {
set_history <<-'EOF'
echo "hello\nworld"
EOF
assertSuggestion \
'echo "hello\' \
'echo "hello\nworld"'
}
testDoubleBackslash() {
set_history <<-'EOF'
echo "\\"
EOF
assertSuggestion \
'echo "\\' \
'echo "\\"'
}
testTilde() {
set_history <<-'EOF'
cd ~/something
EOF
assertSuggestion \
'cd' \
'cd ~/something'
assertSuggestion \
'cd ~' \
'cd ~/something'
assertSuggestion \
'cd ~/s' \
'cd ~/something'
}
testParentheses() {
set_history <<-'EOF'
echo "$(ls foo)"
EOF
assertSuggestion \
'echo "$(' \
'echo "$(ls foo)"'
}
testSquareBrackets() {
set_history <<-'EOF'
echo "$history[123]"
EOF
assertSuggestion \
'echo "$history[' \
'echo "$history[123]"'
}
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"

View File

@@ -1,72 +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 '?')"
}
testPrevCommand() {
set_history <<-'EOF'
ls foo
ls bar
ls baz
EOF
assertEquals \
'Did not output the last command' \
'ls baz' \
"$(_zsh_autosuggest_prev_command)"
set_history <<-'EOF'
ls foo
ls bar
ls baz
ls quux
ls foobar
EOF
assertEquals \
'Did not output the last command' \
'ls foobar' \
"$(_zsh_autosuggest_prev_command)"
}
run_tests "$0"

View File

@@ -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>" \
"$expected_suggestion" \
"$(_zsh_autosuggest_suggestion "$prefix")"
}

View File

@@ -1,133 +0,0 @@
#!/usr/bin/env zsh
source "${0:a:h}/../test_helper.zsh"
oneTimeSetUp() {
source_autosuggestions
}
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"
}
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'
}
run_tests "$0"

View File

@@ -1,51 +0,0 @@
#!/usr/bin/env zsh
source "${0:a:h}/../test_helper.zsh"
oneTimeSetUp() {
source_autosuggestions
}
testClear() {
BUFFER='ec'
POSTDISPLAY='ho hello'
_zsh_autosuggest_clear 'original-widget'
assertEquals \
'BUFFER was modified' \
'ec' \
"$BUFFER"
assertNull \
'POSTDISPLAY was not cleared' \
"$POSTDISPLAY"
}
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'
}
run_tests "$0"

View File

@@ -1,41 +0,0 @@
#!/usr/bin/env zsh
source "${0:a:h}/../test_helper.zsh"
oneTimeSetUp() {
source_autosuggestions
}
testModify() {
BUFFER=''
POSTDISPLAY=''
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"
restore _zsh_autosuggest_invoke_original_widget
restore _zsh_autosuggest_suggestion
}
run_tests "$0"

View File

@@ -1,61 +0,0 @@
#!/usr/bin/env zsh
source "${0:a:h}/../test_helper.zsh"
oneTimeSetUp() {
source_autosuggestions
}
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"
}
run_tests "$0"

1
vendor/shunit2 vendored

Submodule vendor/shunit2 deleted from 46973db9df

1
vendor/stub.sh vendored

Submodule vendor/stub.sh deleted from bd6f3c4666

View File

@@ -1 +0,0 @@
zsh-autosuggestions.zsh

View File

@@ -0,0 +1 @@
source ${0:A:h}/zsh-autosuggestions.zsh

View File

@@ -1,8 +1,8 @@
# Fish-like fast/unobtrusive autosuggestions for zsh.
# https://github.com/zsh-users/zsh-autosuggestions
# v0.3.1
# v0.4.1
# 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
@@ -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 #
#--------------------------------------------------------------------#
@@ -58,6 +68,7 @@ ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=(
end-of-line
vi-forward-char
vi-end-of-line
vi-add-eol
)
# Widgets that accept the entire suggestion and execute it
@@ -67,58 +78,80 @@ ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=(
# Widgets that accept the suggestion as far as the cursor moves
ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=(
forward-word
emacs-forward-word
vi-forward-word
vi-forward-word-end
vi-forward-blank-word
vi-forward-blank-word-end
)
# Widgets that should be ignored (globbing supported but must be escaped)
ZSH_AUTOSUGGEST_IGNORE_WIDGETS=(
orig-\*
beep
run-help
set-local-history
which-command
yank
)
# Max size of buffer to trigger autosuggestion. Leave undefined for no upper bound.
ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=
# Pty name for calculating autosuggestions asynchronously
ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_pty
#--------------------------------------------------------------------#
# Handle Deprecated Variables/Widgets #
# Feature Detection #
#--------------------------------------------------------------------#
_zsh_autosuggest_deprecated_warning() {
>&2 echo "zsh-autosuggestions: $@"
}
_zsh_autosuggest_feature_detect_zpty_returns_fd() {
typeset -g _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD
typeset -h REPLY
_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
zpty zsh_autosuggest_feature_detect '{ zshexit() { kill -KILL $$; sleep 1 } }'
if (( REPLY )); then
_ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=1
else
_ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=0
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
zpty -d zsh_autosuggest_feature_detect
}
_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 #
#--------------------------------------------------------------------#
_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
typeset -gi bind_count=$_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]
}
_zsh_autosuggest_get_bind_count() {
if ((${+_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]})); then
typeset -gi bind_count=$_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]
else
typeset -gi 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
@@ -126,48 +159,63 @@ _zsh_autosuggest_bind_widget() {
# User-defined widget
user:*)
zle -N $prefix$widget ${widgets[$widget]#*:}
_zsh_autosuggest_incr_bind_count $widget
zle -N $prefix${bind_count}-$widget ${widgets[$widget]#*:}
;;
# Built-in widget
builtin)
eval "_zsh_autosuggest_orig_$widget() { zle .$widget }"
zle -N $prefix$widget _zsh_autosuggest_orig_$widget
_zsh_autosuggest_incr_bind_count $widget
eval "_zsh_autosuggest_orig_${(q)widget}() { zle .${(q)widget} }"
zle -N $prefix${bind_count}-$widget _zsh_autosuggest_orig_$widget
;;
# Completion widget
completion:*)
eval "zle -C $prefix$widget ${${widgets[$widget]#*:}/:/ }"
_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_$widget() {
_zsh_autosuggest_widget_$autosuggest_action $prefix$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
_zsh_autosuggest_bind_widgets() {
local widget;
local widget
local ignore_widgets
ignore_widgets=(
.\*
_\*
zle-\*
autosuggest-\*
$ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX\*
$ZSH_AUTOSUGGEST_IGNORE_WIDGETS
)
# Find every widget we might want to bind and bind it appropriately
for widget in ${${(f)"$(builtin zle -la)"}:#(.*|_*|orig-*|autosuggest-*|$ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX*|zle-line-*|run-help|which-command|beep|set-local-history|yank)}; do
if [ ${ZSH_AUTOSUGGEST_CLEAR_WIDGETS[(r)$widget]} ]; then
for widget in ${${(f)"$(builtin zle -la)"}:#${(j:|:)~ignore_widgets}}; do
if [[ -n ${ZSH_AUTOSUGGEST_CLEAR_WIDGETS[(r)$widget]} ]]; then
_zsh_autosuggest_bind_widget $widget clear
elif [ ${ZSH_AUTOSUGGEST_ACCEPT_WIDGETS[(r)$widget]} ]; then
elif [[ -n ${ZSH_AUTOSUGGEST_ACCEPT_WIDGETS[(r)$widget]} ]]; then
_zsh_autosuggest_bind_widget $widget accept
elif [ ${ZSH_AUTOSUGGEST_EXECUTE_WIDGETS[(r)$widget]} ]; then
elif [[ -n ${ZSH_AUTOSUGGEST_EXECUTE_WIDGETS[(r)$widget]} ]]; then
_zsh_autosuggest_bind_widget $widget execute
elif [ ${ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS[(r)$widget]} ]; then
elif [[ -n ${ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS[(r)$widget]} ]]; then
_zsh_autosuggest_bind_widget $widget partial_accept
else
# Assume any unspecified widget might modify the buffer
@@ -179,13 +227,13 @@ _zsh_autosuggest_bind_widgets() {
# Given the name of an original widget and args, invoke it, if it exists
_zsh_autosuggest_invoke_original_widget() {
# Do nothing unless called with at least one arg
[ $# -gt 0 ] || return
(( $# )) || return
local original_widget_name="$1"
shift
if [ $widgets[$original_widget_name] ]; then
if (( ${+widgets[$original_widget_name]} )); then
zle $original_widget_name -- $@
fi
}
@@ -198,7 +246,7 @@ _zsh_autosuggest_invoke_original_widget() {
_zsh_autosuggest_highlight_reset() {
typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
if [ -n "$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT" ]; then
if [[ -n "$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT" ]]; then
region_highlight=("${(@)region_highlight:#$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT}")
unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
fi
@@ -208,8 +256,8 @@ _zsh_autosuggest_highlight_reset() {
_zsh_autosuggest_highlight_apply() {
typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
if [ $#POSTDISPLAY -gt 0 ]; then
_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT="$#BUFFER $(($#BUFFER + $#POSTDISPLAY)) $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE"
if (( $#POSTDISPLAY )); then
typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT="$#BUFFER $(($#BUFFER + $#POSTDISPLAY)) $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE"
region_highlight+=("$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT")
else
unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
@@ -220,6 +268,30 @@ _zsh_autosuggest_highlight_apply() {
# Autosuggest Widget Implementations #
#--------------------------------------------------------------------#
# Disable suggestions
_zsh_autosuggest_disable() {
typeset -g _ZSH_AUTOSUGGEST_DISABLED
_zsh_autosuggest_clear
}
# Enable suggestions
_zsh_autosuggest_enable() {
unset _ZSH_AUTOSUGGEST_DISABLED
if (( $#BUFFER )); then
_zsh_autosuggest_fetch
fi
}
# Toggle suggestions (enable/disable)
_zsh_autosuggest_toggle() {
if [[ -n "${_ZSH_AUTOSUGGEST_DISABLED+x}" ]]; then
_zsh_autosuggest_enable
else
_zsh_autosuggest_disable
fi
}
# Clear the suggestion
_zsh_autosuggest_clear() {
# Remove the suggestion
@@ -230,17 +302,75 @@ _zsh_autosuggest_clear() {
# Modify the buffer and get a new suggestion
_zsh_autosuggest_modify() {
# Original widget modifies the buffer
_zsh_autosuggest_invoke_original_widget $@
local -i retval
# Get a new suggestion if the buffer is not empty after modification
local suggestion
if [ $#BUFFER -gt 0 ]; then
suggestion="$(_zsh_autosuggest_suggestion "$BUFFER")"
# Only available in zsh >= 5.4
local -i KEYS_QUEUED_COUNT
# Save the contents of the buffer/postdisplay
local orig_buffer="$BUFFER"
local orig_postdisplay="$POSTDISPLAY"
# Clear suggestion while waiting for next one
unset POSTDISPLAY
# Original widget may modify the buffer
_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
# Add the suggestion to the POSTDISPLAY
if [ -n "$suggestion" ]; then
# Optimize if manually typing in the suggestion
if (( $#BUFFER > $#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"
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 > 0 )); then
if [[ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]] || (( $#BUFFER <= $ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE )); then
_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_ASYNC_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" ]] && (( $#BUFFER )); then
POSTDISPLAY="${suggestion#$BUFFER}"
else
unset POSTDISPLAY
@@ -253,12 +383,12 @@ _zsh_autosuggest_accept() {
# When vicmd keymap is active, the cursor can't move all the way
# to the end of the buffer
if [ "$KEYMAP" = "vicmd" ]; then
if [[ "$KEYMAP" = "vicmd" ]]; then
max_cursor_pos=$((max_cursor_pos - 1))
fi
# Only accept if the cursor is at the end of the buffer
if [ $CURSOR -eq $max_cursor_pos ]; then
if [[ $CURSOR = $max_cursor_pos ]]; then
# Add the suggestion to the buffer
BUFFER="$BUFFER$POSTDISPLAY"
@@ -287,6 +417,8 @@ _zsh_autosuggest_execute() {
# Partially accept the suggestion
_zsh_autosuggest_partial_accept() {
local -i retval
# Save the contents of the buffer so we can restore later if needed
local original_buffer="$BUFFER"
@@ -295,9 +427,10 @@ _zsh_autosuggest_partial_accept() {
# Original widget moves the cursor
_zsh_autosuggest_invoke_original_widget $@
retval=$?
# If we've moved past the end of the original buffer
if [ $CURSOR -gt $#original_buffer ]; then
if (( $CURSOR > $#original_buffer )); then
# Set POSTDISPLAY to text right of the cursor
POSTDISPLAY="$RBUFFER"
@@ -307,45 +440,35 @@ _zsh_autosuggest_partial_accept() {
# Restore the original buffer
BUFFER="$original_buffer"
fi
return $retval
}
for action in clear modify 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
_zsh_autosuggest_highlight_reset
_zsh_autosuggest_$action \$@
retval=\$?
_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
#--------------------------------------------------------------------#
# Suggestion #
#--------------------------------------------------------------------#
# Delegate to the selected strategy to determine a suggestion
_zsh_autosuggest_suggestion() {
local prefix="$1"
local strategy_function="_zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY"
if [ -n "$functions[$strategy_function]" ]; then
echo -E "$($strategy_function "$prefix")"
fi
}
_zsh_autosuggest_escape_command() {
setopt localoptions EXTENDED_GLOB
# Escape special chars in the string (requires EXTENDED_GLOB)
echo -E "${1//(#m)[\\()\[\]|*?]/\\$MATCH}"
}
# Get the previously executed command
_zsh_autosuggest_prev_command() {
echo -E "${history[$((HISTCMD-1))]}"
}
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 #
@@ -355,14 +478,11 @@ _zsh_autosuggest_prev_command() {
#
_zsh_autosuggest_strategy_default() {
local prefix="$(_zsh_autosuggest_escape_command "$1")"
local prefix="$1"
# 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]]}"
# Get the history items that match
# - (r) subscript flag makes the pattern match on values
typeset -g suggestion="${history[(r)${(b)prefix}*]}"
}
#--------------------------------------------------------------------#
@@ -382,21 +502,23 @@ _zsh_autosuggest_strategy_default() {
# will be 'ls foo' rather than 'ls bar' because your most recently
# executed command (pwd) was previously followed by 'ls foo'.
#
# Note that this strategy won't work as expected with ZSH options that don't
# preserve the history order such as `HIST_IGNORE_ALL_DUPS` or
# `HIST_EXPIRE_DUPS_FIRST`.
_zsh_autosuggest_strategy_match_prev_cmd() {
local prefix="$(_zsh_autosuggest_escape_command "$1")"
local prefix="$1"
# Get all history event numbers that correspond to history
# entries that match pattern $prefix*
local history_match_keys
history_match_keys=(${(k)history[(R)$prefix*]})
history_match_keys=(${(k)history[(R)${(b)prefix}*]})
# By default we use the first history number (most recent history entry)
local histkey="${history_match_keys[1]}"
# Get the previously executed command
local prev_cmd="$(_zsh_autosuggest_prev_command)"
prev_cmd="$(_zsh_autosuggest_escape_command "$prev_cmd")"
local prev_cmd="${history[$((HISTCMD-1))]}"
# Iterate up to the first 200 history event numbers that match $prefix
for key in "${(@)history_match_keys[1,200]}"; do
@@ -411,8 +533,117 @@ _zsh_autosuggest_strategy_match_prev_cmd() {
fi
done
# Echo the matched history entry
echo -E "$history[$histkey]"
# Give back the matched history entry
typeset -g suggestion="$history[$histkey]"
}
#--------------------------------------------------------------------#
# Async #
#--------------------------------------------------------------------#
# 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
# Silence any error messages
exec 2>/dev/null
local strategy=$1
local last_pid
while IFS='' read -r -d $'\0' query; do
# Kill last bg process
kill -KILL $last_pid &>/dev/null
# Run suggestion search in the background
(
local suggestion
_zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY "$query"
echo -n -E "$suggestion"$'\0'
) &
last_pid=$!
done
}
_zsh_autosuggest_async_request() {
# Write the query to the zpty process to fetch a suggestion
zpty -w -n $ZSH_AUTOSUGGEST_ASYNC_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_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'##}"
}
_zsh_autosuggest_async_pty_create() {
# 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 )); 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
# 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
if (( REPLY )); then
_ZSH_AUTOSUGGEST_PTY_FD=$REPLY
else
_ZSH_AUTOSUGGEST_PTY_FD=$zptyfd
fi
# Set up input handler from the zpty
zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_response
}
_zsh_autosuggest_async_pty_destroy() {
if zpty -t $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null; then
# Remove the input handler
zle -F $_ZSH_AUTOSUGGEST_PTY_FD &>/dev/null
# Destroy the zpty
zpty -d $ZSH_AUTOSUGGEST_ASYNC_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_feature_detect_zpty_returns_fd
_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
}
#--------------------------------------------------------------------#
@@ -421,9 +652,20 @@ _zsh_autosuggest_strategy_match_prev_cmd() {
# Start the autosuggestion widgets
_zsh_autosuggest_start() {
_zsh_autosuggest_check_deprecated_config
add-zsh-hook -d precmd _zsh_autosuggest_start
_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
}
autoload -Uz add-zsh-hook
# Start the autosuggestion widgets on the next precmd
add-zsh-hook precmd _zsh_autosuggest_start