├── .gitignore ├── README.md ├── plugin └── matchit.vim ├── doc └── matchit.txt └── autoload └── matchit.vim /.gitignore: -------------------------------------------------------------------------------- 1 | # File types to be ignored by our git repositories 2 | 3 | .DS_Store 4 | doc/tags 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vim-Matchit 2 | 3 | This plugin provides extended matching for the `%` operator. 4 | 5 | This repository maintain the last version from the [matchit.vim](https://github.com/vim/vim/tree/master/runtime/pack/dist/opt/matchit) plugin from the official Vim repository, allowing to use with plugin managers. 6 | 7 | **Main Repository:** https://github.com/chrisbra/matchit 8 | 9 | Bugs can be reported at the main repository. 10 | 11 | The latest development snapshot can also be downloaded there. 12 | 13 | ## Installing 14 | 15 | With [vim-plug](https://github.com/junegunn/vim-plug) 16 | 17 | ```vim 18 | Plug 'https://github.com/adelarsq/vim-matchit' 19 | ``` 20 | 21 | With Vim package feature should be cloned below `pack/dist/opt` in your runtime path (`~/.vim/pack/dist/opt/matchit`) then activated with: 22 | 23 | ```vim 24 | packadd! matchit 25 | ``` 26 | 27 | ## Alternatives 28 | 29 | - [vim match-up](https://github.com/andymass/vim-matchup) 30 | 31 | ## References 32 | 33 | - [Evil Matchit](https://github.com/redguardtoo/evil-matchit) 34 | 35 | ## License & Copyright 36 | 37 | The Vim license (see :h license) applies to the Vim plugin. 38 | 39 | NO WARRANTY, EXPRESS OR IMPLIED. USE AT-YOUR-OWN-RISK 40 | -------------------------------------------------------------------------------- /plugin/matchit.vim: -------------------------------------------------------------------------------- 1 | " matchit.vim: (global plugin) Extended "%" matching 2 | " Maintainer: Christian Brabandt 3 | " Version: 1.17 4 | " Last Change: 2019 Oct 24 5 | " Repository: https://github.com/chrisbra/matchit 6 | " Previous URL:http://www.vim.org/script.php?script_id=39 7 | " Previous Maintainer: Benji Fisher PhD 8 | 9 | " Documentation: 10 | " The documentation is in a separate file: ../doc/matchit.txt 11 | 12 | " Credits: 13 | " Vim editor by Bram Moolenaar (Thanks, Bram!) 14 | " Original script and design by Raul Segura Acevedo 15 | " Support for comments by Douglas Potts 16 | " Support for back references and other improvements by Benji Fisher 17 | " Support for many languages by Johannes Zellner 18 | " Suggestions for improvement, bug reports, and support for additional 19 | " languages by Jordi-Albert Batalla, Neil Bird, Servatius Brandt, Mark 20 | " Collett, Stephen Wall, Dany St-Amant, Yuheng Xie, and Johannes Zellner. 21 | 22 | " Debugging: 23 | " If you'd like to try the built-in debugging commands... 24 | " :MatchDebug to activate debugging for the current buffer 25 | " This saves the values of several key script variables as buffer-local 26 | " variables. See the MatchDebug() function, below, for details. 27 | 28 | " TODO: I should think about multi-line patterns for b:match_words. 29 | " This would require an option: how many lines to scan (default 1). 30 | " This would be useful for Python, maybe also for *ML. 31 | " TODO: Maybe I should add a menu so that people will actually use some of 32 | " the features that I have implemented. 33 | " TODO: Eliminate the MultiMatch function. Add yet another argument to 34 | " Match_wrapper() instead. 35 | " TODO: Allow :let b:match_words = '\(\(foo\)\(bar\)\):\3\2:end\1' 36 | " TODO: Make backrefs safer by using '\V' (very no-magic). 37 | " TODO: Add a level of indirection, so that custom % scripts can use my 38 | " work but extend it. 39 | 40 | " Allow user to prevent loading and prevent duplicate loading. 41 | if exists("g:loaded_matchit") || &cp 42 | finish 43 | endif 44 | let g:loaded_matchit = 1 45 | 46 | let s:save_cpo = &cpo 47 | set cpo&vim 48 | 49 | nnoremap (MatchitNormalForward) :call matchit#Match_wrapper('',1,'n') 50 | nnoremap (MatchitNormalBackward) :call matchit#Match_wrapper('',0,'n') 51 | xnoremap (MatchitVisualForward) :call matchit#Match_wrapper('',1,'v')m'gv`` 52 | xnoremap (MatchitVisualBackward) :call matchit#Match_wrapper('',0,'v')m'gv`` 53 | onoremap (MatchitOperationForward) :call matchit#Match_wrapper('',1,'o') 54 | onoremap (MatchitOperationBackward) :call matchit#Match_wrapper('',0,'o') 55 | 56 | nmap % (MatchitNormalForward) 57 | nmap g% (MatchitNormalBackward) 58 | xmap % (MatchitVisualForward) 59 | xmap g% (MatchitVisualBackward) 60 | omap % (MatchitOperationForward) 61 | omap g% (MatchitOperationBackward) 62 | 63 | " Analogues of [{ and ]} using matching patterns: 64 | nnoremap (MatchitNormalMultiBackward) :call matchit#MultiMatch("bW", "n") 65 | nnoremap (MatchitNormalMultiForward) :call matchit#MultiMatch("W", "n") 66 | xnoremap (MatchitVisualMultiBackward) :call matchit#MultiMatch("bW", "n")m'gv`` 67 | xnoremap (MatchitVisualMultiForward) :call matchit#MultiMatch("W", "n")m'gv`` 68 | onoremap (MatchitOperationMultiBackward) :call matchit#MultiMatch("bW", "o") 69 | onoremap (MatchitOperationMultiForward) :call matchit#MultiMatch("W", "o") 70 | 71 | nmap [% (MatchitNormalMultiBackward) 72 | nmap ]% (MatchitNormalMultiForward) 73 | xmap [% (MatchitVisualMultiBackward) 74 | xmap ]% (MatchitVisualMultiForward) 75 | omap [% (MatchitOperationMultiBackward) 76 | omap ]% (MatchitOperationMultiForward) 77 | 78 | " text object: 79 | xmap (MatchitVisualTextObject) (MatchitVisualMultiBackward)o(MatchitVisualMultiForward) 80 | xmap a% (MatchitVisualTextObject) 81 | 82 | " Call this function to turn on debugging information. Every time the main 83 | " script is run, buffer variables will be saved. These can be used directly 84 | " or viewed using the menu items below. 85 | if !exists(":MatchDebug") 86 | command! -nargs=0 MatchDebug call matchit#Match_debug() 87 | endif 88 | 89 | let &cpo = s:save_cpo 90 | unlet s:save_cpo 91 | 92 | " vim:sts=2:sw=2:et: 93 | 94 | -------------------------------------------------------------------------------- /doc/matchit.txt: -------------------------------------------------------------------------------- 1 | *matchit.txt* Extended "%" matching 2 | 3 | For instructions on installing this file, type 4 | `:help matchit-install` 5 | inside Vim. 6 | 7 | For Vim version 8.1. Last change: 2020 Mar 01 8 | 9 | 10 | VIM REFERENCE MANUAL by Benji Fisher et al 11 | 12 | *matchit* *matchit.vim* 13 | 14 | 1. Extended matching with "%" |matchit-intro| 15 | 2. Activation |matchit-activate| 16 | 3. Configuration |matchit-configure| 17 | 4. Supporting a New Language |matchit-newlang| 18 | 5. Known Bugs and Limitations |matchit-bugs| 19 | 20 | The functionality mentioned here is a plugin, see |add-plugin|. 21 | This plugin is only available if 'compatible' is not set. 22 | 23 | ============================================================================== 24 | 1. Extended matching with "%" *matchit-intro* 25 | 26 | *matchit-%* 27 | % Cycle forward through matching groups, such as "if", "else", "endif", 28 | as specified by |b:match_words|. 29 | 30 | *g%* *v_g%* *o_g%* 31 | g% Cycle backwards through matching groups, as specified by 32 | |b:match_words|. For example, go from "if" to "endif" to "else". 33 | 34 | *[%* *v_[%* *o_[%* 35 | [% Go to [count] previous unmatched group, as specified by 36 | |b:match_words|. Similar to |[{|. 37 | 38 | *]%* *v_]%* *o_]%* 39 | ]% Go to [count] next unmatched group, as specified by 40 | |b:match_words|. Similar to |]}|. 41 | 42 | *v_a%* 43 | a% In Visual mode, select the matching group, as specified by 44 | |b:match_words|, containing the cursor. Similar to |v_a[|. 45 | A [count] is ignored, and only the first character of the closing 46 | pattern is selected. 47 | 48 | In Vim, as in plain vi, the percent key, |%|, jumps the cursor from a brace, 49 | bracket, or paren to its match. This can be configured with the 'matchpairs' 50 | option. The matchit plugin extends this in several ways: 51 | 52 | You can match whole words, such as "if" and "endif", not just 53 | single characters. You can also specify a |regular-expression|. 54 | You can define groups with more than two words, such as "if", 55 | "else", "endif". Banging on the "%" key will cycle from the "if" to 56 | the first "else", the next "else", ..., the closing "endif", and back 57 | to the opening "if". Nested structures are skipped. Using |g%| goes 58 | in the reverse direction. 59 | By default, words inside comments and strings are ignored, unless 60 | the cursor is inside a comment or string when you type "%". If the 61 | only thing you want to do is modify the behavior of "%" so that it 62 | behaves this way, you do not have to define |b:match_words|, since the 63 | script uses the 'matchpairs' option as well as this variable. 64 | 65 | See |matchit-details| for details on what the script does, and |b:match_words| 66 | for how to specify matching patterns. 67 | 68 | MODES: *matchit-modes* *matchit-v_%* *matchit-o_%* 69 | 70 | Mostly, % and related motions (|g%| and |[%| and |]%|) should just work like built-in 71 | |motion| commands in |Operator-pending| and |Visual| modes (as of 8.1.648) 72 | 73 | LANGUAGES: *matchit-languages* 74 | 75 | Currently, the following languages are supported: Ada, ASP with VBS, Csh, 76 | DTD, Entity, Essbase, Fortran, HTML, JSP (same as HTML), LaTeX, Lua, Pascal, 77 | SGML, Shell, Tcsh, Vim, XML. Other languages may already have support via 78 | the default |filetype-plugin|s in the standard vim distribution. 79 | 80 | To support a new language, see |matchit-newlang| below. 81 | 82 | DETAILS: *matchit-details* *matchit-parse* 83 | 84 | Here is an outline of what matchit.vim does each time you hit the "%" key. If 85 | there are |backref|s in |b:match_words| then the first step is to produce a 86 | version in which these back references have been eliminated; if there are no 87 | |backref|s then this step is skipped. This step is called parsing. For 88 | example, "\(foo\|bar\):end\1" is parsed to yield 89 | "\(foo\|bar\):end\(foo\|bar\)". This can get tricky, especially if there are 90 | nested groups. If debugging is turned on, the parsed version is saved as 91 | |b:match_pat|. 92 | 93 | *matchit-choose* 94 | Next, the script looks for a word on the current line that matches the pattern 95 | just constructed. It includes the patterns from the 'matchpairs' option. 96 | The goal is to do what you expect, which turns out to be a little complicated. 97 | The script follows these rules: 98 | 99 | Insist on a match that ends on or after the cursor. 100 | Prefer a match that includes the cursor position (that is, one that 101 | starts on or before the cursor). 102 | Prefer a match that starts as close to the cursor as possible. 103 | If more than one pattern in |b:match_words| matches, choose the one 104 | that is listed first. 105 | 106 | Examples: 107 | 108 | Suppose you > 109 | :let b:match_words = '<:>,:' 110 | < and hit "%" with the cursor on or before the "<" in "a is born". 111 | The pattern '<' comes first, so it is preferred over '', which 112 | also matches. If the cursor is on the "t", however, then '' is 113 | preferred, because this matches a bit of text containing the cursor. 114 | If the two groups of patterns were reversed then '<' would never be 115 | preferred. 116 | 117 | Suppose you > 118 | :let b:match_words = 'if:end if' 119 | < (Note the space!) and hit "%" with the cursor at the end of "end if". 120 | Then "if" matches, which is probably not what you want, but if the 121 | cursor starts on the "end " then "end if" is chosen. (You can avoid 122 | this problem by using a more complicated pattern.) 123 | 124 | If there is no match, the cursor does not move. (Before version 1.13 of the 125 | script, it would fall back on the usual behavior of |%|). If debugging is 126 | turned on, the matched bit of text is saved as |b:match_match| and the cursor 127 | column of the start of the match is saved as |b:match_col|. 128 | 129 | Next, the script looks through |b:match_words| (original and parsed versions) 130 | for the group and pattern that match. If debugging is turned on, the group is 131 | saved as |b:match_ini| (the first pattern) and |b:match_tail| (the rest). If 132 | there are |backref|s then, in addition, the matching pattern is saved as 133 | |b:match_word| and a table of translations is saved as |b:match_table|. If 134 | there are |backref|s, these are determined from the matching pattern and 135 | |b:match_match| and substituted into each pattern in the matching group. 136 | 137 | The script decides whether to search forwards or backwards and chooses 138 | arguments for the |searchpair()| function. Then, the cursor is moved to the 139 | start of the match, and |searchpair()| is called. By default, matching 140 | structures inside strings and comments are ignored. This can be changed by 141 | setting |b:match_skip|. 142 | 143 | ============================================================================== 144 | 2. Activation *matchit-activate* 145 | 146 | To use the matchit plugin add this line to your |vimrc|: > 147 | packadd! matchit 148 | 149 | The script should start working the next time you start Vim. 150 | 151 | (Earlier versions of the script did nothing unless a |buffer-variable| named 152 | |b:match_words| was defined. Even earlier versions contained autocommands 153 | that set this variable for various file types. Now, |b:match_words| is 154 | defined in many of the default |filetype-plugin|s instead.) 155 | 156 | For a new language, you can add autocommands to the script or to your vimrc 157 | file, but the recommended method is to add a line such as > 158 | let b:match_words = '\:\' 159 | to the |filetype-plugin| for your language. See |b:match_words| below for how 160 | this variable is interpreted. 161 | 162 | TROUBLESHOOTING *matchit-troubleshoot* 163 | 164 | The script should work in most installations of Vim. It may not work if Vim 165 | was compiled with a minimal feature set, for example if the |+syntax| option 166 | was not enabled. If your Vim has support for syntax compiled in, but you do 167 | not have |syntax| highlighting turned on, matchit.vim should work, but it may 168 | fail to skip matching groups in comments and strings. If the |filetype| 169 | mechanism is turned off, the |b:match_words| variable will probably not be 170 | defined automatically. 171 | 172 | ============================================================================== 173 | 3. Configuration *matchit-configure* 174 | 175 | There are several variables that govern the behavior of matchit.vim. Note 176 | that these are variables local to the buffer, not options, so use |:let| to 177 | define them, not |:set|. Some of these variables have values that matter; for 178 | others, it only matters whether the variable has been defined. All of these 179 | can be defined in the |filetype-plugin| or autocommand that defines 180 | |b:match_words| or "on the fly." 181 | 182 | The main variable is |b:match_words|. It is described in the section below on 183 | supporting a new language. 184 | 185 | *MatchError* *matchit-hl* *matchit-highlight* 186 | MatchError is the highlight group for error messages from the script. By 187 | default, it is linked to WarningMsg. If you do not want to be bothered by 188 | error messages, you can define this to be something invisible. For example, 189 | if you use the GUI version of Vim and your command line is normally white, you 190 | can do > 191 | :hi MatchError guifg=white guibg=white 192 | < 193 | *b:match_ignorecase* 194 | If you > 195 | :let b:match_ignorecase = 1 196 | then matchit.vim acts as if 'ignorecase' is set: for example, "end" and "END" 197 | are equivalent. If you > 198 | :let b:match_ignorecase = 0 199 | then matchit.vim treats "end" and "END" differently. (There will be no 200 | b:match_infercase option unless someone requests it.) 201 | 202 | *b:match_debug* 203 | Define b:match_debug if you want debugging information to be saved. See 204 | |matchit-debug|, below. 205 | 206 | *b:match_skip* 207 | If b:match_skip is defined, it is passed as the skip argument to 208 | |searchpair()|. This controls when matching structures are skipped, or 209 | ignored. By default, they are ignored inside comments and strings, as 210 | determined by the |syntax| mechanism. (If syntax highlighting is turned off, 211 | nothing is skipped.) You can set b:match_skip to a string, which evaluates to 212 | a non-zero, numerical value if the match is to be skipped or zero if the match 213 | should not be skipped. In addition, the following special values are 214 | supported by matchit.vim: 215 | s:foo becomes (current syntax item) =~ foo 216 | S:foo becomes (current syntax item) !~ foo 217 | r:foo becomes (line before cursor) =~ foo 218 | R:foo becomes (line before cursor) !~ foo 219 | (The "s" is meant to suggest "syntax", and the "r" is meant to suggest 220 | "regular expression".) 221 | 222 | Examples: 223 | 224 | You can get the default behavior with > 225 | :let b:match_skip = 's:comment\|string' 226 | < 227 | If you want to skip matching structures unless they are at the start 228 | of the line (ignoring whitespace) then you can > 229 | :let b:match_skip = 'R:^\s*' 230 | < Do not do this if strings or comments can span several lines, since 231 | the normal syntax checking will not be done if you set b:match_skip. 232 | 233 | In LaTeX, since "%" is used as the comment character, you can > 234 | :let b:match_skip = 'r:%' 235 | < Unfortunately, this will skip anything after "\%", an escaped "%". To 236 | allow for this, and also "\\%" (an escaped backslash followed by the 237 | comment character) you can > 238 | :let b:match_skip = 'r:\(^\|[^\\]\)\(\\\\\)*%' 239 | < 240 | See the $VIMRUNTIME/ftplugin/vim.vim for an example that uses both 241 | syntax and a regular expression. 242 | 243 | ============================================================================== 244 | 4. Supporting a New Language *matchit-newlang* 245 | *b:match_words* 246 | In order for matchit.vim to support a new language, you must define a suitable 247 | pattern for |b:match_words|. You may also want to set some of the 248 | |matchit-configure| variables, as described above. If your language has a 249 | complicated syntax, or many keywords, you will need to know something about 250 | Vim's |regular-expression|s. 251 | 252 | The format for |b:match_words| is similar to that of the 'matchpairs' option: 253 | it is a comma (,)-separated list of groups; each group is a colon(:)-separated 254 | list of patterns (regular expressions). Commas and backslashes that are part 255 | of a pattern should be escaped with backslashes ('\:' and '\,'). It is OK to 256 | have only one group; the effect is undefined if a group has only one pattern. 257 | A simple example is > 258 | :let b:match_words = '\:\,' 259 | \ . '\:\:\:\' 260 | (In Vim regular expressions, |\<| and |\>| denote word boundaries. Thus "if" 261 | matches the end of "endif" but "\" does not.) Then banging on the "%" 262 | key will bounce the cursor between "if" and the matching "endif"; and from 263 | "while" to any matching "continue" or "break", then to the matching "endwhile" 264 | and back to the "while". It is almost always easier to use |literal-string|s 265 | (single quotes) as above: '\' rather than "\\" and so on. 266 | 267 | Exception: If the ":" character does not appear in b:match_words, then it is 268 | treated as an expression to be evaluated. For example, > 269 | :let b:match_words = 'GetMatchWords()' 270 | allows you to define a function. This can return a different string depending 271 | on the current syntax, for example. 272 | 273 | Once you have defined the appropriate value of |b:match_words|, you will 274 | probably want to have this set automatically each time you edit the 275 | appropriate file type. The recommended way to do this is by adding the 276 | definition to a |filetype-plugin| file. 277 | 278 | Tips: Be careful that your initial pattern does not match your final pattern. 279 | See the example above for the use of word-boundary expressions. It is usually 280 | better to use ".\{-}" (as many as necessary) instead of ".*" (as many as 281 | possible). See |\{-|. For example, in the string "label", "<.*>" 282 | matches the whole string whereas "<.\{-}>" and "<[^>]*>" match "" and 283 | "". 284 | 285 | *matchit-spaces* *matchit-s:notend* 286 | If "if" is to be paired with "end if" (Note the space!) then word boundaries 287 | are not enough. Instead, define a regular expression s:notend that will match 288 | anything but "end" and use it as follows: > 289 | :let s:notend = '\%(\:\' 291 | < *matchit-s:sol* 292 | This is a simplified version of what is done for Ada. The s:notend is a 293 | |script-variable|. Similarly, you may want to define a start-of-line regular 294 | expression > 295 | :let s:sol = '\%(^\|;\)\s*' 296 | if keywords are only recognized after the start of a line or after a 297 | semicolon (;), with optional white space. 298 | 299 | *matchit-backref* *matchit-\1* 300 | In any group, the expressions |\1|, |\2|, ..., |\9| refer to parts of the 301 | INITIAL pattern enclosed in |\(|escaped parentheses|\)|. These are referred 302 | to as back references, or backrefs. For example, > 303 | :let b:match_words = '\:\(h\)\1\>' 304 | means that "bo" pairs with "ho" and "boo" pairs with "hoo" and so on. Note 305 | that "\1" does not refer to the "\(h\)" in this example. If you have 306 | "\(nested \(parentheses\)\) then "\d" refers to the d-th "\(" and everything 307 | up to and including the matching "\)": in "\(nested\(parentheses\)\)", "\1" 308 | refers to everything and "\2" refers to "\(parentheses\)". If you use a 309 | variable such as |s:notend| or |s:sol| in the previous paragraph then remember 310 | to count any "\(" patterns in this variable. You do not have to count groups 311 | defined by |\%(\)|. 312 | 313 | It should be possible to resolve back references from any pattern in the 314 | group. For example, > 315 | :let b:match_words = '\(foo\)\(bar\):more\1:and\2:end\1\2' 316 | would not work because "\2" cannot be determined from "morefoo" and "\1" 317 | cannot be determined from "andbar". On the other hand, > 318 | :let b:match_words = '\(\(foo\)\(bar\)\):\3\2:end\1' 319 | should work (and have the same effect as "foobar:barfoo:endfoobar"), although 320 | this has not been thoroughly tested. 321 | 322 | You can use |zero-width| patterns such as |\@<=| and |\zs|. (The latter has 323 | not been thouroughly tested in matchit.vim.) For example, if the keyword "if" 324 | must occur at the start of the line, with optional white space, you might use 325 | the pattern "\(^\s*\)\@<=if" so that the cursor will end on the "i" instead of 326 | at the start of the line. For another example, if HTML had only one tag then 327 | one could > 328 | :let b:match_words = '<:>,<\@<=tag>:<\@<=/tag>' 329 | so that "%" can bounce between matching "<" and ">" pairs or (starting on 330 | "tag" or "/tag") between matching tags. Without the |\@<=|, the script would 331 | bounce from "tag" to the "<" in "", and another "%" would not take you 332 | back to where you started. 333 | 334 | DEBUGGING *matchit-debug* *:MatchDebug* 335 | 336 | If you are having trouble figuring out the appropriate definition of 337 | |b:match_words| then you can take advantage of the same information I use when 338 | debugging the script. This is especially true if you are not sure whether 339 | your patterns or my script are at fault! To make this more convenient, I have 340 | made the command :MatchDebug, which defines the variable |b:match_debug| and 341 | creates a Matchit menu. This menu makes it convenient to check the values of 342 | the variables described below. You will probably also want to read 343 | |matchit-details| above. 344 | 345 | Defining the variable |b:match_debug| causes the script to set the following 346 | variables, each time you hit the "%" key. Several of these are only defined 347 | if |b:match_words| includes |backref|s. 348 | 349 | *b:match_pat* 350 | The b:match_pat variable is set to |b:match_words| with |backref|s parsed. 351 | *b:match_match* 352 | The b:match_match variable is set to the bit of text that is recognized as a 353 | match. 354 | *b:match_col* 355 | The b:match_col variable is set to the cursor column of the start of the 356 | matching text. 357 | *b:match_wholeBR* 358 | The b:match_wholeBR variable is set to the comma-separated group of patterns 359 | that matches, with |backref|s unparsed. 360 | *b:match_iniBR* 361 | The b:match_iniBR variable is set to the first pattern in |b:match_wholeBR|. 362 | *b:match_ini* 363 | The b:match_ini variable is set to the first pattern in |b:match_wholeBR|, 364 | with |backref|s resolved from |b:match_match|. 365 | *b:match_tail* 366 | The b:match_tail variable is set to the remaining patterns in 367 | |b:match_wholeBR|, with |backref|s resolved from |b:match_match|. 368 | *b:match_word* 369 | The b:match_word variable is set to the pattern from |b:match_wholeBR| that 370 | matches |b:match_match|. 371 | *b:match_table* 372 | The back reference '\'.d refers to the same thing as '\'.b:match_table[d] in 373 | |b:match_word|. 374 | 375 | ============================================================================== 376 | 5. Known Bugs and Limitations *matchit-bugs* 377 | 378 | Repository: https://github.com/chrisbra/matchit/ 379 | Bugs can be reported at the repository (alternatively you can send me a mail). 380 | The latest development snapshot can also be downloaded there. 381 | 382 | Just because I know about a bug does not mean that it is on my todo list. I 383 | try to respond to reports of bugs that cause real problems. If it does not 384 | cause serious problems, or if there is a work-around, a bug may sit there for 385 | a while. Moral: if a bug (known or not) bothers you, let me know. 386 | 387 | It would be nice if "\0" were recognized as the entire pattern. That is, it 388 | would be nice if "foo:\end\0" had the same effect as "\(foo\):\end\1". I may 389 | try to implement this in a future version. (This is not so easy to arrange as 390 | you might think!) 391 | 392 | ============================================================================== 393 | vim:tw=78:ts=8:fo=tcq2:ft=help: 394 | 395 | -------------------------------------------------------------------------------- /autoload/matchit.vim: -------------------------------------------------------------------------------- 1 | " matchit.vim: (global plugin) Extended "%" matching 2 | " autload script of matchit plugin, see ../plugin/matchit.vim 3 | " Last Change: Mar 01, 2020 4 | 5 | let s:last_mps = "" 6 | let s:last_words = ":" 7 | let s:patBR = "" 8 | 9 | let s:save_cpo = &cpo 10 | set cpo&vim 11 | 12 | " Auto-complete mappings: (not yet "ready for prime time") 13 | " TODO Read :help write-plugin for the "right" way to let the user 14 | " specify a key binding. 15 | " let g:match_auto = '' 16 | " let g:match_autoCR = '' 17 | " if exists("g:match_auto") 18 | " execute "inoremap " . g:match_auto . ' x"=Autocomplete()Pls' 19 | " endif 20 | " if exists("g:match_autoCR") 21 | " execute "inoremap " . g:match_autoCR . ' =Autocomplete()' 22 | " endif 23 | " if exists("g:match_gthhoh") 24 | " execute "inoremap " . g:match_gthhoh . ' :call Gthhoh()' 25 | " endif " gthhoh = "Get the heck out of here!" 26 | 27 | let s:notslash = '\\\@1" 49 | elseif a:mode == "o" && mode(1) !~# '[vV]' 50 | exe "norm! v" 51 | elseif a:mode == "n" && mode(1) =~# 'ni' 52 | exe "norm! v" 53 | endif 54 | " In s:CleanUp(), we may need to check whether the cursor moved forward. 55 | let startpos = [line("."), col(".")] 56 | " Use default behavior if called with a count. 57 | if v:count 58 | exe "normal! " . v:count . "%" 59 | return s:CleanUp(restore_options, a:mode, startpos) 60 | end 61 | 62 | " First step: if not already done, set the script variables 63 | " s:do_BR flag for whether there are backrefs 64 | " s:pat parsed version of b:match_words 65 | " s:all regexp based on s:pat and the default groups 66 | if !exists("b:match_words") || b:match_words == "" 67 | let match_words = "" 68 | elseif b:match_words =~ ":" 69 | let match_words = b:match_words 70 | else 71 | " Allow b:match_words = "GetVimMatchWords()" . 72 | execute "let match_words =" b:match_words 73 | endif 74 | " Thanks to Preben "Peppe" Guldberg and Bram Moolenaar for this suggestion! 75 | if (match_words != s:last_words) || (&mps != s:last_mps) 76 | \ || exists("b:match_debug") 77 | let s:last_mps = &mps 78 | " quote the special chars in 'matchpairs', replace [,:] with \| and then 79 | " append the builtin pairs (/*, */, #if, #ifdef, #ifndef, #else, #elif, 80 | " #endif) 81 | let default = escape(&mps, '[$^.*~\\/?]') . (strlen(&mps) ? "," : "") . 82 | \ '\/\*:\*\/,#\s*if\%(n\=def\)\=:#\s*else\>:#\s*elif\>:#\s*endif\>' 83 | " s:all = pattern with all the keywords 84 | let match_words = match_words . (strlen(match_words) ? "," : "") . default 85 | let s:last_words = match_words 86 | if match_words !~ s:notslash . '\\\d' 87 | let s:do_BR = 0 88 | let s:pat = match_words 89 | else 90 | let s:do_BR = 1 91 | let s:pat = s:ParseWords(match_words) 92 | endif 93 | let s:all = substitute(s:pat, s:notslash . '\zs[,:]\+', '\\|', 'g') 94 | " Just in case there are too many '\(...)' groups inside the pattern, make 95 | " sure to use \%(...) groups, so that error E872 can be avoided 96 | let s:all = substitute(s:all, '\\(', '\\%(', 'g') 97 | let s:all = '\%(' . s:all . '\)' 98 | if exists("b:match_debug") 99 | let b:match_pat = s:pat 100 | endif 101 | " Reconstruct the version with unresolved backrefs. 102 | let s:patBR = substitute(match_words.',', 103 | \ s:notslash.'\zs[,:]*,[,:]*', ',', 'g') 104 | let s:patBR = substitute(s:patBR, s:notslash.'\zs:\{2,}', ':', 'g') 105 | endif 106 | 107 | " Second step: set the following local variables: 108 | " matchline = line on which the cursor started 109 | " curcol = number of characters before match 110 | " prefix = regexp for start of line to start of match 111 | " suffix = regexp for end of match to end of line 112 | " Require match to end on or after the cursor and prefer it to 113 | " start on or before the cursor. 114 | let matchline = getline(startpos[0]) 115 | if a:word != '' 116 | " word given 117 | if a:word !~ s:all 118 | echohl WarningMsg|echo 'Missing rule for word:"'.a:word.'"'|echohl NONE 119 | return s:CleanUp(restore_options, a:mode, startpos) 120 | endif 121 | let matchline = a:word 122 | let curcol = 0 123 | let prefix = '^\%(' 124 | let suffix = '\)$' 125 | " Now the case when "word" is not given 126 | else " Find the match that ends on or after the cursor and set curcol. 127 | let regexp = s:Wholematch(matchline, s:all, startpos[1]-1) 128 | let curcol = match(matchline, regexp) 129 | " If there is no match, give up. 130 | if curcol == -1 131 | return s:CleanUp(restore_options, a:mode, startpos) 132 | endif 133 | let endcol = matchend(matchline, regexp) 134 | let suf = strlen(matchline) - endcol 135 | let prefix = (curcol ? '^.*\%' . (curcol + 1) . 'c\%(' : '^\%(') 136 | let suffix = (suf ? '\)\%' . (endcol + 1) . 'c.*$' : '\)$') 137 | endif 138 | if exists("b:match_debug") 139 | let b:match_match = matchstr(matchline, regexp) 140 | let b:match_col = curcol+1 141 | endif 142 | 143 | " Third step: Find the group and single word that match, and the original 144 | " (backref) versions of these. Then, resolve the backrefs. 145 | " Set the following local variable: 146 | " group = colon-separated list of patterns, one of which matches 147 | " = ini:mid:fin or ini:fin 148 | " 149 | " Now, set group and groupBR to the matching group: 'if:endif' or 150 | " 'while:endwhile' or whatever. A bit of a kluge: s:Choose() returns 151 | " group . "," . groupBR, and we pick it apart. 152 | let group = s:Choose(s:pat, matchline, ",", ":", prefix, suffix, s:patBR) 153 | let i = matchend(group, s:notslash . ",") 154 | let groupBR = strpart(group, i) 155 | let group = strpart(group, 0, i-1) 156 | " Now, matchline =~ prefix . substitute(group,':','\|','g') . suffix 157 | if s:do_BR " Do the hard part: resolve those backrefs! 158 | let group = s:InsertRefs(groupBR, prefix, group, suffix, matchline) 159 | endif 160 | if exists("b:match_debug") 161 | let b:match_wholeBR = groupBR 162 | let i = matchend(groupBR, s:notslash . ":") 163 | let b:match_iniBR = strpart(groupBR, 0, i-1) 164 | endif 165 | 166 | " Fourth step: Set the arguments for searchpair(). 167 | let i = matchend(group, s:notslash . ":") 168 | let j = matchend(group, '.*' . s:notslash . ":") 169 | let ini = strpart(group, 0, i-1) 170 | let mid = substitute(strpart(group, i,j-i-1), s:notslash.'\zs:', '\\|', 'g') 171 | let fin = strpart(group, j) 172 | "Un-escape the remaining , and : characters. 173 | let ini = substitute(ini, s:notslash . '\zs\\\(:\|,\)', '\1', 'g') 174 | let mid = substitute(mid, s:notslash . '\zs\\\(:\|,\)', '\1', 'g') 175 | let fin = substitute(fin, s:notslash . '\zs\\\(:\|,\)', '\1', 'g') 176 | " searchpair() requires that these patterns avoid \(\) groups. 177 | let ini = substitute(ini, s:notslash . '\zs\\(', '\\%(', 'g') 178 | let mid = substitute(mid, s:notslash . '\zs\\(', '\\%(', 'g') 179 | let fin = substitute(fin, s:notslash . '\zs\\(', '\\%(', 'g') 180 | " Set mid. This is optimized for readability, not micro-efficiency! 181 | if a:forward && matchline =~ prefix . fin . suffix 182 | \ || !a:forward && matchline =~ prefix . ini . suffix 183 | let mid = "" 184 | endif 185 | " Set flag. This is optimized for readability, not micro-efficiency! 186 | if a:forward && matchline =~ prefix . fin . suffix 187 | \ || !a:forward && matchline !~ prefix . ini . suffix 188 | let flag = "bW" 189 | else 190 | let flag = "W" 191 | endif 192 | " Set skip. 193 | if exists("b:match_skip") 194 | let skip = b:match_skip 195 | elseif exists("b:match_comment") " backwards compatibility and testing! 196 | let skip = "r:" . b:match_comment 197 | else 198 | let skip = 's:comment\|string' 199 | endif 200 | let skip = s:ParseSkip(skip) 201 | if exists("b:match_debug") 202 | let b:match_ini = ini 203 | let b:match_tail = (strlen(mid) ? mid.'\|' : '') . fin 204 | endif 205 | 206 | " Fifth step: actually start moving the cursor and call searchpair(). 207 | " Later, :execute restore_cursor to get to the original screen. 208 | let view = winsaveview() 209 | call cursor(0, curcol + 1) 210 | if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on")) 211 | let skip = "0" 212 | else 213 | execute "if " . skip . "| let skip = '0' | endif" 214 | endif 215 | let sp_return = searchpair(ini, mid, fin, flag, skip) 216 | if &selection isnot# 'inclusive' && a:mode == 'v' 217 | " move cursor one pos to the right, because selection is not inclusive 218 | " add virtualedit=onemore, to make it work even when the match ends the " line 219 | if !(col('.') < col('$')-1) 220 | set ve=onemore 221 | endif 222 | norm! l 223 | endif 224 | let final_position = "call cursor(" . line(".") . "," . col(".") . ")" 225 | " Restore cursor position and original screen. 226 | call winrestview(view) 227 | normal! m' 228 | if sp_return > 0 229 | execute final_position 230 | endif 231 | return s:CleanUp(restore_options, a:mode, startpos, mid.'\|'.fin) 232 | endfun 233 | 234 | " Restore options and do some special handling for Operator-pending mode. 235 | " The optional argument is the tail of the matching group. 236 | fun! s:CleanUp(options, mode, startpos, ...) 237 | if strlen(a:options) 238 | execute "set" a:options 239 | endif 240 | " Open folds, if appropriate. 241 | if a:mode != "o" 242 | if &foldopen =~ "percent" 243 | normal! zv 244 | endif 245 | " In Operator-pending mode, we want to include the whole match 246 | " (for example, d%). 247 | " This is only a problem if we end up moving in the forward direction. 248 | elseif (a:startpos[0] < line(".")) || 249 | \ (a:startpos[0] == line(".") && a:startpos[1] < col(".")) 250 | if a:0 251 | " Check whether the match is a single character. If not, move to the 252 | " end of the match. 253 | let matchline = getline(".") 254 | let currcol = col(".") 255 | let regexp = s:Wholematch(matchline, a:1, currcol-1) 256 | let endcol = matchend(matchline, regexp) 257 | if endcol > currcol " This is NOT off by one! 258 | call cursor(0, endcol) 259 | endif 260 | endif " a:0 261 | endif " a:mode != "o" && etc. 262 | return 0 263 | endfun 264 | 265 | " Example (simplified HTML patterns): if 266 | " a:groupBR = '<\(\k\+\)>:' 267 | " a:prefix = '^.\{3}\(' 268 | " a:group = '<\(\k\+\)>:' 269 | " a:suffix = '\).\{2}$' 270 | " a:matchline = "12312" or "12312" 271 | " then extract "tag" from a:matchline and return ":" . 272 | fun! s:InsertRefs(groupBR, prefix, group, suffix, matchline) 273 | if a:matchline !~ a:prefix . 274 | \ substitute(a:group, s:notslash . '\zs:', '\\|', 'g') . a:suffix 275 | return a:group 276 | endif 277 | let i = matchend(a:groupBR, s:notslash . ':') 278 | let ini = strpart(a:groupBR, 0, i-1) 279 | let tailBR = strpart(a:groupBR, i) 280 | let word = s:Choose(a:group, a:matchline, ":", "", a:prefix, a:suffix, 281 | \ a:groupBR) 282 | let i = matchend(word, s:notslash . ":") 283 | let wordBR = strpart(word, i) 284 | let word = strpart(word, 0, i-1) 285 | " Now, a:matchline =~ a:prefix . word . a:suffix 286 | if wordBR != ini 287 | let table = s:Resolve(ini, wordBR, "table") 288 | else 289 | let table = "" 290 | let d = 0 291 | while d < 10 292 | if tailBR =~ s:notslash . '\\' . d 293 | let table = table . d 294 | else 295 | let table = table . "-" 296 | endif 297 | let d = d + 1 298 | endwhile 299 | endif 300 | let d = 9 301 | while d 302 | if table[d] != "-" 303 | let backref = substitute(a:matchline, a:prefix.word.a:suffix, 304 | \ '\'.table[d], "") 305 | " Are there any other characters that should be escaped? 306 | let backref = escape(backref, '*,:') 307 | execute s:Ref(ini, d, "start", "len") 308 | let ini = strpart(ini, 0, start) . backref . strpart(ini, start+len) 309 | let tailBR = substitute(tailBR, s:notslash . '\zs\\' . d, 310 | \ escape(backref, '\\&'), 'g') 311 | endif 312 | let d = d-1 313 | endwhile 314 | if exists("b:match_debug") 315 | if s:do_BR 316 | let b:match_table = table 317 | let b:match_word = word 318 | else 319 | let b:match_table = "" 320 | let b:match_word = "" 321 | endif 322 | endif 323 | return ini . ":" . tailBR 324 | endfun 325 | 326 | " Input a comma-separated list of groups with backrefs, such as 327 | " a:groups = '\(foo\):end\1,\(bar\):end\1' 328 | " and return a comma-separated list of groups with backrefs replaced: 329 | " return '\(foo\):end\(foo\),\(bar\):end\(bar\)' 330 | fun! s:ParseWords(groups) 331 | let groups = substitute(a:groups.",", s:notslash.'\zs[,:]*,[,:]*', ',', 'g') 332 | let groups = substitute(groups, s:notslash . '\zs:\{2,}', ':', 'g') 333 | let parsed = "" 334 | while groups =~ '[^,:]' 335 | let i = matchend(groups, s:notslash . ':') 336 | let j = matchend(groups, s:notslash . ',') 337 | let ini = strpart(groups, 0, i-1) 338 | let tail = strpart(groups, i, j-i-1) . ":" 339 | let groups = strpart(groups, j) 340 | let parsed = parsed . ini 341 | let i = matchend(tail, s:notslash . ':') 342 | while i != -1 343 | " In 'if:else:endif', ini='if' and word='else' and then word='endif'. 344 | let word = strpart(tail, 0, i-1) 345 | let tail = strpart(tail, i) 346 | let i = matchend(tail, s:notslash . ':') 347 | let parsed = parsed . ":" . s:Resolve(ini, word, "word") 348 | endwhile " Now, tail has been used up. 349 | let parsed = parsed . "," 350 | endwhile " groups =~ '[^,:]' 351 | let parsed = substitute(parsed, ',$', '', '') 352 | return parsed 353 | endfun 354 | 355 | " TODO I think this can be simplified and/or made more efficient. 356 | " TODO What should I do if a:start is out of range? 357 | " Return a regexp that matches all of a:string, such that 358 | " matchstr(a:string, regexp) represents the match for a:pat that starts 359 | " as close to a:start as possible, before being preferred to after, and 360 | " ends after a:start . 361 | " Usage: 362 | " let regexp = s:Wholematch(getline("."), 'foo\|bar', col(".")-1) 363 | " let i = match(getline("."), regexp) 364 | " let j = matchend(getline("."), regexp) 365 | " let match = matchstr(getline("."), regexp) 366 | fun! s:Wholematch(string, pat, start) 367 | let group = '\%(' . a:pat . '\)' 368 | let prefix = (a:start ? '\(^.*\%<' . (a:start + 2) . 'c\)\zs' : '^') 369 | let len = strlen(a:string) 370 | let suffix = (a:start+1 < len ? '\(\%>'.(a:start+1).'c.*$\)\@=' : '$') 371 | if a:string !~ prefix . group . suffix 372 | let prefix = '' 373 | endif 374 | return prefix . group . suffix 375 | endfun 376 | 377 | " No extra arguments: s:Ref(string, d) will 378 | " find the d'th occurrence of '\(' and return it, along with everything up 379 | " to and including the matching '\)'. 380 | " One argument: s:Ref(string, d, "start") returns the index of the start 381 | " of the d'th '\(' and any other argument returns the length of the group. 382 | " Two arguments: s:Ref(string, d, "foo", "bar") returns a string to be 383 | " executed, having the effect of 384 | " :let foo = s:Ref(string, d, "start") 385 | " :let bar = s:Ref(string, d, "len") 386 | fun! s:Ref(string, d, ...) 387 | let len = strlen(a:string) 388 | if a:d == 0 389 | let start = 0 390 | else 391 | let cnt = a:d 392 | let match = a:string 393 | while cnt 394 | let cnt = cnt - 1 395 | let index = matchend(match, s:notslash . '\\(') 396 | if index == -1 397 | return "" 398 | endif 399 | let match = strpart(match, index) 400 | endwhile 401 | let start = len - strlen(match) 402 | if a:0 == 1 && a:1 == "start" 403 | return start - 2 404 | endif 405 | let cnt = 1 406 | while cnt 407 | let index = matchend(match, s:notslash . '\\(\|\\)') - 1 408 | if index == -2 409 | return "" 410 | endif 411 | " Increment if an open, decrement if a ')': 412 | let cnt = cnt + (match[index]=="(" ? 1 : -1) " ')' 413 | let match = strpart(match, index+1) 414 | endwhile 415 | let start = start - 2 416 | let len = len - start - strlen(match) 417 | endif 418 | if a:0 == 1 419 | return len 420 | elseif a:0 == 2 421 | return "let " . a:1 . "=" . start . "| let " . a:2 . "=" . len 422 | else 423 | return strpart(a:string, start, len) 424 | endif 425 | endfun 426 | 427 | " Count the number of disjoint copies of pattern in string. 428 | " If the pattern is a literal string and contains no '0' or '1' characters 429 | " then s:Count(string, pattern, '0', '1') should be faster than 430 | " s:Count(string, pattern). 431 | fun! s:Count(string, pattern, ...) 432 | let pat = escape(a:pattern, '\\') 433 | if a:0 > 1 434 | let foo = substitute(a:string, '[^'.a:pattern.']', "a:1", "g") 435 | let foo = substitute(a:string, pat, a:2, "g") 436 | let foo = substitute(foo, '[^' . a:2 . ']', "", "g") 437 | return strlen(foo) 438 | endif 439 | let result = 0 440 | let foo = a:string 441 | let index = matchend(foo, pat) 442 | while index != -1 443 | let result = result + 1 444 | let foo = strpart(foo, index) 445 | let index = matchend(foo, pat) 446 | endwhile 447 | return result 448 | endfun 449 | 450 | " s:Resolve('\(a\)\(b\)', '\(c\)\2\1\1\2') should return table.word, where 451 | " word = '\(c\)\(b\)\(a\)\3\2' and table = '-32-------'. That is, the first 452 | " '\1' in target is replaced by '\(a\)' in word, table[1] = 3, and this 453 | " indicates that all other instances of '\1' in target are to be replaced 454 | " by '\3'. The hard part is dealing with nesting... 455 | " Note that ":" is an illegal character for source and target, 456 | " unless it is preceded by "\". 457 | fun! s:Resolve(source, target, output) 458 | let word = a:target 459 | let i = matchend(word, s:notslash . '\\\d') - 1 460 | let table = "----------" 461 | while i != -2 " There are back references to be replaced. 462 | let d = word[i] 463 | let backref = s:Ref(a:source, d) 464 | " The idea is to replace '\d' with backref. Before we do this, 465 | " replace any \(\) groups in backref with :1, :2, ... if they 466 | " correspond to the first, second, ... group already inserted 467 | " into backref. Later, replace :1 with \1 and so on. The group 468 | " number w+b within backref corresponds to the group number 469 | " s within a:source. 470 | " w = number of '\(' in word before the current one 471 | let w = s:Count( 472 | \ substitute(strpart(word, 0, i-1), '\\\\', '', 'g'), '\(', '1') 473 | let b = 1 " number of the current '\(' in backref 474 | let s = d " number of the current '\(' in a:source 475 | while b <= s:Count(substitute(backref, '\\\\', '', 'g'), '\(', '1') 476 | \ && s < 10 477 | if table[s] == "-" 478 | if w + b < 10 479 | " let table[s] = w + b 480 | let table = strpart(table, 0, s) . (w+b) . strpart(table, s+1) 481 | endif 482 | let b = b + 1 483 | let s = s + 1 484 | else 485 | execute s:Ref(backref, b, "start", "len") 486 | let ref = strpart(backref, start, len) 487 | let backref = strpart(backref, 0, start) . ":". table[s] 488 | \ . strpart(backref, start+len) 489 | let s = s + s:Count(substitute(ref, '\\\\', '', 'g'), '\(', '1') 490 | endif 491 | endwhile 492 | let word = strpart(word, 0, i-1) . backref . strpart(word, i+1) 493 | let i = matchend(word, s:notslash . '\\\d') - 1 494 | endwhile 495 | let word = substitute(word, s:notslash . '\zs:', '\\', 'g') 496 | if a:output == "table" 497 | return table 498 | elseif a:output == "word" 499 | return word 500 | else 501 | return table . word 502 | endif 503 | endfun 504 | 505 | " Assume a:comma = ",". Then the format for a:patterns and a:1 is 506 | " a:patterns = ",,..." 507 | " a:1 = ",,..." 508 | " If is the first pattern that matches a:string then return 509 | " if no optional arguments are given; return , if a:1 is given. 510 | fun! s:Choose(patterns, string, comma, branch, prefix, suffix, ...) 511 | let tail = (a:patterns =~ a:comma."$" ? a:patterns : a:patterns . a:comma) 512 | let i = matchend(tail, s:notslash . a:comma) 513 | if a:0 514 | let alttail = (a:1 =~ a:comma."$" ? a:1 : a:1 . a:comma) 515 | let j = matchend(alttail, s:notslash . a:comma) 516 | endif 517 | let current = strpart(tail, 0, i-1) 518 | if a:branch == "" 519 | let currpat = current 520 | else 521 | let currpat = substitute(current, s:notslash . a:branch, '\\|', 'g') 522 | endif 523 | while a:string !~ a:prefix . currpat . a:suffix 524 | let tail = strpart(tail, i) 525 | let i = matchend(tail, s:notslash . a:comma) 526 | if i == -1 527 | return -1 528 | endif 529 | let current = strpart(tail, 0, i-1) 530 | if a:branch == "" 531 | let currpat = current 532 | else 533 | let currpat = substitute(current, s:notslash . a:branch, '\\|', 'g') 534 | endif 535 | if a:0 536 | let alttail = strpart(alttail, j) 537 | let j = matchend(alttail, s:notslash . a:comma) 538 | endif 539 | endwhile 540 | if a:0 541 | let current = current . a:comma . strpart(alttail, 0, j-1) 542 | endif 543 | return current 544 | endfun 545 | 546 | fun! matchit#Match_debug() 547 | let b:match_debug = 1 " Save debugging information. 548 | " pat = all of b:match_words with backrefs parsed 549 | amenu &Matchit.&pat :echo b:match_pat 550 | " match = bit of text that is recognized as a match 551 | amenu &Matchit.&match :echo b:match_match 552 | " curcol = cursor column of the start of the matching text 553 | amenu &Matchit.&curcol :echo b:match_col 554 | " wholeBR = matching group, original version 555 | amenu &Matchit.wh&oleBR :echo b:match_wholeBR 556 | " iniBR = 'if' piece, original version 557 | amenu &Matchit.ini&BR :echo b:match_iniBR 558 | " ini = 'if' piece, with all backrefs resolved from match 559 | amenu &Matchit.&ini :echo b:match_ini 560 | " tail = 'else\|endif' piece, with all backrefs resolved from match 561 | amenu &Matchit.&tail :echo b:match_tail 562 | " fin = 'endif' piece, with all backrefs resolved from match 563 | amenu &Matchit.&word :echo b:match_word 564 | " '\'.d in ini refers to the same thing as '\'.table[d] in word. 565 | amenu &Matchit.t&able :echo '0:' . b:match_table . ':9' 566 | endfun 567 | 568 | " Jump to the nearest unmatched "(" or "if" or "" if a:spflag == "bW" 569 | " or the nearest unmatched "" or "endif" or ")" if a:spflag == "W". 570 | " Return a "mark" for the original position, so that 571 | " let m = MultiMatch("bW", "n") ... call winrestview(m) 572 | " will return to the original position. If there is a problem, do not 573 | " move the cursor and return {}, unless a count is given, in which case 574 | " go up or down as many levels as possible and again return {}. 575 | " TODO This relies on the same patterns as % matching. It might be a good 576 | " idea to give it its own matching patterns. 577 | fun! matchit#MultiMatch(spflag, mode) 578 | let restore_options = s:RestoreOptions() 579 | let startpos = [line("."), col(".")] 580 | " save v:count1 variable, might be reset from the restore_cursor command 581 | let level = v:count1 582 | if a:mode == "o" && mode(1) !~# '[vV]' 583 | exe "norm! v" 584 | endif 585 | 586 | " First step: if not already done, set the script variables 587 | " s:do_BR flag for whether there are backrefs 588 | " s:pat parsed version of b:match_words 589 | " s:all regexp based on s:pat and the default groups 590 | " This part is copied and slightly modified from matchit#Match_wrapper(). 591 | if !exists("b:match_words") || b:match_words == "" 592 | let match_words = "" 593 | " Allow b:match_words = "GetVimMatchWords()" . 594 | elseif b:match_words =~ ":" 595 | let match_words = b:match_words 596 | else 597 | execute "let match_words =" b:match_words 598 | endif 599 | if (match_words != s:last_words) || (&mps != s:last_mps) || 600 | \ exists("b:match_debug") 601 | let default = escape(&mps, '[$^.*~\\/?]') . (strlen(&mps) ? "," : "") . 602 | \ '\/\*:\*\/,#\s*if\%(n\=def\)\=:#\s*else\>:#\s*elif\>:#\s*endif\>' 603 | let s:last_mps = &mps 604 | let match_words = match_words . (strlen(match_words) ? "," : "") . default 605 | let s:last_words = match_words 606 | if match_words !~ s:notslash . '\\\d' 607 | let s:do_BR = 0 608 | let s:pat = match_words 609 | else 610 | let s:do_BR = 1 611 | let s:pat = s:ParseWords(match_words) 612 | endif 613 | let s:all = '\%(' . substitute(s:pat, '[,:]\+', '\\|', 'g') . '\)' 614 | if exists("b:match_debug") 615 | let b:match_pat = s:pat 616 | endif 617 | " Reconstruct the version with unresolved backrefs. 618 | let s:patBR = substitute(match_words.',', 619 | \ s:notslash.'\zs[,:]*,[,:]*', ',', 'g') 620 | let s:patBR = substitute(s:patBR, s:notslash.'\zs:\{2,}', ':', 'g') 621 | endif 622 | 623 | " Second step: figure out the patterns for searchpair() 624 | " and save the screen, cursor position, and 'ignorecase'. 625 | " - TODO: A lot of this is copied from matchit#Match_wrapper(). 626 | " - maybe even more functionality should be split off 627 | " - into separate functions! 628 | let openlist = split(s:pat . ',', s:notslash . '\zs:.\{-}' . s:notslash . ',') 629 | let midclolist = split(',' . s:pat, s:notslash . '\zs,.\{-}' . s:notslash . ':') 630 | call map(midclolist, {-> split(v:val, s:notslash . ':')}) 631 | let closelist = [] 632 | let middlelist = [] 633 | call map(midclolist, {i,v -> [extend(closelist, v[-1 : -1]), 634 | \ extend(middlelist, v[0 : -2])]}) 635 | call map(openlist, {i,v -> v =~# s:notslash . '\\|' ? '\%(' . v . '\)' : v}) 636 | call map(middlelist, {i,v -> v =~# s:notslash . '\\|' ? '\%(' . v . '\)' : v}) 637 | call map(closelist, {i,v -> v =~# s:notslash . '\\|' ? '\%(' . v . '\)' : v}) 638 | let open = join(openlist, ',') 639 | let middle = join(middlelist, ',') 640 | let close = join(closelist, ',') 641 | if exists("b:match_skip") 642 | let skip = b:match_skip 643 | elseif exists("b:match_comment") " backwards compatibility and testing! 644 | let skip = "r:" . b:match_comment 645 | else 646 | let skip = 's:comment\|string' 647 | endif 648 | let skip = s:ParseSkip(skip) 649 | let view = winsaveview() 650 | 651 | " Third step: call searchpair(). 652 | " Replace '\('--but not '\\('--with '\%(' and ',' with '\|'. 653 | let openpat = substitute(open, '\%(' . s:notslash . '\)\@<=\\(', '\\%(', 'g') 654 | let openpat = substitute(openpat, ',', '\\|', 'g') 655 | let closepat = substitute(close, '\%(' . s:notslash . '\)\@<=\\(', '\\%(', 'g') 656 | let closepat = substitute(closepat, ',', '\\|', 'g') 657 | let middlepat = substitute(middle, '\%(' . s:notslash . '\)\@<=\\(', '\\%(', 'g') 658 | let middlepat = substitute(middlepat, ',', '\\|', 'g') 659 | 660 | if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on")) 661 | let skip = '0' 662 | else 663 | try 664 | execute "if " . skip . "| let skip = '0' | endif" 665 | catch /^Vim\%((\a\+)\)\=:E363/ 666 | " We won't find anything, so skip searching, should keep Vim responsive. 667 | return {} 668 | endtry 669 | endif 670 | mark ' 671 | while level 672 | if searchpair(openpat, middlepat, closepat, a:spflag, skip) < 1 673 | call s:CleanUp(restore_options, a:mode, startpos) 674 | return {} 675 | endif 676 | let level = level - 1 677 | endwhile 678 | 679 | " Restore options and return a string to restore the original position. 680 | call s:CleanUp(restore_options, a:mode, startpos) 681 | return view 682 | endfun 683 | 684 | " Search backwards for "if" or "while" or "" or ... 685 | " and return "endif" or "endwhile" or "" or ... . 686 | " For now, this uses b:match_words and the same script variables 687 | " as matchit#Match_wrapper() . Later, it may get its own patterns, 688 | " either from a buffer variable or passed as arguments. 689 | " fun! s:Autocomplete() 690 | " echo "autocomplete not yet implemented :-(" 691 | " if !exists("b:match_words") || b:match_words == "" 692 | " return "" 693 | " end 694 | " let startpos = matchit#MultiMatch("bW") 695 | " 696 | " if startpos == "" 697 | " return "" 698 | " endif 699 | " " - TODO: figure out whether 'if' or '' matched, and construct 700 | " " - the appropriate closing. 701 | " let matchline = getline(".") 702 | " let curcol = col(".") - 1 703 | " " - TODO: Change the s:all argument if there is a new set of match pats. 704 | " let regexp = s:Wholematch(matchline, s:all, curcol) 705 | " let suf = strlen(matchline) - matchend(matchline, regexp) 706 | " let prefix = (curcol ? '^.\{' . curcol . '}\%(' : '^\%(') 707 | " let suffix = (suf ? '\).\{' . suf . '}$' : '\)$') 708 | " " Reconstruct the version with unresolved backrefs. 709 | " let patBR = substitute(b:match_words.',', '[,:]*,[,:]*', ',', 'g') 710 | " let patBR = substitute(patBR, ':\{2,}', ':', "g") 711 | " " Now, set group and groupBR to the matching group: 'if:endif' or 712 | " " 'while:endwhile' or whatever. 713 | " let group = s:Choose(s:pat, matchline, ",", ":", prefix, suffix, patBR) 714 | " let i = matchend(group, s:notslash . ",") 715 | " let groupBR = strpart(group, i) 716 | " let group = strpart(group, 0, i-1) 717 | " " Now, matchline =~ prefix . substitute(group,':','\|','g') . suffix 718 | " if s:do_BR 719 | " let group = s:InsertRefs(groupBR, prefix, group, suffix, matchline) 720 | " endif 721 | " " let g:group = group 722 | " 723 | " " - TODO: Construct the closing from group. 724 | " let fake = "end" . expand("") 725 | " execute startpos 726 | " return fake 727 | " endfun 728 | 729 | " Close all open structures. "Get the heck out of here!" 730 | " fun! s:Gthhoh() 731 | " let close = s:Autocomplete() 732 | " while strlen(close) 733 | " put=close 734 | " let close = s:Autocomplete() 735 | " endwhile 736 | " endfun 737 | 738 | " Parse special strings as typical skip arguments for searchpair(): 739 | " s:foo becomes (current syntax item) =~ foo 740 | " S:foo becomes (current syntax item) !~ foo 741 | " r:foo becomes (line before cursor) =~ foo 742 | " R:foo becomes (line before cursor) !~ foo 743 | fun! s:ParseSkip(str) 744 | let skip = a:str 745 | if skip[1] == ":" 746 | if skip[0] == "s" 747 | let skip = "synIDattr(synID(line('.'),col('.'),1),'name') =~? '" . 748 | \ strpart(skip,2) . "'" 749 | elseif skip[0] == "S" 750 | let skip = "synIDattr(synID(line('.'),col('.'),1),'name') !~? '" . 751 | \ strpart(skip,2) . "'" 752 | elseif skip[0] == "r" 753 | let skip = "strpart(getline('.'),0,col('.'))=~'" . strpart(skip,2). "'" 754 | elseif skip[0] == "R" 755 | let skip = "strpart(getline('.'),0,col('.'))!~'" . strpart(skip,2). "'" 756 | endif 757 | endif 758 | return skip 759 | endfun 760 | 761 | let &cpo = s:save_cpo 762 | unlet s:save_cpo 763 | 764 | " vim:sts=2:sw=2:et: 765 | 766 | --------------------------------------------------------------------------------