├── .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 | [![Chat on Gitter](https://img.shields.io/gitter/room/zsh-users/zsh-autosuggestions.svg)](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 | --------------------------------------------------------------------------------