# HG changeset patch # User Augie Fackler # Date 1543894649 18000 # Node ID e1ce8897030d712f7cb49f353f1fbddd4dab1ba3 # Parent f248cf012d9aea7c6b8bea5a437a7f3d03ed0c45 zsh: import df6f6f9ff41 of zsh-autosuggestions diff --git a/.shell.d/99.zsh-autosuggestions.zsh b/.shell.d/99.zsh-autosuggestions.zsh new file mode 100644 --- /dev/null +++ b/.shell.d/99.zsh-autosuggestions.zsh @@ -0,0 +1,2 @@ +source ~/.zfun/zsh-autosuggestions/zsh-autosuggestions.zsh +bindkey '^ ' autosuggest-accept diff --git a/.zfun/zsh-autosuggestions/.circleci/config.yml b/.zfun/zsh-autosuggestions/.circleci/config.yml new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/.circleci/config.yml @@ -0,0 +1,16 @@ +version: 2 +jobs: + build: + parallelism: 4 + shell: /bin/bash --login + docker: + - image: ericfreese/zsh-autosuggestions-test:latest + command: /sbin/init + steps: + - checkout + - run: + name: Running tests + command: | + for v in $(grep "^[^#]" ZSH_VERSIONS | awk "(NR + $CIRCLE_NODE_INDEX) % $CIRCLE_NODE_TOTAL == 0"); do + TEST_ZSH_BIN=zsh-$v make test || exit 1 + done diff --git a/.zfun/zsh-autosuggestions/.editorconfig b/.zfun/zsh-autosuggestions/.editorconfig new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/.editorconfig @@ -0,0 +1,18 @@ +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = tab +indent_size = 4 + +[*.md] +indent_style = space + +[*.rb] +indent_style = space +indent_size = 2 + +[*.yml] +indent_style = space +indent_size = 2 diff --git a/.zfun/zsh-autosuggestions/.rspec b/.zfun/zsh-autosuggestions/.rspec new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/.rspec @@ -0,0 +1,3 @@ +--color +--require spec_helper +--format documentation diff --git a/.zfun/zsh-autosuggestions/.rubocop.yml b/.zfun/zsh-autosuggestions/.rubocop.yml new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/.rubocop.yml @@ -0,0 +1,30 @@ +# Rails: +# Enabled: true + +AllCops: + TargetRubyVersion: 2.3 + Include: + - '**/Rakefile' + - '**/config.ru' + - '**/Gemfile' + +Metrics/LineLength: + Max: 120 + +Style/Documentation: + Enabled: false + +Style/DotPosition: + EnforcedStyle: trailing + +Style/FrozenStringLiteralComment: + Enabled: false + +Style/Lambda: + Enabled: false + +Style/MultilineMethodCallIndentation: + EnforcedStyle: indented + +Style/TrailingUnderscoreVariable: + Enabled: false diff --git a/.zfun/zsh-autosuggestions/.ruby-version b/.zfun/zsh-autosuggestions/.ruby-version new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/.ruby-version @@ -0,0 +1,1 @@ +2.5.3 diff --git a/.zfun/zsh-autosuggestions/CHANGELOG.md b/.zfun/zsh-autosuggestions/CHANGELOG.md new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/CHANGELOG.md @@ -0,0 +1,79 @@ +# Changelog + +## v0.5.0 +- Don't overwrite config with default values (#335) +- Support fallback strategies by supplying array to suggestion config var +- Rename "default" suggestion strategy to "history" to name it based on what it actually does +- Reset opts in some functions affected by `GLOB_SUBST` (#334) +- Support widgets starting with dashes (ex: `-a-widget`) (#337) +- Skip async tests in zsh versions less than 5.0.8 because of reliability issues +- Fix handling of newline + carriage return in async pty (#333) + + +## v0.4.3 +- Avoid bell when accepting suggestions with `autosuggest-accept` (#228) +- Don't fetch suggestions after [up,down]-line-or-beginning-search (#227, #241) +- We are now running CI against new 5.5.1 version +- Fix partial-accept in vi mode (#188) +- Fix suggestion disappearing on fast movement after switching to `vicmd` mode (#290) +- Fix issue rotating through kill ring with `yank-pop` (#301) +- Fix issue creating new pty for async mode when previous pty is not properly cleaned up (#249) + +## v0.4.2 +- Fix bug in zsh versions older than 5.0.8 (#296) +- Officially support back to zsh v4.3.11 + +## v0.4.1 +- Switch to [[ and (( conditionals instead of [ (#257) +- Avoid warnnestedvar warnings with `typeset -g` (#275) +- Replace tabs with spaces in yaml (#268) +- Clean up and fix escaping of special characters (#267) +- Add `emacs-forward-word` to default list of partial accept widgets (#246) + +## v0.4.0 +- High-level integration tests using RSpec and tmux +- Add continuous integration with Circle CI +- Experimental support for asynchronous suggestions (#170) +- Fix problems with multi-line suggestions (#225) +- Optimize case where manually typing in suggestion +- Avoid wrapping any zle-* widgets (#206) +- Remove support for deprecated options from v0.0.x +- Handle history entries that begin with dashes +- Gracefully handle being sourced multiple times (#126) +- Add enable/disable/toggle widgets to disable/enable suggestions (#219) + + +## v0.3.3 +- Switch from $history array to fc builtin for better performance with large HISTFILEs (#164) +- Fix tilde handling when extended_glob is set (#168) +- Add config option for maximum buffer length to fetch suggestions for (#178) +- Add config option for list of widgets to ignore (#184) +- Don't fetch a new suggestion unless a modification widget actually modifies the buffer (#183) + +## v0.3.2 +- Test runner now supports running specific tests and choosing zsh binary +- Return code from original widget is now correctly passed through (#135) +- Add `vi-add-eol` to list of accept widgets (#143) +- Escapes widget names within evals to fix problems with irregular widget names (#152) +- Plugin now clears suggestion while within a completion menu (#149) +- .plugin file no longer relies on symbolic link support, fixing issues on Windows (#156) + +## v0.3.1 + +- Fixes issue with `vi-next-char` not accepting suggestion (#137). +- Fixes global variable warning when WARN_CREATE_GLOBAL option enabled (#133). +- Split out a separate test file for each widget. + +## v0.3.0 + +- Adds `autosuggest-execute` widget (PR #124). +- Adds concept of suggestion "strategies" for different ways of fetching suggestions. +- Adds "match_prev_cmd" strategy (PR #131). +- Uses git submodules for testing dependencies. +- Lots of test cleanup. +- Various bug fixes for zsh 5.0.x and `sh_word_split` option. + + +## v0.2.17 + +Start of changelog. diff --git a/.zfun/zsh-autosuggestions/DESCRIPTION b/.zfun/zsh-autosuggestions/DESCRIPTION new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/DESCRIPTION @@ -0,0 +1,1 @@ +Fish-like fast/unobtrusive autosuggestions for zsh. diff --git a/.zfun/zsh-autosuggestions/Dockerfile b/.zfun/zsh-autosuggestions/Dockerfile new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/Dockerfile @@ -0,0 +1,20 @@ +FROM ruby:2.5.3-alpine + +RUN apk add --no-cache autoconf +RUN apk add --no-cache libtool +RUN apk add --no-cache libcap-dev +RUN apk add --no-cache pcre-dev +RUN apk add --no-cache curl +RUN apk add --no-cache build-base +RUN apk add --no-cache ncurses-dev +RUN apk add --no-cache tmux + +WORKDIR /zsh-autosuggestions + +ADD ZSH_VERSIONS /zsh-autosuggestions/ZSH_VERSIONS +ADD install_test_zsh.sh /zsh-autosuggestions/install_test_zsh.sh +RUN ./install_test_zsh.sh + +ADD Gemfile /zsh-autosuggestions/Gemfile +ADD Gemfile.lock /zsh-autosuggestions/Gemfile.lock +RUN bundle install diff --git a/.zfun/zsh-autosuggestions/Gemfile b/.zfun/zsh-autosuggestions/Gemfile new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/Gemfile @@ -0,0 +1,5 @@ +source 'https://rubygems.org' + +gem 'rspec' +gem 'rspec-wait' +gem 'pry-byebug' diff --git a/.zfun/zsh-autosuggestions/Gemfile.lock b/.zfun/zsh-autosuggestions/Gemfile.lock new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/Gemfile.lock @@ -0,0 +1,41 @@ +GEM + remote: https://rubygems.org/ + specs: + byebug (9.0.5) + coderay (1.1.1) + diff-lcs (1.3) + method_source (0.8.2) + pry (0.10.4) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) + pry-byebug (3.4.0) + byebug (~> 9.0) + pry (~> 0.10) + rspec (3.5.0) + rspec-core (~> 3.5.0) + rspec-expectations (~> 3.5.0) + rspec-mocks (~> 3.5.0) + rspec-core (3.5.4) + rspec-support (~> 3.5.0) + rspec-expectations (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-mocks (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-support (3.5.0) + rspec-wait (0.0.9) + rspec (>= 3, < 4) + slop (3.6.0) + +PLATFORMS + ruby + +DEPENDENCIES + pry-byebug + rspec + rspec-wait + +BUNDLED WITH + 1.13.6 diff --git a/.zfun/zsh-autosuggestions/INSTALL.md b/.zfun/zsh-autosuggestions/INSTALL.md new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/INSTALL.md @@ -0,0 +1,77 @@ +## Installation + +### Manual (Git Clone) + +1. Clone this repository somewhere on your machine. This guide will assume `~/.zsh/zsh-autosuggestions`. + + ```sh + git clone https://github.com/zsh-users/zsh-autosuggestions ~/.zsh/zsh-autosuggestions + ``` + +2. Add the following to your `.zshrc`: + + ```sh + source ~/.zsh/zsh-autosuggestions/zsh-autosuggestions.zsh + ``` + +3. Start a new terminal session. + +### Antigen + +1. Add the following to your `.zshrc`: + + ```sh + antigen bundle zsh-users/zsh-autosuggestions + ``` + +2. Start a new terminal session. + +### Oh My Zsh + +1. Clone this repository into `$ZSH_CUSTOM/plugins` (by default `~/.oh-my-zsh/custom/plugins`) + + ```sh + git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions + ``` + +2. Add the plugin to the list of plugins for Oh My Zsh to load (inside `~/.zshrc`): + + ```sh + plugins=(zsh-autosuggestions) + ``` + +3. Start a new terminal session. + +### Arch Linux + +1. Install [`zsh-autosuggestions`](https://www.archlinux.org/packages/community/any/zsh-autosuggestions/) from the `community` repository. + + ```sh + pacman -S zsh-autosuggestions + ``` + + or, to use a package based on the `master` branch, install [`zsh-autosuggestions-git`](https://aur.archlinux.org/packages/zsh-autosuggestions-git/) from the [AUR](https://wiki.archlinux.org/index.php/Arch_User_Repository). + +2. Add the following to your `.zshrc`: + + ```sh + source /usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh + ``` + +3. Start a new terminal session. + +### macOS via Homebrew +1. Install the `zsh-autosuggestions` package using [Homebrew](https://brew.sh/). + + ```sh + brew install zsh-autosuggestions + ``` + +2. Add the following to your `.zshrc`: + + ```sh + source /usr/local/share/zsh-autosuggestions/zsh-autosuggestions.zsh + ``` + +3. Start a new terminal session. + diff --git a/.zfun/zsh-autosuggestions/LICENSE b/.zfun/zsh-autosuggestions/LICENSE new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2013 Thiago de Arruda +Copyright (c) 2016-2018 Eric Freese + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/.zfun/zsh-autosuggestions/Makefile b/.zfun/zsh-autosuggestions/Makefile new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/Makefile @@ -0,0 +1,37 @@ +SRC_DIR := ./src + +SRC_FILES := \ + $(SRC_DIR)/setup.zsh \ + $(SRC_DIR)/config.zsh \ + $(SRC_DIR)/util.zsh \ + $(SRC_DIR)/features.zsh \ + $(SRC_DIR)/bind.zsh \ + $(SRC_DIR)/highlight.zsh \ + $(SRC_DIR)/widgets.zsh \ + $(SRC_DIR)/strategies/*.zsh \ + $(SRC_DIR)/fetch.zsh \ + $(SRC_DIR)/async.zsh \ + $(SRC_DIR)/start.zsh + +HEADER_FILES := \ + DESCRIPTION \ + URL \ + VERSION \ + LICENSE + +PLUGIN_TARGET := zsh-autosuggestions.zsh + +all: $(PLUGIN_TARGET) + +$(PLUGIN_TARGET): $(HEADER_FILES) $(SRC_FILES) + cat $(HEADER_FILES) | sed -e 's/^/# /g' > $@ + cat $(SRC_FILES) >> $@ + +.PHONY: clean +clean: + rm $(PLUGIN_TARGET) + +.PHONY: test +test: all + @test -n "$$TEST_ZSH_BIN" && echo "Testing zsh binary: $(TEST_ZSH_BIN)" || true + bundle exec rspec $(TESTS) diff --git a/.zfun/zsh-autosuggestions/README.md b/.zfun/zsh-autosuggestions/README.md new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/README.md @@ -0,0 +1,160 @@ +# zsh-autosuggestions + +_[Fish](http://fishshell.com/)-like fast/unobtrusive autosuggestions for zsh._ + +It suggests commands as you type, based on command history. + +Requirements: Zsh v4.3.11 or later + +[![CircleCI](https://circleci.com/gh/zsh-users/zsh-autosuggestions.svg?style=svg)](https://circleci.com/gh/zsh-users/zsh-autosuggestions) + + + + +## Installation + +See [INSTALL.md](INSTALL.md). + + +## Usage + +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). + +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. + +If you invoke the `forward-word` widget, it will partially accept the suggestion up to the point that the cursor moves to. + + +## Configuration + +You may want to override the default global config variables. Default values of these variables can be found [here](src/config.zsh). + +**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). + + +### Suggestion Highlight Style + +Set `ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE` to configure the style that the suggestion is shown with. The default is `fg=8`. + + +### Suggestion Strategy + +`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 two built-in strategies to choose from: + +- `history`: Chooses the most recent match from history. +- `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`. + + +### Widget Mapping + +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: + +- `ZSH_AUTOSUGGEST_CLEAR_WIDGETS`: Widgets in this array will clear the suggestion when invoked. +- `ZSH_AUTOSUGGEST_ACCEPT_WIDGETS`: Widgets in this array will accept the suggestion when invoked. +- `ZSH_AUTOSUGGEST_EXECUTE_WIDGETS`: Widgets in this array will execute the suggestion when invoked. +- `ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS`: Widgets in this array will partially accept the suggestion when invoked. +- `ZSH_AUTOSUGGEST_IGNORE_WIDGETS`: Widgets in this array will not trigger any custom behavior. + +Widgets that modify the buffer and are not found in any of these arrays will fetch a new suggestion after they are invoked. + +**Note:** A widget shouldn't belong to more than one of the above arrays. + + +### Disabling suggestion for large buffers + +Set `ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE` to an integer value to disable autosuggestion for large buffers. The default is unset, which means that autosuggestion will be tried for any buffer size. Recommended value is 20. +This can be useful when pasting large amount of text in the terminal, to avoid triggering autosuggestion for too long strings. + +### Enable Asynchronous Mode + +As of `v0.4.0`, suggestions can be fetched asynchronously using the `zsh/zpty` module. To enable this behavior, set the `ZSH_AUTOSUGGEST_USE_ASYNC` variable (it can be set to anything). + + +### Key Bindings + +This plugin provides a few widgets that you can use with `bindkey`: + +1. `autosuggest-accept`: Accepts the current suggestion. +2. `autosuggest-execute`: Accepts and executes the current suggestion. +3. `autosuggest-clear`: Clears the current suggestion. +4. `autosuggest-fetch`: Fetches a suggestion (works even when suggestions are disabled). +5. `autosuggest-disable`: Disables suggestions. +6. `autosuggest-enable`: Re-enables suggestions. +7. `autosuggest-toggle`: Toggles between enabled/disabled suggestions. + +For example, this would bind ctrl + space to accept the current suggestion. + +```sh +bindkey '^ ' autosuggest-accept +``` + + +## Troubleshooting + +If you have a problem, please search through [the list of issues on GitHub](https://github.com/zsh-users/zsh-autosuggestions/issues) to see if someone else has already reported it. + + +### Reporting an Issue + +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. + +When reporting an issue, please include: + +- 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. +- The version of zsh you're using (`zsh --version`) +- Which operating system you're running + + +## Uninstallation + +1. Remove the code referencing this plugin from `~/.zshrc`. + +2. Remove the git repository from your hard drive + + ```sh + rm -rf ~/.zsh/zsh-autosuggestions # Or wherever you installed + ``` + + +## Development + +### Build Process + +Edit the source files in `src/`. Run `make` to build `zsh-autosuggestions.zsh` from those source files. + + +### Pull Requests + +Pull requests are welcome! If you send a pull request, please: + +- Request to merge into the `develop` branch (*NOT* `master`) +- Match the existing coding conventions. +- Include helpful comments to keep the barrier-to-entry low for people new to the project. +- Write tests that cover your code as much as possible. + + +### Testing + +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. + +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`). + +A docker image for testing is available [on docker hub](https://hub.docker.com/r/ericfreese/zsh-autosuggestions-test). It comes with ruby, the bundler dependencies, and all supported versions of zsh installed. + +Pull the docker image with: + +```sh +docker pull ericfreese/zsh-autosuggestions-test +``` + +To run the tests for a specific version of zsh (where `` below is substituted with the contents of a line from the [`ZSH_VERSIONS`](ZSH_VERSIONS) file): + +```sh +docker run -it -e TEST_ZSH_BIN=zsh- -v $PWD:/zsh-autosuggestions zsh-autosuggestions-test make test +``` + + +## License + +This project is licensed under [MIT license](http://opensource.org/licenses/MIT). +For the full text of the license, see the [LICENSE](LICENSE) file. diff --git a/.zfun/zsh-autosuggestions/URL b/.zfun/zsh-autosuggestions/URL new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/URL @@ -0,0 +1,1 @@ +https://github.com/zsh-users/zsh-autosuggestions diff --git a/.zfun/zsh-autosuggestions/VERSION b/.zfun/zsh-autosuggestions/VERSION new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/VERSION @@ -0,0 +1,1 @@ +v0.5.0 diff --git a/.zfun/zsh-autosuggestions/ZSH_VERSIONS b/.zfun/zsh-autosuggestions/ZSH_VERSIONS new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/ZSH_VERSIONS @@ -0,0 +1,15 @@ +# Zsh releases to run tests against +# See https://github.com/zsh-users/zsh/releases +# +# When modifying this file, rebuild and push docker image: +# $ docker build -t ericfreese/zsh-autosuggestions-test . +# $ docker push ericfreese/zsh-autosuggestions-test +4.3.11 +5.0.2 +5.0.8 +5.1.1 +5.2 +5.3.1 +5.4.2 +5.5.1 +5.6.2 diff --git a/.zfun/zsh-autosuggestions/install_test_zsh.sh b/.zfun/zsh-autosuggestions/install_test_zsh.sh new file mode 100755 --- /dev/null +++ b/.zfun/zsh-autosuggestions/install_test_zsh.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +set -ex + +for v in $(grep "^[^#]" ZSH_VERSIONS); do + mkdir zsh-$v + cd zsh-$v + + curl -L https://api.github.com/repos/zsh-users/zsh/tarball/zsh-$v | tar xz --strip=1 + + ./Util/preconfig + ./configure --enable-pcre \ + --enable-cap \ + --enable-multibyte \ + --with-term-lib='ncursesw tinfo' \ + --without-tcsetpgrp \ + --program-suffix="-$v" + + make install.bin + make install.modules + make install.fns + + cd .. + + rm -rf zsh-$v +done diff --git a/.zfun/zsh-autosuggestions/spec/async_spec.rb b/.zfun/zsh-autosuggestions/spec/async_spec.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/async_spec.rb @@ -0,0 +1,84 @@ +context 'with asynchronous suggestions enabled' do + before do + skip 'Async mode not supported below v5.0.8' if session.zsh_version < Gem::Version.new('5.0.8') + end + + let(:options) { ["ZSH_AUTOSUGGEST_USE_ASYNC="] } + + describe '`up-line-or-beginning-search`' do + let(:before_sourcing) do + -> do + session. + run_command('autoload -U up-line-or-beginning-search'). + run_command('zle -N up-line-or-beginning-search'). + send_string('bindkey "'). + send_keys('C-v').send_keys('up'). + send_string('" up-line-or-beginning-search'). + send_keys('enter') + end + end + + it 'should show previous history entries' do + with_history( + 'echo foo', + 'echo bar', + 'echo baz' + ) do + session.clear_screen + 3.times { session.send_keys('up') } + wait_for { session.content }.to eq("echo foo") + end + end + end + + it 'should not add extra carriage returns before newlines' do + session. + send_string('echo "'). + send_keys('escape'). + send_keys('enter'). + send_string('"'). + send_keys('enter') + + session.clear_screen + + session.send_string('echo') + wait_for { session.content }.to eq("echo \"\n\"") + end + + it 'should treat carriage returns and newlines as separate characters' do + session. + send_string('echo "'). + send_keys('C-v'). + send_keys('enter'). + send_string('foo"'). + send_keys('enter') + + session. + send_string('echo "'). + send_keys('control'). + send_keys('enter'). + send_string('bar"'). + send_keys('enter') + + session.clear_screen + + session. + send_string('echo "'). + send_keys('C-v'). + send_keys('enter') + + wait_for { session.content }.to eq('echo "^Mfoo"') + end + + describe 'exiting a subshell' do + it 'should not cause error messages to be printed' do + session.run_command('$(exit)') + + sleep 1 + + expect(session.content).to eq('$(exit)') + end + end +end + + diff --git a/.zfun/zsh-autosuggestions/spec/integrations/bracketed_paste_magic_spec.rb b/.zfun/zsh-autosuggestions/spec/integrations/bracketed_paste_magic_spec.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/integrations/bracketed_paste_magic_spec.rb @@ -0,0 +1,27 @@ +describe 'pasting using bracketed-paste-magic' do + let(:before_sourcing) do + -> do + session. + run_command('autoload -Uz bracketed-paste-magic'). + run_command('zle -N bracketed-paste bracketed-paste-magic') + end + end + + context 'with suggestions disabled while pasting' do + before do + session. + run_command('bpm_init() { zle autosuggest-disable }'). + run_command('bpm_finish() { zle autosuggest-enable }'). + run_command('zstyle :bracketed-paste-magic paste-init bpm_init'). + run_command('zstyle :bracketed-paste-magic paste-finish bpm_finish') + end + + it 'does not show an incorrect suggestion' do + with_history('echo hello') do + session.paste_string("echo #{'a' * 60}") + sleep 1 + expect(session.content).to eq("echo #{'a' * 60}") + end + end + end +end diff --git a/.zfun/zsh-autosuggestions/spec/integrations/client_zpty_spec.rb b/.zfun/zsh-autosuggestions/spec/integrations/client_zpty_spec.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/integrations/client_zpty_spec.rb @@ -0,0 +1,10 @@ +describe 'a running zpty command' do + let(:before_sourcing) { -> { session.run_command('zmodload zsh/zpty && zpty -b kitty cat') } } + + it 'is not affected by running zsh-autosuggestions' do + sleep 1 # Give a little time for precmd hooks to run + session.run_command('zpty -t kitty; echo $?') + + wait_for { session.content }.to end_with("\n0") + end +end diff --git a/.zfun/zsh-autosuggestions/spec/integrations/rebound_bracket_spec.rb b/.zfun/zsh-autosuggestions/spec/integrations/rebound_bracket_spec.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/integrations/rebound_bracket_spec.rb @@ -0,0 +1,13 @@ +describe 'rebinding [' do + context 'initialized before sourcing the plugin' do + before do + session.run_command("function [ { $commands[\\[] \"$@\" }") + session.clear_screen + end + + it 'executes the custom behavior and the built-in behavior' do + session.send_string('asdf') + wait_for { session.content }.to eq('asdf') + end + end +end diff --git a/.zfun/zsh-autosuggestions/spec/integrations/vi_mode_spec.rb b/.zfun/zsh-autosuggestions/spec/integrations/vi_mode_spec.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/integrations/vi_mode_spec.rb @@ -0,0 +1,67 @@ +describe 'when using vi mode' do + let(:before_sourcing) do + -> do + session.run_command('bindkey -v') + end + end + + describe 'moving the cursor after exiting insert mode' do + it 'should not clear the current suggestion' do + with_history('foobar foo') do + session. + send_string('foo'). + send_keys('escape'). + send_keys('h') + + wait_for { session.content }.to eq('foobar foo') + end + end + end + + describe '`vi-forward-word-end`' do + it 'should accept through the end of the current word' do + with_history('foobar foo') do + session. + send_string('foo'). + send_keys('escape'). + send_keys('e'). # vi-forward-word-end + send_keys('a'). # vi-add-next + send_string('baz') + + wait_for { session.content }.to eq('foobarbaz') + end + end + end + + describe '`vi-forward-word`' do + it 'should accept through the first character of the next word' do + with_history('foobar foo') do + session. + send_string('foo'). + send_keys('escape'). + send_keys('w'). # vi-forward-word + send_keys('a'). # vi-add-next + send_string('az') + + wait_for { session.content }.to eq('foobar faz') + end + end + end + + describe '`vi-find-next-char`' do + it 'should accept through the next occurrence of the character' do + with_history('foobar foo') do + session. + send_string('foo'). + send_keys('escape'). + send_keys('f'). # vi-find-next-char + send_keys('o'). + send_keys('a'). # vi-add-next + send_string('b') + + wait_for { session.content }.to eq('foobar fob') + end + end + end +end + diff --git a/.zfun/zsh-autosuggestions/spec/integrations/wrapped_widget_spec.rb b/.zfun/zsh-autosuggestions/spec/integrations/wrapped_widget_spec.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/integrations/wrapped_widget_spec.rb @@ -0,0 +1,39 @@ +describe 'a wrapped widget' do + let(:widget) { 'backward-delete-char' } + + context 'initialized before sourcing the plugin' do + let(:before_sourcing) do + -> do + session. + run_command("_orig_#{widget}() { zle .#{widget} }"). + run_command("zle -N orig-#{widget} _orig_#{widget}"). + run_command("#{widget}-magic() { zle orig-#{widget}; BUFFER+=b }"). + run_command("zle -N #{widget} #{widget}-magic") + end + end + + it 'executes the custom behavior and the built-in behavior' do + with_history('foobar', 'foodar') do + session.send_string('food').send_keys('C-h') + wait_for { session.content }.to eq('foobar') + end + end + end + + context 'initialized after sourcing the plugin' do + before do + session. + run_command("zle -N orig-#{widget} ${widgets[#{widget}]#*:}"). + run_command("#{widget}-magic() { zle orig-#{widget}; BUFFER+=b }"). + run_command("zle -N #{widget} #{widget}-magic"). + clear_screen + end + + it 'executes the custom behavior and the built-in behavior' do + with_history('foobar', 'foodar') do + session.send_string('food').send_keys('C-h') + wait_for { session.content }.to eq('foobar') + end + end + end +end diff --git a/.zfun/zsh-autosuggestions/spec/integrations/zle_input_stack_spec.rb b/.zfun/zsh-autosuggestions/spec/integrations/zle_input_stack_spec.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/integrations/zle_input_stack_spec.rb @@ -0,0 +1,24 @@ +describe 'using `zle -U`' do + let(:before_sourcing) do + -> do + session. + run_command('_zsh_autosuggest_strategy_test() { sleep 1; _zsh_autosuggest_strategy_history "$1" }'). + run_command('foo() { zle -U - "echo hello" }; zle -N foo; bindkey ^B foo') + end + end + + let(:options) { ['unset ZSH_AUTOSUGGEST_USE_ASYNC', 'ZSH_AUTOSUGGEST_STRATEGY=test'] } + + # TODO: This is only possible with the $KEYS_QUEUED_COUNT widget parameter, coming soon... + xit 'does not fetch a suggestion for every inserted character' do + session.send_keys('C-b') + wait_for { session.content }.to eq('echo hello') + end + + it 'shows a suggestion when the widget completes' do + with_history('echo hello world') do + session.send_keys('C-b') + wait_for { session.content(esc_seqs: true) }.to match(/\Aecho hello\e\[[0-9]+m world/) + end + end +end diff --git a/.zfun/zsh-autosuggestions/spec/kill_ring_spec.rb b/.zfun/zsh-autosuggestions/spec/kill_ring_spec.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/kill_ring_spec.rb @@ -0,0 +1,23 @@ +context 'with some items in the kill ring' do + before do + session. + send_string('echo foo'). + send_keys('C-u'). + send_string('echo bar'). + send_keys('C-u') + end + + describe '`yank-pop`' do + it 'should cycle through all items in the kill ring' do + session.send_keys('C-y') + wait_for { session.content }.to eq('echo bar') + + session.send_keys('escape').send_keys('y') + wait_for { session.content }.to eq('echo foo') + + session.send_keys('escape').send_keys('y') + wait_for { session.content }.to eq('echo bar') + end + end +end + diff --git a/.zfun/zsh-autosuggestions/spec/multi_line_spec.rb b/.zfun/zsh-autosuggestions/spec/multi_line_spec.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/multi_line_spec.rb @@ -0,0 +1,13 @@ +describe 'a multi-line suggestion' do + it 'should be displayed on multiple lines' do + with_history(-> { + session.send_string('echo "') + session.send_keys('enter') + session.send_string('"') + session.send_keys('enter') + }) do + session.send_keys('e') + wait_for { session.content }.to eq("echo \"\n\"") + end + end +end diff --git a/.zfun/zsh-autosuggestions/spec/options/async_zpty_name_spec.rb b/.zfun/zsh-autosuggestions/spec/options/async_zpty_name_spec.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/options/async_zpty_name_spec.rb @@ -0,0 +1,19 @@ +context 'when async suggestions are enabled' do + let(:options) { ["ZSH_AUTOSUGGEST_USE_ASYNC="] } + + describe 'the zpty for async suggestions' do + it 'is created with the default name' do + session.run_command('zpty -t zsh_autosuggest_pty &>/dev/null; echo $?') + wait_for { session.content }.to end_with("\n0") + end + + context 'when ZSH_AUTOSUGGEST_ASYNC_PTY_NAME is set' do + let(:options) { super() + ['ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=foo_pty'] } + + it 'is created with the specified name' do + session.run_command('zpty -t foo_pty &>/dev/null; echo $?') + wait_for { session.content }.to end_with("\n0") + end + end + end +end diff --git a/.zfun/zsh-autosuggestions/spec/options/buffer_max_size_spec.rb b/.zfun/zsh-autosuggestions/spec/options/buffer_max_size_spec.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/options/buffer_max_size_spec.rb @@ -0,0 +1,30 @@ +describe 'a suggestion' do + let(:term_opts) { { width: 200 } } + let(:long_command) { "echo #{'a' * 100}" } + + around do |example| + with_history(long_command) { example.run } + end + + it 'is provided for any buffer length' do + session.send_string(long_command[0...-1]) + wait_for { session.content }.to eq(long_command) + end + + context 'when ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE is specified' do + let(:buffer_max_size) { 10 } + let(:options) { ["ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=#{buffer_max_size}"] } + + it 'is provided when the buffer is shorter than the specified length' do + session.send_string(long_command[0...(buffer_max_size - 1)]) + wait_for { session.content }.to eq(long_command) + end + + it 'is provided when the buffer is equal to the specified length' do + session.send_string(long_command[0...(buffer_max_size)]) + wait_for { session.content }.to eq(long_command) + end + + it 'is not provided when the buffer is longer than the specified length' + end +end diff --git a/.zfun/zsh-autosuggestions/spec/options/highlight_style_spec.rb b/.zfun/zsh-autosuggestions/spec/options/highlight_style_spec.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/options/highlight_style_spec.rb @@ -0,0 +1,7 @@ +describe 'a displayed suggestion' do + it 'is shown in the default style' + + describe 'when ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE is set to a zle_highlight string' do + it 'is shown in the specified style' + end +end diff --git a/.zfun/zsh-autosuggestions/spec/options/original_widget_prefix_spec.rb b/.zfun/zsh-autosuggestions/spec/options/original_widget_prefix_spec.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/options/original_widget_prefix_spec.rb @@ -0,0 +1,7 @@ +describe 'an original zle widget' do + context 'is accessible with the default prefix' + + context 'when ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX is set' do + it 'is accessible with the specified prefix' + end +end diff --git a/.zfun/zsh-autosuggestions/spec/options/strategy_spec.rb b/.zfun/zsh-autosuggestions/spec/options/strategy_spec.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/options/strategy_spec.rb @@ -0,0 +1,55 @@ +describe 'a suggestion for a given prefix' do + let(:history_strategy) { '_zsh_autosuggest_strategy_history() { suggestion="history" }' } + let(:foobar_strategy) { '_zsh_autosuggest_strategy_foobar() { [[ "foobar baz" = $1* ]] && suggestion="foobar baz" }' } + let(:foobaz_strategy) { '_zsh_autosuggest_strategy_foobaz() { [[ "foobaz bar" = $1* ]] && suggestion="foobaz bar" }' } + + let(:after_sourcing) do + -> do + session.run_command(history_strategy) + end + end + + it 'by default is determined by calling the `history` strategy function' do + session.send_string('h') + wait_for { session.content }.to eq('history') + end + + context 'when ZSH_AUTOSUGGEST_STRATEGY is set to an array' do + let(:after_sourcing) do + -> do + session. + run_command(foobar_strategy). + run_command(foobaz_strategy). + run_command('ZSH_AUTOSUGGEST_STRATEGY=(foobar foobaz)') + end + end + + it 'is determined by the first strategy function to return a suggestion' do + session.send_string('foo') + wait_for { session.content }.to eq('foobar baz') + + session.send_string('baz') + wait_for { session.content }.to eq('foobaz bar') + end + end + + context 'when ZSH_AUTOSUGGEST_STRATEGY is set to a string' do + let(:after_sourcing) do + -> do + session. + run_command(foobar_strategy). + run_command(foobaz_strategy). + run_command('ZSH_AUTOSUGGEST_STRATEGY="foobar foobaz"') + end + end + + it 'is determined by the first strategy function to return a suggestion' do + session.send_string('foo') + wait_for { session.content }.to eq('foobar baz') + + session.send_string('baz') + wait_for { session.content }.to eq('foobaz bar') + end + end +end + diff --git a/.zfun/zsh-autosuggestions/spec/options/use_async_spec.rb b/.zfun/zsh-autosuggestions/spec/options/use_async_spec.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/options/use_async_spec.rb @@ -0,0 +1,7 @@ +describe 'suggestion fetching' do + it 'is performed synchronously' + + context 'when ZSH_AUTOSUGGEST_USE_ASYNC is set' do + it 'is performed asynchronously' + end +end diff --git a/.zfun/zsh-autosuggestions/spec/options/widget_lists_spec.rb b/.zfun/zsh-autosuggestions/spec/options/widget_lists_spec.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/options/widget_lists_spec.rb @@ -0,0 +1,97 @@ +describe 'a zle widget' do + let(:widget) { 'my-widget' } + let(:before_sourcing) { -> { session.run_command("#{widget}() {}; zle -N #{widget}; bindkey ^B #{widget}") } } + + context 'when added to ZSH_AUTOSUGGEST_ACCEPT_WIDGETS' do + let(:options) { ["ZSH_AUTOSUGGEST_ACCEPT_WIDGETS+=(#{widget})"] } + + it 'accepts the suggestion when invoked' do + with_history('echo hello') do + session.send_string('e') + wait_for { session.content }.to eq('echo hello') + session.send_keys('C-b') + wait_for { session.content(esc_seqs: true) }.to eq('echo hello') + end + end + end + + context 'when added to ZSH_AUTOSUGGEST_CLEAR_WIDGETS' do + let(:options) { ["ZSH_AUTOSUGGEST_CLEAR_WIDGETS+=(#{widget})"] } + + it 'clears the suggestion when invoked' do + with_history('echo hello') do + session.send_string('e') + wait_for { session.content }.to eq('echo hello') + session.send_keys('C-b') + wait_for { session.content }.to eq('e') + end + end + end + + context 'when added to ZSH_AUTOSUGGEST_EXECUTE_WIDGETS' do + let(:options) { ["ZSH_AUTOSUGGEST_EXECUTE_WIDGETS+=(#{widget})"] } + + it 'executes the suggestion when invoked' do + with_history('echo hello') do + session.send_string('e') + wait_for { session.content }.to eq('echo hello') + session.send_keys('C-b') + wait_for { session.content }.to end_with("\nhello") + end + end + end + + context 'when added to ZSH_AUTOSUGGEST_IGNORE_WIDGETS' do + let(:options) { ["ZSH_AUTOSUGGEST_IGNORE_WIDGETS=(#{widget})"] } + + it 'should not be wrapped with an autosuggest widget' do + session.run_command("echo $widgets[#{widget}]") + wait_for { session.content }.to end_with("\nuser:#{widget}") + end + end + + context 'that moves the cursor forward' do + before { session.run_command("#{widget}() { zle forward-char }") } + + context 'when added to ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS' do + let(:options) { ["ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=(#{widget})"] } + + it 'accepts the suggestion as far as the cursor is moved when invoked' do + with_history('echo hello') do + session.send_string('e') + wait_for { session.content }.to start_with('echo hello') + session.send_keys('C-b') + wait_for { session.content(esc_seqs: true) }.to match(/\Aec\e\[[0-9]+mho hello/) + end + end + end + end + + context 'that modifies the buffer' do + before { session.run_command("#{widget}() { BUFFER=\"foo\" }") } + + context 'when not added to any of the widget lists' do + it 'modifies the buffer and fetches a new suggestion' do + with_history('foobar') do + session.send_keys('C-b') + wait_for { session.content }.to eq('foobar') + end + end + end + end +end + +describe 'a modification to the widget lists' do + let(:widget) { 'my-widget' } + let(:before_sourcing) { -> { session.run_command("#{widget}() {}; zle -N #{widget}; bindkey ^B #{widget}") } } + before { session.run_command("ZSH_AUTOSUGGEST_ACCEPT_WIDGETS+=(#{widget})") } + + it 'takes effect on the next cmd line' do + with_history('echo hello') do + session.send_string('e') + wait_for { session.content }.to eq('echo hello') + session.send_keys('C-b') + wait_for { session.content(esc_seqs: true) }.to eq('echo hello') + end + end +end diff --git a/.zfun/zsh-autosuggestions/spec/spec_helper.rb b/.zfun/zsh-autosuggestions/spec/spec_helper.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/spec_helper.rb @@ -0,0 +1,52 @@ +require 'pry' +require 'rspec/wait' +require 'terminal_session' + +RSpec.shared_context 'terminal session' do + let(:term_opts) { {} } + let(:session) { TerminalSession.new(term_opts) } + let(:before_sourcing) { -> {} } + let(:after_sourcing) { -> {} } + let(:options) { [] } + + around do |example| + before_sourcing.call + session.run_command(options.join('; ')) + session.run_command('source zsh-autosuggestions.zsh') + after_sourcing.call + session.clear_screen + + example.run + + session.destroy + end + + def with_history(*commands, &block) + session.run_command('fc -p') + + commands.each do |c| + c.respond_to?(:call) ? c.call : session.run_command(c) + end + + session.clear_screen + + yield block + + session.send_keys('C-c') + session.run_command('fc -P') + end +end + +RSpec.configure do |config| + config.expect_with :rspec do |expectations| + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + config.mock_with :rspec do |mocks| + mocks.verify_partial_doubles = true + end + + config.wait_timeout = 2 + + config.include_context 'terminal session' +end diff --git a/.zfun/zsh-autosuggestions/spec/strategies/default_spec.rb b/.zfun/zsh-autosuggestions/spec/strategies/default_spec.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/strategies/default_spec.rb @@ -0,0 +1,12 @@ +require 'strategies/special_characters_helper' + +describe 'the default suggestion strategy' do + it 'suggests the last matching history entry' do + with_history('ls foo', 'ls bar', 'echo baz') do + session.send_string('ls') + wait_for { session.content }.to eq('ls bar') + end + end + + include_examples 'special characters' +end diff --git a/.zfun/zsh-autosuggestions/spec/strategies/match_prev_cmd_spec.rb b/.zfun/zsh-autosuggestions/spec/strategies/match_prev_cmd_spec.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/strategies/match_prev_cmd_spec.rb @@ -0,0 +1,21 @@ +require 'strategies/special_characters_helper' + +describe 'the match_prev_cmd strategy' do + let(:options) { ['ZSH_AUTOSUGGEST_STRATEGY=match_prev_cmd'] } + + it 'suggests the last matching history entry after the previous command' do + with_history( + 'echo what', + 'ls foo', + 'echo what', + 'ls bar', + 'ls baz', + 'echo what' + ) do + session.send_string('ls') + wait_for { session.content }.to eq('ls bar') + end + end + + include_examples 'special characters' +end diff --git a/.zfun/zsh-autosuggestions/spec/strategies/special_characters_helper.rb b/.zfun/zsh-autosuggestions/spec/strategies/special_characters_helper.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/strategies/special_characters_helper.rb @@ -0,0 +1,62 @@ +shared_examples 'special characters' do + describe 'a special character in the buffer' do + it 'should be treated like any other character' do + with_history('echo "hello*"', 'echo "hello."') do + session.send_string('echo "hello*') + wait_for { session.content }.to eq('echo "hello*"') + end + + with_history('echo "hello?"', 'echo "hello."') do + session.send_string('echo "hello?') + wait_for { session.content }.to eq('echo "hello?"') + end + + with_history('echo "hello\nworld"') do + session.send_string('echo "hello\\') + wait_for { session.content }.to eq('echo "hello\nworld"') + end + + with_history('echo "\\\\"') do + session.send_string('echo "\\\\') + wait_for { session.content }.to eq('echo "\\\\"') + end + + with_history('echo ~/foo') do + session.send_string('echo ~') + wait_for { session.content }.to eq('echo ~/foo') + end + + with_history('echo "$(ls foo)"') do + session.send_string('echo "$(') + wait_for { session.content }.to eq('echo "$(ls foo)"') + end + + with_history('echo "$history[123]"') do + session.send_string('echo "$history[') + wait_for { session.content }.to eq('echo "$history[123]"') + session.send_string('123]') + wait_for { session.content }.to eq('echo "$history[123]"') + end + + with_history('echo "#yolo"') do + session.send_string('echo "#') + wait_for { session.content }.to eq('echo "#yolo"') + end + + with_history('echo "#foo"', 'echo $#abc') do + session.send_string('echo "#') + wait_for { session.content }.to eq('echo "#foo"') + end + + with_history('echo "^A"', 'echo "^B"') do + session.send_string('echo "^A') + wait_for { session.content }.to eq('echo "^A"') + end + + with_history('-foo() {}') do + session.send_string('-') + wait_for { session.content }.to eq('-foo() {}') + end + end + end +end diff --git a/.zfun/zsh-autosuggestions/spec/terminal_session.rb b/.zfun/zsh-autosuggestions/spec/terminal_session.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/terminal_session.rb @@ -0,0 +1,99 @@ +require 'securerandom' + +class TerminalSession + ZSH_BIN = ENV['TEST_ZSH_BIN'] || 'zsh' + + def initialize(opts = {}) + opts = { + width: 80, + height: 24, + prompt: '', + term: 'xterm-256color', + zsh_bin: ZSH_BIN + }.merge(opts) + + @opts = opts + + cmd="PS1=\"#{opts[:prompt]}\" TERM=#{opts[:term]} #{ZSH_BIN} -f" + tmux_command("new-session -d -x #{opts[:width]} -y #{opts[:height]} '#{cmd}'") + end + + def zsh_version + @zsh_version ||= Gem::Version.new(`#{ZSH_BIN} -c 'echo -n $ZSH_VERSION'`) + end + + def tmux_socket_name + @tmux_socket_name ||= SecureRandom.hex(6) + end + + def run_command(command) + send_string(command) + send_keys('enter') + + self + end + + def send_string(str) + tmux_command("send-keys -t 0 -l -- '#{str.gsub("'", "\\'")}'") + + self + end + + def send_keys(*keys) + tmux_command("send-keys -t 0 #{keys.join(' ')}") + + self + end + + def paste_string(str) + tmux_command("set-buffer -- '#{str}'") + tmux_command("paste-buffer -dpr -t 0") + + self + end + + def content(esc_seqs: false) + cmd = 'capture-pane -p -t 0' + cmd += ' -e' if esc_seqs + tmux_command(cmd).strip + end + + def clear_screen + send_keys('C-l') + + i = 0 + until content == opts[:prompt] || i > 20 do + sleep(0.1) + i = i + 1 + end + + self + end + + def destroy + tmux_command('kill-session') + end + + def cursor + tmux_command("display-message -t 0 -p '\#{cursor_x},\#{cursor_y}'"). + strip. + split(','). + map(&:to_i) + end + + def attach! + tmux_command('attach-session') + end + + private + + attr_reader :opts + + def tmux_command(cmd) + out = `tmux -u -L #{tmux_socket_name} #{cmd}` + + raise("tmux error running: '#{cmd}'") unless $?.success? + + out + end +end diff --git a/.zfun/zsh-autosuggestions/spec/widgets/disable_spec.rb b/.zfun/zsh-autosuggestions/spec/widgets/disable_spec.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/widgets/disable_spec.rb @@ -0,0 +1,19 @@ +describe 'the `autosuggest-disable` widget' do + before do + session.run_command('bindkey ^B autosuggest-disable') + end + + it 'disables suggestions and clears the suggestion' do + with_history('echo hello') do + session.send_string('echo') + wait_for { session.content }.to eq('echo hello') + + session.send_keys('C-b') + wait_for { session.content }.to eq('echo') + + session.send_string(' h') + sleep 1 + expect(session.content).to eq('echo h') + end + end +end diff --git a/.zfun/zsh-autosuggestions/spec/widgets/enable_spec.rb b/.zfun/zsh-autosuggestions/spec/widgets/enable_spec.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/widgets/enable_spec.rb @@ -0,0 +1,42 @@ +describe 'the `autosuggest-enable` widget' do + before do + session. + run_command('typeset -g _ZSH_AUTOSUGGEST_DISABLED'). + run_command('bindkey ^B autosuggest-enable') + end + + it 'enables suggestions and fetches a suggestion' do + with_history('echo hello') do + session.send_string('e') + sleep 1 + expect(session.content).to eq('e') + + session.send_keys('C-b') + session.send_string('c') + wait_for { session.content }.to eq('echo hello') + end + end + + context 'invoked on an empty buffer' do + it 'does not fetch a suggestion' do + with_history('echo hello') do + session.send_keys('C-b') + sleep 1 + expect(session.content).to eq('') + end + end + end + + context 'invoked on a non-empty buffer' do + it 'fetches a suggestion' do + with_history('echo hello') do + session.send_string('e') + sleep 1 + expect(session.content).to eq('e') + + session.send_keys('C-b') + wait_for { session.content }.to eq('echo hello') + end + end + end +end diff --git a/.zfun/zsh-autosuggestions/spec/widgets/fetch_spec.rb b/.zfun/zsh-autosuggestions/spec/widgets/fetch_spec.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/widgets/fetch_spec.rb @@ -0,0 +1,24 @@ +describe 'the `autosuggest-fetch` widget' do + context 'when suggestions are disabled' do + before do + session. + run_command('bindkey ^B autosuggest-disable'). + run_command('bindkey ^F autosuggest-fetch'). + send_keys('C-b') + end + + it 'will fetch and display a suggestion' do + with_history('echo hello') do + session.send_string('echo h') + sleep 1 + expect(session.content).to eq('echo h') + + session.send_keys('C-f') + wait_for { session.content }.to eq('echo hello') + + session.send_string('e') + wait_for { session.content }.to eq('echo hello') + end + end + end +end diff --git a/.zfun/zsh-autosuggestions/spec/widgets/toggle_spec.rb b/.zfun/zsh-autosuggestions/spec/widgets/toggle_spec.rb new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/spec/widgets/toggle_spec.rb @@ -0,0 +1,26 @@ +describe 'the `autosuggest-toggle` widget' do + before do + session.run_command('bindkey ^B autosuggest-toggle') + end + + it 'toggles suggestions' do + with_history('echo world', 'echo hello') do + session.send_string('echo') + wait_for { session.content }.to eq('echo hello') + + session.send_keys('C-b') + wait_for { session.content }.to eq('echo') + + session.send_string(' h') + sleep 1 + expect(session.content).to eq('echo h') + + session.send_keys('C-b') + wait_for { session.content }.to eq('echo hello') + + session.send_keys('C-h') + session.send_string('w') + wait_for { session.content }.to eq('echo world') + end + end +end diff --git a/.zfun/zsh-autosuggestions/src/async.zsh b/.zfun/zsh-autosuggestions/src/async.zsh new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/src/async.zsh @@ -0,0 +1,109 @@ + +#--------------------------------------------------------------------# +# Async # +#--------------------------------------------------------------------# + +# Zpty process is spawned running this function +_zsh_autosuggest_async_server() { + emulate -R zsh + + # There is a bug in zpty module (fixed in zsh/master) by which a + # zpty that exits will kill all zpty processes that were forked + # before it. Here we set up a zsh exit hook to SIGKILL the zpty + # process immediately, before it has a chance to kill any other + # zpty processes. + zshexit() { + kill -KILL $$ + sleep 1 # Block for long enough for the signal to come through + } + + # Don't add any extra carriage returns + stty -onlcr + + # Don't translate carriage returns to newlines + stty -icrnl + + # Silence any error messages + exec 2>/dev/null + + local last_pid + + while IFS='' read -r -d $'\0' query; do + # Kill last bg process + kill -KILL $last_pid &>/dev/null + + # Run suggestion search in the background + ( + local suggestion + _zsh_autosuggest_fetch_suggestion "$query" + echo -n -E "$suggestion"$'\0' + ) & + + last_pid=$! + done +} + +_zsh_autosuggest_async_request() { + # Write the query to the zpty process to fetch a suggestion + zpty -w -n $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME "${1}"$'\0' +} + +# Called when new data is ready to be read from the pty +# First arg will be fd ready for reading +# Second arg will be passed in case of error +_zsh_autosuggest_async_response() { + setopt LOCAL_OPTIONS EXTENDED_GLOB + + local suggestion + + zpty -rt $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME suggestion '*'$'\0' 2>/dev/null + zle autosuggest-suggest -- "${suggestion%%$'\0'##}" +} + +_zsh_autosuggest_async_pty_create() { + # With newer versions of zsh, REPLY stores the fd to read from + typeset -h REPLY + + # If we won't get a fd back from zpty, try to guess it + if (( ! $_ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD )); then + integer -l zptyfd + exec {zptyfd}>&1 # Open a new file descriptor (above 10). + exec {zptyfd}>&- # Close it so it's free to be used by zpty. + fi + + # Fork a zpty process running the server function + zpty -b $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME _zsh_autosuggest_async_server + + # Store the fd so we can remove the handler later + if (( REPLY )); then + _ZSH_AUTOSUGGEST_PTY_FD=$REPLY + else + _ZSH_AUTOSUGGEST_PTY_FD=$zptyfd + fi + + # Set up input handler from the zpty + zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_response +} + +_zsh_autosuggest_async_pty_destroy() { + # Remove the input handler + zle -F $_ZSH_AUTOSUGGEST_PTY_FD &>/dev/null + + # Destroy the zpty + zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null +} + +_zsh_autosuggest_async_pty_recreate() { + _zsh_autosuggest_async_pty_destroy + _zsh_autosuggest_async_pty_create +} + +_zsh_autosuggest_async_start() { + typeset -g _ZSH_AUTOSUGGEST_PTY_FD + + _zsh_autosuggest_feature_detect_zpty_returns_fd + _zsh_autosuggest_async_pty_recreate + + # We recreate the pty to get a fresh list of history events + add-zsh-hook precmd _zsh_autosuggest_async_pty_recreate +} diff --git a/.zfun/zsh-autosuggestions/src/bind.zsh b/.zfun/zsh-autosuggestions/src/bind.zsh new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/src/bind.zsh @@ -0,0 +1,120 @@ + +#--------------------------------------------------------------------# +# Widget Helpers # +#--------------------------------------------------------------------# + +_zsh_autosuggest_incr_bind_count() { + if ((${+_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]})); then + ((_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]++)) + else + _ZSH_AUTOSUGGEST_BIND_COUNTS[$1]=1 + fi + + typeset -gi bind_count=$_ZSH_AUTOSUGGEST_BIND_COUNTS[$1] +} + +_zsh_autosuggest_get_bind_count() { + if ((${+_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]})); then + typeset -gi bind_count=$_ZSH_AUTOSUGGEST_BIND_COUNTS[$1] + else + typeset -gi bind_count=0 + fi +} + +# Bind a single widget to an autosuggest widget, saving a reference to the original widget +_zsh_autosuggest_bind_widget() { + typeset -gA _ZSH_AUTOSUGGEST_BIND_COUNTS + + local widget=$1 + local autosuggest_action=$2 + local prefix=$ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX + + local -i bind_count + + # Save a reference to the original widget + case $widgets[$widget] in + # Already bound + user:_zsh_autosuggest_(bound|orig)_*);; + + # User-defined widget + user:*) + _zsh_autosuggest_incr_bind_count $widget + zle -N $prefix${bind_count}-$widget ${widgets[$widget]#*:} + ;; + + # Built-in widget + builtin) + _zsh_autosuggest_incr_bind_count $widget + eval "_zsh_autosuggest_orig_${(q)widget}() { zle .${(q)widget} }" + zle -N $prefix${bind_count}-$widget _zsh_autosuggest_orig_$widget + ;; + + # Completion widget + completion:*) + _zsh_autosuggest_incr_bind_count $widget + eval "zle -C $prefix${bind_count}-${(q)widget} ${${(s.:.)widgets[$widget]}[2,3]}" + ;; + esac + + _zsh_autosuggest_get_bind_count $widget + + # Pass the original widget's name explicitly into the autosuggest + # function. Use this passed in widget name to call the original + # widget instead of relying on the $WIDGET variable being set + # correctly. $WIDGET cannot be trusted because other plugins call + # zle without the `-w` flag (e.g. `zle self-insert` instead of + # `zle self-insert -w`). + eval "_zsh_autosuggest_bound_${bind_count}_${(q)widget}() { + _zsh_autosuggest_widget_$autosuggest_action $prefix$bind_count-${(q)widget} \$@ + }" + + # Create the bound widget + zle -N -- $widget _zsh_autosuggest_bound_${bind_count}_$widget +} + +# Map all configured widgets to the right autosuggest widgets +_zsh_autosuggest_bind_widgets() { + emulate -L zsh + + local widget + local ignore_widgets + + ignore_widgets=( + .\* + _\* + zle-\* + autosuggest-\* + $ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX\* + $ZSH_AUTOSUGGEST_IGNORE_WIDGETS + ) + + # Find every widget we might want to bind and bind it appropriately + for widget in ${${(f)"$(builtin zle -la)"}:#${(j:|:)~ignore_widgets}}; do + if [[ -n ${ZSH_AUTOSUGGEST_CLEAR_WIDGETS[(r)$widget]} ]]; then + _zsh_autosuggest_bind_widget $widget clear + elif [[ -n ${ZSH_AUTOSUGGEST_ACCEPT_WIDGETS[(r)$widget]} ]]; then + _zsh_autosuggest_bind_widget $widget accept + elif [[ -n ${ZSH_AUTOSUGGEST_EXECUTE_WIDGETS[(r)$widget]} ]]; then + _zsh_autosuggest_bind_widget $widget execute + elif [[ -n ${ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS[(r)$widget]} ]]; then + _zsh_autosuggest_bind_widget $widget partial_accept + else + # Assume any unspecified widget might modify the buffer + _zsh_autosuggest_bind_widget $widget modify + fi + done +} + +# Given the name of an original widget and args, invoke it, if it exists +_zsh_autosuggest_invoke_original_widget() { + # Do nothing unless called with at least one arg + (( $# )) || return 0 + + local original_widget_name="$1" + + shift + + if (( ${+widgets[$original_widget_name]} )); then + zle $original_widget_name -- $@ + fi +} diff --git a/.zfun/zsh-autosuggestions/src/config.zsh b/.zfun/zsh-autosuggestions/src/config.zsh new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/src/config.zsh @@ -0,0 +1,73 @@ + +#--------------------------------------------------------------------# +# Global Configuration Variables # +#--------------------------------------------------------------------# + +# Color to use when highlighting suggestion +# Uses format of `region_highlight` +# More info: http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Zle-Widgets +: ${ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8'} + +# Prefix to use when saving original versions of bound widgets +: ${ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig-} + +# Strategies to use to fetch a suggestion +# Will try each strategy in order until a suggestion is returned +(( ! ${+ZSH_AUTOSUGGEST_STRATEGY} )) && ZSH_AUTOSUGGEST_STRATEGY=(history) + +# Widgets that clear the suggestion +(( ! ${+ZSH_AUTOSUGGEST_CLEAR_WIDGETS} )) && ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( + history-search-forward + history-search-backward + history-beginning-search-forward + history-beginning-search-backward + history-substring-search-up + history-substring-search-down + up-line-or-beginning-search + down-line-or-beginning-search + up-line-or-history + down-line-or-history + accept-line +) + +# Widgets that accept the entire suggestion +(( ! ${+ZSH_AUTOSUGGEST_ACCEPT_WIDGETS} )) && ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=( + forward-char + end-of-line + vi-forward-char + vi-end-of-line + vi-add-eol +) + +# Widgets that accept the entire suggestion and execute it +(( ! ${+ZSH_AUTOSUGGEST_EXECUTE_WIDGETS} )) && ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=( +) + +# Widgets that accept the suggestion as far as the cursor moves +(( ! ${+ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS} )) && ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=( + forward-word + emacs-forward-word + vi-forward-word + vi-forward-word-end + vi-forward-blank-word + vi-forward-blank-word-end + vi-find-next-char + vi-find-next-char-skip +) + +# Widgets that should be ignored (globbing supported but must be escaped) +(( ! ${+ZSH_AUTOSUGGEST_IGNORE_WIDGETS} )) && ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( + orig-\* + beep + run-help + set-local-history + which-command + yank + yank-pop +) + +# Max size of buffer to trigger autosuggestion. Leave null for no upper bound. +: ${ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=} + +# Pty name for calculating autosuggestions asynchronously +: ${ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_pty} diff --git a/.zfun/zsh-autosuggestions/src/features.zsh b/.zfun/zsh-autosuggestions/src/features.zsh new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/src/features.zsh @@ -0,0 +1,19 @@ + +#--------------------------------------------------------------------# +# Feature Detection # +#--------------------------------------------------------------------# + +_zsh_autosuggest_feature_detect_zpty_returns_fd() { + typeset -g _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD + typeset -h REPLY + + zpty zsh_autosuggest_feature_detect '{ zshexit() { kill -KILL $$; sleep 1 } }' + + if (( REPLY )); then + _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=1 + else + _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=0 + fi + + zpty -d zsh_autosuggest_feature_detect +} diff --git a/.zfun/zsh-autosuggestions/src/fetch.zsh b/.zfun/zsh-autosuggestions/src/fetch.zsh new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/src/fetch.zsh @@ -0,0 +1,23 @@ + +#--------------------------------------------------------------------# +# Fetch Suggestion # +#--------------------------------------------------------------------# +# Loops through all specified strategies and returns a suggestion +# from the first strategy to provide one. +# + +_zsh_autosuggest_fetch_suggestion() { + typeset -g suggestion + local -a strategies + + # Ensure we are working with an array + strategies=(${=ZSH_AUTOSUGGEST_STRATEGY}) + + for strategy in $strategies; do + # Try to get a suggestion from this strategy + _zsh_autosuggest_strategy_$strategy "$1" + + # Break once we've found a suggestion + [[ -n "$suggestion" ]] && break + done +} diff --git a/.zfun/zsh-autosuggestions/src/highlight.zsh b/.zfun/zsh-autosuggestions/src/highlight.zsh new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/src/highlight.zsh @@ -0,0 +1,26 @@ + +#--------------------------------------------------------------------# +# Highlighting # +#--------------------------------------------------------------------# + +# If there was a highlight, remove it +_zsh_autosuggest_highlight_reset() { + typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT + + if [[ -n "$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT" ]]; then + region_highlight=("${(@)region_highlight:#$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT}") + unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT + fi +} + +# If there's a suggestion, highlight it +_zsh_autosuggest_highlight_apply() { + typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT + + if (( $#POSTDISPLAY )); then + typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT="$#BUFFER $(($#BUFFER + $#POSTDISPLAY)) $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE" + region_highlight+=("$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT") + else + unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT + fi +} diff --git a/.zfun/zsh-autosuggestions/src/setup.zsh b/.zfun/zsh-autosuggestions/src/setup.zsh new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/src/setup.zsh @@ -0,0 +1,10 @@ + +#--------------------------------------------------------------------# +# Setup # +#--------------------------------------------------------------------# + +# Precmd hooks for initializing the library and starting pty's +autoload -Uz add-zsh-hook + +# Asynchronous suggestions are generated in a pty +zmodload zsh/zpty diff --git a/.zfun/zsh-autosuggestions/src/start.zsh b/.zfun/zsh-autosuggestions/src/start.zsh new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/src/start.zsh @@ -0,0 +1,24 @@ + +#--------------------------------------------------------------------# +# Start # +#--------------------------------------------------------------------# + +# Start the autosuggestion widgets +_zsh_autosuggest_start() { + add-zsh-hook -d precmd _zsh_autosuggest_start + + _zsh_autosuggest_bind_widgets + + # Re-bind widgets on every precmd to ensure we wrap other wrappers. + # Specifically, highlighting breaks if our widgets are wrapped by + # zsh-syntax-highlighting widgets. This also allows modifications + # to the widget list variables to take effect on the next precmd. + add-zsh-hook precmd _zsh_autosuggest_bind_widgets + + if [[ -n "${ZSH_AUTOSUGGEST_USE_ASYNC+x}" ]]; then + _zsh_autosuggest_async_start + fi +} + +# Start the autosuggestion widgets on the next precmd +add-zsh-hook precmd _zsh_autosuggest_start diff --git a/.zfun/zsh-autosuggestions/src/strategies/default.zsh b/.zfun/zsh-autosuggestions/src/strategies/default.zsh new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/src/strategies/default.zsh @@ -0,0 +1,25 @@ + +#--------------------------------------------------------------------# +# History Suggestion Strategy # +#--------------------------------------------------------------------# +# Suggests the most recent history item that matches the given +# prefix. +# + +_zsh_autosuggest_strategy_history() { + # Reset options to defaults and enable LOCAL_OPTIONS + emulate -L zsh + + # Enable globbing flags so that we can use (#m) + setopt EXTENDED_GLOB + + # Escape backslashes and all of the glob operators so we can use + # this string as a pattern to search the $history associative array. + # - (#m) globbing flag enables setting references for match data + # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8 + local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" + + # Get the history items that match + # - (r) subscript flag makes the pattern match on values + typeset -g suggestion="${history[(r)${prefix}*]}" +} diff --git a/.zfun/zsh-autosuggestions/src/strategies/match_prev_cmd.zsh b/.zfun/zsh-autosuggestions/src/strategies/match_prev_cmd.zsh new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/src/strategies/match_prev_cmd.zsh @@ -0,0 +1,59 @@ + +#--------------------------------------------------------------------# +# Match Previous Command Suggestion Strategy # +#--------------------------------------------------------------------# +# Suggests the most recent history item that matches the given +# prefix and whose preceding history item also matches the most +# recently executed command. +# +# For example, suppose your history has the following entries: +# - pwd +# - ls foo +# - ls bar +# - pwd +# +# Given the history list above, when you type 'ls', the suggestion +# will be 'ls foo' rather than 'ls bar' because your most recently +# executed command (pwd) was previously followed by 'ls foo'. +# +# Note that this strategy won't work as expected with ZSH options that don't +# preserve the history order such as `HIST_IGNORE_ALL_DUPS` or +# `HIST_EXPIRE_DUPS_FIRST`. + +_zsh_autosuggest_strategy_match_prev_cmd() { + # Reset options to defaults and enable LOCAL_OPTIONS + emulate -L zsh + + # Enable globbing flags so that we can use (#m) + setopt EXTENDED_GLOB + + # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8 + local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" + + # Get all history event numbers that correspond to history + # entries that match pattern $prefix* + local history_match_keys + history_match_keys=(${(k)history[(R)$prefix*]}) + + # By default we use the first history number (most recent history entry) + local histkey="${history_match_keys[1]}" + + # Get the previously executed command + local prev_cmd="$(_zsh_autosuggest_escape_command "${history[$((HISTCMD-1))]}")" + + # Iterate up to the first 200 history event numbers that match $prefix + for key in "${(@)history_match_keys[1,200]}"; do + # Stop if we ran out of history + [[ $key -gt 1 ]] || break + + # See if the history entry preceding the suggestion matches the + # previous command, and use it if it does + if [[ "${history[$((key - 1))]}" == "$prev_cmd" ]]; then + histkey="$key" + break + fi + done + + # Give back the matched history entry + typeset -g suggestion="$history[$histkey]" +} diff --git a/.zfun/zsh-autosuggestions/src/util.zsh b/.zfun/zsh-autosuggestions/src/util.zsh new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/src/util.zsh @@ -0,0 +1,11 @@ + +#--------------------------------------------------------------------# +# Utility Functions # +#--------------------------------------------------------------------# + +_zsh_autosuggest_escape_command() { + setopt localoptions EXTENDED_GLOB + + # Escape special chars in the string (requires EXTENDED_GLOB) + echo -E "${1//(#m)[\"\'\\()\[\]|*?~]/\\$MATCH}" +} diff --git a/.zfun/zsh-autosuggestions/src/widgets.zsh b/.zfun/zsh-autosuggestions/src/widgets.zsh new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/src/widgets.zsh @@ -0,0 +1,217 @@ + +#--------------------------------------------------------------------# +# Autosuggest Widget Implementations # +#--------------------------------------------------------------------# + +# Disable suggestions +_zsh_autosuggest_disable() { + typeset -g _ZSH_AUTOSUGGEST_DISABLED + _zsh_autosuggest_clear +} + +# Enable suggestions +_zsh_autosuggest_enable() { + unset _ZSH_AUTOSUGGEST_DISABLED + + if (( $#BUFFER )); then + _zsh_autosuggest_fetch + fi +} + +# Toggle suggestions (enable/disable) +_zsh_autosuggest_toggle() { + if [[ -n "${_ZSH_AUTOSUGGEST_DISABLED+x}" ]]; then + _zsh_autosuggest_enable + else + _zsh_autosuggest_disable + fi +} + +# Clear the suggestion +_zsh_autosuggest_clear() { + # Remove the suggestion + unset POSTDISPLAY + + _zsh_autosuggest_invoke_original_widget $@ +} + +# Modify the buffer and get a new suggestion +_zsh_autosuggest_modify() { + emulate -L zsh + + local -i retval + + # Only available in zsh >= 5.4 + local -i KEYS_QUEUED_COUNT + + # Save the contents of the buffer/postdisplay + local orig_buffer="$BUFFER" + local orig_postdisplay="$POSTDISPLAY" + + # Clear suggestion while waiting for next one + unset POSTDISPLAY + + # Original widget may modify the buffer + _zsh_autosuggest_invoke_original_widget $@ + retval=$? + + # Don't fetch a new suggestion if there's more input to be read immediately + if (( $PENDING > 0 )) || (( $KEYS_QUEUED_COUNT > 0 )); then + POSTDISPLAY="$orig_postdisplay" + return $retval + fi + + # Optimize if manually typing in the suggestion + if (( $#BUFFER > $#orig_buffer )); then + local added=${BUFFER#$orig_buffer} + + # If the string added matches the beginning of the postdisplay + if [[ "$added" = "${orig_postdisplay:0:$#added}" ]]; then + POSTDISPLAY="${orig_postdisplay:$#added}" + return $retval + fi + fi + + # Don't fetch a new suggestion if the buffer hasn't changed + if [[ "$BUFFER" = "$orig_buffer" ]]; then + POSTDISPLAY="$orig_postdisplay" + return $retval + fi + + # Bail out if suggestions are disabled + if [[ -n "${_ZSH_AUTOSUGGEST_DISABLED+x}" ]]; then + return $? + fi + + # Get a new suggestion if the buffer is not empty after modification + if (( $#BUFFER > 0 )); then + if [[ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]] || (( $#BUFFER <= $ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE )); then + _zsh_autosuggest_fetch + fi + fi + + return $retval +} + +# Fetch a new suggestion based on what's currently in the buffer +_zsh_autosuggest_fetch() { + if zpty -t "$ZSH_AUTOSUGGEST_ASYNC_PTY_NAME" &>/dev/null; then + _zsh_autosuggest_async_request "$BUFFER" + else + local suggestion + _zsh_autosuggest_fetch_suggestion "$BUFFER" + _zsh_autosuggest_suggest "$suggestion" + fi +} + +# Offer a suggestion +_zsh_autosuggest_suggest() { + emulate -L zsh + + local suggestion="$1" + + if [[ -n "$suggestion" ]] && (( $#BUFFER )); then + POSTDISPLAY="${suggestion#$BUFFER}" + else + unset POSTDISPLAY + fi +} + +# Accept the entire suggestion +_zsh_autosuggest_accept() { + local -i max_cursor_pos=$#BUFFER + + # When vicmd keymap is active, the cursor can't move all the way + # to the end of the buffer + if [[ "$KEYMAP" = "vicmd" ]]; then + max_cursor_pos=$((max_cursor_pos - 1)) + fi + + # Only accept if the cursor is at the end of the buffer + if [[ $CURSOR = $max_cursor_pos ]]; then + # Add the suggestion to the buffer + BUFFER="$BUFFER$POSTDISPLAY" + + # Remove the suggestion + unset POSTDISPLAY + + # Move the cursor to the end of the buffer + CURSOR=${#BUFFER} + fi + + _zsh_autosuggest_invoke_original_widget $@ +} + +# Accept the entire suggestion and execute it +_zsh_autosuggest_execute() { + # Add the suggestion to the buffer + BUFFER="$BUFFER$POSTDISPLAY" + + # Remove the suggestion + unset POSTDISPLAY + + # Call the original `accept-line` to handle syntax highlighting or + # other potential custom behavior + _zsh_autosuggest_invoke_original_widget "accept-line" +} + +# Partially accept the suggestion +_zsh_autosuggest_partial_accept() { + local -i retval cursor_loc + + # Save the contents of the buffer so we can restore later if needed + local original_buffer="$BUFFER" + + # Temporarily accept the suggestion. + BUFFER="$BUFFER$POSTDISPLAY" + + # Original widget moves the cursor + _zsh_autosuggest_invoke_original_widget $@ + retval=$? + + # Normalize cursor location across vi/emacs modes + cursor_loc=$CURSOR + if [[ "$KEYMAP" = "vicmd" ]]; then + cursor_loc=$((cursor_loc + 1)) + fi + + # If we've moved past the end of the original buffer + if (( $cursor_loc > $#original_buffer )); then + # Set POSTDISPLAY to text right of the cursor + POSTDISPLAY="${BUFFER[$(($cursor_loc + 1)),$#BUFFER]}" + + # Clip the buffer at the cursor + BUFFER="${BUFFER[1,$cursor_loc]}" + else + # Restore the original buffer + BUFFER="$original_buffer" + fi + + return $retval +} + +for action in clear modify fetch suggest accept partial_accept execute enable disable toggle; do + eval "_zsh_autosuggest_widget_$action() { + local -i retval + + _zsh_autosuggest_highlight_reset + + _zsh_autosuggest_$action \$@ + retval=\$? + + _zsh_autosuggest_highlight_apply + + zle -R + + return \$retval + }" +done + +zle -N autosuggest-fetch _zsh_autosuggest_widget_fetch +zle -N autosuggest-suggest _zsh_autosuggest_widget_suggest +zle -N autosuggest-accept _zsh_autosuggest_widget_accept +zle -N autosuggest-clear _zsh_autosuggest_widget_clear +zle -N autosuggest-execute _zsh_autosuggest_widget_execute +zle -N autosuggest-enable _zsh_autosuggest_widget_enable +zle -N autosuggest-disable _zsh_autosuggest_widget_disable +zle -N autosuggest-toggle _zsh_autosuggest_widget_toggle diff --git a/.zfun/zsh-autosuggestions/zsh-autosuggestions.plugin.zsh b/.zfun/zsh-autosuggestions/zsh-autosuggestions.plugin.zsh new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/zsh-autosuggestions.plugin.zsh @@ -0,0 +1,1 @@ +source ${0:A:h}/zsh-autosuggestions.zsh diff --git a/.zfun/zsh-autosuggestions/zsh-autosuggestions.zsh b/.zfun/zsh-autosuggestions/zsh-autosuggestions.zsh new file mode 100644 --- /dev/null +++ b/.zfun/zsh-autosuggestions/zsh-autosuggestions.zsh @@ -0,0 +1,742 @@ +# Fish-like fast/unobtrusive autosuggestions for zsh. +# https://github.com/zsh-users/zsh-autosuggestions +# v0.5.0 +# Copyright (c) 2013 Thiago de Arruda +# Copyright (c) 2016-2018 Eric Freese +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +#--------------------------------------------------------------------# +# Setup # +#--------------------------------------------------------------------# + +# Precmd hooks for initializing the library and starting pty's +autoload -Uz add-zsh-hook + +# Asynchronous suggestions are generated in a pty +zmodload zsh/zpty + +#--------------------------------------------------------------------# +# Global Configuration Variables # +#--------------------------------------------------------------------# + +# Color to use when highlighting suggestion +# Uses format of `region_highlight` +# More info: http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Zle-Widgets +: ${ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8'} + +# Prefix to use when saving original versions of bound widgets +: ${ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig-} + +# Strategies to use to fetch a suggestion +# Will try each strategy in order until a suggestion is returned +(( ! ${+ZSH_AUTOSUGGEST_STRATEGY} )) && ZSH_AUTOSUGGEST_STRATEGY=(history) + +# Widgets that clear the suggestion +(( ! ${+ZSH_AUTOSUGGEST_CLEAR_WIDGETS} )) && ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( + history-search-forward + history-search-backward + history-beginning-search-forward + history-beginning-search-backward + history-substring-search-up + history-substring-search-down + up-line-or-beginning-search + down-line-or-beginning-search + up-line-or-history + down-line-or-history + accept-line +) + +# Widgets that accept the entire suggestion +(( ! ${+ZSH_AUTOSUGGEST_ACCEPT_WIDGETS} )) && ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=( + forward-char + end-of-line + vi-forward-char + vi-end-of-line + vi-add-eol +) + +# Widgets that accept the entire suggestion and execute it +(( ! ${+ZSH_AUTOSUGGEST_EXECUTE_WIDGETS} )) && ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=( +) + +# Widgets that accept the suggestion as far as the cursor moves +(( ! ${+ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS} )) && ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=( + forward-word + emacs-forward-word + vi-forward-word + vi-forward-word-end + vi-forward-blank-word + vi-forward-blank-word-end + vi-find-next-char + vi-find-next-char-skip +) + +# Widgets that should be ignored (globbing supported but must be escaped) +(( ! ${+ZSH_AUTOSUGGEST_IGNORE_WIDGETS} )) && ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( + orig-\* + beep + run-help + set-local-history + which-command + yank + yank-pop +) + +# Max size of buffer to trigger autosuggestion. Leave null for no upper bound. +: ${ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=} + +# Pty name for calculating autosuggestions asynchronously +: ${ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_pty} + +#--------------------------------------------------------------------# +# Utility Functions # +#--------------------------------------------------------------------# + +_zsh_autosuggest_escape_command() { + setopt localoptions EXTENDED_GLOB + + # Escape special chars in the string (requires EXTENDED_GLOB) + echo -E "${1//(#m)[\"\'\\()\[\]|*?~]/\\$MATCH}" +} + +#--------------------------------------------------------------------# +# Feature Detection # +#--------------------------------------------------------------------# + +_zsh_autosuggest_feature_detect_zpty_returns_fd() { + typeset -g _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD + typeset -h REPLY + + zpty zsh_autosuggest_feature_detect '{ zshexit() { kill -KILL $$; sleep 1 } }' + + if (( REPLY )); then + _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=1 + else + _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=0 + fi + + zpty -d zsh_autosuggest_feature_detect +} + +#--------------------------------------------------------------------# +# Widget Helpers # +#--------------------------------------------------------------------# + +_zsh_autosuggest_incr_bind_count() { + if ((${+_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]})); then + ((_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]++)) + else + _ZSH_AUTOSUGGEST_BIND_COUNTS[$1]=1 + fi + + typeset -gi bind_count=$_ZSH_AUTOSUGGEST_BIND_COUNTS[$1] +} + +_zsh_autosuggest_get_bind_count() { + if ((${+_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]})); then + typeset -gi bind_count=$_ZSH_AUTOSUGGEST_BIND_COUNTS[$1] + else + typeset -gi bind_count=0 + fi +} + +# Bind a single widget to an autosuggest widget, saving a reference to the original widget +_zsh_autosuggest_bind_widget() { + typeset -gA _ZSH_AUTOSUGGEST_BIND_COUNTS + + local widget=$1 + local autosuggest_action=$2 + local prefix=$ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX + + local -i bind_count + + # Save a reference to the original widget + case $widgets[$widget] in + # Already bound + user:_zsh_autosuggest_(bound|orig)_*);; + + # User-defined widget + user:*) + _zsh_autosuggest_incr_bind_count $widget + zle -N $prefix${bind_count}-$widget ${widgets[$widget]#*:} + ;; + + # Built-in widget + builtin) + _zsh_autosuggest_incr_bind_count $widget + eval "_zsh_autosuggest_orig_${(q)widget}() { zle .${(q)widget} }" + zle -N $prefix${bind_count}-$widget _zsh_autosuggest_orig_$widget + ;; + + # Completion widget + completion:*) + _zsh_autosuggest_incr_bind_count $widget + eval "zle -C $prefix${bind_count}-${(q)widget} ${${(s.:.)widgets[$widget]}[2,3]}" + ;; + esac + + _zsh_autosuggest_get_bind_count $widget + + # Pass the original widget's name explicitly into the autosuggest + # function. Use this passed in widget name to call the original + # widget instead of relying on the $WIDGET variable being set + # correctly. $WIDGET cannot be trusted because other plugins call + # zle without the `-w` flag (e.g. `zle self-insert` instead of + # `zle self-insert -w`). + eval "_zsh_autosuggest_bound_${bind_count}_${(q)widget}() { + _zsh_autosuggest_widget_$autosuggest_action $prefix$bind_count-${(q)widget} \$@ + }" + + # Create the bound widget + zle -N -- $widget _zsh_autosuggest_bound_${bind_count}_$widget +} + +# Map all configured widgets to the right autosuggest widgets +_zsh_autosuggest_bind_widgets() { + emulate -L zsh + + local widget + local ignore_widgets + + ignore_widgets=( + .\* + _\* + zle-\* + autosuggest-\* + $ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX\* + $ZSH_AUTOSUGGEST_IGNORE_WIDGETS + ) + + # Find every widget we might want to bind and bind it appropriately + for widget in ${${(f)"$(builtin zle -la)"}:#${(j:|:)~ignore_widgets}}; do + if [[ -n ${ZSH_AUTOSUGGEST_CLEAR_WIDGETS[(r)$widget]} ]]; then + _zsh_autosuggest_bind_widget $widget clear + elif [[ -n ${ZSH_AUTOSUGGEST_ACCEPT_WIDGETS[(r)$widget]} ]]; then + _zsh_autosuggest_bind_widget $widget accept + elif [[ -n ${ZSH_AUTOSUGGEST_EXECUTE_WIDGETS[(r)$widget]} ]]; then + _zsh_autosuggest_bind_widget $widget execute + elif [[ -n ${ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS[(r)$widget]} ]]; then + _zsh_autosuggest_bind_widget $widget partial_accept + else + # Assume any unspecified widget might modify the buffer + _zsh_autosuggest_bind_widget $widget modify + fi + done +} + +# Given the name of an original widget and args, invoke it, if it exists +_zsh_autosuggest_invoke_original_widget() { + # Do nothing unless called with at least one arg + (( $# )) || return 0 + + local original_widget_name="$1" + + shift + + if (( ${+widgets[$original_widget_name]} )); then + zle $original_widget_name -- $@ + fi +} + +#--------------------------------------------------------------------# +# Highlighting # +#--------------------------------------------------------------------# + +# If there was a highlight, remove it +_zsh_autosuggest_highlight_reset() { + typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT + + if [[ -n "$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT" ]]; then + region_highlight=("${(@)region_highlight:#$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT}") + unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT + fi +} + +# If there's a suggestion, highlight it +_zsh_autosuggest_highlight_apply() { + typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT + + if (( $#POSTDISPLAY )); then + typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT="$#BUFFER $(($#BUFFER + $#POSTDISPLAY)) $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE" + region_highlight+=("$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT") + else + unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT + fi +} + +#--------------------------------------------------------------------# +# Autosuggest Widget Implementations # +#--------------------------------------------------------------------# + +# Disable suggestions +_zsh_autosuggest_disable() { + typeset -g _ZSH_AUTOSUGGEST_DISABLED + _zsh_autosuggest_clear +} + +# Enable suggestions +_zsh_autosuggest_enable() { + unset _ZSH_AUTOSUGGEST_DISABLED + + if (( $#BUFFER )); then + _zsh_autosuggest_fetch + fi +} + +# Toggle suggestions (enable/disable) +_zsh_autosuggest_toggle() { + if [[ -n "${_ZSH_AUTOSUGGEST_DISABLED+x}" ]]; then + _zsh_autosuggest_enable + else + _zsh_autosuggest_disable + fi +} + +# Clear the suggestion +_zsh_autosuggest_clear() { + # Remove the suggestion + unset POSTDISPLAY + + _zsh_autosuggest_invoke_original_widget $@ +} + +# Modify the buffer and get a new suggestion +_zsh_autosuggest_modify() { + emulate -L zsh + + local -i retval + + # Only available in zsh >= 5.4 + local -i KEYS_QUEUED_COUNT + + # Save the contents of the buffer/postdisplay + local orig_buffer="$BUFFER" + local orig_postdisplay="$POSTDISPLAY" + + # Clear suggestion while waiting for next one + unset POSTDISPLAY + + # Original widget may modify the buffer + _zsh_autosuggest_invoke_original_widget $@ + retval=$? + + # Don't fetch a new suggestion if there's more input to be read immediately + if (( $PENDING > 0 )) || (( $KEYS_QUEUED_COUNT > 0 )); then + POSTDISPLAY="$orig_postdisplay" + return $retval + fi + + # Optimize if manually typing in the suggestion + if (( $#BUFFER > $#orig_buffer )); then + local added=${BUFFER#$orig_buffer} + + # If the string added matches the beginning of the postdisplay + if [[ "$added" = "${orig_postdisplay:0:$#added}" ]]; then + POSTDISPLAY="${orig_postdisplay:$#added}" + return $retval + fi + fi + + # Don't fetch a new suggestion if the buffer hasn't changed + if [[ "$BUFFER" = "$orig_buffer" ]]; then + POSTDISPLAY="$orig_postdisplay" + return $retval + fi + + # Bail out if suggestions are disabled + if [[ -n "${_ZSH_AUTOSUGGEST_DISABLED+x}" ]]; then + return $? + fi + + # Get a new suggestion if the buffer is not empty after modification + if (( $#BUFFER > 0 )); then + if [[ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]] || (( $#BUFFER <= $ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE )); then + _zsh_autosuggest_fetch + fi + fi + + return $retval +} + +# Fetch a new suggestion based on what's currently in the buffer +_zsh_autosuggest_fetch() { + if zpty -t "$ZSH_AUTOSUGGEST_ASYNC_PTY_NAME" &>/dev/null; then + _zsh_autosuggest_async_request "$BUFFER" + else + local suggestion + _zsh_autosuggest_fetch_suggestion "$BUFFER" + _zsh_autosuggest_suggest "$suggestion" + fi +} + +# Offer a suggestion +_zsh_autosuggest_suggest() { + emulate -L zsh + + local suggestion="$1" + + if [[ -n "$suggestion" ]] && (( $#BUFFER )); then + POSTDISPLAY="${suggestion#$BUFFER}" + else + unset POSTDISPLAY + fi +} + +# Accept the entire suggestion +_zsh_autosuggest_accept() { + local -i max_cursor_pos=$#BUFFER + + # When vicmd keymap is active, the cursor can't move all the way + # to the end of the buffer + if [[ "$KEYMAP" = "vicmd" ]]; then + max_cursor_pos=$((max_cursor_pos - 1)) + fi + + # Only accept if the cursor is at the end of the buffer + if [[ $CURSOR = $max_cursor_pos ]]; then + # Add the suggestion to the buffer + BUFFER="$BUFFER$POSTDISPLAY" + + # Remove the suggestion + unset POSTDISPLAY + + # Move the cursor to the end of the buffer + CURSOR=${#BUFFER} + fi + + _zsh_autosuggest_invoke_original_widget $@ +} + +# Accept the entire suggestion and execute it +_zsh_autosuggest_execute() { + # Add the suggestion to the buffer + BUFFER="$BUFFER$POSTDISPLAY" + + # Remove the suggestion + unset POSTDISPLAY + + # Call the original `accept-line` to handle syntax highlighting or + # other potential custom behavior + _zsh_autosuggest_invoke_original_widget "accept-line" +} + +# Partially accept the suggestion +_zsh_autosuggest_partial_accept() { + local -i retval cursor_loc + + # Save the contents of the buffer so we can restore later if needed + local original_buffer="$BUFFER" + + # Temporarily accept the suggestion. + BUFFER="$BUFFER$POSTDISPLAY" + + # Original widget moves the cursor + _zsh_autosuggest_invoke_original_widget $@ + retval=$? + + # Normalize cursor location across vi/emacs modes + cursor_loc=$CURSOR + if [[ "$KEYMAP" = "vicmd" ]]; then + cursor_loc=$((cursor_loc + 1)) + fi + + # If we've moved past the end of the original buffer + if (( $cursor_loc > $#original_buffer )); then + # Set POSTDISPLAY to text right of the cursor + POSTDISPLAY="${BUFFER[$(($cursor_loc + 1)),$#BUFFER]}" + + # Clip the buffer at the cursor + BUFFER="${BUFFER[1,$cursor_loc]}" + else + # Restore the original buffer + BUFFER="$original_buffer" + fi + + return $retval +} + +for action in clear modify fetch suggest accept partial_accept execute enable disable toggle; do + eval "_zsh_autosuggest_widget_$action() { + local -i retval + + _zsh_autosuggest_highlight_reset + + _zsh_autosuggest_$action \$@ + retval=\$? + + _zsh_autosuggest_highlight_apply + + zle -R + + return \$retval + }" +done + +zle -N autosuggest-fetch _zsh_autosuggest_widget_fetch +zle -N autosuggest-suggest _zsh_autosuggest_widget_suggest +zle -N autosuggest-accept _zsh_autosuggest_widget_accept +zle -N autosuggest-clear _zsh_autosuggest_widget_clear +zle -N autosuggest-execute _zsh_autosuggest_widget_execute +zle -N autosuggest-enable _zsh_autosuggest_widget_enable +zle -N autosuggest-disable _zsh_autosuggest_widget_disable +zle -N autosuggest-toggle _zsh_autosuggest_widget_toggle + +#--------------------------------------------------------------------# +# History Suggestion Strategy # +#--------------------------------------------------------------------# +# Suggests the most recent history item that matches the given +# prefix. +# + +_zsh_autosuggest_strategy_history() { + # Reset options to defaults and enable LOCAL_OPTIONS + emulate -L zsh + + # Enable globbing flags so that we can use (#m) + setopt EXTENDED_GLOB + + # Escape backslashes and all of the glob operators so we can use + # this string as a pattern to search the $history associative array. + # - (#m) globbing flag enables setting references for match data + # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8 + local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" + + # Get the history items that match + # - (r) subscript flag makes the pattern match on values + typeset -g suggestion="${history[(r)${prefix}*]}" +} + +#--------------------------------------------------------------------# +# Match Previous Command Suggestion Strategy # +#--------------------------------------------------------------------# +# Suggests the most recent history item that matches the given +# prefix and whose preceding history item also matches the most +# recently executed command. +# +# For example, suppose your history has the following entries: +# - pwd +# - ls foo +# - ls bar +# - pwd +# +# Given the history list above, when you type 'ls', the suggestion +# will be 'ls foo' rather than 'ls bar' because your most recently +# executed command (pwd) was previously followed by 'ls foo'. +# +# Note that this strategy won't work as expected with ZSH options that don't +# preserve the history order such as `HIST_IGNORE_ALL_DUPS` or +# `HIST_EXPIRE_DUPS_FIRST`. + +_zsh_autosuggest_strategy_match_prev_cmd() { + # Reset options to defaults and enable LOCAL_OPTIONS + emulate -L zsh + + # Enable globbing flags so that we can use (#m) + setopt EXTENDED_GLOB + + # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8 + local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" + + # Get all history event numbers that correspond to history + # entries that match pattern $prefix* + local history_match_keys + history_match_keys=(${(k)history[(R)$prefix*]}) + + # By default we use the first history number (most recent history entry) + local histkey="${history_match_keys[1]}" + + # Get the previously executed command + local prev_cmd="$(_zsh_autosuggest_escape_command "${history[$((HISTCMD-1))]}")" + + # Iterate up to the first 200 history event numbers that match $prefix + for key in "${(@)history_match_keys[1,200]}"; do + # Stop if we ran out of history + [[ $key -gt 1 ]] || break + + # See if the history entry preceding the suggestion matches the + # previous command, and use it if it does + if [[ "${history[$((key - 1))]}" == "$prev_cmd" ]]; then + histkey="$key" + break + fi + done + + # Give back the matched history entry + typeset -g suggestion="$history[$histkey]" +} + +#--------------------------------------------------------------------# +# Fetch Suggestion # +#--------------------------------------------------------------------# +# Loops through all specified strategies and returns a suggestion +# from the first strategy to provide one. +# + +_zsh_autosuggest_fetch_suggestion() { + typeset -g suggestion + local -a strategies + + # Ensure we are working with an array + strategies=(${=ZSH_AUTOSUGGEST_STRATEGY}) + + for strategy in $strategies; do + # Try to get a suggestion from this strategy + _zsh_autosuggest_strategy_$strategy "$1" + + # Break once we've found a suggestion + [[ -n "$suggestion" ]] && break + done +} + +#--------------------------------------------------------------------# +# Async # +#--------------------------------------------------------------------# + +# Zpty process is spawned running this function +_zsh_autosuggest_async_server() { + emulate -R zsh + + # There is a bug in zpty module (fixed in zsh/master) by which a + # zpty that exits will kill all zpty processes that were forked + # before it. Here we set up a zsh exit hook to SIGKILL the zpty + # process immediately, before it has a chance to kill any other + # zpty processes. + zshexit() { + kill -KILL $$ + sleep 1 # Block for long enough for the signal to come through + } + + # Don't add any extra carriage returns + stty -onlcr + + # Don't translate carriage returns to newlines + stty -icrnl + + # Silence any error messages + exec 2>/dev/null + + local last_pid + + while IFS='' read -r -d $'\0' query; do + # Kill last bg process + kill -KILL $last_pid &>/dev/null + + # Run suggestion search in the background + ( + local suggestion + _zsh_autosuggest_fetch_suggestion "$query" + echo -n -E "$suggestion"$'\0' + ) & + + last_pid=$! + done +} + +_zsh_autosuggest_async_request() { + # Write the query to the zpty process to fetch a suggestion + zpty -w -n $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME "${1}"$'\0' +} + +# Called when new data is ready to be read from the pty +# First arg will be fd ready for reading +# Second arg will be passed in case of error +_zsh_autosuggest_async_response() { + setopt LOCAL_OPTIONS EXTENDED_GLOB + + local suggestion + + zpty -rt $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME suggestion '*'$'\0' 2>/dev/null + zle autosuggest-suggest -- "${suggestion%%$'\0'##}" +} + +_zsh_autosuggest_async_pty_create() { + # With newer versions of zsh, REPLY stores the fd to read from + typeset -h REPLY + + # If we won't get a fd back from zpty, try to guess it + if (( ! $_ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD )); then + integer -l zptyfd + exec {zptyfd}>&1 # Open a new file descriptor (above 10). + exec {zptyfd}>&- # Close it so it's free to be used by zpty. + fi + + # Fork a zpty process running the server function + zpty -b $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME _zsh_autosuggest_async_server + + # Store the fd so we can remove the handler later + if (( REPLY )); then + _ZSH_AUTOSUGGEST_PTY_FD=$REPLY + else + _ZSH_AUTOSUGGEST_PTY_FD=$zptyfd + fi + + # Set up input handler from the zpty + zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_response +} + +_zsh_autosuggest_async_pty_destroy() { + # Remove the input handler + zle -F $_ZSH_AUTOSUGGEST_PTY_FD &>/dev/null + + # Destroy the zpty + zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null +} + +_zsh_autosuggest_async_pty_recreate() { + _zsh_autosuggest_async_pty_destroy + _zsh_autosuggest_async_pty_create +} + +_zsh_autosuggest_async_start() { + typeset -g _ZSH_AUTOSUGGEST_PTY_FD + + _zsh_autosuggest_feature_detect_zpty_returns_fd + _zsh_autosuggest_async_pty_recreate + + # We recreate the pty to get a fresh list of history events + add-zsh-hook precmd _zsh_autosuggest_async_pty_recreate +} + +#--------------------------------------------------------------------# +# Start # +#--------------------------------------------------------------------# + +# Start the autosuggestion widgets +_zsh_autosuggest_start() { + add-zsh-hook -d precmd _zsh_autosuggest_start + + _zsh_autosuggest_bind_widgets + + # Re-bind widgets on every precmd to ensure we wrap other wrappers. + # Specifically, highlighting breaks if our widgets are wrapped by + # zsh-syntax-highlighting widgets. This also allows modifications + # to the widget list variables to take effect on the next precmd. + add-zsh-hook precmd _zsh_autosuggest_bind_widgets + + if [[ -n "${ZSH_AUTOSUGGEST_USE_ASYNC+x}" ]]; then + _zsh_autosuggest_async_start + fi +} + +# Start the autosuggestion widgets on the next precmd +add-zsh-hook precmd _zsh_autosuggest_start