├── .editorconfig
├── .travis.yml
├── .zunit.yml
├── LICENSE
├── README.md
├── autopair.plugin.zsh
├── autopair.zsh
├── tests
├── _support
│ └── bootstrap
├── balanced-p.zunit
├── can-delete-p.zunit
├── can-pair-p.zunit
├── can-skip-p.zunit
└── get-pair.zunit
└── zsh-autopair.plugin.zsh
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | charset = utf-8
3 | end_of_line = lf
4 | insert_final_newline = true
5 | trim_trailing_whitespace = true
6 | indent_style = space
7 | indent_size = 4
8 |
9 | [*.md]
10 | indent_style = space
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: trusty
2 | sudo: required
3 |
4 | language: generic
5 |
6 | env:
7 | - ZSH_VERSION=5.3.1 URL=https://downloads.sourceforge.net/project/zsh/zsh/5.3.1/zsh-5.3.1.tar.xz
8 | - ZSH_VERSION=5.2 URL=https://downloads.sourceforge.net/project/zsh/zsh/5.2/zsh-5.2.tar.xz
9 | - ZSH_VERSION=5.1.1 URL=https://downloads.sourceforge.net/project/zsh/zsh/5.1.1/zsh-5.1.1.tar.xz
10 | - ZSH_VERSION=5.0.8 URL=https://downloads.sourceforge.net/project/zsh/zsh/5.0.8/zsh-5.0.8.tar.gz
11 |
12 | addons:
13 | apt:
14 | packages:
15 | - build-essential
16 |
17 | before_install:
18 | - export LOCAL="$(mktemp --directory --tmpdir=${TMPDIR:/tmp} local.bin.XXXXXX)"
19 | - wget $URL
20 | - tar -xf zsh-$ZSH_VERSION.tar.*
21 | - cd zsh-$ZSH_VERSION
22 | - ./configure --prefix=$LOCAL
23 | - make
24 | - make install
25 | - cd -
26 | - export PATH="$LOCAL/bin:$HOME/bin:$PATH"
27 |
28 | before_script:
29 | - mkdir -p ~/bin
30 | - curl -L https://github.com/zunit-zsh/zunit/releases/download/v0.8.1/zunit > ~/bin/zunit
31 | - curl -L https://raw.githubusercontent.com/molovo/revolver/master/revolver > ~/bin/revolver
32 | - chmod u+x ~/bin/{revolver,zunit}
33 |
34 | script:
35 | - zunit --verbose
36 |
--------------------------------------------------------------------------------
/.zunit.yml:
--------------------------------------------------------------------------------
1 | tap: false
2 | directories:
3 | tests: tests
4 | output: tests/_output
5 | support: tests/_support
6 | time_limit: 0
7 | fail_fast: false
8 | allow_risky: false
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016-24 Henrik Lissner.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/hlissner/doom-emacs)
2 | [](./LICENSE)
3 | 
4 | [](https://travis-ci.org/hlissner/zsh-autopair)
5 |
6 | # zsh-autopair
7 | A simple plugin that auto-closes, deletes and skips over matching delimiters in
8 | zsh intelligently. Hopefully.
9 |
10 | > NOTE: zsh-autopair is untested for versions of Zsh below 5.0.2. Please report
11 | > any issues you have in earlier versions!
12 |
13 | Specifically, zsh-autopair does 5 things for you:
14 |
15 | 1. It inserts matching pairs (by default, that means brackets, quotes and
16 | spaces):
17 |
18 | e.g. `echo |` => " => `echo "|"`
19 |
20 | 2. It skips over matched pairs:
21 |
22 | e.g. `cat ./*.{py,rb|}` => } => `cat ./*.{py,rb}|`
23 |
24 | 3. It auto-deletes pairs on backspace:
25 |
26 | e.g. `git commit -m "|"` => backspace => `git commit -m |`
27 |
28 | 4. And does all of the above only when it makes sense to do so. e.g. when the
29 | pair is balanced and when the cursor isn't next to a boundary character:
30 |
31 | e.g. `echo "|""` => backspace => `echo |""` (doesn't aggressively eat up too many quotes)
32 |
33 | 5. Spaces between brackets are expanded and contracted.
34 |
35 | e.g. `echo [|]` => space => `echo [ | ]` => backspace => `echo [|]`
36 |
37 |
38 |
39 | **Table of Contents**
40 |
41 | - [Install](#install)
42 | - [Antigen](#antigen)
43 | - [zgen](#zgen)
44 | - [zplug](#zplug)
45 | - [Hombrew](#homebrew)
46 | - [Oh My Zsh](#oh-my-zsh)
47 | - [Configuration](#configuration)
48 | - [Adding/Removing pairs](#addingremoving-pairs)
49 | - [Troubleshooting & compatibility issues](#troubleshooting--compatibility-issues)
50 | - [zgen & prezto compatibility](#zgen--prezto-compatibility)
51 | - [text on right-side of cursor interfere with completion](#text-on-right-side-of-cursor-interfere-with-completion)
52 | - [zsh-autopair & isearch?](#zsh-autopair--isearch)
53 | - [Midnight Commander](#midnight-commander)
54 | - [Other resources](#other-resources)
55 |
56 |
57 |
58 | ## Install
59 | Download and source `autopair.zsh`
60 |
61 | ```bash
62 | if [[ ! -d ~/.zsh-autopair ]]; then
63 | git clone https://github.com/hlissner/zsh-autopair ~/.zsh-autopair
64 | fi
65 |
66 | source ~/.zsh-autopair/autopair.zsh
67 | autopair-init
68 | ```
69 |
70 | ### Hoembrew
71 | `brew install zsh-autopair`
72 |
73 | ``` bash
74 | # Add to .zshrc
75 | source $HOMEBREW_PREFIX/share/zsh-autopair/autopair.zsh
76 | ```
77 |
78 | ### Antigen
79 | `antigen bundle hlissner/zsh-autopair`
80 |
81 | ### zgen
82 | ```bash
83 | if ! zgen saved; then
84 | echo "Creating a zgen save"
85 |
86 | # ... other plugins
87 | zgen load hlissner/zsh-autopair
88 |
89 | zgen save
90 | fi
91 | ```
92 |
93 | ### zplug
94 | Load autopair _after compinit_, otherwise, the plugin won't work.
95 | ```bash
96 | zplug "hlissner/zsh-autopair", defer:2
97 | ```
98 |
99 | ### Homebrew
100 | For Homebrew users, you can install it through the following command
101 | ```shell
102 | brew install zsh-autopair
103 | ```
104 | Then source it in your `.zshrc`
105 | ```shell
106 | source $(brew --prefix)/share/zsh-autopair/autopair.zsh
107 | ```
108 |
109 | ### Oh My Zsh
110 | 1. Clone this repository into `$ZSH_CUSTOM/plugins` (by default `~/.oh-my-zsh/custom/plugins`)
111 |
112 | ```sh
113 | git clone https://github.com/hlissner/zsh-autopair ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autopair
114 | ```
115 |
116 | 2. Add the plugin to the list of plugins for Oh My Zsh to load (inside `~/.zshrc`):
117 |
118 | ```sh
119 | plugins=(
120 | # other plugins...
121 | zsh-autopair
122 | )
123 | ```
124 |
125 | 3. Start a new terminal session.
126 |
127 | ## Configuration
128 | zsh-autopair sets itself up. You can prevent this by setting
129 | `AUTOPAIR_INHIBIT_INIT`.
130 |
131 | **Options:**
132 | * `AUTOPAIR_BETWEEN_WHITESPACE` (default: blank): if set, regardless of whether
133 | delimiters are unbalanced or do not meet a boundary check, pairs will be
134 | auto-closed if surrounded by whitespace, BOL or EOL.
135 | * `AUTOPAIR_INHIBIT_INIT` (default: blank): if set, autopair will not
136 | automatically set up keybinds. [Check out the initialization
137 | code](autopair.zsh#L118) if you want to know what it does.
138 | * `AUTOPAIR_PAIRS` (default: ``('`' '`' "'" "'" '"' '"' '{' '}' '[' ']' '(' ')'
139 | ' ' ' ')``): An associative array that map pairs. Only one-character pairs are
140 | supported. To modify this, see the "Adding/Removing pairs" section.
141 | * `AUTOPAIR_LBOUNDS`/`AUTOPAIR_RBOUNDS` (default: see below): Associative lists
142 | of regex character groups dictating the 'boundaries' for autopairing depending
143 | on the delimiter. These are their default values:
144 |
145 | ```bash
146 | AUTOPAIR_LBOUNDS=(all '[.:/\!]')
147 | AUTOPAIR_LBOUNDS+=(quotes '[]})a-zA-Z0-9]')
148 | AUTOPAIR_LBOUNDS+=(spaces '[^{([]')
149 | AUTOPAIR_LBOUNDS+=(braces '')
150 | AUTOPAIR_LBOUNDS+=('`' '`')
151 | AUTOPAIR_LBOUNDS+=('"' '"')
152 | AUTOPAIR_LBOUNDS+=("'" "'")
153 |
154 | AUTOPAIR_RBOUNDS=(all '[[{(<,.:?/%$!a-zA-Z0-9]')
155 | AUTOPAIR_RBOUNDS+=(quotes '[a-zA-Z0-9]')
156 | AUTOPAIR_RBOUNDS+=(spaces '[^]})]')
157 | AUTOPAIR_RBOUNDS+=(braces '')
158 | ```
159 |
160 | For example, if `$AUTOPAIR_LBOUNDS[braces]="[a-zA-Z]"`, then braces (`{([`) won't be
161 | autopaired if the cursor follows an alphabetical character.
162 |
163 | Individual delimiters can be used too. Setting `$AUTOPAIR_RBOUNDS['{']="[0-9]"` will
164 | cause { specifically to not be autopaired when the cursor precedes a number.
165 |
166 | ### Adding/Removing pairs
167 | You can change the designated pairs in zsh-autopair by modifying the
168 | `AUTOPAIR_PAIRS` envvar. This can be done _before_ initialization like so:
169 |
170 | ``` sh
171 | typeset -gA AUTOPAIR_PAIRS
172 | AUTOPAIR_PAIRS+=("<" ">")
173 | ```
174 |
175 | Or after initialization; however, you'll have to bind keys to `autopair-insert`
176 | manually:
177 |
178 | ```sh
179 | AUTOPAIR_PAIRS+=("<" ">")
180 | bindkey "<" autopair-insert
181 | # prevents breakage in isearch
182 | bindkey -M isearch "<" self-insert
183 | ```
184 |
185 | To _remove_ pairs, use `unset 'AUTOPAIR_PAIRS[<]'`. Unbinding is optional.
186 |
187 | ## Troubleshooting & compatibility issues
188 | ### zgen & prezto compatibility
189 | Prezto's Editor module is known to reset autopair's bindings. A workaround is to
190 | _defer autopair from initializing_ (by setting `AUTOPAIR_INHIBIT_INIT=1`) and
191 | initialize it manually (by calling `autopair-init`):
192 |
193 | ``` sh
194 | source "$HOME/.zgen/zgen.zsh"
195 |
196 | # Add this
197 | AUTOPAIR_INHIBIT_INIT=1
198 |
199 | if ! zgen saved; then
200 | zgen prezto
201 | # ...
202 | zgen load hlissner/zsh-autopair 'autopair.zsh'
203 | #...
204 | zgen save
205 | fi
206 |
207 | # And this
208 | autopair-init
209 | ```
210 |
211 | ### text on right-side of cursor interfere with completion
212 | Bind Tab to `expand-or-complete-prefix` and completion will ignore
213 | what's to the right of cursor:
214 |
215 | `bindkey '^I' expand-or-complete-prefix`
216 |
217 | This has the unfortunate side-effect of overwriting whatever's right of the
218 | cursor, however.
219 |
220 | ### zsh-autopair & isearch?
221 | zsh-autopair silently disables itself in isearch, as the two are incompatible.
222 |
223 | ### Midnight Commander
224 | MC hangs when zsh-autopair tries to bind the space key. This also breaks the MC
225 | subshell.
226 |
227 | Disable space expansion to work around this: `unset 'AUTOPAIR_PAIRS[ ]'`
228 |
229 | ## Other resources
230 | * Works wonderfully with [zsh-syntax-highlight] and
231 | `ZSH_HIGHLIGHT_HIGHLIGHTERS+=brackets`, but zsh-syntax-highlight must be
232 | loaded *after* zsh-autopair.
233 | * Mixes well with these vi-mode zsh modules: [surround], [select-quoted], and
234 | [select-bracketed] (they're built into zsh as of zsh-5.0.8)
235 | * Other relevant repositories of mine:
236 | + [dotfiles]
237 | + [emacs.d]
238 | + [vimrc]
239 | + [zshrc]
240 |
241 |
242 | [dotfiles]: https://github.com/hlissner/dotfiles
243 | [vimrc]: https://github.com/hlissner/.vim
244 | [emacs.d]: https://github.com/hlissner/doom-emacs
245 | [zshrc]: https://github.com/hlissner/dotfiles/blob/master/config/zsh/.zshrc
246 | [zsh-syntax-highlighting]: https://github.com/zsh-users/zsh-syntax-highlighting/blob/master/docs/highlighters/pattern.md
247 | [surround]: https://github.com/zsh-users/zsh/blob/master/Functions/Zle/surround
248 | [select-quoted]: https://github.com/zsh-users/zsh/blob/master/Functions/Zle/select-quoted
249 | [select-bracketed]: https://github.com/zsh-users/zsh/blob/master/Functions/Zle/select-bracketed
250 |
--------------------------------------------------------------------------------
/autopair.plugin.zsh:
--------------------------------------------------------------------------------
1 | #
2 | 0="${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}"
3 | 0="${${(M)0:#/*}:-$PWD/$0}"
4 | source "${0:A:h}/autopair.zsh"
5 |
--------------------------------------------------------------------------------
/autopair.zsh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env zsh
2 |
3 | AUTOPAIR_INHIBIT_INIT=${AUTOPAIR_INHIBIT_INIT:-}
4 | AUTOPAIR_BETWEEN_WHITESPACE=${AUTOPAIR_BETWEEN_WHITESPACE:-}
5 | AUTOPAIR_SPC_WIDGET=${AUTOPAIR_SPC_WIDGET:-"$(bindkey " " | cut -c5-)"}
6 | AUTOPAIR_BKSPC_WIDGET=${AUTOPAIR_BKSPC_WIDGET:-"$(bindkey "^?" | cut -c6-)"}
7 | AUTOPAIR_DELWORD_WIDGET=${AUTOPAIR_DELWORD_WIDGET:-"$(bindkey "^w" | cut -c6-)"}
8 |
9 | typeset -gA AUTOPAIR_PAIRS
10 | AUTOPAIR_PAIRS=('`' '`' "'" "'" '"' '"' '{' '}' '[' ']' '(' ')' ' ' ' ')
11 |
12 | typeset -gA AUTOPAIR_LBOUNDS
13 | AUTOPAIR_LBOUNDS=(all '[.:/\!]')
14 | AUTOPAIR_LBOUNDS+=(quotes '[]})a-zA-Z0-9]')
15 | AUTOPAIR_LBOUNDS+=(spaces '[^{([]')
16 | AUTOPAIR_LBOUNDS+=(braces '')
17 | AUTOPAIR_LBOUNDS+=('`' '`')
18 | AUTOPAIR_LBOUNDS+=('"' '"')
19 | AUTOPAIR_LBOUNDS+=("'" "'")
20 |
21 | typeset -gA AUTOPAIR_RBOUNDS
22 | AUTOPAIR_RBOUNDS=(all '[[{(<,.:?/%$!a-zA-Z0-9]')
23 | AUTOPAIR_RBOUNDS+=(quotes '[a-zA-Z0-9]')
24 | AUTOPAIR_RBOUNDS+=(spaces '[^]})]')
25 | AUTOPAIR_RBOUNDS+=(braces '')
26 |
27 |
28 | ### Helpers ############################
29 |
30 | # Returns the other pair for $1 (a char), blank otherwise
31 | _ap-get-pair() {
32 | if [[ -n $1 ]]; then
33 | echo ${AUTOPAIR_PAIRS[$1]}
34 | elif [[ -n $2 ]]; then
35 | local i
36 | for i in ${(@k)AUTOPAIR_PAIRS}; do
37 | [[ $2 == ${AUTOPAIR_PAIRS[$i]} ]] && echo $i && break
38 | done
39 | fi
40 | }
41 |
42 | # Return 0 if cursor's surroundings match either regexp: $1 (left) or $2 (right)
43 | _ap-boundary-p() {
44 | [[ -n $1 && $LBUFFER =~ "$1$" ]] || [[ -n $2 && $RBUFFER =~ "^$2" ]]
45 | }
46 |
47 | # Return 0 if the surrounding text matches any of the AUTOPAIR_*BOUNDS regexps
48 | _ap-next-to-boundary-p() {
49 | local -a groups
50 | groups=(all)
51 | case $1 in
52 | \'|\"|\`) groups+=quotes ;;
53 | \{|\[|\(|\<) groups+=braces ;;
54 | " ") groups+=spaces ;;
55 | esac
56 | groups+=$1
57 | local group
58 | for group in $groups; do
59 | _ap-boundary-p ${AUTOPAIR_LBOUNDS[$group]} ${AUTOPAIR_RBOUNDS[$group]} && return 0
60 | done
61 | return 1
62 | }
63 |
64 | # Return 0 if there are the same number of $1 as there are $2 (chars; a
65 | # delimiter pair) in the buffer.
66 | _ap-balanced-p() {
67 | local lbuf="${LBUFFER//\\$1}"
68 | local rbuf="${RBUFFER//\\$2}"
69 | local llen="${#lbuf//[^$1]}"
70 | local rlen="${#rbuf//[^$2]}"
71 | if (( rlen == 0 && llen == 0 )); then
72 | return 0
73 | elif [[ $1 == $2 ]]; then
74 | if [[ $1 == " " ]]; then
75 | # Silence WARN_CREATE_GLOBAL errors
76 | local match=
77 | local mbegin=
78 | local mend=
79 | # Balancing spaces is unnecessary. If there is at least one space on
80 | # either side of the cursor, it is considered balanced.
81 | [[ $LBUFFER =~ "[^'\"]([ ]+)$" && $RBUFFER =~ "^${match[1]}" ]] && return 0
82 | return 1
83 | elif (( llen == rlen || (llen + rlen) % 2 == 0 )); then
84 | return 0
85 | fi
86 | else
87 | local l2len="${#lbuf//[^$2]}"
88 | local r2len="${#rbuf//[^$1]}"
89 | local ltotal=$((llen - l2len))
90 | local rtotal=$((rlen - r2len))
91 |
92 | (( ltotal < 0 )) && ltotal=0
93 | (( ltotal < rtotal )) && return 1
94 | return 0
95 | fi
96 | return 1
97 | }
98 |
99 | # Return 0 if the last keypress can be auto-paired.
100 | _ap-can-pair-p() {
101 | local rchar="$(_ap-get-pair $KEYS)"
102 |
103 | [[ -n $rchar ]] || return 1
104 |
105 | if [[ $rchar != " " ]]; then
106 | # Force pair if surrounded by space/[BE]OL, regardless of
107 | # boundaries/balance
108 | [[ -n $AUTOPAIR_BETWEEN_WHITESPACE && \
109 | $LBUFFER =~ "(^|[ ])$" && \
110 | $RBUFFER =~ "^($|[ ])" ]] && return 0
111 |
112 | # Don't pair quotes if the delimiters are unbalanced
113 | ! _ap-balanced-p $KEYS $rchar && return 1
114 | elif [[ $RBUFFER =~ "^[ ]*$" ]]; then
115 | # Don't pair spaces surrounded by whitespace
116 | return 1
117 | fi
118 |
119 | # Don't pair when in front of characters that likely signify the start of a
120 | # string, path or undesirable boundary.
121 | _ap-next-to-boundary-p $KEYS $rchar && return 1
122 |
123 | return 0
124 | }
125 |
126 | # Return 0 if the adjacent character (on the right) can be safely skipped over.
127 | _ap-can-skip-p() {
128 | if [[ -z $LBUFFER ]]; then
129 | return 1
130 | elif [[ $1 == $2 ]]; then
131 | if [[ $1 == " " ]]; then
132 | return 1
133 | elif ! _ap-balanced-p $1 $2; then
134 | return 1
135 | fi
136 | fi
137 | if ! [[ -n $2 && ${RBUFFER[1]} == $2 && ${LBUFFER[-1]} != '\' ]]; then
138 | return 1
139 | fi
140 | return 0
141 | }
142 |
143 | # Return 0 if the adjacent character (on the right) can be safely deleted.
144 | _ap-can-delete-p() {
145 | local lchar="${LBUFFER[-1]}"
146 | local rchar="$(_ap-get-pair $lchar)"
147 | ! [[ -n $rchar && ${RBUFFER[1]} == $rchar ]] && return 1
148 | if [[ $lchar == $rchar ]]; then
149 | if [[ $lchar == ' ' && ( $LBUFFER =~ "[^{([] +$" || $RBUFFER =~ "^ +[^]})]" ) ]]; then
150 | # Don't collapse spaces unless in delimiters
151 | return 1
152 | elif ! _ap-balanced-p $lchar $rchar; then
153 | return 1
154 | fi
155 | fi
156 | return 0
157 | }
158 |
159 | # Insert $1 and add $2 after the cursor
160 | _ap-self-insert() {
161 | LBUFFER+=$1
162 | RBUFFER="$2$RBUFFER"
163 | }
164 |
165 |
166 | ### Widgets ############################
167 |
168 | autopair-insert() {
169 | local rchar="$(_ap-get-pair $KEYS)"
170 | if [[ $KEYS == (\'|\"|\`| ) ]] && _ap-can-skip-p $KEYS $rchar; then
171 | zle forward-char
172 | elif _ap-can-pair-p; then
173 | _ap-self-insert $KEYS $rchar
174 | elif [[ $rchar == " " ]]; then
175 | zle ${AUTOPAIR_SPC_WIDGET:-self-insert}
176 | else
177 | zle self-insert
178 | fi
179 | }
180 |
181 | autopair-close() {
182 | if _ap-can-skip-p "$(_ap-get-pair "" $KEYS)" $KEYS; then
183 | zle forward-char
184 | else
185 | zle self-insert
186 | fi
187 | }
188 |
189 | autopair-delete() {
190 | _ap-can-delete-p && RBUFFER=${RBUFFER:1}
191 | zle ${AUTOPAIR_BKSPC_WIDGET:-backward-delete-char}
192 | }
193 |
194 | autopair-delete-word() {
195 | _ap-can-delete-p && RBUFFER=${RBUFFER:1}
196 | zle ${AUTOPAIR_DELWORD_WIDGET:-backward-delete-word}
197 | }
198 |
199 |
200 | ### Initialization #####################
201 |
202 | autopair-init() {
203 | zle -N autopair-insert
204 | zle -N autopair-close
205 | zle -N autopair-delete
206 | zle -N autopair-delete-word
207 |
208 | local p
209 | for p in ${(@k)AUTOPAIR_PAIRS}; do
210 | bindkey "$p" autopair-insert
211 | bindkey -M isearch "$p" self-insert
212 |
213 | local rchar="$(_ap-get-pair $p)"
214 | if [[ $p != $rchar ]]; then
215 | bindkey "$rchar" autopair-close
216 | bindkey -M isearch "$rchar" self-insert
217 | fi
218 | done
219 |
220 | bindkey "^?" autopair-delete
221 | bindkey "^h" autopair-delete
222 | bindkey "^w" autopair-delete-word
223 | bindkey -M isearch "^?" backward-delete-char
224 | bindkey -M isearch "^h" backward-delete-char
225 | bindkey -M isearch "^w" backward-delete-word
226 | }
227 | [[ -n $AUTOPAIR_INHIBIT_INIT ]] || autopair-init
228 |
--------------------------------------------------------------------------------
/tests/_support/bootstrap:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env zsh
2 | source autopair.zsh
3 | export KEYS=
4 | export LBUFFER=
5 | export RBUFFER=
6 |
7 | assert_true() {
8 | run $@
9 | assert $state equals 0
10 | }
11 |
12 | assert_false() {
13 | run $@
14 | assert $state equals 1
15 | }
16 |
--------------------------------------------------------------------------------
/tests/balanced-p.zunit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env zunit
2 | @test 'basic boundary tests' {
3 | LBUFFER="abc"
4 | RBUFFER="123"
5 | assert_false _ap-boundary-p " " " "
6 | assert_true _ap-boundary-p "[^3]" "[^a]"
7 | assert_false _ap-boundary-p "[^c]" "[^1]"
8 | assert_true _ap-boundary-p "c" " "
9 | assert_true _ap-boundary-p " " "1"
10 | }
11 |
12 | @test 'no boundary on blank line' {
13 | LBUFFER=
14 | RBUFFER=
15 | assert_false _ap-next-to-boundary-p "{"
16 | }
17 |
--------------------------------------------------------------------------------
/tests/can-delete-p.zunit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env zunit
2 | @test 'delete if next to space and pair' {
3 | LBUFFER="[ " RBUFFER=" ]" assert_true _ap-can-delete-p
4 | }
5 |
6 | @test 'delete if next to homogeneous counter-pair' {
7 | LBUFFER="'" RBUFFER="'" assert_true _ap-can-delete-p
8 | }
9 |
10 | @test 'delete if next to heterogeneous counter-pair' {
11 | LBUFFER="(" RBUFFER=")" assert_true _ap-can-delete-p
12 | }
13 |
14 | @test 'do not delete if at eol' {
15 | LBUFFER="'" assert_false _ap-can-delete-p
16 | }
17 |
18 | @test 'do not delete if within too many spaces' {
19 | LBUFFER="[ " RBUFFER=" ]" assert_false _ap-can-delete-p
20 | }
21 |
22 | @test 'do not delete if only next to space' {
23 | LBUFFER=" " RBUFFER=" " assert_false _ap-can-delete-p
24 | }
25 |
--------------------------------------------------------------------------------
/tests/can-pair-p.zunit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env zunit
2 | @test 'pair if blank line' {
3 | KEYS="'" LBUFFER="" RBUFFER="" assert_true _ap-can-pair-p
4 | }
5 |
6 | @test 'pair if next to balanced delimiter' { # {{|}}
7 | KEYS="{" LBUFFER="{" RBUFFER="}" assert_true _ap-can-pair-p
8 | }
9 |
10 | @test 'pair space if next to brackets' { # { | }
11 | KEYS=" "
12 | LBUFFER="{" RBUFFER="}" assert_true _ap-can-pair-p
13 | LBUFFER="[" RBUFFER="]" assert_true _ap-can-pair-p
14 | LBUFFER="(" RBUFFER=")" assert_true _ap-can-pair-p
15 | }
16 |
17 | @test 'pair brackets at the end of a word' { # abc{|}
18 | KEYS='{' LBUFFER="hello" assert_true _ap-can-pair-p
19 | }
20 |
21 | @test 'do not pair brackets at the beginning of a word' { # {|abc
22 | KEYS='{' RBUFFER="hello" assert_false _ap-can-pair-p
23 | }
24 |
25 | @test 'do not pair quotes next to a word' {
26 | KEYS="'"
27 | LBUFFER="hello" assert_false _ap-can-pair-p # abc"|
28 | RBUFFER="hello" assert_false _ap-can-pair-p # "|abc
29 | }
30 |
31 | @test 'do not pair the same quotes from inside quotes' { # ""|"
32 | KEYS='"' LBUFFER='"' RBUFFER='"' assert_false _ap-can-pair-p
33 | }
34 |
35 | @test 'do not pair if delimiter is invalid' {
36 | KEYS="<" LBUFFER="<" RBUFFER=">" assert_false _ap-can-pair-p
37 | }
38 |
39 | @test 'do not pair if next to unbalanced delimiter' { # {|}
40 | KEYS="{" LBUFFER="" RBUFFER="}" assert_false _ap-can-pair-p
41 | }
42 |
43 | @test 'do not pair if next to unbalanced delimiter after space' { # {| }
44 | AUTOPAIR_BETWEEN_WHITESPACE=
45 | KEYS="{" LBUFFER="" RBUFFER=" }" assert_false _ap-can-pair-p
46 | KEYS="{" LBUFFER="" RBUFFER=" }" assert_false _ap-can-pair-p
47 | KEYS="{" LBUFFER=" " RBUFFER=" }" assert_false _ap-can-pair-p
48 | }
49 |
50 | @test 'do not pair space if next to non-brackets/spaces' {
51 | KEYS=" "
52 | LBUFFER="'" RBUFFER="'" assert_false _ap-can-pair-p # ' |'
53 | LBUFFER="abc" RBUFFER="xyz" assert_false _ap-can-pair-p # abc |xyz'
54 | LBUFFER="[ " RBUFFER=" ]" assert_false _ap-can-pair-p # [ | ]
55 | }
56 |
57 | @test 'AUTOPAIR_BETWEEN_WHITESPACE=1' {
58 | AUTOPAIR_BETWEEN_WHITESPACE=1 KEYS='{' LBUFFER=" " RBUFFER=" }" assert_true _ap-can-pair-p
59 | }
60 |
--------------------------------------------------------------------------------
/tests/can-skip-p.zunit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env zunit
2 |
3 | @test 'fail skip-check on blank line' {
4 | LBUFFER='' RBUFFER='' assert_false _ap-can-skip-p '{' '}'
5 | }
6 |
7 | @test 'fail skip-check on wrong pair' {
8 | LBUFFER='"' RBUFFER='"' assert_false _ap-can-skip-p '{' '}'
9 | }
10 |
11 | @test 'skip if next to homogeneous counter-pair' {
12 | LBUFFER='"' RBUFFER='"' assert_true _ap-can-skip-p '"' '"'
13 | }
14 |
15 | @test 'skip if next to heterogeneous counter-pair' {
16 | LBUFFER='{' RBUFFER='}' assert_true _ap-can-skip-p '{' '}'
17 | }
18 |
19 | @test 'do not skip if next to unbalanced, homogeneous counter-pair' {
20 | LBUFFER='' RBUFFER='"' assert_false _ap-can-skip-p '"' '"'
21 | }
22 |
23 | @test 'do not skip if next to unbalanced, heterogeneous counter-pair' {
24 | LBUFFER='' RBUFFER='}' assert_false _ap-can-skip-p '{' '}'
25 | }
26 |
--------------------------------------------------------------------------------
/tests/get-pair.zunit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env zunit
2 | @test 'existing pair' {
3 | assert $(_ap-get-pair "{") same_as "}"
4 | }
5 |
6 | @test 'existing right-pair' {
7 | assert $(_ap-get-pair "" "}") same_as "{"
8 | }
9 |
10 | @test 'non-existent pair' {
11 | assert $(_ap-get-pair "<") same_as ""
12 | }
13 |
14 | @test 'non-existent right-pair' {
15 | assert $(_ap-get-pair "" ">") same_as ""
16 | }
17 |
18 | @test 'all default pairs' {
19 | assert '"' in ${(@k)AUTOPAIR_PAIRS}
20 | assert "'" in ${(@k)AUTOPAIR_PAIRS}
21 | assert '`' in ${(@k)AUTOPAIR_PAIRS}
22 | assert '{' in ${(@k)AUTOPAIR_PAIRS}
23 | assert '[' in ${(@k)AUTOPAIR_PAIRS}
24 | assert '(' in ${(@k)AUTOPAIR_PAIRS}
25 | assert ' ' in ${(@k)AUTOPAIR_PAIRS}
26 | }
27 |
--------------------------------------------------------------------------------
/zsh-autopair.plugin.zsh:
--------------------------------------------------------------------------------
1 | #
2 | 0="${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}"
3 | 0="${${(M)0:#/*}:-$PWD/$0}"
4 | source "${0:A:h}/autopair.zsh"
5 |
--------------------------------------------------------------------------------