]> ruderich.org/simon Gitweb - config/dotfiles.git/commitdiff
matchit: Add plugin, version 1.13.2.
authorSimon Ruderich <simon@ruderich.org>
Sun, 26 Apr 2009 12:26:48 +0000 (14:26 +0200)
committerSimon Ruderich <simon@ruderich.org>
Sun, 26 Apr 2009 12:54:16 +0000 (14:54 +0200)
doc/matchit.txt [new file with mode: 0644]
plugin/matchit.vim [new file with mode: 0755]

diff --git a/doc/matchit.txt b/doc/matchit.txt
new file mode 100644 (file)
index 0000000..8a3a96e
--- /dev/null
@@ -0,0 +1,406 @@
+*matchit.txt*   Extended "%" matching
+
+For instructions on installing this file, type
+       :help matchit-install
+inside Vim.
+
+For Vim version 6.3.  Last change:  2007 Aug 29
+
+
+                 VIM REFERENCE MANUAL    by Benji Fisher
+
+*matchit* *matchit.vim*
+
+1. Extended matching with "%"                          |matchit-intro|
+2. Activation                                          |matchit-activate|
+3. Configuration                                       |matchit-configure|
+4. Supporting a New Language                           |matchit-newlang|
+5. Known Bugs and Limitations                          |matchit-bugs|
+
+The functionality mentioned here is a plugin, see |add-plugin|.
+This plugin is only available if 'compatible' is not set.
+You can avoid loading this plugin by setting the "loaded_matchit" variable
+in your |vimrc| file: >
+       :let loaded_matchit = 1
+
+{Vi does not have any of this}
+
+==============================================================================
+1. Extended matching with "%"                          *matchit-intro*
+
+                                                       *matchit-%*
+%      Cycle forward through matching groups, such as "if", "else", "endif",
+       as specified by |b:match_words|.
+
+                                                       *g%* *v_g%* *o_g%*
+g%     Cycle backwards through matching groups, as specified by
+       |b:match_words|.  For example, go from "if" to "endif" to "else".
+
+                                                       *[%* *v_[%* *o_[%*
+[%     Go to [count] previous unmatched group, as specified by
+       |b:match_words|.  Similar to |[{|.
+
+                                                       *]%* *v_]%* *o_]%*
+]%     Go to [count] next unmatched group, as specified by
+       |b:match_words|.  Similar to |]}|.
+
+                                                       *v_a%*
+a%     In Visual mode, select the matching group, as specified by
+       |b:match_words|, containing the cursor.  Similar to |v_a[|.
+       A [count] is ignored, and only the first character of the closing
+       pattern is selected.
+
+In Vim, as in plain vi, the percent key, |%|, jumps the cursor from a brace,
+bracket, or paren to its match.  This can be configured with the 'matchpairs'
+option.  The matchit plugin extends this in several ways:
+
+           You can match whole words, such as "if" and "endif", not just
+       single characters.  You can also specify a |regular-expression|.
+           You can define groups with more than two words, such as "if",
+       "else", "endif".  Banging on the "%" key will cycle from the "if" to
+       the first "else", the next "else", ..., the closing "endif", and back
+       to the opening "if".  Nested structures are skipped.  Using |g%| goes
+       in the reverse direction.
+           By default, words inside comments and strings are ignored, unless
+       the cursor is inside a comment or string when you type "%".  If the
+       only thing you want to do is modify the behavior of "%" so that it
+       behaves this way, you do not have to define |b:match_words|, since the
+       script uses the 'matchpairs' option as well as this variable.
+
+See |matchit-details| for details on what the script does, and |b:match_words|
+for how to specify matching patterns.
+
+MODES:                 *matchit-modes* *matchit-v_%* *matchit-o_%*
+
+Mostly, % and related motions (|g%| and |[%| and |]%|) work just like built-in
+|motion| commands in |Operator-pending| and |Visual| modes.  However, you
+cannot make these motions |linewise| or |characterwise|, since the |:omap|s
+that define them start with "v" in order to make the default behavior
+inclusive.  (See |o_v|.)  In other words, "dV%" will not work.  The
+work-around is to go through Visual mode:  "V%d" will work.
+
+LANGUAGES:                                     *matchit-languages*
+
+Currently, the following languages are supported:  Ada, ASP with VBS, Csh,
+DTD, Entity, Essbase, Fortran, HTML, JSP (same as HTML), LaTeX, Lua, Pascal,
+SGML, Shell, Tcsh, Vim, XML.  Other languages may already have support via
+the default |filetype-plugin|s in the standard vim distribution.
+
+To support a new language, see |matchit-newlang| below.
+
+DETAILS:                               *matchit-details* *matchit-parse*
+
+Here is an outline of what matchit.vim does each time you hit the "%" key.  If
+there are |backref|s in |b:match_words| then the first step is to produce a
+version in which these back references have been eliminated; if there are no
+|backref|s then this step is skipped.  This step is called parsing.  For
+example, "\(foo\|bar\):end\1" is parsed to yield
+"\(foo\|bar\):end\(foo\|bar\)".  This can get tricky, especially if there are
+nested groups.  If debugging is turned on, the parsed version is saved as
+|b:match_pat|.
+
+                                                       *matchit-choose*
+Next, the script looks for a word on the current line that matches the pattern
+just constructed.  It includes the patterns from the 'matchpairs' option.
+The goal is to do what you expect, which turns out to be a little complicated.
+The script follows these rules:
+
+       Insist on a match that ends on or after the cursor.
+       Prefer a match that includes the cursor position (that is, one that
+               starts on or before the cursor).
+       Prefer a match that starts as close to the cursor as possible.
+       If more than one pattern in |b:match_words| matches, choose the one
+               that is listed first.
+
+Examples:
+
+       Suppose you >
+               :let b:match_words = '<:>,<tag>:</tag>'
+<      and hit "%" with the cursor on or before the "<" in "a <tag> is born".
+       The pattern '<' comes first, so it is preferred over '<tag>', which
+       also matches.  If the cursor is on the "t", however, then '<tag>' is
+       preferred, because this matches a bit of text containing the cursor.
+       If the two groups of patterns were reversed then '<' would never be
+       preferred.
+
+       Suppose you >
+               :let b:match_words = 'if:end if'
+<      (Note the space!) and hit "%" with the cursor at the end of "end if".
+       Then "if" matches, which is probably not what you want, but if the
+       cursor starts on the "end " then "end if" is chosen.  (You can avoid
+       this problem by using a more complicated pattern.)
+
+If there is no match, the cursor does not move.  (Before version 1.13 of the
+script, it would fall back on the usual behavior of |%|).  If debugging is
+turned on, the matched bit of text is saved as |b:match_match| and the cursor
+column of the start of the match is saved as |b:match_col|.
+
+Next, the script looks through |b:match_words| (original and parsed versions)
+for the group and pattern that match.  If debugging is turned on, the group is
+saved as |b:match_ini| (the first pattern) and |b:match_tail| (the rest).  If
+there are |backref|s then, in addition, the matching pattern is saved as
+|b:match_word| and a table of translations is saved as |b:match_table|.  If
+there are |backref|s, these are determined from the matching pattern and
+|b:match_match| and substituted into each pattern in the matching group.
+
+The script decides whether to search forwards or backwards and chooses
+arguments for the |searchpair()| function.  Then, the cursor is moved to the
+start of the match, and |searchpair()| is called.  By default, matching
+structures inside strings and comments are ignored.  This can be changed by
+setting |b:match_skip|.
+
+==============================================================================
+2. Activation                                          *matchit-activate*
+
+You can use this script as a plugin, by copying it to your plugin directory.
+See |add-global-plugin| for instructions.  You can also add a line to your
+|vimrc| file, such as >
+       :source $VIMRUNTIME/macros/matchit.vim
+or >
+       :runtime macros/matchit.vim
+Either way, the script should start working the next time you start up Vim.
+
+(Earlier versions of the script did nothing unless a |buffer-variable| named
+|b:match_words| was defined.  Even earlier versions contained autocommands
+that set this variable for various file types.  Now, |b:match_words| is
+defined in many of the default |filetype-plugin|s instead.)
+
+For a new language, you can add autocommands to the script or to your vimrc
+file, but the recommended method is to add a line such as >
+       let b:match_words = '\<foo\>:\<bar\>'
+to the |filetype-plugin| for your language.  See |b:match_words| below for how
+this variable is interpreted.
+
+TROUBLESHOOTING                                        *matchit-troubleshoot*
+
+The script should work in most installations of Vim.  It may not work if Vim
+was compiled with a minimal feature set, for example if the |+syntax| option
+was not enabled.  If your Vim has support for syntax compiled in, but you do
+not have |syntax| highlighting turned on, matchit.vim should work, but it may
+fail to skip matching groups in comments and strings.  If the |filetype|
+mechanism is turned off, the |b:match_words| variable will probably not be
+defined automatically.
+
+==============================================================================
+3. Configuration                                       *matchit-configure*
+
+There are several variables that govern the behavior of matchit.vim.  Note
+that these are variables local to the buffer, not options, so use |:let| to
+define them, not |:set|.  Some of these variables have values that matter; for
+others, it only matters whether the variable has been defined.  All of these
+can be defined in the |filetype-plugin| or autocommand that defines
+|b:match_words| or "on the fly."
+
+The main variable is |b:match_words|.  It is described in the section below on
+supporting a new language.
+
+                               *MatchError* *matchit-hl* *matchit-highlight*
+MatchError is the highlight group for error messages from the script.  By
+default, it is linked to WarningMsg.  If you do not want to be bothered by
+error messages, you can define this to be something invisible.  For example,
+if you use the GUI version of Vim and your command line is normally white, you
+can do >
+       :hi MatchError guifg=white guibg=white
+<
+                                               *b:match_ignorecase*
+If you >
+       :let b:match_ignorecase = 1
+then matchit.vim acts as if 'ignorecase' is set: for example, "end" and "END"
+are equivalent.  If you >
+       :let b:match_ignorecase = 0
+then matchit.vim treats "end" and "END" differently.  (There will be no
+b:match_infercase option unless someone requests it.)
+
+                                               *b:match_debug*
+Define b:match_debug if you want debugging information to be saved.  See
+|matchit-debug|, below.
+
+                                               *b:match_skip*
+If b:match_skip is defined, it is passed as the skip argument to
+|searchpair()|.  This controls when matching structures are skipped, or
+ignored.  By default, they are ignored inside comments and strings, as
+determined by the |syntax| mechanism.  (If syntax highlighting is turned off,
+nothing is skipped.)  You can set b:match_skip to a string, which evaluates to
+a non-zero, numerical value if the match is to be skipped or zero if the match
+should not be skipped.  In addition, the following special values are
+supported by matchit.vim:
+       s:foo becomes (current syntax item) =~ foo
+       S:foo becomes (current syntax item) !~ foo
+       r:foo becomes (line before cursor) =~ foo
+       R:foo becomes (line before cursor) !~ foo
+(The "s" is meant to suggest "syntax", and the "r" is meant to suggest
+"regular expression".)
+
+Examples:
+
+       You can get the default behavior with >
+               :let b:match_skip = 's:comment\|string'
+<
+       If you want to skip matching structures unless they are at the start
+       of the line (ignoring whitespace) then you can >
+               :let b:match_skip = 'R:^\s*'
+<      Do not do this if strings or comments can span several lines, since
+       the normal syntax checking will not be done if you set b:match_skip.
+
+       In LaTeX, since "%" is used as the comment character, you can >
+               :let b:match_skip = 'r:%'
+<      Unfortunately, this will skip anything after "\%", an escaped "%".  To
+       allow for this, and also "\\%" (an excaped backslash followed by the
+       comment character) you can >
+               :let b:match_skip = 'r:\(^\|[^\\]\)\(\\\\\)*%'
+<
+       See the $VIMRUNTIME/ftplugin/vim.vim for an example that uses both
+       syntax and a regular expression.
+
+==============================================================================
+4. Supporting a New Language                           *matchit-newlang*
+                                                       *b:match_words*
+In order for matchit.vim to support a new language, you must define a suitable
+pattern for |b:match_words|.  You may also want to set some of the
+|matchit-configure| variables, as described above.  If your language has a
+complicated syntax, or many keywords, you will need to know something about
+Vim's |regular-expression|s.
+
+The format for |b:match_words| is similar to that of the 'matchpairs' option:
+it is a comma (,)-separated list of groups; each group is a colon(:)-separated
+list of patterns (regular expressions).  Commas and backslashes that are part
+of a pattern should be escaped with backslashes ('\:' and '\,').  It is OK to
+have only one group; the effect is undefined if a group has only one pattern.
+A simple example is >
+       :let b:match_words = '\<if\>:\<endif\>,'
+               \ . '\<while\>:\<continue\>:\<break\>:\<endwhile\>'
+(In Vim regular expressions, |\<| and |\>| denote word boundaries.  Thus "if"
+matches the end of "endif" but "\<if\>" does not.)  Then banging on the "%"
+key will bounce the cursor between "if" and the matching "endif"; and from
+"while" to any matching "continue" or "break", then to the matching "endwhile"
+and back to the "while".  It is almost always easier to use |literal-string|s
+(single quotes) as above:  '\<if\>' rather than "\\<if\\>" and so on.
+
+Exception:  If the ":" character does not appear in b:match_words, then it is
+treated as an expression to be evaluated.  For example, >
+       :let b:match_words = 'GetMatchWords()'
+allows you to define a function.  This can return a different string depending
+on the current syntax, for example.
+
+Once you have defined the appropriate value of |b:match_words|, you will
+probably want to have this set automatically each time you edit the
+appropriate file type.  The recommended way to do this is by adding the
+definition to a |filetype-plugin| file.
+
+Tips: Be careful that your initial pattern does not match your final pattern.
+See the example above for the use of word-boundary expressions.  It is usually
+better to use ".\{-}" (as many as necessary) instead of ".*" (as many as
+possible).  See |\{-|.  For example, in the string "<tag>label</tag>", "<.*>"
+matches the whole string whereas "<.\{-}>" and "<[^>]*>" match "<tag>" and
+"</tag>".
+
+                               *matchit-spaces* *matchit-s:notend*
+If "if" is to be paired with "end if" (Note the space!) then word boundaries
+are not enough.  Instead, define a regular expression s:notend that will match
+anything but "end" and use it as follows: >
+       :let s:notend = '\%(\<end\s\+\)\@<!'
+       :let b:match_words = s:notend . '\<if\>:\<end\s\+if\>'
+<                                                      *matchit-s:sol*
+This is a simplified version of what is done for Ada.  The s:notend is a
+|script-variable|.  Similarly, you may want to define a start-of-line regular
+expression >
+       :let s:sol = '\%(^\|;\)\s*'
+if keywords are only recognized after the start of a line or after a
+semicolon (;), with optional white space.
+
+                                       *matchit-backref* *matchit-\1*
+In any group, the expressions |\1|, |\2|, ..., |\9| refer to parts of the
+INITIAL pattern enclosed in |\(|escaped parentheses|\)|.  These are referred
+to as back references, or backrefs.  For example, >
+       :let b:match_words = '\<b\(o\+\)\>:\(h\)\1\>'
+means that "bo" pairs with "ho" and "boo" pairs with "hoo" and so on.  Note
+that "\1" does not refer to the "\(h\)" in this example.  If you have
+"\(nested \(parentheses\)\) then "\d" refers to the d-th "\(" and everything
+up to and including the matching "\)":  in "\(nested\(parentheses\)\)", "\1"
+refers to everything and "\2" refers to "\(parentheses\)".  If you use a
+variable such as |s:notend| or |s:sol| in the previous paragraph then remember
+to count any "\(" patterns in this variable.  You do not have to count groups
+defined by |\%(\)|.
+
+It should be possible to resolve back references from any pattern in the
+group.  For example, >
+       :let b:match_words = '\(foo\)\(bar\):more\1:and\2:end\1\2'
+would not work because "\2" cannot be determined from "morefoo" and "\1"
+cannot be determined from "andbar".  On the other hand, >
+       :let b:match_words = '\(\(foo\)\(bar\)\):\3\2:end\1'
+should work (and have the same effect as "foobar:barfoo:endfoobar"), although
+this has not been thoroughly tested.
+
+You can use |zero-width| patterns such as |\@<=| and |\zs|.  (The latter has
+not been thouroughly tested in matchit.vim.)  For example, if the keyword "if"
+must occur at the start of the line, with optional white space, you might use
+the pattern "\(^\s*\)\@<=if" so that the cursor will end on the "i" instead of
+at the start of the line.  For another example, if HTML had only one tag then
+one could >
+       :let b:match_words = '<:>,<\@<=tag>:<\@<=/tag>'
+so that "%" can bounce between matching "<" and ">" pairs or (starting on
+"tag" or "/tag") between matching tags.  Without the |\@<=|, the script would
+bounce from "tag" to the "<" in "</tag>", and another "%" would not take you
+back to where you started.
+
+DEBUGGING                              *matchit-debug* *:MatchDebug*
+
+If you are having trouble figuring out the appropriate definition of
+|b:match_words| then you can take advantage of the same information I use when
+debugging the script.  This is especially true if you are not sure whether
+your patterns or my script are at fault!  To make this more convenient, I have
+made the command :MatchDebug, which defines the variable |b:match_debug| and
+creates a Matchit menu.  This menu makes it convenient to check the values of
+the variables described below.  You will probably also want to read
+|matchit-details| above.
+
+Defining the variable |b:match_debug| causes the script to set the following
+variables, each time you hit the "%" key.  Several of these are only defined
+if |b:match_words| includes |backref|s.
+
+                                                       *b:match_pat*
+The b:match_pat variable is set to |b:match_words| with |backref|s parsed.
+                                                       *b:match_match*
+The b:match_match variable is set to the bit of text that is recognized as a
+match.
+                                                       *b:match_col*
+The b:match_col variable is set to the cursor column of the start of the
+matching text.
+                                                       *b:match_wholeBR*
+The b:match_wholeBR variable is set to the comma-separated group of patterns
+that matches, with |backref|s unparsed.
+                                                       *b:match_iniBR*
+The b:match_iniBR variable is set to the first pattern in |b:match_wholeBR|.
+                                                       *b:match_ini*
+The b:match_ini variable is set to the first pattern in |b:match_wholeBR|,
+with |backref|s resolved from |b:match_match|.
+                                                       *b:match_tail*
+The b:match_tail variable is set to the remaining patterns in
+|b:match_wholeBR|, with |backref|s resolved from |b:match_match|.
+                                                       *b:match_word*
+The b:match_word variable is set to the pattern from |b:match_wholeBR| that
+matches |b:match_match|.
+                                                       *b:match_table*
+The back reference '\'.d refers to the same thing as '\'.b:match_table[d] in
+|b:match_word|.
+
+==============================================================================
+5. Known Bugs and Limitations                          *matchit-bugs*
+
+Just because I know about a bug does not mean that it is on my todo list.  I
+try to respond to reports of bugs that cause real problems.  If it does not
+cause serious problems, or if there is a work-around, a bug may sit there for
+a while.  Moral:  if a bug (known or not) bothers you, let me know.
+
+The various |:vmap|s defined in the script (%, |g%|, |[%|, |]%|, |a%|) may
+have undesired effects in Select mode |Select-mode-mapping|.  At least, if you
+want to replace the selection with any character in "ag%[]" there will be a
+pause of |'updatetime'| first.
+
+It would be nice if "\0" were recognized as the entire pattern.  That is, it
+would be nice if "foo:\end\0" had the same effect as "\(foo\):\end\1".  I may
+try to implement this in a future version.  (This is not so easy to arrange as
+you might think!)
+
+==============================================================================
+vim:tw=78:fo=tcq2:
diff --git a/plugin/matchit.vim b/plugin/matchit.vim
new file mode 100755 (executable)
index 0000000..e41cda9
--- /dev/null
@@ -0,0 +1,812 @@
+"  matchit.vim: (global plugin) Extended "%" matching
+"  Last Change: Fri Jan 25 10:00 AM 2008 EST
+"  Maintainer:  Benji Fisher PhD   <benji@member.AMS.org>
+"  Version:     1.13.2, for Vim 6.3+
+"  URL:                http://www.vim.org/script.php?script_id=39
+
+" Documentation:
+"  The documentation is in a separate file, matchit.txt .
+
+" Credits:
+"  Vim editor by Bram Moolenaar (Thanks, Bram!)
+"  Original script and design by Raul Segura Acevedo
+"  Support for comments by Douglas Potts
+"  Support for back references and other improvements by Benji Fisher
+"  Support for many languages by Johannes Zellner
+"  Suggestions for improvement, bug reports, and support for additional
+"  languages by Jordi-Albert Batalla, Neil Bird, Servatius Brandt, Mark
+"  Collett, Stephen Wall, Dany St-Amant, Yuheng Xie, and Johannes Zellner.
+
+" Debugging:
+"  If you'd like to try the built-in debugging commands...
+"   :MatchDebug      to activate debugging for the current buffer
+"  This saves the values of several key script variables as buffer-local
+"  variables.  See the MatchDebug() function, below, for details.
+
+" TODO:  I should think about multi-line patterns for b:match_words.
+"   This would require an option:  how many lines to scan (default 1).
+"   This would be useful for Python, maybe also for *ML.
+" TODO:  Maybe I should add a menu so that people will actually use some of
+"   the features that I have implemented.
+" TODO:  Eliminate the MultiMatch function.  Add yet another argument to
+"   Match_wrapper() instead.
+" TODO:  Allow :let b:match_words = '\(\(foo\)\(bar\)\):\3\2:end\1'
+" TODO:  Make backrefs safer by using '\V' (very no-magic).
+" TODO:  Add a level of indirection, so that custom % scripts can use my
+"   work but extend it.
+
+" allow user to prevent loading
+" and prevent duplicate loading
+if exists("loaded_matchit") || &cp
+  finish
+endif
+let loaded_matchit = 1
+let s:last_mps = ""
+let s:last_words = ":"
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+nnoremap <silent> %  :<C-U>call <SID>Match_wrapper('',1,'n') <CR>
+nnoremap <silent> g% :<C-U>call <SID>Match_wrapper('',0,'n') <CR>
+vnoremap <silent> %  :<C-U>call <SID>Match_wrapper('',1,'v') <CR>m'gv``
+vnoremap <silent> g% :<C-U>call <SID>Match_wrapper('',0,'v') <CR>m'gv``
+onoremap <silent> %  v:<C-U>call <SID>Match_wrapper('',1,'o') <CR>
+onoremap <silent> g% v:<C-U>call <SID>Match_wrapper('',0,'o') <CR>
+
+" Analogues of [{ and ]} using matching patterns:
+nnoremap <silent> [% :<C-U>call <SID>MultiMatch("bW", "n") <CR>
+nnoremap <silent> ]% :<C-U>call <SID>MultiMatch("W",  "n") <CR>
+vmap [% <Esc>[%m'gv``
+vmap ]% <Esc>]%m'gv``
+" vnoremap <silent> [% :<C-U>call <SID>MultiMatch("bW", "v") <CR>m'gv``
+" vnoremap <silent> ]% :<C-U>call <SID>MultiMatch("W",  "v") <CR>m'gv``
+onoremap <silent> [% v:<C-U>call <SID>MultiMatch("bW", "o") <CR>
+onoremap <silent> ]% v:<C-U>call <SID>MultiMatch("W",  "o") <CR>
+
+" text object:
+vmap a% <Esc>[%v]%
+
+" Auto-complete mappings:  (not yet "ready for prime time")
+" TODO Read :help write-plugin for the "right" way to let the user
+" specify a key binding.
+"   let g:match_auto = '<C-]>'
+"   let g:match_autoCR = '<C-CR>'
+" if exists("g:match_auto")
+"   execute "inoremap " . g:match_auto . ' x<Esc>"=<SID>Autocomplete()<CR>Pls'
+" endif
+" if exists("g:match_autoCR")
+"   execute "inoremap " . g:match_autoCR . ' <CR><C-R>=<SID>Autocomplete()<CR>'
+" endif
+" if exists("g:match_gthhoh")
+"   execute "inoremap " . g:match_gthhoh . ' <C-O>:call <SID>Gthhoh()<CR>'
+" endif " gthhoh = "Get the heck out of here!"
+
+let s:notslash = '\\\@<!\%(\\\\\)*'
+
+function! s:Match_wrapper(word, forward, mode) range
+  " In s:CleanUp(), :execute "set" restore_options .
+  let restore_options = (&ic ? " " : " no") . "ignorecase"
+  if exists("b:match_ignorecase")
+    let &ignorecase = b:match_ignorecase
+  endif
+  let restore_options = " ve=" . &ve . restore_options
+  set ve=
+  " If this function was called from Visual mode, make sure that the cursor
+  " is at the correct end of the Visual range:
+  if a:mode == "v"
+    execute "normal! gv\<Esc>"
+  endif
+  " In s:CleanUp(), we may need to check whether the cursor moved forward.
+  let startline = line(".")
+  let startcol = col(".")
+  " Use default behavior if called with a count.
+  if v:count
+    exe "normal! " . v:count . "%"
+    return s:CleanUp(restore_options, a:mode, startline, startcol)
+  end
+
+  " First step:  if not already done, set the script variables
+  "   s:do_BR  flag for whether there are backrefs
+  "   s:pat    parsed version of b:match_words
+  "   s:all    regexp based on s:pat and the default groups
+  "
+  if !exists("b:match_words") || b:match_words == ""
+    let match_words = ""
+    " Allow b:match_words = "GetVimMatchWords()" .
+  elseif b:match_words =~ ":"
+    let match_words = b:match_words
+  else
+    execute "let match_words =" b:match_words
+  endif
+" Thanks to Preben "Peppe" Guldberg and Bram Moolenaar for this suggestion!
+  if (match_words != s:last_words) || (&mps != s:last_mps) ||
+    \ exists("b:match_debug")
+    let s:last_words = match_words
+    let s:last_mps = &mps
+    " The next several lines were here before
+    " BF started messing with this script.
+    " quote the special chars in 'matchpairs', replace [,:] with \| and then
+    " append the builtin pairs (/*, */, #if, #ifdef, #else, #elif, #endif)
+    " let default = substitute(escape(&mps, '[$^.*~\\/?]'), '[,:]\+',
+    "  \ '\\|', 'g').'\|\/\*\|\*\/\|#if\>\|#ifdef\>\|#else\>\|#elif\>\|#endif\>'
+    let default = escape(&mps, '[$^.*~\\/?]') . (strlen(&mps) ? "," : "") .
+      \ '\/\*:\*\/,#if\%(def\)\=:#else\>:#elif\>:#endif\>'
+    " s:all = pattern with all the keywords
+    let match_words = match_words . (strlen(match_words) ? "," : "") . default
+    if match_words !~ s:notslash . '\\\d'
+      let s:do_BR = 0
+      let s:pat = match_words
+    else
+      let s:do_BR = 1
+      let s:pat = s:ParseWords(match_words)
+    endif
+    let s:all = substitute(s:pat, s:notslash . '\zs[,:]\+', '\\|', 'g')
+    let s:all = '\%(' . s:all . '\)'
+    " let s:all = '\%(' . substitute(s:all, '\\\ze[,:]', '', 'g') . '\)'
+    if exists("b:match_debug")
+      let b:match_pat = s:pat
+    endif
+  endif
+
+  " Second step:  set the following local variables:
+  "     matchline = line on which the cursor started
+  "     curcol    = number of characters before match
+  "     prefix    = regexp for start of line to start of match
+  "     suffix    = regexp for end of match to end of line
+  " Require match to end on or after the cursor and prefer it to
+  " start on or before the cursor.
+  let matchline = getline(startline)
+  if a:word != ''
+    " word given
+    if a:word !~ s:all
+      echohl WarningMsg|echo 'Missing rule for word:"'.a:word.'"'|echohl NONE
+      return s:CleanUp(restore_options, a:mode, startline, startcol)
+    endif
+    let matchline = a:word
+    let curcol = 0
+    let prefix = '^\%('
+    let suffix = '\)$'
+  " Now the case when "word" is not given
+  else " Find the match that ends on or after the cursor and set curcol.
+    let regexp = s:Wholematch(matchline, s:all, startcol-1)
+    let curcol = match(matchline, regexp)
+    " If there is no match, give up.
+    if curcol == -1
+      return s:CleanUp(restore_options, a:mode, startline, startcol)
+    endif
+    let endcol = matchend(matchline, regexp)
+    let suf = strlen(matchline) - endcol
+    let prefix = (curcol ? '^.*\%'  . (curcol + 1) . 'c\%(' : '^\%(')
+    let suffix = (suf ? '\)\%' . (endcol + 1) . 'c.*$'  : '\)$')
+  endif
+  if exists("b:match_debug")
+    let b:match_match = matchstr(matchline, regexp)
+    let b:match_col = curcol+1
+  endif
+
+  " Third step:  Find the group and single word that match, and the original
+  " (backref) versions of these.  Then, resolve the backrefs.
+  " Set the following local variable:
+  " group = colon-separated list of patterns, one of which matches
+  "       = ini:mid:fin or ini:fin
+  "
+  " Reconstruct the version with unresolved backrefs.
+  let patBR = substitute(match_words.',',
+    \ s:notslash.'\zs[,:]*,[,:]*', ',', 'g')
+  let patBR = substitute(patBR, s:notslash.'\zs:\{2,}', ':', 'g')
+  " Now, set group and groupBR to the matching group: 'if:endif' or
+  " 'while:endwhile' or whatever.  A bit of a kluge:  s:Choose() returns
+  " group . "," . groupBR, and we pick it apart.
+  let group = s:Choose(s:pat, matchline, ",", ":", prefix, suffix, patBR)
+  let i = matchend(group, s:notslash . ",")
+  let groupBR = strpart(group, i)
+  let group = strpart(group, 0, i-1)
+  " Now, matchline =~ prefix . substitute(group,':','\|','g') . suffix
+  if s:do_BR " Do the hard part:  resolve those backrefs!
+    let group = s:InsertRefs(groupBR, prefix, group, suffix, matchline)
+  endif
+  if exists("b:match_debug")
+    let b:match_wholeBR = groupBR
+    let i = matchend(groupBR, s:notslash . ":")
+    let b:match_iniBR = strpart(groupBR, 0, i-1)
+  endif
+
+  " Fourth step:  Set the arguments for searchpair().
+  let i = matchend(group, s:notslash . ":")
+  let j = matchend(group, '.*' . s:notslash . ":")
+  let ini = strpart(group, 0, i-1)
+  let mid = substitute(strpart(group, i,j-i-1), s:notslash.'\zs:', '\\|', 'g')
+  let fin = strpart(group, j)
+  "Un-escape the remaining , and : characters.
+  let ini = substitute(ini, s:notslash . '\zs\\\(:\|,\)', '\1', 'g')
+  let mid = substitute(mid, s:notslash . '\zs\\\(:\|,\)', '\1', 'g')
+  let fin = substitute(fin, s:notslash . '\zs\\\(:\|,\)', '\1', 'g')
+  " searchpair() requires that these patterns avoid \(\) groups.
+  let ini = substitute(ini, s:notslash . '\zs\\(', '\\%(', 'g')
+  let mid = substitute(mid, s:notslash . '\zs\\(', '\\%(', 'g')
+  let fin = substitute(fin, s:notslash . '\zs\\(', '\\%(', 'g')
+  " Set mid.  This is optimized for readability, not micro-efficiency!
+  if a:forward && matchline =~ prefix . fin . suffix
+    \ || !a:forward && matchline =~ prefix . ini . suffix
+    let mid = ""
+  endif
+  " Set flag.  This is optimized for readability, not micro-efficiency!
+  if a:forward && matchline =~ prefix . fin . suffix
+    \ || !a:forward && matchline !~ prefix . ini . suffix
+    let flag = "bW"
+  else
+    let flag = "W"
+  endif
+  " Set skip.
+  if exists("b:match_skip")
+    let skip = b:match_skip
+  elseif exists("b:match_comment") " backwards compatibility and testing!
+    let skip = "r:" . b:match_comment
+  else
+    let skip = 's:comment\|string'
+  endif
+  let skip = s:ParseSkip(skip)
+  if exists("b:match_debug")
+    let b:match_ini = ini
+    let b:match_tail = (strlen(mid) ? mid.'\|' : '') . fin
+  endif
+
+  " Fifth step:  actually start moving the cursor and call searchpair().
+  " Later, :execute restore_cursor to get to the original screen.
+  let restore_cursor = virtcol(".") . "|"
+  normal! g0
+  let restore_cursor = line(".") . "G" .  virtcol(".") . "|zs" . restore_cursor
+  normal! H
+  let restore_cursor = "normal!" . line(".") . "Gzt" . restore_cursor
+  execute restore_cursor
+  call cursor(0, curcol + 1)
+  " normal! 0
+  " if curcol
+  "   execute "normal!" . curcol . "l"
+  " endif
+  if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on"))
+    let skip = "0"
+  else
+    execute "if " . skip . "| let skip = '0' | endif"
+  endif
+  let sp_return = searchpair(ini, mid, fin, flag, skip)
+  let final_position = "call cursor(" . line(".") . "," . col(".") . ")"
+  " Restore cursor position and original screen.
+  execute restore_cursor
+  normal! m'
+  if sp_return > 0
+    execute final_position
+  endif
+  return s:CleanUp(restore_options, a:mode, startline, startcol, mid.'\|'.fin)
+endfun
+
+" Restore options and do some special handling for Operator-pending mode.
+" The optional argument is the tail of the matching group.
+fun! s:CleanUp(options, mode, startline, startcol, ...)
+  execute "set" a:options
+  " Open folds, if appropriate.
+  if a:mode != "o"
+    if &foldopen =~ "percent"
+      normal! zv
+    endif
+    " In Operator-pending mode, we want to include the whole match
+    " (for example, d%).
+    " This is only a problem if we end up moving in the forward direction.
+  elseif (a:startline < line(".")) ||
+       \ (a:startline == line(".") && a:startcol < col("."))
+    if a:0
+      " Check whether the match is a single character.  If not, move to the
+      " end of the match.
+      let matchline = getline(".")
+      let currcol = col(".")
+      let regexp = s:Wholematch(matchline, a:1, currcol-1)
+      let endcol = matchend(matchline, regexp)
+      if endcol > currcol  " This is NOT off by one!
+       execute "normal!" . (endcol - currcol) . "l"
+      endif
+    endif " a:0
+  endif " a:mode != "o" && etc.
+  return 0
+endfun
+
+" Example (simplified HTML patterns):  if
+"   a:groupBR  = '<\(\k\+\)>:</\1>'
+"   a:prefix   = '^.\{3}\('
+"   a:group    = '<\(\k\+\)>:</\(\k\+\)>'
+"   a:suffix   = '\).\{2}$'
+"   a:matchline        =  "123<tag>12" or "123</tag>12"
+" then extract "tag" from a:matchline and return "<tag>:</tag>" .
+fun! s:InsertRefs(groupBR, prefix, group, suffix, matchline)
+  if a:matchline !~ a:prefix .
+    \ substitute(a:group, s:notslash . '\zs:', '\\|', 'g') . a:suffix
+    return a:group
+  endif
+  let i = matchend(a:groupBR, s:notslash . ':')
+  let ini = strpart(a:groupBR, 0, i-1)
+  let tailBR = strpart(a:groupBR, i)
+  let word = s:Choose(a:group, a:matchline, ":", "", a:prefix, a:suffix,
+    \ a:groupBR)
+  let i = matchend(word, s:notslash . ":")
+  let wordBR = strpart(word, i)
+  let word = strpart(word, 0, i-1)
+  " Now, a:matchline =~ a:prefix . word . a:suffix
+  if wordBR != ini
+    let table = s:Resolve(ini, wordBR, "table")
+  else
+    " let table = "----------"
+    let table = ""
+    let d = 0
+    while d < 10
+      if tailBR =~ s:notslash . '\\' . d
+       " let table[d] = d
+       let table = table . d
+      else
+       let table = table . "-"
+      endif
+      let d = d + 1
+    endwhile
+  endif
+  let d = 9
+  while d
+    if table[d] != "-"
+      let backref = substitute(a:matchline, a:prefix.word.a:suffix,
+       \ '\'.table[d], "")
+       " Are there any other characters that should be escaped?
+      let backref = escape(backref, '*,:')
+      execute s:Ref(ini, d, "start", "len")
+      let ini = strpart(ini, 0, start) . backref . strpart(ini, start+len)
+      let tailBR = substitute(tailBR, s:notslash . '\zs\\' . d,
+       \ escape(backref, '\\'), 'g')
+    endif
+    let d = d-1
+  endwhile
+  if exists("b:match_debug")
+    if s:do_BR
+      let b:match_table = table
+      let b:match_word = word
+    else
+      let b:match_table = ""
+      let b:match_word = ""
+    endif
+  endif
+  return ini . ":" . tailBR
+endfun
+
+" Input a comma-separated list of groups with backrefs, such as
+"   a:groups = '\(foo\):end\1,\(bar\):end\1'
+" and return a comma-separated list of groups with backrefs replaced:
+"   return '\(foo\):end\(foo\),\(bar\):end\(bar\)'
+fun! s:ParseWords(groups)
+  let groups = substitute(a:groups.",", s:notslash.'\zs[,:]*,[,:]*', ',', 'g')
+  let groups = substitute(groups, s:notslash . '\zs:\{2,}', ':', 'g')
+  let parsed = ""
+  while groups =~ '[^,:]'
+    let i = matchend(groups, s:notslash . ':')
+    let j = matchend(groups, s:notslash . ',')
+    let ini = strpart(groups, 0, i-1)
+    let tail = strpart(groups, i, j-i-1) . ":"
+    let groups = strpart(groups, j)
+    let parsed = parsed . ini
+    let i = matchend(tail, s:notslash . ':')
+    while i != -1
+      " In 'if:else:endif', ini='if' and word='else' and then word='endif'.
+      let word = strpart(tail, 0, i-1)
+      let tail = strpart(tail, i)
+      let i = matchend(tail, s:notslash . ':')
+      let parsed = parsed . ":" . s:Resolve(ini, word, "word")
+    endwhile " Now, tail has been used up.
+    let parsed = parsed . ","
+  endwhile " groups =~ '[^,:]'
+  let parsed = substitute(parsed, ',$', '', '')
+  return parsed
+endfun
+
+" TODO I think this can be simplified and/or made more efficient.
+" TODO What should I do if a:start is out of range?
+" Return a regexp that matches all of a:string, such that
+" matchstr(a:string, regexp) represents the match for a:pat that starts
+" as close to a:start as possible, before being preferred to after, and
+" ends after a:start .
+" Usage:
+" let regexp = s:Wholematch(getline("."), 'foo\|bar', col(".")-1)
+" let i      = match(getline("."), regexp)
+" let j      = matchend(getline("."), regexp)
+" let match  = matchstr(getline("."), regexp)
+fun! s:Wholematch(string, pat, start)
+  let group = '\%(' . a:pat . '\)'
+  let prefix = (a:start ? '\(^.*\%<' . (a:start + 2) . 'c\)\zs' : '^')
+  let len = strlen(a:string)
+  let suffix = (a:start+1 < len ? '\(\%>'.(a:start+1).'c.*$\)\@=' : '$')
+  if a:string !~ prefix . group . suffix
+    let prefix = ''
+  endif
+  return prefix . group . suffix
+endfun
+
+" No extra arguments:  s:Ref(string, d) will
+" find the d'th occurrence of '\(' and return it, along with everything up
+" to and including the matching '\)'.
+" One argument:  s:Ref(string, d, "start") returns the index of the start
+" of the d'th '\(' and any other argument returns the length of the group.
+" Two arguments:  s:Ref(string, d, "foo", "bar") returns a string to be
+" executed, having the effect of
+"   :let foo = s:Ref(string, d, "start")
+"   :let bar = s:Ref(string, d, "len")
+fun! s:Ref(string, d, ...)
+  let len = strlen(a:string)
+  if a:d == 0
+    let start = 0
+  else
+    let cnt = a:d
+    let match = a:string
+    while cnt
+      let cnt = cnt - 1
+      let index = matchend(match, s:notslash . '\\(')
+      if index == -1
+       return ""
+      endif
+      let match = strpart(match, index)
+    endwhile
+    let start = len - strlen(match)
+    if a:0 == 1 && a:1 == "start"
+      return start - 2
+    endif
+    let cnt = 1
+    while cnt
+      let index = matchend(match, s:notslash . '\\(\|\\)') - 1
+      if index == -2
+       return ""
+      endif
+      " Increment if an open, decrement if a ')':
+      let cnt = cnt + (match[index]=="(" ? 1 : -1)  " ')'
+      " let cnt = stridx('0(', match[index]) + cnt
+      let match = strpart(match, index+1)
+    endwhile
+    let start = start - 2
+    let len = len - start - strlen(match)
+  endif
+  if a:0 == 1
+    return len
+  elseif a:0 == 2
+    return "let " . a:1 . "=" . start . "| let " . a:2 . "=" . len
+  else
+    return strpart(a:string, start, len)
+  endif
+endfun
+
+" Count the number of disjoint copies of pattern in string.
+" If the pattern is a literal string and contains no '0' or '1' characters
+" then s:Count(string, pattern, '0', '1') should be faster than
+" s:Count(string, pattern).
+fun! s:Count(string, pattern, ...)
+  let pat = escape(a:pattern, '\\')
+  if a:0 > 1
+    let foo = substitute(a:string, '[^'.a:pattern.']', "a:1", "g")
+    let foo = substitute(a:string, pat, a:2, "g")
+    let foo = substitute(foo, '[^' . a:2 . ']', "", "g")
+    return strlen(foo)
+  endif
+  let result = 0
+  let foo = a:string
+  let index = matchend(foo, pat)
+  while index != -1
+    let result = result + 1
+    let foo = strpart(foo, index)
+    let index = matchend(foo, pat)
+  endwhile
+  return result
+endfun
+
+" s:Resolve('\(a\)\(b\)', '\(c\)\2\1\1\2') should return table.word, where
+" word = '\(c\)\(b\)\(a\)\3\2' and table = '-32-------'.  That is, the first
+" '\1' in target is replaced by '\(a\)' in word, table[1] = 3, and this
+" indicates that all other instances of '\1' in target are to be replaced
+" by '\3'.  The hard part is dealing with nesting...
+" Note that ":" is an illegal character for source and target,
+" unless it is preceded by "\".
+fun! s:Resolve(source, target, output)
+  let word = a:target
+  let i = matchend(word, s:notslash . '\\\d') - 1
+  let table = "----------"
+  while i != -2 " There are back references to be replaced.
+    let d = word[i]
+    let backref = s:Ref(a:source, d)
+    " The idea is to replace '\d' with backref.  Before we do this,
+    " replace any \(\) groups in backref with :1, :2, ... if they
+    " correspond to the first, second, ... group already inserted
+    " into backref.  Later, replace :1 with \1 and so on.  The group
+    " number w+b within backref corresponds to the group number
+    " s within a:source.
+    " w = number of '\(' in word before the current one
+    let w = s:Count(
+    \ substitute(strpart(word, 0, i-1), '\\\\', '', 'g'), '\(', '1')
+    let b = 1 " number of the current '\(' in backref
+    let s = d " number of the current '\(' in a:source
+    while b <= s:Count(substitute(backref, '\\\\', '', 'g'), '\(', '1')
+    \ && s < 10
+      if table[s] == "-"
+       if w + b < 10
+         " let table[s] = w + b
+         let table = strpart(table, 0, s) . (w+b) . strpart(table, s+1)
+       endif
+       let b = b + 1
+       let s = s + 1
+      else
+       execute s:Ref(backref, b, "start", "len")
+       let ref = strpart(backref, start, len)
+       let backref = strpart(backref, 0, start) . ":". table[s]
+       \ . strpart(backref, start+len)
+       let s = s + s:Count(substitute(ref, '\\\\', '', 'g'), '\(', '1')
+      endif
+    endwhile
+    let word = strpart(word, 0, i-1) . backref . strpart(word, i+1)
+    let i = matchend(word, s:notslash . '\\\d') - 1
+  endwhile
+  let word = substitute(word, s:notslash . '\zs:', '\\', 'g')
+  if a:output == "table"
+    return table
+  elseif a:output == "word"
+    return word
+  else
+    return table . word
+  endif
+endfun
+
+" Assume a:comma = ",".  Then the format for a:patterns and a:1 is
+"   a:patterns = "<pat1>,<pat2>,..."
+"   a:1 = "<alt1>,<alt2>,..."
+" If <patn> is the first pattern that matches a:string then return <patn>
+" if no optional arguments are given; return <patn>,<altn> if a:1 is given.
+fun! s:Choose(patterns, string, comma, branch, prefix, suffix, ...)
+  let tail = (a:patterns =~ a:comma."$" ? a:patterns : a:patterns . a:comma)
+  let i = matchend(tail, s:notslash . a:comma)
+  if a:0
+    let alttail = (a:1 =~ a:comma."$" ? a:1 : a:1 . a:comma)
+    let j = matchend(alttail, s:notslash . a:comma)
+  endif
+  let current = strpart(tail, 0, i-1)
+  if a:branch == ""
+    let currpat = current
+  else
+    let currpat = substitute(current, s:notslash . a:branch, '\\|', 'g')
+  endif
+  while a:string !~ a:prefix . currpat . a:suffix
+    let tail = strpart(tail, i)
+    let i = matchend(tail, s:notslash . a:comma)
+    if i == -1
+      return -1
+    endif
+    let current = strpart(tail, 0, i-1)
+    if a:branch == ""
+      let currpat = current
+    else
+      let currpat = substitute(current, s:notslash . a:branch, '\\|', 'g')
+    endif
+    if a:0
+      let alttail = strpart(alttail, j)
+      let j = matchend(alttail, s:notslash . a:comma)
+    endif
+  endwhile
+  if a:0
+    let current = current . a:comma . strpart(alttail, 0, j-1)
+  endif
+  return current
+endfun
+
+" Call this function to turn on debugging information.  Every time the main
+" script is run, buffer variables will be saved.  These can be used directly
+" or viewed using the menu items below.
+if !exists(":MatchDebug")
+  command! -nargs=0 MatchDebug call s:Match_debug()
+endif
+
+fun! s:Match_debug()
+  let b:match_debug = 1        " Save debugging information.
+  " pat = all of b:match_words with backrefs parsed
+  amenu &Matchit.&pat  :echo b:match_pat<CR>
+  " match = bit of text that is recognized as a match
+  amenu &Matchit.&match        :echo b:match_match<CR>
+  " curcol = cursor column of the start of the matching text
+  amenu &Matchit.&curcol       :echo b:match_col<CR>
+  " wholeBR = matching group, original version
+  amenu &Matchit.wh&oleBR      :echo b:match_wholeBR<CR>
+  " iniBR = 'if' piece, original version
+  amenu &Matchit.ini&BR        :echo b:match_iniBR<CR>
+  " ini = 'if' piece, with all backrefs resolved from match
+  amenu &Matchit.&ini  :echo b:match_ini<CR>
+  " tail = 'else\|endif' piece, with all backrefs resolved from match
+  amenu &Matchit.&tail :echo b:match_tail<CR>
+  " fin = 'endif' piece, with all backrefs resolved from match
+  amenu &Matchit.&word :echo b:match_word<CR>
+  " '\'.d in ini refers to the same thing as '\'.table[d] in word.
+  amenu &Matchit.t&able        :echo '0:' . b:match_table . ':9'<CR>
+endfun
+
+" Jump to the nearest unmatched "(" or "if" or "<tag>" if a:spflag == "bW"
+" or the nearest unmatched "</tag>" or "endif" or ")" if a:spflag == "W".
+" Return a "mark" for the original position, so that
+"   let m = MultiMatch("bW", "n") ... execute m
+" will return to the original position.  If there is a problem, do not
+" move the cursor and return "", unless a count is given, in which case
+" go up or down as many levels as possible and again return "".
+" TODO This relies on the same patterns as % matching.  It might be a good
+" idea to give it its own matching patterns.
+fun! s:MultiMatch(spflag, mode)
+  if !exists("b:match_words") || b:match_words == ""
+    return ""
+  end
+  let restore_options = (&ic ? "" : "no") . "ignorecase"
+  if exists("b:match_ignorecase")
+    let &ignorecase = b:match_ignorecase
+  endif
+  let startline = line(".")
+  let startcol = col(".")
+
+  " First step:  if not already done, set the script variables
+  "   s:do_BR  flag for whether there are backrefs
+  "   s:pat    parsed version of b:match_words
+  "   s:all    regexp based on s:pat and the default groups
+  " This part is copied and slightly modified from s:Match_wrapper().
+  let default = escape(&mps, '[$^.*~\\/?]') . (strlen(&mps) ? "," : "") .
+    \ '\/\*:\*\/,#if\%(def\)\=:#else\>:#elif\>:#endif\>'
+  " Allow b:match_words = "GetVimMatchWords()" .
+  if b:match_words =~ ":"
+    let match_words = b:match_words
+  else
+    execute "let match_words =" b:match_words
+  endif
+  if (match_words != s:last_words) || (&mps != s:last_mps) ||
+    \ exists("b:match_debug")
+    let s:last_words = match_words
+    let s:last_mps = &mps
+    if match_words !~ s:notslash . '\\\d'
+      let s:do_BR = 0
+      let s:pat = match_words
+    else
+      let s:do_BR = 1
+      let s:pat = s:ParseWords(match_words)
+    endif
+    let s:all = '\%(' . substitute(s:pat . (strlen(s:pat)?",":"") . default,
+      \        '[,:]\+','\\|','g') . '\)'
+    if exists("b:match_debug")
+      let b:match_pat = s:pat
+    endif
+  endif
+
+  " Second step:  figure out the patterns for searchpair()
+  " and save the screen, cursor position, and 'ignorecase'.
+  " - TODO:  A lot of this is copied from s:Match_wrapper().
+  " - maybe even more functionality should be split off
+  " - into separate functions!
+  let cdefault = (s:pat =~ '[^,]$' ? "," : "") . default
+  let open =  substitute(s:pat . cdefault,
+       \ s:notslash . '\zs:.\{-}' . s:notslash . ',', '\\),\\(', 'g')
+  let open =  '\(' . substitute(open, s:notslash . '\zs:.*$', '\\)', '')
+  let close = substitute(s:pat . cdefault,
+       \ s:notslash . '\zs,.\{-}' . s:notslash . ':', '\\),\\(', 'g')
+  let close = substitute(close, '^.\{-}' . s:notslash . ':', '\\(', '') . '\)'
+  if exists("b:match_skip")
+    let skip = b:match_skip
+  elseif exists("b:match_comment") " backwards compatibility and testing!
+    let skip = "r:" . b:match_comment
+  else
+    let skip = 's:comment\|string'
+  endif
+  let skip = s:ParseSkip(skip)
+  " let restore_cursor = line(".") . "G" . virtcol(".") . "|"
+  " normal! H
+  " let restore_cursor = "normal!" . line(".") . "Gzt" . restore_cursor
+  let restore_cursor = virtcol(".") . "|"
+  normal! g0
+  let restore_cursor = line(".") . "G" .  virtcol(".") . "|zs" . restore_cursor
+  normal! H
+  let restore_cursor = "normal!" . line(".") . "Gzt" . restore_cursor
+  execute restore_cursor
+
+  " Third step: call searchpair().
+  " Replace '\('--but not '\\('--with '\%(' and ',' with '\|'.
+  let openpat =  substitute(open, '\(\\\@<!\(\\\\\)*\)\@<=\\(', '\\%(', 'g')
+  let openpat = substitute(openpat, ',', '\\|', 'g')
+  let closepat = substitute(close, '\(\\\@<!\(\\\\\)*\)\@<=\\(', '\\%(', 'g')
+  let closepat = substitute(closepat, ',', '\\|', 'g')
+  if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on"))
+    let skip = '0'
+  else
+    execute "if " . skip . "| let skip = '0' | endif"
+  endif
+  mark '
+  let level = v:count1
+  while level
+    if searchpair(openpat, '', closepat, a:spflag, skip) < 1
+      call s:CleanUp(restore_options, a:mode, startline, startcol)
+      return ""
+    endif
+    let level = level - 1
+  endwhile
+
+  " Restore options and return a string to restore the original position.
+  call s:CleanUp(restore_options, a:mode, startline, startcol)
+  return restore_cursor
+endfun
+
+" Search backwards for "if" or "while" or "<tag>" or ...
+" and return "endif" or "endwhile" or "</tag>" or ... .
+" For now, this uses b:match_words and the same script variables
+" as s:Match_wrapper() .  Later, it may get its own patterns,
+" either from a buffer variable or passed as arguments.
+" fun! s:Autocomplete()
+"   echo "autocomplete not yet implemented :-("
+"   if !exists("b:match_words") || b:match_words == ""
+"     return ""
+"   end
+"   let startpos = s:MultiMatch("bW")
+"
+"   if startpos == ""
+"     return ""
+"   endif
+"   " - TODO:  figure out whether 'if' or '<tag>' matched, and construct
+"   " - the appropriate closing.
+"   let matchline = getline(".")
+"   let curcol = col(".") - 1
+"   " - TODO:  Change the s:all argument if there is a new set of match pats.
+"   let regexp = s:Wholematch(matchline, s:all, curcol)
+"   let suf = strlen(matchline) - matchend(matchline, regexp)
+"   let prefix = (curcol ? '^.\{'  . curcol . '}\%(' : '^\%(')
+"   let suffix = (suf ? '\).\{' . suf . '}$'  : '\)$')
+"   " Reconstruct the version with unresolved backrefs.
+"   let patBR = substitute(b:match_words.',', '[,:]*,[,:]*', ',', 'g')
+"   let patBR = substitute(patBR, ':\{2,}', ':', "g")
+"   " Now, set group and groupBR to the matching group: 'if:endif' or
+"   " 'while:endwhile' or whatever.
+"   let group = s:Choose(s:pat, matchline, ",", ":", prefix, suffix, patBR)
+"   let i = matchend(group, s:notslash . ",")
+"   let groupBR = strpart(group, i)
+"   let group = strpart(group, 0, i-1)
+"   " Now, matchline =~ prefix . substitute(group,':','\|','g') . suffix
+"   if s:do_BR
+"     let group = s:InsertRefs(groupBR, prefix, group, suffix, matchline)
+"   endif
+" " let g:group = group
+"
+"   " - TODO:  Construct the closing from group.
+"   let fake = "end" . expand("<cword>")
+"   execute startpos
+"   return fake
+" endfun
+
+" Close all open structures.  "Get the heck out of here!"
+" fun! s:Gthhoh()
+"   let close = s:Autocomplete()
+"   while strlen(close)
+"     put=close
+"     let close = s:Autocomplete()
+"   endwhile
+" endfun
+
+" Parse special strings as typical skip arguments for searchpair():
+"   s:foo becomes (current syntax item) =~ foo
+"   S:foo becomes (current syntax item) !~ foo
+"   r:foo becomes (line before cursor) =~ foo
+"   R:foo becomes (line before cursor) !~ foo
+fun! s:ParseSkip(str)
+  let skip = a:str
+  if skip[1] == ":"
+    if skip[0] == "s"
+      let skip = "synIDattr(synID(line('.'),col('.'),1),'name') =~? '" .
+       \ strpart(skip,2) . "'"
+    elseif skip[0] == "S"
+      let skip = "synIDattr(synID(line('.'),col('.'),1),'name') !~? '" .
+       \ strpart(skip,2) . "'"
+    elseif skip[0] == "r"
+      let skip = "strpart(getline('.'),0,col('.'))=~'" . strpart(skip,2). "'"
+    elseif skip[0] == "R"
+      let skip = "strpart(getline('.'),0,col('.'))!~'" . strpart(skip,2). "'"
+    endif
+  endif
+  return skip
+endfun
+
+let &cpo = s:save_cpo
+
+" vim:sts=2:sw=2: