From a1115e6eb8383e41245c33382c0ae1151967b675 Mon Sep 17 00:00:00 2001 From: Carlo Sala Date: Thu, 10 Sep 2026 14:54:31 +0200 Subject: [PATCH] feat: add `zsh-autosuggestions` and `zsh-syntax-highlighting` (#14088) Closes #12865 Closes #11995 Closes #12864 --- .github/dependencies.yml | 43 + plugins/zsh-autosuggestions/LICENSE | 23 + plugins/zsh-autosuggestions/MANUAL.md | 188 ++ plugins/zsh-autosuggestions/README.md | 16 + plugins/zsh-autosuggestions/src/async.zsh | 77 + plugins/zsh-autosuggestions/src/bind.zsh | 106 + plugins/zsh-autosuggestions/src/config.zsh | 95 + plugins/zsh-autosuggestions/src/fetch.zsh | 27 + plugins/zsh-autosuggestions/src/highlight.zsh | 26 + plugins/zsh-autosuggestions/src/start.zsh | 33 + .../src/strategies/completion.zsh | 137 ++ .../src/strategies/history.zsh | 32 + .../src/strategies/match_prev_cmd.zsh | 66 + plugins/zsh-autosuggestions/src/util.zsh | 11 + plugins/zsh-autosuggestions/src/widgets.zsh | 231 +++ .../zsh-autosuggestions.plugin.zsh | 867 ++++++++ plugins/zsh-syntax-highlighting/LICENSE | 23 + plugins/zsh-syntax-highlighting/MANUAL.md | 97 + plugins/zsh-syntax-highlighting/README.md | 15 + .../docs/highlighters.md | 132 ++ .../docs/highlighters/brackets.md | 31 + .../docs/highlighters/cursor.md | 24 + .../docs/highlighters/line.md | 24 + .../docs/highlighters/main.md | 121 ++ .../docs/highlighters/pattern.md | 24 + .../docs/highlighters/regexp.md | 65 + .../docs/highlighters/root.md | 25 + .../highlighters/README.md | 8 + .../highlighters/brackets/README.md | 1 + .../brackets/brackets-highlighter.zsh | 107 + .../highlighters/cursor/README.md | 1 + .../cursor/cursor-highlighter.zsh | 47 + .../highlighters/line/README.md | 1 + .../highlighters/line/line-highlighter.zsh | 44 + .../highlighters/main/README.md | 1 + .../highlighters/main/main-highlighter.zsh | 1848 +++++++++++++++++ .../highlighters/pattern/README.md | 1 + .../pattern/pattern-highlighter.zsh | 60 + .../highlighters/regexp/README.md | 1 + .../regexp/regexp-highlighter.zsh | 62 + .../highlighters/root/README.md | 1 + .../highlighters/root/root-highlighter.zsh | 44 + .../images/after1-smaller.png | Bin 0 -> 2517 bytes .../zsh-syntax-highlighting/images/after1.png | Bin 0 -> 5842 bytes .../images/after2-smaller.png | Bin 0 -> 4386 bytes .../zsh-syntax-highlighting/images/after2.png | Bin 0 -> 10228 bytes .../images/after3-smaller.png | Bin 0 -> 1673 bytes .../zsh-syntax-highlighting/images/after3.png | Bin 0 -> 3427 bytes .../images/after4-smaller.png | Bin 0 -> 3224 bytes .../images/before1-smaller.png | Bin 0 -> 1589 bytes .../images/before1.png | Bin 0 -> 2636 bytes .../images/before2-smaller.png | Bin 0 -> 2605 bytes .../images/before2.png | Bin 0 -> 2614 bytes .../images/before3-smaller.png | Bin 0 -> 763 bytes .../images/before3.png | Bin 0 -> 1461 bytes .../images/before4-smaller.png | Bin 0 -> 2987 bytes .../images/preview-smaller.png | Bin 0 -> 5982 bytes .../images/preview.png | Bin 0 -> 14650 bytes .../zsh-syntax-highlighting.plugin.zsh | 587 ++++++ 59 files changed, 5373 insertions(+) create mode 100644 plugins/zsh-autosuggestions/LICENSE create mode 100644 plugins/zsh-autosuggestions/MANUAL.md create mode 100644 plugins/zsh-autosuggestions/README.md create mode 100644 plugins/zsh-autosuggestions/src/async.zsh create mode 100644 plugins/zsh-autosuggestions/src/bind.zsh create mode 100644 plugins/zsh-autosuggestions/src/config.zsh create mode 100644 plugins/zsh-autosuggestions/src/fetch.zsh create mode 100644 plugins/zsh-autosuggestions/src/highlight.zsh create mode 100644 plugins/zsh-autosuggestions/src/start.zsh create mode 100644 plugins/zsh-autosuggestions/src/strategies/completion.zsh create mode 100644 plugins/zsh-autosuggestions/src/strategies/history.zsh create mode 100644 plugins/zsh-autosuggestions/src/strategies/match_prev_cmd.zsh create mode 100644 plugins/zsh-autosuggestions/src/util.zsh create mode 100644 plugins/zsh-autosuggestions/src/widgets.zsh create mode 100644 plugins/zsh-autosuggestions/zsh-autosuggestions.plugin.zsh create mode 100644 plugins/zsh-syntax-highlighting/LICENSE create mode 100644 plugins/zsh-syntax-highlighting/MANUAL.md create mode 100644 plugins/zsh-syntax-highlighting/README.md create mode 100644 plugins/zsh-syntax-highlighting/docs/highlighters.md create mode 100644 plugins/zsh-syntax-highlighting/docs/highlighters/brackets.md create mode 100644 plugins/zsh-syntax-highlighting/docs/highlighters/cursor.md create mode 100644 plugins/zsh-syntax-highlighting/docs/highlighters/line.md create mode 100644 plugins/zsh-syntax-highlighting/docs/highlighters/main.md create mode 100644 plugins/zsh-syntax-highlighting/docs/highlighters/pattern.md create mode 100644 plugins/zsh-syntax-highlighting/docs/highlighters/regexp.md create mode 100644 plugins/zsh-syntax-highlighting/docs/highlighters/root.md create mode 100644 plugins/zsh-syntax-highlighting/highlighters/README.md create mode 120000 plugins/zsh-syntax-highlighting/highlighters/brackets/README.md create mode 100644 plugins/zsh-syntax-highlighting/highlighters/brackets/brackets-highlighter.zsh create mode 120000 plugins/zsh-syntax-highlighting/highlighters/cursor/README.md create mode 100644 plugins/zsh-syntax-highlighting/highlighters/cursor/cursor-highlighter.zsh create mode 120000 plugins/zsh-syntax-highlighting/highlighters/line/README.md create mode 100644 plugins/zsh-syntax-highlighting/highlighters/line/line-highlighter.zsh create mode 120000 plugins/zsh-syntax-highlighting/highlighters/main/README.md create mode 100644 plugins/zsh-syntax-highlighting/highlighters/main/main-highlighter.zsh create mode 120000 plugins/zsh-syntax-highlighting/highlighters/pattern/README.md create mode 100644 plugins/zsh-syntax-highlighting/highlighters/pattern/pattern-highlighter.zsh create mode 120000 plugins/zsh-syntax-highlighting/highlighters/regexp/README.md create mode 100644 plugins/zsh-syntax-highlighting/highlighters/regexp/regexp-highlighter.zsh create mode 120000 plugins/zsh-syntax-highlighting/highlighters/root/README.md create mode 100644 plugins/zsh-syntax-highlighting/highlighters/root/root-highlighter.zsh create mode 100644 plugins/zsh-syntax-highlighting/images/after1-smaller.png create mode 100644 plugins/zsh-syntax-highlighting/images/after1.png create mode 100644 plugins/zsh-syntax-highlighting/images/after2-smaller.png create mode 100644 plugins/zsh-syntax-highlighting/images/after2.png create mode 100644 plugins/zsh-syntax-highlighting/images/after3-smaller.png create mode 100644 plugins/zsh-syntax-highlighting/images/after3.png create mode 100644 plugins/zsh-syntax-highlighting/images/after4-smaller.png create mode 100644 plugins/zsh-syntax-highlighting/images/before1-smaller.png create mode 100644 plugins/zsh-syntax-highlighting/images/before1.png create mode 100644 plugins/zsh-syntax-highlighting/images/before2-smaller.png create mode 100644 plugins/zsh-syntax-highlighting/images/before2.png create mode 100644 plugins/zsh-syntax-highlighting/images/before3-smaller.png create mode 100644 plugins/zsh-syntax-highlighting/images/before3.png create mode 100644 plugins/zsh-syntax-highlighting/images/before4-smaller.png create mode 100644 plugins/zsh-syntax-highlighting/images/preview-smaller.png create mode 100644 plugins/zsh-syntax-highlighting/images/preview.png create mode 100644 plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.plugin.zsh diff --git a/.github/dependencies.yml b/.github/dependencies.yml index 9ada57348..2c33dd28b 100644 --- a/.github/dependencies.yml +++ b/.github/dependencies.yml @@ -55,3 +55,46 @@ dependencies: set -e test -e _zshz && mv -f _zshz _z test -e zsh-z.plugin.zsh && mv -f zsh-z.plugin.zsh z.plugin.zsh + plugins/zsh-autosuggestions: + repo: zsh-users/zsh-autosuggestions + branch: master + version: 85919cd1ffa7d2d5412f6d3fe437ebdbeeec4fc5 + precopy: | + set -e + test -e README.md && mv -f README.md MANUAL.md + sed -i 's|\[INSTALL.md\](INSTALL.md)|[the Oh My Zsh plugin README](README.md)|' MANUAL.md + rm -f zsh-autosuggestions.plugin.zsh + mv -f zsh-autosuggestions.zsh zsh-autosuggestions.plugin.zsh + find . -depth -mindepth 1 \ + ! -path './LICENSE' \ + ! -path './MANUAL.md' \ + ! -path './src' \ + ! -path './src/*' \ + ! -path './zsh-autosuggestions.plugin.zsh' \ + -delete + plugins/zsh-syntax-highlighting: + repo: zsh-users/zsh-syntax-highlighting + branch: master + version: 2fc57d63067c18b1100ecdbf684fa5baf49459d1 + precopy: | + set -e + version=$(<.version) + test -e README.md && mv -f README.md MANUAL.md + sed -i 's|\[INSTALL.md\](INSTALL.md)|[the Oh My Zsh plugin README](README.md)|' MANUAL.md + rm -f zsh-syntax-highlighting.plugin.zsh + mv -f zsh-syntax-highlighting.zsh zsh-syntax-highlighting.plugin.zsh + mv -f COPYING.md LICENSE + sed -i "s|typeset -g ZSH_HIGHLIGHT_VERSION=.*|typeset -g ZSH_HIGHLIGHT_VERSION=$version|" zsh-syntax-highlighting.plugin.zsh + sed -i 's|typeset -g ZSH_HIGHLIGHT_REVISION=.*|typeset -g ZSH_HIGHLIGHT_REVISION=HEAD|' zsh-syntax-highlighting.plugin.zsh + find highlighters -type d -name test-data -prune -exec rm -rf {} + + find . -depth -mindepth 1 \ + ! -path './LICENSE' \ + ! -path './MANUAL.md' \ + ! -path './docs' \ + ! -path './docs/*' \ + ! -path './highlighters' \ + ! -path './highlighters/*' \ + ! -path './images' \ + ! -path './images/*' \ + ! -path './zsh-syntax-highlighting.plugin.zsh' \ + -delete diff --git a/plugins/zsh-autosuggestions/LICENSE b/plugins/zsh-autosuggestions/LICENSE new file mode 100644 index 000000000..7ea78cc64 --- /dev/null +++ b/plugins/zsh-autosuggestions/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2013 Thiago de Arruda +Copyright (c) 2016-2021 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/plugins/zsh-autosuggestions/MANUAL.md b/plugins/zsh-autosuggestions/MANUAL.md new file mode 100644 index 000000000..d4e14952b --- /dev/null +++ b/plugins/zsh-autosuggestions/MANUAL.md @@ -0,0 +1,188 @@ +# zsh-autosuggestions + +_[Fish](http://fishshell.com/)-like fast/unobtrusive autosuggestions for zsh._ + +It suggests commands as you type based on history and completions. + +Requirements: Zsh v4.3.11 or later + +[![Chat on Gitter](https://img.shields.io/gitter/room/zsh-users/zsh-autosuggestions.svg)](https://gitter.im/zsh-users/zsh-autosuggestions) + + + + +## Installation + +See [the Oh My Zsh plugin README](README.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`, which will set the foreground color to color 8 from the [256-color palette](https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg). If your terminal only supports 8 colors, you will need to use a number between 0 and 7. + +Background color can also be set, and the suggestion can be styled bold, underlined, or standout. For example, this would show suggestions with bold, underlined, pink text on a cyan background: + +```sh +ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE="fg=#ff00ff,bg=cyan,bold,underline" +``` + +For more info, read the Character Highlighting section of the zsh manual: `man zshzle` or [online](http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Character-Highlighting). + +**Note:** Some iTerm2 users have reported [not being able to see the suggestions](https://github.com/zsh-users/zsh-autosuggestions/issues/416#issuecomment-486516333). If this affects you, the problem is likely caused by incorrect color settings. In order to correct this, go into iTerm2's setting, navigate to profile > colors and make sure that the colors for Basic Colors > Background and ANSI Colors > Bright Black are **different**. + + +### 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 three built-in strategies to choose from: + +- `history`: Chooses the most recent match from history. +- `completion`: Chooses a suggestion based on what tab-completion would suggest. (requires `zpty` module, which is included with zsh since 4.0.1) +- `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`. + +For example, setting `ZSH_AUTOSUGGEST_STRATEGY=(history completion)` will first try to find a suggestion from your history, but, if it can't find a match, will find a suggestion from the completion engine. + + +### 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 strings that are too long. + +### Asynchronous Mode + +Suggestions are fetched asynchronously by default in zsh versions 5.0.8 and greater. To disable asynchronous suggestions and fetch them synchronously instead, `unset ZSH_AUTOSUGGEST_USE_ASYNC` after sourcing the plugin. + +Alternatively, if you are using a version of zsh older than 5.0.8 and want to enable asynchronous mode, set the `ZSH_AUTOSUGGEST_USE_ASYNC` variable after sourcing the plugin (it can be set to anything). Note that there is [a bug](https://github.com/zsh-users/zsh-autosuggestions/issues/364#issuecomment-481423232) in versions of zsh older than 5.0.8 where ctrl + c will fail to reset the prompt immediately after fetching a suggestion asynchronously. + +### Disabling automatic widget re-binding + +Set `ZSH_AUTOSUGGEST_MANUAL_REBIND` (it can be set to anything) to disable automatic widget re-binding on each precmd. This can be a big boost to performance, but you'll need to handle re-binding yourself if any of the widget lists change or if you or another plugin wrap any of the autosuggest widgets. To re-bind widgets, run `_zsh_autosuggest_bind_widgets`. + +### Ignoring history suggestions that match a pattern + +Set `ZSH_AUTOSUGGEST_HISTORY_IGNORE` to a [glob pattern](http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Operators) to prevent offering suggestions for history entries that match the pattern. For example, set it to `"cd *"` to never suggest any `cd` commands from history. Or set to `"?(#c50,)"` to never suggest anything 50 characters or longer. + +**Note:** This only affects the `history` and `match_prev_cmd` suggestion strategies. + +### Skipping completion suggestions for certain cases + +Set `ZSH_AUTOSUGGEST_COMPLETION_IGNORE` to a [glob pattern](http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Operators) to prevent offering completion suggestions when the buffer matches that pattern. For example, set it to `"git *"` to disable completion suggestions for git subcommands. + +**Note:** This only affects the `completion` suggestion strategy. + + +### 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?q=) 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`). + +It's possible to run the tests for any supported version of zsh in a Docker image by building an image from the provided Dockerfile. To build the docker image for a specific version of zsh (where `` below is substituted with the contents of a line from the [`ZSH_VERSIONS`](ZSH_VERSIONS) file), run: + +```sh +docker build --build-arg TEST_ZSH_VERSION= -t zsh-autosuggestions-test . +``` + +After building the image, run the tests via: + +```sh +docker run -it -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/plugins/zsh-autosuggestions/README.md b/plugins/zsh-autosuggestions/README.md new file mode 100644 index 000000000..7afac2773 --- /dev/null +++ b/plugins/zsh-autosuggestions/README.md @@ -0,0 +1,16 @@ +# zsh-autosuggestions + +This plugin suggests commands as you type, based on command history and completions. + +To use it, add `zsh-autosuggestions` to the plugins array in your zshrc file: + +```zsh +plugins=(... zsh-autosuggestions) +``` + +Start a new terminal session after updating your zshrc file. + +Suggestions appear in muted gray text by default. Press or End at the end of the +command line to accept the displayed suggestion. + +For configuration options and additional key bindings, see the [upstream documentation](MANUAL.md). diff --git a/plugins/zsh-autosuggestions/src/async.zsh b/plugins/zsh-autosuggestions/src/async.zsh new file mode 100644 index 000000000..e1797345f --- /dev/null +++ b/plugins/zsh-autosuggestions/src/async.zsh @@ -0,0 +1,77 @@ + +#--------------------------------------------------------------------# +# Async # +#--------------------------------------------------------------------# + +_zsh_autosuggest_async_request() { + zmodload zsh/system 2>/dev/null # For `$sysparams` + + typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD _ZSH_AUTOSUGGEST_CHILD_PID + + # If we've got a pending request, cancel it + if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then + # Close the file descriptor and remove the handler + builtin exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&- + zle -F $_ZSH_AUTOSUGGEST_ASYNC_FD + + # We won't know the pid unless the user has zsh/system module installed + if [[ -n "$_ZSH_AUTOSUGGEST_CHILD_PID" ]]; then + # Zsh will make a new process group for the child process only if job + # control is enabled (MONITOR option) + if [[ -o MONITOR ]]; then + # Send the signal to the process group to kill any processes that may + # have been forked by the suggestion strategy + kill -TERM -$_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null + else + # Kill just the child process since it wasn't placed in a new process + # group. If the suggestion strategy forked any child processes they may + # be orphaned and left behind. + kill -TERM $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null + fi + fi + fi + + # Fork a process to fetch a suggestion and open a pipe to read from it + builtin exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <( + # Tell parent process our pid + echo $sysparams[pid] + + # Fetch and print the suggestion + local suggestion + _zsh_autosuggest_fetch_suggestion "$1" + echo -nE "$suggestion" + ) + + # There's a weird bug here where ^C stops working unless we force a fork + # See https://github.com/zsh-users/zsh-autosuggestions/issues/364 + autoload -Uz is-at-least + is-at-least 5.8 || command true + + # Read the pid from the child process + read _ZSH_AUTOSUGGEST_CHILD_PID <&$_ZSH_AUTOSUGGEST_ASYNC_FD + + # When the fd is readable, call the response handler + zle -F "$_ZSH_AUTOSUGGEST_ASYNC_FD" _zsh_autosuggest_async_response +} + +# Called when new data is ready to be read from the pipe +# First arg will be fd ready for reading +# Second arg will be passed in case of error +_zsh_autosuggest_async_response() { + emulate -L zsh + + local suggestion + + if [[ -z "$2" || "$2" == "hup" ]]; then + # Read everything from the fd and give it as a suggestion + IFS='' read -rd '' -u $1 suggestion + zle autosuggest-suggest -- "$suggestion" + + # Close the fd + builtin exec {1}<&- + fi + + # Always remove the handler + zle -F "$1" + _ZSH_AUTOSUGGEST_ASYNC_FD= +} diff --git a/plugins/zsh-autosuggestions/src/bind.zsh b/plugins/zsh-autosuggestions/src/bind.zsh new file mode 100644 index 000000000..1dde137c0 --- /dev/null +++ b/plugins/zsh-autosuggestions/src/bind.zsh @@ -0,0 +1,106 @@ + +#--------------------------------------------------------------------# +# Widget Helpers # +#--------------------------------------------------------------------# + +_zsh_autosuggest_incr_bind_count() { + typeset -gi bind_count=$((_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]+1)) + _ZSH_AUTOSUGGEST_BIND_COUNTS[$1]=$bind_count +} + +# 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)_*) + bind_count=$((_ZSH_AUTOSUGGEST_BIND_COUNTS[$widget])) + ;; + + # 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 + + # 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=( + .\* + _\* + ${_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS/#/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/plugins/zsh-autosuggestions/src/config.zsh b/plugins/zsh-autosuggestions/src/config.zsh new file mode 100644 index 000000000..32d32b250 --- /dev/null +++ b/plugins/zsh-autosuggestions/src/config.zsh @@ -0,0 +1,95 @@ + +#--------------------------------------------------------------------# +# 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} )) && +typeset -g ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8' + +# Prefix to use when saving original versions of bound widgets +(( ! ${+ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX} )) && +typeset -g 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} )) && { + typeset -ga ZSH_AUTOSUGGEST_STRATEGY + ZSH_AUTOSUGGEST_STRATEGY=(history) +} + +# Widgets that clear the suggestion +(( ! ${+ZSH_AUTOSUGGEST_CLEAR_WIDGETS} )) && { + typeset -ga ZSH_AUTOSUGGEST_CLEAR_WIDGETS + ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( + history-search-forward + history-search-backward + history-beginning-search-forward + history-beginning-search-backward + history-beginning-search-forward-end + history-beginning-search-backward-end + 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 + copy-earlier-word + ) +} + +# Widgets that accept the entire suggestion +(( ! ${+ZSH_AUTOSUGGEST_ACCEPT_WIDGETS} )) && { + typeset -ga 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} )) && { + typeset -ga ZSH_AUTOSUGGEST_EXECUTE_WIDGETS + ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=( + ) +} + +# Widgets that accept the suggestion as far as the cursor moves +(( ! ${+ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS} )) && { + typeset -ga 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} )) && { + typeset -ga ZSH_AUTOSUGGEST_IGNORE_WIDGETS + ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( + orig-\* + beep + run-help + set-local-history + which-command + yank + yank-pop + zle-\* + ) +} + +# Pty name for capturing completions for completion suggestion strategy +(( ! ${+ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME} )) && +typeset -g ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME=zsh_autosuggest_completion_pty diff --git a/plugins/zsh-autosuggestions/src/fetch.zsh b/plugins/zsh-autosuggestions/src/fetch.zsh new file mode 100644 index 000000000..fef271520 --- /dev/null +++ b/plugins/zsh-autosuggestions/src/fetch.zsh @@ -0,0 +1,27 @@ + +#--------------------------------------------------------------------# +# 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 + local strategy + + # 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" + + # Ensure the suggestion matches the prefix + [[ "$suggestion" != "$1"* ]] && unset suggestion + + # Break once we've found a valid suggestion + [[ -n "$suggestion" ]] && break + done +} diff --git a/plugins/zsh-autosuggestions/src/highlight.zsh b/plugins/zsh-autosuggestions/src/highlight.zsh new file mode 100644 index 000000000..273c03d53 --- /dev/null +++ b/plugins/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/plugins/zsh-autosuggestions/src/start.zsh b/plugins/zsh-autosuggestions/src/start.zsh new file mode 100644 index 000000000..5d4ee521e --- /dev/null +++ b/plugins/zsh-autosuggestions/src/start.zsh @@ -0,0 +1,33 @@ + +#--------------------------------------------------------------------# +# Start # +#--------------------------------------------------------------------# + +# Start the autosuggestion widgets +_zsh_autosuggest_start() { + # By default we 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. However this has + # a decent performance hit, so users can set ZSH_AUTOSUGGEST_MANUAL_REBIND + # to disable the automatic re-binding. + if (( ${+ZSH_AUTOSUGGEST_MANUAL_REBIND} )); then + add-zsh-hook -d precmd _zsh_autosuggest_start + fi + + _zsh_autosuggest_bind_widgets +} + +# Mark for auto-loading the functions that we use +autoload -Uz add-zsh-hook is-at-least + +# Automatically enable asynchronous mode in newer versions of zsh. Disable for +# older versions because there is a bug when using async mode where ^C does not +# work immediately after fetching a suggestion. +# See https://github.com/zsh-users/zsh-autosuggestions/issues/364 +if is-at-least 5.0.8; then + typeset -g ZSH_AUTOSUGGEST_USE_ASYNC= +fi + +# Start the autosuggestion widgets on the next precmd +add-zsh-hook precmd _zsh_autosuggest_start diff --git a/plugins/zsh-autosuggestions/src/strategies/completion.zsh b/plugins/zsh-autosuggestions/src/strategies/completion.zsh new file mode 100644 index 000000000..e2d114c8a --- /dev/null +++ b/plugins/zsh-autosuggestions/src/strategies/completion.zsh @@ -0,0 +1,137 @@ + +#--------------------------------------------------------------------# +# Completion Suggestion Strategy # +#--------------------------------------------------------------------# +# Fetches a suggestion from the completion engine +# + +_zsh_autosuggest_capture_postcompletion() { + # Always insert the first completion into the buffer + compstate[insert]=1 + + # Don't list completions + unset 'compstate[list]' +} + +_zsh_autosuggest_capture_completion_widget() { + # Add a post-completion hook to be called after all completions have been + # gathered. The hook can modify compstate to affect what is done with the + # gathered completions. + local -a +h comppostfuncs + comppostfuncs=(_zsh_autosuggest_capture_postcompletion) + + # Only capture completions at the end of the buffer + CURSOR=$#BUFFER + + # Run the original widget wrapping `.complete-word` so we don't + # recursively try to fetch suggestions, since our pty is forked + # after autosuggestions is initialized. + zle -- ${(k)widgets[(r)completion:.complete-word:_main_complete]} + + if is-at-least 5.0.3; then + # Don't do any cr/lf transformations. We need to do this immediately before + # output because if we do it in setup, onlcr will be re-enabled when we enter + # vared in the async code path. There is a bug in zpty module in older versions + # where the tty is not properly attached to the pty slave, resulting in stty + # getting stopped with a SIGTTOU. See zsh-workers thread 31660 and upstream + # commit f75904a38 + stty -onlcr -ocrnl -F /dev/tty + fi + + # The completion has been added, print the buffer as the suggestion + echo -nE - $'\0'$BUFFER$'\0' +} + +zle -N autosuggest-capture-completion _zsh_autosuggest_capture_completion_widget + +_zsh_autosuggest_capture_setup() { + # There is a bug in zpty module in older zsh versions 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. + if ! is-at-least 5.4; then + zshexit() { + # The zsh builtin `kill` fails sometimes in older versions + # https://unix.stackexchange.com/a/477647/156673 + kill -KILL $$ 2>&- || command kill -KILL $$ + + # Block for long enough for the signal to come through + sleep 1 + } + fi + + # Try to avoid any suggestions that wouldn't match the prefix + zstyle ':completion:*' matcher-list '' + zstyle ':completion:*' path-completion false + zstyle ':completion:*' max-errors 0 not-numeric + + bindkey '^I' autosuggest-capture-completion +} + +_zsh_autosuggest_capture_completion_sync() { + _zsh_autosuggest_capture_setup + + zle autosuggest-capture-completion +} + +_zsh_autosuggest_capture_completion_async() { + _zsh_autosuggest_capture_setup + + zmodload zsh/parameter 2>/dev/null || return # For `$functions` + + # Make vared completion work as if for a normal command line + # https://stackoverflow.com/a/7057118/154703 + autoload +X _complete + functions[_original_complete]=$functions[_complete] + function _complete() { + unset 'compstate[vared]' + _original_complete "$@" + } + + # Open zle with buffer set so we can capture completions for it + vared 1 +} + +_zsh_autosuggest_strategy_completion() { + # Reset options to defaults and enable LOCAL_OPTIONS + emulate -L zsh + + # Enable extended glob for completion ignore pattern + setopt EXTENDED_GLOB + + typeset -g suggestion + local line REPLY + + # Exit if we don't have completions + whence compdef >/dev/null || return + + # Exit if we don't have zpty + zmodload zsh/zpty 2>/dev/null || return + + # Exit if our search string matches the ignore pattern + [[ -n "$ZSH_AUTOSUGGEST_COMPLETION_IGNORE" ]] && [[ "$1" == $~ZSH_AUTOSUGGEST_COMPLETION_IGNORE ]] && return + + # Zle will be inactive if we are in async mode + if zle; then + zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_completion_sync + else + zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_completion_async "\$1" + zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME $'\t' + fi + + { + # The completion result is surrounded by null bytes, so read the + # content between the first two null bytes. + zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line '*'$'\0''*'$'\0' + + # Extract the suggestion from between the null bytes. On older + # versions of zsh (older than 5.3), we sometimes get extra bytes after + # the second null byte, so trim those off the end. + # See http://www.zsh.org/mla/workers/2015/msg03290.html + suggestion="${${(@0)line}[2]}" + } always { + # Destroy the pty + zpty -d $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME + } +} diff --git a/plugins/zsh-autosuggestions/src/strategies/history.zsh b/plugins/zsh-autosuggestions/src/strategies/history.zsh new file mode 100644 index 000000000..0672a1364 --- /dev/null +++ b/plugins/zsh-autosuggestions/src/strategies/history.zsh @@ -0,0 +1,32 @@ + +#--------------------------------------------------------------------# +# 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) and (x~y) glob operator + 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 the prefix, excluding those that match + # the ignore pattern + local pattern="$prefix*" + if [[ -n $ZSH_AUTOSUGGEST_HISTORY_IGNORE ]]; then + pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)" + fi + + # Give the first history item matching the pattern as the suggestion + # - (r) subscript flag makes the pattern match on values + typeset -g suggestion="${history[(r)$pattern]}" +} diff --git a/plugins/zsh-autosuggestions/src/strategies/match_prev_cmd.zsh b/plugins/zsh-autosuggestions/src/strategies/match_prev_cmd.zsh new file mode 100644 index 000000000..b7097837e --- /dev/null +++ b/plugins/zsh-autosuggestions/src/strategies/match_prev_cmd.zsh @@ -0,0 +1,66 @@ + +#--------------------------------------------------------------------# +# 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) and (x~y) glob operator + 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 the history items that match the prefix, excluding those that match + # the ignore pattern + local pattern="$prefix*" + if [[ -n $ZSH_AUTOSUGGEST_HISTORY_IGNORE ]]; then + pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)" + fi + + # Get all history event numbers that correspond to history + # entries that match the pattern + local history_match_keys + history_match_keys=(${(k)history[(R)$~pattern]}) + + # 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/plugins/zsh-autosuggestions/src/util.zsh b/plugins/zsh-autosuggestions/src/util.zsh new file mode 100644 index 000000000..1f55d3634 --- /dev/null +++ b/plugins/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/plugins/zsh-autosuggestions/src/widgets.zsh b/plugins/zsh-autosuggestions/src/widgets.zsh new file mode 100644 index 000000000..7562897ea --- /dev/null +++ b/plugins/zsh-autosuggestions/src/widgets.zsh @@ -0,0 +1,231 @@ + +#--------------------------------------------------------------------# +# 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 (( ${+_ZSH_AUTOSUGGEST_DISABLED} )); then + _zsh_autosuggest_enable + else + _zsh_autosuggest_disable + fi +} + +# Clear the suggestion +_zsh_autosuggest_clear() { + # Remove the suggestion + POSTDISPLAY= + + _zsh_autosuggest_invoke_original_widget $@ +} + +# Modify the buffer and get a new suggestion +_zsh_autosuggest_modify() { + 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 + POSTDISPLAY= + + # Original widget may modify the buffer + _zsh_autosuggest_invoke_original_widget $@ + retval=$? + + emulate -L zsh + + # 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 or if buffer hasn't changed + if [[ "$BUFFER" = "$orig_buffer"* && "$orig_postdisplay" = "${BUFFER:$#orig_buffer}"* ]]; then + POSTDISPLAY="${orig_postdisplay:$(($#BUFFER - $#orig_buffer))}" + return $retval + fi + + # Bail out if suggestions are disabled + if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )); 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 (( ${+ZSH_AUTOSUGGEST_USE_ASYNC} )); 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 + POSTDISPLAY= + fi +} + +# Accept the entire suggestion +_zsh_autosuggest_accept() { + local -i retval 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 + + # If we're not in a valid state to accept a suggestion, just run the + # original widget and bail out + if (( $CURSOR != $max_cursor_pos || !$#POSTDISPLAY )); then + _zsh_autosuggest_invoke_original_widget $@ + return + fi + + # Only accept if the cursor is at the end of the buffer + # Add the suggestion to the buffer + BUFFER="$BUFFER$POSTDISPLAY" + + # Remove the suggestion + POSTDISPLAY= + + # Run the original widget before manually moving the cursor so that the + # cursor movement doesn't make the widget do something unexpected + _zsh_autosuggest_invoke_original_widget $@ + retval=$? + + # Move the cursor to the end of the buffer + if [[ "$KEYMAP" = "vicmd" ]]; then + CURSOR=$(($#BUFFER - 1)) + else + CURSOR=$#BUFFER + fi + + return $retval +} + +# Accept the entire suggestion and execute it +_zsh_autosuggest_execute() { + # Add the suggestion to the buffer + BUFFER="$BUFFER$POSTDISPLAY" + + # Remove the suggestion + 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 +} + +() { + typeset -ga _ZSH_AUTOSUGGEST_BUILTIN_ACTIONS + + _ZSH_AUTOSUGGEST_BUILTIN_ACTIONS=( + clear + fetch + suggest + accept + execute + enable + disable + toggle + ) + + local action + for action in $_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS modify partial_accept; 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 + + for action in $_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS; do + zle -N autosuggest-$action _zsh_autosuggest_widget_$action + done +} diff --git a/plugins/zsh-autosuggestions/zsh-autosuggestions.plugin.zsh b/plugins/zsh-autosuggestions/zsh-autosuggestions.plugin.zsh new file mode 100644 index 000000000..e78022516 --- /dev/null +++ b/plugins/zsh-autosuggestions/zsh-autosuggestions.plugin.zsh @@ -0,0 +1,867 @@ +# Fish-like fast/unobtrusive autosuggestions for zsh. +# https://github.com/zsh-users/zsh-autosuggestions +# v0.7.1 +# Copyright (c) 2013 Thiago de Arruda +# Copyright (c) 2016-2021 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. + +#--------------------------------------------------------------------# +# 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} )) && +typeset -g ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8' + +# Prefix to use when saving original versions of bound widgets +(( ! ${+ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX} )) && +typeset -g 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} )) && { + typeset -ga ZSH_AUTOSUGGEST_STRATEGY + ZSH_AUTOSUGGEST_STRATEGY=(history) +} + +# Widgets that clear the suggestion +(( ! ${+ZSH_AUTOSUGGEST_CLEAR_WIDGETS} )) && { + typeset -ga ZSH_AUTOSUGGEST_CLEAR_WIDGETS + ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( + history-search-forward + history-search-backward + history-beginning-search-forward + history-beginning-search-backward + history-beginning-search-forward-end + history-beginning-search-backward-end + 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 + copy-earlier-word + ) +} + +# Widgets that accept the entire suggestion +(( ! ${+ZSH_AUTOSUGGEST_ACCEPT_WIDGETS} )) && { + typeset -ga 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} )) && { + typeset -ga ZSH_AUTOSUGGEST_EXECUTE_WIDGETS + ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=( + ) +} + +# Widgets that accept the suggestion as far as the cursor moves +(( ! ${+ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS} )) && { + typeset -ga 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} )) && { + typeset -ga ZSH_AUTOSUGGEST_IGNORE_WIDGETS + ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( + orig-\* + beep + run-help + set-local-history + which-command + yank + yank-pop + zle-\* + ) +} + +# Pty name for capturing completions for completion suggestion strategy +(( ! ${+ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME} )) && +typeset -g ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME=zsh_autosuggest_completion_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}" +} + +#--------------------------------------------------------------------# +# Widget Helpers # +#--------------------------------------------------------------------# + +_zsh_autosuggest_incr_bind_count() { + typeset -gi bind_count=$((_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]+1)) + _ZSH_AUTOSUGGEST_BIND_COUNTS[$1]=$bind_count +} + +# 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)_*) + bind_count=$((_ZSH_AUTOSUGGEST_BIND_COUNTS[$widget])) + ;; + + # 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 + + # 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=( + .\* + _\* + ${_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS/#/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 (( ${+_ZSH_AUTOSUGGEST_DISABLED} )); then + _zsh_autosuggest_enable + else + _zsh_autosuggest_disable + fi +} + +# Clear the suggestion +_zsh_autosuggest_clear() { + # Remove the suggestion + POSTDISPLAY= + + _zsh_autosuggest_invoke_original_widget $@ +} + +# Modify the buffer and get a new suggestion +_zsh_autosuggest_modify() { + 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 + POSTDISPLAY= + + # Original widget may modify the buffer + _zsh_autosuggest_invoke_original_widget $@ + retval=$? + + emulate -L zsh + + # 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 or if buffer hasn't changed + if [[ "$BUFFER" = "$orig_buffer"* && "$orig_postdisplay" = "${BUFFER:$#orig_buffer}"* ]]; then + POSTDISPLAY="${orig_postdisplay:$(($#BUFFER - $#orig_buffer))}" + return $retval + fi + + # Bail out if suggestions are disabled + if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )); 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 (( ${+ZSH_AUTOSUGGEST_USE_ASYNC} )); 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 + POSTDISPLAY= + fi +} + +# Accept the entire suggestion +_zsh_autosuggest_accept() { + local -i retval 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 + + # If we're not in a valid state to accept a suggestion, just run the + # original widget and bail out + if (( $CURSOR != $max_cursor_pos || !$#POSTDISPLAY )); then + _zsh_autosuggest_invoke_original_widget $@ + return + fi + + # Only accept if the cursor is at the end of the buffer + # Add the suggestion to the buffer + BUFFER="$BUFFER$POSTDISPLAY" + + # Remove the suggestion + POSTDISPLAY= + + # Run the original widget before manually moving the cursor so that the + # cursor movement doesn't make the widget do something unexpected + _zsh_autosuggest_invoke_original_widget $@ + retval=$? + + # Move the cursor to the end of the buffer + if [[ "$KEYMAP" = "vicmd" ]]; then + CURSOR=$(($#BUFFER - 1)) + else + CURSOR=$#BUFFER + fi + + return $retval +} + +# Accept the entire suggestion and execute it +_zsh_autosuggest_execute() { + # Add the suggestion to the buffer + BUFFER="$BUFFER$POSTDISPLAY" + + # Remove the suggestion + 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 +} + +() { + typeset -ga _ZSH_AUTOSUGGEST_BUILTIN_ACTIONS + + _ZSH_AUTOSUGGEST_BUILTIN_ACTIONS=( + clear + fetch + suggest + accept + execute + enable + disable + toggle + ) + + local action + for action in $_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS modify partial_accept; 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 + + for action in $_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS; do + zle -N autosuggest-$action _zsh_autosuggest_widget_$action + done +} + +#--------------------------------------------------------------------# +# Completion Suggestion Strategy # +#--------------------------------------------------------------------# +# Fetches a suggestion from the completion engine +# + +_zsh_autosuggest_capture_postcompletion() { + # Always insert the first completion into the buffer + compstate[insert]=1 + + # Don't list completions + unset 'compstate[list]' +} + +_zsh_autosuggest_capture_completion_widget() { + # Add a post-completion hook to be called after all completions have been + # gathered. The hook can modify compstate to affect what is done with the + # gathered completions. + local -a +h comppostfuncs + comppostfuncs=(_zsh_autosuggest_capture_postcompletion) + + # Only capture completions at the end of the buffer + CURSOR=$#BUFFER + + # Run the original widget wrapping `.complete-word` so we don't + # recursively try to fetch suggestions, since our pty is forked + # after autosuggestions is initialized. + zle -- ${(k)widgets[(r)completion:.complete-word:_main_complete]} + + if is-at-least 5.0.3; then + # Don't do any cr/lf transformations. We need to do this immediately before + # output because if we do it in setup, onlcr will be re-enabled when we enter + # vared in the async code path. There is a bug in zpty module in older versions + # where the tty is not properly attached to the pty slave, resulting in stty + # getting stopped with a SIGTTOU. See zsh-workers thread 31660 and upstream + # commit f75904a38 + stty -onlcr -ocrnl -F /dev/tty + fi + + # The completion has been added, print the buffer as the suggestion + echo -nE - $'\0'$BUFFER$'\0' +} + +zle -N autosuggest-capture-completion _zsh_autosuggest_capture_completion_widget + +_zsh_autosuggest_capture_setup() { + # There is a bug in zpty module in older zsh versions 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. + if ! is-at-least 5.4; then + zshexit() { + # The zsh builtin `kill` fails sometimes in older versions + # https://unix.stackexchange.com/a/477647/156673 + kill -KILL $$ 2>&- || command kill -KILL $$ + + # Block for long enough for the signal to come through + sleep 1 + } + fi + + # Try to avoid any suggestions that wouldn't match the prefix + zstyle ':completion:*' matcher-list '' + zstyle ':completion:*' path-completion false + zstyle ':completion:*' max-errors 0 not-numeric + + bindkey '^I' autosuggest-capture-completion +} + +_zsh_autosuggest_capture_completion_sync() { + _zsh_autosuggest_capture_setup + + zle autosuggest-capture-completion +} + +_zsh_autosuggest_capture_completion_async() { + _zsh_autosuggest_capture_setup + + zmodload zsh/parameter 2>/dev/null || return # For `$functions` + + # Make vared completion work as if for a normal command line + # https://stackoverflow.com/a/7057118/154703 + autoload +X _complete + functions[_original_complete]=$functions[_complete] + function _complete() { + unset 'compstate[vared]' + _original_complete "$@" + } + + # Open zle with buffer set so we can capture completions for it + vared 1 +} + +_zsh_autosuggest_strategy_completion() { + # Reset options to defaults and enable LOCAL_OPTIONS + emulate -L zsh + + # Enable extended glob for completion ignore pattern + setopt EXTENDED_GLOB + + typeset -g suggestion + local line REPLY + + # Exit if we don't have completions + whence compdef >/dev/null || return + + # Exit if we don't have zpty + zmodload zsh/zpty 2>/dev/null || return + + # Exit if our search string matches the ignore pattern + [[ -n "$ZSH_AUTOSUGGEST_COMPLETION_IGNORE" ]] && [[ "$1" == $~ZSH_AUTOSUGGEST_COMPLETION_IGNORE ]] && return + + # Zle will be inactive if we are in async mode + if zle; then + zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_completion_sync + else + zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_completion_async "\$1" + zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME $'\t' + fi + + { + # The completion result is surrounded by null bytes, so read the + # content between the first two null bytes. + zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line '*'$'\0''*'$'\0' + + # Extract the suggestion from between the null bytes. On older + # versions of zsh (older than 5.3), we sometimes get extra bytes after + # the second null byte, so trim those off the end. + # See http://www.zsh.org/mla/workers/2015/msg03290.html + suggestion="${${(@0)line}[2]}" + } always { + # Destroy the pty + zpty -d $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME + } +} + +#--------------------------------------------------------------------# +# 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) and (x~y) glob operator + 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 the prefix, excluding those that match + # the ignore pattern + local pattern="$prefix*" + if [[ -n $ZSH_AUTOSUGGEST_HISTORY_IGNORE ]]; then + pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)" + fi + + # Give the first history item matching the pattern as the suggestion + # - (r) subscript flag makes the pattern match on values + typeset -g suggestion="${history[(r)$pattern]}" +} + +#--------------------------------------------------------------------# +# 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) and (x~y) glob operator + 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 the history items that match the prefix, excluding those that match + # the ignore pattern + local pattern="$prefix*" + if [[ -n $ZSH_AUTOSUGGEST_HISTORY_IGNORE ]]; then + pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)" + fi + + # Get all history event numbers that correspond to history + # entries that match the pattern + local history_match_keys + history_match_keys=(${(k)history[(R)$~pattern]}) + + # 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 + local strategy + + # 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" + + # Ensure the suggestion matches the prefix + [[ "$suggestion" != "$1"* ]] && unset suggestion + + # Break once we've found a valid suggestion + [[ -n "$suggestion" ]] && break + done +} + +#--------------------------------------------------------------------# +# Async # +#--------------------------------------------------------------------# + +_zsh_autosuggest_async_request() { + zmodload zsh/system 2>/dev/null # For `$sysparams` + + typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD _ZSH_AUTOSUGGEST_CHILD_PID + + # If we've got a pending request, cancel it + if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then + # Close the file descriptor and remove the handler + builtin exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&- + zle -F $_ZSH_AUTOSUGGEST_ASYNC_FD + + # We won't know the pid unless the user has zsh/system module installed + if [[ -n "$_ZSH_AUTOSUGGEST_CHILD_PID" ]]; then + # Zsh will make a new process group for the child process only if job + # control is enabled (MONITOR option) + if [[ -o MONITOR ]]; then + # Send the signal to the process group to kill any processes that may + # have been forked by the suggestion strategy + kill -TERM -$_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null + else + # Kill just the child process since it wasn't placed in a new process + # group. If the suggestion strategy forked any child processes they may + # be orphaned and left behind. + kill -TERM $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null + fi + fi + fi + + # Fork a process to fetch a suggestion and open a pipe to read from it + builtin exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <( + # Tell parent process our pid + echo $sysparams[pid] + + # Fetch and print the suggestion + local suggestion + _zsh_autosuggest_fetch_suggestion "$1" + echo -nE "$suggestion" + ) + + # There's a weird bug here where ^C stops working unless we force a fork + # See https://github.com/zsh-users/zsh-autosuggestions/issues/364 + autoload -Uz is-at-least + is-at-least 5.8 || command true + + # Read the pid from the child process + read _ZSH_AUTOSUGGEST_CHILD_PID <&$_ZSH_AUTOSUGGEST_ASYNC_FD + + # When the fd is readable, call the response handler + zle -F "$_ZSH_AUTOSUGGEST_ASYNC_FD" _zsh_autosuggest_async_response +} + +# Called when new data is ready to be read from the pipe +# First arg will be fd ready for reading +# Second arg will be passed in case of error +_zsh_autosuggest_async_response() { + emulate -L zsh + + local suggestion + + if [[ -z "$2" || "$2" == "hup" ]]; then + # Read everything from the fd and give it as a suggestion + IFS='' read -rd '' -u $1 suggestion + zle autosuggest-suggest -- "$suggestion" + + # Close the fd + builtin exec {1}<&- + fi + + # Always remove the handler + zle -F "$1" + _ZSH_AUTOSUGGEST_ASYNC_FD= +} + +#--------------------------------------------------------------------# +# Start # +#--------------------------------------------------------------------# + +# Start the autosuggestion widgets +_zsh_autosuggest_start() { + # By default we 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. However this has + # a decent performance hit, so users can set ZSH_AUTOSUGGEST_MANUAL_REBIND + # to disable the automatic re-binding. + if (( ${+ZSH_AUTOSUGGEST_MANUAL_REBIND} )); then + add-zsh-hook -d precmd _zsh_autosuggest_start + fi + + _zsh_autosuggest_bind_widgets +} + +# Mark for auto-loading the functions that we use +autoload -Uz add-zsh-hook is-at-least + +# Automatically enable asynchronous mode in newer versions of zsh. Disable for +# older versions because there is a bug when using async mode where ^C does not +# work immediately after fetching a suggestion. +# See https://github.com/zsh-users/zsh-autosuggestions/issues/364 +if is-at-least 5.0.8; then + typeset -g ZSH_AUTOSUGGEST_USE_ASYNC= +fi + +# Start the autosuggestion widgets on the next precmd +add-zsh-hook precmd _zsh_autosuggest_start diff --git a/plugins/zsh-syntax-highlighting/LICENSE b/plugins/zsh-syntax-highlighting/LICENSE new file mode 100644 index 000000000..cacbcbee0 --- /dev/null +++ b/plugins/zsh-syntax-highlighting/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2010-2020 zsh-syntax-highlighting contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted +provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of + conditions and the following disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of the zsh-syntax-highlighting contributors nor the names of its contributors + may be used to endorse or promote products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/plugins/zsh-syntax-highlighting/MANUAL.md b/plugins/zsh-syntax-highlighting/MANUAL.md new file mode 100644 index 000000000..e4a34bff0 --- /dev/null +++ b/plugins/zsh-syntax-highlighting/MANUAL.md @@ -0,0 +1,97 @@ +zsh-syntax-highlighting [![Build Status][build-status-image]][build-status] +======================= + +**[Fish shell][fish]-like syntax highlighting for [Zsh][zsh].** + +*Requirements: zsh 4.3.11+.* + +[fish]: https://fishshell.com/ +[zsh]: https://www.zsh.org/ + +This package provides syntax highlighting for the shell zsh. It enables +highlighting of commands whilst they are typed at a zsh prompt into an +interactive terminal. This helps in reviewing commands before running +them, particularly in catching syntax errors. + +Some examples: + +Before: [![Screenshot #1.1](images/before1-smaller.png)](images/before1.png) +
+After:  [![Screenshot #1.2](images/after1-smaller.png)](images/after1.png) + +Before: [![Screenshot #2.1](images/before2-smaller.png)](images/before2.png) +
+After:  [![Screenshot #2.2](images/after2-smaller.png)](images/after2.png) + +Before: [![Screenshot #3.1](images/before3-smaller.png)](images/before3.png) +
+After:  [![Screenshot #3.2](images/after3-smaller.png)](images/after3.png) + +Before: [![Screenshot #4.1](images/before4-smaller.png)](images/before4-smaller.png) +
+After:  [![Screenshot #4.2](images/after4-smaller.png)](images/after4-smaller.png) + + + +How to install +-------------- + +See [the Oh My Zsh plugin README](README.md). + + +FAQ +--- + +### Why must `zsh-syntax-highlighting.zsh` be sourced at the end of the `.zshrc` file? + +zsh-syntax-highlighting works by hooking into the Zsh Line Editor (ZLE) and +computing syntax highlighting for the command-line buffer as it stands at the +time z-sy-h's hook is invoked. + +In zsh 5.2 and older, +`zsh-syntax-highlighting.zsh` hooks into ZLE by wrapping ZLE widgets. It must +be sourced after all custom widgets have been created (i.e., after all `zle -N` +calls and after running `compinit`) in order to be able to wrap all of them. +Widgets created after z-sy-h is sourced will work, but will not update the +syntax highlighting. + +In zsh newer than 5.8 (not including 5.8 itself), +zsh-syntax-highlighting uses the `add-zle-hook-widget` facility to install +a `zle-line-pre-redraw` hook. Hooks are run in order of registration, +therefore, z-sy-h must be sourced (and register its hook) after anything else +that adds hooks that modify the command-line buffer. + +### Does syntax highlighting work during incremental history search? + +Highlighting the command line during an incremental history search (by default bound to +to Ctrl+R in zsh's emacs keymap) requires zsh 5.4 or newer. + +Under zsh versions older than 5.4, the zsh-default [underlining][zshzle-Character-Highlighting] +of the matched portion of the buffer remains available, but zsh-syntax-highlighting's +additional highlighting is unavailable. (Those versions of zsh do not provide +enough information to allow computing the highlighting correctly.) + +See issues [#288][i288] and [#415][i415] for details. + +[zshzle-Character-Highlighting]: https://zsh.sourceforge.io/Doc/Release/Zsh-Line-Editor.html#Character-Highlighting +[i288]: https://github.com/zsh-users/zsh-syntax-highlighting/pull/288 +[i415]: https://github.com/zsh-users/zsh-syntax-highlighting/pull/415 + +### How are new releases announced? + +There is currently no "push" announcements channel. However, the following +alternatives exist: + +- GitHub's RSS feed of releases: https://github.com/zsh-users/zsh-syntax-highlighting/releases.atom +- An anitya entry: https://release-monitoring.org/project/7552/ + + +How to tweak +------------ + +Syntax highlighting is done by pluggable highlighter scripts. See the +[documentation on highlighters](docs/highlighters.md) for details and +configuration settings. + +[build-status]: https://github.com/zsh-users/zsh-syntax-highlighting/actions +[build-status-image]: https://github.com/zsh-users/zsh-syntax-highlighting/workflows/Tests/badge.svg diff --git a/plugins/zsh-syntax-highlighting/README.md b/plugins/zsh-syntax-highlighting/README.md new file mode 100644 index 000000000..6b45dc40e --- /dev/null +++ b/plugins/zsh-syntax-highlighting/README.md @@ -0,0 +1,15 @@ +# zsh-syntax-highlighting + +This plugin enables Fish-like syntax highlighting for commands as they are typed at a zsh prompt. + +To use it, add `zsh-syntax-highlighting` to the end of the plugins array in your zshrc file: + +```zsh +plugins=(... zsh-autosuggestions zsh-syntax-highlighting) +``` + +The plugin must be loaded after plugins that create or wrap ZLE widgets, so it should remain the last plugin +in the array. Start a new terminal session after updating your zshrc file. + +For available highlighters and configuration options, see the [upstream documentation](MANUAL.md) and the +[highlighter documentation](docs/highlighters.md). diff --git a/plugins/zsh-syntax-highlighting/docs/highlighters.md b/plugins/zsh-syntax-highlighting/docs/highlighters.md new file mode 100644 index 000000000..fb64c3afe --- /dev/null +++ b/plugins/zsh-syntax-highlighting/docs/highlighters.md @@ -0,0 +1,132 @@ +zsh-syntax-highlighting / highlighters +====================================== + +Syntax highlighting is done by pluggable highlighters: + +* `main` - the base highlighter, and the only one [active by default][main]. +* `brackets` - [matches brackets][brackets] and parenthesis. +* `pattern` - matches [user-defined patterns][pattern]. +* `regexp` - matches [user-defined regular expressions][regexp]. +* `cursor` - matches [the cursor position][cursor]. +* `root` - highlights the whole command line [if the current user is root][root]. +* `line` - applied to [the whole command line][line]. + +[main]: highlighters/main.md +[brackets]: highlighters/brackets.md +[pattern]: highlighters/pattern.md +[regexp]: highlighters/regexp.md +[cursor]: highlighters/cursor.md +[root]: highlighters/root.md +[line]: highlighters/line.md + + +Highlighter-independent settings +-------------------------------- + +By default, all command lines are highlighted. However, it is possible to +prevent command lines longer than a fixed number of characters from being +highlighted by setting the variable `${ZSH_HIGHLIGHT_MAXLENGTH}` to the maximum +length (in characters) of command lines to be highlighter. This is useful when +editing very long command lines (for example, with the [`fned`][fned] utility +function). Example: + +[fned]: https://zsh.sourceforge.io/Doc/Release/User-Contributions.html#index-zed + +```zsh +ZSH_HIGHLIGHT_MAXLENGTH=512 +``` + + +How to activate highlighters +---------------------------- + +To activate an highlighter, add it to the `ZSH_HIGHLIGHT_HIGHLIGHTERS` array. +By default `ZSH_HIGHLIGHT_HIGHLIGHTERS` is `(main)`. For example to activate +`brackets`, `pattern`, and `cursor` highlighters, in `~/.zshrc` do: + +```zsh +ZSH_HIGHLIGHT_HIGHLIGHTERS+=(brackets pattern cursor) +``` + + +How to tweak highlighters +------------------------- + +Highlighters look up styles from the `ZSH_HIGHLIGHT_STYLES` associative array. +Navigate into the [individual highlighters' documentation](highlighters/) to +see what styles (keys) each highlighter defines; the syntax for values is the +same as the syntax of "types of highlighting" of the zsh builtin +`$zle_highlight` array, which is documented in [the `zshzle(1)` manual +page][zshzle-Character-Highlighting]. + +[zshzle-Character-Highlighting]: https://zsh.sourceforge.io/Doc/Release/Zsh-Line-Editor.html#Character-Highlighting + +Some highlighters support additional configuration parameters; see each +highlighter's documentation for details and examples. + + +How to implement a new highlighter +---------------------------------- + +To create your own `acme` highlighter: + +* Create your script at + `highlighters/acme/acme-highlighter.zsh`. + +* Implement the `_zsh_highlight_highlighter_acme_predicate` function. + This function must return 0 when the highlighter needs to be called and + non-zero otherwise, for example: + + ```zsh + _zsh_highlight_highlighter_acme_predicate() { + # Call this highlighter in SVN working copies + [[ -d .svn ]] + } + ``` + +* Implement the `_zsh_highlight_highlighter_acme_paint` function. + This function does the actual syntax highlighting, by calling + `_zsh_highlight_add_highlight` with the start and end of the region to + be highlighted and the `ZSH_HIGHLIGHT_STYLES` key to use. Define the default + style for that key in the highlighter script outside of any function with + `: ${ZSH_HIGHLIGHT_STYLES[key]:=value}`, being sure to prefix + the key with your highlighter name and a colon. For example: + + ```zsh + : ${ZSH_HIGHLIGHT_STYLES[acme:aurora]:=fg=green} + + _zsh_highlight_highlighter_acme_paint() { + # Colorize the whole buffer with the 'aurora' style + _zsh_highlight_add_highlight 0 $#BUFFER acme:aurora + } + ``` + + If you need to test which options the user has set, test `zsyh_user_options` + with a sensible default if the option is not present in supported zsh + versions. For example: + + ```zsh + [[ ${zsyh_user_options[ignoreclosebraces]:-off} == on ]] + ``` + + The option name must be all lowercase with no underscores and not an alias. + +* Name your own functions and global variables `_zsh_highlight_acme_*`. + + - In zsh-syntax-highlighting 0.4.0 and earlier, the entrypoints + `_zsh_highlight_highlighter_acme_predicate` and + `_zsh_highlight_highlighter_acme_paint` + were named + `_zsh_highlight_acme_highlighter_predicate` and + `_zsh_highlight_highlighter_acme_paint` respectively. + + These names are still supported for backwards compatibility; + however, support for them will be removed in a future major or minor release (v0.x.0 or v1.0.0). + +* Activate your highlighter in `~/.zshrc`: + + ```zsh + ZSH_HIGHLIGHT_HIGHLIGHTERS+=(acme) + ``` + +* [Write tests](../tests/README.md). diff --git a/plugins/zsh-syntax-highlighting/docs/highlighters/brackets.md b/plugins/zsh-syntax-highlighting/docs/highlighters/brackets.md new file mode 100644 index 000000000..0101699e0 --- /dev/null +++ b/plugins/zsh-syntax-highlighting/docs/highlighters/brackets.md @@ -0,0 +1,31 @@ +zsh-syntax-highlighting / highlighters / brackets +------------------------------------------------- + +This is the `brackets` highlighter, that highlights brackets and parentheses, and +matches them. + + +### How to tweak it + +This highlighter defines the following styles: + +* `bracket-error` - unmatched brackets +* `bracket-level-N` - brackets with nest level N +* `cursor-matchingbracket` - the matching bracket, if cursor is on a bracket + +To override one of those styles, change its entry in `ZSH_HIGHLIGHT_STYLES`, +for example in `~/.zshrc`: + +```zsh +# To define styles for nested brackets up to level 4 +ZSH_HIGHLIGHT_STYLES[bracket-level-1]='fg=blue,bold' +ZSH_HIGHLIGHT_STYLES[bracket-level-2]='fg=red,bold' +ZSH_HIGHLIGHT_STYLES[bracket-level-3]='fg=yellow,bold' +ZSH_HIGHLIGHT_STYLES[bracket-level-4]='fg=magenta,bold' +``` + +The syntax for values is the same as the syntax of "types of highlighting" of +the zsh builtin `$zle_highlight` array, which is documented in [the `zshzle(1)` +manual page][zshzle-Character-Highlighting]. + +[zshzle-Character-Highlighting]: https://zsh.sourceforge.io/Doc/Release/Zsh-Line-Editor.html#Character-Highlighting diff --git a/plugins/zsh-syntax-highlighting/docs/highlighters/cursor.md b/plugins/zsh-syntax-highlighting/docs/highlighters/cursor.md new file mode 100644 index 000000000..3f87d422e --- /dev/null +++ b/plugins/zsh-syntax-highlighting/docs/highlighters/cursor.md @@ -0,0 +1,24 @@ +zsh-syntax-highlighting / highlighters / cursor +----------------------------------------------- + +This is the `cursor` highlighter, that highlights the cursor. + + +### How to tweak it + +This highlighter defines the following styles: + +* `cursor` - the style for the current cursor position + +To override one of those styles, change its entry in `ZSH_HIGHLIGHT_STYLES`, +for example in `~/.zshrc`: + +```zsh +ZSH_HIGHLIGHT_STYLES[cursor]='bg=blue' +``` + +The syntax for values is the same as the syntax of "types of highlighting" of +the zsh builtin `$zle_highlight` array, which is documented in [the `zshzle(1)` +manual page][zshzle-Character-Highlighting]. + +[zshzle-Character-Highlighting]: https://zsh.sourceforge.io/Doc/Release/Zsh-Line-Editor.html#Character-Highlighting diff --git a/plugins/zsh-syntax-highlighting/docs/highlighters/line.md b/plugins/zsh-syntax-highlighting/docs/highlighters/line.md new file mode 100644 index 000000000..f76639ee9 --- /dev/null +++ b/plugins/zsh-syntax-highlighting/docs/highlighters/line.md @@ -0,0 +1,24 @@ +zsh-syntax-highlighting / highlighters / line +--------------------------------------------- + +This is the `line` highlighter, that highlights the whole line. + + +### How to tweak it + +This highlighter defines the following styles: + +* `line` - the style for the whole line + +To override one of those styles, change its entry in `ZSH_HIGHLIGHT_STYLES`, +for example in `~/.zshrc`: + +```zsh +ZSH_HIGHLIGHT_STYLES[line]='bold' +``` + +The syntax for values is the same as the syntax of "types of highlighting" of +the zsh builtin `$zle_highlight` array, which is documented in [the `zshzle(1)` +manual page][zshzle-Character-Highlighting]. + +[zshzle-Character-Highlighting]: https://zsh.sourceforge.io/Doc/Release/Zsh-Line-Editor.html#Character-Highlighting diff --git a/plugins/zsh-syntax-highlighting/docs/highlighters/main.md b/plugins/zsh-syntax-highlighting/docs/highlighters/main.md new file mode 100644 index 000000000..4a27653f0 --- /dev/null +++ b/plugins/zsh-syntax-highlighting/docs/highlighters/main.md @@ -0,0 +1,121 @@ +zsh-syntax-highlighting / highlighters / main +--------------------------------------------- + +This is the `main` highlighter, that highlights: + +* Commands +* Options +* Arguments +* Paths +* Strings + +This highlighter is active by default. + + +### How to tweak it + +This highlighter defines the following styles: + +* `unknown-token` - unknown tokens / errors +* `reserved-word` - shell reserved words (`if`, `for`) +* `alias` - aliases +* `suffix-alias` - suffix aliases (requires zsh 5.1.1 or newer) +* `global-alias` - global aliases +* `builtin` - shell builtin commands (`shift`, `pwd`, `zstyle`) +* `function` - function names +* `command` - command names +* `precommand` - precommand modifiers (e.g., `noglob`, `builtin`) +* `commandseparator` - command separation tokens (`;`, `&&`) +* `hashed-command` - hashed commands +* `autodirectory` - a directory name in command position when the `AUTO_CD` option is set +* `path` - existing filenames +* `path_pathseparator` - path separators in filenames (`/`); if unset, `path` is used (default) +* `path_prefix` - prefixes of existing filenames +* `path_prefix_pathseparator` - path separators in prefixes of existing filenames (`/`); if unset, `path_prefix` is used (default) +* `globbing` - globbing expressions (`*.txt`) +* `history-expansion` - history expansion expressions (`!foo` and `^foo^bar`) +* `command-substitution` - command substitutions (`$(echo foo)`) +* `command-substitution-unquoted` - an unquoted command substitution (`$(echo foo)`) +* `command-substitution-quoted` - a quoted command substitution (`"$(echo foo)"`) +* `command-substitution-delimiter` - command substitution delimiters (`$(` and `)`) +* `command-substitution-delimiter-unquoted` - an unquoted command substitution delimiters (`$(` and `)`) +* `command-substitution-delimiter-quoted` - a quoted command substitution delimiters (`"$(` and `)"`) +* `process-substitution` - process substitutions (`<(echo foo)`) +* `process-substitution-delimiter` - process substitution delimiters (`<(` and `)`) +* `arithmetic-expansion` - arithmetic expansion `$(( 42 ))`) +* `single-hyphen-option` - single-hyphen options (`-o`) +* `double-hyphen-option` - double-hyphen options (`--option`) +* `back-quoted-argument` - backtick command substitution (`` `foo` ``) +* `back-quoted-argument-unclosed` - unclosed backtick command substitution (`` `foo ``) +* `back-quoted-argument-delimiter` - backtick command substitution delimiters (`` ` ``) +* `single-quoted-argument` - single-quoted arguments (`` 'foo' ``) +* `single-quoted-argument-unclosed` - unclosed single-quoted arguments (`` 'foo ``) +* `double-quoted-argument` - double-quoted arguments (`` "foo" ``) +* `double-quoted-argument-unclosed` - unclosed double-quoted arguments (`` "foo ``) +* `dollar-quoted-argument` - dollar-quoted arguments (`` $'foo' ``) +* `dollar-quoted-argument-unclosed` - unclosed dollar-quoted arguments (`` $'foo ``) +* `rc-quote` - two single quotes inside single quotes when the `RC_QUOTES` option is set (`` 'foo''bar' ``) +* `dollar-double-quoted-argument` - parameter expansion inside double quotes (`$foo` inside `""`) +* `back-double-quoted-argument` - backslash escape sequences inside double-quoted arguments (`\"` in `"foo\"bar"`) +* `back-dollar-quoted-argument` - backslash escape sequences inside dollar-quoted arguments (`\x` in `$'\x48'`) +* `assign` - parameter assignments (`x=foo` and `x=( )`) +* `redirection` - redirection operators (`<`, `>`, etc) +* `comment` - comments, when `setopt INTERACTIVE_COMMENTS` is in effect (`echo # foo`) +* `comment` - elided parameters in command position (`$x ls` when `$x` is unset or empty) +* `named-fd` - named file descriptor (the `fd` in `echo foo {fd}>&2`) +* `numeric-fd` - numeric file descriptor (the `2` in `echo foo {fd}>&2`) +* `arg0` - a command word other than one of those enumerated above (other than a command, precommand, alias, function, or shell builtin command). +* `default` - everything else + +To override one of those styles, change its entry in `ZSH_HIGHLIGHT_STYLES`, +for example in `~/.zshrc`: + +```zsh +# Declare the variable +typeset -A ZSH_HIGHLIGHT_STYLES + +# To differentiate aliases from other command types +ZSH_HIGHLIGHT_STYLES[alias]='fg=magenta,bold' + +# To have paths colored instead of underlined +ZSH_HIGHLIGHT_STYLES[path]='fg=cyan' + +# To disable highlighting of globbing expressions +ZSH_HIGHLIGHT_STYLES[globbing]='none' +``` + +The syntax for values is the same as the syntax of "types of highlighting" of +the zsh builtin `$zle_highlight` array, which is documented in [the `zshzle(1)` +manual page][zshzle-Character-Highlighting]. + +#### Parameters + +To avoid partial path lookups on a path, add the path to the `ZSH_HIGHLIGHT_DIRS_BLACKLIST` array. + +```zsh +ZSH_HIGHLIGHT_DIRS_BLACKLIST+=(/mnt/slow_share) +``` + +### Useless trivia + +#### Forward compatibility. + +zsh-syntax-highlighting attempts to be forward-compatible with zsh. +Specifically, we attempt to facilitate highlighting _command word_ types that +had not yet been invented when this version of zsh-syntax-highlighting was +released. + +A _command word_ is something like a function name, external command name, et +cetera. (See +[Simple Commands & Pipelines in `zshmisc(1)`][zshmisc-Simple-Commands-And-Pipelines] +for a formal definition.) + +If a new _kind_ of command word is ever added to zsh — something conceptually +different than "function" and "alias" and "external command" — then command words +of that (new) kind will be highlighted by the style `arg0_$kind`, +where `$kind` is the output of `type -w` on the new kind of command word. If that +style is not defined, then the style `arg0` will be used instead. + +[zshmisc-Simple-Commands-And-Pipelines]: https://zsh.sourceforge.io/Doc/Release/Shell-Grammar.html#Simple-Commands-_0026-Pipelines + +[zshzle-Character-Highlighting]: https://zsh.sourceforge.io/Doc/Release/Zsh-Line-Editor.html#Character-Highlighting diff --git a/plugins/zsh-syntax-highlighting/docs/highlighters/pattern.md b/plugins/zsh-syntax-highlighting/docs/highlighters/pattern.md new file mode 100644 index 000000000..ecaa6a7f1 --- /dev/null +++ b/plugins/zsh-syntax-highlighting/docs/highlighters/pattern.md @@ -0,0 +1,24 @@ +zsh-syntax-highlighting / highlighters / pattern +------------------------------------------------ + +This is the `pattern` highlighter, that highlights user-defined patterns. + + +### How to tweak it + +To use this highlighter, associate patterns with styles in the +`ZSH_HIGHLIGHT_PATTERNS` associative array, for example in `~/.zshrc`: + +```zsh +# Declare the variable +typeset -A ZSH_HIGHLIGHT_PATTERNS + +# To have commands starting with `rm -rf` in red: +ZSH_HIGHLIGHT_PATTERNS+=('rm -rf *' 'fg=white,bold,bg=red') +``` + +The syntax for values is the same as the syntax of "types of highlighting" of +the zsh builtin `$zle_highlight` array, which is documented in [the `zshzle(1)` +manual page][zshzle-Character-Highlighting]. + +[zshzle-Character-Highlighting]: https://zsh.sourceforge.io/Doc/Release/Zsh-Line-Editor.html#Character-Highlighting diff --git a/plugins/zsh-syntax-highlighting/docs/highlighters/regexp.md b/plugins/zsh-syntax-highlighting/docs/highlighters/regexp.md new file mode 100644 index 000000000..8c711a3c7 --- /dev/null +++ b/plugins/zsh-syntax-highlighting/docs/highlighters/regexp.md @@ -0,0 +1,65 @@ +zsh-syntax-highlighting / highlighters / regexp +------------------------------------------------ + +This is the `regexp` highlighter, that highlights user-defined regular +expressions. It's similar to the `pattern` highlighter, but allows more complex +patterns. + +### How to tweak it + +To use this highlighter, associate regular expressions with styles in the +`ZSH_HIGHLIGHT_REGEXP` associative array, for example in `~/.zshrc`: + +```zsh +typeset -A ZSH_HIGHLIGHT_REGEXP +ZSH_HIGHLIGHT_REGEXP+=('^rm .*' fg=red,bold) +``` + +This will highlight lines that start with a call to the `rm` command. + +The regular expressions flavour used is [PCRE][pcresyntax] when the +`RE_MATCH_PCRE` option is set and POSIX Extended Regular Expressions (ERE), +as implemented by the platform's C library, otherwise. For details on the +latter, see [the `zsh/regex` module's documentation][MAN_ZSH_REGEX] and the +`regcomp(3)` and `re_format(7)` manual pages on your system. + +For instance, to highlight `sudo` only as a complete word, i.e., `sudo cmd`, +but not `sudoedit`, one might use: + +* When the `RE_MATCH_PCRE` is set: + + ```zsh + typeset -A ZSH_HIGHLIGHT_REGEXP + ZSH_HIGHLIGHT_REGEXP+=('\bsudo\b' fg=123,bold) + ``` + +* When the `RE_MATCH_PCRE` is unset, on platforms with GNU `libc` (e.g., many GNU/Linux distributions): + + ```zsh + typeset -A ZSH_HIGHLIGHT_REGEXP + ZSH_HIGHLIGHT_REGEXP+=('\' fg=123,bold) + ``` + +* When the `RE_MATCH_PCRE` is unset, on BSD-based platforms (e.g., macOS): + + ```zsh + typeset -A ZSH_HIGHLIGHT_REGEXP + ZSH_HIGHLIGHT_REGEXP+=('[[:<:]]sudo[[:>:]]' fg=123,bold) + ``` + +Note, however, that PCRE and POSIX ERE have a large common subset: +for instance, the regular expressions `[abc]`, `a*`, and `(a|b)` have the same +meaning in both flavours. + +The syntax for values is the same as the syntax of "types of highlighting" of +the zsh builtin `$zle_highlight` array, which is documented in [the `zshzle(1)` +manual page][zshzle-Character-Highlighting]. + +See also: [regular expressions tutorial][perlretut], zsh regexp operator `=~` +in [the `zshmisc(1)` manual page][zshmisc-Conditional-Expressions] + +[zshzle-Character-Highlighting]: https://zsh.sourceforge.io/Doc/Release/Zsh-Line-Editor.html#Character-Highlighting +[perlretut]: https://perldoc.perl.org/perlretut +[zshmisc-Conditional-Expressions]: https://zsh.sourceforge.io/Doc/Release/Conditional-Expressions.html#Conditional-Expressions +[MAN_ZSH_REGEX]: https://zsh.sourceforge.io/Doc/Release/Zsh-Modules.html#The-zsh_002fregex-Module +[pcresyntax]: https://www.pcre.org/original/doc/html/pcresyntax.html diff --git a/plugins/zsh-syntax-highlighting/docs/highlighters/root.md b/plugins/zsh-syntax-highlighting/docs/highlighters/root.md new file mode 100644 index 000000000..8197e4bd4 --- /dev/null +++ b/plugins/zsh-syntax-highlighting/docs/highlighters/root.md @@ -0,0 +1,25 @@ +zsh-syntax-highlighting / highlighters / root +--------------------------------------------- + +This is the `root` highlighter, that highlights the whole line if the current +user is root. + + +### How to tweak it + +This highlighter defines the following styles: + +* `root` - the style for the whole line if the current user is root. + +To override one of those styles, change its entry in `ZSH_HIGHLIGHT_STYLES`, +for example in `~/.zshrc`: + +```zsh +ZSH_HIGHLIGHT_STYLES[root]='bg=red' +``` + +The syntax for values is the same as the syntax of "types of highlighting" of +the zsh builtin `$zle_highlight` array, which is documented in [the `zshzle(1)` +manual page][zshzle-Character-Highlighting]. + +[zshzle-Character-Highlighting]: https://zsh.sourceforge.io/Doc/Release/Zsh-Line-Editor.html#Character-Highlighting diff --git a/plugins/zsh-syntax-highlighting/highlighters/README.md b/plugins/zsh-syntax-highlighting/highlighters/README.md new file mode 100644 index 000000000..8490ddf43 --- /dev/null +++ b/plugins/zsh-syntax-highlighting/highlighters/README.md @@ -0,0 +1,8 @@ +zsh-syntax-highlighting / highlighters +====================================== + +Navigate into the individual highlighters' documentation to see +what styles (`$ZSH_HIGHLIGHT_STYLES` keys) each highlighter defines. + +Refer to the [documentation on highlighters](../docs/highlighters.md) for further +information. diff --git a/plugins/zsh-syntax-highlighting/highlighters/brackets/README.md b/plugins/zsh-syntax-highlighting/highlighters/brackets/README.md new file mode 120000 index 000000000..6997bd60d --- /dev/null +++ b/plugins/zsh-syntax-highlighting/highlighters/brackets/README.md @@ -0,0 +1 @@ +../../docs/highlighters/brackets.md \ No newline at end of file diff --git a/plugins/zsh-syntax-highlighting/highlighters/brackets/brackets-highlighter.zsh b/plugins/zsh-syntax-highlighting/highlighters/brackets/brackets-highlighter.zsh new file mode 100644 index 000000000..bc388fdbf --- /dev/null +++ b/plugins/zsh-syntax-highlighting/highlighters/brackets/brackets-highlighter.zsh @@ -0,0 +1,107 @@ +# ------------------------------------------------------------------------------------------------- +# Copyright (c) 2010-2017 zsh-syntax-highlighting contributors +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted +# provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this list of conditions +# and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, this list of +# conditions and the following disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the zsh-syntax-highlighting contributors nor the names of its contributors +# may be used to endorse or promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# ------------------------------------------------------------------------------------------------- +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et +# ------------------------------------------------------------------------------------------------- + + +# Define default styles. +: ${ZSH_HIGHLIGHT_STYLES[bracket-error]:=fg=red,bold} +: ${ZSH_HIGHLIGHT_STYLES[bracket-level-1]:=fg=blue,bold} +: ${ZSH_HIGHLIGHT_STYLES[bracket-level-2]:=fg=green,bold} +: ${ZSH_HIGHLIGHT_STYLES[bracket-level-3]:=fg=magenta,bold} +: ${ZSH_HIGHLIGHT_STYLES[bracket-level-4]:=fg=yellow,bold} +: ${ZSH_HIGHLIGHT_STYLES[bracket-level-5]:=fg=cyan,bold} +: ${ZSH_HIGHLIGHT_STYLES[cursor-matchingbracket]:=standout} + +# Whether the brackets highlighter should be called or not. +_zsh_highlight_highlighter_brackets_predicate() +{ + [[ $WIDGET == zle-line-finish ]] || _zsh_highlight_cursor_moved || _zsh_highlight_buffer_modified +} + +# Brackets highlighting function. +_zsh_highlight_highlighter_brackets_paint() +{ + local char style + local -i bracket_color_size=${#ZSH_HIGHLIGHT_STYLES[(I)bracket-level-*]} buflen=${#BUFFER} level=0 matchingpos pos + local -A levelpos lastoflevel matching + + # Find all brackets and remember which one is matching + pos=0 + for char in ${(s..)BUFFER} ; do + (( ++pos )) + case $char in + ["([{"]) + levelpos[$pos]=$((++level)) + lastoflevel[$level]=$pos + ;; + [")]}"]) + if (( level > 0 )); then + matchingpos=$lastoflevel[$level] + levelpos[$pos]=$((level--)) + if _zsh_highlight_brackets_match $matchingpos $pos; then + matching[$matchingpos]=$pos + matching[$pos]=$matchingpos + fi + else + levelpos[$pos]=-1 + fi + ;; + esac + done + + # Now highlight all found brackets + for pos in ${(k)levelpos}; do + if (( $+matching[$pos] )); then + if (( bracket_color_size )); then + _zsh_highlight_add_highlight $((pos - 1)) $pos bracket-level-$(( (levelpos[$pos] - 1) % bracket_color_size + 1 )) + fi + else + _zsh_highlight_add_highlight $((pos - 1)) $pos bracket-error + fi + done + + # If cursor is on a bracket, then highlight corresponding bracket, if any. + if [[ $WIDGET != zle-line-finish ]]; then + pos=$((CURSOR + 1)) + if (( $+levelpos[$pos] )) && (( $+matching[$pos] )); then + local -i otherpos=$matching[$pos] + _zsh_highlight_add_highlight $((otherpos - 1)) $otherpos cursor-matchingbracket + fi + fi +} + +# Helper function to differentiate type +_zsh_highlight_brackets_match() +{ + case $BUFFER[$1] in + \() [[ $BUFFER[$2] == \) ]];; + \[) [[ $BUFFER[$2] == \] ]];; + \{) [[ $BUFFER[$2] == \} ]];; + *) false;; + esac +} diff --git a/plugins/zsh-syntax-highlighting/highlighters/cursor/README.md b/plugins/zsh-syntax-highlighting/highlighters/cursor/README.md new file mode 120000 index 000000000..70e0c8c42 --- /dev/null +++ b/plugins/zsh-syntax-highlighting/highlighters/cursor/README.md @@ -0,0 +1 @@ +../../docs/highlighters/cursor.md \ No newline at end of file diff --git a/plugins/zsh-syntax-highlighting/highlighters/cursor/cursor-highlighter.zsh b/plugins/zsh-syntax-highlighting/highlighters/cursor/cursor-highlighter.zsh new file mode 100644 index 000000000..81633a3c3 --- /dev/null +++ b/plugins/zsh-syntax-highlighting/highlighters/cursor/cursor-highlighter.zsh @@ -0,0 +1,47 @@ +# ------------------------------------------------------------------------------------------------- +# Copyright (c) 2010-2011 zsh-syntax-highlighting contributors +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted +# provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this list of conditions +# and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, this list of +# conditions and the following disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the zsh-syntax-highlighting contributors nor the names of its contributors +# may be used to endorse or promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# ------------------------------------------------------------------------------------------------- +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et +# ------------------------------------------------------------------------------------------------- + + +# Define default styles. +: ${ZSH_HIGHLIGHT_STYLES[cursor]:=standout} + +# Whether the cursor highlighter should be called or not. +_zsh_highlight_highlighter_cursor_predicate() +{ + # remove cursor highlighting when the line is finished + [[ $WIDGET == zle-line-finish ]] || _zsh_highlight_cursor_moved +} + +# Cursor highlighting function. +_zsh_highlight_highlighter_cursor_paint() +{ + [[ $WIDGET == zle-line-finish ]] && return + + _zsh_highlight_add_highlight $CURSOR $(( $CURSOR + 1 )) cursor +} diff --git a/plugins/zsh-syntax-highlighting/highlighters/line/README.md b/plugins/zsh-syntax-highlighting/highlighters/line/README.md new file mode 120000 index 000000000..052fe20b5 --- /dev/null +++ b/plugins/zsh-syntax-highlighting/highlighters/line/README.md @@ -0,0 +1 @@ +../../docs/highlighters/line.md \ No newline at end of file diff --git a/plugins/zsh-syntax-highlighting/highlighters/line/line-highlighter.zsh b/plugins/zsh-syntax-highlighting/highlighters/line/line-highlighter.zsh new file mode 100644 index 000000000..f922dc9b2 --- /dev/null +++ b/plugins/zsh-syntax-highlighting/highlighters/line/line-highlighter.zsh @@ -0,0 +1,44 @@ +# ------------------------------------------------------------------------------------------------- +# Copyright (c) 2010-2011 zsh-syntax-highlighting contributors +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted +# provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this list of conditions +# and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, this list of +# conditions and the following disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the zsh-syntax-highlighting contributors nor the names of its contributors +# may be used to endorse or promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# ------------------------------------------------------------------------------------------------- +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et +# ------------------------------------------------------------------------------------------------- + + +# Define default styles. +: ${ZSH_HIGHLIGHT_STYLES[line]:=} + +# Whether the root highlighter should be called or not. +_zsh_highlight_highlighter_line_predicate() +{ + _zsh_highlight_buffer_modified +} + +# root highlighting function. +_zsh_highlight_highlighter_line_paint() +{ + _zsh_highlight_add_highlight 0 $#BUFFER line +} diff --git a/plugins/zsh-syntax-highlighting/highlighters/main/README.md b/plugins/zsh-syntax-highlighting/highlighters/main/README.md new file mode 120000 index 000000000..035473184 --- /dev/null +++ b/plugins/zsh-syntax-highlighting/highlighters/main/README.md @@ -0,0 +1 @@ +../../docs/highlighters/main.md \ No newline at end of file diff --git a/plugins/zsh-syntax-highlighting/highlighters/main/main-highlighter.zsh b/plugins/zsh-syntax-highlighting/highlighters/main/main-highlighter.zsh new file mode 100644 index 000000000..061e2a489 --- /dev/null +++ b/plugins/zsh-syntax-highlighting/highlighters/main/main-highlighter.zsh @@ -0,0 +1,1848 @@ +# ------------------------------------------------------------------------------------------------- +# Copyright (c) 2010-2020 zsh-syntax-highlighting contributors +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted +# provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this list of conditions +# and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, this list of +# conditions and the following disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the zsh-syntax-highlighting contributors nor the names of its contributors +# may be used to endorse or promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# ------------------------------------------------------------------------------------------------- +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et +# ------------------------------------------------------------------------------------------------- + + +# Define default styles. +: ${ZSH_HIGHLIGHT_STYLES[default]:=none} +: ${ZSH_HIGHLIGHT_STYLES[unknown-token]:=fg=red,bold} +: ${ZSH_HIGHLIGHT_STYLES[reserved-word]:=fg=yellow} +: ${ZSH_HIGHLIGHT_STYLES[suffix-alias]:=fg=green,underline} +: ${ZSH_HIGHLIGHT_STYLES[global-alias]:=fg=cyan} +: ${ZSH_HIGHLIGHT_STYLES[precommand]:=fg=green,underline} +: ${ZSH_HIGHLIGHT_STYLES[commandseparator]:=none} +: ${ZSH_HIGHLIGHT_STYLES[autodirectory]:=fg=green,underline} +: ${ZSH_HIGHLIGHT_STYLES[path]:=underline} +: ${ZSH_HIGHLIGHT_STYLES[path_pathseparator]:=} +: ${ZSH_HIGHLIGHT_STYLES[path_prefix_pathseparator]:=} +: ${ZSH_HIGHLIGHT_STYLES[globbing]:=fg=blue} +: ${ZSH_HIGHLIGHT_STYLES[history-expansion]:=fg=blue} +: ${ZSH_HIGHLIGHT_STYLES[command-substitution]:=none} +: ${ZSH_HIGHLIGHT_STYLES[command-substitution-delimiter]:=fg=magenta} +: ${ZSH_HIGHLIGHT_STYLES[process-substitution]:=none} +: ${ZSH_HIGHLIGHT_STYLES[process-substitution-delimiter]:=fg=magenta} +: ${ZSH_HIGHLIGHT_STYLES[single-hyphen-option]:=none} +: ${ZSH_HIGHLIGHT_STYLES[double-hyphen-option]:=none} +: ${ZSH_HIGHLIGHT_STYLES[back-quoted-argument]:=none} +: ${ZSH_HIGHLIGHT_STYLES[back-quoted-argument-delimiter]:=fg=magenta} +: ${ZSH_HIGHLIGHT_STYLES[single-quoted-argument]:=fg=yellow} +: ${ZSH_HIGHLIGHT_STYLES[double-quoted-argument]:=fg=yellow} +: ${ZSH_HIGHLIGHT_STYLES[dollar-quoted-argument]:=fg=yellow} +: ${ZSH_HIGHLIGHT_STYLES[rc-quote]:=fg=cyan} +: ${ZSH_HIGHLIGHT_STYLES[dollar-double-quoted-argument]:=fg=cyan} +: ${ZSH_HIGHLIGHT_STYLES[back-double-quoted-argument]:=fg=cyan} +: ${ZSH_HIGHLIGHT_STYLES[back-dollar-quoted-argument]:=fg=cyan} +: ${ZSH_HIGHLIGHT_STYLES[assign]:=none} +: ${ZSH_HIGHLIGHT_STYLES[redirection]:=fg=yellow} +: ${ZSH_HIGHLIGHT_STYLES[comment]:=fg=black,bold} +: ${ZSH_HIGHLIGHT_STYLES[named-fd]:=none} +: ${ZSH_HIGHLIGHT_STYLES[numeric-fd]:=none} +: ${ZSH_HIGHLIGHT_STYLES[arg0]:=fg=green} + +# Whether the highlighter should be called or not. +_zsh_highlight_highlighter_main_predicate() +{ + # may need to remove path_prefix highlighting when the line ends + [[ $WIDGET == zle-line-finish ]] || _zsh_highlight_buffer_modified +} + +# Helper to deal with tokens crossing line boundaries. +_zsh_highlight_main_add_region_highlight() { + integer start=$1 end=$2 + shift 2 + + if (( $#in_alias )); then + [[ $1 == unknown-token ]] && alias_style=unknown-token + return + fi + if (( in_param )); then + if [[ $1 == unknown-token ]]; then + param_style=unknown-token + fi + if [[ -n $param_style ]]; then + return + fi + param_style=$1 + return + fi + + # The calculation was relative to $buf but region_highlight is relative to $BUFFER. + (( start += buf_offset )) + (( end += buf_offset )) + + list_highlights+=($start $end $1) +} + +_zsh_highlight_main_add_many_region_highlights() { + for 1 2 3; do + _zsh_highlight_main_add_region_highlight $1 $2 $3 + done +} + +_zsh_highlight_main_calculate_fallback() { + local -A fallback_of; fallback_of=( + alias arg0 + suffix-alias arg0 + global-alias dollar-double-quoted-argument + builtin arg0 + function arg0 + command arg0 + precommand arg0 + hashed-command arg0 + autodirectory arg0 + arg0_\* arg0 + + # TODO: Maybe these? — + # named-fd file-descriptor + # numeric-fd file-descriptor + + path_prefix path + # The path separator fallback won't ever be used, due to the optimisation + # in _zsh_highlight_main_highlighter_highlight_path_separators(). + path_pathseparator path + path_prefix_pathseparator path_prefix + + single-quoted-argument{-unclosed,} + double-quoted-argument{-unclosed,} + dollar-quoted-argument{-unclosed,} + back-quoted-argument{-unclosed,} + + command-substitution{-quoted,,-unquoted,} + command-substitution-delimiter{-quoted,,-unquoted,} + + command-substitution{-delimiter,} + process-substitution{-delimiter,} + back-quoted-argument{-delimiter,} + ) + local needle=$1 value + reply=($1) + while [[ -n ${value::=$fallback_of[(k)$needle]} ]]; do + unset "fallback_of[$needle]" # paranoia against infinite loops + reply+=($value) + needle=$value + done +} + +# Get the type of a command. +# +# Uses the zsh/parameter module if available to avoid forks, and a +# wrapper around 'type -w' as fallback. +# +# If $2 is 0, do not consider aliases. +# +# The result will be stored in REPLY. +_zsh_highlight_main__type() { + integer -r aliases_allowed=${2-1} + # We won't cache replies of anything that exists as an alias at all, to + # ensure the cached value is correct regardless of $aliases_allowed. + # + # ### We probably _should_ cache them in a cache that's keyed on the value of + # ### $aliases_allowed, on the assumption that aliases are the common case. + integer may_cache=1 + + # Cache lookup + if (( $+_zsh_highlight_main__command_type_cache )); then + REPLY=$_zsh_highlight_main__command_type_cache[(e)$1] + if [[ -n "$REPLY" ]]; then + return + fi + fi + + # Main logic + if (( $#options_to_set )); then + setopt localoptions $options_to_set; + fi + unset REPLY + if zmodload -e zsh/parameter; then + if (( $+aliases[(e)$1] )); then + may_cache=0 + fi + if (( ${+galiases[(e)$1]} )) && (( aliases_allowed )); then + REPLY='global alias' + elif (( $+aliases[(e)$1] )) && (( aliases_allowed )); then + REPLY=alias + elif [[ $1 == *.* && -n ${1%.*} ]] && (( $+saliases[(e)${1##*.}] )); then + REPLY='suffix alias' + elif (( $reswords[(Ie)$1] )); then + REPLY=reserved + elif (( $+functions[(e)$1] )); then + REPLY=function + elif (( $+builtins[(e)$1] )); then + REPLY=builtin + elif (( $+commands[(e)$1] )); then + REPLY=command + # None of the special hashes had a match, so fall back to 'type -w', for + # forward compatibility with future versions of zsh that may add new command + # types. + # + # zsh 5.2 and older have a bug whereby running 'type -w ./sudo' implicitly + # runs 'hash ./sudo=/usr/local/bin/./sudo' (assuming /usr/local/bin/sudo + # exists and is in $PATH). Avoid triggering the bug, at the expense of + # falling through to the $() below, incurring a fork. (Issue #354.) + # + # The first disjunct mimics the isrelative() C call from the zsh bug. + elif { [[ $1 != */* ]] || is-at-least 5.3 } && + # Add a subshell to avoid a zsh upstream bug; see issue #606. + # ### Remove the subshell when we stop supporting zsh 5.7.1 (I assume 5.8 will have the bugfix). + ! (builtin type -w -- "$1") >/dev/null 2>&1; then + REPLY=none + fi + fi + if ! (( $+REPLY )); then + # zsh/parameter not available or had no matches. + # + # Note that 'type -w' will run 'rehash' implicitly. + # + # We 'unalias' in a subshell, so the parent shell is not affected. + # + # The colon command is there just to avoid a command substitution that + # starts with an arithmetic expression [«((…))» as the first thing inside + # «$(…)»], which is area that has had some parsing bugs before 5.6 + # (approximately). + REPLY="${$(:; (( aliases_allowed )) || unalias -- "$1" 2>/dev/null; LC_ALL=C builtin type -w -- "$1" 2>/dev/null)##*: }" + if [[ $REPLY == 'alias' ]]; then + may_cache=0 + fi + fi + + # Cache population + if (( may_cache )) && (( $+_zsh_highlight_main__command_type_cache )); then + _zsh_highlight_main__command_type_cache[(e)$1]=$REPLY + fi + [[ -n $REPLY ]] + return $? +} + +# Checks whether $1 is something that can be run. +# +# Return 0 if runnable, 1 if not runnable, 2 if trouble. +_zsh_highlight_main__is_runnable() { + if _zsh_highlight_main__type "$1"; then + [[ $REPLY != none ]] + else + return 2 + fi +} + +# Check whether the first argument is a redirection operator token. +# Report result via the exit code. +_zsh_highlight_main__is_redirection() { + # A redirection operator token: + # - starts with an optional single-digit number; + # - is one of the tokens listed in zshmisc(1) + # - however (z) normalizes ! to | + [[ ${1#[0-9]} == (\<|\<\>|(\>|\>\>)(|\|)|\<\<(|-)|\<\<\<|\<\&|\&\<|(\>|\>\>)\&(|\|)|\&(\>|\>\>)(|\||\!)) ]] +} + +# Resolve alias. +# +# Takes a single argument. +# +# The result will be stored in REPLY. +_zsh_highlight_main__resolve_alias() { + if zmodload -e zsh/parameter; then + REPLY=${aliases[$arg]} + else + REPLY="${"$(alias -- $arg)"#*=}" + fi +} + +# Return true iff $1 is a global alias +_zsh_highlight_main__is_global_alias() { + if zmodload -e zsh/parameter; then + (( ${+galiases[$arg]} )) + elif [[ $arg == '='* ]]; then + # avoid running into «alias -L '=foo'» erroring out with 'bad assignment' + return 1 + else + alias -L -g -- "$1" >/dev/null + fi +} + +# Check that the top of $braces_stack has the expected value. If it does, set +# the style according to $2; otherwise, set style=unknown-token. +# +# $1: character expected to be at the top of $braces_stack +# $2: optional assignment to style it if matches +# return value is 0 if there is a match else 1 +_zsh_highlight_main__stack_pop() { + if [[ $braces_stack[1] == $1 ]]; then + braces_stack=${braces_stack:1} + if (( $+2 )); then + style=$2 + fi + return 0 + else + style=unknown-token + return 1 + fi +} + +# Main syntax highlighting function. +_zsh_highlight_highlighter_main_paint() +{ + setopt localoptions extendedglob + + # At the PS3 prompt and in vared, highlight nothing. + # + # (We can't check this in _zsh_highlight_highlighter_main_predicate because + # if the predicate returns false, the previous value of region_highlight + # would be reused.) + if [[ $CONTEXT == (select|vared) ]]; then + return + fi + + typeset -a ZSH_HIGHLIGHT_TOKENS_COMMANDSEPARATOR + typeset -a ZSH_HIGHLIGHT_TOKENS_CONTROL_FLOW + local -a options_to_set reply # used in callees + local REPLY + + # $flags_with_argument is a set of letters, corresponding to the option letters + # that would be followed by a colon in a getopts specification. + local flags_with_argument + # $flags_sans_argument is a set of letters, corresponding to the option letters + # that wouldn't be followed by a colon in a getopts specification. + local flags_sans_argument + # $flags_solo is a set of letters, corresponding to option letters that, if + # present, mean the precommand will not be acting as a precommand, i.e., will + # not be followed by a :start: word. + local flags_solo + # $precommand_options maps precommand name to values of $flags_with_argument, + # $flags_sans_argument, and flags_solo for that precommand, joined by a + # colon. (The value is NOT a getopt(3) spec, although it resembles one.) + # + # Currently, setting $flags_sans_argument is only important for commands that + # have a non-empty $flags_with_argument; see test-data/precommand4.zsh. + local -A precommand_options + precommand_options=( + # Precommand modifiers as of zsh 5.6.2 cf. zshmisc(1). + '-' '' + 'builtin' '' + 'command' :pvV + 'exec' a:cl + 'noglob' '' + # 'time' and 'nocorrect' shouldn't be added here; they're reserved words, not precommands. + + # miscellaneous commands + 'doas' aCu:Lns # as of OpenBSD's doas(1) dated September 4, 2016 + 'nice' n: # as of current POSIX spec + 'pkexec' '' # doesn't take short options; immune to #121 because it's usually not passed --option flags + # Not listed: -h, which has two different meanings. + 'sudo' Cgprtu:AEHPSbilns:eKkVv # as of sudo 1.8.21p2 + 'run0' ugD:h # systemd 256.6 + 'stdbuf' ioe: + 'eatmydata' '' + 'catchsegv' '' + 'nohup' '' + 'setsid' :wc + 'env' u:i + 'ionice' cn:t:pPu # util-linux 2.33.1-0.1 + 'strace' IbeaosXPpEuOS:ACdfhikqrtTvVxyDc # strace 4.26-0.2 + 'proxychains' f:q # proxychains 4.4.0 + 'torsocks' idq:upaP # Torsocks 2.3.0 + 'torify' idq:upaP # Torsocks 2.3.0 + 'ssh-agent' aEPt:csDd:k # As of OpenSSH 8.1p1 + 'tabbed' gnprtTuU:cdfhs:v # suckless-tools v44 + 'chronic' :ev # moreutils 0.62-1 + 'ifne' :n # moreutils 0.62-1 + 'grc' :se # grc - a "generic colouriser" (that's their spelling, not mine) + 'cpulimit' elp:ivz # cpulimit 0.2 + 'ktrace' fgpt:aBCcdiT + 'caffeinate' tw:dimsu # as of macOS's caffeinate(8) dated November 9, 2012 + ) + # Commands that would need to skip one positional argument: + # flock + # ssh + # _wanted (skip two) + + if [[ $zsyh_user_options[ignorebraces] == on || ${zsyh_user_options[ignoreclosebraces]:-off} == on ]]; then + local right_brace_is_recognised_everywhere=false + else + local right_brace_is_recognised_everywhere=true + fi + + if [[ $zsyh_user_options[pathdirs] == on ]]; then + options_to_set+=( PATH_DIRS ) + fi + + ZSH_HIGHLIGHT_TOKENS_COMMANDSEPARATOR=( + '|' '||' ';' '&' '&&' + $'\n' # ${(z)} returns ';' but we convert it to $'\n' + '|&' + '&!' '&|' + # ### 'case' syntax, but followed by a pattern, not by a command + # ';;' ';&' ';|' + ) + + # Tokens that, at (naively-determined) "command position", are followed by + # a de jure command position. All of these are reserved words. + ZSH_HIGHLIGHT_TOKENS_CONTROL_FLOW=( + $'\x7b' # block + $'\x28' # subshell + '()' # anonymous function + 'while' + 'until' + 'if' + 'then' + 'elif' + 'else' + 'do' + 'time' + 'coproc' + '!' # reserved word; unrelated to $histchars[1] + ) + + if (( $+X_ZSH_HIGHLIGHT_DIRS_BLACKLIST )); then + print >&2 'zsh-syntax-highlighting: X_ZSH_HIGHLIGHT_DIRS_BLACKLIST is deprecated. Please use ZSH_HIGHLIGHT_DIRS_BLACKLIST.' + ZSH_HIGHLIGHT_DIRS_BLACKLIST=($X_ZSH_HIGHLIGHT_DIRS_BLACKLIST) + unset X_ZSH_HIGHLIGHT_DIRS_BLACKLIST + fi + + _zsh_highlight_main_highlighter_highlight_list -$#PREBUFFER '' 1 "$PREBUFFER$BUFFER" + + # end is a reserved word + local start end_ style + for start end_ style in $reply; do + (( start >= end_ )) && { print -r -- >&2 "zsh-syntax-highlighting: BUG: _zsh_highlight_highlighter_main_paint: start($start) >= end($end_)"; return } + (( end_ <= 0 )) && continue + (( start < 0 )) && start=0 # having start<0 is normal with e.g. multiline strings + _zsh_highlight_main_calculate_fallback $style + _zsh_highlight_add_highlight $start $end_ $reply + done +} + +# Try to expand $1, if it's possible to do so safely. +# +# Uses two parameters from the caller: $parameter_name_pattern and $res. +# +# If expansion was done, set $reply to the expansion and return true. +# Otherwise, return false. +_zsh_highlight_main_highlighter__try_expand_parameter() +{ + local arg="$1" + unset reply + { + # ### For now, expand just '$foo' or '${foo}', possibly with braces, but with + # ### no other features of the parameter expansion syntax. (No ${(x)foo}, + # ### no ${foo[x]}, no ${foo:-x}.) + { + local -a match mbegin mend + local MATCH; integer MBEGIN MEND + local parameter_name + local -a words + if [[ $arg[1] != '$' ]]; then + return 1 + fi + if [[ ${arg[2]} == '{' ]] && [[ ${arg[-1]} == '}' ]]; then + parameter_name=${${arg:2}%?} + else + parameter_name=${arg:1} + fi + if [[ $res == none ]] && + [[ ${parameter_name} =~ ^${~parameter_name_pattern}$ ]] && + [[ ${(tP)MATCH} != *special* ]] + then + # Set $arg and update $res. + case ${(tP)MATCH} in + (*array*|*assoc*) + words=( ${(P)MATCH} ) + ;; + ("") + # not set + words=( ) + ;; + (*) + # scalar, presumably + if [[ $zsyh_user_options[shwordsplit] == on ]]; then + words=( ${(P)=MATCH} ) + else + words=( ${(P)MATCH} ) + fi + ;; + esac + reply=( "${words[@]}" ) + else + return 1 + fi + } + } +} + +# $1 is the offset of $4 from the parent buffer. Added to the returned highlights. +# $2 is the initial braces_stack (for a closing paren). +# $3 is 1 if $4 contains the end of $BUFFER, else 0. +# $4 is the buffer to highlight. +# Returns: +# $REPLY: $buf[REPLY] is the last character parsed. +# $reply is an array of region_highlight additions. +# exit code is 0 if the braces_stack is empty, 1 otherwise. +_zsh_highlight_main_highlighter_highlight_list() +{ + integer start_pos end_pos=0 buf_offset=$1 has_end=$3 + # alias_style is the style to apply to an alias once $#in_alias == 0 + # Usually 'alias' but set to 'unknown-token' if any word expanded from + # the alias would be highlighted as unknown-token + # param_style is analogous for parameter expansions + local alias_style param_style last_arg arg buf=$4 highlight_glob=true saw_assignment=false style + local in_array_assignment=false # true between 'a=(' and the matching ')' + # in_alias is an array of integers with each element equal to the number + # of shifts needed until arg=args[1] pops an arg from the next level up + # alias or from BUFFER. + # in_param is analogous for parameter expansions + integer in_param=0 len=$#buf + local -a in_alias match mbegin mend list_highlights + # seen_alias is a map of aliases already seen to avoid loops like alias a=b b=a + local -A seen_alias + # Pattern for parameter names + readonly parameter_name_pattern='([A-Za-z_][A-Za-z0-9_]*|[0-9]+)' + list_highlights=() + + # "R" for round + # "Q" for square + # "Y" for curly + # "T" for [[ ]] + # "S" for $( ), =( ), <( ), >( ) + # "D" for do/done + # "$" for 'end' (matches 'foreach' always; also used with cshjunkiequotes in repeat/while) + # "?" for 'if'/'fi'; also checked by 'elif'/'else' + # ":" for 'then' + local braces_stack=$2 + + # State machine + # + # The states are: + # - :start: Command word + # - :start_of_pipeline: Start of a 'pipeline' as defined in zshmisc(1). + # Only valid when :start: is present + # - :sudo_opt: A leading-dash option to a precommand, whether it takes an + # argument or not. (Example: sudo's "-u" or "-i".) + # - :sudo_arg: The argument to a precommand's leading-dash option, + # when given as a separate word; i.e., "foo" in "-u foo" (two + # words) but not in "-ufoo" (one word). + # Note: :sudo_opt: and :sudo_arg: are used for any precommand + # declared in ${precommand_options}, not just for sudo(8). + # The naming is historical. + # - :regular: "Not a command word", and command delimiters are permitted. + # Mainly used to detect premature termination of commands. + # - :always: The word 'always' in the «{ foo } always { bar }» syntax. + # + # When the kind of a word is not yet known, $this_word / $next_word may contain + # multiple states. For example, after "sudo -i", the next word may be either + # another --flag or a command name, hence the state would include both ':start:' + # and ':sudo_opt:'. + # + # The tokens are always added with both leading and trailing colons to serve as + # word delimiters (an improvised array); [[ $x == *':foo:'* ]] and x=${x//:foo:/} + # will DTRT regardless of how many elements or repetitions $x has. + # + # Handling of redirections: upon seeing a redirection token, we must stall + # the current state --- that is, the value of $this_word --- for two iterations + # (one for the redirection operator, one for the word following it representing + # the redirection target). Therefore, we set $in_redirection to 2 upon seeing a + # redirection operator, decrement it each iteration, and stall the current state + # when it is non-zero. Thus, upon reaching the next word (the one that follows + # the redirection operator and target), $this_word will still contain values + # appropriate for the word immediately following the word that preceded the + # redirection operator. + # + # The "the previous word was a redirection operator" state is not communicated + # to the next iteration via $next_word/$this_word as usual, but via + # $in_redirection. The value of $next_word from the iteration that processed + # the operator is discarded. + # + # $in_redirection is currently used for: + # - comments + # - aliases + # - redirections + # - parameter elision in command position + # - 'repeat' loops + # + local this_word next_word=':start::start_of_pipeline:' + integer in_redirection + # Processing buffer + local proc_buf="$buf" + local -a args + if [[ $zsyh_user_options[interactivecomments] == on ]]; then + args=(${(zZ+c+)buf}) + else + args=(${(z)buf}) + fi + + # Special case: $(<*) isn't globbing. + if [[ $braces_stack == 'S' ]] && (( $+args[3] && ! $+args[4] )) && [[ $args[3] == $'\x29' ]] && + [[ $args[1] == *'<'* ]] && _zsh_highlight_main__is_redirection $args[1]; then + highlight_glob=false + fi + + while (( $#args )); do + last_arg=$arg + arg=$args[1] + shift args + if (( $#in_alias )); then + (( in_alias[1]-- )) + # Remove leading 0 entries + in_alias=($in_alias[$in_alias[(i)<1->],-1]) + if (( $#in_alias == 0 )); then + seen_alias=() + # start_pos and end_pos are of the alias (previous $arg) here + _zsh_highlight_main_add_region_highlight $start_pos $end_pos $alias_style + else + # We can't unset keys that contain special characters (] \ and some others). + # More details: https://www.zsh.org/workers/43269 + (){ + local alias_name + for alias_name in ${(k)seen_alias[(R)<$#in_alias->]}; do + seen_alias=("${(@kv)seen_alias[(I)^$alias_name]}") + done + } + fi + fi + if (( in_param )); then + (( in_param-- )) + if (( in_param == 0 )); then + # start_pos and end_pos are of the '$foo' word (previous $arg) here + _zsh_highlight_main_add_region_highlight $start_pos $end_pos $param_style + param_style="" + fi + fi + + # Initialize this_word and next_word. + if (( in_redirection == 0 )); then + this_word=$next_word + next_word=':regular:' + elif (( !in_param )); then + # Stall $next_word. + (( --in_redirection )) + fi + + # Initialize per-"simple command" [zshmisc(1)] variables: + # + # $style how to highlight $arg + # $in_array_assignment boolean flag for "between '(' and ')' of array assignment" + # $highlight_glob boolean flag for "'noglob' is in effect" + # $saw_assignment boolean flag for "was preceded by an assignment" + # + style=unknown-token + if [[ $this_word == *':start:'* ]]; then + in_array_assignment=false + if [[ $arg == 'noglob' ]]; then + highlight_glob=false + fi + fi + + if (( $#in_alias == 0 && in_param == 0 )); then + # Compute the new $start_pos and $end_pos, skipping over whitespace in $buf. + [[ "$proc_buf" = (#b)(#s)(''([ $'\t']|[\\]$'\n')#)(?|)* ]] + # The first, outer parenthesis + integer offset="${#match[1]}" + (( start_pos = end_pos + offset )) + (( end_pos = start_pos + $#arg )) + + # The zsh lexer considers ';' and newline to be the same token, so + # ${(z)} converts all newlines to semicolons. Convert them back here to + # make later processing simpler. + [[ $arg == ';' && ${match[3]} == $'\n' ]] && arg=$'\n' + + # Compute the new $proc_buf. We advance it + # (chop off characters from the beginning) + # beyond what end_pos points to, by skipping + # as many characters as end_pos was advanced. + # + # end_pos was advanced by $offset (via start_pos) + # and by $#arg. Note the `start_pos=$end_pos` + # below. + # + # As for the [,len]. We could use [,len-start_pos+offset] + # here, but to make it easier on eyes, we use len and + # rely on the fact that Zsh simply handles that. The + # length of proc_buf is len-start_pos+offset because + # we're chopping it to match current start_pos, so its + # length matches the previous value of start_pos. + # + # Why [,-1] is slower than [,length] isn't clear. + proc_buf="${proc_buf[offset + $#arg + 1,len]}" + fi + + # Handle the INTERACTIVE_COMMENTS option. + # + # We use the (Z+c+) flag so the entire comment is presented as one token in $arg. + if [[ $zsyh_user_options[interactivecomments] == on && $arg[1] == $histchars[3] ]]; then + if [[ $this_word == *(':regular:'|':start:')* ]]; then + style=comment + else + style=unknown-token # prematurely terminated + fi + _zsh_highlight_main_add_region_highlight $start_pos $end_pos $style + # Stall this arg + in_redirection=1 + continue + fi + + if [[ $this_word == *':start:'* ]] && ! (( in_redirection )); then + # Expand aliases. + # An alias is ineligible for expansion while it's being expanded (see #652/#653). + _zsh_highlight_main__type "$arg" "$(( ! ${+seen_alias[$arg]} ))" + local res="$REPLY" + if [[ $res == "alias" ]]; then + # Mark insane aliases as unknown-token (cf. #263). + if [[ $arg == ?*=* ]]; then + _zsh_highlight_main_add_region_highlight $start_pos $end_pos unknown-token + continue + fi + seen_alias[$arg]=$#in_alias + _zsh_highlight_main__resolve_alias $arg + local -a alias_args + # Elision is desired in case alias x='' + if [[ $zsyh_user_options[interactivecomments] == on ]]; then + alias_args=(${(zZ+c+)REPLY}) + else + alias_args=(${(z)REPLY}) + fi + args=( $alias_args $args ) + if (( $#in_alias == 0 )); then + alias_style=alias + else + # Transfer the count of this arg to the new element about to be appended. + (( in_alias[1]-- )) + fi + # Add one because we will in_alias[1]-- on the next loop iteration so + # this iteration should be considered in in_alias as well + in_alias=( $(($#alias_args + 1)) $in_alias ) + (( in_redirection++ )) # Stall this arg + continue + else + _zsh_highlight_main_highlighter_expand_path $arg + _zsh_highlight_main__type "$REPLY" 0 + res="$REPLY" + fi + fi + + # Analyse the current word. + if _zsh_highlight_main__is_redirection $arg ; then + if (( in_redirection == 1 )); then + # Two consecutive redirection operators is an error. + _zsh_highlight_main_add_region_highlight $start_pos $end_pos unknown-token + else + in_redirection=2 + _zsh_highlight_main_add_region_highlight $start_pos $end_pos redirection + fi + continue + elif [[ $arg == '{'${~parameter_name_pattern}'}' ]] && _zsh_highlight_main__is_redirection $args[1]; then + # named file descriptor: {foo}>&2 + in_redirection=3 + _zsh_highlight_main_add_region_highlight $start_pos $end_pos named-fd + continue + fi + + # Expand parameters. + if (( ! in_param )) && _zsh_highlight_main_highlighter__try_expand_parameter "$arg"; then + # That's not entirely correct --- if the parameter's value happens to be a reserved + # word, the parameter expansion will be highlighted as a reserved word --- but that + # incorrectness is outweighed by the usability improvement of permitting the use of + # parameters that refer to commands, functions, and builtins. + () { + local -a words; words=( "${reply[@]}" ) + if (( $#words == 0 )) && (( ! in_redirection )); then + # Parameter elision is happening + (( ++in_redirection )) + _zsh_highlight_main_add_region_highlight $start_pos $end_pos comment + continue + else + (( in_param = 1 + $#words )) + args=( $words $args ) + arg=$args[1] + _zsh_highlight_main__type "$arg" 0 + res=$REPLY + fi + } + fi + + # Parse the sudo command line + if (( ! in_redirection )); then + if [[ $this_word == *':sudo_opt:'* ]]; then + if [[ -n $flags_with_argument ]] && + { + # Trenary + if [[ -n $flags_sans_argument ]] + then [[ $arg == '-'[$flags_sans_argument]#[$flags_with_argument] ]] + else [[ $arg == '-'[$flags_with_argument] ]] + fi + } then + # Flag that requires an argument + this_word=${this_word//:start:/} + next_word=':sudo_arg:' + elif [[ -n $flags_with_argument ]] && + { + # Trenary + if [[ -n $flags_sans_argument ]] + then [[ $arg == '-'[$flags_sans_argument]#[$flags_with_argument]* ]] + else [[ $arg == '-'[$flags_with_argument]* ]] + fi + } then + # Argument attached in the same word + this_word=${this_word//:start:/} + next_word+=':start:' + next_word+=':sudo_opt:' + elif [[ -n $flags_sans_argument ]] && + [[ $arg == '-'[$flags_sans_argument]# ]]; then + # Flag that requires no argument + this_word=':sudo_opt:' + next_word+=':start:' + next_word+=':sudo_opt:' + elif [[ -n $flags_solo ]] && + { + # Trenary + if [[ -n $flags_sans_argument ]] + then [[ $arg == '-'[$flags_sans_argument]#[$flags_solo]* ]] + else [[ $arg == '-'[$flags_solo]* ]] + fi + } then + # Solo flags + this_word=':sudo_opt:' + next_word=':regular:' # no :start:, nor :sudo_opt: since we don't know whether the solo flag takes an argument or not + elif [[ $arg == '-'* ]]; then + # Unknown flag. We don't know whether it takes an argument or not, + # so modify $next_word as we do for flags that require no argument. + # With that behaviour, if the flag in fact takes no argument we'll + # highlight the inner command word correctly, and if it does take an + # argument we'll highlight the command word correctly if the argument + # was given in the same shell word as the flag (as in '-uphy1729' or + # '--user=phy1729' without spaces). + this_word=':sudo_opt:' + next_word+=':start:' + next_word+=':sudo_opt:' + else + # Not an option flag; nothing to do. (If the command line is + # syntactically valid, ${this_word//:sudo_opt:/} should be + # non-empty now.) + this_word=${this_word//:sudo_opt:/} + fi + elif [[ $this_word == *':sudo_arg:'* ]]; then + next_word+=':sudo_opt:' + next_word+=':start:' + fi + fi + + # The Great Fork: is this a command word? Is this a non-command word? + if [[ -n ${(M)ZSH_HIGHLIGHT_TOKENS_COMMANDSEPARATOR:#"$arg"} ]] && + [[ $braces_stack != *T* || $arg != ('||'|'&&') ]]; then + + # First, determine the style of the command separator itself. + if _zsh_highlight_main__stack_pop T || _zsh_highlight_main__stack_pop Q; then + # Missing closing square bracket(s) + style=unknown-token + elif $in_array_assignment; then + case $arg in + # Literal newlines are just fine. + ($'\n') style=commandseparator;; + # Semicolons are parsed the same way as literal newlines. Nevertheless, + # highlight them as errors since they're probably unintended. Compare + # issue #691. + (';') style=unknown-token;; + # Other command separators aren't allowed. + (*) style=unknown-token;; + esac + elif [[ $this_word == *':regular:'* ]]; then + style=commandseparator + elif [[ $this_word == *':start:'* ]] && [[ $arg == $'\n' ]]; then + style=commandseparator + elif [[ $this_word == *':start:'* ]] && [[ $arg == ';' ]] && (( $#in_alias )); then + style=commandseparator + else + # Empty commands (semicolon follows nothing) are valid syntax. + # However, in interactive use they are likely to be erroneous; + # therefore, we highlight them as errors. + # + # Alias definitions are exempted from this check to allow multiline aliases + # with explicit (redundant) semicolons: «alias foo=$'bar;\nbaz'» (issue #677). + # + # See also #691 about possibly changing the style used here. + style=unknown-token + fi + + # Second, determine the style of next_word. + if [[ $arg == $'\n' ]] && $in_array_assignment; then + # literal newline inside an array assignment + next_word=':regular:' + elif [[ $arg == ';' ]] && $in_array_assignment; then + # literal semicolon inside an array assignment + next_word=':regular:' + else + next_word=':start:' + highlight_glob=true + saw_assignment=false + (){ + local alias_name + for alias_name in ${(k)seen_alias[(R)<$#in_alias->]}; do + # We can't unset keys that contain special characters (] \ and some others). + # More details: https://www.zsh.org/workers/43269 + seen_alias=("${(@kv)seen_alias[(I)^$alias_name]}") + done + } + if [[ $arg != '|' && $arg != '|&' ]]; then + next_word+=':start_of_pipeline:' + fi + fi + + elif ! (( in_redirection)) && [[ $this_word == *':always:'* && $arg == 'always' ]]; then + # try-always construct + style=reserved-word # de facto a reserved word, although not de jure + highlight_glob=true + saw_assignment=false + next_word=':start::start_of_pipeline:' # only left brace is allowed, apparently + elif ! (( in_redirection)) && [[ $this_word == *':start:'* ]]; then # $arg is the command word + if (( ${+precommand_options[$arg]} )) && _zsh_highlight_main__is_runnable $arg; then + style=precommand + () { + set -- "${(@s.:.)precommand_options[$arg]}" + flags_with_argument=$1 + flags_sans_argument=$2 + flags_solo=$3 + } + next_word=${next_word//:regular:/} + next_word+=':sudo_opt:' + next_word+=':start:' + if [[ $arg == 'exec' || $arg == 'env' ]]; then + # To allow "exec 2>&1;" and "env | grep" where there's no command word + next_word+=':regular:' + fi + else + case $res in + (reserved) # reserved word + style=reserved-word + # Match braces and handle special cases. + case $arg in + (time|nocorrect) + next_word=${next_word//:regular:/} + next_word+=':start:' + ;; + ($'\x7b') + braces_stack='Y'"$braces_stack" + ;; + ($'\x7d') + # We're at command word, so no need to check $right_brace_is_recognised_everywhere + _zsh_highlight_main__stack_pop 'Y' reserved-word + if [[ $style == reserved-word ]]; then + next_word+=':always:' + fi + ;; + ($'\x5b\x5b') + braces_stack='T'"$braces_stack" + ;; + ('do') + braces_stack='D'"$braces_stack" + ;; + ('done') + _zsh_highlight_main__stack_pop 'D' reserved-word + ;; + ('if') + braces_stack=':?'"$braces_stack" + ;; + ('then') + _zsh_highlight_main__stack_pop ':' reserved-word + ;; + ('elif') + if [[ ${braces_stack[1]} == '?' ]]; then + braces_stack=':'"$braces_stack" + else + style=unknown-token + fi + ;; + ('else') + if [[ ${braces_stack[1]} == '?' ]]; then + : + else + style=unknown-token + fi + ;; + ('fi') + _zsh_highlight_main__stack_pop '?' + ;; + ('foreach') + braces_stack='$'"$braces_stack" + ;; + ('end') + _zsh_highlight_main__stack_pop '$' reserved-word + ;; + ('repeat') + # skip the repeat-count word + in_redirection=2 + # The redirection mechanism assumes $this_word describes the word + # following the redirection. Make it so. + # + # That word can be a command word with shortloops (`repeat 2 ls`) + # or a command separator (`repeat 2; ls` or `repeat 2; do ls; done`). + # + # The repeat-count word will be handled like a redirection target. + this_word=':start::regular:' + ;; + ('!') + if [[ $this_word != *':start_of_pipeline:'* ]]; then + style=unknown-token + else + # '!' reserved word at start of pipeline; style already set above + fi + ;; + esac + if $saw_assignment && [[ $style != unknown-token ]]; then + style=unknown-token + fi + ;; + ('suffix alias') + style=suffix-alias + ;; + ('global alias') + style=global-alias + ;; + (alias) :;; + (builtin) style=builtin + [[ $arg == $'\x5b' ]] && braces_stack='Q'"$braces_stack" + ;; + (function) style=function;; + (command) style=command;; + (hashed) style=hashed-command;; + (none) if (( ! in_param )) && _zsh_highlight_main_highlighter_check_assign; then + _zsh_highlight_main_add_region_highlight $start_pos $end_pos assign + local i=$(( arg[(i)=] + 1 )) + saw_assignment=true + if [[ $arg[i] == '(' ]]; then + in_array_assignment=true + _zsh_highlight_main_add_region_highlight start_pos+i-1 start_pos+i reserved-word + else + # assignment to a scalar parameter. + # (For array assignments, the command doesn't start until the ")" token.) + # + # Discard :start_of_pipeline:, if present, as '!' is not valid + # after assignments. + next_word+=':start:' + if (( i <= $#arg )); then + () { + local highlight_glob=false + [[ $zsyh_user_options[globassign] == on ]] && highlight_glob=true + _zsh_highlight_main_highlighter_highlight_argument $i + } + fi + fi + continue + elif (( ! in_param )) && + [[ $arg[0,1] = $histchars[0,1] ]] && (( $#arg[0,2] == 2 )); then + style=history-expansion + elif (( ! in_param )) && + [[ $arg[0,1] == $histchars[2,2] ]]; then + style=history-expansion + elif (( ! in_param )) && + ! $saw_assignment && + [[ $arg[1,2] == '((' ]]; then + # Arithmetic evaluation. + # + # Note: prior to zsh-5.1.1-52-g4bed2cf (workers/36669), the ${(z)...} + # splitter would only output the '((' token if the matching '))' had + # been typed. Therefore, under those versions of zsh, BUFFER="(( 42" + # would be highlighted as an error until the matching "))" are typed. + # + # We highlight just the opening parentheses, as a reserved word; this + # is how [[ ... ]] is highlighted, too. + _zsh_highlight_main_add_region_highlight $start_pos $((start_pos + 2)) reserved-word + if [[ $arg[-2,-1] == '))' ]]; then + _zsh_highlight_main_add_region_highlight $((end_pos - 2)) $end_pos reserved-word + fi + continue + elif (( ! in_param )) && + [[ $arg == '()' ]]; then + # anonymous function + style=reserved-word + elif (( ! in_param )) && + ! $saw_assignment && + [[ $arg == $'\x28' ]]; then + # subshell + style=reserved-word + braces_stack='R'"$braces_stack" + elif (( ! in_param )) && + [[ $arg == $'\x29' ]]; then + # end of subshell or command substitution + if _zsh_highlight_main__stack_pop 'S'; then + REPLY=$start_pos + reply=($list_highlights) + return 0 + fi + _zsh_highlight_main__stack_pop 'R' reserved-word + else + if _zsh_highlight_main_highlighter_check_path $arg 1; then + style=$REPLY + else + style=unknown-token + fi + fi + ;; + (*) _zsh_highlight_main_add_region_highlight $start_pos $end_pos arg0_$res + continue + ;; + esac + fi + if [[ -n ${(M)ZSH_HIGHLIGHT_TOKENS_CONTROL_FLOW:#"$arg"} ]]; then + next_word=':start::start_of_pipeline:' + fi + elif _zsh_highlight_main__is_global_alias "$arg"; then # $arg is a global alias that isn't in command position + style=global-alias + else # $arg is a non-command word + case $arg in + ($'\x29') + # subshell or end of array assignment + if $in_array_assignment; then + _zsh_highlight_main_add_region_highlight $start_pos $end_pos assign + _zsh_highlight_main_add_region_highlight $start_pos $end_pos reserved-word + in_array_assignment=false + next_word+=':start:' + continue + elif (( in_redirection )); then + style=unknown-token + else + if _zsh_highlight_main__stack_pop 'S'; then + REPLY=$start_pos + reply=($list_highlights) + return 0 + fi + _zsh_highlight_main__stack_pop 'R' reserved-word + fi + ;; + ($'\x28\x29') + # possibly a function definition + if (( in_redirection )) || $in_array_assignment; then + style=unknown-token + else + if [[ $zsyh_user_options[multifuncdef] == on ]] || false # TODO: or if the previous word was a command word + then + next_word+=':start::start_of_pipeline:' + fi + style=reserved-word + fi + ;; + (*) if false; then + elif [[ $arg = $'\x7d' ]] && $right_brace_is_recognised_everywhere; then + # Parsing rule: { + # + # Additionally, `tt(})' is recognized in any position if neither the + # tt(IGNORE_BRACES) option nor the tt(IGNORE_CLOSE_BRACES) option is set. + if (( in_redirection )) || $in_array_assignment; then + style=unknown-token + else + _zsh_highlight_main__stack_pop 'Y' reserved-word + if [[ $style == reserved-word ]]; then + next_word+=':always:' + fi + fi + elif [[ $arg[0,1] = $histchars[0,1] ]] && (( $#arg[0,2] == 2 )); then + style=history-expansion + elif [[ $arg == $'\x5d\x5d' ]] && _zsh_highlight_main__stack_pop 'T' reserved-word; then + : + elif [[ $arg == $'\x5d' ]] && _zsh_highlight_main__stack_pop 'Q' builtin; then + : + else + _zsh_highlight_main_highlighter_highlight_argument 1 $(( 1 != in_redirection )) + continue + fi + ;; + esac + fi + _zsh_highlight_main_add_region_highlight $start_pos $end_pos $style + done + (( $#in_alias )) && in_alias=() _zsh_highlight_main_add_region_highlight $start_pos $end_pos $alias_style + (( in_param == 1 )) && in_param=0 _zsh_highlight_main_add_region_highlight $start_pos $end_pos $param_style + [[ "$proc_buf" = (#b)(#s)(([[:space:]]|\\$'\n')#) ]] + REPLY=$(( end_pos + ${#match[1]} - 1 )) + reply=($list_highlights) + return $(( $#braces_stack > 0 )) +} + +# Check if $arg is variable assignment +_zsh_highlight_main_highlighter_check_assign() +{ + setopt localoptions extended_glob + [[ $arg == [[:alpha:]_][[:alnum:]_]#(|\[*\])(|[+])=* ]] || + [[ $arg == [0-9]##(|[+])=* ]] +} + +_zsh_highlight_main_highlighter_highlight_path_separators() +{ + local pos style_pathsep + style_pathsep=$1_pathseparator + reply=() + [[ -z "$ZSH_HIGHLIGHT_STYLES[$style_pathsep]" || "$ZSH_HIGHLIGHT_STYLES[$1]" == "$ZSH_HIGHLIGHT_STYLES[$style_pathsep]" ]] && return 0 + for (( pos = start_pos; $pos <= end_pos; pos++ )) ; do + if [[ $BUFFER[pos] == / ]]; then + reply+=($((pos - 1)) $pos $style_pathsep) + fi + done +} + +# Check if $1 is a path. +# If yes, return 0 and in $REPLY the style to use. +# Else, return non-zero (and the contents of $REPLY is undefined). +# +# $2 should be non-zero iff we're in command position. +_zsh_highlight_main_highlighter_check_path() +{ + _zsh_highlight_main_highlighter_expand_path "$1" + local expanded_path="$REPLY" tmp_path + integer in_command_position=$2 + + if [[ $zsyh_user_options[autocd] == on ]]; then + integer autocd=1 + else + integer autocd=0 + fi + + if (( in_command_position )); then + # ### Currently, this value is never returned: either it's overwritten + # ### below, or the return code is non-zero + REPLY=arg0 + else + REPLY=path + fi + + if [[ ${1[1]} == '=' && $1 == ??* && ${1[2]} != $'\x28' && $zsyh_user_options[equals] == 'on' && $expanded_path[1] != '/' ]]; then + REPLY=unknown-token # will error out if executed + return 0 + fi + + [[ -z $expanded_path ]] && return 1 + + # Check if this is a blacklisted path + if [[ $expanded_path[1] == / ]]; then + tmp_path=$expanded_path + else + tmp_path=$PWD/$expanded_path + fi + tmp_path=$tmp_path:a + + while [[ $tmp_path != / ]]; do + [[ -n ${(M)ZSH_HIGHLIGHT_DIRS_BLACKLIST:#$tmp_path} ]] && return 1 + tmp_path=$tmp_path:h + done + + if (( in_command_position )); then + if [[ -x $expanded_path ]]; then + if (( autocd )); then + if [[ -d $expanded_path ]]; then + REPLY=autodirectory + fi + return 0 + elif [[ ! -d $expanded_path ]]; then + # ### This seems unreachable for the current callers + return 0 + fi + fi + else + if [[ -L $expanded_path || -e $expanded_path ]]; then + return 0 + fi + fi + + # Search the path in CDPATH + if [[ $expanded_path != /* ]] && (( autocd || ! in_command_position )); then + # TODO: When we've dropped support for pre-5.0.6 zsh, use the *(Y1) glob qualifier here. + local cdpath_dir + for cdpath_dir in $cdpath ; do + if [[ -d "$cdpath_dir/$expanded_path" && -x "$cdpath_dir/$expanded_path" ]]; then + if (( in_command_position && autocd )); then + REPLY=autodirectory + fi + return 0 + fi + done + fi + + # If dirname($1) doesn't exist, neither does $1. + [[ ! -d ${expanded_path:h} ]] && return 1 + + # If this word ends the buffer, check if it's the prefix of a valid path. + if (( has_end && (len == end_pos) )) && + (( ! $#in_alias )) && + [[ $WIDGET != zle-line-finish ]]; then + # TODO: When we've dropped support for pre-5.0.6 zsh, use the *(Y1) glob qualifier here. + local -a tmp + if (( in_command_position )); then + # We include directories even when autocd is enabled, because those + # directories might contain executable files: e.g., BUFFER="/bi" en route + # to typing "/bin/sh". + tmp=( ${expanded_path}*(N-*,N-/) ) + else + tmp=( ${expanded_path}*(N) ) + fi + (( ${+tmp[1]} )) && REPLY=path_prefix && return 0 + fi + + # It's not a path. + return 1 +} + +# Highlight an argument and possibly special chars in quotes starting at $1 in $arg +# This command will at least highlight $1 to end_pos with the default style +# If $2 is set to 0, the argument cannot be highlighted as an option. +# +# This function currently assumes it's never called for the command word. +_zsh_highlight_main_highlighter_highlight_argument() +{ + local base_style=default i=$1 option_eligible=${2:-1} path_eligible=1 ret start style + local -a highlights + + local -a match mbegin mend + local MATCH; integer MBEGIN MEND + + case "$arg[i]" in + '%') + if [[ $arg[i+1] == '?' ]]; then + (( i += 2 )) + fi + ;; + '-') + if (( option_eligible )); then + if [[ $arg[i+1] == - ]]; then + base_style=double-hyphen-option + else + base_style=single-hyphen-option + fi + path_eligible=0 + fi + ;; + '=') + if [[ $arg[i+1] == $'\x28' ]]; then + (( i += 2 )) + _zsh_highlight_main_highlighter_highlight_list $(( start_pos + i - 1 )) S $has_end $arg[i,-1] + ret=$? + (( i += REPLY )) + highlights+=( + $(( start_pos + $1 - 1 )) $(( start_pos + i )) process-substitution + $(( start_pos + $1 - 1 )) $(( start_pos + $1 + 1 )) process-substitution-delimiter + $reply + ) + if (( ret == 0 )); then + highlights+=($(( start_pos + i - 1 )) $(( start_pos + i )) process-substitution-delimiter) + fi + fi + esac + + # This loop is a hot path. Keep it fast! + (( --i )) + while (( ++i <= $#arg )); do + i=${arg[(ib.i.)[\\\'\"\`\$\<\>\*\?]]} + case "$arg[$i]" in + "") break;; + "\\") (( i += 1 )); continue;; + "'") + _zsh_highlight_main_highlighter_highlight_single_quote $i + (( i = REPLY )) + highlights+=($reply) + ;; + '"') + _zsh_highlight_main_highlighter_highlight_double_quote $i + (( i = REPLY )) + highlights+=($reply) + ;; + '`') + _zsh_highlight_main_highlighter_highlight_backtick $i + (( i = REPLY )) + highlights+=($reply) + ;; + '$') + if [[ $arg[i+1] != "'" ]]; then + path_eligible=0 + fi + if [[ $arg[i+1] == "'" ]]; then + _zsh_highlight_main_highlighter_highlight_dollar_quote $i + (( i = REPLY )) + highlights+=($reply) + continue + elif [[ $arg[i+1] == $'\x28' ]]; then + if [[ $arg[i+2] == $'\x28' ]] && _zsh_highlight_main_highlighter_highlight_arithmetic $i; then + # Arithmetic expansion + (( i = REPLY )) + highlights+=($reply) + continue + fi + start=$i + (( i += 2 )) + _zsh_highlight_main_highlighter_highlight_list $(( start_pos + i - 1 )) S $has_end $arg[i,-1] + ret=$? + (( i += REPLY )) + highlights+=( + $(( start_pos + start - 1)) $(( start_pos + i )) command-substitution-unquoted + $(( start_pos + start - 1)) $(( start_pos + start + 1)) command-substitution-delimiter-unquoted + $reply + ) + if (( ret == 0 )); then + highlights+=($(( start_pos + i - 1)) $(( start_pos + i )) command-substitution-delimiter-unquoted) + fi + continue + fi + while [[ $arg[i+1] == [=~#+'^'] ]]; do + (( i += 1 )) + done + if [[ $arg[i+1] == [*@#?$!-] ]]; then + (( i += 1 )) + fi;; + [\<\>]) + if [[ $arg[i+1] == $'\x28' ]]; then # \x28 = open paren + start=$i + (( i += 2 )) + _zsh_highlight_main_highlighter_highlight_list $(( start_pos + i - 1 )) S $has_end $arg[i,-1] + ret=$? + (( i += REPLY )) + highlights+=( + $(( start_pos + start - 1)) $(( start_pos + i )) process-substitution + $(( start_pos + start - 1)) $(( start_pos + start + 1 )) process-substitution-delimiter + $reply + ) + if (( ret == 0 )); then + highlights+=($(( start_pos + i - 1)) $(( start_pos + i )) process-substitution-delimiter) + fi + continue + fi + ;| + *) + if $highlight_glob && + [[ $zsyh_user_options[multios] == on || $in_redirection -eq 0 ]] && + [[ ${arg[$i]} =~ ^[*?] || ${arg:$i-1} =~ ^\<[0-9]*-[0-9]*\> ]]; then + highlights+=($(( start_pos + i - 1 )) $(( start_pos + i + $#MATCH - 1)) globbing) + (( i += $#MATCH - 1 )) + path_eligible=0 + else + continue + fi + ;; + esac + done + + if (( path_eligible )); then + if (( in_redirection )) && [[ $last_arg == *['<>']['&'] && $arg[$1,-1] == (<0->|p|-) ]]; then + if [[ $arg[$1,-1] == (p|-) ]]; then + base_style=redirection + else + base_style=numeric-fd + fi + # This function is currently never called for the command word, so $2 is hard-coded as 0. + elif _zsh_highlight_main_highlighter_check_path $arg[$1,-1] 0; then + base_style=$REPLY + _zsh_highlight_main_highlighter_highlight_path_separators $base_style + highlights+=($reply) + fi + fi + + highlights=($(( start_pos + $1 - 1 )) $end_pos $base_style $highlights) + _zsh_highlight_main_add_many_region_highlights $highlights +} + +# Quote Helper Functions +# +# $arg is expected to be set to the current argument +# $start_pos is expected to be set to the start of $arg in $BUFFER +# $1 is the index in $arg which starts the quote +# $REPLY is returned as the end of quote index in $arg +# $reply is returned as an array of region_highlight additions + +# Highlight single-quoted strings +_zsh_highlight_main_highlighter_highlight_single_quote() +{ + local arg1=$1 i q=\' style + i=$arg[(ib:arg1+1:)$q] + reply=() + + if [[ $zsyh_user_options[rcquotes] == on ]]; then + while [[ $arg[i+1] == "'" ]]; do + reply+=($(( start_pos + i - 1 )) $(( start_pos + i + 1 )) rc-quote) + (( i++ )) + i=$arg[(ib:i+1:)$q] + done + fi + + if [[ $arg[i] == "'" ]]; then + style=single-quoted-argument + else + # If unclosed, i points past the end + (( i-- )) + style=single-quoted-argument-unclosed + fi + reply=($(( start_pos + arg1 - 1 )) $(( start_pos + i )) $style $reply) + REPLY=$i +} + +# Highlight special chars inside double-quoted strings +_zsh_highlight_main_highlighter_highlight_double_quote() +{ + local -a breaks match mbegin mend saved_reply + local MATCH; integer last_break=$(( start_pos + $1 - 1 )) MBEGIN MEND + local i j k ret style + reply=() + + for (( i = $1 + 1 ; i <= $#arg ; i += 1 )) ; do + (( j = i + start_pos - 1 )) + (( k = j + 1 )) + case "$arg[$i]" in + ('"') break;; + ('`') saved_reply=($reply) + _zsh_highlight_main_highlighter_highlight_backtick $i + (( i = REPLY )) + reply=($saved_reply $reply) + continue + ;; + ('$') style=dollar-double-quoted-argument + # Look for an alphanumeric parameter name. + if [[ ${arg:$i} =~ ^([A-Za-z_][A-Za-z0-9_]*|[0-9]+) ]] ; then + (( k += $#MATCH )) # highlight the parameter name + (( i += $#MATCH )) # skip past it + elif [[ ${arg:$i} =~ ^[{]([A-Za-z_][A-Za-z0-9_]*|[0-9]+)[}] ]] ; then + (( k += $#MATCH )) # highlight the parameter name and braces + (( i += $#MATCH )) # skip past it + elif [[ $arg[i+1] == '$' ]]; then + # $$ - pid + (( k += 1 )) # highlight both dollar signs + (( i += 1 )) # don't consider the second one as introducing another parameter expansion + elif [[ $arg[i+1] == [-#*@?] ]]; then + # $#, $*, $@, $?, $- - like $$ above + (( k += 1 )) # highlight both dollar signs + (( i += 1 )) # don't consider the second one as introducing another parameter expansion + elif [[ $arg[i+1] == $'\x28' ]]; then + saved_reply=($reply) + if [[ $arg[i+2] == $'\x28' ]] && _zsh_highlight_main_highlighter_highlight_arithmetic $i; then + # Arithmetic expansion + (( i = REPLY )) + reply=($saved_reply $reply) + continue + fi + + breaks+=( $last_break $(( start_pos + i - 1 )) ) + (( i += 2 )) + _zsh_highlight_main_highlighter_highlight_list $(( start_pos + i - 1 )) S $has_end $arg[i,-1] + ret=$? + (( i += REPLY )) + last_break=$(( start_pos + i )) + reply=( + $saved_reply + $j $(( start_pos + i )) command-substitution-quoted + $j $(( j + 2 )) command-substitution-delimiter-quoted + $reply + ) + if (( ret == 0 )); then + reply+=($(( start_pos + i - 1 )) $(( start_pos + i )) command-substitution-delimiter-quoted) + fi + continue + else + continue + fi + ;; + "\\") style=back-double-quoted-argument + if [[ \\\`\"\$${histchars[1]} == *$arg[$i+1]* ]]; then + (( k += 1 )) # Color following char too. + (( i += 1 )) # Skip parsing the escaped char. + else + continue + fi + ;; + ($histchars[1]) # ! - may be a history expansion + if [[ $arg[i+1] != ('='|$'\x28'|$'\x7b'|[[:blank:]]) ]]; then + style=history-expansion + else + continue + fi + ;; + *) continue ;; + + esac + reply+=($j $k $style) + done + + if [[ $arg[i] == '"' ]]; then + style=double-quoted-argument + else + # If unclosed, i points past the end + (( i-- )) + style=double-quoted-argument-unclosed + fi + (( last_break != start_pos + i )) && breaks+=( $last_break $(( start_pos + i )) ) + saved_reply=($reply) + reply=() + for 1 2 in $breaks; do + (( $1 != $2 )) && reply+=($1 $2 $style) + done + reply+=($saved_reply) + REPLY=$i +} + +# Highlight special chars inside dollar-quoted strings +_zsh_highlight_main_highlighter_highlight_dollar_quote() +{ + local -a match mbegin mend + local MATCH; integer MBEGIN MEND + local i j k style + local AA + integer c + reply=() + + for (( i = $1 + 2 ; i <= $#arg ; i += 1 )) ; do + (( j = i + start_pos - 1 )) + (( k = j + 1 )) + case "$arg[$i]" in + "'") break;; + "\\") style=back-dollar-quoted-argument + for (( c = i + 1 ; c <= $#arg ; c += 1 )); do + [[ "$arg[$c]" != ([0-9xXuUa-fA-F]) ]] && break + done + AA=$arg[$i+1,$c-1] + # Matching for HEX and OCT values like \0xA6, \xA6 or \012 + if [[ "$AA" =~ "^(x|X)[0-9a-fA-F]{1,2}" + || "$AA" =~ "^[0-7]{1,3}" + || "$AA" =~ "^u[0-9a-fA-F]{1,4}" + || "$AA" =~ "^U[0-9a-fA-F]{1,8}" + ]]; then + (( k += $#MATCH )) + (( i += $#MATCH )) + else + if (( $#arg > $i+1 )) && [[ $arg[$i+1] == [xXuU] ]]; then + # \x not followed by hex digits is probably an error + style=unknown-token + fi + (( k += 1 )) # Color following char too. + (( i += 1 )) # Skip parsing the escaped char. + fi + ;; + *) continue ;; + + esac + reply+=($j $k $style) + done + + if [[ $arg[i] == "'" ]]; then + style=dollar-quoted-argument + else + # If unclosed, i points past the end + (( i-- )) + style=dollar-quoted-argument-unclosed + fi + reply=($(( start_pos + $1 - 1 )) $(( start_pos + i )) $style $reply) + REPLY=$i +} + +# Highlight backtick substitutions +_zsh_highlight_main_highlighter_highlight_backtick() +{ + # buf is the contents of the backticks with a layer of backslashes removed. + # last is the index of arg for the start of the string to be copied into buf. + # It is either one past the beginning backtick or one past the last backslash. + # offset is a count of consumed \ (the delta between buf and arg). + # offsets is an array indexed by buf offset of when the delta between buf and arg changes. + # It is sparse, so search backwards to the last value + local buf highlight style=back-quoted-argument-unclosed style_end + local -i arg1=$1 end_ i=$1 last offset=0 start subshell_has_end=0 + local -a highlight_zone highlights offsets + reply=() + + last=$(( arg1 + 1 )) + # Remove one layer of backslashes and find the end + while i=$arg[(ib:i+1:)[\\\\\`]]; do # find the next \ or ` + if (( i > $#arg )); then + buf=$buf$arg[last,i] + offsets[i-arg1-offset]='' # So we never index past the end + (( i-- )) + subshell_has_end=$(( has_end && (start_pos + i == len) )) + break + fi + + if [[ $arg[i] == '\' ]]; then + (( i++ )) + # POSIX XCU 2.6.3 + if [[ $arg[i] == ('$'|'`'|'\') ]]; then + buf=$buf$arg[last,i-2] + (( offset++ )) + # offsets is relative to buf, so adjust by -arg1 + offsets[i-arg1-offset]=$offset + else + buf=$buf$arg[last,i-1] + fi + else # it's an unquoted ` and this is the end + style=back-quoted-argument + style_end=back-quoted-argument-delimiter + buf=$buf$arg[last,i-1] + offsets[i-arg1-offset]='' # So we never index past the end + break + fi + last=$i + done + + _zsh_highlight_main_highlighter_highlight_list 0 '' $subshell_has_end $buf + + # Munge the reply to account for removed backslashes + for start end_ highlight in $reply; do + start=$(( start_pos + arg1 + start + offsets[(Rb:start:)?*] )) + end_=$(( start_pos + arg1 + end_ + offsets[(Rb:end_:)?*] )) + highlights+=($start $end_ $highlight) + if [[ $highlight == back-quoted-argument-unclosed && $style == back-quoted-argument ]]; then + # An inner backtick command substitution is unclosed, but this level is closed + style_end=unknown-token + fi + done + + reply=( + $(( start_pos + arg1 - 1 )) $(( start_pos + i )) $style + $(( start_pos + arg1 - 1 )) $(( start_pos + arg1 )) back-quoted-argument-delimiter + $highlights + ) + if (( $#style_end )); then + reply+=($(( start_pos + i - 1)) $(( start_pos + i )) $style_end) + fi + REPLY=$i +} + +# Highlight special chars inside arithmetic expansions +_zsh_highlight_main_highlighter_highlight_arithmetic() +{ + local -a saved_reply + local style + integer i j k paren_depth ret + reply=() + + for (( i = $1 + 3 ; i <= end_pos - start_pos ; i += 1 )) ; do + (( j = i + start_pos - 1 )) + (( k = j + 1 )) + case "$arg[$i]" in + [\'\"\\@{}]) + style=unknown-token + ;; + '(') + (( paren_depth++ )) + continue + ;; + ')') + if (( paren_depth )); then + (( paren_depth-- )) + continue + fi + [[ $arg[i+1] == ')' ]] && { (( i++ )); break; } + # Special case ) at the end of the buffer to avoid flashing command substitution for a character + (( has_end && (len == k) )) && break + # This is a single paren and there are no open parens, so this isn't an arithmetic expansion + return 1 + ;; + '`') + saved_reply=($reply) + _zsh_highlight_main_highlighter_highlight_backtick $i + (( i = REPLY )) + reply=($saved_reply $reply) + continue + ;; + '$' ) + if [[ $arg[i+1] == $'\x28' ]]; then + saved_reply=($reply) + if [[ $arg[i+2] == $'\x28' ]] && _zsh_highlight_main_highlighter_highlight_arithmetic $i; then + # Arithmetic expansion + (( i = REPLY )) + reply=($saved_reply $reply) + continue + fi + + (( i += 2 )) + _zsh_highlight_main_highlighter_highlight_list $(( start_pos + i - 1 )) S $has_end $arg[i,end_pos] + ret=$? + (( i += REPLY )) + reply=( + $saved_reply + $j $(( start_pos + i )) command-substitution-quoted + $j $(( j + 2 )) command-substitution-delimiter-quoted + $reply + ) + if (( ret == 0 )); then + reply+=($(( start_pos + i - 1 )) $(( start_pos + i )) command-substitution-delimiter) + fi + continue + else + continue + fi + ;; + ($histchars[1]) # ! - may be a history expansion + if [[ $arg[i+1] != ('='|$'\x28'|$'\x7b'|[[:blank:]]) ]]; then + style=history-expansion + else + continue + fi + ;; + *) + continue + ;; + + esac + reply+=($j $k $style) + done + + if [[ $arg[i] != ')' ]]; then + # If unclosed, i points past the end + (( i-- )) + fi + style=arithmetic-expansion + reply=($(( start_pos + $1 - 1)) $(( start_pos + i )) arithmetic-expansion $reply) + REPLY=$i +} + + +# Called with a single positional argument. +# Perform filename expansion (tilde expansion) on the argument and set $REPLY to the expanded value. +# +# Does not perform filename generation (globbing). +_zsh_highlight_main_highlighter_expand_path() +{ + (( $# == 1 )) || print -r -- >&2 "zsh-syntax-highlighting: BUG: _zsh_highlight_main_highlighter_expand_path: called without argument" + + # The $~1 syntax normally performs filename generation, but not when it's on the right-hand side of ${x:=y}. + setopt localoptions nonomatch + unset REPLY + : ${REPLY:=${(Q)${~1}}} +} + +# ------------------------------------------------------------------------------------------------- +# Main highlighter initialization +# ------------------------------------------------------------------------------------------------- + +_zsh_highlight_main__precmd_hook() { + # Unset the WARN_NESTED_VAR option, taking care not to error if the option + # doesn't exist (zsh older than zsh-5.3.1-test-2). + setopt localoptions + if eval '[[ -o warnnestedvar ]]' 2>/dev/null; then + unsetopt warnnestedvar + fi + + _zsh_highlight_main__command_type_cache=() +} + +autoload -Uz add-zsh-hook +if add-zsh-hook precmd _zsh_highlight_main__precmd_hook 2>/dev/null; then + # Initialize command type cache + typeset -gA _zsh_highlight_main__command_type_cache +else + print -r -- >&2 'zsh-syntax-highlighting: Failed to load add-zsh-hook. Some speed optimizations will not be used.' + # Make sure the cache is unset + unset _zsh_highlight_main__command_type_cache +fi +typeset -ga ZSH_HIGHLIGHT_DIRS_BLACKLIST diff --git a/plugins/zsh-syntax-highlighting/highlighters/pattern/README.md b/plugins/zsh-syntax-highlighting/highlighters/pattern/README.md new file mode 120000 index 000000000..5ff00e645 --- /dev/null +++ b/plugins/zsh-syntax-highlighting/highlighters/pattern/README.md @@ -0,0 +1 @@ +../../docs/highlighters/pattern.md \ No newline at end of file diff --git a/plugins/zsh-syntax-highlighting/highlighters/pattern/pattern-highlighter.zsh b/plugins/zsh-syntax-highlighting/highlighters/pattern/pattern-highlighter.zsh new file mode 100644 index 000000000..e0422d084 --- /dev/null +++ b/plugins/zsh-syntax-highlighting/highlighters/pattern/pattern-highlighter.zsh @@ -0,0 +1,60 @@ +# ------------------------------------------------------------------------------------------------- +# Copyright (c) 2010-2011 zsh-syntax-highlighting contributors +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted +# provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this list of conditions +# and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, this list of +# conditions and the following disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the zsh-syntax-highlighting contributors nor the names of its contributors +# may be used to endorse or promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# ------------------------------------------------------------------------------------------------- +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et +# ------------------------------------------------------------------------------------------------- + + +# List of keyword and color pairs. +typeset -gA ZSH_HIGHLIGHT_PATTERNS + +# Whether the pattern highlighter should be called or not. +_zsh_highlight_highlighter_pattern_predicate() +{ + _zsh_highlight_buffer_modified +} + +# Pattern syntax highlighting function. +_zsh_highlight_highlighter_pattern_paint() +{ + setopt localoptions extendedglob + local pattern + for pattern in ${(k)ZSH_HIGHLIGHT_PATTERNS}; do + _zsh_highlight_pattern_highlighter_loop "$BUFFER" "$pattern" + done +} + +_zsh_highlight_pattern_highlighter_loop() +{ + # This does *not* do its job syntactically, sorry. + local buf="$1" pat="$2" + local -a match mbegin mend + local MATCH; integer MBEGIN MEND + if [[ "$buf" == (#b)(*)(${~pat})* ]]; then + region_highlight+=("$((mbegin[2] - 1)) $mend[2] $ZSH_HIGHLIGHT_PATTERNS[$pat], memo=zsh-syntax-highlighting") + "$0" "$match[1]" "$pat"; return $? + fi +} diff --git a/plugins/zsh-syntax-highlighting/highlighters/regexp/README.md b/plugins/zsh-syntax-highlighting/highlighters/regexp/README.md new file mode 120000 index 000000000..9f2522d51 --- /dev/null +++ b/plugins/zsh-syntax-highlighting/highlighters/regexp/README.md @@ -0,0 +1 @@ +../../docs/highlighters/regexp.md \ No newline at end of file diff --git a/plugins/zsh-syntax-highlighting/highlighters/regexp/regexp-highlighter.zsh b/plugins/zsh-syntax-highlighting/highlighters/regexp/regexp-highlighter.zsh new file mode 100644 index 000000000..0d43aacd3 --- /dev/null +++ b/plugins/zsh-syntax-highlighting/highlighters/regexp/regexp-highlighter.zsh @@ -0,0 +1,62 @@ +# ------------------------------------------------------------------------------------------------- +# Copyright (c) 2010-2016 zsh-syntax-highlighting contributors +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted +# provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this list of conditions +# and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, this list of +# conditions and the following disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the zsh-syntax-highlighting contributors nor the names of its contributors +# may be used to endorse or promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# ------------------------------------------------------------------------------------------------- +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et +# ------------------------------------------------------------------------------------------------- + + +# List of keyword and color pairs. +typeset -gA ZSH_HIGHLIGHT_REGEXP + +# Whether the pattern highlighter should be called or not. +_zsh_highlight_highlighter_regexp_predicate() +{ + _zsh_highlight_buffer_modified +} + +# Pattern syntax highlighting function. +_zsh_highlight_highlighter_regexp_paint() +{ + setopt localoptions extendedglob + local pattern + for pattern in ${(k)ZSH_HIGHLIGHT_REGEXP}; do + _zsh_highlight_regexp_highlighter_loop "$BUFFER" "$pattern" + done +} + +_zsh_highlight_regexp_highlighter_loop() +{ + local buf="$1" pat="$2" + integer OFFSET=0 + local MATCH; integer MBEGIN MEND + local -a match mbegin mend + while true; do + [[ "$buf" =~ "$pat" ]] || return; + region_highlight+=("$((MBEGIN - 1 + OFFSET)) $((MEND + OFFSET)) $ZSH_HIGHLIGHT_REGEXP[$pat], memo=zsh-syntax-highlighting") + buf="$buf[$(($MEND+1)),-1]" + OFFSET=$((MEND+OFFSET)); + done +} diff --git a/plugins/zsh-syntax-highlighting/highlighters/root/README.md b/plugins/zsh-syntax-highlighting/highlighters/root/README.md new file mode 120000 index 000000000..ca6799d6f --- /dev/null +++ b/plugins/zsh-syntax-highlighting/highlighters/root/README.md @@ -0,0 +1 @@ +../../docs/highlighters/root.md \ No newline at end of file diff --git a/plugins/zsh-syntax-highlighting/highlighters/root/root-highlighter.zsh b/plugins/zsh-syntax-highlighting/highlighters/root/root-highlighter.zsh new file mode 100644 index 000000000..3718c449a --- /dev/null +++ b/plugins/zsh-syntax-highlighting/highlighters/root/root-highlighter.zsh @@ -0,0 +1,44 @@ +# ------------------------------------------------------------------------------------------------- +# Copyright (c) 2010-2011 zsh-syntax-highlighting contributors +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted +# provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this list of conditions +# and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, this list of +# conditions and the following disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the zsh-syntax-highlighting contributors nor the names of its contributors +# may be used to endorse or promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# ------------------------------------------------------------------------------------------------- +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et +# ------------------------------------------------------------------------------------------------- + + +# Define default styles. +: ${ZSH_HIGHLIGHT_STYLES[root]:=standout} + +# Whether the root highlighter should be called or not. +_zsh_highlight_highlighter_root_predicate() +{ + _zsh_highlight_buffer_modified +} + +# root highlighting function. +_zsh_highlight_highlighter_root_paint() +{ + if (( EUID == 0 )) { _zsh_highlight_add_highlight 0 $#BUFFER root } +} diff --git a/plugins/zsh-syntax-highlighting/images/after1-smaller.png b/plugins/zsh-syntax-highlighting/images/after1-smaller.png new file mode 100644 index 0000000000000000000000000000000000000000..768294c3b3cdee1b8bb60849faad86aaa05b4ab9 GIT binary patch literal 2517 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 Gp$P!J#f60c literal 0 HcmV?d00001 diff --git a/plugins/zsh-syntax-highlighting/images/after1.png b/plugins/zsh-syntax-highlighting/images/after1.png new file mode 100644 index 0000000000000000000000000000000000000000..ea378d3efbcf363e727d6c0689702d02ebd9700a GIT binary patch literal 5842 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 literal 0 HcmV?d00001 diff --git a/plugins/zsh-syntax-highlighting/images/after2-smaller.png b/plugins/zsh-syntax-highlighting/images/after2-smaller.png new file mode 100644 index 0000000000000000000000000000000000000000..8b5b1f8f21d923c80a7d4c787e66774f4d9a170f GIT binary patch literal 4386 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 literal 0 HcmV?d00001 diff --git a/plugins/zsh-syntax-highlighting/images/after3-smaller.png b/plugins/zsh-syntax-highlighting/images/after3-smaller.png new file mode 100644 index 0000000000000000000000000000000000000000..d6aaa7bdf30aea607d98d8fa23f715534cb3bea7 GIT binary patch literal 1673 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�UI$EDE9<9C2>~#Ll>GL+*ugtryr`M(Z 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 aGSez?YuFLa`+|Xifx*+&&t;ucLK6V--rW8G literal 0 HcmV?d00001 diff --git a/plugins/zsh-syntax-highlighting/images/before1.png b/plugins/zsh-syntax-highlighting/images/before1.png new file mode 100644 index 0000000000000000000000000000000000000000..80c6f54ff5941aad6244aa9f3be3cb4d4d0311ec GIT binary patch literal 2636 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 S^S%Hz6Fpu1T-G@yGywq2gzV7( literal 0 HcmV?d00001 diff --git a/plugins/zsh-syntax-highlighting/images/before2-smaller.png b/plugins/zsh-syntax-highlighting/images/before2-smaller.png new file mode 100644 index 0000000000000000000000000000000000000000..991a716d54186b242ea0a09c13b5533f5719d6bf GIT binary patch literal 2605 zcmeAS@N?(olHy`uVBq!ia0y~yVA#ySz@W^*!oa{#TK(CYfq{V~-O<;Pfnj4m_n$;o z1_lO&WRD zmbgK;wrPfG-@#?({ajia&lUQGrBVWFE9Q6}k;r5hU3T}uqI%6{rcLub4WG&+2Mc^; ztZ@FJYg^eI=wjSB)3Hs`;@pOM>!`=Z{@VDA$o_I4_uAObkkS5c`&Sc(#!L9a>^5P7?do%RHuem zp9Jr9##vrLR(J-}DY*)s9e(l?2g>N1*M3uWUcIT>aj(63*8IQ;&(e%pPU4Nob5 znU*Kkm%HJUh@!|U8D62R^^;%ewp3-dTwn>!+7U2=YpR-wTWglu6_3hU&yqG*{hodA z;@e%$#ic&AtpR%kxGuI!X^E_y{7R!`oA%L*hdg8hS1wyIPoV3fr0wd?Q&;p%iz1DK z%Oh*+|K45c;$CZQ?{~I(+DpbO$*rf?o3}7sk~nCv?^#rWg!E5-hDaU81at4kqB23| zo-{$#1BGG}o?X|y(ZpgE=%s&;QDJ_8)#YBLq{gJUXJrq9qBfo@{KtJH@PGu@4jzjG zsy+#f=0WEwri)4!+R9xaLW=nK$oBq{zc+$`bd1bZM_Ulag)u$IM>QwJ9`F11Zo8X}}S)cTl3Hr@kZCzRNJd+uGP9+uhO{}18-_wiz@wo_r!{p=&_@}D2y9;!OWsxP9bW9_#a zpN`syO{m{@x5Rbsa?6(G5$E^rIs5nd?Wx5M8(dr7^UB$&%{;1ObH8s+vPkKltbc_M zmREiL-jv_?Y*~!ZhdF5u9KPquzucF6^Re~NQJ+-nmmPW~50*Cg@;$vCBX^-^-Or|2P}+Yyu$B`$X95eNM&oToFAzc5r1T3gU+<#)``k94(?7d zyd7gZ;Xu*7=|Oq1N1wRy60Hp8z?O*vsjvJCwO$N&6STYV}} zR=k)ox?%l{6WR_My_R98{Yp8kCC(X?ZMf!N|8~Rc+dnrRXJ7Da*0Pkpe%&!~B8y7` z9*eA8_{%A`Mejm&)Y`aL>v(hpW-T~e6{3~#m1eN)`-eB^}g68 z#P}mRG=JOc+dm6eW-459Vn4HJU+%JhnN|NUlq|fxCH$e2M$AMf;Sv|$mJ4jYB3{?K z-Tq7dJ*_{>yVRq-)X6*7w6Q*W(*$>G6R#Efysq53=;VC%O5oGXO?#XsMy-)pIL~V3 z>ey>B%R9sF$JejU09%yxO!6c9rOb08x2*0c#lG;j`}xFpg2n>&_cNcwr5W9k3s$o6 zV!3eH>i=C2@%?#+c!XZK6t@Luh1E&iQ%Kkc4qTUQT37%tq)D&KW_aR;MlMZA`s@iItIM5&HsPzMexI2odvo4VL8+ZD+(JIz zjIY;!u)ptDSzS!#yu*i8Hq2DF^sttn`t?qh-E_;}!fTeuhcjQ8B*v!}Za9nM!0Ce5 ziozUjn>(i8IJrC~$E|O33;T<6TJC@4Y!c2L5dO)rV?y;uhOZuG>UZ~9_+}q)cDDbM z>sq`(^WvMbp7ZDWjju%C{G=7O^VQmZk%yDR4{dRie`R+ztkIHZZ~XEtA>5@AlEOpC1F!gVFyGitHY>NWQRFHP3_T)*(_uMpQ;i*;N%k7jK* zd%^iMYqDj_*IeV081AWycmMrRzUo`nx}vo_IoH2GJ-+dtO^cHqi_ng~&^N_DRBdAX z!}}(kOix?FY|fDX$?(F|(~&F(&b~?KexN$jPh``n{>K#_N?UI62|w_v3O>ZPE8s{P z!yXx>R{ogWEyKx`vR<2>S@-^Q|NE-a zeNN);iseRqd0VqeQ~TeZ?%360*g@OvNUO_QmvAUQWHy38H@}J&2$Y7bd3x`3=FJ{O|6W~ pwG9lc3=AA+SyiBD$jwj5OsmAL!Qgn)5e5bZ22WQ%mvv4FO#p=IsK)>R literal 0 HcmV?d00001 diff --git a/plugins/zsh-syntax-highlighting/images/before2.png b/plugins/zsh-syntax-highlighting/images/before2.png new file mode 100644 index 0000000000000000000000000000000000000000..48cfc327a65b527fd73a8eaf144298e753e64357 GIT binary patch literal 2614 zcmeAS@N?(olHy`uVBq!ia0y~yU`%6RU~uDLU|?XlH7(JDfq{V~-O<;Pfnj4m_n$;o z1_lO&WRDI~ z{R&rBaE1szQg-z3Yi*vlV#yU1UtxI_w`kQP%^{amxQYx8uFAZ$_~H_|z{MT?4-7A= zEVoM%@zGXeJ-T?BMnZ>t-+69di(Y=(ee;g& zVaA(oIPI`)ZZ19k>4%VXOn+TXn8fZ_G~NPxz<-+_^%RD@%et*k*~}qUFTT2Q+b^6Wp(4Uk@0d12 zTaf8&9s7=)*PEOEzDnEoYsD`s!<70>Gg317lr>{5FYSEY!gT4+(T_2D3uO0;zsi4b zo-0?^p?UsaufzN=&Q^R4efxWjiSxhU&HMRGPQRV|qp-u@#b@JN`^__KBNkuUmnWa~ z|H}hr?tRX7A8WIeU)`Vg-N()J%tV{ByZ3KOv)#W)=$T>6Ry+1>XP(cyli~PNf5y3k zmu%)Oxv}t%MYZ9S2Y0j$`*k#&O#eTyKg1a%&(#xgwxpt5==%^jlF~j6=Cp_mx%ak|jOQIy08v zY;a5TlrZRin0YM3Zudm_*6(+ZMFq{(*t>l0g_@-c7A#o7w&Leh=d(p~p8wp>w(0DN z<3dl>4((5y`B%?(hV-(>?JVD>a@ug7PMK;qc?atnhPD5?Ai>)hA+mC<;c>>p9$G93 z?w#wHUd!;D>(swl6qCH8y_-=bKgVH}+APk0yFy5ddTcYmT%;*<<@@a9o#fP4rXZ$Yh zzHjo?@;Xd^{s<60yPunnPtGh`BtB0)WCdRid!%CKR6uB1L7_W_tnJm!an5(`)(0$34uzFW; zfM0nSp3G|&b09o5z(OjrX*J)^i3e)JP2L~ho^*D3*~N_IGh$6x(!ZlRFT8rM_%K>?`}EI?`${XOR|@Vfy{F2x`|iz* z(un01rYFBmG@5n&fZDC;35$Q5RL|bf&9jPQV%oOu{YLMTw|0kB%$ojiLG4YE6>oQK z{;+e(wO8*AmuTEN{WH*Y!Ony8WoH=jeC|6`b@+UfMSD|oMp4ARQyKGE3WX0YOWW=L zS$M-2<&_ql&z^tWUst-#;VE0&ew9l#9X6d8etR7CGWj03FID@W?$WKlqLp}(cJ39n z&$`i^lBeTRxcQ9J^H;Jf1eOJTTIIjtp~%hmHeaJzXU!De@l~Mw?d1L>UiA}2vz{-W z`DRnCp0`Q$^~C))3)V2+^^TTJ@omRts9E6gPdoG)j$7y z$NiAigeRc%9AY4tx>r(7a($8fY=P&$7K-lJc;Ubcx7_Bg6h^utq#$v{2J5Len@XS$7XUYBjIKxS4_@f^^+&%w_UBj z%a`ql3hTMb?d6)vB$#-&92j7q#|sOdAAq{6g<=l`nSs%~p#J}R_x?;Ls8GiLLj zec$H1$>*pho2&3+?)fuk`5d4AO^mzAEM~=O>;3cg=%k-{64TphJe_mi(fO;gG)`S8 zT6FNgr|HFwDkbNa{_>Vr{pDvI?v<+Ede}Pk+M&nA8_U;}sF@ip-mvY@=FsLV?=P%B zv0C?rVes;k_aoc2Uvp1-J8#pSd!6&EMfc{H74hZ&k5_12ULh+U^MAAO%#R-Bzbo>S z^{ngt|2aNh)OpDH$Gse@iWR)azn0$BDTpy!UFUDLY>6W>2OQrMjM z^uH8DAIC0dx4Nl|jl+KMZ)HDqLiUmHg|9}R+6yNBz1u48GV5?_mz<8c~vxSdwa$T$GwvlFDFYU}&amXrOCk5Mp3pWo&9?WUg&s lU}a$7ILoR6MMG|WN@iLmZVd*=OV0 literal 0 HcmV?d00001 diff --git a/plugins/zsh-syntax-highlighting/images/before3-smaller.png b/plugins/zsh-syntax-highlighting/images/before3-smaller.png new file mode 100644 index 0000000000000000000000000000000000000000..e525c6d4b1478af025a32e29bed9c3883d5e9d2d GIT binary patch literal 763 zcmeAS@N?(olHy`uVBq!ia0y~yU`SzLU=ZS9U|?YA_@olTz`($g?&#~tz_78O`%fY( z0|SFXvPY0F14ES>14Ba#1H&%{28MmEry73tF%YYuWfzcPQX-pLClf{}4Q&nY>o0e#Je$ceNp9ZkuNrA{coP!@jz53VWv|M^bBKRV<)wSxO5N%28-Kpq zdf>_-)}<$kdVkLdI8%|tw(#km(uo;md;$sVo3{AnGe;d#U^vpAG{H2=u4&^WdAZ^p zhx=5HemTP(nR;&O{qprD46&ELrd(ZYnp5!Wg1CSM6VIB(%cQ*)bm{+4%HG-^nBcKP|HOgny)u4>9V^+H&rI5JTcgwC7q|VKx3w*Tk*A(A zMoqA1K7Rk7l)}V%A-}^7&sh0ps4mnm2`o*U*U`KwVqL_U{dYSj6jrocos>OOAvy2x z%YV8G1``wC&aM3&cmE|b7n_~i=H|qMLe(1DwJZPTJ&Qh7AUU(X>4r|EwYVJ@rvMYV@aJrg6hM}O3O@P1*F>fT&IACcKkHVh1T zXEw|$)-8O?z`&qd;u=wsl30>zm0XmXSdz+MWMF8fYiOWrWDsItU}b7)Wo)W#U|?ln i@IsA|2}MJ0eoAIqC2kEft9R}LrD{)CKbLh*2~7a_*exXh literal 0 HcmV?d00001 diff --git a/plugins/zsh-syntax-highlighting/images/before3.png b/plugins/zsh-syntax-highlighting/images/before3.png new file mode 100644 index 0000000000000000000000000000000000000000..40c360151a09b90ea2b449b4e8f07c9a16d5837d GIT binary patch literal 1461 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 M>FVdQ&MBb@0H49S#{d8T literal 0 HcmV?d00001 diff --git a/plugins/zsh-syntax-highlighting/images/preview-smaller.png b/plugins/zsh-syntax-highlighting/images/preview-smaller.png new file mode 100644 index 0000000000000000000000000000000000000000..6fb84d18e545b56beef047eea45d7b03f9e50c05 GIT binary patch literal 5982 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 V=WLr>#=yY9;OXk;vd$@?2>^/dev/null ) + return $? + fi +} + +# ------------------------------------------------------------------------------------------------- +# Core highlighting update system +# ------------------------------------------------------------------------------------------------- + +# Use workaround for bug in ZSH? +# zsh-users/zsh@48cadf4 https://www.zsh.org/mla/workers/2017/msg00034.html +autoload -Uz is-at-least +if is-at-least 5.4; then + typeset -g zsh_highlight__pat_static_bug=false +else + typeset -g zsh_highlight__pat_static_bug=true +fi + +# Array declaring active highlighters names. +typeset -ga ZSH_HIGHLIGHT_HIGHLIGHTERS + +# Update ZLE buffer syntax highlighting. +# +# Invokes each highlighter that needs updating. +# This function is supposed to be called whenever the ZLE state changes. +_zsh_highlight() +{ + # Store the previous command return code to restore it whatever happens. + local ret=$? + # Make it read-only. Can't combine this with the previous line when POSIX_BUILTINS may be set. + typeset -r ret + + # $region_highlight should be predefined, either by zle or by the test suite's mock (non-special) array. + (( ${+region_highlight[@]} )) || { + echo >&2 'zsh-syntax-highlighting: error: $region_highlight is not defined' + echo >&2 'zsh-syntax-highlighting: (Check whether zsh-syntax-highlighting was installed according to the instructions.)' + return $ret + } + + # Probe the memo= feature, once. + (( ${+zsh_highlight__memo_feature} )) || { + region_highlight+=( " 0 0 fg=red, memo=zsh-syntax-highlighting" ) + case ${region_highlight[-1]} in + ("0 0 fg=red") + # zsh 5.8 or earlier + integer -gr zsh_highlight__memo_feature=0 + ;; + ("0 0 fg=red memo=zsh-syntax-highlighting") + # zsh 5.9 or later + integer -gr zsh_highlight__memo_feature=1 + ;; + (" 0 0 fg=red, memo=zsh-syntax-highlighting") ;& + (*) + # We can get here in two ways: + # + # 1. When not running as a widget. In that case, $region_highlight is + # not a special variable (= one with custom getter/setter functions + # written in C) but an ordinary one, so the third case pattern matches + # and we fall through to this block. (The test suite uses this codepath.) + # + # 2. When running under a future version of zsh that will have changed + # the serialization of $region_highlight elements from their underlying + # C structs, so that none of the previous case patterns will match. + # + # In either case, fall back to a version check. + if is-at-least 5.9; then + integer -gr zsh_highlight__memo_feature=1 + else + integer -gr zsh_highlight__memo_feature=0 + fi + ;; + esac + region_highlight[-1]=() + } + + # Reset region_highlight to build it from scratch + if (( zsh_highlight__memo_feature )); then + region_highlight=( "${(@)region_highlight:#*memo=zsh-syntax-highlighting*}" ) + else + # Legacy codepath. Not very interoperable with other plugins (issue #418). + region_highlight=() + fi + + # Remove all highlighting in isearch, so that only the underlining done by zsh itself remains. + # For details see FAQ entry 'Why does syntax highlighting not work while searching history?'. + # This disables highlighting during isearch (for reasons explained in README.md) unless zsh is new enough + # and doesn't have the pattern matching bug + if [[ $WIDGET == zle-isearch-update ]] && { $zsh_highlight__pat_static_bug || ! (( $+ISEARCHMATCH_ACTIVE )) }; then + return $ret + fi + + # Before we 'emulate -L', save the user's options + local -A zsyh_user_options + if zmodload -e zsh/parameter; then + zsyh_user_options=("${(kv)options[@]}") + else + local canonical_options onoff option raw_options + raw_options=(${(f)"$(emulate -R zsh; set -o)"}) + canonical_options=(${${${(M)raw_options:#*off}%% *}#no} ${${(M)raw_options:#*on}%% *}) + for option in "${canonical_options[@]}"; do + [[ -o $option ]] + case $? in + (0) zsyh_user_options+=($option on);; + (1) zsyh_user_options+=($option off);; + (*) # Can't happen, surely? + echo "zsh-syntax-highlighting: warning: '[[ -o $option ]]' returned $?" + ;; + esac + done + fi + typeset -r zsyh_user_options + + emulate -L zsh + setopt localoptions warncreateglobal nobashrematch + local REPLY # don't leak $REPLY into global scope + + # Do not highlight if there are more than 300 chars in the buffer. It's most + # likely a pasted command or a huge list of files in that case.. + [[ -n ${ZSH_HIGHLIGHT_MAXLENGTH:-} ]] && [[ $#BUFFER -gt $ZSH_HIGHLIGHT_MAXLENGTH ]] && return $ret + + # Do not highlight if there are pending inputs (copy/paste). + (( KEYS_QUEUED_COUNT > 0 )) && return $ret + (( PENDING > 0 )) && return $ret + + { + local cache_place + local -a region_highlight_copy + + # Select which highlighters in ZSH_HIGHLIGHT_HIGHLIGHTERS need to be invoked. + local highlighter; for highlighter in $ZSH_HIGHLIGHT_HIGHLIGHTERS; do + + # eval cache place for current highlighter and prepare it + cache_place="_zsh_highlight__highlighter_${highlighter}_cache" + typeset -ga ${cache_place} + + # If highlighter needs to be invoked + if ! type "_zsh_highlight_highlighter_${highlighter}_predicate" >&/dev/null; then + echo "zsh-syntax-highlighting: warning: disabling the ${(qq)highlighter} highlighter as it has not been loaded" >&2 + # TODO: use ${(b)} rather than ${(q)} if supported + ZSH_HIGHLIGHT_HIGHLIGHTERS=( ${ZSH_HIGHLIGHT_HIGHLIGHTERS:#${highlighter}} ) + elif "_zsh_highlight_highlighter_${highlighter}_predicate"; then + + # save a copy, and cleanup region_highlight + region_highlight_copy=("${region_highlight[@]}") + region_highlight=() + + # Execute highlighter and save result + { + "_zsh_highlight_highlighter_${highlighter}_paint" + } always { + : ${(AP)cache_place::="${region_highlight[@]}"} + } + + # Restore saved region_highlight + region_highlight=("${region_highlight_copy[@]}") + + fi + + # Use value form cache if any cached + region_highlight+=("${(@P)cache_place}") + + done + + # Re-apply zle_highlight settings + + # region + () { + (( REGION_ACTIVE )) || return + integer min max + if (( MARK > CURSOR )) ; then + min=$CURSOR max=$MARK + else + min=$MARK max=$CURSOR + fi + if (( REGION_ACTIVE == 1 )); then + [[ $KEYMAP = vicmd ]] && (( max++ )) + elif (( REGION_ACTIVE == 2 )); then + local needle=$'\n' + # CURSOR and MARK are 0 indexed between letters like region_highlight + # Do not include the newline in the highlight + (( min = ${BUFFER[(Ib:min:)$needle]} )) + (( max = ${BUFFER[(ib:max:)$needle]} - 1 )) + fi + _zsh_highlight_apply_zle_highlight region standout "$min" "$max" + } + + # yank / paste (zsh-5.1.1 and newer) + (( $+YANK_ACTIVE )) && (( YANK_ACTIVE )) && _zsh_highlight_apply_zle_highlight paste standout "$YANK_START" "$YANK_END" + + # isearch + (( $+ISEARCHMATCH_ACTIVE )) && (( ISEARCHMATCH_ACTIVE )) && _zsh_highlight_apply_zle_highlight isearch underline "$ISEARCHMATCH_START" "$ISEARCHMATCH_END" + + # suffix + (( $+SUFFIX_ACTIVE )) && (( SUFFIX_ACTIVE )) && _zsh_highlight_apply_zle_highlight suffix bold "$SUFFIX_START" "$SUFFIX_END" + + + return $ret + + + } always { + typeset -g _ZSH_HIGHLIGHT_PRIOR_BUFFER="$BUFFER" + typeset -gi _ZSH_HIGHLIGHT_PRIOR_CURSOR=$CURSOR + } +} + +# Apply highlighting based on entries in the zle_highlight array. +# This function takes four arguments: +# 1. The exact entry (no patterns) in the zle_highlight array: +# region, paste, isearch, or suffix +# 2. The default highlighting that should be applied if the entry is unset +# 3. and 4. Two integer values describing the beginning and end of the +# range. The order does not matter. +_zsh_highlight_apply_zle_highlight() { + local entry="$1" default="$2" + integer first="$3" second="$4" + + # read the relevant entry from zle_highlight + # + # ### In zsh≥5.0.8 we'd use ${(b)entry}, but we support older zsh's, so we don't + # ### add (b). The only effect is on the failure mode for callers that violate + # ### the precondition. + local region="${zle_highlight[(r)${entry}:*]-}" + + if [[ -z "$region" ]]; then + # entry not specified at all, use default value + region=$default + else + # strip prefix + region="${region#${entry}:}" + + # no highlighting when set to the empty string or to 'none' + if [[ -z "$region" ]] || [[ "$region" == none ]]; then + return + fi + fi + + integer start end + if (( first < second )); then + start=$first end=$second + else + start=$second end=$first + fi + region_highlight+=("$start $end $region, memo=zsh-syntax-highlighting") +} + + +# ------------------------------------------------------------------------------------------------- +# API/utility functions for highlighters +# ------------------------------------------------------------------------------------------------- + +# Array used by highlighters to declare user overridable styles. +typeset -gA ZSH_HIGHLIGHT_STYLES + +# Whether the command line buffer has been modified or not. +# +# Returns 0 if the buffer has changed since _zsh_highlight was last called. +_zsh_highlight_buffer_modified() +{ + [[ "${_ZSH_HIGHLIGHT_PRIOR_BUFFER:-}" != "$BUFFER" ]] +} + +# Whether the cursor has moved or not. +# +# Returns 0 if the cursor has moved since _zsh_highlight was last called. +_zsh_highlight_cursor_moved() +{ + [[ -n $CURSOR ]] && [[ -n ${_ZSH_HIGHLIGHT_PRIOR_CURSOR-} ]] && (($_ZSH_HIGHLIGHT_PRIOR_CURSOR != $CURSOR)) +} + +# Add a highlight defined by ZSH_HIGHLIGHT_STYLES. +# +# Should be used by all highlighters aside from 'pattern' (cf. ZSH_HIGHLIGHT_PATTERN). +# Overwritten in tests/test-highlighting.zsh when testing. +_zsh_highlight_add_highlight() +{ + local -i start end + local highlight + start=$1 + end=$2 + shift 2 + for highlight; do + if (( $+ZSH_HIGHLIGHT_STYLES[$highlight] )); then + region_highlight+=("$start $end $ZSH_HIGHLIGHT_STYLES[$highlight], memo=zsh-syntax-highlighting") + break + fi + done +} + +# ------------------------------------------------------------------------------------------------- +# Setup functions +# ------------------------------------------------------------------------------------------------- + +# Helper for _zsh_highlight_bind_widgets +# $1 is name of widget to call +_zsh_highlight_call_widget() +{ + builtin zle "$@" && + _zsh_highlight +} + +# Decide whether to use the zle-line-pre-redraw codepath (colloquially known as +# "feature/redrawhook", after the topic branch's name) or the legacy "bind all +# widgets" codepath. +# +# We use the new codepath under two conditions: +# +# 1. If it's available, which we check by testing for add-zle-hook-widget's availability. +# +# 2. If zsh has the memo= feature, which is required for interoperability reasons. +# See issues #579 and #735, and the issues referenced from them. +# +# We check this with a plain version number check, since a functional check, +# as done by _zsh_highlight, can only be done from inside a widget +# function — a catch-22. +if is-at-least 5.9 && _zsh_highlight__function_callable_p add-zle-hook-widget +then + autoload -U add-zle-hook-widget + _zsh_highlight__zle-line-finish() { + # Reset $WIDGET since the 'main' highlighter depends on it. + # + # Since $WIDGET is declared by zle as read-only in this function's scope, + # a nested function is required in order to shadow its built-in value; + # see "User-defined widgets" in zshall. + () { + local -h -r WIDGET=zle-line-finish + _zsh_highlight + } + } + _zsh_highlight__zle-line-pre-redraw() { + # Set $? to 0 for _zsh_highlight. Without this, subsequent + # zle-line-pre-redraw hooks won't run, since add-zle-hook-widget happens to + # call us with $? == 1 in the common case. + true && _zsh_highlight "$@" + } + _zsh_highlight_bind_widgets(){} + if [[ -o zle ]]; then + add-zle-hook-widget zle-line-pre-redraw _zsh_highlight__zle-line-pre-redraw + add-zle-hook-widget zle-line-finish _zsh_highlight__zle-line-finish + fi +else + # Rebind all ZLE widgets to make them invoke _zsh_highlights. + _zsh_highlight_bind_widgets() + { + setopt localoptions noksharrays + typeset -F SECONDS + local prefix=orig-s$SECONDS-r$RANDOM # unique each time, in case we're sourced more than once + + # Load ZSH module zsh/zleparameter, needed to override user defined widgets. + zmodload zsh/zleparameter 2>/dev/null || { + print -r -- >&2 'zsh-syntax-highlighting: failed loading zsh/zleparameter.' + return 1 + } + + # Override ZLE widgets to make them invoke _zsh_highlight. + local -U widgets_to_bind + widgets_to_bind=(${${(k)widgets}:#(.*|run-help|which-command|beep|set-local-history|yank|yank-pop)}) + + # Always wrap special zle-line-finish widget. This is needed to decide if the + # current line ends and special highlighting logic needs to be applied. + # E.g. remove cursor imprint, don't highlight partial paths, ... + widgets_to_bind+=(zle-line-finish) + + # Always wrap special zle-isearch-update widget to be notified of updates in isearch. + # This is needed because we need to disable highlighting in that case. + widgets_to_bind+=(zle-isearch-update) + + local cur_widget + for cur_widget in $widgets_to_bind; do + case ${widgets[$cur_widget]:-""} in + + # Already rebound event: do nothing. + user:_zsh_highlight_widget_*);; + + # The "eval"'s are required to make $cur_widget a closure: the value of the parameter at function + # definition time is used. + # + # We can't use ${0/_zsh_highlight_widget_} because these widgets are always invoked with + # NO_function_argzero, regardless of the option's setting here. + + # User defined widget: override and rebind old one with prefix "orig-". + user:*) zle -N $prefix-$cur_widget ${widgets[$cur_widget]#*:} + eval "_zsh_highlight_widget_${(q)prefix}-${(q)cur_widget}() { _zsh_highlight_call_widget ${(q)prefix}-${(q)cur_widget} -- \"\$@\" }" + zle -N $cur_widget _zsh_highlight_widget_$prefix-$cur_widget;; + + # Completion widget: override and rebind old one with prefix "orig-". + completion:*) zle -C $prefix-$cur_widget ${${(s.:.)widgets[$cur_widget]}[2,3]} + eval "_zsh_highlight_widget_${(q)prefix}-${(q)cur_widget}() { _zsh_highlight_call_widget ${(q)prefix}-${(q)cur_widget} -- \"\$@\" }" + zle -N $cur_widget _zsh_highlight_widget_$prefix-$cur_widget;; + + # Builtin widget: override and make it call the builtin ".widget". + builtin) eval "_zsh_highlight_widget_${(q)prefix}-${(q)cur_widget}() { _zsh_highlight_call_widget .${(q)cur_widget} -- \"\$@\" }" + zle -N $cur_widget _zsh_highlight_widget_$prefix-$cur_widget;; + + # Incomplete or nonexistent widget: Bind to z-sy-h directly. + *) + if [[ $cur_widget == zle-* ]] && (( ! ${+widgets[$cur_widget]} )); then + _zsh_highlight_widget_${cur_widget}() { :; _zsh_highlight } + zle -N $cur_widget _zsh_highlight_widget_$cur_widget + else + # Default: unhandled case. + print -r -- >&2 "zsh-syntax-highlighting: unhandled ZLE widget ${(qq)cur_widget}" + print -r -- >&2 "zsh-syntax-highlighting: (This is sometimes caused by doing \`bindkey ${(q-)cur_widget}\` without creating the ${(qq)cur_widget} widget with \`zle -N\` or \`zle -C\`.)" + fi + esac + done + } +fi + +# Load highlighters from directory. +# +# Arguments: +# 1) Path to the highlighters directory. +_zsh_highlight_load_highlighters() +{ + setopt localoptions noksharrays bareglobqual + + # Check the directory exists. + [[ -d "$1" ]] || { + print -r -- >&2 "zsh-syntax-highlighting: highlighters directory ${(qq)1} not found." + return 1 + } + + # Load highlighters from highlighters directory and check they define required functions. + local highlighter highlighter_dir + for highlighter_dir ($1/*/(/)); do + highlighter="${highlighter_dir:t}" + [[ -f "$highlighter_dir${highlighter}-highlighter.zsh" ]] && + . "$highlighter_dir${highlighter}-highlighter.zsh" + if type "_zsh_highlight_highlighter_${highlighter}_paint" &> /dev/null && + type "_zsh_highlight_highlighter_${highlighter}_predicate" &> /dev/null; + then + # New (0.5.0) function names + elif type "_zsh_highlight_${highlighter}_highlighter" &> /dev/null && + type "_zsh_highlight_${highlighter}_highlighter_predicate" &> /dev/null; + then + # Old (0.4.x) function names + if false; then + # TODO: only show this warning for plugin authors/maintainers, not for end users + print -r -- >&2 "zsh-syntax-highlighting: warning: ${(qq)highlighter} highlighter uses deprecated entry point names; please ask its maintainer to update it: https://github.com/zsh-users/zsh-syntax-highlighting/issues/329" + fi + # Make it work. + eval "_zsh_highlight_highlighter_${(q)highlighter}_paint() { _zsh_highlight_${(q)highlighter}_highlighter \"\$@\" }" + eval "_zsh_highlight_highlighter_${(q)highlighter}_predicate() { _zsh_highlight_${(q)highlighter}_highlighter_predicate \"\$@\" }" + else + print -r -- >&2 "zsh-syntax-highlighting: ${(qq)highlighter} highlighter should define both required functions '_zsh_highlight_highlighter_${highlighter}_paint' and '_zsh_highlight_highlighter_${highlighter}_predicate' in ${(qq):-"$highlighter_dir${highlighter}-highlighter.zsh"}." + fi + done +} + + +# ------------------------------------------------------------------------------------------------- +# Setup +# ------------------------------------------------------------------------------------------------- + +# Try binding widgets. +_zsh_highlight_bind_widgets || { + print -r -- >&2 'zsh-syntax-highlighting: failed binding ZLE widgets, exiting.' + return 1 +} + +# Resolve highlighters directory location. +_zsh_highlight_load_highlighters "${ZSH_HIGHLIGHT_HIGHLIGHTERS_DIR:-${${0:A}:h}/highlighters}" || { + print -r -- >&2 'zsh-syntax-highlighting: failed loading highlighters, exiting.' + return 1 +} + +# Reset scratch variables when commandline is done. +_zsh_highlight_preexec_hook() +{ + typeset -g _ZSH_HIGHLIGHT_PRIOR_BUFFER= + typeset -gi _ZSH_HIGHLIGHT_PRIOR_CURSOR= +} +autoload -Uz add-zsh-hook +add-zsh-hook preexec _zsh_highlight_preexec_hook 2>/dev/null || { + print -r -- >&2 'zsh-syntax-highlighting: failed loading add-zsh-hook.' + } + +# Load zsh/parameter module if available +zmodload zsh/parameter 2>/dev/null || true + +# Initialize the array of active highlighters if needed. +[[ $#ZSH_HIGHLIGHT_HIGHLIGHTERS -eq 0 ]] && ZSH_HIGHLIGHT_HIGHLIGHTERS=(main) + +if (( $+X_ZSH_HIGHLIGHT_DIRS_BLACKLIST )); then + print >&2 'zsh-syntax-highlighting: X_ZSH_HIGHLIGHT_DIRS_BLACKLIST is deprecated. Please use ZSH_HIGHLIGHT_DIRS_BLACKLIST.' + ZSH_HIGHLIGHT_DIRS_BLACKLIST=($X_ZSH_HIGHLIGHT_DIRS_BLACKLIST) + unset X_ZSH_HIGHLIGHT_DIRS_BLACKLIST +fi + +# Restore the aliases we unned +eval "$zsh_highlight__aliases" +builtin unset zsh_highlight__aliases + +# Set $?. +true