├── .editorconfig
├── .github
├── ISSUE_TEMPLATE
│ ├── bug-report.md
│ └── feature_request.md
└── workflows
│ └── integration.yml
├── .gitignore
├── .rspec
├── .rubocop.yml
├── .ruby-version
├── CHANGELOG.md
├── DESCRIPTION
├── Dockerfile
├── Gemfile
├── Gemfile.lock
├── INSTALL.md
├── LICENSE
├── Makefile
├── README.md
├── URL
├── VERSION
├── ZSH_VERSIONS
├── install_test_zsh.sh
├── spec
├── async_spec.rb
├── integrations
│ ├── auto_cd_spec.rb
│ ├── bracketed_paste_magic_spec.rb
│ ├── client_zpty_spec.rb
│ ├── glob_subst_spec.rb
│ ├── rebound_bracket_spec.rb
│ ├── vi_mode_spec.rb
│ ├── wrapped_widget_spec.rb
│ └── zle_input_stack_spec.rb
├── kill_ring_spec.rb
├── line_init_spec.rb
├── multi_line_spec.rb
├── options
│ ├── buffer_max_size_spec.rb
│ ├── highlight_style_spec.rb
│ ├── original_widget_prefix_spec.rb
│ ├── strategy_spec.rb
│ └── widget_lists_spec.rb
├── spec_helper.rb
├── strategies
│ ├── completion_spec.rb
│ ├── history_spec.rb
│ ├── match_prev_cmd_spec.rb
│ └── special_characters_helper.rb
├── terminal_session.rb
└── widgets
│ ├── disable_spec.rb
│ ├── enable_spec.rb
│ ├── fetch_spec.rb
│ └── toggle_spec.rb
├── src
├── async.zsh
├── bind.zsh
├── config.zsh
├── fetch.zsh
├── highlight.zsh
├── start.zsh
├── strategies
│ ├── completion.zsh
│ ├── history.zsh
│ └── match_prev_cmd.zsh
├── util.zsh
└── widgets.zsh
├── zsh-autosuggestions.plugin.zsh
└── zsh-autosuggestions.zsh
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | charset = utf-8
3 | end_of_line = lf
4 | insert_final_newline = true
5 | trim_trailing_whitespace = true
6 | indent_style = tab
7 | indent_size = 4
8 |
9 | [*.md]
10 | indent_style = space
11 |
12 | [*.rb]
13 | indent_style = space
14 | indent_size = 2
15 |
16 | [*.yml]
17 | indent_style = space
18 | indent_size = 2
19 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### Describe the bug
11 |
12 |
13 | ### To Reproduce
14 | Steps to reproduce the behavior:
15 |
16 |
17 |
18 | ```sh
19 | % zsh -df
20 | % source path/to/zsh-autosuggestions.zsh
21 | % ... # what do you do to reproduce?
22 | ```
23 |
24 | ### Expected behavior
25 |
26 |
27 | ### Screenshots
28 |
29 |
30 | ### Desktop
31 | - OS + distribution:
32 | - Zsh version:
33 | - Plugin version:
34 |
35 | ### Additional context
36 |
37 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### Is your feature request related to a problem? Please describe.
11 |
12 |
13 | ### Describe the solution you'd like
14 |
15 |
16 | ### Describe alternatives you've considered
17 |
18 |
19 | ### Additional context
20 |
21 |
--------------------------------------------------------------------------------
/.github/workflows/integration.yml:
--------------------------------------------------------------------------------
1 | on: [push, pull_request]
2 | concurrency:
3 | group: ${{ github.workflow }}-${{ github.ref }}
4 | cancel-in-progress: true
5 | env:
6 | IMAGE_CACHE_PATH: /tmp/.image-cache
7 | IMAGE_CACHE_NAME: zsh-autosuggestions-test
8 | jobs:
9 | determine-versions:
10 | runs-on: ubuntu-22.04
11 | outputs:
12 | versions: ${{ steps.set-versions.outputs.versions }}
13 | steps:
14 | - uses: actions/checkout@v3
15 | - id: set-versions
16 | run: |
17 | echo "versions=$(
18 | grep "^[^#]" ZSH_VERSIONS \
19 | | sed -E 's/(^|$)/"/g' \
20 | | paste -sd ',' - \
21 | | sed -e 's/^/[/' -e 's/$/]/'
22 | )" >> $GITHUB_OUTPUT
23 | test:
24 | needs: determine-versions
25 | runs-on: ubuntu-22.04
26 | strategy:
27 | matrix:
28 | version: ${{ fromJson(needs.determine-versions.outputs.versions) }}
29 | steps:
30 | - uses: actions/checkout@v3
31 | - name: Docker image cache
32 | id: image-cache
33 | uses: actions/cache@v3
34 | with:
35 | path: ${{ env.IMAGE_CACHE_PATH }}
36 | key: image-cache-${{ matrix.version }}-${{ hashFiles('Dockerfile', 'install_test_zsh.sh', 'Gemfile.lock') }}
37 | - name: Load cached docker image if available
38 | if: ${{ steps.image-cache.outputs.cache-hit }}
39 | run: gunzip < $IMAGE_CACHE_PATH/$IMAGE_CACHE_NAME.tar.gz | docker load
40 | - name: Build the docker image if necessary
41 | if: ${{ !steps.image-cache.outputs.cache-hit }}
42 | run: |
43 | docker build --build-arg TEST_ZSH_VERSION=${{ matrix.version }} -t $IMAGE_CACHE_NAME .
44 | mkdir -p $IMAGE_CACHE_PATH
45 | docker save $IMAGE_CACHE_NAME | gzip > $IMAGE_CACHE_PATH/$IMAGE_CACHE_NAME.tar.gz
46 | - name: Run the tests
47 | run: |
48 | docker run --rm \
49 | -v $PWD:/zsh-autosuggestions \
50 | $IMAGE_CACHE_NAME \
51 | make test
52 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # zsh word code files
2 | *.zwc
3 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --require spec_helper
3 | --format documentation
4 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | # Rails:
2 | # Enabled: true
3 |
4 | AllCops:
5 | TargetRubyVersion: 2.3
6 | Include:
7 | - '**/Rakefile'
8 | - '**/config.ru'
9 | - '**/Gemfile'
10 |
11 | Metrics/LineLength:
12 | Max: 120
13 |
14 | Style/Documentation:
15 | Enabled: false
16 |
17 | Style/DotPosition:
18 | EnforcedStyle: trailing
19 |
20 | Style/FrozenStringLiteralComment:
21 | Enabled: false
22 |
23 | Style/Lambda:
24 | Enabled: false
25 |
26 | Style/MultilineMethodCallIndentation:
27 | EnforcedStyle: indented
28 |
29 | Style/TrailingUnderscoreVariable:
30 | Enabled: false
31 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.5.3
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## v0.7.1
4 | - Clear POSTDISPLAY instead of unsetting (#634)
5 | - Always reset async file descriptor after consuming it (#630)
6 | - Always use builtin `exec` (#628)
7 | - Add `history-beginning-search-*-end` widgets to clear widget list (#619)
8 | - Switch CI from Circle CI to GitHub Actions
9 |
10 | ## v0.7.0
11 | - Enable asynchronous mode by default (#498)
12 | - No longer wrap user widgets starting with `autosuggest-` prefix (#496)
13 | - Fix a bug wrapping widgets that modify the buffer (#541)
14 |
15 |
16 | ## v0.6.4
17 | - Fix `vi-forward-char` triggering a bell when using it to accept a suggestion (#488)
18 | - New configuration option to skip completion suggestions when buffer matches a pattern (#487)
19 | - New configuration option to ignore history entries matching a pattern (#456)
20 |
21 | ## v0.6.3
22 | - Fixed bug moving cursor to end of buffer after accepting suggestion (#453)
23 |
24 | ## v0.6.2
25 | - Fixed bug deleting the last character in the buffer in vi mode (#450)
26 | - Degrade gracefully when user doesn't have `zsh/system` module installed (#447)
27 |
28 | ## v0.6.1
29 | - Fixed bug occurring when `_complete` had been aliased (#443)
30 |
31 | ## v0.6.0
32 | - Added `completion` suggestion strategy powered by completion system (#111)
33 | - Allow setting `ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE` to an empty string (#422)
34 | - Don't fetch suggestions after copy-earlier-word (#439)
35 | - Allow users to unignore zle-\* widgets (e.g. zle-line-init) (#432)
36 |
37 |
38 | ## v0.5.2
39 | - Allow disabling automatic widget re-binding for better performance (#418)
40 | - Fix async suggestions when `SH_WORD_SPLIT` is set
41 | - Refactor async mode to use process substitution instead of zpty (#417)
42 |
43 | ## v0.5.1
44 | - Speed up widget rebinding (#413)
45 | - Clean up global variable creations (#403)
46 | - Respect user's set options when running original widget (#402)
47 |
48 | ## v0.5.0
49 | - Don't overwrite config with default values (#335)
50 | - Support fallback strategies by supplying array to suggestion config var
51 | - Rename "default" suggestion strategy to "history" to name it based on what it actually does
52 | - Reset opts in some functions affected by `GLOB_SUBST` (#334)
53 | - Support widgets starting with dashes (ex: `-a-widget`) (#337)
54 | - Skip async tests in zsh versions less than 5.0.8 because of reliability issues
55 | - Fix handling of newline + carriage return in async pty (#333)
56 |
57 |
58 | ## v0.4.3
59 | - Avoid bell when accepting suggestions with `autosuggest-accept` (#228)
60 | - Don't fetch suggestions after [up,down]-line-or-beginning-search (#227, #241)
61 | - We are now running CI against new 5.5.1 version
62 | - Fix partial-accept in vi mode (#188)
63 | - Fix suggestion disappearing on fast movement after switching to `vicmd` mode (#290)
64 | - Fix issue rotating through kill ring with `yank-pop` (#301)
65 | - Fix issue creating new pty for async mode when previous pty is not properly cleaned up (#249)
66 |
67 | ## v0.4.2
68 | - Fix bug in zsh versions older than 5.0.8 (#296)
69 | - Officially support back to zsh v4.3.11
70 |
71 | ## v0.4.1
72 | - Switch to [[ and (( conditionals instead of [ (#257)
73 | - Avoid warnnestedvar warnings with `typeset -g` (#275)
74 | - Replace tabs with spaces in yaml (#268)
75 | - Clean up and fix escaping of special characters (#267)
76 | - Add `emacs-forward-word` to default list of partial accept widgets (#246)
77 |
78 | ## v0.4.0
79 | - High-level integration tests using RSpec and tmux
80 | - Add continuous integration with Circle CI
81 | - Experimental support for asynchronous suggestions (#170)
82 | - Fix problems with multi-line suggestions (#225)
83 | - Optimize case where manually typing in suggestion
84 | - Avoid wrapping any zle-\* widgets (#206)
85 | - Remove support for deprecated options from v0.0.x
86 | - Handle history entries that begin with dashes
87 | - Gracefully handle being sourced multiple times (#126)
88 | - Add enable/disable/toggle widgets to disable/enable suggestions (#219)
89 |
90 |
91 | ## v0.3.3
92 | - Switch from $history array to fc builtin for better performance with large HISTFILEs (#164)
93 | - Fix tilde handling when extended_glob is set (#168)
94 | - Add config option for maximum buffer length to fetch suggestions for (#178)
95 | - Add config option for list of widgets to ignore (#184)
96 | - Don't fetch a new suggestion unless a modification widget actually modifies the buffer (#183)
97 |
98 | ## v0.3.2
99 | - Test runner now supports running specific tests and choosing zsh binary
100 | - Return code from original widget is now correctly passed through (#135)
101 | - Add `vi-add-eol` to list of accept widgets (#143)
102 | - Escapes widget names within evals to fix problems with irregular widget names (#152)
103 | - Plugin now clears suggestion while within a completion menu (#149)
104 | - .plugin file no longer relies on symbolic link support, fixing issues on Windows (#156)
105 |
106 | ## v0.3.1
107 |
108 | - Fixes issue with `vi-next-char` not accepting suggestion (#137).
109 | - Fixes global variable warning when WARN_CREATE_GLOBAL option enabled (#133).
110 | - Split out a separate test file for each widget.
111 |
112 | ## v0.3.0
113 |
114 | - Adds `autosuggest-execute` widget (PR #124).
115 | - Adds concept of suggestion "strategies" for different ways of fetching suggestions.
116 | - Adds "match_prev_cmd" strategy (PR #131).
117 | - Uses git submodules for testing dependencies.
118 | - Lots of test cleanup.
119 | - Various bug fixes for zsh 5.0.x and `sh_word_split` option.
120 |
121 |
122 | ## v0.2.17
123 |
124 | Start of changelog.
125 |
--------------------------------------------------------------------------------
/DESCRIPTION:
--------------------------------------------------------------------------------
1 | Fish-like fast/unobtrusive autosuggestions for zsh.
2 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ruby:2.5.3-alpine
2 |
3 | ARG TEST_ZSH_VERSION
4 | RUN : "${TEST_ZSH_VERSION:?}"
5 |
6 | RUN apk add --no-cache autoconf
7 | RUN apk add --no-cache libtool
8 | RUN apk add --no-cache libcap-dev
9 | RUN apk add --no-cache pcre-dev
10 | RUN apk add --no-cache curl
11 | RUN apk add --no-cache build-base
12 | RUN apk add --no-cache ncurses-dev
13 | RUN apk add --no-cache tmux
14 |
15 | WORKDIR /zsh-autosuggestions
16 |
17 | ADD install_test_zsh.sh ./
18 | RUN ./install_test_zsh.sh
19 |
20 | ADD Gemfile Gemfile.lock ./
21 | RUN bundle install
22 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'rspec'
4 | gem 'rspec-wait'
5 | gem 'pry-byebug'
6 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | byebug (9.0.5)
5 | coderay (1.1.1)
6 | diff-lcs (1.3)
7 | method_source (0.8.2)
8 | pry (0.10.4)
9 | coderay (~> 1.1.0)
10 | method_source (~> 0.8.1)
11 | slop (~> 3.4)
12 | pry-byebug (3.4.0)
13 | byebug (~> 9.0)
14 | pry (~> 0.10)
15 | rspec (3.5.0)
16 | rspec-core (~> 3.5.0)
17 | rspec-expectations (~> 3.5.0)
18 | rspec-mocks (~> 3.5.0)
19 | rspec-core (3.5.4)
20 | rspec-support (~> 3.5.0)
21 | rspec-expectations (3.5.0)
22 | diff-lcs (>= 1.2.0, < 2.0)
23 | rspec-support (~> 3.5.0)
24 | rspec-mocks (3.5.0)
25 | diff-lcs (>= 1.2.0, < 2.0)
26 | rspec-support (~> 3.5.0)
27 | rspec-support (3.5.0)
28 | rspec-wait (0.0.9)
29 | rspec (>= 3, < 4)
30 | slop (3.6.0)
31 |
32 | PLATFORMS
33 | ruby
34 |
35 | DEPENDENCIES
36 | pry-byebug
37 | rspec
38 | rspec-wait
39 |
40 | BUNDLED WITH
41 | 1.13.6
42 |
--------------------------------------------------------------------------------
/INSTALL.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | * [Packages](#packages)
4 | * [Antigen](#antigen)
5 | * [Oh My Zsh](#oh-my-zsh)
6 | * [HomeBrew](#homebrew)
7 | * [Manual](#manual-git-clone)
8 |
9 | ## Packages
10 |
11 | | System | Package |
12 | | ------------- | ------------- |
13 | | Alpine Linux | [zsh-autosuggestions](https://pkgs.alpinelinux.org/packages?name=zsh-autosuggestions) |
14 | | Debian / Ubuntu | [zsh-autosuggestions OBS repository](https://software.opensuse.org/download.html?project=shells%3Azsh-users%3Azsh-autosuggestions&package=zsh-autosuggestions) |
15 | | Fedora / CentOS / RHEL / Scientific Linux | [zsh-autosuggestions OBS repository](https://software.opensuse.org/download.html?project=shells%3Azsh-users%3Azsh-autosuggestions&package=zsh-autosuggestions) |
16 | | OpenSUSE / SLE | [zsh-autosuggestions OBS repository](https://software.opensuse.org/download.html?project=shells%3Azsh-users%3Azsh-autosuggestions&package=zsh-autosuggestions) |
17 | | Arch Linux / Manjaro / Antergos / Hyperbola | [zsh-autosuggestions](https://www.archlinux.org/packages/zsh-autosuggestions), [zsh-autosuggestions-git](https://aur.archlinux.org/packages/zsh-autosuggestions-git) |
18 | | NixOS | [zsh-autosuggestions](https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/zs/zsh-autosuggestions/package.nix) |
19 | | Void Linux | [zsh-autosuggestions](https://github.com/void-linux/void-packages/blob/master/srcpkgs/zsh-autosuggestions/template) |
20 | | Mac OS | [homebrew](https://github.com/Homebrew/homebrew-core/blob/master/Formula/z/zsh-autosuggestions.rb) |
21 | | NetBSD | [pkgsrc](http://ftp.netbsd.org/pub/pkgsrc/current/pkgsrc/shells/zsh-autosuggestions/README.html) |
22 |
23 | ## Antigen
24 |
25 | 1. Add the following to your `.zshrc`:
26 |
27 | ```sh
28 | antigen bundle zsh-users/zsh-autosuggestions
29 | ```
30 |
31 | 2. Start a new terminal session.
32 |
33 | ## Oh My Zsh
34 |
35 | 1. Clone this repository into `$ZSH_CUSTOM/plugins` (by default `~/.oh-my-zsh/custom/plugins`)
36 |
37 | ```sh
38 | git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
39 | ```
40 |
41 | 2. Add the plugin to the list of plugins for Oh My Zsh to load (inside `~/.zshrc`):
42 |
43 | ```sh
44 | plugins=(
45 | # other plugins...
46 | zsh-autosuggestions
47 | )
48 | ```
49 |
50 | 3. Start a new terminal session.
51 |
52 | ## Homebrew
53 |
54 | 1. Install command:
55 | ```sh
56 | brew install zsh-autosuggestions
57 | ```
58 |
59 | 2. To activate the autosuggestions, add the following at the end of your .zshrc:
60 |
61 | ```sh
62 | source $(brew --prefix)/share/zsh-autosuggestions/zsh-autosuggestions.zsh
63 | ```
64 |
65 | 3. Start a new terminal session.
66 |
67 | ## Manual (Git Clone)
68 |
69 | 1. Clone this repository somewhere on your machine. This guide will assume `~/.zsh/zsh-autosuggestions`.
70 |
71 | ```sh
72 | git clone https://github.com/zsh-users/zsh-autosuggestions ~/.zsh/zsh-autosuggestions
73 | ```
74 |
75 | 2. Add the following to your `.zshrc`:
76 |
77 | ```sh
78 | source ~/.zsh/zsh-autosuggestions/zsh-autosuggestions.zsh
79 | ```
80 |
81 | 3. Start a new terminal session.
82 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 Thiago de Arruda
2 | Copyright (c) 2016-2021 Eric Freese
3 |
4 | Permission is hereby granted, free of charge, to any person
5 | obtaining a copy of this software and associated documentation
6 | files (the "Software"), to deal in the Software without
7 | restriction, including without limitation the rights to use,
8 | copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 | OTHER DEALINGS IN THE SOFTWARE.
24 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | SRC_DIR := ./src
2 |
3 | SRC_FILES := \
4 | $(SRC_DIR)/config.zsh \
5 | $(SRC_DIR)/util.zsh \
6 | $(SRC_DIR)/bind.zsh \
7 | $(SRC_DIR)/highlight.zsh \
8 | $(SRC_DIR)/widgets.zsh \
9 | $(SRC_DIR)/strategies/*.zsh \
10 | $(SRC_DIR)/fetch.zsh \
11 | $(SRC_DIR)/async.zsh \
12 | $(SRC_DIR)/start.zsh
13 |
14 | HEADER_FILES := \
15 | DESCRIPTION \
16 | URL \
17 | VERSION \
18 | LICENSE
19 |
20 | PLUGIN_TARGET := zsh-autosuggestions.zsh
21 |
22 | all: $(PLUGIN_TARGET)
23 |
24 | $(PLUGIN_TARGET): $(HEADER_FILES) $(SRC_FILES)
25 | cat $(HEADER_FILES) | sed -e 's/^/# /g' > $@
26 | cat $(SRC_FILES) >> $@
27 |
28 | .PHONY: clean
29 | clean:
30 | rm $(PLUGIN_TARGET)
31 |
32 | .PHONY: test
33 | test: all
34 | @test -n "$$TEST_ZSH_BIN" && echo "Testing zsh binary: $(TEST_ZSH_BIN)" || true
35 | bundle exec rspec $(TESTS)
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # zsh-autosuggestions
2 |
3 | _[Fish](http://fishshell.com/)-like fast/unobtrusive autosuggestions for zsh._
4 |
5 | It suggests commands as you type based on history and completions.
6 |
7 | Requirements: Zsh v4.3.11 or later
8 |
9 | [](https://gitter.im/zsh-users/zsh-autosuggestions)
10 |
11 |
12 |
13 |
14 | ## Installation
15 |
16 | See [INSTALL.md](INSTALL.md).
17 |
18 |
19 | ## Usage
20 |
21 | As you type commands, you will see a completion offered after the cursor in a muted gray color. This color can be changed by setting the `ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE` variable. See [configuration](#configuration).
22 |
23 | If you press the → key (`forward-char` widget) or End (`end-of-line` widget) with the cursor at the end of the buffer, it will accept the suggestion, replacing the contents of the command line buffer with the suggestion.
24 |
25 | If you invoke the `forward-word` widget, it will partially accept the suggestion up to the point that the cursor moves to.
26 |
27 |
28 | ## Configuration
29 |
30 | You may want to override the default global config variables. Default values of these variables can be found [here](src/config.zsh).
31 |
32 | **Note:** If you are using Oh My Zsh, you can put this configuration in a file in the `$ZSH_CUSTOM` directory. See their comments on [overriding internals](https://github.com/robbyrussell/oh-my-zsh/wiki/Customization#overriding-internals).
33 |
34 |
35 | ### Suggestion Highlight Style
36 |
37 | Set `ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE` to configure the style that the suggestion is shown with. The default is `fg=8`, which will set the foreground color to color 8 from the [256-color palette](https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg). If your terminal only supports 8 colors, you will need to use a number between 0 and 7.
38 |
39 | Background color can also be set, and the suggestion can be styled bold, underlined, or standout. For example, this would show suggestions with bold, underlined, pink text on a cyan background:
40 |
41 | ```sh
42 | ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE="fg=#ff00ff,bg=cyan,bold,underline"
43 | ```
44 |
45 | For more info, read the Character Highlighting section of the zsh manual: `man zshzle` or [online](http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Character-Highlighting).
46 |
47 | **Note:** Some iTerm2 users have reported [not being able to see the suggestions](https://github.com/zsh-users/zsh-autosuggestions/issues/416#issuecomment-486516333). If this affects you, the problem is likely caused by incorrect color settings. In order to correct this, go into iTerm2's setting, navigate to profile > colors and make sure that the colors for Basic Colors > Background and ANSI Colors > Bright Black are **different**.
48 |
49 |
50 | ### Suggestion Strategy
51 |
52 | `ZSH_AUTOSUGGEST_STRATEGY` is an array that specifies how suggestions should be generated. The strategies in the array are tried successively until a suggestion is found. There are currently three built-in strategies to choose from:
53 |
54 | - `history`: Chooses the most recent match from history.
55 | - `completion`: Chooses a suggestion based on what tab-completion would suggest. (requires `zpty` module, which is included with zsh since 4.0.1)
56 | - `match_prev_cmd`: Like `history`, but 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`.
57 |
58 | For example, setting `ZSH_AUTOSUGGEST_STRATEGY=(history completion)` will first try to find a suggestion from your history, but, if it can't find a match, will find a suggestion from the completion engine.
59 |
60 |
61 | ### Widget Mapping
62 |
63 | This plugin works by triggering custom behavior when certain [zle widgets](http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Zle-Widgets) are invoked. You can add and remove widgets from these arrays to change the behavior of this plugin:
64 |
65 | - `ZSH_AUTOSUGGEST_CLEAR_WIDGETS`: Widgets in this array will clear the suggestion when invoked.
66 | - `ZSH_AUTOSUGGEST_ACCEPT_WIDGETS`: Widgets in this array will accept the suggestion when invoked.
67 | - `ZSH_AUTOSUGGEST_EXECUTE_WIDGETS`: Widgets in this array will execute the suggestion when invoked.
68 | - `ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS`: Widgets in this array will partially accept the suggestion when invoked.
69 | - `ZSH_AUTOSUGGEST_IGNORE_WIDGETS`: Widgets in this array will not trigger any custom behavior.
70 |
71 | Widgets that modify the buffer and are not found in any of these arrays will fetch a new suggestion after they are invoked.
72 |
73 | **Note:** A widget shouldn't belong to more than one of the above arrays.
74 |
75 |
76 | ### Disabling suggestion for large buffers
77 |
78 | 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.
79 | This can be useful when pasting large amount of text in the terminal, to avoid triggering autosuggestion for strings that are too long.
80 |
81 | ### Asynchronous Mode
82 |
83 | Suggestions are fetched asynchronously by default in zsh versions 5.0.8 and greater. To disable asynchronous suggestions and fetch them synchronously instead, `unset ZSH_AUTOSUGGEST_USE_ASYNC` after sourcing the plugin.
84 |
85 | Alternatively, if you are using a version of zsh older than 5.0.8 and want to enable asynchronous mode, set the `ZSH_AUTOSUGGEST_USE_ASYNC` variable after sourcing the plugin (it can be set to anything). Note that there is [a bug](https://github.com/zsh-users/zsh-autosuggestions/issues/364#issuecomment-481423232) in versions of zsh older than 5.0.8 where ctrl + c will fail to reset the prompt immediately after fetching a suggestion asynchronously.
86 |
87 | ### Disabling automatic widget re-binding
88 |
89 | Set `ZSH_AUTOSUGGEST_MANUAL_REBIND` (it can be set to anything) to disable automatic widget re-binding on each precmd. This can be a big boost to performance, but you'll need to handle re-binding yourself if any of the widget lists change or if you or another plugin wrap any of the autosuggest widgets. To re-bind widgets, run `_zsh_autosuggest_bind_widgets`.
90 |
91 | ### Ignoring history suggestions that match a pattern
92 |
93 | Set `ZSH_AUTOSUGGEST_HISTORY_IGNORE` to a [glob pattern](http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Operators) to prevent offering suggestions for history entries that match the pattern. For example, set it to `"cd *"` to never suggest any `cd` commands from history. Or set to `"?(#c50,)"` to never suggest anything 50 characters or longer.
94 |
95 | **Note:** This only affects the `history` and `match_prev_cmd` suggestion strategies.
96 |
97 | ### Skipping completion suggestions for certain cases
98 |
99 | Set `ZSH_AUTOSUGGEST_COMPLETION_IGNORE` to a [glob pattern](http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Operators) to prevent offering completion suggestions when the buffer matches that pattern. For example, set it to `"git *"` to disable completion suggestions for git subcommands.
100 |
101 | **Note:** This only affects the `completion` suggestion strategy.
102 |
103 |
104 | ### Key Bindings
105 |
106 | This plugin provides a few widgets that you can use with `bindkey`:
107 |
108 | 1. `autosuggest-accept`: Accepts the current suggestion.
109 | 2. `autosuggest-execute`: Accepts and executes the current suggestion.
110 | 3. `autosuggest-clear`: Clears the current suggestion.
111 | 4. `autosuggest-fetch`: Fetches a suggestion (works even when suggestions are disabled).
112 | 5. `autosuggest-disable`: Disables suggestions.
113 | 6. `autosuggest-enable`: Re-enables suggestions.
114 | 7. `autosuggest-toggle`: Toggles between enabled/disabled suggestions.
115 |
116 | For example, this would bind ctrl + space to accept the current suggestion.
117 |
118 | ```sh
119 | bindkey '^ ' autosuggest-accept
120 | ```
121 |
122 |
123 | ## Troubleshooting
124 |
125 | If you have a problem, please search through [the list of issues on GitHub](https://github.com/zsh-users/zsh-autosuggestions/issues?q=) to see if someone else has already reported it.
126 |
127 | ### Reporting an Issue
128 |
129 | Before reporting an issue, please try temporarily disabling sections of your configuration and other plugins that may be conflicting with this plugin to isolate the problem.
130 |
131 | When reporting an issue, please include:
132 |
133 | - The smallest, simplest `.zshrc` configuration that will reproduce the problem. See [this comment](https://github.com/zsh-users/zsh-autosuggestions/issues/102#issuecomment-180944764) for a good example of what this means.
134 | - The version of zsh you're using (`zsh --version`)
135 | - Which operating system you're running
136 |
137 |
138 | ## Uninstallation
139 |
140 | 1. Remove the code referencing this plugin from `~/.zshrc`.
141 |
142 | 2. Remove the git repository from your hard drive
143 |
144 | ```sh
145 | rm -rf ~/.zsh/zsh-autosuggestions # Or wherever you installed
146 | ```
147 |
148 |
149 | ## Development
150 |
151 | ### Build Process
152 |
153 | Edit the source files in `src/`. Run `make` to build `zsh-autosuggestions.zsh` from those source files.
154 |
155 |
156 | ### Pull Requests
157 |
158 | Pull requests are welcome! If you send a pull request, please:
159 |
160 | - Request to merge into the `develop` branch (*NOT* `master`)
161 | - Match the existing coding conventions.
162 | - Include helpful comments to keep the barrier-to-entry low for people new to the project.
163 | - Write tests that cover your code as much as possible.
164 |
165 |
166 | ### Testing
167 |
168 | 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.
169 |
170 | 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`).
171 |
172 | It's possible to run the tests for any supported version of zsh in a Docker image by building an image from the provided Dockerfile. To build the docker image for a specific version of zsh (where `` below is substituted with the contents of a line from the [`ZSH_VERSIONS`](ZSH_VERSIONS) file), run:
173 |
174 | ```sh
175 | docker build --build-arg TEST_ZSH_VERSION= -t zsh-autosuggestions-test .
176 | ```
177 |
178 | After building the image, run the tests via:
179 |
180 | ```sh
181 | docker run -it -v $PWD:/zsh-autosuggestions zsh-autosuggestions-test make test
182 | ```
183 |
184 |
185 | ## License
186 |
187 | This project is licensed under [MIT license](http://opensource.org/licenses/MIT).
188 | For the full text of the license, see the [LICENSE](LICENSE) file.
189 |
--------------------------------------------------------------------------------
/URL:
--------------------------------------------------------------------------------
1 | https://github.com/zsh-users/zsh-autosuggestions
2 |
--------------------------------------------------------------------------------
/VERSION:
--------------------------------------------------------------------------------
1 | v0.7.1
2 |
--------------------------------------------------------------------------------
/ZSH_VERSIONS:
--------------------------------------------------------------------------------
1 | # Zsh releases to run tests against
2 | # See https://github.com/zsh-users/zsh/releases
3 | 4.3.11
4 | 5.0.2
5 | 5.0.8
6 | 5.1.1
7 | 5.2
8 | 5.3.1
9 | 5.4.2
10 | 5.5.1
11 | 5.6.2
12 | 5.7.1
13 | 5.8.1
14 | 5.9
15 |
--------------------------------------------------------------------------------
/install_test_zsh.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -ex
4 |
5 | mkdir zsh-build
6 | cd zsh-build
7 |
8 | curl -L https://api.github.com/repos/zsh-users/zsh/tarball/zsh-$TEST_ZSH_VERSION | tar xz --strip=1
9 |
10 | ./Util/preconfig
11 | ./configure --enable-pcre \
12 | --enable-cap \
13 | --enable-multibyte \
14 | --with-term-lib='ncursesw tinfo' \
15 | --with-tcsetpgrp
16 |
17 | make install.bin
18 | make install.modules
19 | make install.fns
20 |
21 | cd ..
22 |
23 | rm -rf zsh-build
24 |
--------------------------------------------------------------------------------
/spec/async_spec.rb:
--------------------------------------------------------------------------------
1 | context 'with asynchronous suggestions enabled' do
2 | let(:options) { ["ZSH_AUTOSUGGEST_USE_ASYNC="] }
3 |
4 | describe '`up-line-or-beginning-search`' do
5 | let(:before_sourcing) do
6 | -> do
7 | session.
8 | run_command('autoload -U up-line-or-beginning-search').
9 | run_command('zle -N up-line-or-beginning-search').
10 | send_string('bindkey "').
11 | send_keys('C-v').send_keys('up').
12 | send_string('" up-line-or-beginning-search').
13 | send_keys('enter')
14 | end
15 | end
16 |
17 | it 'should show previous history entries' do
18 | with_history(
19 | 'echo foo',
20 | 'echo bar',
21 | 'echo baz'
22 | ) do
23 | session.clear_screen
24 | 3.times { session.send_keys('up') }
25 | wait_for { session.content }.to eq("echo foo")
26 | end
27 | end
28 | end
29 |
30 | describe '`copy-earlier-word`' do
31 | let(:before_sourcing) do
32 | -> do
33 | session.
34 | run_command('autoload -Uz copy-earlier-word').
35 | run_command('zle -N copy-earlier-word').
36 | run_command('bindkey "^N" copy-earlier-word')
37 | end
38 | end
39 |
40 | it 'should cycle through previous words in the buffer' do
41 | session.clear_screen
42 | session.send_string('foo bar baz')
43 | sleep 0.5
44 | session.send_keys('C-n')
45 | wait_for { session.content }.to eq('foo bar bazbaz')
46 | session.send_keys('C-n')
47 | wait_for { session.content }.to eq('foo bar bazbar')
48 | session.send_keys('C-n')
49 | wait_for { session.content }.to eq('foo bar bazfoo')
50 | end
51 | end
52 |
53 | describe 'pressing ^C after fetching a suggestion' do
54 | before do
55 | skip 'Workaround does not work below v5.0.8' if session.zsh_version < Gem::Version.new('5.0.8')
56 | end
57 |
58 | it 'terminates the prompt and begins a new one' do
59 | session.send_keys('e')
60 | sleep 0.5
61 | session.send_keys('C-c')
62 | sleep 0.5
63 | session.send_keys('echo')
64 |
65 | wait_for { session.content }.to eq("e\necho")
66 | end
67 | end
68 | end
69 |
70 |
71 |
--------------------------------------------------------------------------------
/spec/integrations/auto_cd_spec.rb:
--------------------------------------------------------------------------------
1 | describe 'with `AUTO_CD` option set' do
2 | let(:after_sourcing) do
3 | -> {
4 | session.run_command('setopt AUTO_CD')
5 | session.run_command('autoload compinit && compinit')
6 | }
7 | end
8 |
9 | it 'directory names are still completed' do
10 | session.send_string('sr')
11 | session.send_keys('C-i')
12 | wait_for { session.content }.to eq('src/')
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/spec/integrations/bracketed_paste_magic_spec.rb:
--------------------------------------------------------------------------------
1 | describe 'pasting using bracketed-paste-magic' do
2 | let(:before_sourcing) do
3 | -> do
4 | session.
5 | run_command('autoload -Uz bracketed-paste-magic').
6 | run_command('zle -N bracketed-paste bracketed-paste-magic')
7 | end
8 | end
9 |
10 | context 'with suggestions disabled while pasting' do
11 | before do
12 | session.
13 | run_command('bpm_init() { zle autosuggest-disable }').
14 | run_command('bpm_finish() { zle autosuggest-enable }').
15 | run_command('zstyle :bracketed-paste-magic paste-init bpm_init').
16 | run_command('zstyle :bracketed-paste-magic paste-finish bpm_finish')
17 | end
18 |
19 | it 'does not show an incorrect suggestion' do
20 | with_history('echo hello') do
21 | session.paste_string("echo #{'a' * 60}")
22 | sleep 1
23 | expect(session.content).to eq("echo #{'a' * 60}")
24 | end
25 | end
26 | end
27 |
28 | context 'with `bracketed-paste` added to the list of widgets that clear the suggestion' do
29 | let(:options) { ['ZSH_AUTOSUGGEST_CLEAR_WIDGETS+=(bracketed-paste)'] }
30 |
31 | it 'does not retain an old suggestion' do
32 | with_history ('echo foo') do
33 | session.send_string('echo ')
34 | wait_for { session.content }.to eq('echo foo')
35 | session.paste_string('bar')
36 | wait_for { session.content }.to eq('echo bar')
37 | session.send_keys('C-a') # Any cursor movement works
38 | sleep 1
39 | expect(session.content).to eq('echo bar')
40 | end
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/spec/integrations/client_zpty_spec.rb:
--------------------------------------------------------------------------------
1 | describe 'a running zpty command' do
2 | let(:before_sourcing) { -> { session.run_command('zmodload zsh/zpty && zpty -b kitty cat') } }
3 |
4 | context 'when using `completion` strategy' do
5 | let(:options) { ["ZSH_AUTOSUGGEST_STRATEGY=completion"] }
6 |
7 | it 'is not affected' do
8 | session.send_keys('a').send_keys('C-h')
9 | session.run_command('zpty -t kitty; echo $?')
10 |
11 | wait_for { session.content }.to end_with("\n0")
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/spec/integrations/glob_subst_spec.rb:
--------------------------------------------------------------------------------
1 | describe 'with `GLOB_SUBST` option set' do
2 | let(:after_sourcing) do
3 | -> {
4 | session.run_command('setopt GLOB_SUBST')
5 | }
6 | end
7 |
8 | it 'error messages are not printed' do
9 | session.send_string('[[')
10 | wait_for { session.content }.to eq('[[')
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/spec/integrations/rebound_bracket_spec.rb:
--------------------------------------------------------------------------------
1 | describe 'rebinding [' do
2 | context 'initialized before sourcing the plugin' do
3 | before do
4 | session.run_command("function [ { $commands[\\[] \"$@\" }")
5 | session.clear_screen
6 | end
7 |
8 | it 'executes the custom behavior and the built-in behavior' do
9 | session.send_string('asdf')
10 | wait_for { session.content }.to eq('asdf')
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/spec/integrations/vi_mode_spec.rb:
--------------------------------------------------------------------------------
1 | describe 'when using vi mode' do
2 | let(:before_sourcing) do
3 | -> do
4 | session.run_command('bindkey -v')
5 | end
6 | end
7 |
8 | describe 'moving the cursor after exiting insert mode' do
9 | it 'should not clear the current suggestion' do
10 | with_history('foobar foo') do
11 | session.
12 | send_string('foo').
13 | send_keys('escape').
14 | send_keys('h')
15 |
16 | wait_for { session.content }.to eq('foobar foo')
17 | end
18 | end
19 | end
20 |
21 | describe '`vi-forward-word-end`' do
22 | it 'should accept through the end of the current word' do
23 | with_history('foobar foo') do
24 | session.
25 | send_string('foo').
26 | send_keys('escape').
27 | send_keys('e'). # vi-forward-word-end
28 | send_keys('a'). # vi-add-next
29 | send_string('baz')
30 |
31 | wait_for { session.content }.to eq('foobarbaz')
32 | end
33 | end
34 | end
35 |
36 | describe '`vi-forward-word`' do
37 | it 'should accept through the first character of the next word' do
38 | with_history('foobar foo') do
39 | session.
40 | send_string('foo').
41 | send_keys('escape').
42 | send_keys('w'). # vi-forward-word
43 | send_keys('a'). # vi-add-next
44 | send_string('az')
45 |
46 | wait_for { session.content }.to eq('foobar faz')
47 | end
48 | end
49 | end
50 |
51 | describe '`vi-find-next-char`' do
52 | it 'should accept through the next occurrence of the character' do
53 | with_history('foobar foo') do
54 | session.
55 | send_string('foo').
56 | send_keys('escape').
57 | send_keys('f'). # vi-find-next-char
58 | send_keys('o').
59 | send_keys('a'). # vi-add-next
60 | send_string('b')
61 |
62 | wait_for { session.content }.to eq('foobar fob')
63 | end
64 | end
65 | end
66 |
67 | describe '`vi-delete`' do
68 | it 'should be able to remove the last character in the buffer' do
69 | skip 'deleting last char did not work below zsh version 5.0.8' if session.zsh_version < Gem::Version.new('5.0.8')
70 |
71 | session.
72 | send_string('echo foo').
73 | send_keys('escape').
74 | send_keys('d').
75 | send_keys('l')
76 |
77 | wait_for { session.content }.to eq('echo fo')
78 | end
79 | end
80 | end
81 |
--------------------------------------------------------------------------------
/spec/integrations/wrapped_widget_spec.rb:
--------------------------------------------------------------------------------
1 | describe 'a wrapped widget' do
2 | let(:widget) { 'backward-delete-char' }
3 |
4 | context 'initialized before sourcing the plugin' do
5 | let(:before_sourcing) do
6 | -> do
7 | session.
8 | run_command("_orig_#{widget}() { zle .#{widget} }").
9 | run_command("zle -N orig-#{widget} _orig_#{widget}").
10 | run_command("#{widget}-magic() { zle orig-#{widget}; BUFFER+=b }").
11 | run_command("zle -N #{widget} #{widget}-magic")
12 | end
13 | end
14 |
15 | it 'executes the custom behavior and the built-in behavior' do
16 | with_history('foobar', 'foodar') do
17 | session.send_string('food').send_keys('C-h')
18 | wait_for { session.content }.to eq('foobar')
19 | end
20 | end
21 | end
22 |
23 | context 'initialized after sourcing the plugin' do
24 | before do
25 | session.
26 | run_command("zle -N orig-#{widget} ${widgets[#{widget}]#*:}").
27 | run_command("#{widget}-magic() { zle orig-#{widget}; BUFFER+=b }").
28 | run_command("zle -N #{widget} #{widget}-magic").
29 | clear_screen
30 | end
31 |
32 | it 'executes the custom behavior and the built-in behavior' do
33 | with_history('foobar', 'foodar') do
34 | session.send_string('food').send_keys('C-h')
35 | wait_for { session.content }.to eq('foobar')
36 | end
37 | end
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/spec/integrations/zle_input_stack_spec.rb:
--------------------------------------------------------------------------------
1 | describe 'using `zle -U`' do
2 | let(:before_sourcing) do
3 | -> do
4 | session.
5 | run_command('_zsh_autosuggest_strategy_test() { sleep 1; _zsh_autosuggest_strategy_history "$1" }').
6 | run_command('foo() { zle -U - "echo hello" }; zle -N foo; bindkey ^B foo')
7 | end
8 | end
9 |
10 | let(:options) { ['unset ZSH_AUTOSUGGEST_USE_ASYNC', 'ZSH_AUTOSUGGEST_STRATEGY=test'] }
11 |
12 | # TODO: This is only possible with the $KEYS_QUEUED_COUNT widget parameter, coming soon...
13 | xit 'does not fetch a suggestion for every inserted character' do
14 | session.send_keys('C-b')
15 | wait_for { session.content }.to eq('echo hello')
16 | end
17 |
18 | it 'shows a suggestion when the widget completes' do
19 | with_history('echo hello world') do
20 | session.send_keys('C-b')
21 | wait_for { session.content(esc_seqs: true) }.to match(/\Aecho hello\e\[[0-9]+m world/)
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/spec/kill_ring_spec.rb:
--------------------------------------------------------------------------------
1 | context 'with some items in the kill ring' do
2 | before do
3 | session.
4 | send_string('echo foo').
5 | send_keys('C-u').
6 | send_string('echo bar').
7 | send_keys('C-u')
8 | end
9 |
10 | describe '`yank-pop`' do
11 | it 'should cycle through all items in the kill ring' do
12 | session.send_keys('C-y')
13 | wait_for { session.content }.to eq('echo bar')
14 |
15 | session.send_keys('escape').send_keys('y')
16 | wait_for { session.content }.to eq('echo foo')
17 |
18 | session.send_keys('escape').send_keys('y')
19 | wait_for { session.content }.to eq('echo bar')
20 | end
21 | end
22 | end
23 |
24 |
--------------------------------------------------------------------------------
/spec/line_init_spec.rb:
--------------------------------------------------------------------------------
1 | context 'with zle-line-init unignored' do
2 | let(:after_sourcing) do
3 | -> do
4 | session.
5 | run_command('setopt extendedglob').
6 | run_command('ZSH_AUTOSUGGEST_IGNORE_WIDGETS=(${(@)ZSH_AUTOSUGGEST_IGNORE_WIDGETS:#zle-\*} zle-\^line-init)').
7 | run_command('zle-line-init() { BUFFER="echo" }')
8 | end
9 | end
10 |
11 | it 'should fetch a suggestion on each line initialization' do
12 | with_history('echo foo') do
13 | session.run_command('zle -N zle-line-init')
14 | wait_for { session.content }.to end_with('echo foo')
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/multi_line_spec.rb:
--------------------------------------------------------------------------------
1 | describe 'a multi-line suggestion' do
2 | it 'should be displayed on multiple lines' do
3 | with_history("echo \"\n\"") do
4 | session.send_keys('e')
5 | wait_for { session.content }.to eq("echo \"\n\"")
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/spec/options/buffer_max_size_spec.rb:
--------------------------------------------------------------------------------
1 | describe 'a suggestion' do
2 | let(:term_opts) { { width: 200 } }
3 | let(:long_command) { "echo #{'a' * 100}" }
4 |
5 | around do |example|
6 | with_history(long_command) { example.run }
7 | end
8 |
9 | it 'is provided for any buffer length' do
10 | session.send_string(long_command[0...-1])
11 | wait_for { session.content }.to eq(long_command)
12 | end
13 |
14 | context 'when ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE is specified' do
15 | let(:buffer_max_size) { 10 }
16 | let(:options) { ["ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=#{buffer_max_size}"] }
17 |
18 | it 'is provided when the buffer is shorter than the specified length' do
19 | session.send_string(long_command[0...(buffer_max_size - 1)])
20 | wait_for { session.content }.to eq(long_command)
21 | end
22 |
23 | it 'is provided when the buffer is equal to the specified length' do
24 | session.send_string(long_command[0...(buffer_max_size)])
25 | wait_for { session.content }.to eq(long_command)
26 | end
27 |
28 | it 'is not provided when the buffer is longer than the specified length'
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/spec/options/highlight_style_spec.rb:
--------------------------------------------------------------------------------
1 | describe 'a displayed suggestion' do
2 | it 'is shown in the default style'
3 |
4 | describe 'when ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE is set to a zle_highlight string' do
5 | it 'is shown in the specified style'
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/options/original_widget_prefix_spec.rb:
--------------------------------------------------------------------------------
1 | describe 'an original zle widget' do
2 | context 'is accessible with the default prefix'
3 |
4 | context 'when ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX is set' do
5 | it 'is accessible with the specified prefix'
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/options/strategy_spec.rb:
--------------------------------------------------------------------------------
1 | describe 'a suggestion for a given prefix' do
2 | let(:history_strategy) { '_zsh_autosuggest_strategy_history() { suggestion="history" }' }
3 | let(:foobar_strategy) { '_zsh_autosuggest_strategy_foobar() { [[ "foobar baz" = $1* ]] && suggestion="foobar baz" }' }
4 | let(:foobaz_strategy) { '_zsh_autosuggest_strategy_foobaz() { [[ "foobaz bar" = $1* ]] && suggestion="foobaz bar" }' }
5 |
6 | let(:after_sourcing) do
7 | -> do
8 | session.run_command(history_strategy)
9 | end
10 | end
11 |
12 | it 'by default is determined by calling the `history` strategy function' do
13 | session.send_string('h')
14 | wait_for { session.content }.to eq('history')
15 | end
16 |
17 | context 'when ZSH_AUTOSUGGEST_STRATEGY is set to an array' do
18 | let(:after_sourcing) do
19 | -> do
20 | session.
21 | run_command(foobar_strategy).
22 | run_command(foobaz_strategy).
23 | run_command('ZSH_AUTOSUGGEST_STRATEGY=(foobar foobaz)')
24 | end
25 | end
26 |
27 | it 'is determined by the first strategy function to return a suggestion' do
28 | session.send_string('foo')
29 | wait_for { session.content }.to eq('foobar baz')
30 |
31 | session.send_string('baz')
32 | wait_for { session.content }.to eq('foobaz bar')
33 | end
34 | end
35 |
36 | context 'when ZSH_AUTOSUGGEST_STRATEGY is set to a string' do
37 | let(:after_sourcing) do
38 | -> do
39 | session.
40 | run_command(foobar_strategy).
41 | run_command(foobaz_strategy).
42 | run_command('ZSH_AUTOSUGGEST_STRATEGY="foobar foobaz"')
43 | end
44 | end
45 |
46 | it 'is determined by the first strategy function to return a suggestion' do
47 | session.send_string('foo')
48 | wait_for { session.content }.to eq('foobar baz')
49 |
50 | session.send_string('baz')
51 | wait_for { session.content }.to eq('foobaz bar')
52 | end
53 | end
54 | end
55 |
56 |
--------------------------------------------------------------------------------
/spec/options/widget_lists_spec.rb:
--------------------------------------------------------------------------------
1 | describe 'a zle widget' do
2 | let(:widget) { 'my-widget' }
3 | let(:before_sourcing) { -> { session.run_command("#{widget}() {}; zle -N #{widget}; bindkey ^B #{widget}") } }
4 |
5 | context 'when added to ZSH_AUTOSUGGEST_ACCEPT_WIDGETS' do
6 | let(:options) { ["ZSH_AUTOSUGGEST_ACCEPT_WIDGETS+=(#{widget})"] }
7 |
8 | it 'accepts the suggestion and moves the cursor to the end of the buffer when invoked' do
9 | with_history('echo hello') do
10 | session.send_string('e')
11 | wait_for { session.content }.to eq('echo hello')
12 | session.send_keys('C-b')
13 | wait_for { session.content(esc_seqs: true) }.to eq('echo hello')
14 | wait_for { session.cursor }.to eq([10, 0])
15 | end
16 | end
17 | end
18 |
19 | context 'when added to ZSH_AUTOSUGGEST_CLEAR_WIDGETS' do
20 | let(:options) { ["ZSH_AUTOSUGGEST_CLEAR_WIDGETS+=(#{widget})"] }
21 |
22 | it 'clears the suggestion when invoked' do
23 | with_history('echo hello') do
24 | session.send_string('e')
25 | wait_for { session.content }.to eq('echo hello')
26 | session.send_keys('C-b')
27 | wait_for { session.content }.to eq('e')
28 | end
29 | end
30 | end
31 |
32 | context 'when added to ZSH_AUTOSUGGEST_EXECUTE_WIDGETS' do
33 | let(:options) { ["ZSH_AUTOSUGGEST_EXECUTE_WIDGETS+=(#{widget})"] }
34 |
35 | it 'executes the suggestion when invoked' do
36 | with_history('echo hello') do
37 | session.send_string('e')
38 | wait_for { session.content }.to eq('echo hello')
39 | session.send_keys('C-b')
40 | wait_for { session.content }.to end_with("\nhello")
41 | end
42 | end
43 | end
44 |
45 | context 'when added to ZSH_AUTOSUGGEST_IGNORE_WIDGETS' do
46 | let(:options) { ["ZSH_AUTOSUGGEST_IGNORE_WIDGETS=(#{widget})"] }
47 |
48 | it 'should not be wrapped with an autosuggest widget' do
49 | session.run_command("echo $widgets[#{widget}]")
50 | wait_for { session.content }.to end_with("\nuser:#{widget}")
51 | end
52 | end
53 |
54 | context 'that moves the cursor forward' do
55 | before { session.run_command("#{widget}() { zle forward-char }") }
56 |
57 | context 'when added to ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS' do
58 | let(:options) { ["ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=(#{widget})"] }
59 |
60 | it 'accepts the suggestion as far as the cursor is moved when invoked' do
61 | with_history('echo hello') do
62 | session.send_string('e')
63 | wait_for { session.content }.to start_with('echo hello')
64 | session.send_keys('C-b')
65 | wait_for { session.content(esc_seqs: true) }.to match(/\Aec\e\[[0-9]+mho hello/)
66 | end
67 | end
68 | end
69 | end
70 |
71 | context 'that modifies the buffer' do
72 | before { session.run_command("#{widget}() { BUFFER=\"foo\" }") }
73 |
74 | context 'when not added to any of the widget lists' do
75 | it 'modifies the buffer and fetches a new suggestion' do
76 | with_history('foobar') do
77 | session.send_keys('C-b')
78 | wait_for { session.content }.to eq('foobar')
79 | end
80 | end
81 | end
82 | end
83 | end
84 |
85 | describe 'a modification to the widget lists' do
86 | let(:widget) { 'my-widget' }
87 | let(:before_sourcing) { -> { session.run_command("#{widget}() {}; zle -N #{widget}; bindkey ^B #{widget}") } }
88 | before { session.run_command("ZSH_AUTOSUGGEST_ACCEPT_WIDGETS+=(#{widget})") }
89 |
90 | it 'takes effect on the next cmd line' do
91 | with_history('echo hello') do
92 | session.send_string('e')
93 | wait_for { session.content }.to eq('echo hello')
94 | session.send_keys('C-b')
95 | wait_for { session.content(esc_seqs: true) }.to eq('echo hello')
96 | end
97 | end
98 |
99 | context 'when manual rebind is enabled' do
100 | let(:options) { ["ZSH_AUTOSUGGEST_MANUAL_REBIND=true"] }
101 |
102 | it 'does not take effect until bind command is re-run' do
103 | with_history('echo hello') do
104 | session.send_string('e')
105 | wait_for { session.content }.to eq('echo hello')
106 | session.send_keys('C-b')
107 | sleep 1
108 | expect(session.content(esc_seqs: true)).not_to eq('echo hello')
109 |
110 | session.send_keys('C-c')
111 | session.run_command('_zsh_autosuggest_bind_widgets').clear_screen
112 | wait_for { session.content }.to eq('')
113 |
114 | session.send_string('e')
115 | wait_for { session.content }.to eq('echo hello')
116 | session.send_keys('C-b')
117 | wait_for { session.content(esc_seqs: true) }.to eq('echo hello')
118 | end
119 | end
120 | end
121 | end
122 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'pry'
2 | require 'rspec/wait'
3 | require 'terminal_session'
4 | require 'tempfile'
5 |
6 | RSpec.shared_context 'terminal session' do
7 | let(:term_opts) { {} }
8 | let(:session) { TerminalSession.new(term_opts) }
9 | let(:before_sourcing) { -> {} }
10 | let(:after_sourcing) { -> {} }
11 | let(:options) { [] }
12 |
13 | around do |example|
14 | before_sourcing.call
15 | session.run_command(['source zsh-autosuggestions.zsh', *options].join('; '))
16 | after_sourcing.call
17 | session.clear_screen
18 |
19 | example.run
20 |
21 | session.destroy
22 | end
23 |
24 | def with_history(*commands, &block)
25 | Tempfile.create do |f|
26 | f.write(commands.map{|c| c.gsub("\n", "\\\n")}.join("\n"))
27 | f.flush
28 |
29 | session.run_command('fc -p')
30 | session.run_command("fc -R #{f.path}")
31 |
32 | session.clear_screen
33 |
34 | yield block
35 |
36 | session.send_keys('C-c')
37 | session.run_command('fc -P')
38 | end
39 | end
40 | end
41 |
42 | RSpec.configure do |config|
43 | config.expect_with :rspec do |expectations|
44 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true
45 | end
46 |
47 | config.mock_with :rspec do |mocks|
48 | mocks.verify_partial_doubles = true
49 | end
50 |
51 | config.wait_timeout = 2
52 |
53 | config.include_context 'terminal session'
54 | end
55 |
--------------------------------------------------------------------------------
/spec/strategies/completion_spec.rb:
--------------------------------------------------------------------------------
1 | describe 'the `completion` suggestion strategy' do
2 | let(:options) { ['ZSH_AUTOSUGGEST_STRATEGY=completion'] }
3 | let(:before_sourcing) do
4 | -> do
5 | session.
6 | run_command('autoload compinit && compinit').
7 | run_command('_foo() { compadd bar; compadd bat }').
8 | run_command('_num() { compadd two; compadd three }').
9 | run_command('compdef _foo baz').
10 | run_command('compdef _num one')
11 | end
12 | end
13 |
14 | it 'suggests the first completion result' do
15 | session.send_string('baz ')
16 | wait_for { session.content }.to eq('baz bar')
17 | end
18 |
19 | it 'does not add extra carriage returns when prefix has a line feed' do
20 | skip '`stty` does not work inside zpty below zsh version 5.0.3' if session.zsh_version < Gem::Version.new('5.0.3')
21 | session.send_string('baz \\').send_keys('C-v', 'C-j')
22 | wait_for { session.content }.to eq("baz \\\nbar")
23 | end
24 |
25 | context 'when `_complete` is aliased' do
26 | let(:before_sourcing) do
27 | -> do
28 | session.
29 | run_command('autoload compinit && compinit').
30 | run_command('_foo() { compadd bar; compadd bat }').
31 | run_command('compdef _foo baz').
32 | run_command('alias _complete=_complete')
33 | end
34 | end
35 |
36 | it 'suggests the first completion result' do
37 | session.send_string('baz ')
38 | wait_for { session.content }.to eq('baz bar')
39 | end
40 | end
41 |
42 | context 'when ZSH_AUTOSUGGEST_COMPLETION_IGNORE is set to a pattern' do
43 | let(:options) { ['ZSH_AUTOSUGGEST_STRATEGY=completion', 'ZSH_AUTOSUGGEST_COMPLETION_IGNORE="one *"'] }
44 |
45 | it 'makes suggestions when the buffer does not match the pattern' do
46 | session.send_string('baz ')
47 | wait_for { session.content }.to eq('baz bar')
48 | end
49 |
50 | it 'does not make suggestions when the buffer matches the pattern' do
51 | session.send_string('one t')
52 | sleep 1
53 | expect(session.content).to eq('one t')
54 | end
55 | end
56 |
57 | context 'when async mode is enabled' do
58 | let(:options) { ['ZSH_AUTOSUGGEST_USE_ASYNC=true', 'ZSH_AUTOSUGGEST_STRATEGY=completion'] }
59 |
60 | it 'suggests the first completion result' do
61 | session.send_string('baz ')
62 | wait_for { session.content }.to eq('baz bar')
63 | end
64 |
65 | it 'does not add extra carriage returns when prefix has a line feed' do
66 | skip '`stty` does not work inside zpty below zsh version 5.0.3' if session.zsh_version < Gem::Version.new('5.0.3')
67 | session.send_string('baz \\').send_keys('C-v', 'C-j')
68 | wait_for { session.content }.to eq("baz \\\nbar")
69 | end
70 | end
71 | end
72 |
73 |
--------------------------------------------------------------------------------
/spec/strategies/history_spec.rb:
--------------------------------------------------------------------------------
1 | require 'strategies/special_characters_helper'
2 |
3 | describe 'the `history` suggestion strategy' do
4 | it 'suggests the last matching history entry' do
5 | with_history('ls foo', 'ls bar', 'echo baz') do
6 | session.send_string('ls')
7 | wait_for { session.content }.to eq('ls bar')
8 | end
9 | end
10 |
11 | context 'when ZSH_AUTOSUGGEST_HISTORY_IGNORE is set to a pattern' do
12 | let(:options) { ['ZSH_AUTOSUGGEST_HISTORY_IGNORE="* bar"'] }
13 |
14 | it 'does not make suggestions that match the pattern' do
15 | with_history('ls foo', 'ls bar', 'echo baz') do
16 | session.send_string('ls')
17 | wait_for { session.content }.to eq('ls foo')
18 | end
19 | end
20 | end
21 |
22 | include_examples 'special characters'
23 | end
24 |
--------------------------------------------------------------------------------
/spec/strategies/match_prev_cmd_spec.rb:
--------------------------------------------------------------------------------
1 | require 'strategies/special_characters_helper'
2 |
3 | describe 'the `match_prev_cmd` strategy' do
4 | let(:options) { ['ZSH_AUTOSUGGEST_STRATEGY=match_prev_cmd'] }
5 |
6 | let(:history) { [
7 | 'echo what',
8 | 'ls foo',
9 | 'echo what',
10 | 'ls bar',
11 | 'ls baz',
12 | 'echo what'
13 | ] }
14 |
15 | it 'suggests the last matching history entry after the previous command' do
16 | with_history(*history) do
17 | session.send_string('ls')
18 | wait_for { session.content }.to eq('ls bar')
19 | end
20 | end
21 |
22 | context 'when ZSH_AUTOSUGGEST_HISTORY_IGNORE is set to a pattern' do
23 | let(:options) { ['ZSH_AUTOSUGGEST_STRATEGY=match_prev_cmd', 'ZSH_AUTOSUGGEST_HISTORY_IGNORE="* bar"'] }
24 |
25 | it 'does not make suggestions that match the pattern' do
26 | with_history(*history) do
27 | session.send_string('ls')
28 | wait_for { session.content }.to eq('ls foo')
29 | end
30 | end
31 | end
32 |
33 | include_examples 'special characters'
34 | end
35 |
--------------------------------------------------------------------------------
/spec/strategies/special_characters_helper.rb:
--------------------------------------------------------------------------------
1 | shared_examples 'special characters' do
2 | describe 'a special character in the buffer should be treated like any other character' do
3 | it 'asterisk' do
4 | with_history('echo "hello*"', 'echo "hello."') do
5 | session.send_string('echo "hello*')
6 | wait_for { session.content }.to eq('echo "hello*"')
7 | end
8 | end
9 |
10 | it 'question mark' do
11 | with_history('echo "hello?"', 'echo "hello."') do
12 | session.send_string('echo "hello?')
13 | wait_for { session.content }.to eq('echo "hello?"')
14 | end
15 | end
16 |
17 | it 'backslash' do
18 | with_history('echo "hello\nworld"') do
19 | session.send_string('echo "hello\\')
20 | wait_for { session.content }.to eq('echo "hello\nworld"')
21 | end
22 | end
23 |
24 | it 'double backslash' do
25 | with_history('echo "\\\\"') do
26 | session.send_string('echo "\\\\')
27 | wait_for { session.content }.to eq('echo "\\\\"')
28 | end
29 | end
30 |
31 | it 'tilde' do
32 | with_history('echo ~/foo') do
33 | session.send_string('echo ~')
34 | wait_for { session.content }.to eq('echo ~/foo')
35 | end
36 | end
37 |
38 | it 'parentheses' do
39 | with_history('echo "$(ls foo)"') do
40 | session.send_string('echo "$(')
41 | wait_for { session.content }.to eq('echo "$(ls foo)"')
42 | end
43 | end
44 |
45 | it 'square bracket' do
46 | with_history('echo "$history[123]"') do
47 | session.send_string('echo "$history[')
48 | wait_for { session.content }.to eq('echo "$history[123]"')
49 | session.send_string('123]')
50 | wait_for { session.content }.to eq('echo "$history[123]"')
51 | end
52 | end
53 |
54 | it 'octothorpe' do
55 | with_history('echo "#yolo"') do
56 | session.send_string('echo "#')
57 | wait_for { session.content }.to eq('echo "#yolo"')
58 | end
59 | end
60 |
61 | it 'caret' do
62 | with_history('echo "^A"', 'echo "^B"') do
63 | session.send_string('echo "^A')
64 | wait_for { session.content }.to eq('echo "^A"')
65 | end
66 | end
67 |
68 | it 'dash' do
69 | with_history('-foo() {}') do
70 | session.send_string('-')
71 | wait_for { session.content }.to eq('-foo() {}')
72 | end
73 | end
74 | end
75 | end
76 |
--------------------------------------------------------------------------------
/spec/terminal_session.rb:
--------------------------------------------------------------------------------
1 | require 'securerandom'
2 |
3 | class TerminalSession
4 | ZSH_BIN = ENV['TEST_ZSH_BIN'] || 'zsh'
5 |
6 | def initialize(opts = {})
7 | opts = {
8 | width: 80,
9 | height: 24,
10 | prompt: '',
11 | term: 'xterm-256color',
12 | zsh_bin: ZSH_BIN
13 | }.merge(opts)
14 |
15 | @opts = opts
16 |
17 | cmd="PS1=\"#{opts[:prompt]}\" TERM=#{opts[:term]} #{ZSH_BIN} -f"
18 | tmux_command("new-session -d -x #{opts[:width]} -y #{opts[:height]} '#{cmd}'")
19 | end
20 |
21 | def zsh_version
22 | @zsh_version ||= Gem::Version.new(`#{ZSH_BIN} -c 'echo -n $ZSH_VERSION'`)
23 | end
24 |
25 | def tmux_socket_name
26 | @tmux_socket_name ||= SecureRandom.hex(6)
27 | end
28 |
29 | def run_command(command)
30 | send_string(command)
31 | send_keys('enter')
32 |
33 | self
34 | end
35 |
36 | def send_string(str)
37 | tmux_command("send-keys -t 0 -l -- '#{str.gsub("'", "\\'")}'")
38 |
39 | self
40 | end
41 |
42 | def send_keys(*keys)
43 | tmux_command("send-keys -t 0 #{keys.join(' ')}")
44 |
45 | self
46 | end
47 |
48 | def paste_string(str)
49 | tmux_command("set-buffer -- '#{str}'")
50 | tmux_command("paste-buffer -dpr -t 0")
51 |
52 | self
53 | end
54 |
55 | def content(esc_seqs: false)
56 | cmd = 'capture-pane -p -t 0'
57 | cmd += ' -e' if esc_seqs
58 | tmux_command(cmd).strip
59 | end
60 |
61 | def clear_screen
62 | send_keys('C-l')
63 |
64 | i = 0
65 | until content == opts[:prompt] || i > 20 do
66 | sleep(0.1)
67 | i = i + 1
68 | end
69 |
70 | self
71 | end
72 |
73 | def destroy
74 | tmux_command('kill-session')
75 | end
76 |
77 | def cursor
78 | tmux_command("display-message -t 0 -p '\#{cursor_x},\#{cursor_y}'").
79 | strip.
80 | split(',').
81 | map(&:to_i)
82 | end
83 |
84 | def attach!
85 | tmux_command('attach-session')
86 | end
87 |
88 | private
89 |
90 | attr_reader :opts
91 |
92 | def tmux_command(cmd)
93 | out = `tmux -u -L #{tmux_socket_name} #{cmd}`
94 |
95 | raise("tmux error running: '#{cmd}'") unless $?.success?
96 |
97 | out
98 | end
99 | end
100 |
--------------------------------------------------------------------------------
/spec/widgets/disable_spec.rb:
--------------------------------------------------------------------------------
1 | describe 'the `autosuggest-disable` widget' do
2 | before do
3 | session.run_command('bindkey ^B autosuggest-disable')
4 | end
5 |
6 | it 'disables suggestions and clears the suggestion' do
7 | with_history('echo hello') do
8 | session.send_string('echo')
9 | wait_for { session.content }.to eq('echo hello')
10 |
11 | session.send_keys('C-b')
12 | wait_for { session.content }.to eq('echo')
13 |
14 | session.send_string(' h')
15 | sleep 1
16 | expect(session.content).to eq('echo h')
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/spec/widgets/enable_spec.rb:
--------------------------------------------------------------------------------
1 | describe 'the `autosuggest-enable` widget' do
2 | before do
3 | session.
4 | run_command('typeset -g _ZSH_AUTOSUGGEST_DISABLED').
5 | run_command('bindkey ^B autosuggest-enable')
6 | end
7 |
8 | it 'enables suggestions and fetches a suggestion' do
9 | with_history('echo hello') do
10 | session.send_string('e')
11 | sleep 1
12 | expect(session.content).to eq('e')
13 |
14 | session.send_keys('C-b')
15 | session.send_string('c')
16 | wait_for { session.content }.to eq('echo hello')
17 | end
18 | end
19 |
20 | context 'invoked on an empty buffer' do
21 | it 'does not fetch a suggestion' do
22 | with_history('echo hello') do
23 | session.send_keys('C-b')
24 | sleep 1
25 | expect(session.content).to eq('')
26 | end
27 | end
28 | end
29 |
30 | context 'invoked on a non-empty buffer' do
31 | it 'fetches a suggestion' do
32 | with_history('echo hello') do
33 | session.send_string('e')
34 | sleep 1
35 | expect(session.content).to eq('e')
36 |
37 | session.send_keys('C-b')
38 | wait_for { session.content }.to eq('echo hello')
39 | end
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/spec/widgets/fetch_spec.rb:
--------------------------------------------------------------------------------
1 | describe 'the `autosuggest-fetch` widget' do
2 | context 'when suggestions are disabled' do
3 | before do
4 | session.
5 | run_command('bindkey ^B autosuggest-disable').
6 | run_command('bindkey ^F autosuggest-fetch').
7 | send_keys('C-b')
8 | end
9 |
10 | it 'will fetch and display a suggestion' do
11 | with_history('echo hello') do
12 | session.send_string('echo h')
13 | sleep 1
14 | expect(session.content).to eq('echo h')
15 |
16 | session.send_keys('C-f')
17 | wait_for { session.content }.to eq('echo hello')
18 |
19 | session.send_string('e')
20 | wait_for { session.content }.to eq('echo hello')
21 | end
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/spec/widgets/toggle_spec.rb:
--------------------------------------------------------------------------------
1 | describe 'the `autosuggest-toggle` widget' do
2 | before do
3 | session.run_command('bindkey ^B autosuggest-toggle')
4 | end
5 |
6 | it 'toggles suggestions' do
7 | with_history('echo world', 'echo hello') do
8 | session.send_string('echo')
9 | wait_for { session.content }.to eq('echo hello')
10 |
11 | session.send_keys('C-b')
12 | wait_for { session.content }.to eq('echo')
13 |
14 | session.send_string(' h')
15 | sleep 1
16 | expect(session.content).to eq('echo h')
17 |
18 | session.send_keys('C-b')
19 | wait_for { session.content }.to eq('echo hello')
20 |
21 | session.send_keys('C-h')
22 | session.send_string('w')
23 | wait_for { session.content }.to eq('echo world')
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/src/async.zsh:
--------------------------------------------------------------------------------
1 |
2 | #--------------------------------------------------------------------#
3 | # Async #
4 | #--------------------------------------------------------------------#
5 |
6 | _zsh_autosuggest_async_request() {
7 | zmodload zsh/system 2>/dev/null # For `$sysparams`
8 |
9 | typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD _ZSH_AUTOSUGGEST_CHILD_PID
10 |
11 | # If we've got a pending request, cancel it
12 | if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then
13 | # Close the file descriptor and remove the handler
14 | builtin exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&-
15 | zle -F $_ZSH_AUTOSUGGEST_ASYNC_FD
16 |
17 | # We won't know the pid unless the user has zsh/system module installed
18 | if [[ -n "$_ZSH_AUTOSUGGEST_CHILD_PID" ]]; then
19 | # Zsh will make a new process group for the child process only if job
20 | # control is enabled (MONITOR option)
21 | if [[ -o MONITOR ]]; then
22 | # Send the signal to the process group to kill any processes that may
23 | # have been forked by the suggestion strategy
24 | kill -TERM -$_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null
25 | else
26 | # Kill just the child process since it wasn't placed in a new process
27 | # group. If the suggestion strategy forked any child processes they may
28 | # be orphaned and left behind.
29 | kill -TERM $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null
30 | fi
31 | fi
32 | fi
33 |
34 | # Fork a process to fetch a suggestion and open a pipe to read from it
35 | builtin exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <(
36 | # Tell parent process our pid
37 | echo $sysparams[pid]
38 |
39 | # Fetch and print the suggestion
40 | local suggestion
41 | _zsh_autosuggest_fetch_suggestion "$1"
42 | echo -nE "$suggestion"
43 | )
44 |
45 | # There's a weird bug here where ^C stops working unless we force a fork
46 | # See https://github.com/zsh-users/zsh-autosuggestions/issues/364
47 | autoload -Uz is-at-least
48 | is-at-least 5.8 || command true
49 |
50 | # Read the pid from the child process
51 | read _ZSH_AUTOSUGGEST_CHILD_PID <&$_ZSH_AUTOSUGGEST_ASYNC_FD
52 |
53 | # When the fd is readable, call the response handler
54 | zle -F "$_ZSH_AUTOSUGGEST_ASYNC_FD" _zsh_autosuggest_async_response
55 | }
56 |
57 | # Called when new data is ready to be read from the pipe
58 | # First arg will be fd ready for reading
59 | # Second arg will be passed in case of error
60 | _zsh_autosuggest_async_response() {
61 | emulate -L zsh
62 |
63 | local suggestion
64 |
65 | if [[ -z "$2" || "$2" == "hup" ]]; then
66 | # Read everything from the fd and give it as a suggestion
67 | IFS='' read -rd '' -u $1 suggestion
68 | zle autosuggest-suggest -- "$suggestion"
69 |
70 | # Close the fd
71 | builtin exec {1}<&-
72 | fi
73 |
74 | # Always remove the handler
75 | zle -F "$1"
76 | _ZSH_AUTOSUGGEST_ASYNC_FD=
77 | }
78 |
--------------------------------------------------------------------------------
/src/bind.zsh:
--------------------------------------------------------------------------------
1 |
2 | #--------------------------------------------------------------------#
3 | # Widget Helpers #
4 | #--------------------------------------------------------------------#
5 |
6 | _zsh_autosuggest_incr_bind_count() {
7 | typeset -gi bind_count=$((_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]+1))
8 | _ZSH_AUTOSUGGEST_BIND_COUNTS[$1]=$bind_count
9 | }
10 |
11 | # Bind a single widget to an autosuggest widget, saving a reference to the original widget
12 | _zsh_autosuggest_bind_widget() {
13 | typeset -gA _ZSH_AUTOSUGGEST_BIND_COUNTS
14 |
15 | local widget=$1
16 | local autosuggest_action=$2
17 | local prefix=$ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX
18 |
19 | local -i bind_count
20 |
21 | # Save a reference to the original widget
22 | case $widgets[$widget] in
23 | # Already bound
24 | user:_zsh_autosuggest_(bound|orig)_*)
25 | bind_count=$((_ZSH_AUTOSUGGEST_BIND_COUNTS[$widget]))
26 | ;;
27 |
28 | # User-defined widget
29 | user:*)
30 | _zsh_autosuggest_incr_bind_count $widget
31 | zle -N $prefix$bind_count-$widget ${widgets[$widget]#*:}
32 | ;;
33 |
34 | # Built-in widget
35 | builtin)
36 | _zsh_autosuggest_incr_bind_count $widget
37 | eval "_zsh_autosuggest_orig_${(q)widget}() { zle .${(q)widget} }"
38 | zle -N $prefix$bind_count-$widget _zsh_autosuggest_orig_$widget
39 | ;;
40 |
41 | # Completion widget
42 | completion:*)
43 | _zsh_autosuggest_incr_bind_count $widget
44 | eval "zle -C $prefix$bind_count-${(q)widget} ${${(s.:.)widgets[$widget]}[2,3]}"
45 | ;;
46 | esac
47 |
48 | # Pass the original widget's name explicitly into the autosuggest
49 | # function. Use this passed in widget name to call the original
50 | # widget instead of relying on the $WIDGET variable being set
51 | # correctly. $WIDGET cannot be trusted because other plugins call
52 | # zle without the `-w` flag (e.g. `zle self-insert` instead of
53 | # `zle self-insert -w`).
54 | eval "_zsh_autosuggest_bound_${bind_count}_${(q)widget}() {
55 | _zsh_autosuggest_widget_$autosuggest_action $prefix$bind_count-${(q)widget} \$@
56 | }"
57 |
58 | # Create the bound widget
59 | zle -N -- $widget _zsh_autosuggest_bound_${bind_count}_$widget
60 | }
61 |
62 | # Map all configured widgets to the right autosuggest widgets
63 | _zsh_autosuggest_bind_widgets() {
64 | emulate -L zsh
65 |
66 | local widget
67 | local ignore_widgets
68 |
69 | ignore_widgets=(
70 | .\*
71 | _\*
72 | ${_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS/#/autosuggest-}
73 | $ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX\*
74 | $ZSH_AUTOSUGGEST_IGNORE_WIDGETS
75 | )
76 |
77 | # Find every widget we might want to bind and bind it appropriately
78 | for widget in ${${(f)"$(builtin zle -la)"}:#${(j:|:)~ignore_widgets}}; do
79 | if [[ -n ${ZSH_AUTOSUGGEST_CLEAR_WIDGETS[(r)$widget]} ]]; then
80 | _zsh_autosuggest_bind_widget $widget clear
81 | elif [[ -n ${ZSH_AUTOSUGGEST_ACCEPT_WIDGETS[(r)$widget]} ]]; then
82 | _zsh_autosuggest_bind_widget $widget accept
83 | elif [[ -n ${ZSH_AUTOSUGGEST_EXECUTE_WIDGETS[(r)$widget]} ]]; then
84 | _zsh_autosuggest_bind_widget $widget execute
85 | elif [[ -n ${ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS[(r)$widget]} ]]; then
86 | _zsh_autosuggest_bind_widget $widget partial_accept
87 | else
88 | # Assume any unspecified widget might modify the buffer
89 | _zsh_autosuggest_bind_widget $widget modify
90 | fi
91 | done
92 | }
93 |
94 | # Given the name of an original widget and args, invoke it, if it exists
95 | _zsh_autosuggest_invoke_original_widget() {
96 | # Do nothing unless called with at least one arg
97 | (( $# )) || return 0
98 |
99 | local original_widget_name="$1"
100 |
101 | shift
102 |
103 | if (( ${+widgets[$original_widget_name]} )); then
104 | zle $original_widget_name -- $@
105 | fi
106 | }
107 |
--------------------------------------------------------------------------------
/src/config.zsh:
--------------------------------------------------------------------------------
1 |
2 | #--------------------------------------------------------------------#
3 | # Global Configuration Variables #
4 | #--------------------------------------------------------------------#
5 |
6 | # Color to use when highlighting suggestion
7 | # Uses format of `region_highlight`
8 | # More info: http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Zle-Widgets
9 | (( ! ${+ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE} )) &&
10 | typeset -g ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8'
11 |
12 | # Prefix to use when saving original versions of bound widgets
13 | (( ! ${+ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX} )) &&
14 | typeset -g ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig-
15 |
16 | # Strategies to use to fetch a suggestion
17 | # Will try each strategy in order until a suggestion is returned
18 | (( ! ${+ZSH_AUTOSUGGEST_STRATEGY} )) && {
19 | typeset -ga ZSH_AUTOSUGGEST_STRATEGY
20 | ZSH_AUTOSUGGEST_STRATEGY=(history)
21 | }
22 |
23 | # Widgets that clear the suggestion
24 | (( ! ${+ZSH_AUTOSUGGEST_CLEAR_WIDGETS} )) && {
25 | typeset -ga ZSH_AUTOSUGGEST_CLEAR_WIDGETS
26 | ZSH_AUTOSUGGEST_CLEAR_WIDGETS=(
27 | history-search-forward
28 | history-search-backward
29 | history-beginning-search-forward
30 | history-beginning-search-backward
31 | history-beginning-search-forward-end
32 | history-beginning-search-backward-end
33 | history-substring-search-up
34 | history-substring-search-down
35 | up-line-or-beginning-search
36 | down-line-or-beginning-search
37 | up-line-or-history
38 | down-line-or-history
39 | accept-line
40 | copy-earlier-word
41 | )
42 | }
43 |
44 | # Widgets that accept the entire suggestion
45 | (( ! ${+ZSH_AUTOSUGGEST_ACCEPT_WIDGETS} )) && {
46 | typeset -ga ZSH_AUTOSUGGEST_ACCEPT_WIDGETS
47 | ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=(
48 | forward-char
49 | end-of-line
50 | vi-forward-char
51 | vi-end-of-line
52 | vi-add-eol
53 | )
54 | }
55 |
56 | # Widgets that accept the entire suggestion and execute it
57 | (( ! ${+ZSH_AUTOSUGGEST_EXECUTE_WIDGETS} )) && {
58 | typeset -ga ZSH_AUTOSUGGEST_EXECUTE_WIDGETS
59 | ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=(
60 | )
61 | }
62 |
63 | # Widgets that accept the suggestion as far as the cursor moves
64 | (( ! ${+ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS} )) && {
65 | typeset -ga ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS
66 | ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=(
67 | forward-word
68 | emacs-forward-word
69 | vi-forward-word
70 | vi-forward-word-end
71 | vi-forward-blank-word
72 | vi-forward-blank-word-end
73 | vi-find-next-char
74 | vi-find-next-char-skip
75 | )
76 | }
77 |
78 | # Widgets that should be ignored (globbing supported but must be escaped)
79 | (( ! ${+ZSH_AUTOSUGGEST_IGNORE_WIDGETS} )) && {
80 | typeset -ga ZSH_AUTOSUGGEST_IGNORE_WIDGETS
81 | ZSH_AUTOSUGGEST_IGNORE_WIDGETS=(
82 | orig-\*
83 | beep
84 | run-help
85 | set-local-history
86 | which-command
87 | yank
88 | yank-pop
89 | zle-\*
90 | )
91 | }
92 |
93 | # Pty name for capturing completions for completion suggestion strategy
94 | (( ! ${+ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME} )) &&
95 | typeset -g ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME=zsh_autosuggest_completion_pty
96 |
--------------------------------------------------------------------------------
/src/fetch.zsh:
--------------------------------------------------------------------------------
1 |
2 | #--------------------------------------------------------------------#
3 | # Fetch Suggestion #
4 | #--------------------------------------------------------------------#
5 | # Loops through all specified strategies and returns a suggestion
6 | # from the first strategy to provide one.
7 | #
8 |
9 | _zsh_autosuggest_fetch_suggestion() {
10 | typeset -g suggestion
11 | local -a strategies
12 | local strategy
13 |
14 | # Ensure we are working with an array
15 | strategies=(${=ZSH_AUTOSUGGEST_STRATEGY})
16 |
17 | for strategy in $strategies; do
18 | # Try to get a suggestion from this strategy
19 | _zsh_autosuggest_strategy_$strategy "$1"
20 |
21 | # Ensure the suggestion matches the prefix
22 | [[ "$suggestion" != "$1"* ]] && unset suggestion
23 |
24 | # Break once we've found a valid suggestion
25 | [[ -n "$suggestion" ]] && break
26 | done
27 | }
28 |
--------------------------------------------------------------------------------
/src/highlight.zsh:
--------------------------------------------------------------------------------
1 |
2 | #--------------------------------------------------------------------#
3 | # Highlighting #
4 | #--------------------------------------------------------------------#
5 |
6 | # If there was a highlight, remove it
7 | _zsh_autosuggest_highlight_reset() {
8 | typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
9 |
10 | if [[ -n "$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT" ]]; then
11 | region_highlight=("${(@)region_highlight:#$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT}")
12 | unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
13 | fi
14 | }
15 |
16 | # If there's a suggestion, highlight it
17 | _zsh_autosuggest_highlight_apply() {
18 | typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
19 |
20 | if (( $#POSTDISPLAY )); then
21 | typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT="$#BUFFER $(($#BUFFER + $#POSTDISPLAY)) $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE"
22 | region_highlight+=("$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT")
23 | else
24 | unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
25 | fi
26 | }
27 |
--------------------------------------------------------------------------------
/src/start.zsh:
--------------------------------------------------------------------------------
1 |
2 | #--------------------------------------------------------------------#
3 | # Start #
4 | #--------------------------------------------------------------------#
5 |
6 | # Start the autosuggestion widgets
7 | _zsh_autosuggest_start() {
8 | # By default we re-bind widgets on every precmd to ensure we wrap other
9 | # wrappers. Specifically, highlighting breaks if our widgets are wrapped by
10 | # zsh-syntax-highlighting widgets. This also allows modifications to the
11 | # widget list variables to take effect on the next precmd. However this has
12 | # a decent performance hit, so users can set ZSH_AUTOSUGGEST_MANUAL_REBIND
13 | # to disable the automatic re-binding.
14 | if (( ${+ZSH_AUTOSUGGEST_MANUAL_REBIND} )); then
15 | add-zsh-hook -d precmd _zsh_autosuggest_start
16 | fi
17 |
18 | _zsh_autosuggest_bind_widgets
19 | }
20 |
21 | # Mark for auto-loading the functions that we use
22 | autoload -Uz add-zsh-hook is-at-least
23 |
24 | # Automatically enable asynchronous mode in newer versions of zsh. Disable for
25 | # older versions because there is a bug when using async mode where ^C does not
26 | # work immediately after fetching a suggestion.
27 | # See https://github.com/zsh-users/zsh-autosuggestions/issues/364
28 | if is-at-least 5.0.8; then
29 | typeset -g ZSH_AUTOSUGGEST_USE_ASYNC=
30 | fi
31 |
32 | # Start the autosuggestion widgets on the next precmd
33 | add-zsh-hook precmd _zsh_autosuggest_start
34 |
--------------------------------------------------------------------------------
/src/strategies/completion.zsh:
--------------------------------------------------------------------------------
1 |
2 | #--------------------------------------------------------------------#
3 | # Completion Suggestion Strategy #
4 | #--------------------------------------------------------------------#
5 | # Fetches a suggestion from the completion engine
6 | #
7 |
8 | _zsh_autosuggest_capture_postcompletion() {
9 | # Always insert the first completion into the buffer
10 | compstate[insert]=1
11 |
12 | # Don't list completions
13 | unset 'compstate[list]'
14 | }
15 |
16 | _zsh_autosuggest_capture_completion_widget() {
17 | # Add a post-completion hook to be called after all completions have been
18 | # gathered. The hook can modify compstate to affect what is done with the
19 | # gathered completions.
20 | local -a +h comppostfuncs
21 | comppostfuncs=(_zsh_autosuggest_capture_postcompletion)
22 |
23 | # Only capture completions at the end of the buffer
24 | CURSOR=$#BUFFER
25 |
26 | # Run the original widget wrapping `.complete-word` so we don't
27 | # recursively try to fetch suggestions, since our pty is forked
28 | # after autosuggestions is initialized.
29 | zle -- ${(k)widgets[(r)completion:.complete-word:_main_complete]}
30 |
31 | if is-at-least 5.0.3; then
32 | # Don't do any cr/lf transformations. We need to do this immediately before
33 | # output because if we do it in setup, onlcr will be re-enabled when we enter
34 | # vared in the async code path. There is a bug in zpty module in older versions
35 | # where the tty is not properly attached to the pty slave, resulting in stty
36 | # getting stopped with a SIGTTOU. See zsh-workers thread 31660 and upstream
37 | # commit f75904a38
38 | stty -onlcr -ocrnl -F /dev/tty
39 | fi
40 |
41 | # The completion has been added, print the buffer as the suggestion
42 | echo -nE - $'\0'$BUFFER$'\0'
43 | }
44 |
45 | zle -N autosuggest-capture-completion _zsh_autosuggest_capture_completion_widget
46 |
47 | _zsh_autosuggest_capture_setup() {
48 | # There is a bug in zpty module in older zsh versions by which a
49 | # zpty that exits will kill all zpty processes that were forked
50 | # before it. Here we set up a zsh exit hook to SIGKILL the zpty
51 | # process immediately, before it has a chance to kill any other
52 | # zpty processes.
53 | if ! is-at-least 5.4; then
54 | zshexit() {
55 | # The zsh builtin `kill` fails sometimes in older versions
56 | # https://unix.stackexchange.com/a/477647/156673
57 | kill -KILL $$ 2>&- || command kill -KILL $$
58 |
59 | # Block for long enough for the signal to come through
60 | sleep 1
61 | }
62 | fi
63 |
64 | # Try to avoid any suggestions that wouldn't match the prefix
65 | zstyle ':completion:*' matcher-list ''
66 | zstyle ':completion:*' path-completion false
67 | zstyle ':completion:*' max-errors 0 not-numeric
68 |
69 | bindkey '^I' autosuggest-capture-completion
70 | }
71 |
72 | _zsh_autosuggest_capture_completion_sync() {
73 | _zsh_autosuggest_capture_setup
74 |
75 | zle autosuggest-capture-completion
76 | }
77 |
78 | _zsh_autosuggest_capture_completion_async() {
79 | _zsh_autosuggest_capture_setup
80 |
81 | zmodload zsh/parameter 2>/dev/null || return # For `$functions`
82 |
83 | # Make vared completion work as if for a normal command line
84 | # https://stackoverflow.com/a/7057118/154703
85 | autoload +X _complete
86 | functions[_original_complete]=$functions[_complete]
87 | function _complete() {
88 | unset 'compstate[vared]'
89 | _original_complete "$@"
90 | }
91 |
92 | # Open zle with buffer set so we can capture completions for it
93 | vared 1
94 | }
95 |
96 | _zsh_autosuggest_strategy_completion() {
97 | # Reset options to defaults and enable LOCAL_OPTIONS
98 | emulate -L zsh
99 |
100 | # Enable extended glob for completion ignore pattern
101 | setopt EXTENDED_GLOB
102 |
103 | typeset -g suggestion
104 | local line REPLY
105 |
106 | # Exit if we don't have completions
107 | whence compdef >/dev/null || return
108 |
109 | # Exit if we don't have zpty
110 | zmodload zsh/zpty 2>/dev/null || return
111 |
112 | # Exit if our search string matches the ignore pattern
113 | [[ -n "$ZSH_AUTOSUGGEST_COMPLETION_IGNORE" ]] && [[ "$1" == $~ZSH_AUTOSUGGEST_COMPLETION_IGNORE ]] && return
114 |
115 | # Zle will be inactive if we are in async mode
116 | if zle; then
117 | zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_completion_sync
118 | else
119 | zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_completion_async "\$1"
120 | zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME $'\t'
121 | fi
122 |
123 | {
124 | # The completion result is surrounded by null bytes, so read the
125 | # content between the first two null bytes.
126 | zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line '*'$'\0''*'$'\0'
127 |
128 | # Extract the suggestion from between the null bytes. On older
129 | # versions of zsh (older than 5.3), we sometimes get extra bytes after
130 | # the second null byte, so trim those off the end.
131 | # See http://www.zsh.org/mla/workers/2015/msg03290.html
132 | suggestion="${${(@0)line}[2]}"
133 | } always {
134 | # Destroy the pty
135 | zpty -d $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/strategies/history.zsh:
--------------------------------------------------------------------------------
1 |
2 | #--------------------------------------------------------------------#
3 | # History Suggestion Strategy #
4 | #--------------------------------------------------------------------#
5 | # Suggests the most recent history item that matches the given
6 | # prefix.
7 | #
8 |
9 | _zsh_autosuggest_strategy_history() {
10 | # Reset options to defaults and enable LOCAL_OPTIONS
11 | emulate -L zsh
12 |
13 | # Enable globbing flags so that we can use (#m) and (x~y) glob operator
14 | setopt EXTENDED_GLOB
15 |
16 | # Escape backslashes and all of the glob operators so we can use
17 | # this string as a pattern to search the $history associative array.
18 | # - (#m) globbing flag enables setting references for match data
19 | # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8
20 | local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}"
21 |
22 | # Get the history items that match the prefix, excluding those that match
23 | # the ignore pattern
24 | local pattern="$prefix*"
25 | if [[ -n $ZSH_AUTOSUGGEST_HISTORY_IGNORE ]]; then
26 | pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)"
27 | fi
28 |
29 | # Give the first history item matching the pattern as the suggestion
30 | # - (r) subscript flag makes the pattern match on values
31 | typeset -g suggestion="${history[(r)$pattern]}"
32 | }
33 |
--------------------------------------------------------------------------------
/src/strategies/match_prev_cmd.zsh:
--------------------------------------------------------------------------------
1 |
2 | #--------------------------------------------------------------------#
3 | # Match Previous Command Suggestion Strategy #
4 | #--------------------------------------------------------------------#
5 | # Suggests the most recent history item that matches the given
6 | # prefix and whose preceding history item also matches the most
7 | # recently executed command.
8 | #
9 | # For example, suppose your history has the following entries:
10 | # - pwd
11 | # - ls foo
12 | # - ls bar
13 | # - pwd
14 | #
15 | # Given the history list above, when you type 'ls', the suggestion
16 | # will be 'ls foo' rather than 'ls bar' because your most recently
17 | # executed command (pwd) was previously followed by 'ls foo'.
18 | #
19 | # Note that this strategy won't work as expected with ZSH options that don't
20 | # preserve the history order such as `HIST_IGNORE_ALL_DUPS` or
21 | # `HIST_EXPIRE_DUPS_FIRST`.
22 |
23 | _zsh_autosuggest_strategy_match_prev_cmd() {
24 | # Reset options to defaults and enable LOCAL_OPTIONS
25 | emulate -L zsh
26 |
27 | # Enable globbing flags so that we can use (#m) and (x~y) glob operator
28 | setopt EXTENDED_GLOB
29 |
30 | # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8
31 | local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}"
32 |
33 | # Get the history items that match the prefix, excluding those that match
34 | # the ignore pattern
35 | local pattern="$prefix*"
36 | if [[ -n $ZSH_AUTOSUGGEST_HISTORY_IGNORE ]]; then
37 | pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)"
38 | fi
39 |
40 | # Get all history event numbers that correspond to history
41 | # entries that match the pattern
42 | local history_match_keys
43 | history_match_keys=(${(k)history[(R)$~pattern]})
44 |
45 | # By default we use the first history number (most recent history entry)
46 | local histkey="${history_match_keys[1]}"
47 |
48 | # Get the previously executed command
49 | local prev_cmd="$(_zsh_autosuggest_escape_command "${history[$((HISTCMD-1))]}")"
50 |
51 | # Iterate up to the first 200 history event numbers that match $prefix
52 | for key in "${(@)history_match_keys[1,200]}"; do
53 | # Stop if we ran out of history
54 | [[ $key -gt 1 ]] || break
55 |
56 | # See if the history entry preceding the suggestion matches the
57 | # previous command, and use it if it does
58 | if [[ "${history[$((key - 1))]}" == "$prev_cmd" ]]; then
59 | histkey="$key"
60 | break
61 | fi
62 | done
63 |
64 | # Give back the matched history entry
65 | typeset -g suggestion="$history[$histkey]"
66 | }
67 |
--------------------------------------------------------------------------------
/src/util.zsh:
--------------------------------------------------------------------------------
1 |
2 | #--------------------------------------------------------------------#
3 | # Utility Functions #
4 | #--------------------------------------------------------------------#
5 |
6 | _zsh_autosuggest_escape_command() {
7 | setopt localoptions EXTENDED_GLOB
8 |
9 | # Escape special chars in the string (requires EXTENDED_GLOB)
10 | echo -E "${1//(#m)[\"\'\\()\[\]|*?~]/\\$MATCH}"
11 | }
12 |
--------------------------------------------------------------------------------
/src/widgets.zsh:
--------------------------------------------------------------------------------
1 |
2 | #--------------------------------------------------------------------#
3 | # Autosuggest Widget Implementations #
4 | #--------------------------------------------------------------------#
5 |
6 | # Disable suggestions
7 | _zsh_autosuggest_disable() {
8 | typeset -g _ZSH_AUTOSUGGEST_DISABLED
9 | _zsh_autosuggest_clear
10 | }
11 |
12 | # Enable suggestions
13 | _zsh_autosuggest_enable() {
14 | unset _ZSH_AUTOSUGGEST_DISABLED
15 |
16 | if (( $#BUFFER )); then
17 | _zsh_autosuggest_fetch
18 | fi
19 | }
20 |
21 | # Toggle suggestions (enable/disable)
22 | _zsh_autosuggest_toggle() {
23 | if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )); then
24 | _zsh_autosuggest_enable
25 | else
26 | _zsh_autosuggest_disable
27 | fi
28 | }
29 |
30 | # Clear the suggestion
31 | _zsh_autosuggest_clear() {
32 | # Remove the suggestion
33 | POSTDISPLAY=
34 |
35 | _zsh_autosuggest_invoke_original_widget $@
36 | }
37 |
38 | # Modify the buffer and get a new suggestion
39 | _zsh_autosuggest_modify() {
40 | local -i retval
41 |
42 | # Only available in zsh >= 5.4
43 | local -i KEYS_QUEUED_COUNT
44 |
45 | # Save the contents of the buffer/postdisplay
46 | local orig_buffer="$BUFFER"
47 | local orig_postdisplay="$POSTDISPLAY"
48 |
49 | # Clear suggestion while waiting for next one
50 | POSTDISPLAY=
51 |
52 | # Original widget may modify the buffer
53 | _zsh_autosuggest_invoke_original_widget $@
54 | retval=$?
55 |
56 | emulate -L zsh
57 |
58 | # Don't fetch a new suggestion if there's more input to be read immediately
59 | if (( $PENDING > 0 || $KEYS_QUEUED_COUNT > 0 )); then
60 | POSTDISPLAY="$orig_postdisplay"
61 | return $retval
62 | fi
63 |
64 | # Optimize if manually typing in the suggestion or if buffer hasn't changed
65 | if [[ "$BUFFER" = "$orig_buffer"* && "$orig_postdisplay" = "${BUFFER:$#orig_buffer}"* ]]; then
66 | POSTDISPLAY="${orig_postdisplay:$(($#BUFFER - $#orig_buffer))}"
67 | return $retval
68 | fi
69 |
70 | # Bail out if suggestions are disabled
71 | if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )); then
72 | return $?
73 | fi
74 |
75 | # Get a new suggestion if the buffer is not empty after modification
76 | if (( $#BUFFER > 0 )); then
77 | if [[ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]] || (( $#BUFFER <= $ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE )); then
78 | _zsh_autosuggest_fetch
79 | fi
80 | fi
81 |
82 | return $retval
83 | }
84 |
85 | # Fetch a new suggestion based on what's currently in the buffer
86 | _zsh_autosuggest_fetch() {
87 | if (( ${+ZSH_AUTOSUGGEST_USE_ASYNC} )); then
88 | _zsh_autosuggest_async_request "$BUFFER"
89 | else
90 | local suggestion
91 | _zsh_autosuggest_fetch_suggestion "$BUFFER"
92 | _zsh_autosuggest_suggest "$suggestion"
93 | fi
94 | }
95 |
96 | # Offer a suggestion
97 | _zsh_autosuggest_suggest() {
98 | emulate -L zsh
99 |
100 | local suggestion="$1"
101 |
102 | if [[ -n "$suggestion" ]] && (( $#BUFFER )); then
103 | POSTDISPLAY="${suggestion#$BUFFER}"
104 | else
105 | POSTDISPLAY=
106 | fi
107 | }
108 |
109 | # Accept the entire suggestion
110 | _zsh_autosuggest_accept() {
111 | local -i retval max_cursor_pos=$#BUFFER
112 |
113 | # When vicmd keymap is active, the cursor can't move all the way
114 | # to the end of the buffer
115 | if [[ "$KEYMAP" = "vicmd" ]]; then
116 | max_cursor_pos=$((max_cursor_pos - 1))
117 | fi
118 |
119 | # If we're not in a valid state to accept a suggestion, just run the
120 | # original widget and bail out
121 | if (( $CURSOR != $max_cursor_pos || !$#POSTDISPLAY )); then
122 | _zsh_autosuggest_invoke_original_widget $@
123 | return
124 | fi
125 |
126 | # Only accept if the cursor is at the end of the buffer
127 | # Add the suggestion to the buffer
128 | BUFFER="$BUFFER$POSTDISPLAY"
129 |
130 | # Remove the suggestion
131 | POSTDISPLAY=
132 |
133 | # Run the original widget before manually moving the cursor so that the
134 | # cursor movement doesn't make the widget do something unexpected
135 | _zsh_autosuggest_invoke_original_widget $@
136 | retval=$?
137 |
138 | # Move the cursor to the end of the buffer
139 | if [[ "$KEYMAP" = "vicmd" ]]; then
140 | CURSOR=$(($#BUFFER - 1))
141 | else
142 | CURSOR=$#BUFFER
143 | fi
144 |
145 | return $retval
146 | }
147 |
148 | # Accept the entire suggestion and execute it
149 | _zsh_autosuggest_execute() {
150 | # Add the suggestion to the buffer
151 | BUFFER="$BUFFER$POSTDISPLAY"
152 |
153 | # Remove the suggestion
154 | POSTDISPLAY=
155 |
156 | # Call the original `accept-line` to handle syntax highlighting or
157 | # other potential custom behavior
158 | _zsh_autosuggest_invoke_original_widget "accept-line"
159 | }
160 |
161 | # Partially accept the suggestion
162 | _zsh_autosuggest_partial_accept() {
163 | local -i retval cursor_loc
164 |
165 | # Save the contents of the buffer so we can restore later if needed
166 | local original_buffer="$BUFFER"
167 |
168 | # Temporarily accept the suggestion.
169 | BUFFER="$BUFFER$POSTDISPLAY"
170 |
171 | # Original widget moves the cursor
172 | _zsh_autosuggest_invoke_original_widget $@
173 | retval=$?
174 |
175 | # Normalize cursor location across vi/emacs modes
176 | cursor_loc=$CURSOR
177 | if [[ "$KEYMAP" = "vicmd" ]]; then
178 | cursor_loc=$((cursor_loc + 1))
179 | fi
180 |
181 | # If we've moved past the end of the original buffer
182 | if (( $cursor_loc > $#original_buffer )); then
183 | # Set POSTDISPLAY to text right of the cursor
184 | POSTDISPLAY="${BUFFER[$(($cursor_loc + 1)),$#BUFFER]}"
185 |
186 | # Clip the buffer at the cursor
187 | BUFFER="${BUFFER[1,$cursor_loc]}"
188 | else
189 | # Restore the original buffer
190 | BUFFER="$original_buffer"
191 | fi
192 |
193 | return $retval
194 | }
195 |
196 | () {
197 | typeset -ga _ZSH_AUTOSUGGEST_BUILTIN_ACTIONS
198 |
199 | _ZSH_AUTOSUGGEST_BUILTIN_ACTIONS=(
200 | clear
201 | fetch
202 | suggest
203 | accept
204 | execute
205 | enable
206 | disable
207 | toggle
208 | )
209 |
210 | local action
211 | for action in $_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS modify partial_accept; do
212 | eval "_zsh_autosuggest_widget_$action() {
213 | local -i retval
214 |
215 | _zsh_autosuggest_highlight_reset
216 |
217 | _zsh_autosuggest_$action \$@
218 | retval=\$?
219 |
220 | _zsh_autosuggest_highlight_apply
221 |
222 | zle -R
223 |
224 | return \$retval
225 | }"
226 | done
227 |
228 | for action in $_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS; do
229 | zle -N autosuggest-$action _zsh_autosuggest_widget_$action
230 | done
231 | }
232 |
--------------------------------------------------------------------------------
/zsh-autosuggestions.plugin.zsh:
--------------------------------------------------------------------------------
1 | source ${0:A:h}/zsh-autosuggestions.zsh
2 |
--------------------------------------------------------------------------------
/zsh-autosuggestions.zsh:
--------------------------------------------------------------------------------
1 | # Fish-like fast/unobtrusive autosuggestions for zsh.
2 | # https://github.com/zsh-users/zsh-autosuggestions
3 | # v0.7.1
4 | # Copyright (c) 2013 Thiago de Arruda
5 | # Copyright (c) 2016-2021 Eric Freese
6 | #
7 | # Permission is hereby granted, free of charge, to any person
8 | # obtaining a copy of this software and associated documentation
9 | # files (the "Software"), to deal in the Software without
10 | # restriction, including without limitation the rights to use,
11 | # copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | # copies of the Software, and to permit persons to whom the
13 | # Software is furnished to do so, subject to the following
14 | # conditions:
15 | #
16 | # The above copyright notice and this permission notice shall be
17 | # included in all copies or substantial portions of the Software.
18 | #
19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
21 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
23 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
24 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
26 | # OTHER DEALINGS IN THE SOFTWARE.
27 |
28 | #--------------------------------------------------------------------#
29 | # Global Configuration Variables #
30 | #--------------------------------------------------------------------#
31 |
32 | # Color to use when highlighting suggestion
33 | # Uses format of `region_highlight`
34 | # More info: http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Zle-Widgets
35 | (( ! ${+ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE} )) &&
36 | typeset -g ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8'
37 |
38 | # Prefix to use when saving original versions of bound widgets
39 | (( ! ${+ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX} )) &&
40 | typeset -g ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig-
41 |
42 | # Strategies to use to fetch a suggestion
43 | # Will try each strategy in order until a suggestion is returned
44 | (( ! ${+ZSH_AUTOSUGGEST_STRATEGY} )) && {
45 | typeset -ga ZSH_AUTOSUGGEST_STRATEGY
46 | ZSH_AUTOSUGGEST_STRATEGY=(history)
47 | }
48 |
49 | # Widgets that clear the suggestion
50 | (( ! ${+ZSH_AUTOSUGGEST_CLEAR_WIDGETS} )) && {
51 | typeset -ga ZSH_AUTOSUGGEST_CLEAR_WIDGETS
52 | ZSH_AUTOSUGGEST_CLEAR_WIDGETS=(
53 | history-search-forward
54 | history-search-backward
55 | history-beginning-search-forward
56 | history-beginning-search-backward
57 | history-beginning-search-forward-end
58 | history-beginning-search-backward-end
59 | history-substring-search-up
60 | history-substring-search-down
61 | up-line-or-beginning-search
62 | down-line-or-beginning-search
63 | up-line-or-history
64 | down-line-or-history
65 | accept-line
66 | copy-earlier-word
67 | )
68 | }
69 |
70 | # Widgets that accept the entire suggestion
71 | (( ! ${+ZSH_AUTOSUGGEST_ACCEPT_WIDGETS} )) && {
72 | typeset -ga ZSH_AUTOSUGGEST_ACCEPT_WIDGETS
73 | ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=(
74 | forward-char
75 | end-of-line
76 | vi-forward-char
77 | vi-end-of-line
78 | vi-add-eol
79 | )
80 | }
81 |
82 | # Widgets that accept the entire suggestion and execute it
83 | (( ! ${+ZSH_AUTOSUGGEST_EXECUTE_WIDGETS} )) && {
84 | typeset -ga ZSH_AUTOSUGGEST_EXECUTE_WIDGETS
85 | ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=(
86 | )
87 | }
88 |
89 | # Widgets that accept the suggestion as far as the cursor moves
90 | (( ! ${+ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS} )) && {
91 | typeset -ga ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS
92 | ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=(
93 | forward-word
94 | emacs-forward-word
95 | vi-forward-word
96 | vi-forward-word-end
97 | vi-forward-blank-word
98 | vi-forward-blank-word-end
99 | vi-find-next-char
100 | vi-find-next-char-skip
101 | )
102 | }
103 |
104 | # Widgets that should be ignored (globbing supported but must be escaped)
105 | (( ! ${+ZSH_AUTOSUGGEST_IGNORE_WIDGETS} )) && {
106 | typeset -ga ZSH_AUTOSUGGEST_IGNORE_WIDGETS
107 | ZSH_AUTOSUGGEST_IGNORE_WIDGETS=(
108 | orig-\*
109 | beep
110 | run-help
111 | set-local-history
112 | which-command
113 | yank
114 | yank-pop
115 | zle-\*
116 | )
117 | }
118 |
119 | # Pty name for capturing completions for completion suggestion strategy
120 | (( ! ${+ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME} )) &&
121 | typeset -g ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME=zsh_autosuggest_completion_pty
122 |
123 | #--------------------------------------------------------------------#
124 | # Utility Functions #
125 | #--------------------------------------------------------------------#
126 |
127 | _zsh_autosuggest_escape_command() {
128 | setopt localoptions EXTENDED_GLOB
129 |
130 | # Escape special chars in the string (requires EXTENDED_GLOB)
131 | echo -E "${1//(#m)[\"\'\\()\[\]|*?~]/\\$MATCH}"
132 | }
133 |
134 | #--------------------------------------------------------------------#
135 | # Widget Helpers #
136 | #--------------------------------------------------------------------#
137 |
138 | _zsh_autosuggest_incr_bind_count() {
139 | typeset -gi bind_count=$((_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]+1))
140 | _ZSH_AUTOSUGGEST_BIND_COUNTS[$1]=$bind_count
141 | }
142 |
143 | # Bind a single widget to an autosuggest widget, saving a reference to the original widget
144 | _zsh_autosuggest_bind_widget() {
145 | typeset -gA _ZSH_AUTOSUGGEST_BIND_COUNTS
146 |
147 | local widget=$1
148 | local autosuggest_action=$2
149 | local prefix=$ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX
150 |
151 | local -i bind_count
152 |
153 | # Save a reference to the original widget
154 | case $widgets[$widget] in
155 | # Already bound
156 | user:_zsh_autosuggest_(bound|orig)_*)
157 | bind_count=$((_ZSH_AUTOSUGGEST_BIND_COUNTS[$widget]))
158 | ;;
159 |
160 | # User-defined widget
161 | user:*)
162 | _zsh_autosuggest_incr_bind_count $widget
163 | zle -N $prefix$bind_count-$widget ${widgets[$widget]#*:}
164 | ;;
165 |
166 | # Built-in widget
167 | builtin)
168 | _zsh_autosuggest_incr_bind_count $widget
169 | eval "_zsh_autosuggest_orig_${(q)widget}() { zle .${(q)widget} }"
170 | zle -N $prefix$bind_count-$widget _zsh_autosuggest_orig_$widget
171 | ;;
172 |
173 | # Completion widget
174 | completion:*)
175 | _zsh_autosuggest_incr_bind_count $widget
176 | eval "zle -C $prefix$bind_count-${(q)widget} ${${(s.:.)widgets[$widget]}[2,3]}"
177 | ;;
178 | esac
179 |
180 | # Pass the original widget's name explicitly into the autosuggest
181 | # function. Use this passed in widget name to call the original
182 | # widget instead of relying on the $WIDGET variable being set
183 | # correctly. $WIDGET cannot be trusted because other plugins call
184 | # zle without the `-w` flag (e.g. `zle self-insert` instead of
185 | # `zle self-insert -w`).
186 | eval "_zsh_autosuggest_bound_${bind_count}_${(q)widget}() {
187 | _zsh_autosuggest_widget_$autosuggest_action $prefix$bind_count-${(q)widget} \$@
188 | }"
189 |
190 | # Create the bound widget
191 | zle -N -- $widget _zsh_autosuggest_bound_${bind_count}_$widget
192 | }
193 |
194 | # Map all configured widgets to the right autosuggest widgets
195 | _zsh_autosuggest_bind_widgets() {
196 | emulate -L zsh
197 |
198 | local widget
199 | local ignore_widgets
200 |
201 | ignore_widgets=(
202 | .\*
203 | _\*
204 | ${_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS/#/autosuggest-}
205 | $ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX\*
206 | $ZSH_AUTOSUGGEST_IGNORE_WIDGETS
207 | )
208 |
209 | # Find every widget we might want to bind and bind it appropriately
210 | for widget in ${${(f)"$(builtin zle -la)"}:#${(j:|:)~ignore_widgets}}; do
211 | if [[ -n ${ZSH_AUTOSUGGEST_CLEAR_WIDGETS[(r)$widget]} ]]; then
212 | _zsh_autosuggest_bind_widget $widget clear
213 | elif [[ -n ${ZSH_AUTOSUGGEST_ACCEPT_WIDGETS[(r)$widget]} ]]; then
214 | _zsh_autosuggest_bind_widget $widget accept
215 | elif [[ -n ${ZSH_AUTOSUGGEST_EXECUTE_WIDGETS[(r)$widget]} ]]; then
216 | _zsh_autosuggest_bind_widget $widget execute
217 | elif [[ -n ${ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS[(r)$widget]} ]]; then
218 | _zsh_autosuggest_bind_widget $widget partial_accept
219 | else
220 | # Assume any unspecified widget might modify the buffer
221 | _zsh_autosuggest_bind_widget $widget modify
222 | fi
223 | done
224 | }
225 |
226 | # Given the name of an original widget and args, invoke it, if it exists
227 | _zsh_autosuggest_invoke_original_widget() {
228 | # Do nothing unless called with at least one arg
229 | (( $# )) || return 0
230 |
231 | local original_widget_name="$1"
232 |
233 | shift
234 |
235 | if (( ${+widgets[$original_widget_name]} )); then
236 | zle $original_widget_name -- $@
237 | fi
238 | }
239 |
240 | #--------------------------------------------------------------------#
241 | # Highlighting #
242 | #--------------------------------------------------------------------#
243 |
244 | # If there was a highlight, remove it
245 | _zsh_autosuggest_highlight_reset() {
246 | typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
247 |
248 | if [[ -n "$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT" ]]; then
249 | region_highlight=("${(@)region_highlight:#$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT}")
250 | unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
251 | fi
252 | }
253 |
254 | # If there's a suggestion, highlight it
255 | _zsh_autosuggest_highlight_apply() {
256 | typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
257 |
258 | if (( $#POSTDISPLAY )); then
259 | typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT="$#BUFFER $(($#BUFFER + $#POSTDISPLAY)) $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE"
260 | region_highlight+=("$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT")
261 | else
262 | unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
263 | fi
264 | }
265 |
266 | #--------------------------------------------------------------------#
267 | # Autosuggest Widget Implementations #
268 | #--------------------------------------------------------------------#
269 |
270 | # Disable suggestions
271 | _zsh_autosuggest_disable() {
272 | typeset -g _ZSH_AUTOSUGGEST_DISABLED
273 | _zsh_autosuggest_clear
274 | }
275 |
276 | # Enable suggestions
277 | _zsh_autosuggest_enable() {
278 | unset _ZSH_AUTOSUGGEST_DISABLED
279 |
280 | if (( $#BUFFER )); then
281 | _zsh_autosuggest_fetch
282 | fi
283 | }
284 |
285 | # Toggle suggestions (enable/disable)
286 | _zsh_autosuggest_toggle() {
287 | if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )); then
288 | _zsh_autosuggest_enable
289 | else
290 | _zsh_autosuggest_disable
291 | fi
292 | }
293 |
294 | # Clear the suggestion
295 | _zsh_autosuggest_clear() {
296 | # Remove the suggestion
297 | POSTDISPLAY=
298 |
299 | _zsh_autosuggest_invoke_original_widget $@
300 | }
301 |
302 | # Modify the buffer and get a new suggestion
303 | _zsh_autosuggest_modify() {
304 | local -i retval
305 |
306 | # Only available in zsh >= 5.4
307 | local -i KEYS_QUEUED_COUNT
308 |
309 | # Save the contents of the buffer/postdisplay
310 | local orig_buffer="$BUFFER"
311 | local orig_postdisplay="$POSTDISPLAY"
312 |
313 | # Clear suggestion while waiting for next one
314 | POSTDISPLAY=
315 |
316 | # Original widget may modify the buffer
317 | _zsh_autosuggest_invoke_original_widget $@
318 | retval=$?
319 |
320 | emulate -L zsh
321 |
322 | # Don't fetch a new suggestion if there's more input to be read immediately
323 | if (( $PENDING > 0 || $KEYS_QUEUED_COUNT > 0 )); then
324 | POSTDISPLAY="$orig_postdisplay"
325 | return $retval
326 | fi
327 |
328 | # Optimize if manually typing in the suggestion or if buffer hasn't changed
329 | if [[ "$BUFFER" = "$orig_buffer"* && "$orig_postdisplay" = "${BUFFER:$#orig_buffer}"* ]]; then
330 | POSTDISPLAY="${orig_postdisplay:$(($#BUFFER - $#orig_buffer))}"
331 | return $retval
332 | fi
333 |
334 | # Bail out if suggestions are disabled
335 | if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )); then
336 | return $?
337 | fi
338 |
339 | # Get a new suggestion if the buffer is not empty after modification
340 | if (( $#BUFFER > 0 )); then
341 | if [[ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]] || (( $#BUFFER <= $ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE )); then
342 | _zsh_autosuggest_fetch
343 | fi
344 | fi
345 |
346 | return $retval
347 | }
348 |
349 | # Fetch a new suggestion based on what's currently in the buffer
350 | _zsh_autosuggest_fetch() {
351 | if (( ${+ZSH_AUTOSUGGEST_USE_ASYNC} )); then
352 | _zsh_autosuggest_async_request "$BUFFER"
353 | else
354 | local suggestion
355 | _zsh_autosuggest_fetch_suggestion "$BUFFER"
356 | _zsh_autosuggest_suggest "$suggestion"
357 | fi
358 | }
359 |
360 | # Offer a suggestion
361 | _zsh_autosuggest_suggest() {
362 | emulate -L zsh
363 |
364 | local suggestion="$1"
365 |
366 | if [[ -n "$suggestion" ]] && (( $#BUFFER )); then
367 | POSTDISPLAY="${suggestion#$BUFFER}"
368 | else
369 | POSTDISPLAY=
370 | fi
371 | }
372 |
373 | # Accept the entire suggestion
374 | _zsh_autosuggest_accept() {
375 | local -i retval max_cursor_pos=$#BUFFER
376 |
377 | # When vicmd keymap is active, the cursor can't move all the way
378 | # to the end of the buffer
379 | if [[ "$KEYMAP" = "vicmd" ]]; then
380 | max_cursor_pos=$((max_cursor_pos - 1))
381 | fi
382 |
383 | # If we're not in a valid state to accept a suggestion, just run the
384 | # original widget and bail out
385 | if (( $CURSOR != $max_cursor_pos || !$#POSTDISPLAY )); then
386 | _zsh_autosuggest_invoke_original_widget $@
387 | return
388 | fi
389 |
390 | # Only accept if the cursor is at the end of the buffer
391 | # Add the suggestion to the buffer
392 | BUFFER="$BUFFER$POSTDISPLAY"
393 |
394 | # Remove the suggestion
395 | POSTDISPLAY=
396 |
397 | # Run the original widget before manually moving the cursor so that the
398 | # cursor movement doesn't make the widget do something unexpected
399 | _zsh_autosuggest_invoke_original_widget $@
400 | retval=$?
401 |
402 | # Move the cursor to the end of the buffer
403 | if [[ "$KEYMAP" = "vicmd" ]]; then
404 | CURSOR=$(($#BUFFER - 1))
405 | else
406 | CURSOR=$#BUFFER
407 | fi
408 |
409 | return $retval
410 | }
411 |
412 | # Accept the entire suggestion and execute it
413 | _zsh_autosuggest_execute() {
414 | # Add the suggestion to the buffer
415 | BUFFER="$BUFFER$POSTDISPLAY"
416 |
417 | # Remove the suggestion
418 | POSTDISPLAY=
419 |
420 | # Call the original `accept-line` to handle syntax highlighting or
421 | # other potential custom behavior
422 | _zsh_autosuggest_invoke_original_widget "accept-line"
423 | }
424 |
425 | # Partially accept the suggestion
426 | _zsh_autosuggest_partial_accept() {
427 | local -i retval cursor_loc
428 |
429 | # Save the contents of the buffer so we can restore later if needed
430 | local original_buffer="$BUFFER"
431 |
432 | # Temporarily accept the suggestion.
433 | BUFFER="$BUFFER$POSTDISPLAY"
434 |
435 | # Original widget moves the cursor
436 | _zsh_autosuggest_invoke_original_widget $@
437 | retval=$?
438 |
439 | # Normalize cursor location across vi/emacs modes
440 | cursor_loc=$CURSOR
441 | if [[ "$KEYMAP" = "vicmd" ]]; then
442 | cursor_loc=$((cursor_loc + 1))
443 | fi
444 |
445 | # If we've moved past the end of the original buffer
446 | if (( $cursor_loc > $#original_buffer )); then
447 | # Set POSTDISPLAY to text right of the cursor
448 | POSTDISPLAY="${BUFFER[$(($cursor_loc + 1)),$#BUFFER]}"
449 |
450 | # Clip the buffer at the cursor
451 | BUFFER="${BUFFER[1,$cursor_loc]}"
452 | else
453 | # Restore the original buffer
454 | BUFFER="$original_buffer"
455 | fi
456 |
457 | return $retval
458 | }
459 |
460 | () {
461 | typeset -ga _ZSH_AUTOSUGGEST_BUILTIN_ACTIONS
462 |
463 | _ZSH_AUTOSUGGEST_BUILTIN_ACTIONS=(
464 | clear
465 | fetch
466 | suggest
467 | accept
468 | execute
469 | enable
470 | disable
471 | toggle
472 | )
473 |
474 | local action
475 | for action in $_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS modify partial_accept; do
476 | eval "_zsh_autosuggest_widget_$action() {
477 | local -i retval
478 |
479 | _zsh_autosuggest_highlight_reset
480 |
481 | _zsh_autosuggest_$action \$@
482 | retval=\$?
483 |
484 | _zsh_autosuggest_highlight_apply
485 |
486 | zle -R
487 |
488 | return \$retval
489 | }"
490 | done
491 |
492 | for action in $_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS; do
493 | zle -N autosuggest-$action _zsh_autosuggest_widget_$action
494 | done
495 | }
496 |
497 | #--------------------------------------------------------------------#
498 | # Completion Suggestion Strategy #
499 | #--------------------------------------------------------------------#
500 | # Fetches a suggestion from the completion engine
501 | #
502 |
503 | _zsh_autosuggest_capture_postcompletion() {
504 | # Always insert the first completion into the buffer
505 | compstate[insert]=1
506 |
507 | # Don't list completions
508 | unset 'compstate[list]'
509 | }
510 |
511 | _zsh_autosuggest_capture_completion_widget() {
512 | # Add a post-completion hook to be called after all completions have been
513 | # gathered. The hook can modify compstate to affect what is done with the
514 | # gathered completions.
515 | local -a +h comppostfuncs
516 | comppostfuncs=(_zsh_autosuggest_capture_postcompletion)
517 |
518 | # Only capture completions at the end of the buffer
519 | CURSOR=$#BUFFER
520 |
521 | # Run the original widget wrapping `.complete-word` so we don't
522 | # recursively try to fetch suggestions, since our pty is forked
523 | # after autosuggestions is initialized.
524 | zle -- ${(k)widgets[(r)completion:.complete-word:_main_complete]}
525 |
526 | if is-at-least 5.0.3; then
527 | # Don't do any cr/lf transformations. We need to do this immediately before
528 | # output because if we do it in setup, onlcr will be re-enabled when we enter
529 | # vared in the async code path. There is a bug in zpty module in older versions
530 | # where the tty is not properly attached to the pty slave, resulting in stty
531 | # getting stopped with a SIGTTOU. See zsh-workers thread 31660 and upstream
532 | # commit f75904a38
533 | stty -onlcr -ocrnl -F /dev/tty
534 | fi
535 |
536 | # The completion has been added, print the buffer as the suggestion
537 | echo -nE - $'\0'$BUFFER$'\0'
538 | }
539 |
540 | zle -N autosuggest-capture-completion _zsh_autosuggest_capture_completion_widget
541 |
542 | _zsh_autosuggest_capture_setup() {
543 | # There is a bug in zpty module in older zsh versions by which a
544 | # zpty that exits will kill all zpty processes that were forked
545 | # before it. Here we set up a zsh exit hook to SIGKILL the zpty
546 | # process immediately, before it has a chance to kill any other
547 | # zpty processes.
548 | if ! is-at-least 5.4; then
549 | zshexit() {
550 | # The zsh builtin `kill` fails sometimes in older versions
551 | # https://unix.stackexchange.com/a/477647/156673
552 | kill -KILL $$ 2>&- || command kill -KILL $$
553 |
554 | # Block for long enough for the signal to come through
555 | sleep 1
556 | }
557 | fi
558 |
559 | # Try to avoid any suggestions that wouldn't match the prefix
560 | zstyle ':completion:*' matcher-list ''
561 | zstyle ':completion:*' path-completion false
562 | zstyle ':completion:*' max-errors 0 not-numeric
563 |
564 | bindkey '^I' autosuggest-capture-completion
565 | }
566 |
567 | _zsh_autosuggest_capture_completion_sync() {
568 | _zsh_autosuggest_capture_setup
569 |
570 | zle autosuggest-capture-completion
571 | }
572 |
573 | _zsh_autosuggest_capture_completion_async() {
574 | _zsh_autosuggest_capture_setup
575 |
576 | zmodload zsh/parameter 2>/dev/null || return # For `$functions`
577 |
578 | # Make vared completion work as if for a normal command line
579 | # https://stackoverflow.com/a/7057118/154703
580 | autoload +X _complete
581 | functions[_original_complete]=$functions[_complete]
582 | function _complete() {
583 | unset 'compstate[vared]'
584 | _original_complete "$@"
585 | }
586 |
587 | # Open zle with buffer set so we can capture completions for it
588 | vared 1
589 | }
590 |
591 | _zsh_autosuggest_strategy_completion() {
592 | # Reset options to defaults and enable LOCAL_OPTIONS
593 | emulate -L zsh
594 |
595 | # Enable extended glob for completion ignore pattern
596 | setopt EXTENDED_GLOB
597 |
598 | typeset -g suggestion
599 | local line REPLY
600 |
601 | # Exit if we don't have completions
602 | whence compdef >/dev/null || return
603 |
604 | # Exit if we don't have zpty
605 | zmodload zsh/zpty 2>/dev/null || return
606 |
607 | # Exit if our search string matches the ignore pattern
608 | [[ -n "$ZSH_AUTOSUGGEST_COMPLETION_IGNORE" ]] && [[ "$1" == $~ZSH_AUTOSUGGEST_COMPLETION_IGNORE ]] && return
609 |
610 | # Zle will be inactive if we are in async mode
611 | if zle; then
612 | zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_completion_sync
613 | else
614 | zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_completion_async "\$1"
615 | zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME $'\t'
616 | fi
617 |
618 | {
619 | # The completion result is surrounded by null bytes, so read the
620 | # content between the first two null bytes.
621 | zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line '*'$'\0''*'$'\0'
622 |
623 | # Extract the suggestion from between the null bytes. On older
624 | # versions of zsh (older than 5.3), we sometimes get extra bytes after
625 | # the second null byte, so trim those off the end.
626 | # See http://www.zsh.org/mla/workers/2015/msg03290.html
627 | suggestion="${${(@0)line}[2]}"
628 | } always {
629 | # Destroy the pty
630 | zpty -d $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME
631 | }
632 | }
633 |
634 | #--------------------------------------------------------------------#
635 | # History Suggestion Strategy #
636 | #--------------------------------------------------------------------#
637 | # Suggests the most recent history item that matches the given
638 | # prefix.
639 | #
640 |
641 | _zsh_autosuggest_strategy_history() {
642 | # Reset options to defaults and enable LOCAL_OPTIONS
643 | emulate -L zsh
644 |
645 | # Enable globbing flags so that we can use (#m) and (x~y) glob operator
646 | setopt EXTENDED_GLOB
647 |
648 | # Escape backslashes and all of the glob operators so we can use
649 | # this string as a pattern to search the $history associative array.
650 | # - (#m) globbing flag enables setting references for match data
651 | # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8
652 | local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}"
653 |
654 | # Get the history items that match the prefix, excluding those that match
655 | # the ignore pattern
656 | local pattern="$prefix*"
657 | if [[ -n $ZSH_AUTOSUGGEST_HISTORY_IGNORE ]]; then
658 | pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)"
659 | fi
660 |
661 | # Give the first history item matching the pattern as the suggestion
662 | # - (r) subscript flag makes the pattern match on values
663 | typeset -g suggestion="${history[(r)$pattern]}"
664 | }
665 |
666 | #--------------------------------------------------------------------#
667 | # Match Previous Command Suggestion Strategy #
668 | #--------------------------------------------------------------------#
669 | # Suggests the most recent history item that matches the given
670 | # prefix and whose preceding history item also matches the most
671 | # recently executed command.
672 | #
673 | # For example, suppose your history has the following entries:
674 | # - pwd
675 | # - ls foo
676 | # - ls bar
677 | # - pwd
678 | #
679 | # Given the history list above, when you type 'ls', the suggestion
680 | # will be 'ls foo' rather than 'ls bar' because your most recently
681 | # executed command (pwd) was previously followed by 'ls foo'.
682 | #
683 | # Note that this strategy won't work as expected with ZSH options that don't
684 | # preserve the history order such as `HIST_IGNORE_ALL_DUPS` or
685 | # `HIST_EXPIRE_DUPS_FIRST`.
686 |
687 | _zsh_autosuggest_strategy_match_prev_cmd() {
688 | # Reset options to defaults and enable LOCAL_OPTIONS
689 | emulate -L zsh
690 |
691 | # Enable globbing flags so that we can use (#m) and (x~y) glob operator
692 | setopt EXTENDED_GLOB
693 |
694 | # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8
695 | local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}"
696 |
697 | # Get the history items that match the prefix, excluding those that match
698 | # the ignore pattern
699 | local pattern="$prefix*"
700 | if [[ -n $ZSH_AUTOSUGGEST_HISTORY_IGNORE ]]; then
701 | pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)"
702 | fi
703 |
704 | # Get all history event numbers that correspond to history
705 | # entries that match the pattern
706 | local history_match_keys
707 | history_match_keys=(${(k)history[(R)$~pattern]})
708 |
709 | # By default we use the first history number (most recent history entry)
710 | local histkey="${history_match_keys[1]}"
711 |
712 | # Get the previously executed command
713 | local prev_cmd="$(_zsh_autosuggest_escape_command "${history[$((HISTCMD-1))]}")"
714 |
715 | # Iterate up to the first 200 history event numbers that match $prefix
716 | for key in "${(@)history_match_keys[1,200]}"; do
717 | # Stop if we ran out of history
718 | [[ $key -gt 1 ]] || break
719 |
720 | # See if the history entry preceding the suggestion matches the
721 | # previous command, and use it if it does
722 | if [[ "${history[$((key - 1))]}" == "$prev_cmd" ]]; then
723 | histkey="$key"
724 | break
725 | fi
726 | done
727 |
728 | # Give back the matched history entry
729 | typeset -g suggestion="$history[$histkey]"
730 | }
731 |
732 | #--------------------------------------------------------------------#
733 | # Fetch Suggestion #
734 | #--------------------------------------------------------------------#
735 | # Loops through all specified strategies and returns a suggestion
736 | # from the first strategy to provide one.
737 | #
738 |
739 | _zsh_autosuggest_fetch_suggestion() {
740 | typeset -g suggestion
741 | local -a strategies
742 | local strategy
743 |
744 | # Ensure we are working with an array
745 | strategies=(${=ZSH_AUTOSUGGEST_STRATEGY})
746 |
747 | for strategy in $strategies; do
748 | # Try to get a suggestion from this strategy
749 | _zsh_autosuggest_strategy_$strategy "$1"
750 |
751 | # Ensure the suggestion matches the prefix
752 | [[ "$suggestion" != "$1"* ]] && unset suggestion
753 |
754 | # Break once we've found a valid suggestion
755 | [[ -n "$suggestion" ]] && break
756 | done
757 | }
758 |
759 | #--------------------------------------------------------------------#
760 | # Async #
761 | #--------------------------------------------------------------------#
762 |
763 | _zsh_autosuggest_async_request() {
764 | zmodload zsh/system 2>/dev/null # For `$sysparams`
765 |
766 | typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD _ZSH_AUTOSUGGEST_CHILD_PID
767 |
768 | # If we've got a pending request, cancel it
769 | if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then
770 | # Close the file descriptor and remove the handler
771 | builtin exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&-
772 | zle -F $_ZSH_AUTOSUGGEST_ASYNC_FD
773 |
774 | # We won't know the pid unless the user has zsh/system module installed
775 | if [[ -n "$_ZSH_AUTOSUGGEST_CHILD_PID" ]]; then
776 | # Zsh will make a new process group for the child process only if job
777 | # control is enabled (MONITOR option)
778 | if [[ -o MONITOR ]]; then
779 | # Send the signal to the process group to kill any processes that may
780 | # have been forked by the suggestion strategy
781 | kill -TERM -$_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null
782 | else
783 | # Kill just the child process since it wasn't placed in a new process
784 | # group. If the suggestion strategy forked any child processes they may
785 | # be orphaned and left behind.
786 | kill -TERM $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null
787 | fi
788 | fi
789 | fi
790 |
791 | # Fork a process to fetch a suggestion and open a pipe to read from it
792 | builtin exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <(
793 | # Tell parent process our pid
794 | echo $sysparams[pid]
795 |
796 | # Fetch and print the suggestion
797 | local suggestion
798 | _zsh_autosuggest_fetch_suggestion "$1"
799 | echo -nE "$suggestion"
800 | )
801 |
802 | # There's a weird bug here where ^C stops working unless we force a fork
803 | # See https://github.com/zsh-users/zsh-autosuggestions/issues/364
804 | autoload -Uz is-at-least
805 | is-at-least 5.8 || command true
806 |
807 | # Read the pid from the child process
808 | read _ZSH_AUTOSUGGEST_CHILD_PID <&$_ZSH_AUTOSUGGEST_ASYNC_FD
809 |
810 | # When the fd is readable, call the response handler
811 | zle -F "$_ZSH_AUTOSUGGEST_ASYNC_FD" _zsh_autosuggest_async_response
812 | }
813 |
814 | # Called when new data is ready to be read from the pipe
815 | # First arg will be fd ready for reading
816 | # Second arg will be passed in case of error
817 | _zsh_autosuggest_async_response() {
818 | emulate -L zsh
819 |
820 | local suggestion
821 |
822 | if [[ -z "$2" || "$2" == "hup" ]]; then
823 | # Read everything from the fd and give it as a suggestion
824 | IFS='' read -rd '' -u $1 suggestion
825 | zle autosuggest-suggest -- "$suggestion"
826 |
827 | # Close the fd
828 | builtin exec {1}<&-
829 | fi
830 |
831 | # Always remove the handler
832 | zle -F "$1"
833 | _ZSH_AUTOSUGGEST_ASYNC_FD=
834 | }
835 |
836 | #--------------------------------------------------------------------#
837 | # Start #
838 | #--------------------------------------------------------------------#
839 |
840 | # Start the autosuggestion widgets
841 | _zsh_autosuggest_start() {
842 | # By default we re-bind widgets on every precmd to ensure we wrap other
843 | # wrappers. Specifically, highlighting breaks if our widgets are wrapped by
844 | # zsh-syntax-highlighting widgets. This also allows modifications to the
845 | # widget list variables to take effect on the next precmd. However this has
846 | # a decent performance hit, so users can set ZSH_AUTOSUGGEST_MANUAL_REBIND
847 | # to disable the automatic re-binding.
848 | if (( ${+ZSH_AUTOSUGGEST_MANUAL_REBIND} )); then
849 | add-zsh-hook -d precmd _zsh_autosuggest_start
850 | fi
851 |
852 | _zsh_autosuggest_bind_widgets
853 | }
854 |
855 | # Mark for auto-loading the functions that we use
856 | autoload -Uz add-zsh-hook is-at-least
857 |
858 | # Automatically enable asynchronous mode in newer versions of zsh. Disable for
859 | # older versions because there is a bug when using async mode where ^C does not
860 | # work immediately after fetching a suggestion.
861 | # See https://github.com/zsh-users/zsh-autosuggestions/issues/364
862 | if is-at-least 5.0.8; then
863 | typeset -g ZSH_AUTOSUGGEST_USE_ASYNC=
864 | fi
865 |
866 | # Start the autosuggestion widgets on the next precmd
867 | add-zsh-hook precmd _zsh_autosuggest_start
868 |
--------------------------------------------------------------------------------