diff --git a/etc/soft/nvim/+plugins/vim-matchup/LICENSE.md b/etc/soft/nvim/+plugins/vim-matchup/LICENSE.md
new file mode 100644
index 0000000..9bfab03
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/LICENSE.md
@@ -0,0 +1,23 @@
+MIT license
+
+Copyright (c) 2020 Andy Massimino
+
+Copyright (c) 2016 Karl Yngve Lervåg
+
+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/etc/soft/nvim/+plugins/vim-matchup/README.md b/etc/soft/nvim/+plugins/vim-matchup/README.md
new file mode 100644
index 0000000..05ffbb4
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/README.md
@@ -0,0 +1,780 @@
+# vim match-up
+
+match-up is a plugin that lets you highlight, navigate, and operate on
+sets of matching text. It extends vim's `%` key to language-specific
+words instead of just single characters.
+
+
+
+## Screenshot
+
+
+
+## Table of contents
+
+ * [Overview](#overview)
+ * [Installation](#installation)
+ * [Features](#features)
+ * [Options](#options)
+ * [FAQ](#faq)
+ * [Interoperability](#interoperability)
+ * [Acknowledgments](#acknowledgments)
+ * [Development](#development)
+
+## Overview
+
+match-up can be used as a drop-in replacement for the classic plugin [matchit.vim].
+match-up aims to enhance all of matchit's features, fix a number of its
+deficiencies and bugs, and add a few totally new features. It also
+replaces the standard plugin [matchparen], allowing all of matchit's words
+to be highlighted along with the `matchpairs` (`(){}[]`).
+
+[matchit.vim]: http://ftp.vim.org/pub/vim/runtime/macros/matchit.txt
+[matchparen]: http://ftp.vim.org/pub/vim/runtime/doc/pi_paren.txt
+
+See [detailed feature documentation](#detailed-feature-documentation) for
+more information. This plugin:
+
+- Extends vim's `%` motion to language-specific words. The following vim
+ file type plugins currently provide support for match-up:
+
+ > abaqus, ada, aspvbs, c, clojure, cobol, config, context, csc, csh,
+ > dtd, dtrace, eiffel, eruby, falcon, fortran, framescript, haml,
+ > hamster, hog, html, ishd, j, jsp, kconfig, liquid, lua, make, matlab,
+ > mf, mp, ocaml, pascal, pdf, perl, php, plaintex, postscr, ruby, sh,
+ > spec, sql, tex, vb, verilog, vhdl, vim, xhtml, xml, zimbu, zsh
+
+ Note: match-up uses the same `b:match_words` as matchit.
+- Adds motions `g%`, `[%`, `]%`, and `z%`.
+- Combines these motions into convenient text objects `i%` and `a%`.
+- Highlights symbols and words under the cursor which `%` can work on,
+ and highlights matching symbols and words. Now you can easily tell
+ where `%` will jump to.
+
+## Installation
+
+If you use [vim-plug](https://github.com/junegunn/vim-plug), then add the following line to your vimrc file:
+
+```vim
+Plug 'andymass/vim-matchup'
+```
+
+and then use `:PlugInstall`. Or, you can use any other plugin manager such as
+[vundle](https://github.com/gmarik/vundle),
+[dein](https://github.com/Shougo/dein.vim),
+[neobundle](https://github.com/Shougo/neobundle.vim), or
+[pathogen](https://github.com/tpope/vim-pathogen).
+
+match-up should automatically disable matchit and matchparen, but if you
+are still having trouble, try placing this near the top of your vimrc:
+
+```vim
+let g:loaded_matchit = 1
+```
+
+See [Interoperability](#interoperability) for more information about working
+together with other plugins.
+
+## Features
+
+| | feature | __match-up__ | matchit | matchparen |
+| ------- | -------------------------------- | -------------- | ------------- | ------------- |
+| ([a.1]) | jump between matching words | :thumbsup: | :thumbsup: | :x: |
+| ([a.2]) | jump to open & close words | :thumbsup: | :thumbsup: | :x: |
+| ([a.3]) | jump inside | :thumbsup: | :x: | :x: |
+| ([b.1]) | full set of text objects | :thumbsup: | :question: | :x: |
+| ([c.1]) | highlight `()`, `[]`, & `{}` | :thumbsup: | :x: | :thumbsup: |
+| ([c.2]) | highlight _all_ matches | :thumbsup: | :x: | :x: |
+| ([c.3]) | display matches off-screen | :thumbsup: | :x: | :x: |
+| ([d.1]) | modern, modular coding style | :thumbsup: | :x: | :x: |
+| ([d.2]) | actively developed | :thumbsup: | :x: | :x: |
+
+[a.1]: #a1-jump-between-matching-words
+[a.2]: #a2-jump-to-open-and-close-words
+[a.3]: #a3-jump-inside
+[b.1]: #b1-full-set-of-text-objects
+[c.1]: #c1-highlight---and-
+[c.2]: #c2-highlight-all-matches
+[c.3]: #c3-display-matches-off-screen
+[d.1]: #development
+[d.2]: #development
+[inclusive]: #inclusive-and-exclusive-motions
+[exclusive]: #inclusive-and-exclusive-motions
+
+Legend: :thumbsup: supported. :construction: TODO, planned, or in progress.
+:question: poorly implemented, broken, or uncertain. :x: not possible.
+
+### Detailed feature documentation
+
+What do we mean by open, close, mid? This depends on the specific file
+type and is configured through the variable `b:match_words`. Here are a
+couple examples:
+
+#### vim-script
+
+```vim
+if l:x == 1
+ call one()
+elseif l:x == 2
+ call two()
+else
+ call three()
+endif
+```
+
+For the vim-script language, match-up understands the words `if`,
+`else`, `elseif`, `endif` and that they form a sequential construct. The
+"open" word is `if`, the "close" word is `endif`, and the "mid"
+words are `else` and `elseif`. The `if`/`endif` pair is called an
+"open-to-close" block and the `if`/`else`, `else`/`elsif`, and
+`elseif`/`endif` are called "any" blocks.
+
+#### C, C++
+```c
+#if 0
+#else
+#endif
+
+void some_func() {
+ if (true) {
+ one();
+ } else if (false && false) {
+ two();
+ } else {
+ three();
+ }
+}
+```
+
+Since in C and C++, blocks are delimited using braces (`{` & `}`),
+match-up will recognize `{` as the open word and `}` as the close word.
+It will ignore the `if` and `else if` because they are not defined in
+vim's C file type plugin.
+
+On the other hand, match-up will recognize the `#if`, `#else`, `#endif`
+preprocessor directives.
+
+#### (a.1) jump between matching words
+ - `%` go forwards to next matching word. If at a close word,
+ cycle back to the corresponding open word.
+ - `{count}%` forwards `{count}` times. Requires
+ `{count} <= g:matchup_motion_override_Npercent`. For larger
+ `{count}`, `{count}%` goes to the `{count}` percentage in the file.
+ - `g%` go backwards to `[count]`th previous matching word. If at an
+ open word, cycle around to the corresponding close word.
+
+#### (a.2) jump to open and close words
+- `[%` go to `[count]`th previous outer open word. Allows navigation
+to the start of blocks surrounding the cursor. This is similar to vim's
+built-in `[(` and `[{` and is an [exclusive] motion.
+- `]%` go to `[count]`th next surrounding close word. This is an
+[exclusive] motion.
+
+#### (a.3) jump inside
+- `z%` go to inside `[count]`th nearest inner contained block. This
+ is an [exclusive] motion when used with operators, except it eats
+ whitespace. For example, where `█` is the cursor position,
+
+```vim
+ █ call somefunction(param1, param2)
+```
+`dz%` produces
+```vim
+ param1, param2)
+```
+but in
+```vim
+ █ call somefunction( param1, param2)
+```
+`dz%` also produces
+```vim
+ param1, param2)
+```
+
+#### (b.1) full set of text objects
+- `i%` the inside of an any block
+- `1i%` the inside of an open-to-close block
+- `{count}i%` If count is greater than 1, the inside of the `{count}`th
+ surrounding open-to-close block
+
+- `a%` an any block.
+- `1a%` an open-to-close block. Includes mids but does not include open
+ and close words.
+- `{count}a%` if `{count}` is greater than 1, the `{count}`th surrounding
+ open-to-close block.
+
+See [here](#line-wise-operatortext-object-combinations)
+for some examples and important special cases.
+
+#### (c.1) highlight `()`, `[]`, and `{}`
+
+match-up emulates vim's matchparen to highlight the symbols contained
+in the `matchpairs` setting.
+
+#### (c.2) highlight _all_ matches
+
+To disable match highlighting at startup, use
+`let g:matchup_matchparen_enabled = 0`
+in your vimrc.
+See [here](#module-matchparen) for more information and related
+options.
+
+You can enable highlighting on the fly using `:DoMatchParen`.
+Likewise, you can disable highlighting at any time using
+`:NoMatchParen`.
+
+After start-up, is better to use `:NoMatchParen` and `:DoMatchParen`
+to toggle highlighting globally than setting the global variable
+since these commands make sure not to leave stale matches around.
+
+#### (c.3) display matches off screen
+
+If a open or close which would have been highlighted is on a line
+positioned outside the current window, the match is shown in the
+status line. If both the open and close match are off-screen, the
+close match is preferred.
+(See the option `g:matchup_matchparen_offscreen`).
+
+### Inclusive and exclusive motions
+
+In vim, character motions following operators (such as `d` for delete
+and `c` for change) are either _inclusive_ or _exclusive_. This means
+they either include the ending position or not. Here, "ending position"
+means the line and column closest to the end of the buffer of the region
+swept over by the motion. match-up is designed so that `d]%` inside a set
+of parenthesis behaves exactly like `d])`, except generalized to words.
+
+Put differently, _forward_ exclusive motions will not include the close
+word. In this example, where `█` is the cursor position,
+
+```vim
+if █x | continue | endif
+```
+
+pressing `d]%` will produce (cursor on the `e`)
+
+```vim
+if endif
+```
+
+To include the close word, use either `dv]%` or `vd]%`. This is also
+compatible with vim's `d])` and `d]}`.
+
+Operators over _backward_ exclusive motions will instead exclude the
+position the cursor was on before the operator was invoked. For example,
+in
+
+```vim
+ if █x | continue | endif
+```
+pressing `d[%` will produce
+
+```vim
+ █x | continue | endif
+```
+This is compatible with vim's `d[(` and `d[{`.
+
+Unlike `]%`, `%` is an _inclusive_ motion. As a special case for the
+`d` (delete) operator, if `d%` leaves behind lines white-space, they will
+be deleted also. In effect, it will be operating line-wise. As an
+example, pressing `d%` will leave behind nothing.
+
+```text
+ █(
+
+ )
+```
+
+To operate character-wise in this situation, use `dv%` or `vd%`.
+This is vim compatible with the built-in `d%` on `matchpairs`.
+
+### Line-wise operator/text-object combinations
+
+Normally, the text objects `i%` and `a%` work character-wise. However,
+there are some special cases. For certain operators combined with `i%`,
+under certain conditions, match-up will effectively operate line-wise
+instead. For example, in
+```vim
+if condition
+ █call one()
+ call two()
+endif
+```
+pressing `di%` will produce
+```vim
+if condition
+endif
+```
+even though deleting ` condition` would be suggested by the object `i%`.
+The intention is to make operators more useful in some cases. The
+following rules apply:
+1. The operator must be listed in `g:matchup_text_obj_linewise_operators`.
+ By default this is `d` and `y` (e.g., `di%` and `ya%`).
+2. The outer block must span multiple lines.
+3. The open and close delimiters must be more than one character long. In
+ particular, `di%` involving a `(`...`)` block will not be subject to
+ these special rules.
+
+To prevent this behavior for a particular operation, use `vi%d`. Note that
+special cases involving indentation still apply (like with |i)| etc).
+
+To disable this entirely, remove the operator from the following variable,
+```vim
+let g:matchup_text_obj_linewise_operators = [ 'y' ]
+```
+
+Note: unlike vim's built-in `i)`, `ab`, etc., `i%` does not make an
+existing visual mode character-wise.
+
+A second special case involves `da%`. In this example,
+```vim
+ if condition
+ █call one()
+ call two()
+ endif
+```
+pressing `da%` will delete all four lines and leave no white-space. This
+is vim compatible with `da(`, `dab`, etc.
+
+## Options
+
+To disable the plugin entirely,
+```vim
+let g:matchup_enabled = 0
+```
+default: 1
+
+To disable a particular module,
+```vim
+let g:matchup_matchparen_enabled = 0
+let g:matchup_motion_enabled = 0
+let g:matchup_text_obj_enabled = 0
+```
+defaults: 1
+
+To enable the delete surrounding (`ds%`) and change surrounding (`cs%`)
+maps,
+```vim
+let g:matchup_surround_enabled = 1
+```
+default: 0
+
+To enable the experimental [transmute](#d1-parallel-transmutation)
+module,
+```vim
+let g:matchup_transmute_enabled = 1
+```
+default: 0
+
+To configure the number of lines to search in either direction while using
+motions and text objects. Does not apply to match highlighting
+(see `g:matchup_matchparen_stopline` instead).
+```vim
+let g:matchup_delim_stopline = 1500
+```
+default: 1500
+
+To disable matching within strings and comments,
+```vim
+let g:matchup_delim_noskips = 1 " recognize symbols within comments
+let g:matchup_delim_noskips = 2 " don't recognize anything in comments
+```
+default: 0 (matching is enabled within strings and comments)
+
+### Variables
+
+match-up understands the following variables from matchit.
+- `b:match_words`
+- `b:match_skip`
+- `b:match_ignorecase`
+
+These are set in the respective ftplugin files. They may not exist for
+every file type. To support a new file type, create a file
+`after/ftplugin/{filetype}.vim` which sets them appropriately.
+
+### Module matchparen
+
+To disable match highlighting at startup, use
+`let g:matchup_matchparen_enabled = 0` in your vimrc.
+Note: vim's built-in plugin |pi_paren| plugin is also disabled.
+The variable `g:loaded_matchparen` has no effect on match-up.
+
+#### Customizing the highlighting colors
+
+match-up uses the `MatchParen` highlighting group by default, which can be
+configured. For example,
+```vim
+:hi MatchParen ctermbg=blue guibg=lightblue cterm=italic gui=italic
+```
+
+You may want to put this inside a `ColorScheme` `autocmd` so it is
+preserved after colorscheme changes:
+```vim
+augroup matchup_matchparen_highlight
+ autocmd!
+ autocmd ColorScheme * hi MatchParen guifg=red
+augroup END
+```
+
+You can also highlight words differently than parentheses using the
+`MatchWord` highlighting group. You might do this if you find the
+`MatchParen` style distracting for large blocks.
+```vim
+:hi MatchWord ctermfg=red guifg=blue cterm=underline gui=underline
+```
+
+There are also `MatchParenCur` and `MatchWordCur` which allow you to
+configure the highlight separately for the match under the cursor.
+```vim
+:hi MatchParenCur cterm=underline gui=underline
+:hi MatchWordCur cterm=underline gui=underline
+```
+
+The matchparen module can be disabled on a per-buffer basis (there is
+no command for this). By default, when disabling highlighting for a
+particular buffer, the standard plugin matchparen will still be used
+for that buffer.
+
+```vim
+let b:matchup_matchparen_enabled = 0
+```
+default: 1
+
+If this module is disabled on a particular buffer, match-up will still
+fall-back to the vim standard plugin matchparen, which will highlight
+`matchpairs` such as `()`, `[]`, & `{}`. To disable this,
+```vim
+let b:matchup_matchparen_fallback = 0
+```
+default: 1
+
+A common usage of these options is to automatically disable
+matchparen for particular file types;
+```vim
+augroup matchup_matchparen_disable_ft
+ autocmd!
+ autocmd FileType tex let [b:matchup_matchparen_fallback,
+ \ b:matchup_matchparen_enabled] = [0, 0]
+augroup END
+```
+
+Whether to highlight known words even if there is no match:
+```vim
+let g:matchup_matchparen_singleton = 1
+```
+default: 0
+
+Dictionary controlling the behavior with off-screen matches.
+```vim
+let g:matchup_matchparen_offscreen = { ... }
+```
+
+default: `{'method': 'status'}`
+
+If empty, this feature is disabled. Else, it should contain the
+following optional keys:
+
+- `method`:
+ Sets the method to use to show off-screen matches.
+ Possible values are:
+
+ `'status'` (default): Replace the |status-line| for off-screen matches.
+
+ If a match is off of the screen, the line belonging to that match will be
+ displayed syntax-highlighted in the status line along with the line number
+ (if line numbers are enabled). If the match is above the screen border,
+ an additional Δ symbol will be shown to indicate that the matching line is
+ really above the cursor line.
+
+ `'status_manual'`: Compute the status-line but do not display it (future
+ extension).
+
+- `scrolloff`:
+ When enabled, off-screen matches will not be shown in the statusline while
+ the cursor is at the screen edge (respects the value of 'scrolloff').
+ This is intended to prevent flickering while scrolling with j and k.
+
+ default: 0.
+
+The number of lines to search in either direction while highlighting
+matches. Set this conservatively since high values may cause performance
+issues.
+```vim
+let g:matchup_matchparen_stopline = 400 " for match highlighting only
+```
+
+default: 400
+
+#### highlighting timeouts
+
+Adjust timeouts in milliseconds for matchparen highlighting:
+```vim
+let g:matchup_matchparen_timeout = 300
+let g:matchup_matchparen_insert_timeout = 60
+```
+default: 300, 60
+
+#### deferred highlighting
+
+Deferred highlighting improves cursor movement performance (for example,
+when using `hjkl`) by delaying highlighting for a short time and waiting
+to see if the cursor continues moving;
+```vim
+let g:matchup_matchparen_deferred = 1
+```
+default: 0 (disabled)
+
+Note: this feature is only available if your vim version has `timers` and
+the function `timer_pause`, version 7.4.2180 and after. For neovim, this
+will only work in nvim-0.2.1 and after.
+
+Adjust delays in milliseconds for deferred highlighting:
+```vim
+let g:matchup_matchparen_deferred_show_delay = 50
+let g:matchup_matchparen_deferred_hide_delay = 700
+```
+default: 50, 700
+
+Note: these delays cannot be changed dynamically and should be configured
+before the plugin loads (e.g., in your vimrc).
+
+#### highlight surrounding
+
+To highlight the surrounding delimiters until the cursor moves, use a map
+such as the following
+```vim
+nmap (matchup-hi-surround)
+```
+There is no default map for this feature.
+
+You can also highlight surrounding delimiters always as the cursor moves.
+```vim
+let g:matchup_matchparen_deferred = 1
+let g:matchup_matchparen_hi_surround_always = 1
+```
+default: 0 (off)
+
+This can be set on a per-buffer basis:
+```vim
+autocmd FileType tex let b:matchup_matchparen_hi_surround_always = 1
+```
+
+Note: this feature _requires_
+[deferred highlighting](#deferred-highlighting) to be supported and
+enabled.
+
+### Module motion
+
+In vim, `{count}%` goes to the `{count}` percentage in the file.
+match-up overrides this motion for small `{count}` (by default, anything
+less than 7). To allow `{count}%` for `{count}` up to 11,
+```vim
+g:matchup_motion_override_Npercent = 11
+```
+To disable this feature, and restore vim's default `{count}%`,
+```vim
+g:matchup_motion_override_Npercent = 0
+```
+default: 6
+
+If enabled, cursor will land on the end of mid and close words while
+moving downwards (`%`/`]%`). While moving upwards (`g%`, `[%`) the cursor
+will land on the beginning. To disable,
+```vim
+let g:matchup_motion_cursor_end = 0
+```
+default: 1
+
+### Module text_obj
+
+Modify the set of operators which may operate
+[line-wise](#line-wise-operatortext-object-combinations)
+```vim
+let g:matchup_text_obj_linewise_operators' = ['d', 'y']
+```
+default: `['d', 'y']`
+
+### Module transmute
+
+_Options planned_.
+
+## FAQ
+
+- match-up doesn't work
+
+ This plugin requires at least vim 7.4. It should work in vim 7.4.898
+ but at least vim 7.4.1689 is better. I recommend using the most recent
+ version of vim if possible.
+
+ If you have issues, please tell me your vim version and error messages.
+ Try updating vim and see if the problem persists.
+
+- Why does jumping not work for construct X in language Y?
+
+ Please open a new issue
+
+- Highlighting is not correct for construct X
+
+ match-up uses matchit's filetype-specific data, which may not give
+ enough information to create proper highlights. To fix this, you may
+ need to modify `b:match_words`.
+
+ For help, please open a new issue and be as specific as possible.
+
+- I'm having performance problems
+
+ match-up aims to be as fast as possible, but highlighting matching words
+ can be intensive and may be slow on less powerful machines. There are a
+ few things you can try to improve performance:
+
+ 1. Update to a recent version of vim. Newer versions are faster, more
+ extensively tested, and better supported by match-up.
+ 2. Try [deferred highlighting](#deferred-highlighting), which delays
+ highlighting until the cursor is stationary to improve cursor movement
+ performance.
+ 3. Lower the [highlighting timeouts](#highlighting-timeouts). Note that
+ if highlighting takes longer than the timeout, highlighting will not be
+ attempted again until the cursor moves.
+
+ If are having any other performance issues, please open a new issue and
+ report the output of `:MatchupShowTimes`.
+
+- Why is there a weird entry on the status line?
+
+ This is a feature which helps you see matches that are outside of the
+ vim screen, similar to some IDEs. If you wish to disable it, use
+
+ ```vim
+ let g:matchup_matchparen_offscreen = {}
+ ```
+
+- Matching does not work when lines are too far apart.
+
+ The number of search lines is limited for performance reasons. You may
+ increase the limits with the following options:
+
+ ```vim
+ let g:matchup_delim_stopline = 1500 " generally
+ let g:matchup_matchparen_stopline = 400 " for match highlighting only
+ ```
+- The maps `1i%` and `1a%` are difficult to press.
+
+ You may use the following maps `I%` and `A%` for convenience:
+
+ ```vim
+ function! s:matchup_convenience_maps()
+ xnoremap (std-I) I
+ xnoremap (std-A) A
+ xmap I mode()==''?'(std-I)':(v:count?'':'1').'i'
+ xmap A mode()==''?'(std-A)':(v:count?'':'1').'a'
+ for l:v in ['', 'v', 'V', '']
+ execute 'omap ' l:v.'I%' "(v:count?'':'1').'".l:v."i%'"
+ execute 'omap ' l:v.'A%' "(v:count?'':'1').'".l:v."a%'"
+ endfor
+ endfunction
+ call s:matchup_convenience_maps()
+ ```
+
+ Note: this is not compatible with the plugin targets.vim.
+
+- How can I contribute?
+
+ Read the [contribution guidelines](CONTRIBUTING.md) and [issue
+ template](ISSUE_TEMPLATE.md). Be as precise and detailed as possible
+ when submitting issues and pull requests.
+
+## Interoperability
+
+### vimtex, for LaTeX documents
+
+By default, match-up will be disabled automatically for tex files when
+[vimtex] is detected. To enable match-up for tex files, use
+
+```vim
+let g:matchup_override_vimtex = 1
+```
+
+match-up's matching engine is more advanced than vimtex's and supports
+middle delimiters such as `\middle|` and `\else`. The exact set of
+delimiters recognized may differ between the two plugins. For example,
+the mappings `da%` and `dad` will not always match, particularly if you
+have customized vimtex's delimiters.
+
+### Surroundings
+
+match-up provides built-in support for [vim-surround]-style `ds%` and
+`cs%` operations. If vim-surround is installed, you can use vim-surround
+replacements such as `cs%)`. `%` cannot be used as a replacement.
+An alternative plugin is [vim-sandwich], which allows more complex
+surround replacement rules but is not currently supported.
+
+[vim-surround]: https://github.com/tpope/vim-surround
+[vim-sandwich]: https://github.com/machakann/vim-sandwich
+
+### Auto-closing plugins
+
+match-up does not provide auto-complete or auto-insertion of matches.
+See for instance one of the following plugins for this;
+
+- [vim-endwise](https://github.com/tpope/vim-endwise)
+- [auto-pairs](https://github.com/jiangmiao/auto-pairs)
+- [delimitMate](https://github.com/Raimondi/delimitMate)
+- [splitjoin.vim](https://github.com/AndrewRadev/splitjoin.vim)
+- [Pear Tree](https://github.com/tmsvg/pear-tree)
+
+### Matchit
+
+match-up tries to work around matchit.vim in all cases, but if
+you experience problems, read the following.
+matchit.vim should not be loaded. If it is loaded, it should be loaded
+after match-up (in this case, matchit.vim will be disabled). Note that
+some plugins, such as
+[vim-sensible](https://github.com/tpope/vim-sensible),
+load matchit.vim so these should also be initialized after match-up.
+
+### Matchparen emulation
+
+match-up loads [matchparen] if it is not already loaded. Ordinarily, match-up
+disables matchparen's highlighting and emulates it to highlight the symbol
+contained in the 'matchpairs' option (by default `()`, `[]`, and `{}`). If match-up
+is disabled per-buffer using `b:matchup_matchparen_enabled`, match-up will use
+matchparen instead of its own highlighting. See `b:matchup_matchparen_fallback`
+for more information.
+
+## Acknowledgments
+
+### Origins
+
+match-up was originally based on [@lervag](https://github.com/lervag)'s
+[vimtex]. The concept and style of this plugin and its development are
+heavily influenced by vimtex. :beers:
+
+[vimtex]: https://github.com/lervag/vimtex
+
+### Other inspirations
+
+- [matchit](http://ftp.vim.org/pub/vim/runtime/macros/matchit.txt)
+- [matchparen](http://ftp.vim.org/pub/vim/runtime/doc/pi_paren.txt)
+- [MatchTag](https://github.com/gregsexton/MatchTag)
+- [MatchTagAlways](https://github.com/Valloric/MatchTagAlways)
+- [vim-textobj-anyblock](https://github.com/rhysd/vim-textobj-anyblock)
+
+## Development
+
+### Reporting problems
+
+This is a new plugin and there are likely to be some bugs.
+Thorough issue reports are encouraged. Please read the [issue
+template](ISSUE_TEMPLATE.md) first. Be as precise and detailed as
+possible when submitting issues.
+
+Feature requests are also welcome.
+
+### Contributing
+
+Please read the [contribution guidelines](CONTRIBUTING.md) before
+contributing.
+
+A major goal of this project is to keep a modern and modular code base.
+Contributions are welcome!
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/c_matchup.vim b/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/c_matchup.vim
new file mode 100644
index 0000000..64fb6fe
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/c_matchup.vim
@@ -0,0 +1,16 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+if !exists('g:loaded_matchup') || !exists('b:did_ftplugin')
+ finish
+endif
+
+if matchup#util#check_match_words('bb2bcbee')
+ call matchup#util#append_match_words('/\*:\*/')
+endif
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/cpp_matchup.vim b/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/cpp_matchup.vim
new file mode 100644
index 0000000..4d4f2cf
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/cpp_matchup.vim
@@ -0,0 +1,25 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+if !exists('g:loaded_matchup') || !exists('b:did_ftplugin')
+ finish
+endif
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+if matchup#util#matchpref('template', 0)
+ call matchup#util#append_match_words(
+ \ '\%(\s\@\|>\s\@!\)=\@!')
+ if stridx(&matchpairs, '<:>')
+ setlocal matchpairs-=<:>
+ endif
+endif
+
+let &cpo = s:save_cpo
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/fortran_matchup.vim b/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/fortran_matchup.vim
new file mode 100644
index 0000000..56b4129
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/fortran_matchup.vim
@@ -0,0 +1,25 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+if !exists('g:loaded_matchup') || !exists('b:did_ftplugin')
+ finish
+endif
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+call matchup#util#patch_match_words('\')
+call matchup#util#patch_match_words('\\s*if')
+
+call matchup#util#append_match_words(
+ \ '^\s*#\s*if\(\|def\|ndef\)\>'
+ \ . ':^\s*#\s*elif\>:^\s*#\s*else\>'
+ \ . ':^\s*#\s*endif\>')
+
+let &cpo = s:save_cpo
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/html_matchup.vim b/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/html_matchup.vim
new file mode 100644
index 0000000..87e7651
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/html_matchup.vim
@@ -0,0 +1,44 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+if !exists('g:loaded_matchup') || !exists('b:did_ftplugin')
+ finish
+endif
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+call matchup#util#patch_match_words(
+ \ '<\@<=\([^/][^ \t>]*\)[^>]*\%(>\|$\):<\@<=/\1>',
+ \ '<\@<=\([^/][^ \t>]*\)\%(>\|$\|[ \t][^>]*\%(>\|$\)\):<\@<=/\1>'
+ \)
+
+if matchup#util#matchpref('nolists',
+ \ get(g:, 'matchup_matchpref_html_nolists', 0))
+ call matchup#util#patch_match_words(
+ \ '<\@<=[ou]l\>[^>]*\%(>\|$\):<\@<=li\>:<\@<=/[ou]l>',
+ \ '')
+ call matchup#util#patch_match_words(
+ \ '<\@<=dl\>[^>]*\%(>\|$\):<\@<=d[td]\>:<\@<=/dl>',
+ \ '')
+endif
+
+if matchup#util#matchpref('tagnameonly', 0)
+ call matchup#util#patch_match_words(
+ \ '\)\%(',
+ \ '\)\g{hlend}\%(')
+ call matchup#util#patch_match_words(
+ \ ']l\>[',
+ \ ']l\>\g{hlend}[')
+ call matchup#util#patch_match_words(
+ \ 'dl\>',
+ \ 'dl\>\g{hlend}')
+endif
+
+let &cpo = s:save_cpo
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/lua_matchup.vim b/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/lua_matchup.vim
new file mode 100644
index 0000000..ad20a0a
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/lua_matchup.vim
@@ -0,0 +1,22 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+if !exists('g:loaded_matchup') || !exists('b:did_ftplugin')
+ finish
+endif
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+let b:match_midmap = [
+ \ ['luaFunction', 'return'],
+ \]
+let b:undo_ftplugin .= '| unlet! b:match_midmap'
+
+let &cpo = s:save_cpo
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/ocaml_matchup.vim b/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/ocaml_matchup.vim
new file mode 100644
index 0000000..5bd2d4f
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/ocaml_matchup.vim
@@ -0,0 +1,15 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+if !exists('g:loaded_matchup') || !exists('b:did_ftplugin')
+ finish
+endif
+
+let b:matchup_matchparen_timeout=100
+let b:undo_ftplugin .= ' | unlet! b:matchup_matchparen_timeout'
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/ruby_matchup.vim b/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/ruby_matchup.vim
new file mode 100644
index 0000000..6aeab49
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/ruby_matchup.vim
@@ -0,0 +1,25 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+if !exists('g:loaded_matchup') || !exists('b:did_ftplugin')
+ finish
+endif
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+call matchup#util#patch_match_words('retry', 'retry\|return')
+
+let b:match_midmap = [
+ \ ['rubyRepeat', 'next'],
+ \ ['rubyDefine', 'return'],
+ \]
+let b:undo_ftplugin .= '| unlet! b:match_midmap'
+
+let &cpo = s:save_cpo
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/tex_matchup.vim b/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/tex_matchup.vim
new file mode 100644
index 0000000..2f0c3d8
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/tex_matchup.vim
@@ -0,0 +1,117 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+if !exists('g:loaded_matchup') || !exists('b:did_ftplugin')
+ finish
+endif
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+function! s:has_plugin(plug)
+ return !empty(filter(split(&rtp,','), 'v:val =~? ''\<'.a:plug.'\>'''))
+endfunction
+
+let s:not_bslash = '\v%(\\@\|\\[|{}]\|.\)'
+ let l:match_words = '\\left\>'.l:delim
+ \ .':\\middle\>'.l:delim
+ \ .':\\right\>'.l:delim
+ let l:match_words .= ',\(\\[bB]igg\?\)l\>'.l:delim
+ \ . ':\1m\>'.l:delim
+ \ . ':\1r\>'.l:delim
+
+ " un-sided sized, left and right delimiters
+ let l:mod = '\(\\[bB]igg\?\)'
+ let l:wdelim = '\%(angle\|floor\|ceil\|[vV]ert\|brace\)\>'
+ let l:ldelim = '\%(\\l'.l:wdelim.'\|\\[lu]lcorner\>\|(\|\[\|\\{\)'
+ let l:mdelim = '\%(\\vert\>\||\|\\|\)'
+ let l:rdelim = '\%(\\r'.l:wdelim.'\|\\[lu]rcorner\>\|)\|]\|\\}\)'
+ let l:mtopt = '\%(\%(\w\[\)\@2'
+
+ " the curly braces
+ let l:match_words .= ',{:}'
+
+ " latex equation markers
+ let l:match_words .= ',\\(:\\),\\\[:\\]'
+
+ " simple blocks
+ let l:match_words .= ',\\if\%(\w\|@\)*\>:\\else\>:\\fi\>'
+ let l:match_words .= ',\\makeatletter:\\makeatother'
+ let l:match_words .= ',\\begingroup:\\endgroup,\\bgroup:\\egroup'
+
+ " environments
+ let l:match_words .= ',\\begin{tabular}'
+ \ . ':\\toprule\>:\\midrule\>:\\bottomrule\>'
+ \ . ':\\end{tabular}'
+
+ " enumerate, itemize
+ let l:match_words .= ',\\begin\s*{\(enumerate\*\=\|itemize\*\=\)}'
+ \ . ':\\item\>:\\end\s*{\1}'
+
+ " generic environment
+ let l:match_words .= ',\\begin\s*{\([^}]*\)}:\\end\s*{\1}'
+
+ return l:match_words
+endfunction
+
+function! s:setup_match_words()
+ setlocal matchpairs=(:),{:},[:]
+ let b:matchup_delim_nomatchpairs = 1
+ let b:match_words = s:get_match_words()
+
+ " the syntax method is too slow for latex
+ let b:match_skip = 'r:\\\@ %
+ silent! xunmap %
+ silent! ounmap %
+
+ " lervag/vimtex/issues/1051
+ let g:vimtex_matchparen_enabled = 0
+ silent! call vimtex#matchparen#disable()
+
+ call s:setup_match_words()
+ else
+ let b:matchup_matchparen_enabled = 0
+ let b:matchup_matchparen_fallback = 0
+ endif
+else
+ call s:setup_match_words()
+endif
+
+let &cpo = s:save_cpo
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/vim_matchup.vim b/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/vim_matchup.vim
new file mode 100644
index 0000000..8254d1a
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/vim_matchup.vim
@@ -0,0 +1,27 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+if !exists('g:loaded_matchup') || !exists('b:did_ftplugin')
+ finish
+endif
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+let b:match_skip = 's:comment\|string\|vimSynReg'
+ \ . '\|vimSet\|vimFuncName\|vimNotPatSep'
+ \ . '\|vimVar\|vimFuncVar\|vimFBVar\|vimOperParen'
+ \ . '\|vimUserFunc'
+
+call matchup#util#patch_match_words(
+ \ '\\)\@!\S:',
+ \ '\\)\@!\S:'
+ \)
+
+let &cpo = s:save_cpo
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/vue_matchup.vim b/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/vue_matchup.vim
new file mode 100644
index 0000000..ca47401
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/vue_matchup.vim
@@ -0,0 +1,10 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+runtime after/ftplugin/html_matchup.vim
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/xml_matchup.vim b/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/xml_matchup.vim
new file mode 100644
index 0000000..41b6d2b
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/after/ftplugin/xml_matchup.vim
@@ -0,0 +1,22 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+if !exists('g:loaded_matchup') || !exists('b:did_ftplugin')
+ finish
+endif
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+if matchup#util#matchpref('tagnameonly', 0)
+ call matchup#util#patch_match_words('\)\%(', '\)\g{hlend}\%(')
+ call matchup#util#patch_match_words('\)\%(', '\)\g{hlend}\%(')
+endif
+
+let &cpo = s:save_cpo
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/after/plugin/matchit.vim b/etc/soft/nvim/+plugins/vim-matchup/after/plugin/matchit.vim
new file mode 100644
index 0000000..e69de29
diff --git a/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup.vim b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup.vim
new file mode 100644
index 0000000..2ddadba
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup.vim
@@ -0,0 +1,362 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+function! matchup#init()
+ call matchup#perf#tic('loading')
+
+ call s:init_options()
+ call s:init_modules()
+ call s:init_default_mappings()
+
+ call matchup#perf#toc('loading', 'init_done')
+endfunction
+
+function! s:init_options()
+ call s:init_option('matchup_matchparen_enabled',
+ \ !(&t_Co < 8 && !has('gui_running')))
+ let l:offs = {'method': 'status'}
+ if !get(g:, 'matchup_matchparen_status_offscreen', 1)
+ let l:offs = {}
+ endif
+ if get(g:, 'matchup_matchparen_status_offscreen_manual', 0)
+ let l:offs.method = 'status_manual'
+ endif
+ if exists('g:matchup_matchparen_scrolloff')
+ let l:offs.scrolloff = g:matchup_matchparen_scrolloff
+ endif
+ call s:init_option('matchup_matchparen_offscreen', l:offs)
+ call s:init_option('matchup_matchparen_singleton', 0)
+ call s:init_option('matchup_matchparen_deferred', 0)
+ call s:init_option('matchup_matchparen_deferred_show_delay', 50)
+ call s:init_option('matchup_matchparen_deferred_hide_delay', 700)
+ call s:init_option('matchup_matchparen_deferred_fade_time', 0)
+ call s:init_option('matchup_matchparen_stopline', 400)
+ call s:init_option('matchup_matchparen_pumvisible', 1)
+ call s:init_option('matchup_matchparen_nomode', '')
+ call s:init_option('matchup_matchparen_hi_surround_always', 0)
+ call s:init_option('matchup_matchparen_hi_background', 0)
+
+ call s:init_option('matchup_matchparen_timeout',
+ \ get(g:, 'matchparen_timeout', 300))
+ call s:init_option('matchup_matchparen_insert_timeout',
+ \ get(g:, 'matchparen_insert_timeout', 60))
+
+ call s:init_option('matchup_delim_count_fail', 0)
+ call s:init_option('matchup_delim_count_max', 8)
+ call s:init_option('matchup_delim_start_plaintext', 1)
+ call s:init_option('matchup_delim_noskips', 0)
+
+ call s:init_option('matchup_motion_enabled', 1)
+ call s:init_option('matchup_motion_cursor_end', 1)
+ call s:init_option('matchup_motion_override_Npercent', 6)
+
+ call s:init_option('matchup_text_obj_enabled', 1)
+ call s:init_option('matchup_text_obj_linewise_operators', ['d', 'y'])
+
+ call s:init_option('matchup_transmute_enabled', 0)
+ call s:init_option('matchup_transmute_breakundo', 0)
+
+ call s:init_option('matchup_mouse_enabled', 1)
+
+ call s:init_option('matchup_surround_enabled', 0)
+
+ call s:init_option('matchup_where_enabled', 1)
+ call s:init_option('matchup_where_separator', '')
+
+ call s:init_option('matchup_matchpref', {})
+endfunction
+
+function! s:init_option(option, default)
+ let l:option = 'g:' . a:option
+ if !exists(l:option)
+ let {l:option} = a:default
+ endif
+endfunction
+
+function! s:init_modules()
+ for l:mod in [ 'loader', 'matchparen' ]
+ if !get(g:, 'matchup_'.l:mod.'_enabled', 1)
+ continue
+ endif
+ call matchup#perf#tic('loading_module')
+ call matchup#{l:mod}#init_module()
+ call matchup#perf#toc('loading_module', l:mod)
+ endfor
+
+ call s:motion_init_module()
+ call s:text_obj_init_module()
+ call s:misc_init_module()
+ call s:surround_init_module()
+ call s:where_init_module()
+endfunction
+
+function! s:init_oldstyle_ops() " {{{1
+ if get(g:, 'matchup_motion_enabled', 0)
+ \ || get(g:, 'matchup_text_obj_enabled', 0)
+ for l:opforce in ['', 'v', 'V', '']
+ call s:map('onore', ' (matchup-o_'.l:opforce.')',
+ \ 'force('''.l:opforce.''')')
+ endfor
+ endif
+
+ if get(g:, 'matchup_motion_enabled', 0)
+ for l:opforce in ['', 'v', 'V', '']
+ call s:map('o', l:opforce.'%',
+ \ '(matchup-o_'.l:opforce.')(matchup-%)')
+ call s:map('o', l:opforce.'g%',
+ \ '(matchup-o_'.l:opforce.')(matchup-g%)')
+ call s:map('o', l:opforce.']%',
+ \ '(matchup-o_'.l:opforce.')(matchup-]%)')
+ call s:map('o', l:opforce.'[%',
+ \ '(matchup-o_'.l:opforce.')(matchup-[%)')
+ call s:map('o', l:opforce.'z%',
+ \ '(matchup-o_'.l:opforce.')(matchup-z%)')
+ endfor
+ endif
+
+ if get(g:, 'matchup_text_obj_enabled', 0)
+ for l:opforce in ['', 'v', 'V', '']
+ call s:map('o', l:opforce.'i%',
+ \ '(matchup-o_'.l:opforce.')(matchup-i%)')
+ call s:map('o', l:opforce.'a%',
+ \ '(matchup-o_'.l:opforce.')(matchup-a%)')
+ endfor
+ endif
+endfunction
+
+function! s:make_oldstyle_omaps(lhs, rhs)
+ if !s:old_style_ops
+ return 0
+ endif
+ for l:opforce in ['', 'v', 'V', '']
+ silent! execute 'omap' l:opforce.a:lhs
+ \ '(matchup-o_'.l:opforce.')(matchup-'.a:rhs.')'
+ endfor
+ return 1
+endfunction
+
+let s:old_style_ops = !has('patch-8.1.0648')
+
+let g:v_motion_force = ''
+function! s:force(wise)
+ let g:v_motion_force = a:wise
+ return ''
+endfunction
+
+function! matchup#motion_force() abort
+ if !s:old_style_ops
+ let l:mode = mode(1)
+ let g:v_motion_force = len(l:mode) >= 3
+ \ && l:mode[0:1] ==# 'no' ? l:mode[2] : ''
+ endif
+ return g:v_motion_force
+endfunction
+
+" }}}1
+
+function! s:init_default_mappings()
+ if !get(g:,'matchup_mappings_enabled', 1) | return | endif
+
+ function! s:map(mode, lhs, rhs, ...)
+ if !hasmapto(a:rhs, a:mode)
+ \ && ((a:0 > 0) || (maparg(a:lhs, a:mode) ==# ''))
+ silent execute a:mode . 'map ' a:lhs a:rhs
+ endif
+ endfunction
+
+ if s:old_style_ops
+ call s:init_oldstyle_ops()
+ endif
+
+ " these won't conflict since matchit should not be loaded at this point
+ if get(g:, 'matchup_motion_enabled', 0)
+ call s:map('n', '%', '(matchup-%)' )
+ call s:map('n', 'g%', '(matchup-g%)')
+
+ call s:map('x', '%', '(matchup-%)' )
+ call s:map('x', 'g%', '(matchup-g%)')
+
+ call s:map('n', ']%', '(matchup-]%)')
+ call s:map('n', '[%', '(matchup-[%)')
+
+ call s:map('x', ']%', '(matchup-]%)')
+ call s:map('x', '[%', '(matchup-[%)')
+
+ call s:map('n', 'z%', '(matchup-z%)')
+ call s:map('x', 'z%', '(matchup-z%)')
+
+ if !s:old_style_ops
+ call s:map('o', '%', '(matchup-%)')
+ call s:map('o', 'g%', '(matchup-g%)')
+ call s:map('o', ']%', '(matchup-]%)')
+ call s:map('o', '[%', '(matchup-[%)')
+ call s:map('o', 'z%', '(matchup-z%)')
+ endif
+
+ call s:map('i', '%', '(matchup-c_g%)')
+ endif
+
+ if get(g:, 'matchup_text_obj_enabled', 0)
+ call s:map('x', 'i%', '(matchup-i%)')
+ call s:map('x', 'a%', '(matchup-a%)')
+
+ if !s:old_style_ops
+ call s:map('o', 'i%', '(matchup-i%)')
+ call s:map('o', 'a%', '(matchup-a%)')
+ endif
+ endif
+
+ if get(g:, 'matchup_mouse_enabled', 1)
+ call s:map('n', '<2-LeftMouse>', '(matchup-double-click)')
+ endif
+
+ if get(g:, 'matchup_surround_enabled', 0)
+ call s:map('n', 'ds%', '(matchup-ds%)')
+ call s:map('n', 'cs%', '(matchup-cs%)')
+ endif
+endfunction
+
+" module initialization
+
+function! s:motion_init_module() " {{{1
+ if !g:matchup_motion_enabled | return | endif
+
+ call matchup#perf#tic('loading_module')
+
+ " gets the current forced motion type
+ nnoremap (wise)
+ \ empty(g:v_motion_force) ? 'v' : g:v_motion_force
+
+ " the basic motions % and g%
+ nnoremap (matchup-%)
+ \ :call matchup#motion#find_matching_pair(0, 1)
+ nnoremap (matchup-g%)
+ \ :call matchup#motion#find_matching_pair(0, 0)
+
+ " visual and operator-pending
+ xnoremap (matchup-%)
+ \ :call matchup#motion#find_matching_pair(1, 1)
+ xmap (matchup-%) (matchup-%)
+ onoremap (matchup-%)
+ \ :call matchup#motion#op('%')
+
+ xnoremap (matchup-g%)
+ \ :call matchup#motion#find_matching_pair(1, 0)
+ xmap (matchup-g%) (matchup-g%)
+ onoremap (matchup-g%)
+ \ :call matchup#motion#op('g%')
+
+ " ]% and [%
+ nnoremap (matchup-]%)
+ \ :call matchup#motion#find_unmatched(0, 1)
+ nnoremap (matchup-[%)
+ \ :call matchup#motion#find_unmatched(0, 0)
+
+ xnoremap (matchup-]%)
+ \ :call matchup#motion#find_unmatched(1, 1)
+ xnoremap (matchup-[%)
+ \ :call matchup#motion#find_unmatched(1, 0)
+ xmap (matchup-]%) (matchup-]%)
+ xmap (matchup-[%) (matchup-[%)
+ onoremap (matchup-]%)
+ \ :call matchup#motion#op(']%')
+ onoremap (matchup-[%)
+ \ :call matchup#motion#op('[%')
+
+ " jump inside z%
+ nnoremap (matchup-z%)
+ \ :call matchup#motion#jump_inside(0)
+
+ xnoremap (matchup-z%)
+ \ :call matchup#motion#jump_inside(1)
+ xmap (matchup-z%) (matchup-z%)
+ onoremap (matchup-z%)
+ \ :call matchup#motion#op('z%')
+
+ inoremap (matchup-c_g%)
+ \ :call matchup#motion#insert_mode()
+
+ call matchup#perf#toc('loading_module', 'motion')
+endfunction
+
+" TODO redo this
+function! s:snr()
+ return str2nr(matchstr(expand(''), '\zs\d\+\ze_snr$'))
+endfunction
+let s:sid = printf("\%d_", s:snr())
+
+function! matchup#motion_sid()
+ return s:sid
+endfunction
+
+" }}}1
+function! s:text_obj_init_module() " {{{1
+ if !g:matchup_text_obj_enabled | return | endif
+
+ call matchup#perf#tic('loading_module')
+
+ for [l:map, l:name, l:opt] in [
+ \ ['%', 'delimited', 'delim_all'],
+ \]
+ let l:p1 = 'noremap (matchup-'
+ let l:p2 = l:map . ') :call matchup#text_obj#' . l:name
+ let l:p3 = empty(l:opt) ? ')' : ', ''' . l:opt . ''')'
+ execute 'x' . l:p1 . 'i' . l:p2 . '(1, 1' . l:p3
+ execute 'x' . l:p1 . 'a' . l:p2 . '(0, 1' . l:p3
+ execute 'o' . l:p1 . 'i' . l:p2 . '(1, 0' . l:p3
+ execute 'o' . l:p1 . 'a' . l:p2 . '(0, 0' . l:p3
+ endfor
+
+ nnoremap (matchup-double-click)
+ \ :call matchup#text_obj#double_click()
+
+ call matchup#perf#toc('loading_module', 'motion')
+endfunction
+
+" }}}1
+function! s:misc_init_module() " {{{1
+ call matchup#perf#tic('loading_module')
+ command! MatchupReload call matchup#misc#reload()
+ nnoremap (matchup-reload) :MatchupReload
+ call matchup#perf#toc('loading_module', 'misc')
+endfunction
+
+" }}}1
+function! s:surround_init_module() " {{{1
+ if !g:matchup_surround_enabled | return | endif
+
+ call matchup#perf#tic('loading_module')
+
+ for [l:map, l:name, l:opt] in [
+ \ ['%', 'delimited', 'delim_all'],
+ \]
+ let l:p1 = 'noremap (matchup-'
+ let l:p2 = l:map . ') :call matchup#surround#' . l:name
+ let l:p3 = empty(l:opt) ? ')' : ', ''' . l:opt . ''')'
+ execute 'n' . l:p1 . 'ds' . l:p2 . '(0, "d"' . l:p3
+ execute 'n' . l:p1 . 'cs' . l:p2 . '(0, "c"' . l:p3
+ endfor
+
+ call matchup#perf#toc('loading_module', 'surround')
+endfunction
+
+" }}}1
+function! s:where_init_module() " {{{1
+ if !g:matchup_where_enabled | return | endif
+
+ command! -nargs=? -bang MatchupWhereAmI
+ \ call matchup#where#print('' . )
+endfunction
+
+" }}}1
+
+let &cpo = s:save_cpo
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/custom.vim b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/custom.vim
new file mode 100644
index 0000000..7dda34f
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/custom.vim
@@ -0,0 +1,152 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+""
+" example motion as described in issues/49:
+" - if on a delim, go to {count} next matching delim, up or down
+" - if not on a delim, go to {count} local surrounding, up or down
+"
+" {info} dict with the following fields:
+" visual : 1 if visual or operator mode
+" count/count1: v:count/v:count1 for this map
+" operator : if non-empty, the operator in op mode
+" motion_force: forced operator mode, e.g, for 'dvx' this is 'v'
+" {opts} user data dict from motion definition
+function! matchup#custom#example_motion(info, opts) abort
+ let l:delim = matchup#delim#get_current('all', 'both_all')
+ if !empty(l:delim)
+ let l:matches = matchup#delim#get_matching(l:delim, 1)
+ if len(l:matches)
+ for _ in range(a:info.count1)
+ let l:delim = l:delim.links[a:opts.down ? 'next': 'prev']
+ endfor
+ return matchup#custom#suggest_pos(l:delim, a:opts)
+ endif
+ endif
+
+ let [l:open_, l:close_] = matchup#delim#get_surrounding(
+ \ 'delim_all', v:count1)
+ if empty(l:open_) || empty(l:close_)
+ return []
+ endif
+ let [l:open, l:close] = matchup#delim#get_surround_nearest(l:open_)
+ if empty(l:open)
+ let [l:open, l:close] = [l:open_, l:open_.links.next]
+ endif
+ let l:delim = a:opts.down ? l:close : l:open
+
+ " exclude delim in operators unless v is given
+ if !empty(a:info.operator) && a:info.motion_force !=# 'v'
+ if a:opts.down
+ return matchup#pos#prev(l:delim)
+ else
+ return matchup#pos#next(matchup#delim#end_pos(l:delim))
+ endif
+ else
+ return matchup#custom#suggest_pos(l:delim, a:opts)
+ endif
+endfunction
+
+""
+" api function: get the preferred cursor location for delim
+" {delim} delimiter object
+" {opts} field 'down' denotes motion direction
+function! matchup#custom#suggest_pos(delim, opts) abort
+ if g:matchup_motion_cursor_end && (a:delim.side ==# 'close'
+ \ || a:delim.side ==# 'mid' && get(a:opts, 'down', 0))
+ return [a:delim.lnum, matchup#delim#jump_target(a:delim)]
+ endif
+ return matchup#pos#(a:delim)
+endfunction
+
+""
+" define a custom motion
+" {modes} specify which modes modes of {n,o,x} the mapping is active in
+" {keys} key sequence for map
+" {fcn} function to call, must take two arguments
+" [@opts] user data dict passed to function
+function! matchup#custom#define_motion(modes, keys, fcn, ...) abort
+ if a:modes !~# '^[nox]\+$'
+ echoerr "invalid modes"
+ endif
+
+ let s:custom_counter += 1
+ let l:k = s:custom_counter
+ let l:opts = a:0 ? deepcopy(a:1) : {}
+ call extend(l:opts, { 'fcn': a:fcn, 'keys': a:keys })
+ let s:custom_opts[l:k] = l:opts
+
+ if a:modes =~# 'n'
+ execute 'nnoremap (matchup-custom-'.a:keys.')'
+ \ ':call matchup#custom#wrap(0, '.l:k.')'
+ execute 'nmap' a:keys '(matchup-custom-'.a:keys.')'
+ endif
+
+ if a:modes =~# '[xo]'
+ let l:sid = substitute(matchup#motion_sid(), "\", '', '')
+ execute 'xnoremap ' l:sid.'(matchup-custom-'.l:k.')'
+ \ ':call matchup#custom#wrap(1, '.l:k.')'
+ endif
+
+ if a:modes =~# 'x'
+ execute 'xmap (matchup-custom-'.a:keys.')'
+ \ l:sid.'(matchup-custom-'.l:k.')'
+ execute 'xmap ' a:keys '(matchup-custom-'.a:keys.')'
+ endif
+
+ if a:modes =~# 'o'
+ execute 'onoremap (matchup-custom-'.a:keys.')'
+ \ ':call matchup#motion#op('
+ \ . string('custom-'.l:k).')'
+ if !call(matchup#motion_sid().'make_oldstyle_omaps',
+ \ [a:keys, 'custom-'.a:keys])
+ execute 'omap' a:keys '(matchup-custom-'.a:keys.')'
+ endif
+ endif
+endfunction
+
+if !exists('s:custom_opts')
+ let s:custom_opts = {}
+ let s:custom_counter = 0
+endif
+
+" motion wrapper
+function! matchup#custom#wrap(visual, id) abort
+ " default to 1 second (can override in custom motion)
+ call matchup#perf#timeout_start(1000)
+
+ let l:info = {
+ \ 'visual': a:visual,
+ \ 'count': v:count,
+ \ 'count1': v:count1,
+ \ 'operator': matchup#motion#getoper(),
+ \ 'motion_force': g:v_motion_force,
+ \}
+ let l:is_oper = !empty(l:info.operator)
+ let l:opts = s:custom_opts[a:id]
+
+ if a:visual
+ normal! gv
+ endif
+
+ let l:ret = call(l:opts.fcn, [l:info, l:opts])
+
+ if type(l:ret) != type([]) || empty(l:ret)
+ if !a:visual || l:is_oper
+ execute "normal! \"
+ endif
+ elseif type(l:ret) == type([]) && len(l:ret) >= 2
+ call matchup#pos#set_cursor(l:ret)
+ endif
+endfunction
+
+let &cpo = s:save_cpo
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/delim.vim b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/delim.vim
new file mode 100644
index 0000000..942e5e7
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/delim.vim
@@ -0,0 +1,918 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+function! matchup#delim#get_next(type, side, ...) " {{{1
+ return s:get_delim(extend({
+ \ 'direction' : 'next',
+ \ 'type' : a:type,
+ \ 'side' : a:side,
+ \}, get(a:, '1', {})))
+endfunction
+
+" }}}1
+function! matchup#delim#get_prev(type, side, ...) " {{{1
+ return s:get_delim(extend({
+ \ 'direction' : 'prev',
+ \ 'type' : a:type,
+ \ 'side' : a:side,
+ \}, get(a:, '1', {})))
+endfunction
+
+" }}}1
+function! matchup#delim#get_current(type, side, ...) " {{{1
+ return s:get_delim(extend({
+ \ 'direction' : 'current',
+ \ 'type' : a:type,
+ \ 'side' : a:side,
+ \}, get(a:, '1', {})))
+endfunction
+
+" }}}1
+
+function! matchup#delim#get_matching(delim, ...) " {{{1
+ if empty(a:delim) || !has_key(a:delim, 'lnum') | return {} | endif
+
+ let l:opts = a:0 && type(a:1) == type({}) ? a:1 : {}
+ let l:stopline = get(l:opts, 'stopline', s:stopline)
+
+ " get all the matching position(s)
+ " *important*: in the case of mid, we search up before searching down
+ " this gives us a context object which we use for the other side
+ " TODO: what if no open is found here?
+ let l:matches = []
+ let l:save_pos = matchup#pos#get_cursor()
+ for l:down in {'open': [1], 'close': [0], 'mid': [0,1]}[a:delim.side]
+ call matchup#pos#set_cursor(a:delim)
+
+ " second iteration: [] refers to the current match
+ if !empty(l:matches)
+ call add(l:matches, [])
+ endif
+
+ let l:res = a:delim.get_matching(l:down, l:stopline)
+ if l:res[0][1] > 0
+ call extend(l:matches, l:res)
+ elseif l:down
+ let l:matches = []
+ endif
+ endfor
+ call matchup#pos#set_cursor(l:save_pos)
+
+ if a:delim.side ==# 'open'
+ call insert(l:matches, [])
+ endif
+ if a:delim.side ==# 'close'
+ call add(l:matches, [])
+ endif
+
+ " create the match result(s)
+ let l:matching_list = []
+ for l:i in range(len(l:matches))
+ if empty(l:matches[l:i])
+ let a:delim.match_index = l:i
+ call add(l:matching_list, a:delim)
+ continue
+ end
+
+ let [l:match, l:lnum, l:cnum] = l:matches[l:i]
+
+ let l:matching = copy(a:delim)
+ let l:matching.class = copy(a:delim.class)
+
+ let l:matching.lnum = l:lnum
+ let l:matching.cnum = l:cnum
+ let l:matching.match = l:match
+ let l:matching.side = l:i == 0 ? 'open'
+ \ : l:i == len(l:matches)-1 ? 'close' : 'mid'
+ let l:matching.class[1] = 'FIXME'
+ let l:matching.match_index = l:i
+
+ call add(l:matching_list, l:matching)
+ endfor
+
+ " set up links between matches
+ for l:i in range(len(l:matching_list))
+ let l:c = l:matching_list[l:i]
+ let l:c.links = {}
+ let l:c.links.next = l:matching_list[(l:i+1) % len(l:matching_list)]
+ let l:c.links.prev = l:matching_list[l:i-1]
+ let l:c.links.open = l:matching_list[0]
+ let l:c.links.close = l:matching_list[-1]
+ endfor
+
+ return l:matching_list
+endfunction
+
+" }}}1
+
+function! matchup#delim#get_surrounding(type, ...) " {{{1
+ call matchup#perf#tic('delim#get_surrounding')
+
+ let l:save_pos = matchup#pos#get_cursor()
+ let l:pos_val_cursor = matchup#pos#val(l:save_pos)
+ let l:pos_val_last = l:pos_val_cursor
+ let l:pos_val_open = l:pos_val_cursor - 1
+
+ let l:count = a:0 >= 1 ? a:1 : 1
+ let l:counter = l:count
+
+ " third argument specifies local any block, otherwise,
+ " provided count == 0 refers to local any block
+ let l:opts = a:0 >= 2 ? a:2 : {}
+ let l:local = get(l:opts, 'local', l:count == 0 ? 1 : 0)
+
+ let l:delimopts = {}
+ let s:invert_skip = 0 " TODO: this logic is still bad
+ if matchup#delim#skip() " TODO: check for insert mode (?)
+ let l:delimopts.check_skip = 0
+ endif
+ " TODO: pin skip
+ if get(l:opts, 'check_skip', 0)
+ let l:delimopts.check_skip = 1
+ endif
+
+ " keep track of the outermost pair found so far
+ " returned when g:matchup_delim_count_fail = 1
+ let l:best = []
+
+ while l:pos_val_open < l:pos_val_last
+ let l:open = matchup#delim#get_prev(a:type,
+ \ l:local ? 'open_mid' : 'open', l:delimopts)
+ if empty(l:open) | break | endif
+
+ " if configured, we may still accept this match
+ if matchup#perf#timeout_check() && !g:matchup_delim_count_fail
+ break
+ endif
+
+ let l:matches = matchup#delim#get_matching(l:open, 1)
+
+ " TODO: getting one match result here is surely wrong
+ if len(l:matches) == 1
+ let l:matches = []
+ endif
+
+ if has_key(l:opts, 'matches')
+ let l:opts.matches = l:matches
+ endif
+
+ if len(l:matches)
+ let l:close = l:local ? l:open.links.next : l:open.links.close
+ let l:pos_val_try = matchup#pos#val(l:close)
+ \ + matchup#delim#end_offset(l:close)
+ endif
+
+ if len(l:matches) && l:pos_val_try >= l:pos_val_cursor
+ if l:counter <= 1
+ " restore cursor and accept
+ call matchup#pos#set_cursor(l:save_pos)
+ call matchup#perf#toc('delim#get_surrounding', 'accept')
+ return [l:open, l:close]
+ endif
+ let l:counter -= 1
+ let l:best = [l:open, l:close]
+ else
+ let l:pos_val_last = l:pos_val_open
+ let l:pos_val_open = matchup#pos#val(l:open)
+ endif
+
+ if l:open.lnum == 1 && l:open.cnum == 1
+ break
+ endif
+ call matchup#pos#set_cursor(matchup#pos#prev(l:open))
+ endwhile
+
+ if !empty(l:best) && g:matchup_delim_count_fail
+ call matchup#pos#set_cursor(l:save_pos)
+ call matchup#perf#toc('delim#get_surrounding', 'bad_count')
+ return l:best
+ endif
+
+ " restore cursor and return failure
+ call matchup#pos#set_cursor(l:save_pos)
+ call matchup#perf#toc('delim#get_surrounding', 'fail')
+ return [{}, {}]
+endfunction
+
+" }}}1
+function! matchup#delim#get_surround_nearest(open, ...) " {{{1
+ " finds the first consecutive pair whose start
+ " positions surround pos (default to the cursor)
+ let l:cur_pos = a:0 ? a:1 : matchup#pos#get_cursor()
+ let l:pos_val_cursor = matchup#pos#val(l:cur_pos)
+ let l:pos_val_open = matchup#pos#val(a:open)
+
+ let l:pos_val_prev = l:pos_val_open
+ let l:delim = a:open.links.next
+ let l:pos_val_next = matchup#pos#val(l:delim)
+ while l:pos_val_next > l:pos_val_open
+ let l:end_offset = matchup#delim#end_offset(l:delim)
+ if l:pos_val_prev <= l:pos_val_cursor
+ \ && l:pos_val_next + l:end_offset >= l:pos_val_cursor
+ return [l:delim.links.prev, l:delim]
+ endif
+ let l:pos_val_prev = l:pos_val_next
+ let l:delim = l:delim.links.next
+ let l:pos_val_next = matchup#pos#val(l:delim)
+ endwhile
+
+ return [{}, {}]
+endfunction
+
+" }}}1
+
+function! matchup#delim#jump_target(delim) " {{{1
+ let l:save_pos = matchup#pos#get_cursor()
+
+ " unicode note: technically wrong, but works in practice
+ " since the cursor snaps back to start of multi-byte chars
+ let l:column = a:delim.cnum
+ let l:column += strlen(a:delim.match) - 1
+
+ if strlen(a:delim.match) < 2
+ return l:column
+ endif
+
+ for l:tries in range(strlen(a:delim.match)-1)
+ call matchup#pos#set_cursor(a:delim.lnum, l:column)
+
+ let l:delim_test = matchup#delim#get_current('all', 'both_all')
+ if l:delim_test.class[0] ==# a:delim.class[0]
+ break
+ endif
+
+ let l:column -= 1
+ endfor
+
+ call matchup#pos#set_cursor(l:save_pos)
+ return l:column
+endfunction
+
+" }}}1
+function! matchup#delim#end_offset(delim) " {{{1
+ return max([0, match(a:delim.match, '.$')])
+endfunction
+
+" }}}1
+function! matchup#delim#end_pos(delim) abort " {{{1
+ return [a:delim.lnum, a:delim.cnum + matchup#delim#end_offset(a:delim)]
+endfunction
+
+" }}}1
+
+function! s:get_delim(opts) " {{{1
+ " arguments: {{{2
+ " opts = {
+ " 'direction' : 'next' | 'prev' | 'current'
+ " 'type' : 'delim_tex'
+ " | 'delim_all'
+ " | 'all'
+ " 'side' : 'open' | 'close'
+ " | 'both' | 'mid'
+ " | 'both_all' | 'open_mid'
+ " }
+ "
+ " }}}2
+ " returns: {{{2
+ " delim = {
+ " type : 'delim'
+ " lnum : line number
+ " cnum : column number
+ " match : the actual text match
+ " augment : how to match a corresponding open
+ " groups : dict of captured groups
+ " side : 'open' | 'close' | 'mid'
+ " class : [ c1, c2 ] identifies the kind of match_words
+ " regexone : the regex item, like \1foo
+ " regextwo : the regex_capture item, like \(group\)foo
+ " }
+ "
+ " }}}2
+
+ if !get(b:, 'matchup_delim_enabled', 0)
+ return {}
+ endif
+
+ call matchup#perf#tic('s:get_delim')
+
+ let l:save_pos = matchup#pos#get_cursor()
+
+ call matchup#loader#refresh_match_words()
+
+ " this contains all the patterns for the specified type and side
+ let l:re = b:matchup_delim_re[a:opts.type][a:opts.side]
+
+ let l:cursorpos = col('.')
+
+ let l:insertmode = get(a:opts, 'insertmode', 0)
+ if l:cursorpos > 1 && l:insertmode
+ let l:cursorpos -= 1
+ endif
+ if l:cursorpos > strlen(getline('.'))
+ \ && stridx("vV\", mode()) > -1
+ let l:cursorpos -= 1
+ endif
+
+ let s:invert_skip = 0
+
+ if a:opts.direction ==# 'current'
+ let l:check_skip = get(a:opts, 'check_skip',
+ \ g:matchup_delim_noskips >= 2
+ \ || g:matchup_delim_noskips >= 1
+ \ && getline(line('.'))[l:cursorpos-1] =~ '[^[:punct:]]')
+ if l:check_skip && matchup#delim#skip(line('.'), l:cursorpos)
+ return {}
+ endif
+ else
+ " check skip if cursor is not currently in skip
+ let l:check_skip = get(a:opts, 'check_skip',
+ \ !matchup#delim#skip(line('.'), l:cursorpos)
+ \ || g:matchup_delim_noskips >= 2)
+ endif
+
+ let a:opts.cursorpos = l:cursorpos
+
+ " for current, we want to find matches that end after the cursor
+ " note: we expect this to give false-positives with \ze
+ if a:opts.direction ==# 'current'
+ let l:re .= '\%>'.(l:cursorpos).'c'
+ " let l:re = '\%<'.(l:cursorpos+1).'c' . l:re
+ endif
+
+ " allow overlapping delimiters
+ " without this, the > in would not be found
+ if b:matchup_delim_re[a:opts.type]._engine_info.has_zs[a:opts.side]
+ let l:save_cpo = &cpo
+ noautocmd set cpo-=c
+ else
+ " faster than changing cpo but doesn't work right with \zs
+ let l:re .= '\&'
+ endif
+
+ " move cursor one left for searchpos if necessary
+ let l:need_restore_cursor = 0
+ if l:insertmode
+ call matchup#pos#set_cursor(line('.'), col('.')-1)
+ let l:need_restore_cursor = 1
+ endif
+
+ " stopline may depend on the current action
+ let l:stopline = get(a:opts, 'stopline', s:stopline)
+
+ " in the first pass, we get matching line and column numbers
+ " this is intended to be as fast as possible, with no capture groups
+ " we look for a match on this line (if direction == current)
+ " or forwards or backwards (if direction == next or prev)
+ " for current, we actually search leftwards from the cursor
+ while 1
+ let l:to = matchup#perf#timeout()
+ let [l:lnum, l:cnum] = a:opts.direction ==# 'next'
+ \ ? searchpos(l:re, 'cnW', line('.') + l:stopline, l:to)
+ \ : a:opts.direction ==# 'prev'
+ \ ? searchpos(l:re, 'bcnW',
+ \ max([line('.') - l:stopline, 1]), l:to)
+ \ : searchpos(l:re, 'bcnW', line('.'), l:to)
+ if l:lnum == 0 | break | endif
+
+ " note: the skip here should not be needed
+ " in 'current' mode, but be explicit
+ if a:opts.direction !=# 'current'
+ \ && (l:check_skip || g:matchup_delim_noskips == 1
+ \ && getline(l:lnum)[l:cnum-1] =~ '[^[:punct:]]')
+ \ && matchup#delim#skip(l:lnum, l:cnum)
+ \ && (a:opts.direction ==# 'prev' ? (l:lnum > 1 || l:cnum > 1)
+ \ : (l:lnum < line('$') || l:cnum < len(getline('$'))))
+
+ " invalid match, move cursor and keep looking
+ call matchup#pos#set_cursor(a:opts.direction ==# 'next'
+ \ ? matchup#pos#next(l:lnum, l:cnum)
+ \ : matchup#pos#prev(l:lnum, l:cnum))
+ let l:need_restore_cursor = 1
+ continue
+ endif
+
+ break
+ endwhile
+
+ " restore cpo if necessary
+ " note: this messes with cursor position
+ if exists('l:save_cpo')
+ noautocmd let &cpo = l:save_cpo
+ let l:need_restore_cursor = 1
+ endif
+
+ " restore cursor
+ if l:need_restore_cursor
+ call matchup#pos#set_cursor(l:save_pos)
+ endif
+
+ call matchup#perf#toc('s:get_delim', 'first_pass')
+
+ " nothing found, leave now
+ if l:lnum == 0
+ call matchup#perf#toc('s:get_delim', 'nothing_found')
+ return {}
+ endif
+
+ if matchup#perf#timeout_check()
+ return {}
+ endif
+
+ let l:skip_state = 0
+ if !l:check_skip && (!&synmaxcol || l:cnum <= &synmaxcol)
+ " XXX: workaround an apparent obscure vim bug where the
+ " reported syntax id is incorrect on the first synID() call
+ call matchup#delim#skip(l:lnum, l:cnum)
+ if matchup#perf#timeout_check()
+ return {}
+ endif
+
+ let l:skip_state = matchup#delim#skip(l:lnum, l:cnum)
+ endif
+
+ " now we get more data about the match in this position
+ " there may be capture groups which need to be stored
+
+ " result stub, to be filled by the parser when there is a match
+ let l:result = {
+ \ 'lnum' : l:lnum,
+ \ 'cnum' : l:cnum,
+ \ 'type' : '',
+ \ 'match' : '',
+ \ 'augment' : '',
+ \ 'groups' : '',
+ \ 'side' : '',
+ \ 'class' : [],
+ \ 'regexone' : '',
+ \ 'regextwo' : '',
+ \ 'skip' : l:skip_state,
+ \}
+
+ for l:type in s:types[a:opts.type]
+ let l:parser_result = l:type.parser(l:lnum, l:cnum, a:opts)
+ if !empty(l:parser_result)
+ let l:result = extend(l:parser_result, l:result, 'keep')
+ break
+ endif
+ endfor
+
+ call matchup#perf#toc('s:get_delim', 'got_results')
+
+ return empty(l:result.type) ? {} : l:result
+endfunction
+
+" }}}1
+
+function! s:parser_delim_new(lnum, cnum, opts) " {{{1
+ let l:cursorpos = a:opts.cursorpos
+ let l:found = 0
+
+ let l:sides = matchup#loader#sidedict()[a:opts.side]
+ let l:rebrs = b:matchup_delim_lists[a:opts.type].regex_capture
+
+ " use b:match_ignorecase
+ let l:ic = get(b:, 'match_ignorecase', 0) ? '\c' : '\C'
+
+ " loop through all (index, side) pairs,
+ let l:ns = len(l:sides)
+ let l:found = 0
+ for l:i in range(len(l:rebrs)*l:ns)
+ let l:side = l:sides[ l:i % l:ns ]
+
+ if l:side ==# 'mid'
+ let l:res = l:rebrs[l:i / l:ns].mid_list
+ if empty(l:res) | continue | end
+ else
+ let l:res = [ l:rebrs[l:i / l:ns][l:side] ]
+ if empty(l:res[0]) | continue | end
+ endif
+
+ " if pattern may contain \zs, extra processing is required
+ let l:extra_info = l:rebrs[l:i / l:ns].extra_info
+ let l:has_zs = get(l:extra_info, 'has_zs', 0)
+
+ let l:mid_id = 0
+ for l:re in l:res
+ let l:mid_id += 1
+
+ " check whether hlend needs to be handled
+ let l:id = l:side ==# 'mid' ? l:mid_id : l:side ==# 'open' ? 0 : -1
+ let l:extra_entry = l:rebrs[l:i / l:ns].extra_list[l:id]
+ let l:has_hlend = has_key(l:extra_entry, 'hlend')
+
+ if l:has_hlend && get(a:opts, 'highlighting', 0)
+ let l:re = s:process_hlend(l:re, l:cursorpos)
+ endif
+
+ " prepend the column number and append the cursor column
+ " to anchor the match; we don't use {start} for matchlist
+ " because there may be zero-width look behinds
+ let l:re_anchored = l:ic . s:anchor_regex(l:re, a:cnum, l:has_zs)
+
+ " for current we want the first match which the cursor is inside
+ if a:opts.direction ==# 'current'
+ let l:re_anchored .= '\%>'.(l:cursorpos).'c'
+ endif
+
+ let l:matches = matchlist(getline(a:lnum), l:re_anchored)
+ if empty(l:matches) | continue | endif
+
+ " reject matches which the cursor is outside of
+ " this matters only for \ze
+ if !l:has_hlend && a:opts.direction ==# 'current'
+ \ && a:cnum + strlen(l:matches[0]) <= l:cursorpos
+ continue
+ endif
+
+ " if pattern contains \zs we need to re-check the starting column
+ if l:has_zs && match(getline(a:lnum), l:re_anchored) != a:cnum-1
+ continue
+ endif
+
+ let l:found = 1
+ break
+ endfor
+
+ if !l:found | continue | endif
+
+ break
+ endfor
+
+ " reset ignorecase (defunct)
+
+ if !l:found
+ return {}
+ endif
+
+ let l:match = l:matches[0]
+
+ let l:list = b:matchup_delim_lists[a:opts.type]
+ let l:thisre = l:list.regex[l:i / l:ns]
+ let l:thisrebr = l:list.regex_capture[l:i / l:ns]
+
+ let l:augment = {}
+
+ " these are the capture groups indexed by their 'open' id
+ let l:groups = {}
+ let l:id = 0
+
+ if l:side ==# 'open'
+ " XXX we might as well store all the groups...
+ "for l:br in keys(l:thisrebr.need_grp)
+ for l:br in range(1,9)
+ if empty(l:matches[l:br]) | continue | endif
+ let l:groups[l:br] = l:matches[l:br]
+ endfor
+ else
+ let l:id = (l:side ==# 'close')
+ \ ? len(l:thisrebr.mid_list)+1
+ \ : l:mid_id
+
+ if has_key(l:thisrebr.grp_renu, l:id)
+ for [l:br, l:to] in items(l:thisrebr.grp_renu[l:id])
+ let l:groups[l:to] = l:matches[l:br]
+ endfor
+ endif
+
+ " fill in augment pattern
+ " TODO all the augment patterns should match,
+ " but checking might be too slow
+ if has_key(l:thisrebr.aug_comp, l:id)
+ let l:aug = l:thisrebr.aug_comp[l:id][0]
+ let l:augment.str = matchup#delim#fill_backrefs(
+ \ l:aug.str, l:groups, 0)
+ let l:augment.unresolved = deepcopy(l:aug.outputmap)
+ endif
+ endif
+
+ let l:result = {
+ \ 'type' : 'delim_tex',
+ \ 'match' : l:match,
+ \ 'augment' : l:augment,
+ \ 'groups' : l:groups,
+ \ 'side' : l:side,
+ \ 'class' : [(l:i / l:ns), l:id],
+ \ 'get_matching' : s:basetypes['delim_tex'].get_matching,
+ \ 'regexone' : l:thisre,
+ \ 'regextwo' : l:thisrebr,
+ \ 'midmap' : get(l:list, 'midmap', {}),
+ \ 'highlighting' : get(a:opts, 'highlighting', 0),
+ \}
+
+ return l:result
+endfunction
+" }}}1
+
+function! s:get_matching_delims(down, stopline) dict " {{{1
+ " called as: a:delim.get_matching(...)
+ " called from: matchup#delim#get_matching <- matchparen, motion
+ " from: matchup#delim#get_surrounding <- matchparen, motion, text_obj
+
+ call matchup#perf#tic('get_matching_delims')
+
+ " first, we figure out what the furthest match is, which will be
+ " either the open or close depending on the direction
+ let [l:re, l:flags, l:stopline] = a:down
+ \ ? [self.regextwo.close, 'W', line('.') + a:stopline]
+ \ : [self.regextwo.open, 'bW', max([line('.') - a:stopline, 1])]
+
+ " these are the anchors for searchpairpos
+ let l:open = self.regexone.open " TODO is this right? BADLOGIC
+ let l:close = self.regexone.close
+
+ " if we're searching up, we anchor by the augment, if it exists
+ if !a:down && !empty(self.augment)
+ let l:open = self.augment.str
+ endif
+
+ " TODO temporary workaround for BADLOGIC
+ if a:down && self.side ==# 'mid'
+ let l:open = self.regextwo.open
+ endif
+
+ " turn \(\) into \%(\) for searchpairpos
+ let l:open = matchup#loader#remove_capture_groups(l:open)
+ let l:close = matchup#loader#remove_capture_groups(l:close)
+
+ " fill in back-references
+ " TODO: BADLOGIC2: when going up we don't have these groups yet..
+ " the second anchor needs to be mid/self for mid self
+ let l:open = matchup#delim#fill_backrefs(l:open, self.groups, 0)
+ let l:close = matchup#delim#fill_backrefs(l:close, self.groups, 0)
+
+ let s:invert_skip = self.skip
+ if empty(b:matchup_delim_skip)
+ let l:skip = 'matchup#delim#skip_default()'
+ else
+ let l:skip = 'matchup#delim#skip0()'
+ endif
+
+ " disambiguate matches for languages like julia, matlab, ruby, etc
+ if !empty(self.midmap)
+ let l:midmap = self.midmap.elements
+ if self.side ==# 'mid'
+ let l:idx = filter(range(len(l:midmap)),
+ \ 'self.match =~# l:midmap[v:val][1]')
+ else
+ let l:syn = synIDattr(synID(self.lnum, self.cnum, 0), 'name')
+ let l:idx = filter(range(len(l:midmap)),
+ \ 'l:syn =~# l:midmap[v:val][0]')
+ endif
+ if len(l:idx)
+ let l:valid = l:midmap[l:idx[0]]
+ let l:skip = printf('matchup#delim#skip1(%s, %s)',
+ \ string(l:midmap[l:idx[0]]), string(l:skip))
+ else
+ let l:skip = printf('matchup#delim#skip2(%s, %s)',
+ \ string(self.midmap.strike), string(l:skip))
+ endif
+ endif
+
+ if matchup#perf#timeout_check() | return [['', 0, 0]] | endif
+
+ " improves perceptual performance in insert mode
+ if mode() ==# 'i' || mode() ==# 'R'
+ if !g:matchup_matchparen_deferred
+ sleep 1m
+ endif
+ endif
+
+ " use b:match_ignorecase
+ let l:ic = get(b:, 'match_ignorecase', 0) ? '\c' : '\C'
+ let l:open = l:ic . l:open
+ let l:close = l:ic . l:close
+
+ let [l:lnum_corr, l:cnum_corr] = searchpairpos(l:open, '', l:close,
+ \ 'n'.l:flags, l:skip, l:stopline, matchup#perf#timeout())
+
+ call matchup#perf#toc('get_matching_delims', 'initial_pair')
+
+ " if nothing found, bail immediately
+ if l:lnum_corr == 0
+ " reset ignorecase (defunct)
+
+ return [['', 0, 0]]
+ endif
+
+ " when highlighting, respect hlend
+ let l:extra_entry = self.regextwo.extra_list[a:down ? -1 : 0]
+ if self.highlighting && has_key(l:extra_entry, 'hlend')
+ let l:re = s:process_hlend(l:re, -1)
+ endif
+
+ " get the match and groups
+ let l:has_zs = self.regextwo.extra_info.has_zs
+ let l:re_anchored = l:ic . s:anchor_regex(l:re, l:cnum_corr, l:has_zs)
+ let l:matches = matchlist(getline(l:lnum_corr), l:re_anchored)
+ let l:match_corr = l:matches[0]
+
+ " reset ignorecase (defunct)
+
+ " store these in these groups
+ if a:down
+ " let l:id = len(self.regextwo.mid_list)+1
+ " for [l:from, l:to] in items(self.regextwo.grp_renu[l:id])
+ " let self.groups[l:to] = l:matches[l:from]
+ " endfor
+ else
+ for l:to in range(1,9)
+ if !has_key(self.groups, l:to) && !empty(l:matches[l:to])
+ let self.groups[l:to] = l:matches[l:to]
+ endif
+ endfor
+ endif
+
+ call matchup#perf#toc('get_matching_delims', 'get_matches')
+
+ " fill in additional groups
+ let l:mids = matchup#loader#remove_capture_groups(self.regexone.mid)
+ let l:mids = matchup#delim#fill_backrefs(l:mids, self.groups, 1)
+
+ " if there are no mids, we're done
+ if empty(l:mids)
+ return [[l:match_corr, l:lnum_corr, l:cnum_corr]]
+ endif
+
+ let l:re = l:mids
+
+ " when highlighting, respect hlend
+ if get(self.regextwo.extra_info, 'mid_hlend') && self.highlighting
+ let l:re = s:process_hlend(l:re, -1)
+ endif
+
+ " use b:match_ignorecase
+ let l:mid = l:ic . l:mids
+ let l:re = l:ic . l:re
+
+ let l:list = []
+ while 1
+ if matchup#perf#timeout_check() | break | endif
+
+ let [l:lnum, l:cnum] = searchpairpos(l:open, l:mids, l:close,
+ \ l:flags, l:skip, l:lnum_corr, matchup#perf#timeout())
+ if l:lnum <= 0 | break | endif
+
+ if a:down
+ if l:lnum > l:lnum_corr || l:lnum == l:lnum_corr
+ \ && l:cnum >= l:cnum_corr | break | endif
+ else
+ if l:lnum < l:lnum_corr || l:lnum == l:lnum_corr
+ \ && l:cnum <= l:cnum_corr | break | endif
+ endif
+
+ let l:re_anchored = s:anchor_regex(l:re, l:cnum, l:has_zs)
+ let l:matches = matchlist(getline(l:lnum), l:re_anchored)
+ if empty(l:matches)
+ " this should never happen
+ continue
+ endif
+ let l:match = l:matches[0]
+
+ call add(l:list, [l:match, l:lnum, l:cnum])
+ endwhile
+
+ " reset ignorecase (defunct)
+
+ call add(l:list, [l:match_corr, l:lnum_corr, l:cnum_corr])
+
+ if !a:down
+ call reverse(l:list)
+ endif
+
+ return l:list
+endfunction
+" }}}1
+
+function! matchup#delim#skip(...) " {{{1
+ if a:0 >= 2
+ let [l:lnum, l:cnum] = [a:1, a:2]
+ else
+ let [l:lnum, l:cnum] = matchup#pos#get_cursor()[1:2]
+ endif
+
+ if empty(get(b:, 'matchup_delim_skip', ''))
+ return matchup#util#in_comment_or_string(l:lnum, l:cnum)
+ \ ? !s:invert_skip : s:invert_skip
+ endif
+
+ let s:eff_curpos = [l:lnum, l:cnum]
+ execute 'return' (s:invert_skip ? '!(' : '(') b:matchup_delim_skip ')'
+endfunction
+
+function! matchup#delim#skip_default()
+ return matchup#util#in_comment_or_string(line('.'), col('.'))
+ \ ? !s:invert_skip : s:invert_skip
+endfunction
+
+function! matchup#delim#skip0()
+ let s:eff_curpos = [line('.'), col('.')]
+ execute 'return' (s:invert_skip ? '!(' : '(') b:matchup_delim_skip ')'
+endfunction
+
+""
+" advanced mid/syntax skip when found in midmap
+" {val} is a 2 element array of allowed [syntax, words]
+" {def} is the fallback skip expression
+function! matchup#delim#skip1(val, def)
+ if getline('.')[col('.')-1:] =~# '^'.a:val[1]
+ return eval(a:def)
+ endif
+ let l:s = synIDattr(synID(line('.'),col('.'), 0), 'name')
+ return l:s !~# a:val[0] || eval(a:def)
+endfunction
+
+""
+" advanced mid/syntax skip when word is not in midmap
+" {strike} pattern of disallowed mid words
+" {def} is the fallback skip expression
+function! matchup#delim#skip2(strike, def)
+ return getline('.')[col('.')-1:] =~# '^' . a:strike || eval(a:def)
+endfunction
+
+let s:invert_skip = 0
+let s:eff_curpos = [1, 1]
+
+" effective column/line
+function! s:effline(expr)
+ return a:expr ==# '.' ? s:eff_curpos[0] : line(a:expr)
+endfunction
+
+function! s:effcol(expr)
+ return a:expr ==# '.' ? s:eff_curpos[1] : col(a:expr)
+endfunction
+
+function! s:geteffline(expr)
+ return a:expr ==# '.' ? getline(s:effline(a:expr)) : getline(a:expr)
+endfunction
+
+" }}}1
+
+function! matchup#delim#fill_backrefs(re, groups, warn) " {{{1
+ return substitute(a:re, g:matchup#re#backref,
+ \ '\=s:get_backref(a:groups, submatch(1), a:warn)', 'g')
+ " \ '\=get(a:groups, submatch(1), "")', 'g')
+endfunction
+
+function! s:get_backref(groups, bref, warn)
+ if !has_key(a:groups, a:bref)
+ if a:warn
+ echohl WarningMsg
+ echo 'match-up: requested invalid backreference \'.a:bref
+ echohl None
+ endif
+ return ''
+ endif
+ return '\V'.escape(get(a:groups, a:bref), '\').'\m'
+endfunction
+
+"}}}1
+
+function! s:anchor_regex(re, cnum, method) " {{{1
+ if a:method
+ " trick to re-match at a particular column
+ " handles the case where pattern contains \ze, \zs, and assertions
+ " but doesn't work with overlapping matches and is possibly slower
+ return '\%<'.(a:cnum+1).'c\%('.a:re.'\)\%>'.(a:cnum).'c'
+ else
+ " fails to match with \zs
+ return '\%'.(a:cnum).'c\%('.a:re.'\)'
+ endif
+endfunction
+
+" }}}1
+function! s:process_hlend(re, cursorpos) " {{{1
+ " first replace all \ze with \%>{cursorpos}c
+ let l:re = substitute(a:re, g:matchup#re#ze,
+ \ a:cursorpos < 0 ? '' : '\\%>'.a:cursorpos.'c', 'g')
+ " next convert hlend mark to \ze
+ return substitute(l:re, '\V\\%(hlend\\)\\{0}', '\\ze', 'g')
+endfunction
+
+" }}}1
+
+" initialize script variables
+let s:stopline = get(g:, 'matchup_delim_stopline', 1500)
+
+let s:basetypes = {
+ \ 'delim_tex': {
+ \ 'parser' : function('s:parser_delim_new'),
+ \ 'get_matching' : function('s:get_matching_delims'),
+ \ },
+ \}
+
+let s:types = {
+ \ 'all' : [ s:basetypes.delim_tex ],
+ \ 'delim_all' : [ s:basetypes.delim_tex ],
+ \ 'delim_tex' : [ s:basetypes.delim_tex ],
+ \}
+
+let &cpo = s:save_cpo
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/loader.vim b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/loader.vim
new file mode 100644
index 0000000..2a63c2f
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/loader.vim
@@ -0,0 +1,721 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+function! matchup#loader#init_module() abort " {{{1
+ augroup matchup_filetype
+ au!
+ autocmd FileType * call matchup#loader#init_buffer()
+ if g:matchup_delim_start_plaintext
+ autocmd BufWinEnter,CmdWinEnter * call matchup#loader#bufwinenter()
+ endif
+ augroup END
+endfunction
+
+" }}}1
+function! matchup#loader#init_buffer() abort " {{{1
+ call matchup#perf#tic('loader_init_buffer')
+
+ " initialize lists of delimiter pairs and regular expressions
+ " this is the data obtained from parsing b:match_words
+ let b:matchup_delim_lists = s:init_delim_lists()
+
+ " this is the combined set of regular expressions used for matching
+ " its structure is matchup_delim_re[type][open,close,both,mid,both_all]
+ let b:matchup_delim_re = s:init_delim_regexes()
+
+ " process match_skip
+ let b:matchup_delim_skip = s:init_delim_skip()
+
+ " enable/disable for this buffer
+ let b:matchup_delim_enabled = !empty(b:matchup_delim_lists.all.regex)
+
+ call matchup#perf#toc('loader_init_buffer', 'done')
+endfunction
+
+" }}}1
+function! matchup#loader#bufwinenter() abort " {{{1
+ if get(b:, 'matchup_delim_enabled', 0)
+ return
+ endif
+ call matchup#loader#init_buffer()
+endfunction
+
+" }}}1
+function! matchup#loader#refresh_match_words() abort " {{{1
+ if get(b:, 'match_words', ':') !~# ':'
+ call matchup#perf#tic('refresh')
+
+ " protect the cursor from the match_words function
+ let l:save_pos = matchup#pos#get_cursor()
+ execute 'let l:match_words = ' b:match_words
+ if l:save_pos != matchup#pos#get_cursor()
+ call matchup#pos#set_cursor(l:save_pos)
+ endif
+
+ call matchup#perf#toc('refresh', 'function')
+
+ if has_key(s:match_word_cache, l:match_words)
+ let b:matchup_delim_lists
+ \ = s:match_word_cache[l:match_words].delim_lists
+ let b:matchup_delim_re
+ \ = s:match_word_cache[l:match_words].delim_regexes
+ call matchup#perf#toc('refresh', 'cache_hit')
+ else
+ " re-parse match words
+ let b:matchup_delim_lists = s:init_delim_lists()
+ let b:matchup_delim_re = s:init_delim_regexes()
+ let s:match_word_cache[l:match_words] = {
+ \ 'delim_lists' : b:matchup_delim_lists,
+ \ 'delim_regexes': b:matchup_delim_re,
+ \}
+ call matchup#perf#toc('refresh', 'parse')
+ endif
+ endif
+endfunction
+
+let s:match_word_cache = {}
+
+" }}}1
+
+function! s:init_delim_lists(...) abort " {{{1
+ let l:lists = {
+ \ 'delim_tex': {
+ \ 'regex': [],
+ \ 'regex_capture': [],
+ \ 'midmap': {},
+ \ },
+ \}
+
+ " very tricky examples:
+ " good: let b:match_words = '\(\(foo\)\(bar\)\):\3\2:end\1'
+ " bad: let b:match_words = '\(foo\)\(bar\):more\1:and\2:end\1\2'
+
+ " *subtlety*: there is a huge assumption in matchit:
+ " ``It should be possible to resolve back references
+ " from any pattern in the group.''
+ " we don't explicitly check this, but the behavior might
+ " be unpredictable if such groups are encountered.. (ref-1)
+
+ if exists('g:matchup_hotfix') && has_key(g:matchup_hotfix, &filetype)
+ call call(g:matchup_hotfix[&filetype], [])
+ elseif exists('g:matchup_hotfix_'.&filetype)
+ call call(g:matchup_hotfix_{&filetype}, [])
+ elseif exists('b:matchup_hotfix')
+ call call(b:matchup_hotfix, [])
+ endif
+
+ " parse matchpairs and b:match_words
+ let l:match_words = a:0 ? a:1 : get(b:, 'match_words', '')
+ if !empty(l:match_words) && l:match_words !~# ':'
+ if a:0
+ echohl ErrorMsg
+ echo 'match-up: function b:match_words error'
+ echohl None
+ let l:match_words = ''
+ else
+ execute 'let l:match_words =' b:match_words
+ " echohl ErrorMsg
+ " echo 'match-up: function b:match_words not supported'
+ " echohl None
+ " let l:match_words = ''
+ endif
+ endif
+ let l:simple = empty(l:match_words)
+
+ let l:mps = escape(&matchpairs, '[$^.*~\\/?]')
+ if !get(b:, 'matchup_delim_nomatchpairs', 0) && !empty(l:mps)
+ let l:match_words .= (l:simple ? '' : ',').l:mps
+ endif
+
+ if l:simple
+ return s:init_delim_lists_fast(l:match_words)
+ endif
+
+ let l:sets = split(l:match_words, g:matchup#re#not_bslash.',')
+
+ " do not duplicate whole groups of match words
+ let l:seen = {}
+ for l:s in l:sets
+ " very special case, escape bare [:]
+ " TODO: the bare [] bug might show up in other places too
+ if l:s ==# '[:]' || l:s ==# '\[:\]'
+ let l:s = '\[:]'
+ endif
+
+ if has_key(l:seen, l:s) | continue | endif
+ let l:seen[l:s] = 1
+
+ if l:s =~# '^\s*$' | continue | endif
+
+ let l:words = split(l:s, g:matchup#re#not_bslash.':')
+
+ if len(l:words) < 2 | continue | endif
+
+ " stores series-level information
+ let l:extra_info = {}
+
+ " stores information for each word
+ let l:extra_list = map(range(len(l:words)), '{}')
+
+ " pre-process various \g{special} instructions
+ let l:replacement = {
+ \ 'hlend': '\%(hlend\)\{0}',
+ \ 'syn': ''
+ \}
+ for l:i in range(len(l:words))
+ let l:special_flags = []
+ let l:words[l:i] = substitute(l:words[l:i],
+ \ g:matchup#re#gspec,
+ \ '\=[get(l:replacement,submatch(1),""),'
+ \ . 'add(l:special_flags,'
+ \ . '[submatch(1),submatch(2)])][0]', 'g')
+ for [l:f, l:a] in l:special_flags
+ let l:extra_list[l:i][l:f] = len(l:a) ? l:a : 1
+ endfor
+ endfor
+
+ " we will resolve backrefs to produce two sets of words,
+ " one with \(foo\)s and one with \1s, along with a set of
+ " bookkeeping structures
+ let l:words_backref = copy(l:words)
+
+ " *subtlety*: backref numbers refer to the capture groups
+ " in the 'open' pattern so we have to carefully keep track
+ " of the group renumbering
+ let l:group_renumber = {}
+ let l:augment_comp = {}
+ let l:all_needed_groups = {}
+
+ " *subtlety*: when replacing things like \1 with \(...\)
+ " the insertion could possibly contain back references of
+ " its own; this poses a very difficult bookkeeping problem,
+ " so we just disallow it.. (ref-2)
+
+ " get the groups like \(foo\) in the 'open' pattern
+ let l:cg = matchup#loader#get_capture_groups(l:words[0])
+
+ " if any of these contain \d raise a warning
+ " and substitute it out (ref-2)
+ for l:cg_i in keys(l:cg)
+ if l:cg[l:cg_i].str =~# g:matchup#re#backref
+ echohl WarningMsg
+ echom 'match-up: capture group' l:cg[l:cg_i].str
+ \ 'should not contain backrefs (ref-2)'
+ echohl None
+ let l:cg[l:cg_i].str = substitute(l:cg[l:cg_i].str,
+ \ g:matchup#re#backref, '', 'g')
+ endif
+ endfor
+
+ " for the 'open' pattern, create a series of replacements
+ " of the capture groups with \9, \8, ..., \1
+ " this must be done deepest to shallowest
+ let l:augments = {}
+ let l:order = matchup#loader#capture_group_replacement_order(l:cg)
+
+ let l:curaug = l:words[0]
+ " TODO: \0 should match the whole pattern..
+ " augments[0] is the original words[0] with original capture groups
+ let l:augments[0] = l:curaug " XXX does putting this in 0 make sense?
+ for l:j in l:order
+ " these indexes are not invalid because we work backwards
+ let l:curaug = strpart(l:curaug, 0, l:cg[l:j].pos[0])
+ \ .('\'.l:j).strpart(l:curaug, l:cg[l:j].pos[1])
+ let l:augments[l:j] = l:curaug
+ endfor
+
+ " TODO this logic might be bad BADLOGIC
+ " should we not fill groups that aren't needed?
+ " dragons: create the augmentation operators from the
+ " open pattern- this is all super tricky!!
+ " TODO we should be building the augment later, so
+ " we can remove augments that can never be filled
+
+ " now for the rest of the words...
+ for l:i in range(1, len(l:words)-1)
+
+ " first get rid of the capture groups in this pattern
+ let l:words_backref[l:i] = matchup#loader#remove_capture_groups(
+ \ l:words_backref[l:i])
+
+ " get the necessary \1, \2, etc back-references
+ let l:needed_groups = []
+ call substitute(l:words_backref[l:i], g:matchup#re#backref,
+ \ '\=len(add(l:needed_groups, submatch(1)))', 'g')
+ call filter(l:needed_groups,
+ \ 'index(l:needed_groups, v:val) == v:key')
+
+ " warn if the back-referenced groups don't actually exist
+ for l:ng in l:needed_groups
+ if has_key(l:cg, l:ng)
+ let l:all_needed_groups[l:ng] = 1
+ else
+ echohl WarningMsg
+ echom 'match-up: backref \' . l:ng 'requested but no '
+ \ . 'matching capture group provided'
+ echohl None
+ endif
+ endfor
+
+ " substitute capture groups into the backrefs and keep
+ " track of the mapping to the original backref number
+ let l:group_renumber[l:i] = {}
+
+ let l:cg2 = {}
+ for l:bref in l:needed_groups
+
+ " turn things like \1 into \(...\)
+ " replacement is guaranteed to exist and not contain \d
+ let l:words_backref[l:i] = substitute(l:words_backref[l:i],
+ \ g:matchup#re#backref,
+ \ '\='''.l:cg[l:bref].str."'", '') " not global!!
+
+ " complicated: need to count the number of inserted groups
+ let l:prev_max = max(keys(l:cg2))
+ let l:cg2 = matchup#loader#get_capture_groups(l:words_backref[l:i])
+
+ for l:cg2_i in sort(keys(l:cg2), s:Nsort)
+ if l:cg2_i > l:prev_max
+ " maps capture groups to 'open' back reference numbers
+ let l:group_renumber[l:i][l:cg2_i] = l:bref
+ \ + (l:cg2_i - 1 - l:prev_max)
+ endif
+ endfor
+
+ " if any backrefs remain, replace with re-numbered versions
+ let l:words_backref[l:i] = substitute(l:words_backref[l:i],
+ \ g:matchup#re#not_bslash.'\\'.l:bref,
+ \ '\\\=l:group_renumber[l:i][submatch(1)]', 'g')
+ endfor
+
+ " mostly a sanity check
+ if matchup#util#has_duplicate_str(values(l:group_renumber[l:i]))
+ echohl ErrorMsg
+ echom 'match-up: duplicate bref in set ' l:s ':' l:i
+ echohl None
+ endif
+
+ " compile the augment list for this set of backrefs, going
+ " deepest first and combining as many steps as possible
+ let l:resolvable = {}
+ let l:dependency = {}
+
+ let l:instruct = []
+ for l:j in l:order
+ " the in group is the local number from this word pattern
+ let l:in_grp_l = keys(filter(
+ \ deepcopy(l:group_renumber[l:i]), 'v:val == l:j'))
+
+ if empty(l:in_grp_l) | continue | endif
+ let l:in_grp = l:in_grp_l[0]
+
+ " if anything depends on this, flush out the current resolvable
+ if has_key(l:dependency, l:j)
+ call add(l:instruct, copy(l:resolvable))
+ let l:dependency = {}
+ endif
+
+ " walk up the tree marking any new dependency
+ let l:node = l:j
+ for l:dummy in range(11)
+ let l:node = l:cg[l:node].parent
+ if l:node == 0 | break | endif
+ let l:dependency[l:node] = 1
+ endfor
+
+ " mark l:j as resolvable
+ let l:resolvable[l:j] = l:in_grp
+ endfor
+
+ if !empty(l:resolvable)
+ call add(l:instruct, copy(l:resolvable))
+ endif
+
+ " *note*: recall that l:augments[2] is the result of augments
+ " up to and including 2
+
+ " this is a set of instructions of which brefs to resolve
+ let l:augment_comp[l:i] = []
+ for l:instr in l:instruct
+ " the smallest key is the greediest, due to l:order
+ let l:minkey = min(keys(l:instr))
+ call insert(l:augment_comp[l:i], {
+ \ 'inputmap': {},
+ \ 'outputmap': {},
+ \ 'str': l:augments[l:minkey],
+ \})
+
+ let l:remaining_out = {}
+ for l:out_grp in keys(l:cg)
+ let l:remaining_out[l:out_grp] = 1
+ endfor
+
+ " input map turns this word pattern numbers into 'open' numbers
+ for [l:out_grp, l:in_grp] in items(l:instr)
+ let l:augment_comp[l:i][0].inputmap[l:in_grp] = l:out_grp
+ if has_key(l:remaining_out, l:out_grp)
+ call remove(l:remaining_out, l:out_grp)
+ endif
+ endfor
+
+ " output map turns remaining group numbers into 'open' numbers
+ let l:counter = 1
+ for l:out_grp in sort(keys(l:remaining_out), s:Nsort)
+ let l:augment_comp[l:i][0].outputmap[l:counter] = l:out_grp
+ let l:counter += 1
+ endfor
+ endfor
+
+ " if l:instruct was empty, there are no constraints
+ if empty(l:instruct) && !empty(l:augments)
+ let l:augment_comp[l:i] = [{
+ \ 'inputmap': {},
+ \ 'outputmap': {},
+ \ 'str': l:augments[0],
+ \}]
+ for l:cg_i in keys(l:cg)
+ let l:augment_comp[l:i][0].outputmap[l:cg_i] = l:cg_i
+ endfor
+ endif
+ endfor
+
+ " strip out unneeded groups in output maps
+ for l:i in keys(l:augment_comp)
+ for l:aug in l:augment_comp[l:i]
+ call filter(l:aug.outputmap,
+ \ 'has_key(l:all_needed_groups, v:key)')
+ endfor
+ endfor
+
+ " TODO should l:words[0] actually be used? BADLOGIC
+ " the last element in the order gives the most augmented string
+ " this includes groups that might not actually be needed elsewhere
+ " as a concrete example,
+ " l:augments = { '0': '\<\(wh\%[ile]\|for\)\>', '1': '\<\1\>'}
+ " l:words[0] = \<\1\> (bad)
+ " instead, get the furthest out needed augment.. Heuristic TODO
+ for l:g in add(reverse(copy(l:order)), 0)
+ if has_key(l:all_needed_groups, l:g)
+ let l:words[0] = l:augments[l:g]
+ break
+ endif
+ endfor
+
+ " check whether any of these patterns has \zs
+ let l:extra_info.has_zs
+ \ = match(l:words_backref, g:matchup#re#zs) >= 0
+
+ if !empty(filter(copy(l:extra_list[1:-2]),
+ \ 'get(v:val, "hlend")'))
+ let l:extra_info.mid_hlend = 1
+ endif
+
+ " this is the original set of words plus the set of augments
+ " TODO this should probably be renamed
+ " (also called regexone)
+ call add(l:lists.delim_tex.regex, {
+ \ 'open' : l:words[0],
+ \ 'close' : l:words[-1],
+ \ 'mid' : join(l:words[1:-2], '\|'),
+ \ 'mid_list' : l:words[1:-2],
+ \ 'augments' : l:augments,
+ \})
+
+ " this list has \(groups\) and we also stuff recapture data
+ " TODO this should probably be renamed
+ " (also called regextwo)
+ call add(l:lists.delim_tex.regex_capture, {
+ \ 'open' : l:words_backref[0],
+ \ 'close' : l:words_backref[-1],
+ \ 'mid' : join(l:words_backref[1:-2], '\|'),
+ \ 'mid_list' : l:words_backref[1:-2],
+ \ 'need_grp' : l:all_needed_groups,
+ \ 'grp_renu' : l:group_renumber,
+ \ 'aug_comp' : l:augment_comp,
+ \ 'extra_list' : l:extra_list,
+ \ 'extra_info' : l:extra_info,
+ \})
+ endfor
+
+ " load info for advanced mid-mapper
+ if exists('b:match_midmap') && type(b:match_midmap) == type([])
+ let l:elems = deepcopy(b:match_midmap)
+ let l:lists.delim_tex.midmap = {
+ \ 'elements': l:elems,
+ \ 'strike': '\%(' . join(map(range(len(l:elems)),
+ \ '"\\(".l:elems[v:val][1]."\\)"'), '\|') . '\)'
+ \}
+ endif
+
+ " generate combined lists
+ let l:lists.delim_all = {}
+ let l:lists.all = {}
+ for l:k in ['regex', 'regex_capture', 'midmap']
+ let l:lists.delim_all[l:k] = l:lists.delim_tex[l:k]
+ let l:lists.all[l:k] = l:lists.delim_all[l:k]
+ endfor
+
+ return l:lists
+endfunction
+
+" }}}1
+function! s:init_delim_lists_fast(mps) abort " {{{1
+ let l:lists = { 'delim_tex': { 'regex': [], 'regex_capture': [] } }
+
+ let l:sets = split(a:mps, ',')
+ let l:seen = {}
+
+ for l:s in l:sets
+ if l:s =~# '^\s*$' | continue | endif
+
+ if l:s ==# '[:]' || l:s ==# '\[:\]'
+ let l:s = '\[:]'
+ endif
+
+ if has_key(l:seen, l:s) | continue | endif
+ let l:seen[l:s] = 1
+
+ let l:words = split(l:s, ':')
+ if len(l:words) < 2 | continue | endif
+
+ call add(l:lists.delim_tex.regex, {
+ \ 'open' : l:words[0],
+ \ 'close' : l:words[-1],
+ \ 'mid' : '',
+ \ 'mid_list' : [],
+ \ 'augments' : {},
+ \})
+ call add(l:lists.delim_tex.regex_capture, {
+ \ 'open' : l:words[0],
+ \ 'close' : l:words[-1],
+ \ 'mid' : '',
+ \ 'mid_list' : [],
+ \ 'need_grp' : {},
+ \ 'grp_renu' : {},
+ \ 'aug_comp' : {},
+ \ 'has_zs' : 0,
+ \ 'extra_list' : [{}, {}],
+ \ 'extra_info' : { 'has_zs': 0, },
+ \})
+ endfor
+
+ " TODO if this is empty!
+
+ " generate combined lists
+ let l:lists.delim_all = {}
+ let l:lists.all = {}
+ for l:k in ['regex', 'regex_capture']
+ let l:lists.delim_all[l:k] = l:lists.delim_tex[l:k]
+ let l:lists.all[l:k] = l:lists.delim_all[l:k]
+ endfor
+
+ return l:lists
+endfunction
+
+" }}}1
+function! s:init_delim_regexes() abort " {{{1
+ let l:re = {}
+ let l:re.delim_all = {}
+ let l:re.all = {}
+
+ let l:re.delim_tex = s:init_delim_regexes_generator('delim_tex')
+ let l:re.delim_tex._engine_info = { 'has_zs': {} }
+
+ " use a flag for b:match_ignorecase
+ let l:ic = get(b:, 'match_ignorecase', 0) ? '\c' : '\C'
+
+ " if a particular engine is specified, use that for the patterns
+ " (currently only applied to delim_re TODO)
+ let l:eng = string(get(b:, 'matchup_regexpengine', 0))
+ let l:eng = l:eng > 0 ? '\%#='.l:eng : ''
+
+ for l:k in keys(s:sidedict)
+ let l:re.delim_tex._engine_info.has_zs[l:k]
+ \ = l:re.delim_tex[l:k] =~# g:matchup#re#zs
+
+ if l:re.delim_tex[l:k] ==# '\%(\)'
+ let l:re.delim_tex[l:k] = ''
+ else
+ " since these patterns are used in searchpos(),
+ " be explicit about regex mode (set magic mode and ignorecase)
+ let l:re.delim_tex[l:k] = l:eng . '\m' . l:ic . l:re.delim_tex[l:k]
+ endif
+
+ let l:re.delim_all[l:k] = l:re.delim_tex[l:k]
+ let l:re.all[l:k] = l:re.delim_all[l:k]
+ endfor
+
+ let l:re.delim_all._engine_info = l:re.delim_tex._engine_info
+ let l:re.all._engine_info = l:re.delim_all._engine_info
+
+ return l:re
+endfunction
+
+" }}}1
+function! s:init_delim_regexes_generator(list_name) abort " {{{1
+ let l:list = b:matchup_delim_lists[a:list_name].regex_capture
+
+ " build the full regex strings: order matters here
+ let l:regexes = {}
+ for [l:key, l:sidelist] in items(s:sidedict)
+ let l:relist = []
+
+ for l:set in l:list
+ for l:side in l:sidelist
+ if strlen(l:set[l:side])
+ call add(l:relist, l:set[l:side])
+ endif
+ endfor
+ endfor
+
+ let l:regexes[l:key] = matchup#loader#remove_capture_groups(
+ \ '\%(' . join(l:relist, '\|') . '\)')
+ endfor
+
+ return l:regexes
+endfunction
+
+" }}}1
+
+function! matchup#loader#capture_group_replacement_order(cg) abort " {{{1
+ let l:order = reverse(sort(keys(a:cg), s:Nsort))
+ call sort(l:order, 's:capture_group_sort', a:cg)
+ return l:order
+endfunction
+
+function! s:capture_group_sort(a, b) abort dict
+ return self[a:b].depth - self[a:a].depth
+endfunction
+
+" }}}1
+function! matchup#loader#get_capture_groups(str, ...) abort " {{{1
+ let l:allow_percent = a:0 ? a:1 : 0
+ let l:pat = g:matchup#re#not_bslash . '\(\\%(\|\\(\|\\)\)'
+
+ let l:start = 0
+
+ let l:brefs = {}
+ let l:stack = []
+ let l:counter = 0
+ while 1
+ let l:match = s:matchstrpos(a:str, l:pat, l:start)
+ if l:match[1] < 0 | break | endif
+ let l:start = l:match[2]
+
+ if l:match[0] ==# '\(' || (l:match[0] ==# '\%(' && l:allow_percent)
+ let l:counter += 1
+ call add(l:stack, l:counter)
+ let l:cgstack = filter(copy(l:stack), 'v:val > 0')
+ let l:brefs[l:counter] = {
+ \ 'str': '',
+ \ 'depth': len(l:cgstack),
+ \ 'parent': (len(l:cgstack) > 1 ? l:cgstack[-2] : 0),
+ \ 'pos': [l:match[1], 0],
+ \}
+ elseif l:match[0] ==# '\%('
+ call add(l:stack, 0)
+ else
+ if empty(l:stack) | break | endif
+ let l:i = remove(l:stack, -1)
+ if l:i < 1 | continue | endif
+ let l:j = l:brefs[l:i].pos[0]
+ let l:brefs[l:i].str = strpart(a:str, l:j, l:match[2]-l:j)
+ let l:brefs[l:i].pos[1] = l:match[2]
+ endif
+ endwhile
+
+ call filter(l:brefs, 'has_key(v:val, "str")')
+
+ return l:brefs
+endfunction
+
+" compatibility
+if exists('*matchstrpos')
+ function! s:matchstrpos(expr, pat, start) abort
+ return matchstrpos(a:expr, a:pat, a:start)
+ endfunction
+else
+ function! s:matchstrpos(expr, pat, start) abort
+ return [matchstr(a:expr, a:pat, a:start),
+ \ match(a:expr, a:pat, a:start),
+ \ matchend(a:expr, a:pat, a:start)]
+ endfunction
+endif
+
+" }}}1
+function! matchup#loader#remove_capture_groups(re) abort "{{{1
+ let l:sub_grp = '\(\\\@ l:b ? 1 : -1
+endfunction
+
+" }}}1
+
+let s:sidedict = {
+ \ 'open' : ['open'],
+ \ 'mid' : ['mid'],
+ \ 'close' : ['close'],
+ \ 'both' : ['close', 'open'],
+ \ 'both_all' : ['close', 'mid', 'open'],
+ \ 'open_mid' : ['mid', 'open'],
+ \}
+
+function! matchup#loader#sidedict() abort
+ return s:sidedict
+endfunction
+
+" in case the 'N' sort flag is not available (compatibility for 7.4.898)
+let s:Nsort = has('patch-7.4.951') ? 'N' : 's:Nsort_func'
+
+let &cpo = s:save_cpo
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/matchparen.vim b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/matchparen.vim
new file mode 100644
index 0000000..a87e76b
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/matchparen.vim
@@ -0,0 +1,953 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+scriptencoding utf-8
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+function! matchup#matchparen#init_module() " {{{1
+ if !g:matchup_matchparen_enabled | return | endif
+
+ call matchup#matchparen#enable()
+
+ nnoremap (matchup-hi-surround)
+ \ :call matchup#matchparen#highlight_surrounding()
+endfunction
+
+" }}}1
+
+function! matchup#matchparen#enable() " {{{1
+ let g:matchup_matchparen_enabled = 1
+
+ if g:matchup_matchparen_deferred
+ \ && (!has('timers') || !exists('*timer_pause')
+ \ || has('nvim') && !has('nvim-0.2.1'))
+ let g:matchup_matchparen_deferred = 0
+ echohl WarningMsg
+ echom "match-up's deferred highlighting feature is "
+ \ . 'not supported in your vim version'
+ echohl None
+ endif
+
+ augroup matchup_matchparen
+ autocmd!
+ autocmd CursorMoved,CursorMovedI *
+ \ call s:matchparen.highlight_deferred()
+ autocmd WinEnter * call s:matchparen.highlight(1)
+ autocmd TextChanged,TextChangedI *
+ \ call s:matchparen.highlight_deferred()
+ if has('patch-8.0.1494')
+ autocmd TextChangedP * call s:matchparen.highlight_deferred()
+ endif
+ autocmd BufReadPost * call s:matchparen.transmute_reset()
+ autocmd WinLeave,BufLeave * call s:matchparen.clear()
+ autocmd InsertEnter,InsertChange * call s:matchparen.highlight(1, 1)
+ autocmd InsertLeave * call s:matchparen.highlight(1)
+ augroup END
+
+ call s:ensure_match_popup()
+
+ if has('vim_starting')
+ " prevent this from autoloading during timer callback at startup
+ if g:matchup_matchparen_deferred
+ call matchup#pos#val(0,0)
+ endif
+
+ " prevent loading the delim module at vim startup
+ let w:last_changedtick = 2
+ let w:last_cursor = [0,1,1,0,1]
+ endif
+endfunction
+
+" }}}1
+
+function! s:pi_paren_sid() " {{{1
+ if s:pi_paren_sid >= 0
+ return s:pi_paren_sid
+ endif
+
+ let s:pi_paren_sid = 0
+ if get(g:, 'loaded_matchparen')
+ let l:pat = '\%#=1\V'.expand('$VIM').'\m.\+matchparen\.vim$'
+ if v:version >= 800
+ " execute() was added in 7.4.2008
+ " :filter was introduced in 7.4.2244 but I have not tested it there
+ let l:lines = split(execute("filter '".l:pat."' scriptnames"), '\n')
+ else
+ let l:lines = matchup#util#command('scriptnames')
+ call filter(l:lines, 'v:val =~# l:pat')
+ endif
+ let s:pi_paren_sid = matchstr(get(l:lines, 0), '\d\+\ze: ')
+ if !exists('*'.s:pi_paren_sid.'_Highlight_Matching_Pair')
+ let s:pi_paren_sid = 0
+ endif
+ endif
+ if s:pi_paren_sid
+ let s:pi_paren_fcn = function(''.s:pi_paren_sid
+ \ .'_Highlight_Matching_Pair')
+ endif
+ return s:pi_paren_sid
+endfunction
+
+let s:pi_paren_sid = -1
+
+" }}}1
+
+function! matchup#matchparen#disable() " {{{1
+ let g:matchup_matchparen_enabled = 0
+ call s:matchparen.clear()
+ silent! autocmd! matchup_matchparen
+endfunction
+
+" }}}1
+function! matchup#matchparen#toggle(...) " {{{1
+ let g:matchup_matchparen_enabled = a:0 > 0
+ \ ? a:1
+ \ : !g:matchup_matchparen_enabled
+ call matchup#matchparen#reload()
+endfunction
+
+" }}}1
+function! matchup#matchparen#reload() " {{{1
+ if g:matchup_matchparen_enabled
+ call matchup#matchparen#enable()
+ call s:matchparen.highlight(1)
+ else
+ call matchup#matchparen#disable()
+ endif
+endfunction
+
+" }}}1
+function! matchup#matchparen#update() " {{{1
+ call s:matchparen.highlight(1)
+endfunction
+
+" }}}1
+
+let s:matchparen = {}
+
+function! s:matchparen.clear() abort dict " {{{1
+ if exists('w:matchup_match_id_list')
+ for l:id in w:matchup_match_id_list
+ silent! call matchdelete(l:id)
+ endfor
+ unlet! w:matchup_match_id_list
+ endif
+
+ if exists('t:match_popup')
+ call popup_hide(t:match_popup)
+ elseif has('nvim')
+ call s:close_floating_win()
+ endif
+
+ if exists('w:matchup_oldstatus')
+ let &l:statusline = w:matchup_oldstatus
+ unlet w:matchup_oldstatus
+ if exists('#User#MatchupOffscreenLeave')
+ doautocmd User MatchupOffscreenLeave
+ endif
+ endif
+ if exists('w:matchup_statusline')
+ unlet w:matchup_statusline
+ endif
+
+ let w:matchup_need_clear = 0
+endfunction
+
+" }}}1
+
+function! s:timer_callback(win_id, timer_id) abort " {{{1
+ if a:win_id != win_getid()
+ call timer_pause(a:timer_id, 1)
+ return
+ endif
+
+ " if we timed out, do a highlight and pause the timer
+ let l:elapsed = 1000*s:reltimefloat(reltime(w:matchup_pulse_time))
+ if l:elapsed >= s:show_delay
+ call timer_pause(a:timer_id, 1)
+ if exists('#TextYankPost') && !has('patch-8.1.0192')
+ " workaround crash with autocmd trigger during regex match (#3175)
+ let l:save_ei = &eventignore
+ try
+ set eventignore+=TextYankPost
+ call s:matchparen.highlight()
+ finally
+ let &eventignore = l:save_ei
+ endtry
+ else
+ call s:matchparen.highlight()
+ endif
+ elseif w:matchup_need_clear && exists('w:matchup_hi_time')
+ " if highlighting becomes too stale, clear it
+ let l:elapsed = 1000*s:reltimefloat(reltime(w:matchup_hi_time))
+ if l:elapsed >= s:hide_delay
+ call s:matchparen.clear()
+ endif
+ endif
+endfunction
+
+" }}}1
+
+function! s:matchparen.fade(level, pos, token) abort dict " {{{1
+ ""
+ " fade feature: remove highlights after a certain time
+ " {level}
+ " = 0: prepare for possible loss of cursor support
+ " = 1: new highlights are coming (cancel prior fade)
+ " = 2: end of new highlights
+ " {pos} [lnum, column] of current match
+ " {token} in/out saves state between calls
+ "
+ " returns 1 if highlighting should be canceled
+
+ if !g:matchup_matchparen_deferred || !exists('w:matchup_fade_timer')
+ if a:level <= 0
+ call s:matchparen.clear()
+ endif
+ return 0
+ endif
+
+ " jumping between windows
+ if a:level == 0 && win_getid() != get(s:, 'save_win')
+ call timer_pause(w:matchup_fade_timer, 1)
+ if exists('w:matchup_fade_pos')
+ unlet w:matchup_fade_pos
+ endif
+ call s:matchparen.clear()
+ let s:save_win = win_getid()
+ endif
+
+ " highlighting might be stale
+ if a:level == 0
+ if exists('w:matchup_fade_pos')
+ let a:token.save_pos = w:matchup_fade_pos
+ unlet w:matchup_fade_pos
+ endif
+ if !w:matchup_need_clear
+ call timer_pause(w:matchup_fade_timer, 1)
+ endif
+ return 0
+ endif
+
+ " prepare for new highlighting
+ if a:level == 1
+ " if token has no save_pos, cursor was previously off of a match
+ if !has_key(a:token, 'save_pos') || a:pos != a:token.save_pos
+ " clear immediately
+ call timer_pause(w:matchup_fade_timer, 1)
+ call s:matchparen.clear()
+ return 0
+ endif
+ let w:matchup_fade_pos = a:token.save_pos
+ return 1
+ endif
+
+ " new highlighting is active
+ if a:level == 2 && a:pos != get(w:, 'matchup_fade_pos', [])
+ " init fade request
+ let w:matchup_fade_pos = a:pos
+ let w:matchup_fade_start = reltime()
+ call timer_pause(w:matchup_fade_timer, 0)
+ endif
+
+ return 0
+endfunction
+
+" }}}1
+
+function! s:fade_timer_callback(win_id, timer_id) abort " {{{1
+ if a:win_id != win_getid()
+ call timer_pause(a:timer_id, 1)
+ return
+ endif
+
+ if !exists('w:matchup_fade_start') || !w:matchup_need_clear
+ call timer_pause(a:timer_id, 1)
+ return
+ endif
+
+ let l:elapsed = 1000*s:reltimefloat(reltime(w:matchup_fade_start))
+ if l:elapsed >= s:fade_time
+ call s:matchparen.clear()
+ call timer_pause(a:timer_id, 1)
+ endif
+endfunction
+
+" }}}1
+
+" function! s:reltimefloat(time) {{{1
+if exists('*reltimefloat')
+ function! s:reltimefloat(time)
+ return reltimefloat(a:time)
+ endfunction
+else
+ function! s:reltimefloat(time)
+ return str2float(reltimestr(a:time))
+ endfunction
+endif
+
+" }}}1
+
+function! s:matchparen.highlight_deferred() abort dict " {{{1
+ if !get(b:, 'matchup_matchparen_deferred',
+ \ g:matchup_matchparen_deferred)
+ return s:matchparen.highlight()
+ endif
+
+ if !exists('w:matchup_timer')
+ let s:show_delay = g:matchup_matchparen_deferred_show_delay
+ let s:hide_delay = g:matchup_matchparen_deferred_hide_delay
+ let w:matchup_timer = timer_start(s:show_delay,
+ \ function('s:timer_callback', [ win_getid() ]),
+ \ {'repeat': -1})
+ if !exists('w:matchup_need_clear')
+ let w:matchup_need_clear = 0
+ endif
+ let s:fade_time = g:matchup_matchparen_deferred_fade_time
+ if s:fade_time > 0
+ let w:matchup_fade_timer = timer_start(s:fade_time,
+ \ function('s:fade_timer_callback', [ win_getid() ]),
+ \ {'repeat': -1})
+ call timer_pause(w:matchup_fade_timer, 1)
+ endif
+ endif
+
+ " keep the timer alive with a heartbeat
+ let w:matchup_pulse_time = reltime()
+
+ " if the timer is paused, some time has passed
+ if timer_info(w:matchup_timer)[0].paused
+ " unpause the timer
+ call timer_pause(w:matchup_timer, 0)
+
+ " set the hi time to the pulse time
+ let w:matchup_hi_time = w:matchup_pulse_time
+ endif
+endfunction
+
+" }}}1
+
+function! s:matchparen.highlight(...) abort dict " {{{1
+ if !g:matchup_matchparen_enabled | return | endif
+
+ if has('vim_starting') | return | endif
+
+ if !g:matchup_matchparen_pumvisible && pumvisible() | return | endif
+
+ if !get(b:, 'matchup_matchparen_enabled', 1)
+ \ && get(b:, 'matchup_matchparen_fallback', 1) && s:pi_paren_sid()
+ return call(s:pi_paren_fcn, [])
+ endif
+
+ if !get(b:, 'matchup_matchparen_enabled', 1) | return | endif
+
+ let l:force_update = a:0 >= 1 ? a:1 : 0
+ let l:changing_insert = a:0 >= 2 ? a:2 : 0
+ let l:real_mode = l:changing_insert ? v:insertmode : mode()
+
+ if !l:force_update
+ \ && exists('w:last_changedtick') && exists('w:last_cursor')
+ \ && matchup#pos#equal(w:last_cursor, matchup#pos#get_cursor())
+ \ && w:last_changedtick == b:changedtick
+ return
+ endif
+ let w:last_changedtick = b:changedtick
+ let w:last_cursor = matchup#pos#get_cursor()
+
+ call matchup#perf#tic('matchparen.highlight')
+
+ " request eventual clearing of stale matches
+ let l:token = {}
+ call self.fade(0, [], l:token)
+
+ let l:modes = g:matchup_matchparen_nomode
+ if get(g:, 'matchup_matchparen_novisual', 0) " deprecated option name
+ let l:modes .= "vV\"
+ endif
+ if stridx(l:modes, l:real_mode) >= 0
+ return
+ endif
+
+ " don't get matches when inside a closed fold
+ if foldclosed(line('.')) > -1
+ return
+ endif
+
+ " give up when cursor is far into a very long line
+ if &synmaxcol && col('.') > &synmaxcol
+ return
+ endif
+
+ " in insert mode, cursor is treated as being one behind
+ let l:insertmode = l:real_mode ==# 'i'
+
+ " start the timeout period
+ let l:timeout = l:insertmode
+ \ ? get(b:, 'matchup_matchparen_insert_timeout',
+ \ g:matchup_matchparen_insert_timeout)
+ \ : get(b:, 'matchup_matchparen_timeout',
+ \ g:matchup_matchparen_timeout)
+ call matchup#perf#timeout_start(l:timeout)
+
+ let l:current = matchup#delim#get_current('all', 'both_all',
+ \ { 'insertmode': l:insertmode,
+ \ 'stopline': g:matchup_matchparen_stopline,
+ \ 'highlighting': 1, })
+ call matchup#perf#toc('matchparen.highlight', 'get_current')
+ if empty(l:current)
+ if get(b:, 'matchup_matchparen_deferred',
+ \ g:matchup_matchparen_deferred)
+ \ && get(b:, 'matchup_matchparen_hi_surround_always',
+ \ g:matchup_matchparen_hi_surround_always)
+ call s:highlight_surrounding(l:insertmode)
+ endif
+ return
+ endif
+
+ let l:corrlist = matchup#delim#get_matching(l:current,
+ \ { 'stopline': g:matchup_matchparen_stopline,
+ \ 'highlighting': 1, })
+ call matchup#perf#toc('matchparen.highlight', 'get_matching')
+ if empty(l:corrlist) | return | endif
+
+ if g:matchup_transmute_enabled
+ if !exists('w:matchup_matchparen_context')
+ let w:matchup_matchparen_context = {
+ \ 'normal': {
+ \ 'current': {},
+ \ 'corrlist': [],
+ \ },
+ \ 'prior': {},
+ \ 'counter': 0,
+ \}
+ endif
+
+ let w:matchup_matchparen_context.counter += 1
+
+ if !l:insertmode
+ let w:matchup_matchparen_context.prior
+ \ = copy(w:matchup_matchparen_context.normal)
+
+ let w:matchup_matchparen_context.normal.current = l:current
+ let w:matchup_matchparen_context.normal.corrlist = l:corrlist
+ endif
+
+ " if transmuted, highlight again (will reset timeout)
+ if matchup#transmute#tick(l:insertmode)
+ " no force_update here because it would screw up prior
+ return s:matchparen.highlight(0, l:changing_insert)
+ endif
+ endif
+
+ if !has_key(l:current, 'match_index')
+ \ || len(l:corrlist) <= (l:current.side ==# 'mid' ? 2 : 1)
+ \ && !g:matchup_matchparen_singleton
+ " TODO this doesn't catch every case, needs refactor
+ " TODO singleton doesn't work right for mids
+ return
+ endif
+
+ " prepare for (possibly) new highlights
+ let l:pos = [l:current.lnum, l:current.cnum]
+ if self.fade(1, l:pos, l:token)
+ return
+ endif
+
+ " store flag meaning highlighting is active
+ let w:matchup_need_clear = 1
+
+ " disable off-screen when scrolling with j/k
+ let l:scrolling = get(g:matchup_matchparen_offscreen, 'scrolloff', 0)
+ \ && winheight(0) > 2*&scrolloff
+ \ && (line('.') == line('w$')-&scrolloff
+ \ && line('$') != line('w$')
+ \ || line('.') == line('w0')+&scrolloff)
+
+ " show off-screen matches
+ let l:method = get(g:matchup_matchparen_offscreen, 'method', '')
+ if !empty(l:method) && l:method !=# 'none'
+ \ && !l:current.skip && !l:scrolling
+ \ && winheight(0) > 0
+ call s:do_offscreen(l:current, l:method)
+ endif
+
+ " add highlighting matches
+ call s:add_matches(l:corrlist, l:current)
+
+ " highlight the background between parentheses
+ if g:matchup_matchparen_hi_background >= 1
+ call s:highlight_background(l:corrlist)
+ endif
+
+ " new highlights done, request fade away
+ call self.fade(2, l:pos, l:token)
+
+ call matchup#perf#toc('matchparen.highlight', 'end')
+endfunction
+
+function s:matchparen.transmute_reset() abort dict
+ if g:matchup_transmute_enabled
+ call matchup#transmute#reset()
+ endif
+endfunction
+
+" }}}1
+
+function! s:do_offscreen(current, method) " {{{1
+ let l:offscreen = {}
+
+ if !has_key(a:current, 'links') | return | endif
+
+ " prefer to show close
+ if a:current.links.open.lnum < line('w0')
+ let l:offscreen = a:current.links.open
+ endif
+ if a:current.links.close.lnum > line('w$')
+ let l:offscreen = a:current.links.close
+ endif
+
+ if empty(l:offscreen) | return | endif
+
+ if a:method ==# 'status'
+ call s:do_offscreen_statusline(l:offscreen, 0)
+ elseif a:method ==# 'status_manual'
+ call s:do_offscreen_statusline(l:offscreen, 1)
+ elseif a:method ==# 'popup' && winheight(0) > 1
+ if has('nvim')
+ call s:do_offscreen_popup_nvim(l:offscreen)
+ elseif exists('*popup_create')
+ call s:ensure_match_popup()
+ call s:do_offscreen_popup(l:offscreen)
+ endif
+ endif
+endfunction
+
+" }}}1
+function! s:do_offscreen_statusline(offscreen, manual) " {{{1
+ let l:opts = {}
+ if a:manual
+ let l:opts.compact = 1
+ endif
+ let [l:sl, l:lnum] = matchup#matchparen#status_str(a:offscreen, l:opts)
+ if s:ensure_scroll_timer() && !a:manual
+ let l:sl .= '%{matchup#matchparen#scroll_update('.l:lnum.')}'
+ endif
+
+ let w:matchup_statusline = l:sl
+ if !exists('w:matchup_oldstatus')
+ let w:matchup_oldstatus = &l:statusline
+ endif
+ if !a:manual
+ let &l:statusline = w:matchup_statusline
+ endif
+
+ if exists('#User#MatchupOffscreenEnter')
+ doautocmd User MatchupOffscreenEnter
+ endif
+endfunction
+
+" }}}1
+function! s:ensure_match_popup() abort " {{{1
+ if !exists('*popup_create') || exists('t:match_popup')
+ return
+ endif
+
+ " create a popup and store its winid
+ let t:match_popup = popup_create('', {
+ \ 'hidden': v:true,
+ \})
+
+ if !has('patch-8.1.1406')
+ " in case 'hidden' in popup_create-usage is unimplemented
+ call popup_hide(t:match_popup)
+ endif
+endfunction
+
+" }}}1
+function! s:do_offscreen_popup(offscreen) " {{{1
+ " screen position of top-left corner of current window
+ let [l:row, l:col] = win_screenpos(winnr())
+ let l:height = winheight(0) " height of current window
+ let l:adjust = matchup#quirks#status_adjust(a:offscreen)
+ let l:lnum = a:offscreen.lnum + l:adjust
+ let l:line = l:lnum < line('.') ? l:row : l:row + l:height - 1
+
+ " if popup would overlap with cursor
+ if l:line == winline() | return | endif
+
+ call popup_move(t:match_popup, {
+ \ 'line': l:line,
+ \ 'col': l:col,
+ \ 'maxheight': 1,
+ \})
+
+ " set popup text
+ let l:text = ''
+ if &number || &relativenumber
+ let l:text = printf('%*S ', wincol()-virtcol('.')-1, l:lnum)
+ endif
+ let l:text .= getline(l:lnum) . ' '
+ if l:adjust
+ let l:text .= '… ' . a:offscreen.match . ' '
+ endif
+ call setbufline(winbufnr(t:match_popup), 1, l:text)
+ call popup_show(t:match_popup)
+endfunction
+
+" }}}1
+function! s:do_offscreen_popup_nvim(offscreen) " {{{1
+ if exists('*nvim_open_win')
+ " neovim floating window
+ call s:close_floating_win()
+
+ let l:lnum = a:offscreen.lnum
+ let [l:row, l:anchor] = l:lnum < line('.')
+ \ ? [0, 'NW'] : [winheight(0), 'SW']
+ if l:row == winline() | return | endif
+
+ " Set default width and height for now.
+ let s:float_id = nvim_open_win(bufnr('%'), v:false, {
+ \ 'relative': 'win',
+ \ 'anchor': l:anchor,
+ \ 'row': l:row,
+ \ 'col': 0,
+ \ 'width': 42,
+ \ 'height': &previewheight,
+ \ 'focusable': v:false,
+ \})
+
+ if &relativenumber
+ call nvim_win_set_option(s:float_id, 'number', v:true)
+ call nvim_win_set_option(s:float_id, 'relativenumber', v:false)
+ endif
+
+ call s:populate_floating_win(a:offscreen)
+ endif
+endfunction
+
+" }}}1
+function! s:populate_floating_win(offscreen) " {{{1
+ let l:adjust = matchup#quirks#status_adjust(a:offscreen)
+ let l:lnum = a:offscreen.lnum + l:adjust
+ let l:body = getline(l:lnum, a:offscreen.lnum)
+ let l:body_length = len(l:body)
+ let l:height = min([l:body_length, &previewheight])
+
+ if exists('*nvim_open_win')
+ " neovim floating win
+ let width = max(map(copy(l:body), 'strdisplaywidth(v:val)'))
+ let l:width += wincol()-virtcol('.')
+ call nvim_win_set_width(s:float_id, l:width + 1)
+ call nvim_win_set_height(s:float_id, l:height)
+ call nvim_win_set_cursor(s:float_id, [l:lnum, 0])
+ call nvim_win_set_option(s:float_id, 'wrap', v:false)
+ endif
+endfunction
+
+" }}}1
+function! s:close_floating_win() " {{{1
+ if !exists('s:float_id')
+ return
+ endif
+ if win_id2win(s:float_id) > 0
+ call nvim_win_close(s:float_id, 0)
+ endif
+ let s:float_id = 0
+endfunction
+
+" }}}1
+
+function! MatchupStatusOffscreen() " {{{1
+ return substitute(get(w:, 'matchup_statusline', ''),
+ \ '%<\|%#\w*#', '', 'g')
+endfunction
+
+" }}}1
+
+function! matchup#matchparen#highlight_surrounding() abort " {{{1
+ call matchup#perf#timeout_start(500)
+ call s:highlight_surrounding()
+endfunction
+
+" }}}1
+
+function! s:highlight_surrounding(...) " {{{1
+ let l:opts = { 'local': 0, 'matches': [] }
+ let l:delims = matchup#delim#get_surrounding('delim_all', 1, l:opts)
+ let l:open = l:delims[0]
+ if empty(l:open) | return | endif
+
+ let l:corrlist = l:opts.matches
+ if empty(l:corrlist) | return | endif
+
+ " store flag meaning highlighting is active
+ let w:matchup_need_clear = 1
+
+ " add highlighting matches
+ call s:add_matches(l:corrlist)
+
+ " highlight the background between parentheses
+ if g:matchup_matchparen_hi_background >= 2
+ call s:highlight_background(l:corrlist)
+ endif
+endfunction
+
+" }}}1
+function! s:highlight_background(corrlist) " {{{1
+ let [l:lo1, l:lo2] = [a:corrlist[0], a:corrlist[-1]]
+
+ let l:inclusive = 1
+ if l:inclusive
+ call s:add_background_matches_1(
+ \ l:lo1.lnum,
+ \ l:lo1.cnum,
+ \ l:lo2.lnum,
+ \ l:lo2.cnum + matchup#delim#end_offset(l:lo2))
+ else
+ call s:add_background_matches_1(
+ \ l:lo1.lnum,
+ \ l:lo1.cnum + matchup#delim#end_offset(l:lo1) + 1,
+ \ l:lo2.lnum,
+ \ l:lo2.cnum - 1)
+ endif
+endfunction
+
+"}}}1
+
+function! s:format_gutter(lnum, ...) " {{{1
+ let l:opts = a:0 ? a:1 : {}
+ let l:padding = wincol()-virtcol('.')
+ let l:sl = ''
+
+ let l:direction = a:lnum < line('.')
+ if &number || &relativenumber
+ let l:nw = max([strlen(line('$')), &numberwidth-1])
+ let l:linenr = a:lnum " distinct for relativenumber
+
+ if &relativenumber
+ let l:linenr = abs(l:linenr-line('.'))
+ endif
+
+ let l:sl = printf('%'.(l:nw).'s', l:linenr)
+ if l:direction && !get(l:opts, 'noshowdir', 0)
+ let l:sl = '%#Search#' . l:sl . '∆%#Normal#'
+ else
+ let l:sl = '%#CursorLineNr#' . l:sl . ' %#Normal#'
+ endif
+ let l:padding -= l:nw + 1
+ endif
+
+ if empty(l:sl) && l:direction && !get(l:opts, 'noshowdir', 0)
+ let l:sl = '%#Search#∆%#Normal#'
+ let l:padding -= 1 " OK if this is negative
+ if l:padding == -1 && indent(a:lnum) == 0
+ let l:padding = 0
+ endif
+ endif
+
+ " possible fold column, up to &foldcolumn characters
+ let l:fdcstr = ''
+ if &foldcolumn
+ let l:fdc = max([1, &foldcolumn-1])
+ let l:fdl = foldlevel(a:lnum)
+ let l:fdcstr = l:fdl <= l:fdc ? repeat('|', l:fdl)
+ \ : join(range(l:fdl-l:fdc+1, l:fdl), '')
+ let l:padding -= len(l:fdcstr)
+ let l:fdcstr = '%#FoldColumn#' . l:fdcstr . '%#Normal#'
+ elseif empty(l:sl)
+ let l:sl = '%#Normal#'
+ endif
+
+ " add remaining padding (this handles rest of fdc and scl)
+ let l:sl = l:fdcstr . repeat(' ', l:padding) . l:sl
+ return l:sl
+endfunction
+
+" }}}1
+function! matchup#matchparen#status_str(offscreen, ...) abort " {{{1
+ let l:opts = a:0 ? a:1 : {}
+ let l:adjust = matchup#quirks#status_adjust(a:offscreen)
+ let l:lnum = a:offscreen.lnum + l:adjust
+ let l:line = getline(l:lnum)
+
+ let l:sl = ''
+ let l:trimming = 0
+ if get(l:opts, 'compact', 0)
+ let l:trimming = 1
+ else
+ let l:sl = s:format_gutter(l:lnum, l:opts)
+ endif
+
+ if has_key(l:opts, 'width')
+ " TODO subtract the gutter from above
+ let l:room = l:opts.width
+ else
+ let l:room = min([300, winwidth(0)]) - (wincol()-virtcol('.'))
+ endif
+ let l:room -= l:adjust ? 3+strdisplaywidth(a:offscreen.match) : 0
+ let l:lasthi = ''
+ for l:c in range(min([l:room, strlen(l:line)]))
+ if !l:adjust && a:offscreen.cnum <= l:c+1 && l:c+1 <= a:offscreen.cnum
+ \ - 1 + strlen(a:offscreen.match)
+ let l:wordish = a:offscreen.match !~? '^[[:punct:]]\{1,3\}$'
+ " TODO: we can't overlap groups, this might not be totally correct
+ let l:curhi = l:wordish ? 'MatchWord' : 'MatchParen'
+ elseif char2nr(l:line[l:c]) < 32
+ let l:curhi = 'SpecialKey'
+ else
+ let l:curhi = synIDattr(synID(l:lnum, l:c+1, 1), 'name')
+ if empty(l:curhi)
+ let l:curhi = 'Normal'
+ endif
+ endif
+ let l:sl .= (l:curhi !=# l:lasthi ? '%#'.l:curhi.'#' : '')
+ if l:trimming && l:line[l:c] !~ '\s'
+ let l:trimming = 0
+ endif
+ if l:trimming
+ elseif l:line[l:c] ==# "\t"
+ let l:sl .= repeat(' ', strdisplaywidth(strpart(l:line, 0, 1+l:c))
+ \ - strdisplaywidth(strpart(l:line, 0, l:c)))
+ elseif char2nr(l:line[l:c]) < 32
+ let l:sl .= strtrans(l:line[l:c])
+ elseif l:line[l:c] == '%'
+ let l:sl .= '%%'
+ else
+ let l:sl .= l:line[l:c]
+ endif
+ let l:lasthi = l:curhi
+ endfor
+ let l:sl = substitute(l:sl, '\s\+$', '', '') . '%<%#Normal#'
+ if l:adjust
+ let l:sl .= '%#LineNr# … %#Normal#'
+ \ . '%#MatchParen#' . a:offscreen.match . '%#Normal#'
+ endif
+
+ return [l:sl, l:lnum]
+endfunction
+
+" }}}1
+
+function! s:ensure_scroll_timer() " {{{1
+ if has('timers') && exists('*timer_pause')
+ if !exists('s:scroll_timer')
+ let s:scroll_timer = timer_start(50,
+ \ 'matchup#matchparen#scroll_callback',
+ \ { 'repeat': -1 })
+ call timer_pause(s:scroll_timer, 1)
+ endif
+ endif
+
+ return exists('s:scroll_timer')
+endfunction
+
+" }}}1
+function! matchup#matchparen#scroll_callback(tid) " {{{1
+ call timer_pause(a:tid, 1)
+ call s:matchparen.highlight(1)
+endfunction
+
+" }}}1
+function! matchup#matchparen#scroll_update(lnum) " {{{1
+ if line('w0') <= a:lnum && a:lnum <= line('w$')
+ \ && exists('s:scroll_timer')
+ call timer_pause(s:scroll_timer, 0)
+ endif
+ return ''
+endfunction
+
+" }}}1
+
+function! s:add_matches(corrlist, ...) " {{{1
+ if !exists('w:matchup_match_id_list')
+ let w:matchup_match_id_list = []
+ endif
+
+ " if MatchwordCur is undefined and MatchWord links to MatchParen
+ " (as default), behave like MatchWordCur is the same as MatchParenCur
+ " otherwise, MatchWordCur is the same as MatchWord
+ if a:0
+ let l:mwc = hlexists('MatchWordCur') ? 'MatchWordCur'
+ \ : (synIDtrans(hlID('MatchWord')) == hlID('MatchParen')
+ \ ? 'MatchParenCur' : 'MatchWord')
+ endif
+
+ for l:corr in a:corrlist
+ let l:wordish = l:corr.match !~? '^[[:punct:]]\{1,3\}$'
+
+ if a:0 && l:corr.match_index == a:1.match_index
+ let l:group = l:wordish ? l:mwc : 'MatchParenCur'
+ else
+ let l:group = l:wordish ? 'MatchWord' : 'MatchParen'
+ endif
+
+ if exists('*matchaddpos')
+ call add(w:matchup_match_id_list, matchaddpos(l:group,
+ \ [[l:corr.lnum, l:corr.cnum, strlen(l:corr.match)]], 0))
+ else
+ call add(w:matchup_match_id_list, matchadd(l:group,
+ \ '\%'.(l:corr.lnum).'l\%'.(l:corr.cnum).'c'
+ \ . '.\+\%<'.(l:corr.cnum+strlen(l:corr.match)+1).'c', 0))
+ endif
+ endfor
+endfunction
+
+" }}}1
+function! s:add_background_matches_1(line1, col1, line2, col2) " {{{1
+ if a:line1 == a:line2 && a:col1 > a:col2
+ return
+ endif
+
+ let l:priority = -1
+
+ if a:line1 == a:line2
+ let l:match = '\%'.(a:line1).'l\&'
+ \ . '\%'.(a:col1).'c.*\%'.(a:col2).'c.'
+ else
+ let l:match = '\%>'.(a:line1).'l\(.\+\|^$\)\%<'.(a:line2).'l'
+ \ . '\|\%'.(a:line1).'l\%>'.(a:col1-1).'c.\+'
+ \ . '\|\%'.(a:line2).'l.\+\%<'.(a:col2+1).'c.'
+ endif
+
+ call add(w:matchup_match_id_list,
+ \ matchadd('MatchBackground', l:match, l:priority))
+endfunction
+
+" }}}1
+function! s:add_background_matches_2(line1, col1, line2, col2) " {{{1
+ if a:line1 == a:line2 && a:col1 > a:col2
+ return
+ endif
+
+ let l:priority = -1
+
+ let l:curline = a:line1
+ while l:curline <= a:line2
+ let l:endline = min([l:curline+7, a:line2])
+ let l:list = range(l:curline, l:endline)
+ if l:curline == a:line1
+ let l:list[0] = [a:line1, a:col1,
+ \ l:curline == a:line2 ? (a:col2-a:col1+1)
+ \ : strlen(getline(a:line1))]
+ endif
+ if l:endline == a:line2 && l:curline != a:line2
+ let l:list[-1] = [a:line2, 1, a:col2]
+ endif
+
+ call add(w:matchup_match_id_list,
+ \ matchaddpos('MatchBackground', l:list, l:priority))
+ let l:curline = l:endline+1
+ endwhile
+endfunction
+
+" }}}1
+
+let &cpo = s:save_cpo
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/misc.vim b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/misc.vim
new file mode 100644
index 0000000..b4194bc
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/misc.vim
@@ -0,0 +1,27 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+" {{{1 function! matchup#misc#reload()
+if get(s:, 'reload_guard', 1)
+ function! matchup#misc#reload() abort
+ let s:reload_guard = 0
+
+ for l:file in glob(fnamemodify(s:file, ':h') . '/../**/*.vim', 0, 1)
+ execute 'source' l:file
+ endfor
+
+ call matchup#init()
+
+ unlet s:reload_guard
+ endfunction
+endif
+
+" }}}1
+
+let s:file = expand('')
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/motion.vim b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/motion.vim
new file mode 100644
index 0000000..682e1e0
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/motion.vim
@@ -0,0 +1,313 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+" TODO this can probably be simplified
+function! matchup#motion#op(motion) abort
+ call matchup#motion_force()
+ let l:sid = matchup#motion_sid()
+ let s:v_operator = v:operator
+ execute 'normal' l:sid.'(wise)' . (v:count > 0 ? v:count : '')
+ \ . l:sid.'(matchup-'.a:motion.')'
+ unlet s:v_operator
+endfunction
+
+function matchup#motion#getoper()
+ return get(s:, 'v_operator', '')
+endfunction
+
+function! matchup#motion#find_matching_pair(visual, down) " {{{1
+ let [l:count, l:count1] = [v:count, v:count1]
+
+ let l:is_oper = !empty(get(s:, 'v_operator', ''))
+
+ if a:visual && !l:is_oper
+ normal! gv
+ endif
+
+ if a:down && l:count > g:matchup_motion_override_Npercent
+ " TODO: dv50% does not work properly
+ if a:visual && l:is_oper
+ normal! V
+ endif
+ exe 'normal!' l:count.'%'
+ return
+ endif
+
+ " disable the timeout
+ call matchup#perf#timeout_start(0)
+
+ " get a delim where the cursor is
+ let l:delim = matchup#delim#get_current('all', 'both_all')
+ if empty(l:delim)
+ " otherwise search forward
+ let l:delim = matchup#delim#get_next('all', 'both_all')
+ if empty(l:delim) | return | endif
+ endif
+
+ " loop count number of times
+ for l:dummy in range(l:count1)
+ let l:matches = matchup#delim#get_matching(l:delim, 1)
+ if len(l:matches) <= (l:delim.side ==# 'mid' ? 2 : 1) | return | endif
+ if !has_key(l:delim, 'links') | return | endif
+ let l:delim = get(l:delim.links, a:down ? 'next' : 'prev', {})
+ if empty(l:delim) | return | endif
+ endfor
+
+ if a:visual && l:is_oper
+ normal! gv
+ endif
+
+ let l:exclusive = l:is_oper && (g:v_motion_force ==# 'v')
+ let l:forward = ((a:down && l:delim.side !=# 'open')
+ \ || l:delim.side ==# 'close')
+
+ " go to the end of the delimiter, if necessary
+ let l:column = l:delim.cnum
+ if g:matchup_motion_cursor_end && !l:is_oper && l:forward
+ let l:column = matchup#delim#jump_target(l:delim)
+ endif
+
+ let l:start_pos = matchup#pos#get_cursor()
+
+ normal! m`
+
+ " column position of last character in match
+ let l:eom = l:delim.cnum + matchup#delim#end_offset(l:delim)
+
+ if l:is_oper && l:forward
+ let l:column = l:exclusive ? (l:column - 1) : l:eom
+ endif
+
+ if l:is_oper && l:exclusive
+ \ && matchup#pos#smaller(l:delim, l:start_pos)
+ normal! o
+ call matchup#pos#set_cursor(matchup#pos#prev(l:start_pos))
+ normal! o
+ endif
+
+ " special handling for d%
+ let [l:start_lnum, l:start_cnum] = l:start_pos[1:2]
+ if get(s:, 'v_operator', '') ==# 'd' && l:start_lnum != l:delim.lnum
+ \ && g:v_motion_force ==# ''
+ let l:tl = [l:start_lnum, l:start_cnum]
+ let [l:tl, l:br, l:swap] = l:tl[0] <= l:delim.lnum
+ \ ? [l:tl, [l:delim.lnum, l:eom], 0]
+ \ : [[l:delim.lnum, l:delim.cnum], l:tl, 1]
+
+ if getline(l:tl[0]) =~# '^[ \t]*\%'.l:tl[1].'c'
+ \ && getline(l:br[0]) =~# '\%'.(l:br[1]+1).'c[ \t]*$'
+ if l:swap
+ normal! o
+ call matchup#pos#set_cursor(l:br[0], strlen(getline(l:br[0]))+1)
+ normal! o
+ let l:column = 1
+ else
+ normal! o
+ call matchup#pos#set_cursor(l:tl[0], 1)
+ normal! o
+ let l:column = strlen(getline(l:br[0]))+1
+ endif
+ endif
+ endif
+
+ let l:lnum = l:delim.lnum
+
+ " make adjustments for selection option 'exclusive
+ if l:forward && a:visual && &selection ==# 'exclusive'
+ let [l:lnum, l:column] = matchup#pos#next_eol(l:lnum, l:column)[1:2]
+ endif
+ if !l:forward && l:is_oper && &selection ==# 'exclusive'
+ normal! o
+ call matchup#pos#set_cursor(matchup#pos#next_eol(
+ \ matchup#pos#get_cursor()))
+ normal! o
+ endif
+
+ call matchup#pos#set_cursor(l:lnum, l:column)
+ if stridx(&foldopen, 'percent') >= 0
+ normal! zv
+ endif
+endfunction
+
+" }}}1
+function! matchup#motion#find_unmatched(visual, down, ...) " {{{1
+ call matchup#perf#tic('motion#find_unmatched')
+
+ let l:opts = a:0 ? a:1 : {}
+ let l:count = v:count1
+
+ let l:is_oper = !empty(get(s:, 'v_operator', ''))
+ let l:exclusive = l:is_oper
+ \ && g:v_motion_force !=# 'v' && g:v_motion_force !=# "\"
+
+ if a:visual
+ normal! gv
+ endif
+
+ " set the timeout fairly high by default
+ let l:timeout = get(l:opts, 'timeout', 750)
+ call matchup#perf#timeout_start(l:timeout)
+
+ for l:tries in range(3)
+ let [l:open, l:close] = matchup#delim#get_surrounding('delim_all',
+ \ l:tries ? l:count : 1,
+ \ { 'check_skip': get(l:opts, '__where_impl__', 0) })
+
+ if empty(l:open) || empty(l:close)
+ call matchup#perf#toc('motion#find_unmatched', 'fail'.l:tries)
+ return
+ endif
+
+ let l:delim = a:down ? l:close : l:open
+
+ let l:save_pos = matchup#pos#get_cursor()
+ let l:new_pos = [l:delim.lnum, l:delim.cnum]
+
+ " this is an exclusive motion, ]%
+ if l:delim.side ==# 'close'
+ if l:exclusive
+ let l:new_pos[1] -= 1
+ else
+ let l:new_pos[1] += matchup#delim#end_offset(l:delim)
+ endif
+ endif
+
+ " if the cursor didn't move, increment count
+ if matchup#pos#equal(l:save_pos, l:new_pos)
+ let l:count += 1
+ elseif l:tries
+ break
+ endif
+
+ if l:count <= 1
+ break
+ endif
+ endfor
+
+ if a:down && !l:is_oper
+ let l:new_pos[1] = matchup#delim#jump_target(l:delim)
+ endif
+
+ " this is an exclusive motion, [%
+ if !a:down && l:exclusive
+ normal! o
+ call matchup#pos#set_cursor(matchup#pos#prev(
+ \ matchup#pos#get_cursor()))
+ normal! o
+ endif
+
+ " handle selection option 'exclusive' going backwards
+ if !a:down && l:is_oper && &selection ==# 'exclusive'
+ normal! o
+ call matchup#pos#set_cursor(matchup#pos#next_eol(
+ \ matchup#pos#get_cursor()))
+ normal! o
+ endif
+
+ " handle selection option 'exclusive' going forwards
+ if a:down && l:is_oper && &selection ==# 'exclusive'
+ let l:new_pos = matchup#pos#next_eol(l:new_pos)[1:2]
+ endif
+
+ if get(l:opts, '__where_impl__', 0)
+ let l:opts.delim = l:delim
+ else
+ normal! m`
+ endif
+ call matchup#pos#set_cursor(l:new_pos)
+
+ call matchup#perf#toc('motion#find_unmatched', 'done')
+endfunction
+
+" }}}1
+function! matchup#motion#jump_inside(visual) " {{{1
+ let l:count = v:count1
+
+ let l:save_pos = matchup#pos#get_cursor()
+
+ call matchup#perf#timeout_start(750)
+
+ if a:visual
+ normal! gv
+ endif
+
+ for l:counter in range(l:count)
+ if l:counter
+ let l:delim = matchup#delim#get_next('all', 'open')
+ else
+ let l:delim = matchup#delim#get_current('all', 'open')
+ if empty(l:delim)
+ let l:delim = matchup#delim#get_next('all', 'open')
+ endif
+ endif
+ if empty(l:delim)
+ call matchup#pos#set_cursor(l:save_pos)
+ return
+ endif
+
+ let l:new_pos = [l:delim.lnum, l:delim.cnum]
+ let l:new_pos[1] += matchup#delim#end_offset(l:delim)
+ call matchup#pos#set_cursor(matchup#pos#next(l:new_pos))
+ endfor
+
+ call matchup#pos#set_cursor(l:save_pos)
+
+ " convert to [~, lnum, cnum, ~] format
+ let l:new_pos = matchup#pos#next(l:new_pos)
+
+ " this is an exclusive motion except when dealing with whitespace
+ let l:is_oper = !empty(get(s:, 'v_operator', ''))
+ if l:is_oper
+ \ && g:v_motion_force !=# 'v' && g:v_motion_force !=# "\"
+ while matchup#util#in_whitespace(l:new_pos[1], l:new_pos[2])
+ let l:new_pos = matchup#pos#next(l:new_pos)
+ endwhile
+ let l:new_pos = matchup#pos#prev(l:new_pos)
+ endif
+
+ " jump ahead if inside indent
+ if !l:is_oper && matchup#util#in_indent(l:new_pos[1], l:new_pos[2])
+ let l:new_pos[2] = 1 + strlen(matchstr(
+ \ getline(l:new_pos[1]), '^\s\+'))
+ endif
+
+ " handle selection option 'exclusive' (motion only goes forwards)
+ if a:visual && &selection ==# 'exclusive'
+ let l:new_pos = matchup#pos#next_eol(l:new_pos)
+ endif
+
+ normal! m`
+ call matchup#pos#set_cursor(l:new_pos)
+endfunction
+
+" }}}1
+function! matchup#motion#insert_mode() " {{{1
+ call matchup#perf#timeout_start(0) " disable the timeout
+
+ let l:delim = matchup#delim#get_current(
+ \ 'all', 'both_all', {'insertmode': 1})
+ if empty(l:delim) | return | endif
+
+ let l:matches = matchup#delim#get_matching(l:delim, 1)
+ if len(l:matches) <= (l:delim.side ==# 'mid' ? 2 : 1) | return | endif
+ if !has_key(l:delim, 'links') | return | endif
+ let l:delim = get(l:delim.links, 'next', {})
+ if empty(l:delim) | return | endif
+
+ let l:new_pos = [l:delim.lnum, l:delim.cnum]
+ let l:new_pos[1] += matchup#delim#end_offset(l:delim)
+ call matchup#pos#set_cursor(matchup#pos#next_eol(l:new_pos))
+endfunction
+
+" }}}1
+
+let &cpo = s:save_cpo
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/perf.vim b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/perf.vim
new file mode 100644
index 0000000..b9a736f
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/perf.vim
@@ -0,0 +1,117 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+let s:time_start = {}
+let s:alpha = 2.0/(10+1)
+
+let g:matchup#perf#times = {}
+
+function! matchup#perf#tic(context)
+ let s:time_start[a:context] = reltime()
+endfunction
+
+function! matchup#perf#toc(context, state)
+ let l:elapsed = s:reltimefloat(reltime(s:time_start[a:context]))
+
+ let l:key = a:context.'#'.a:state
+ if has_key(g:matchup#perf#times, l:key)
+ if l:elapsed > g:matchup#perf#times[l:key].maximum
+ let g:matchup#perf#times[l:key].maximum = l:elapsed
+ endif
+ let g:matchup#perf#times[l:key].last = l:elapsed
+ let g:matchup#perf#times[l:key].emavg = s:alpha*l:elapsed
+ \ + (1-s:alpha)*g:matchup#perf#times[l:key].emavg
+ else
+ let g:matchup#perf#times[l:key] = {
+ \ 'maximum' : l:elapsed,
+ \ 'emavg' : l:elapsed,
+ \ 'last' : l:elapsed,
+ \}
+ endif
+endfunction
+
+function! s:sort_by_last(a, b)
+ let l:a = g:matchup#perf#times[a:a].last
+ let l:b = g:matchup#perf#times[a:b].last
+ return l:a == l:b ? 0 : l:a > l:b ? 1 : -1
+endfunction
+
+function! matchup#perf#show_times()
+ let l:keys = keys(g:matchup#perf#times)
+ let l:contexts = uniq(sort(map(copy(l:keys), 'split(v:val, "#")[0]')))
+ if empty(l:contexts)
+ echo 'no times'
+ return
+ end
+
+ echohl Title
+ echo printf("%42s%11s%17s", 'average', 'last', 'maximum')
+ echohl None
+ for l:c in l:contexts
+ echohl Special
+ echo '['.l:c.']'
+ echohl None
+ let l:states = filter(copy(l:keys), 'v:val =~# "^\\V'.l:c.'#"')
+ call sort(l:states, 's:sort_by_last')
+ for l:s in l:states
+ echo printf(" %-25s%12.2gms%12.2gms%12.2gms",
+ \ join(split(l:s,'#')[1:],'#'),
+ \ 1000*g:matchup#perf#times[l:s].emavg,
+ \ 1000*g:matchup#perf#times[l:s].last,
+ \ 1000*g:matchup#perf#times[l:s].maximum)
+ endfor
+ endfor
+endfunction
+
+command! MatchupShowTimes call matchup#perf#show_times()
+command! MatchupClearTimes let g:matchup#perf#times = {}
+
+let s:timeout = 0
+let s:timeout_enabled = 0
+let s:timeout_pulse_time = reltime()
+
+function! matchup#perf#timeout() " {{{1
+ return float2nr(s:timeout)
+endfunction
+
+"}}}1
+function! matchup#perf#timeout_start(timeout) " {{{1
+ let s:timeout = a:timeout
+ let s:timeout_enabled = (a:timeout == 0) ? 0 : 1
+ let s:timeout_pulse_time = reltime()
+endfunction
+
+" }}}1
+function! matchup#perf#timeout_check() " {{{1
+ if !s:timeout_enabled | return 0 | endif
+ let l:elapsed = 1000.0 * s:reltimefloat(reltime(s:timeout_pulse_time))
+ let s:timeout -= l:elapsed
+ let s:timeout_pulse_time = reltime()
+ return s:timeout <= 0.0
+endfunction
+
+" }}}1
+
+" function! s:reltimefloat(time) {{{1
+if exists('*reltimefloat')
+ function! s:reltimefloat(time)
+ return reltimefloat(a:time)
+ endfunction
+else
+ function! s:reltimefloat(time)
+ return str2float(reltimestr(a:time))
+ endfunction
+endif
+
+" }}}1
+
+let &cpo = s:save_cpo
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/pos.vim b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/pos.vim
new file mode 100644
index 0000000..e26a38b
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/pos.vim
@@ -0,0 +1,143 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+function! matchup#pos#set_cursor(...) " {{{1
+ call cursor(s:parse_args(a:000))
+endfunction
+
+" }}}1
+" function! matchup#pos#get_cursor() {{{1
+if exists('*getcurpos')
+ function! matchup#pos#get_cursor()
+ return getcurpos()
+ endfunction
+else
+ function! matchup#pos#get_cursor()
+ return getpos('.')
+ endfunction
+endif
+
+" }}}1
+
+" }}}1
+function! matchup#pos#get_cursor_line() " {{{1
+ let l:pos = matchup#pos#get_cursor()
+ return l:pos[1]
+endfunction
+
+" }}}1
+
+function! matchup#pos#(...) abort " {{{1
+ let [l:lnum, l:cnum; l:rest] = s:parse_args(a:000)
+ return [l:lnum, l:cnum]
+endfunction
+
+" }}}1
+function! matchup#pos#val(...) " {{{1
+ let [l:lnum, l:cnum; l:rest] = s:parse_args(a:000)
+
+ return 100000*l:lnum + min([l:cnum, 90000])
+endfunction
+
+" }}}1
+function! matchup#pos#next_eol(...) " {{{1
+ let [l:lnum, l:cnum; l:rest] = s:parse_args(a:000)
+
+ if l:cnum > strlen(getline(l:lnum))
+ return [0, l:lnum+1, 1, 0]
+ endif
+
+ let l:next = matchup#pos#next(l:lnum, l:cnum)
+ if l:next[1] > l:lnum
+ return [0, l:lnum, l:cnum+1, 0]
+ endif
+ return l:next
+endfunction
+
+" }}}1
+function! matchup#pos#next(...) " {{{1
+ let [l:lnum, l:cnum; l:rest] = s:parse_args(a:000)
+
+ let l:line = getline(l:lnum)
+ let l:charlen = matchend(l:line[l:cnum-1:], '.')
+ if l:charlen >= 0 && l:cnum + l:charlen <= strlen(l:line)
+ return [0, l:lnum, l:cnum + l:charlen, 0]
+ else
+ return [0, l:lnum+1, 1, 0]
+ endif
+endfunction
+
+" }}}1
+function! matchup#pos#prev(...) " {{{1
+ let [l:lnum, l:cnum; l:rest] = s:parse_args(a:000)
+
+ if l:cnum > 1
+ return [0, l:lnum, match(getline(l:lnum)[0:l:cnum-2], '.$') + 1, 0]
+ else
+ return [0, max([l:lnum-1, 1]),
+ \ max([strlen(getline(l:lnum-1)), 1]), 0]
+ endif
+endfunction
+
+" }}}1
+function! matchup#pos#larger(pos1, pos2) " {{{1
+ return matchup#pos#val(a:pos1) > matchup#pos#val(a:pos2)
+endfunction
+
+" }}}1
+function! matchup#pos#equal(p1, p2) " {{{1
+ let l:pos1 = s:parse_args(a:p1)
+ let l:pos2 = s:parse_args(a:p2)
+ return l:pos1[:1] == l:pos2[:1]
+endfunction
+
+" }}}1
+function! matchup#pos#smaller(pos1, pos2) " {{{1
+ return matchup#pos#val(a:pos1) < matchup#pos#val(a:pos2)
+endfunction
+
+" }}}1
+function! matchup#pos#smaller_or_equal(pos1, pos2) " {{{1
+ return matchup#pos#smaller(a:pos1, a:pos2)
+ \ || matchup#pos#equal(a:pos1, a:pos2)
+endfunction
+
+" }}}1
+function! s:parse_args(args) " {{{1
+ "
+ " The arguments should be in one of the following forms (when unpacked):
+ "
+ " [lnum, cnum]
+ " [bufnum, lnum, cnum, ...]
+ " {'lnum' : lnum, 'cnum' : cnum}
+ "
+
+ if len(a:args) > 1
+ return s:parse_args([a:args])
+ elseif len(a:args) == 1
+ if type(a:args[0]) == type({})
+ return [get(a:args[0], 'lnum'), get(a:args[0], 'cnum')]
+ else
+ if len(a:args[0]) == 2
+ return a:args[0]
+ else
+ return a:args[0][1:]
+ endif
+ endif
+ else
+ return a:args
+ endif
+endfunction
+
+" }}}1
+
+let &cpo = s:save_cpo
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/quirks.vim b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/quirks.vim
new file mode 100644
index 0000000..f37e9f6
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/quirks.vim
@@ -0,0 +1,52 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+function! matchup#quirks#isclike() abort " {{{1
+ let l:ft = get(split(&filetype, '\.'), 0, '')
+ return index(s:clikeft, l:ft) > -1
+endfunction
+
+let s:clikeft = [ 'arduino', 'c', 'cpp', 'cuda',
+ \ 'go', 'javascript', 'ld', 'php' ]
+
+" }}}1
+
+let s:adjust_max = 7
+
+function! matchup#quirks#status_adjust(offscreen) abort " {{{1
+ if a:offscreen.match ==# '{' && matchup#quirks#isclike()
+ let [l:a, l:b] = [indent(a:offscreen.lnum),
+ \ indent(a:offscreen.links.close.lnum)]
+ if strpart(getline(a:offscreen.lnum),
+ \ 0, a:offscreen.cnum-1) =~# '^\s*$'
+ let l:target = l:a
+ elseif l:a != l:b
+ let l:target = l:b
+ else
+ return 0
+ endif
+ " go up to next line with same indent (up to s:adjust_max)
+ for l:adjust in range(-1, -s:adjust_max, -1)
+ let l:lnum = a:offscreen.lnum + l:adjust
+ if indent(l:lnum) == l:target
+ \ && getline(l:lnum) !~ '^\s*\%(#\|/\*\|//\)'
+ return l:adjust
+ endif
+ endfor
+ endif
+
+ return 0
+endfunction
+
+" }}}1
+
+let &cpo = s:save_cpo
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/re.vim b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/re.vim
new file mode 100644
index 0000000..cf3ec12
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/re.vim
@@ -0,0 +1,23 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+let s:nbsl = '\v%(\\@","\"], l:char) >= 0
+ return
+ endif
+ endif
+ let l:tpope = !empty(maparg('VSurround', 'x'))
+
+ let [l:l1, l:c11, l:c12] = [l:open.lnum, l:open.cnum,
+ \ l:open.cnum + strlen(l:open.match) - 1]
+ let [l:l2, l:c21, l:c22] = [l:close.lnum, l:close.cnum,
+ \ l:close.cnum + strlen(l:close.match) - 1]
+
+ if a:op ==# 'd' || a:op ==# 'c'
+ call matchup#pos#set_cursor(l1, c12+1)
+
+ let [l:insl, l:insr] = ['', '']
+ if a:op ==# 'c' && !l:tpope
+ let l:idx = index(s:pairtrans, l:char)
+ let l:insl = l:idx < 0 ? l:char : s:pairtrans[l:idx/2*2]
+ let l:insr = l:idx < 0 ? l:char : s:pairtrans[l:idx/2*2+1]
+ endif
+
+ let l:line = getline(l:l2)
+ call setline(l:l2, strpart(l:line, 0, l:c21-1)
+ \ . l:insr . strpart(l:line, l:c22))
+ let l:regtext = strpart(l:line, l:c21-1, l:c22-l:c21+1)
+
+ let l:line = getline(l:l1)
+ call setline(l:l1, strpart(l:line, 0, l:c11-1)
+ \ . l:insl . strpart(l:line, l:c12))
+
+ call setreg(v:register, strpart(l:line, l:c11-1, l:c12-l:c11+1)
+ \ . ' ' . l:regtext)
+
+ let l:epos = l:c21-1 - (l:l1 == l:l2
+ \ ? (l:c12-l:c11+1-strlen(l:insl)-strlen(l:insr)) : 0)
+ call setpos("']", [0, l:l2, l:epos, 0])
+ call setpos("'[", [0, l:l1, l:c11, 0])
+ endif
+
+ if a:op ==# 'd' || a:op ==# 'c' && empty(l:char)
+ silent! call repeat#set("\(matchup-ds%)", v:count)
+ elseif a:op ==# 'c' && l:tpope
+ normal! `[v`]
+ undojoin
+ execute "normal \VSurround".l:char
+ silent! call repeat#set("\(matchup-cs%)"
+ \ . matchstr(g:repeat_sequence, 'SSurroundRepeat\zs.\+'),
+ \ v:count)
+ endif
+
+ call matchup#pos#set_cursor(l1, c11)
+endfunction
+
+let s:pairtrans = split('()<>[]{}«»“”', '\ze')
+
+" }}}1
+
+let &cpo = s:save_cpo
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/text_obj.vim b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/text_obj.vim
new file mode 100644
index 0000000..5e26640
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/text_obj.vim
@@ -0,0 +1,296 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+function! matchup#text_obj#delimited(is_inner, visual, type) " {{{1
+ let l:v_motion_force = matchup#motion_force()
+
+ " get the current selection, move to the _start_ the of range
+ if a:visual
+ let l:selection = getpos("'<")[1:2] + getpos("'>")[1:2]
+ call matchup#pos#set_cursor(getpos("'<"))
+ endif
+
+ " motion forcing
+ let l:forced = a:visual ? '' : l:v_motion_force
+
+ " determine if operator is able to act line-wise (i.e., for inner)
+ let l:linewise_op = index(g:matchup_text_obj_linewise_operators,
+ \ v:operator) >= 0
+
+ if v:operator ==# 'g@'
+ let l:save_reg = v:register
+ let l:spec = matchlist(g:matchup_text_obj_linewise_operators,
+ \ '^g@\%(,\(.\+\)\)\?')
+ if !empty(l:spec)
+ if empty(l:spec[1])
+ let l:linewise_op = 1
+ else
+ execute 'let l:linewise_op =' l:spec[1]
+ endif
+ endif
+ elseif v:operator ==# ':'
+ \ && index(g:matchup_text_obj_linewise_operators,
+ \ visualmode()) >= 0
+ let l:linewise_op = 1
+ endif
+
+ " set the timeout fairly high
+ call matchup#perf#timeout_start(725)
+
+ " try up to six times
+ for [l:local, l:try_again] in (v:count == 1
+ \ || v:count > g:matchup_delim_count_max)
+ \ ? a:is_inner ? [[0, 0], [0, 1], [0, 2], [0, 3]]
+ \ : [[0, 0], [0, 1], [0, 2]]
+ \ : a:is_inner ? [[1, 0], [0, 0], [1, 1], [0, 1], [1, 2], [0, 2]]
+ \ : [[1, 0], [0, 0], [1, 1], [0, 1]]
+
+ let l:count = v:count1 + l:try_again
+
+ " we use v:count1 on the first try and increment each successive time
+ " find the open-close block then narrow down to local after
+ let [l:open_, l:close_] = matchup#delim#get_surrounding(
+ \ a:type, l:count, { 'local': 0 })
+
+ if empty(l:open_)
+ if a:visual
+ normal! gv
+ else
+ " TODO: can this be simplified by making omaps ?
+ " invalid text object, try to do nothing
+ " cause a drop into normal mode
+ call feedkeys("\\\", 'n')
+
+ " and undo the text vim enters if necessary
+ call feedkeys(":call matchup#text_obj#undo("
+ \ .undotree().seq_cur.")\:\", 'n')
+ endif
+ return
+ endif
+
+ if l:local
+ let [l:open, l:close] = matchup#delim#get_surround_nearest(l:open_)
+ if empty(l:open)
+ let [l:open, l:close] = [l:open_, l:open_.links.next]
+ endif
+ else
+ let [l:open, l:close] = [l:open_, l:open_.links.close]
+ endif
+
+ " no way to specify an empty region so we need to use some tricks
+ let l:epos = [l:open.lnum, l:open.cnum]
+ let l:epos[1] += matchup#delim#end_offset(l:open)
+ if !a:visual && a:is_inner
+ \ && matchup#pos#equal(l:close, matchup#pos#next(l:epos))
+
+ " TODO: cpo-E
+ if v:operator ==# 'c'
+ " this is apparently the most reliable way to handle
+ " the 'c' operator, although it raises a TextChangedI
+ " and fills registers with a space (from targets.vim)
+ call matchup#pos#set_cursor(l:close)
+ silent! execute "normal! i \v"
+ elseif stridx('<>', v:operator) < 0
+ let l:byte = line2byte(l:close.lnum) + l:close.cnum - 1
+ call feedkeys(l:byte.'go', 'n')
+ endif
+
+ return
+ endif
+
+ let [l:l1, l:c1, l:l2, l:c2] = [l:open.lnum, l:open.cnum,
+ \ l:close.lnum, l:close.cnum]
+
+ " whether the pair has at least one line in between them
+ let l:line_count = l:l2 - l:l1 + 1
+
+ " special case: if inner and the current selection coincides
+ " with the open and close positions, try for a second time
+ " this allows vi% in [[ ]] to work
+ if a:visual && a:is_inner && l:selection == [l:l1, l:c1, l:l2, l:c2]
+ continue
+ endif
+
+ " adjust the borders of the selection
+ if a:is_inner
+ let l:c1 += matchup#delim#end_offset(l:open)
+ let [l:l1, l:c1] = matchup#pos#next(l:l1, l:c1)[1:2]
+ let l:sol = (l:c2 <= 1)
+ let [l:l2, l:c2] = matchup#pos#prev(l:l2, l:c2)[1:2]
+
+ " don't select only indent at close
+ while matchup#util#in_indent(l:l2, l:c2)
+ let l:c2 = 1
+ let [l:l2, l:c2] = matchup#pos#prev(l:l2, l:c2)[1:2]
+ let l:sol = 1
+ endwhile
+
+ " include the line break if we had wrapped around
+ if a:visual && l:sol
+ let l:c2 = strlen(getline(l:l2))+1
+ endif
+
+ if !a:visual
+ " otherwise adjust end pos
+ if l:sol
+ let [l:l2, l:c2] = matchup#pos#next(l:l2, l:c2)[1:2]
+ endif
+
+ " toggle exclusive: difference between di% and dvi%
+ let l:inclusive = 0
+ if !l:sol && matchup#pos#smaller_or_equal(
+ \ [l:l1, l:c1], [l:l2, l:c2])
+ let l:inclusive = 1
+ endif
+ if l:forced ==# 'v'
+ let l:inclusive = !l:inclusive
+ endif
+
+ " sometimes operate in visual line motion (re-purpose force)
+ " cf src/normal.c:1824
+ if empty(l:v_motion_force)
+ \ && l:c2 <= 1 && l:line_count > 1 && !l:inclusive
+ let l:l2 -= 1
+ if l:c1 <= 1 || matchup#util#in_indent(l:l1, l:c1-1)
+ let l:forced = 'V'
+ let l:inclusive = 1
+ else
+ " end_adjusted
+ let l:c2 = strlen(getline(l:l2)) + 1
+ if l:c2 > 1
+ let l:c2 -= 1
+ let l:inclusive = 1
+ endif
+ endif
+ endif
+
+ if !l:inclusive
+ let [l:l2, l:c2] = matchup#pos#prev(l:l2, l:c2)[1:2]
+ endif
+ endif
+
+ " check for the line-wise special case
+ if l:line_count > 2 && l:linewise_op && strlen(l:close.match) > 1
+ if l:c1 != 1
+ let l:l1 += 1
+ let l:c1 = 1
+ endif
+ let l:l2 = l:close.lnum - 1
+ let l:c2 = strlen(getline(l:l2))+1
+ endif
+
+ " if this would be an empty selection..
+ if !a:visual && (l:l2 < l:l1 || l:l1 == l:l2 && l:c1 > l:c2)
+ if v:operator ==# 'c'
+ call matchup#pos#set_cursor(l:l1, l:c1)
+ silent! execute "normal! i \v"
+ elseif stridx('<>', v:operator) < 0
+ let l:byte = line2byte(l:l1) + l:c1 - 1
+ call feedkeys(l:byte.'go', 'n')
+ endif
+ return
+ endif
+ else
+ let l:c2 += matchup#delim#end_offset(l:close)
+
+ " special case for delete operator
+ if !a:visual && v:operator ==# 'd'
+ \ && strpart(getline(l:l2), l:c2) =~# '^\s*$'
+ \ && strpart(getline(l:l2), 0, l:c1-1) =~# '^\s*$'
+ let l:c1 = 1
+ let l:c2 = strlen(getline(l:l2))+1
+ endif
+ endif
+
+ " in visual line mode, force new selection to not be smaller
+ " (only check line numbers)
+ if a:visual && visualmode() ==# 'V'
+ \ && (l:l1 > l:selection[0] || l:l2 < l:selection[2])
+ continue
+ endif
+
+ " in other visual modes, try again if we didn't reach a bigger range
+ if a:visual && visualmode() !=# 'V'
+ \ && !matchup#pos#equal(l:selection[0:1], l:selection[2:3])
+ \ && (l:selection == [l:l1, l:c1, l:l2, l:c2]
+ \ || matchup#pos#larger([l:l1, l:c1], l:selection[0:1])
+ \ || matchup#pos#larger(l:selection[2:3], [l:l2, l:c2]))
+ continue
+ endif
+
+ break
+ endfor
+
+ " set the proper visual mode for this selection
+ let l:select_mode = (v:operator ==# ':')
+ \ ? visualmode()
+ \ : (l:forced !=# '')
+ \ ? l:forced
+ \ : 'v'
+
+ if &selection ==# 'exclusive'
+ let [l:l2, l:c2] = matchup#pos#next_eol(l:l2, l:c2)[1:2]
+ endif
+
+ " apply selection
+ execute 'normal!' l:select_mode
+ normal! o
+ call matchup#pos#set_cursor(l:l1, l:c1)
+ normal! o
+ call matchup#pos#set_cursor(l:l2, l:c2)
+ if exists('l:save_reg')
+ execute 'normal! "' . l:save_reg
+ endif
+endfunction
+
+function! matchup#text_obj#undo(seq)
+ if undotree().seq_cur > a:seq
+ silent! undo
+ endif
+endfunction
+
+" }}}1
+function! matchup#text_obj#double_click() " {{{1
+ let [l:open, l:close] = [{}, {}]
+
+ call matchup#perf#timeout_start(0)
+ let l:delim = matchup#delim#get_current('all', 'both_all')
+ if !empty(l:delim)
+ let l:matches = matchup#delim#get_matching(l:delim, 1)
+ if len(l:matches) > 1 && has_key(l:delim, 'links')
+ let [l:open, l:close] = [l:delim.links.open, l:delim.links.close]
+ endif
+ endif
+
+ if empty(l:open) || empty(l:close)
+ call feedkeys("\<2-LeftMouse>", 'nt')
+ return
+ endif
+
+ let [l:lnum, l:cnum] = [l:close.lnum, l:close.cnum]
+ let l:cnum += matchup#delim#end_offset(l:close)
+
+ if &selection ==# 'exclusive'
+ let [l:lnum, l:cnum] = matchup#pos#next_eol(l:lnum, l:cnum)[1:2]
+ endif
+
+ call matchup#pos#set_cursor(l:open)
+ normal! v
+ call matchup#pos#set_cursor(l:lnum, l:cnum)
+ if l:delim.side ==# 'close'
+ normal! o
+ endif
+endfunction
+
+" }}}1
+
+let &cpo = s:save_cpo
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/transmute.vim b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/transmute.vim
new file mode 100644
index 0000000..3f75ca1
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/transmute.vim
@@ -0,0 +1,146 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+function! matchup#transmute#init_module() " {{{1
+ if !g:matchup_transmute_enabled | return | endif
+
+ call matchup#transmute#enable()
+endfunction
+
+" }}}1
+
+function! matchup#transmute#enable() " {{{1
+ " TODO: add insert mode map
+ " TODO: add g:matchup_transmute_auto
+endfunction
+
+" }}}1
+function! matchup#transmute#disable() " {{{1
+
+endfunction
+
+" }}}1
+
+function! matchup#transmute#tick(insertmode) " {{{1
+ if !g:matchup_transmute_enabled | return 0 | endif
+
+ if a:insertmode
+ return 0
+ endif
+
+ if changenr() > get(w:, 'matchup_transmute_last_changenr', -1)
+ \ && !empty('w:matchup_matchparen_context.prior')
+ let w:matchup_transmute_last_changenr = changenr()
+
+ return matchup#transmute#dochange(
+ \ w:matchup_matchparen_context.prior.corrlist,
+ \ w:matchup_matchparen_context.prior.current,
+ \ w:matchup_matchparen_context.normal.current)
+ endif
+
+ return 0
+endfunction
+
+" }}}1
+function! matchup#transmute#reset() " {{{1
+ if !g:matchup_transmute_enabled | return 0 | endif
+ let w:matchup_transmute_last_changenr = changenr()
+endfunction
+
+" }}}1
+function! matchup#transmute#dochange(list, pri, cur) " {{{1
+ if empty(a:list) || empty(a:pri) || empty(a:cur) | return 0 | endif
+
+ let l:cur = a:cur
+
+ " check back one
+ if a:pri.class[0] != l:cur.class[0]
+ let l:cur = matchup#delim#get_current('all', a:pri.side,
+ \ {'insertmode': 1})
+ if empty(l:cur) | return 0 | endif
+ endif
+
+ " right now, only same-class changes are supported
+ if a:pri.class[0] != l:cur.class[0]
+ return 0
+ endif
+ if a:pri.side =~# '^open\|close$' && a:pri.side isnot l:cur.side
+ return 0
+ endif
+ if !matchup#pos#equal(a:pri, l:cur)
+ return 0
+ endif
+
+ let l:num_changes = 0
+
+ let l:delta = strdisplaywidth(l:cur.match)
+ \ - strdisplaywidth(a:pri.match)
+
+ for l:i in range(len(a:list))
+ if l:i == a:pri.match_index | continue | endif
+
+ let l:corr = a:list[l:i]
+ let l:line = getline(l:corr.lnum)
+
+ let l:column = l:corr.cnum
+ if l:corr.lnum == l:cur.lnum && l:i > a:pri.match_index
+ let l:column += l:delta
+ endif
+
+ let l:re_anchored = '\%'.(l:column).'c'
+ \ . '\%('.(l:corr.regexone[l:corr.side]).'\)'
+
+ let l:groups = copy(l:corr.groups)
+ for l:grp in keys(l:groups)
+ let l:count = len(split(l:re_anchored,
+ \ g:matchup#re#not_bslash.'\\'.l:grp))-1
+ if l:count == 0 | continue | endif
+
+ if l:cur.groups[l:grp] ==# l:groups[l:grp]
+ continue
+ endif
+
+ for l:dummy in range(len(l:count))
+ " create a pattern which isolates the old group text
+ let l:prevtext = s:qescape(l:groups[l:grp])
+ let l:pattern = substitute(l:re_anchored,
+ \ g:matchup#re#not_bslash.'\\'.l:grp,
+ \ '\=''\zs\V'.l:prevtext.'\m\ze''', '')
+ let l:pattern = matchup#delim#fill_backrefs(l:pattern,
+ \ l:groups, 0)
+ let l:string = l:cur.groups[l:grp]
+ let l:line = substitute(l:line, l:pattern,
+ \ '\='''.s:qescape(l:string)."'", '')
+ endfor
+
+ let l:groups[l:grp] = l:cur.groups[l:grp]
+ endfor
+
+ if getline(l:corr.lnum) !=# l:line
+ if g:matchup_transmute_breakundo && l:num_changes == 0
+ execute "normal! a\u"
+ endif
+ call setline(l:corr.lnum, l:line)
+ let l:num_changes += 1
+ endif
+ endfor
+
+ return l:num_changes
+endfunction
+
+function s:qescape(str)
+ return escape(substitute(a:str, "'", "''", 'g'), '\')
+endfunction
+
+" }}}1
+
+let &cpo = s:save_cpo
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/unmatchit.vim b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/unmatchit.vim
new file mode 100644
index 0000000..15a1d12
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/unmatchit.vim
@@ -0,0 +1,26 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+" this file is loaded only from plugin/matchup.vim
+
+if !exists('g:loaded_matchup')
+ \ || !exists('g:loaded_matchit')
+ \ || !exists(":MatchDebug")
+ finish
+endif
+
+unlet g:loaded_matchit
+
+delcommand MatchDebug
+
+silent! unmap %
+silent! unmap [%
+silent! unmap ]%
+silent! unmap a%
+silent! unmap g%
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/util.vim b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/util.vim
new file mode 100644
index 0000000..ee95f06
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/util.vim
@@ -0,0 +1,153 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+function! matchup#util#command(cmd) " {{{1
+ let l:lines = ''
+ try
+ silent! redir => l:lines
+ silent! execute a:cmd
+ redir END
+ finally
+ return split(l:lines, '\n')
+ endtry
+endfunction
+
+" }}}1
+
+function! matchup#util#in_comment(...) " {{{1
+ return call('matchup#util#in_syntax', ['^Comment$'] + a:000)
+endfunction
+
+" }}}1
+function! matchup#util#in_string(...) " {{{1
+ return call('matchup#util#in_syntax', ['^String$'] + a:000)
+endfunction
+
+" }}}1
+function! matchup#util#in_comment_or_string(...) " {{{1
+ return call('matchup#util#in_syntax',
+ \ ['^\%(String\|Comment\)$'] + a:000)
+endfunction
+
+" }}}1
+function! matchup#util#in_syntax(name, ...) " {{{1
+ " usage: matchup#util#in_syntax(name, [line, col])
+ let l:pos = a:0 > 0 ? [a:1, a:2] : [line('.'), col('.')]
+
+ " check syntax at position (same as matchit's s: method)
+ let l:syn = synIDattr(synID(l:pos[0], l:pos[1], 1), 'name')
+ return l:syn =~? a:name
+endfunction
+
+" }}}1
+function! matchup#util#in_whitespace(...) " {{{1
+ let l:pos = a:0 > 0 ? [a:1, a:2] : [line('.'), col('.')]
+ return matchstr(getline(l:pos[0]), '\%'.l:pos[1].'c.') =~# '\s'
+endfunction
+
+" }}}1
+function! matchup#util#in_indent(...) " {{{1
+ let l:pos = a:0 > 0 ? [a:1, a:2] : [line('.'), col('.')]
+ return l:pos[1] > 0 && getline(l:pos[0]) =~# '^\s*\%'.(l:pos[1]+1).'c'
+endfunction
+
+" }}}1
+
+function! matchup#util#uniq(list) " {{{1
+ if exists('*uniq') | return uniq(a:list) | endif
+ if len(a:list) <= 1 | return a:list | endif
+
+ let l:uniq = [a:list[0]]
+ for l:next in a:list[1:]
+ if l:uniq[-1] != l:next
+ call add(l:uniq, l:next)
+ endif
+ endfor
+ return l:uniq
+endfunction
+
+" }}}1
+function! matchup#util#uniq_unsorted(list) " {{{1
+ if len(a:list) <= 1 | return a:list | endif
+
+ let l:visited = [a:list[0]]
+ for l:index in reverse(range(1, len(a:list)-1))
+ if index(l:visited, a:list[l:index]) >= 0
+ call remove(a:list, l:index)
+ else
+ call add(l:visited, a:list[l:index])
+ endif
+ endfor
+ return a:list
+endfunction
+
+" }}}1
+function! matchup#util#has_duplicate_str(list) " {{{1
+ if len(a:list) <= 1 | return 0 | endif
+ let l:seen = {}
+ for l:elem in a:list
+ if has_key(l:seen, l:elem)
+ return 1
+ endif
+ let l:seen[l:elem] = 1
+ endfor
+ return 0
+endfunction
+
+" }}}1
+
+function! matchup#util#patch_match_words(from, to, ...) abort " {{{1
+ if !exists('b:match_words') | return | endif
+
+ " if extra argument is given, give diagnostic information
+ if a:0
+ let l:first = stridx(b:match_words, a:from)
+ if l:first < 0
+ echoerr 'match-up: patch_match_words:' a:from 'not found'
+ return
+ elseif stridx(b:match_words, a:from, l:first+1) > -1
+ echoerr 'match-up: patch_match_words: multiple occurences of' a:from
+ return
+ endif
+ endif
+
+ let b:match_words = substitute(b:match_words,
+ \ '\V'.escape(a:from, '\'),
+ \ escape(a:to, '\'),
+ \ '')
+endfunction
+
+" }}}1
+function! matchup#util#check_match_words(sha256) " {{{1
+ if !exists('b:match_words') | return 0 | endif
+ return sha256(b:match_words) =~# '^'.a:sha256
+endfunction
+
+" }}}1
+function! matchup#util#append_match_words(str) abort " {{{1
+ if !exists('b:match_words') | return | endif
+
+ if len(b:match_words) && b:match_words[-1] !=# ',' && a:str[0] !=# ','
+ let b:match_words .= ','
+ endif
+ let b:match_words .= a:str
+endfunction
+
+" }}}1
+
+function! matchup#util#matchpref(id, default) " {{{1
+ return get(get(g:matchup_matchpref, &filetype, {}), a:id, a:default)
+endfunction
+
+" }}}1
+
+let &cpo = s:save_cpo
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/where.vim b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/where.vim
new file mode 100644
index 0000000..9a65f9b
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/autoload/matchup/where.vim
@@ -0,0 +1,156 @@
+" vim match-up - even better matching
+"
+" Maintainer: Andy Massimino
+" Email: a@normed.space
+"
+
+scriptencoding utf8
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+let s:curpos = []
+
+function! matchup#where#get(timeout) abort " {{{1
+ let l:save_view = winsaveview()
+ let l:trail = []
+
+ let l:prev = [matchup#pos#get_cursor_line(), 1]
+ call matchup#pos#set_cursor(l:prev)
+ for l:dummy in range(15)
+ " TODO make this into an api
+ " TODO replace with a faster version with searchpairpos/return value?
+ let l:opts_io = {
+ \ '__where_impl__': 1,
+ \ 'timeout': a:timeout,
+ \}
+ call matchup#motion#find_unmatched(0, 0, l:opts_io)
+
+ if matchup#pos#get_cursor()[1:2] == l:prev
+ break
+ endif
+
+ let l:prev = matchup#pos#get_cursor()[1:2]
+ call add(l:trail, l:prev + [l:opts_io.delim])
+ endfor
+
+ call winrestview(l:save_view)
+ return reverse(l:trail)
+endfunction
+
+" }}}1
+
+function! s:print_verbose() " {{{1
+ echohl Title | echon 'match-up:' | echohl None
+ echon ' loading...'
+ let l:trail = matchup#where#get(500)
+ redraw!
+ if empty(l:trail)
+ echohl Title | echon 'match-up:' | echohl None
+ echon ' no context found'
+ return
+ endif
+ let l:last = -1
+ for l:t in l:trail
+ let l:opts = {
+ \ 'noshowdir': 1,
+ \ 'width': &columns - 1,
+ \}
+ let [l:str, l:adj] = matchup#matchparen#status_str(l:t[2], l:opts)
+ if l:adj == l:last
+ continue
+ endif
+ if l:last != -1
+ echon "\n"
+ endif
+ call s:EchoHLString(l:str)
+ let l:last = l:adj
+ endfor
+endfunction
+
+" }}}1
+function! s:print_short() " {{{1
+ echohl Title | echon 'match-up:' | echohl None
+ echon ' loading...'
+ let l:trail = matchup#where#get(200)
+ redraw!
+ if empty(l:trail)
+ echohl Title | echon 'match-up:' | echohl None
+ echon ' no context found'
+ return
+ endif
+ " TODO len(trail) is not quite right here
+ let l:width = (&columns - 3*(len(l:trail)-1)) / len(l:trail)
+ let l:fullstr = ''
+ let l:prev = -1
+ for l:t in l:trail
+ let l:opts = {
+ \ 'noshowdir': 1,
+ \ 'compact': l:prev != -1,
+ \ 'width': l:width,
+ \}
+ let [l:str, l:adj] = matchup#matchparen#status_str(l:t[2], l:opts)
+ if l:adj == l:prev
+ continue
+ endif
+ if l:prev != -1
+ let l:fullstr .= ' %#Title#' . s:arrow() . '%#Normal# '
+ endif
+ let l:fullstr .= l:str
+ let l:prev = l:adj
+ endfor
+ call matchup#perf#tic('where')
+ call s:EchoHLString(l:fullstr)
+ call matchup#perf#toc('where', 'echohlstring')
+endfunction
+
+function! s:arrow()
+ if empty(g:matchup_where_separator)
+ return '▶'
+ endif
+ return g:matchup_where_separator
+endfunction
+
+" }}}1
+
+function! matchup#where#print(args)
+ let l:verbose = 0
+ if a:args =~ '!' || len(a:args) >= 2
+ \ || a:args =~ '?' && s:curpos == getcurpos()
+ let l:verbose = 1
+ endif
+ let s:curpos = getcurpos()
+
+ if l:verbose
+ call s:print_verbose()
+ else
+ call s:print_short()
+ endif
+endfunction
+
+function! s:EchoHLString(str)
+ let l:str = '%<' . substitute(a:str, '%{[^}]\+}', '', 'g')
+ let l:pat = '\%(%\(<\)\|%#\(\w*\)#\)'
+ let l:components = split(l:str, l:pat.'\&')
+ call map(l:components, 'matchlist(v:val, "^".l:pat."\\(.*\\)")')
+
+ for l:c in l:components
+ let l:m = matchlist(l:c, '^'.l:pat.'\(.*\)')
+ if empty(l:m)
+ let l:str = l:c
+ elseif !empty(l:m[1])
+ let l:str = l:m[3]
+ echon l:m[2]
+ elseif !empty(l:m[2])
+ let l:str = l:m[3]
+ execute 'echohl' l:m[2]
+ endif
+ echon l:str
+ endfor
+ echohl NONE
+endfunction
+
+let &cpo = s:save_cpo
+
+" vim: fdm=marker sw=2
+
diff --git a/etc/soft/nvim/+plugins/vim-matchup/doc/matchup.txt b/etc/soft/nvim/+plugins/vim-matchup/doc/matchup.txt
new file mode 100644
index 0000000..09a8165
--- /dev/null
+++ b/etc/soft/nvim/+plugins/vim-matchup/doc/matchup.txt
@@ -0,0 +1,1072 @@
+*matchup.txt* modern matching words
+*matchup* *match-up*
+
+Author: Andy Massimino
+Web: https://github.com/andymass/vim-matchup
+Script ID: 5624
+License: MIT license {{{
+
+ Copyright (c) 2020 Andy Massimino
+
+ Copyright (c) 2016 Karl Yngve Lervåg
+
+ 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.
+
+}}}
+
+==============================================================================
+CONTENTS *matchup-contents*
+
+ Introduction |matchup-introduction|
+ Feature overview |matchup-overview|
+ Usage |matchup-usage|
+ Default mappings |matchup-default-mappings|
+ Customizing mappings |matchup-custom-mappings|
+ Features |matchup-features|
+ Commands |matchup-commands|
+ Options |matchup-options|
+ File type options |matchup-file-types|
+ FAQ |matchup-faq|
+ Interoperability |matchup-interoperability|
+ Acknowledgments |matchup-acknowledgments|
+ Development |matchup-development|
+
+==============================================================================
+INTRODUCTION *matchup-introduction*
+
+|match-up| is a plugin that lets you visualize, navigate, and operate on
+matching sets of text. It is a replacement for the venerable vim plugin
+|matchit|. match-up aims to replicate all of |matchit|'s features, fix a number
+of its deficiencies and bugs, and add a few totally new features. It also
+replaces the standard plugin |matchparen|, allowing all of |matchit|'s words to be
+highlighted along with the 'matchpairs' symbols such as `()`, `{}`, and `[]`.
+
+------------------------------------------------------------------------------
+Feature overview~
+ *matchup-overview*
+
+This plugin:
+
+- Extends vim's |%| (see |matchup-%|) motion to language-specific words instead of
+ just single characters. The words depend on the specific |filetype| plugin
+ (|ftplugin|). The following built-in vim filetype plugins
+ currently provide support for match-up: >
+
+ abaqus, ada, aspvbs, c, clojure, cobol, config, context, csc, csh, dtd,
+ dtrace, eiffel, eruby, falcon, fortran, framescript, haml, hamster, hog,
+ html, ishd, j, jsp, kconfig, liquid, lua, make, matlab, mf, mp, ocaml,
+ pascal, pdf, perl, php, plaintex, postscr, ruby, sh, spec, sql, tex, vb,
+ verilog, vhdl, vim, xhtml, xml, zimbu, zsh
+<
+- Highlights symbols and words under the cursor along with their matches.
+
+- Display off-screen matches in the |status-line|.
+
+- Adds motions |g%|, |[%|, |]%|, and |z%|.
+
+- Provides analogous text objects |i%| and |a%|.
+
+==============================================================================
+USAGE *matchup-usage*
+
+------------------------------------------------------------------------------
+Default mappings~
+ *matchup-default-mappings*
+
+For customization, see |matchup-custom-mappings|.
+
+ *matchup-%*
+% When on a recognized word, go forwards to its next
+ matching word. If at a close word, cycle back to the
+ corresponding open word. If the cursor is not on a
+ word, seek forwards to one and then jump to its match.
+ This is an |inclusive| motion.
+
+ *matchup-N%*
+{count}% If {count} is less than 6, go forwards {count} times.
+ Otherwise, go to {count} percentage in the file (|N%|).
+ See |g:matchup_motion_override_Npercent|.
+
+ *g%*
+g% When on a recognized word, go backwards to [count]th
+ previous matching word. If at an open word, cycle
+ around to the corresponding open word. If the cursor
+ is not on a word, seek forwards to one and then jump
+ to its match.
+
+ *[%*
+[% Go to [count]th previous outer open word. Allows
+ navigation to the start of blocks surrounding the
+ cursor. This is similar to vim's built-in |[(| and |[{|
+ and is an |exclusive| motion (|matchup-feat-exclusive|).
+
+ *]%*
+]% Go to [count]th next outer close word. Allows
+ navigation to the end of blocks surrounding the
+ cursor. This is similar to vim's built-in |])| and |]}|
+ and is an |exclusive| motion (|matchup-feat-exclusive|).
+
+ *z%*
+z% Go to inside [count]th nearest block. This is an
+ |exclusive| motion when used with operators,
+ except it eats white-space. For example, where `█` is
+ the cursor position, >
+
+ █ call somefunction( param1, param2)
+<
+ `dz%` produces >
+
+ param1, param2)
+<
+ *v_a%* *a%*
+a% Select an |any-block|.
+ This closely matches vim's built-in |ab|.
+
+ *v_Na%* *Na%*
+{count}a% Select an |open-to-close-block|.
+ When {count} is greater than 1, select the {count}th
+ surrounding open-to-close block.
+
+ *v_i%* *i%*
+i% Select the inside of an |any-block|.
+ This closely matches vim's built-in |ib|.
+ See also |matchup-feat-linewise|.
+
+ *v_Ni%* *Ni%*
+{count}i% Select the inside of an |open-to-close-block|.
+ When {count} is greater than 1, select the inside of
+ the {count}th surrounding open-to-close block.
+
+ *ds%*
+{count}ds% Delete {count}th surrounding matching words. This
+ only works for open and close words.
+ Requires |g:matchup_surround_enabled| = 1.
+
+ *cs%*
+{count}cs% Change {count}th surrounding matching words. This
+ only works for open and close words. If vim-surround
+ is installed, you can type replacements according to
+ that plugin's rules. Otherwise, match-up will give
+ you the opportunity to type a single character. Some
+ simple replacement pairs are supported.
+ Requires |g:matchup_surround_enabled| = 1.
+
+------------------------------------------------------------------------------
+Customizing mappings~
+ *matchup-custom-mappings*
+
+|match-up| provides a number of default mappings. Each right-hand side is
+provided as a -mapping (see |using-|). For any given map, the
+default mapping will only be created if it does not already exist. This means
+that if a user defines a custom mapping, e.g., with >
+
+ nmap % (matchup-z%)
+<
+the corresponding default left-hand side will not be mapped.
+
+ -------------------------------------------------------------------------~
+ LHS RHS Mode Module
+ -------------------------------------------------------------------------~
+ % |(matchup-%)| nx motion
+ g% |(matchup-g%)| nx motion
+ [% |(matchup-[%)| nx motion
+ ]% |(matchup-]%)| nx motion
+ z% |(matchup-z%)| nx motion
+ a% |(matchup-%)| x text_obj
+ i% |(matchup-%)| x text_obj
+ ds% |(matchup-ds%)| n surround
+ cs% |(matchup-cs%)| n surround
+ (none) |(matchup-hi-surround)| n matchparen
+ -------------------------------------------------------------------------~
+ Operator pending maps~
+ -------------------------------------------------------------------------~
+ % |(matchup-%)| o motion
+ ... And so on for g%, [%, ]%, z%, a%, i% ...
+
+Key: (n) normal, (x) visual, (o) operator-pending (|omap-info|).
+
+Note: Prior to vim 8.1.0648, in order to support motion forcing (see |o_v|, |o_V|,
+|o_CTRL-V|) for each operator-pending map, match-up made four maps corresponding
+to each motion type:
+
+ -------------------------------------------------------------------------~
+ Obsolete operator pending maps~
+ -------------------------------------------------------------------------~
+ % |(matchup-o_)||(matchup-%)| o motion
+ v% |(matchup-o_v)||(matchup-%)| o motion
+ V% |(matchup-o_V)||(matchup-%)| o motion
+ % |(matchup-o_)||(matchup-%)| o motion
+ ... And so on for g%, [%, ]%, z%, a%, i% ...
+
+This meant, for example, `d2v%` would work (meaning delete 2 matches forward,
+exclusively), but `dv2%` would not work as you would expect (the behavior is
+undefined). After vim version 8.1.0648, this limitation of operator-pending
+mapping was lifted and match-up is able to use simplified mappings.
+
+------------------------------------------------------------------------------
+Features~
+ *matchup-features*
+
+What do we mean by open, close, mid? This depends on the specific file type
+and is configured through the variable |b:match_words|. Here are a couple
+of examples: >
+
+ if l:x == 1
+ call one()
+ elseif l:x == 2
+ call two()
+ else
+ call three()
+ endif
+<
+ *any-block* *open-to-close-block*
+
+For the vim-script language, match-up understands the words `if`, `else`, `elseif`,
+`endif` and that they form a sequential construct. The "open" word is `if`, the
+"close" word is `endif`, and the "mid" words are `else` and `elseif`. The `if`/`endif`
+pair is called an "open-to-close" block and the `if`/`else`, `else`/`elsif`, and
+`elseif`/`endif` are called "any" blocks.
+
+C, C++: >
+
+ #if 0
+ #else
+ #endif
+
+ void some_func() {
+ if (true) {
+ one();
+ } else if (false && false) {
+ two();
+ } else {
+ three();
+ }
+ }
+<
+Since in C and C++, blocks are delimited using braces (`{` & `}`), match-up will
+recognize `{` as the open word and `}` as the close word. It will ignore the if
+and else if because they are not defined in vim's C file type plugin.
+
+On the other hand, match-up will recognize the `#if`, `#else`, `#endif` preprocessor
+directives.
+
+See |matchup-feat-exclusive| and |matchup-feat-linewise| for some examples and
+important special cases.
+
+Highlighting matches~
+
+To disable match highlighting at |startup|, use >
+
+ let g:matchup_matchparen_enabled = 0
+>
+in your vimrc. See |matchup-opts-matchparen| for more information and related
+options.
+
+You can enable highlighting on the fly using >
+
+ :DoMatchParen
+<
+Likewise, you can disable highlighting at any time using >
+
+ :NoMatchParen
+<
+After start-up, is better to use `:NoMatchParen` and `:DoMatchParen` to toggle
+highlighting globally than setting the global variable since these commands
+make sure not to leave stale matches around.
+
+Display matches off screen~
+
+If an open or close match which would have been highlighted is on a line
+positioned outside of the current window, the match is shown in the status
+line (the default). If both the open and close match are off-screen, the
+close match is preferred.
+See |g:matchup_matchparen_offscreen| for configuration.
+
+ *(matchup-hi-surround)*
+Highlight surrounding~
+
+To highlight the surrounding delimiters until the cursor moves, use a map such
+as the following >
+
+ nmap (matchup-hi-surround)
+<
+There is no default map for this feature.
+
+Parallel transmutation~
+
+In insert mode, after changing text inside a word, matching words will be
+changed in parallel. As an example, >
+
+
+ text
+
+<
+Changing `pre` to `div` and leaving insert mode will produce: >
+
+
+ text
+
+<
+Note: this currently only works for match words which define a backref
+relation like `\1`. A wider set of transmutations are planned.
+
+Parallel transmutation requires the matchparen module to be enabled.
+
+ *matchup-feat-exclusive*
+Inclusive and exclusive motions~
+
+In vim, character motions following operators (such as `d` for delete and `c` for
+change) are either |inclusive| or |exclusive|. This means they either include
+the ending position or not. Here, "ending position" means the line and column
+closest to the end of the buffer of the region swept over by the motion.
+match-up is designed so that `d]%` inside a set of parenthesis behaves exactly
+like `d])`, except generalized to words.
+
+Put differently, forward exclusive motions will not include the close word.
+In this example, where `█` is the cursor position, >
+
+ if █x | continue | endif
+<
+pressing `d]%` will produce (cursor on the `e`) >
+
+ if endif
+<
+To include the close word, use either `dv]%` or `vd]%`. This is also
+compatible with vim's `dv])` and `dv]}`.
+
+Operators over backward exclusive motions will instead exclude the position
+the cursor was on before the operator was invoked. For example, in >
+
+ if █x | continue | endif
+<
+pressing `d[%` will produce >
+
+ █x | continue | endif
+<
+This is compatible with vim's `d[(` and `d[{`.
+
+ *d%* *dg%*
+
+Unlike `]%`, `%` is an |inclusive| motion. As a special case for the `d` (delete)
+operator, if `d%` leaves behind lines white-space, they will be deleted also.
+In effect, it will be operating line-wise. As an example, pressing `d%` will
+leave behind nothing. >
+
+ █(
+
+ )
+<
+To operate character-wise in this situation, use `dv%` or `vd%`. This is vim
+compatible with the built-in `d%` on items in 'matchpairs'.
+
+ *matchup-feat-linewise*
+Line-wise operator/text-object combinations~
+
+Normally, the text objects |i%| and |a%| work character-wise. However,
+there are some special cases. For certain operators combined with |i%|,
+under certain conditions, match-up will effectively operate line-wise
+instead. For example, in >
+
+ if condition
+ █call one()
+ call two()
+ endif
+<
+pressing `di%` will produce >
+
+ if condition
+ endif
+<
+even though deleting ` condition` would be suggested by the object `i%`.
+The intention is to make operators more useful in some cases. The
+following rules apply:
+
+1. The operator must be listed in |g:matchup_text_obj_linewise_operators|.
+ By default this is |d| and |y| (e.g., `di%` and `ya%`).
+
+2. The outer block must span multiple lines.
+
+3. The open and close delimiters must be more than one character long. In
+ particular, `di%` involving a `(`...`)` block will not be subject to
+ these special rules.
+
+To prevent this behavior for a particular operation, use `vi%d`. Note that
+special cases involving indentation still apply (like with |i)| etc).
+
+To disable this entirely, remove the operator from the following variable, >
+
+ let g:matchup_text_obj_linewise_operators = [ 'y' ]
+<
+Note: unlike vim's built-in |i)|, |ab|, etc., |i%| does not make an existing visual
+mode character-wise.
+
+A second special case involves `da%`. In this example, >
+
+ if condition
+ █call one()
+ call two()
+ endif
+<
+pressing `da%` will delete all four lines and leave no white-space. This is
+compatible with vim's `da(`, `dab`, etc.
+
+------------------------------------------------------------------------------
+Commands~
+ *matchup-commands*
+
+ *:NoMatchParen* *matchup-:NoMatchParen*
+:NoMatchParen Disable matching after the plugin was loaded.
+ *:DoMatchParen* *matchup-:DoMatchParen*
+:DoMatchParen Enable matching again.
+
+ *:MatchupShowTimes*
+:MatchupShowTimes Display performance data, useful for debugging slow
+ matching. Shows last, average, and maximum times.
+ *:MatchupClearTimes*
+:MatchupClearTimes Reset performance counters to zero.
+
+ *:MatchupWhereAmI*
+:MatchupWhereAmI? Echos your position in code by finding successive matching
+ words, like doing `[%` repeatedly. Example:
+>
+ 1310 do_pending_operator( … { ⯈ if ((finish_op || VIsual_active) && oap- … {
+<
+ Running this command twice in the same position will give
+ more verbose output.
+
+:MatchupWhereAmI?? Be more verbose about your position in code by displaying
+ several lines. Example:
+>
+ 1310 do_pending_operator(cmdarg_T *cap, int old_col, int gui_yank) … {
+ 1349 if ((finish_op || VIsual_active) && oap->op_type != OP_NOP) … {
+<
+ *:MatchupReload*
+:MatchupReload Reload the plugin, mostly for debugging. Note that
+ this does not reload 'matchpairs' or `b:match_word`.
+
+------------------------------------------------------------------------------
+Options~
+ *matchup-options*
+
+*g:matchup_enabled*
+
+ Set to 0 to disable the plugin entirely.
+
+ Default: 1
+
+*g:matchup_mappings_enabled* all mappings
+|g:matchup_matchparen_enabled| match highlighting
+*g:matchup_mouse_enabled* double click to select matches
+*g:matchup_motion_enabled* |matchup-%|, |%|, |[%|, |]%|
+*g:matchup_text_obj_enabled* |a%|, |i%|
+
+ Set to 0 to disable a particular module or feature.
+
+ Defaults: 1
+
+*g:matchup_transmute_enabled*
+
+ Set to 1 to enable the experimental transmute feature (|matchup-transmute|).
+
+ Default: 0
+
+*g:matchup_delim_stopline*
+
+ Configures the number of lines to search in either direction while using
+ motions and text objects. Does not apply to match highlighting
+ (see |g:matchup_matchparen_stopline| instead).
+
+ Default: 1500
+
+*g:matchup_delim_noskips*
+
+ This option controls whether matching is done within strings and comments.
+ By default, it is set to 0 which means all valid matches are made within
+ strings and comments. If set to 1, symbols like `()` will still be matched
+ but words like `for` and `end` will not. If set to 2, nothing will be
+ matched within strings and comments. >
+
+ let g:matchup_delim_noskips = 1
+ let g:matchup_delim_noskips = 2
+<
+ Default: 0 (matching enabled within strings and comments)
+
+*g:matchup_delim_start_plaintext*
+
+ When enabled (the default), the plugin will be loaded for all buffers,
+ including ones without a file type set. This allows matching to be done in
+ new buffers and plain text files but adds a small start-up cost to vim.
+
+ Default: 1
+
+Variables~
+
+*b:match_words*
+*b:match_skip*
+*b:match_ignorecase*
+
+ match-up understands these variables originally from matchit. These are set
+ in the respective |ftplugin| files. They may not exist for every file type.
+ To support a new file type, create a file `after/ftplugin/{filetype}.vim`
+ which sets them appropriately.
+
+ *matchup-opts-matchparen*
+Module matchparen~
+
+*g:matchup_matchparen_enabled*
+
+ This option will disable match highlighting at |startup|, e.g., use >
+
+ let g:matchup_matchparen_enabled = 0
+<
+ in your vimrc.
+
+ Note: vim's built-in plugin |pi_paren| plugin is also disabled. The variable
+ `g:loaded_matchparen` has no effect on match-up.
+
+ Default: 1
+
+ You can also enable and disable highlighting for specific buffers using the
+ variable |b:matchup_matchparen_enabled|.
+
+ Customizing the highlighting colors~
+
+ match-up uses the |MatchParen| highlighting group by default, which can be
+ configured. For example, >
+
+ :hi MatchParen ctermbg=blue guibg=lightblue cterm=italic gui=italic
+<
+ You may want to put this inside a |ColorScheme| |autocmd| so it is preserved
+ after colorscheme changes: >
+
+ augroup matchup_matchparen_highlight
+ autocmd!
+ autocmd ColorScheme * hi MatchParen guifg=red
+ augroup END
+<
+ You can also highlight words differently than parentheses using the
+ `MatchWord` highlighting group. You might do this if you find the
+ `MatchParen` style distracting for large blocks. >
+
+ :hi MatchWord ctermfg=red guifg=blue cterm=underline gui=underline
+<
+ There are also `MatchParenCur` and `MatchWordCur` which allow you to configure
+ the highlight separately for the match under the cursor. >
+
+ :hi MatchParenCur cterm=underline gui=underline
+ :hi MatchWordCur cterm=underline gui=underline
+<
+*b:matchup_matchparen_enabled*
+
+ Set to 0 to disable highlighting on a per-buffer basis (there is no command
+ for this). By default, when disabling highlighting for a particular buffer,
+ the standard plugin |pi_paren| will still be used for that buffer.
+
+ Default: undefined (equivalent to 1)
+
+*b:matchup_matchparen_fallback*
+
+ If highlighting is disabled on a particular buffer, match-up will fall back
+ to the vim standard plugin |pi_paren|, which will highlight 'matchpairs' such
+ as `()`, `[]`, & `{}`. To disable this, set this option to 0.
+
+ Default: undefined (equivalent to 1)
+
+ A common usage of these options is to automatically disable matchparen for
+ particular file types: >
+
+ augroup matchup_matchparen_disable_ft
+ autocmd!
+ autocmd FileType tex let [b:matchup_matchparen_fallback,
+ \ b:matchup_matchparen_enabled] = [0, 0]
+ augroup END
+<
+*g:matchup_matchparen_singleton*
+
+ Whether or not to highlight recognized words even if there is no match.
+
+ Default: 0
+
+*g:matchup_matchparen_offscreen*
+
+ Dictionary controlling the behavior with off-screen matches. If empty, this
+ feature is disabled. Else, it should contain the following optional keys:
+
+ method~
+ Sets the method to use to show off-screen matches.
+ Possible values are:
+
+ `'status'` (default): Replace the |status-line| for off-screen matches.
+
+ If a match is off of the screen, the line belonging to that match will be
+ displayed syntax-highlighted in the status line along with the line number
+ (if line numbers are enabled). If the match is above the screen border,
+ an additional Δ symbol will be shown to indicate that the matching line is
+ really above the cursor line.
+
+ `'status_manual'`: Compute the status-line but do not display it (future
+ extension).
+
+ `'popup'`: Use a popup window (requires at least vim 8.1.1406) or
+ a floating window (in neovim) to show the off-screen match.
+
+ scrolloff~
+ When enabled, off-screen matches will not be shown in the statusline while
+ the cursor is at the screen edge (respects the value of 'scrolloff').
+ This is intended to prevent flickering while scrolling with j and k.
+
+ Default: 0
+
+ Default: `{'method': 'status'}`
+
+*g:matchup_matchparen_stopline*
+
+ The number of lines to search in either direction while highlighting
+ matches. Set this conservatively since high values may cause performance
+ issues.
+
+ Default: 400
+
+*g:matchup_matchparen_timeout*
+*g:matchup_matchparen_insert_timeout*
+
+ Adjust the timeouts in milliseconds for highlighting.
+
+ Defaults: `g:matchparen_timeout`, `g:matchparen_insert_timeout`
+ (300, 60 respectively)
+
+*b:matchup_matchparen_timeout*
+*b:matchup_matchparen_insert_timeout*
+
+ Buffer local versions of the above.
+
+*g:matchup_matchparen_deferred*
+
+ Deferred highlighting improves cursor movement performance (for example, when
+ using |hjkl|) by delaying highlighting for a short time and waiting to see if
+ the cursor continues moving.
+
+ Default: 0 (disabled)
+
+Note: this feature is only available if your vim version has |timers| and
+the function |timer_pause|, version 7.4.2180 and after. For neovim, this
+will only work in nvim-0.2.1 and after.
+
+*g:matchup_matchparen_deferred_show_delay*
+
+ Delay, in milliseconds, between when the cursor moves and when we start
+ checking if the cursor is on a match. Applies to both making highlights and
+ clearing them for deferred highlighting.
+
+ Note: these delays cannot be changed dynamically and should be configured
+ before the plugin loads (e.g., in your vimrc).
+
+ Default: 50
+
+*g:matchup_matchparen_deferred_hide_delay*
+
+ If the cursor has not stopped moving, assume highlight is stale after this
+ many milliseconds. Stale highlights are hidden.
+
+ Note: this option cannot be changed dynamically.
+
+ Default: 700
+
+*g:matchup_matchparen_deferred_fade_time* *matchup-fading*
+
+ When set to {time} in milliseconds, the deferred highlighting behavior
+ is changed in two ways:
+ 1. Highlighting of matches is preserved for at least {time} even when
+ the cursor is moved away.
+ 2. If the cursor stays on the same match for longer than {time},
+ highlighting is cleared.
+ The effect is that highlighting occurs momentarily and then disappears,
+ regardless of where the cursor is. It is possible that fading takes
+ longer than {time}, if vim is busy doing other things.
+
+ This value should be greater than the deferred show delay.
+ Note: this option cannot be changed dynamically.
+
+ Example: >
+
+ let g:matchup_matchparen_deferred = 1
+ let g:matchup_matchparen_deferred_fade_time = 450
+<
+ Default: 0 (fading disabled)
+
+*g:matchup_matchparen_pumvisible*
+
+ If set to 1, matches will be made even when the |popupmenu-completion| is
+ visible. If you use an auto-complete plugin which interacts badly with
+ matching, set this option to 0.
+
+ Default: 1
+
+*g:matchup_matchparen_nomode*
+
+ When not empty, match highlighting will be disabled in the specified modes,
+ where each mode is a single character like in the |mode()| function. E.g., to
+ disable highlighting in insert mode,
+>
+ let g:matchup_matchparen_nomode = 'i'
+<
+ and in visual modes,
+>
+ let g:matchup_matchparen_nomode = "vV\"
+<
+ Note: In visual modes, this takes effect only after moving the cursor.
+
+ Default: ''
+
+*g:matchup_matchparen_hi_surround_always*
+
+ Always highlight the surrounding words, if possible. This is like
+ |(matchup-hi-surround)| but is updated each time the cursor moves.
+ This requires deferred matching (|g:matchup_matchparen_deferred| = 1).
+
+ Default: 0
+
+*g:matchup_matchparen_hi_background*
+
+ Highlight buffer background between matches. This uses the `MatchBackground`
+ highlighting group and is linked to `ColorColumn` by default but can be
+ configured with >
+
+ :hi MatchBackground guibg=grey ctermbg=grey
+<
+ Default: 0
+
+Module motion~
+
+*g:matchup_motion_override_Npercent*
+
+ In vim, {count}% goes to the {count} percentage in the file (see |N%|).
+ match-up overrides this motion for small {count} (by default, anything less
+ than 7). For example, to allow {count}% for {count} up to 11, >
+ let g:matchup_motion_override_Npercent = 11
+<
+ To disable this feature, and restore vim's default {count}%, >
+ let g:matchup_motion_override_Npercent = 0
+<
+*g:matchup_motion_cursor_end*
+
+ If enabled, cursor will land on the end of mid and close words while
+ moving downwards (|%|/|]%|). While moving upwards (|g%|, |[%|) the cursor
+ will land on the beginning. Set to 0 to disable.
+ Note: this has no effect on operators: `d%` will delete |inclusive| of the
+ ending word (this is compatible with matchit).
+
+ Default: 1
+
+*g:matchup_delim_count_fail*
+
+ When disabled (default), giving an invalid count to the |[%| and |]%| motions
+ and the text objects |i%| and |a%| will cause the motion or operation to fail.
+ When enabled, they will move as far as possible.
+ Note: targeting high counts when this option is enabled can become slow
+ because many positions need to be tried before giving up.
+
+ Default: 0
+
+Module text_obj~
+
+*g:matchup_text_obj_linewise_operators*
+
+ Modifies the set of operators which may operate line-wise with |i%|
+ (see |matchup-feat-linewise|).
+
+ You may use 'v', 'V', and "\" (i.e., an actual CTRL-V character) to the
+ specify the corresponding visual mode.
+
+ You can also specify custom plugin operators with 'g@' and optionally, an
+ expression separated by a comma. For example, to make |commentary|'s |gc|
+ mapping work likewise when used in the operator `gci%`, >
+
+ function! IsCommentaryOpFunc()
+ return &operatorfunc ==? matchstr(maparg('Commentary', 'n'),
+ \ '\c\w\+\ze()\|set op\%(erator\)\?func=\zs.\{-\}\ze