You can match whole words, such as if and
endif, not just single characters.
You can define groups with more than two words, such as
if, else, endif.
Banging on the % 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.
By default, words inside comments are ignored, unless the cursor
is inside a comment when you type %. If the only thing you
want to do is modify the behavior of % so that it treats
comments this way, you can :let b:match_words = &matchpairs
and source the script.
See the description of comments for
details.
Currently, the following languages are supported:
Ada,
Csh,
DTD,
Entity,
Essbase,
Fortran,
HTML,
LaTeX,
Pascal,
SGML,
Shell,
Tcsh,
Vim,
XML.
To support a new language, see
below.
First,
downloadmatchit.vim.
For these instructions, I assume that you save it as
$VIM/matchit.vim.
(This is appropriate on single-user systems. For multi-user systems,
something like
$HOME/vim/matchit.vim
would be more reasonable.) There is nothing magic about the directory nor the
name of the file. Note that most users only have to follow Step 2 below.
(optional) If you want to "test drive" before you commit
yourself, do :source $VIM/matchit.vim
within Vim. Then open a file with one of the supported file types and
start banging on the % key. If you already have the file
open, you will have to trigger the autocommand that defines
b:match_words, so do :set ft=<file type>
where <file type> is the current file type: tex,
html, vim, or whatever.
Add the line :source $VIM/matchit.vim
to your vimrc file. (If you do not have a vimrc file, read
:help vimrc
within Vim.) It should start working the next time you start Vim. If
you are impatient and do not want to restart Vim, see the previous
step.
(trouble shooting) What if nothing happens? Check the file
matchit.vim and look for a line like au FileType html ...
for the file type you are using. If your file type is not yet
supported, go to the section on
supporting a new language.
If it is supported, return to the file you were editing and type :echo b:match_words<CR>
where <CR> means a carriage-return. If this is not
set as indicated in the autocommand, try :filetype on
:set ft=<file type>
You should consider adding a
:filetype on
or
:syntax on
line to your vimrc file (or gvimrc file). If b:match_words
is still not set, check that the autocommand has been defined: :au Matchit FileType <file type>
This should show an autocommand to set b:match_words. If
it does not, try sourcing matchit.vim again. If the
autocommand is there but b:match_words is still not set, I
am stumped. Maybe you should replace FileType html in the
autocommand with BufRead,BufNewFile *.html or whatever.
Maybe it is time to ask for help.
MatchError:
This 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 unless someone requests it.)
b:match_strings_like_comments:
If this variable has been defined then strings will be treated
like comments. For example,
'("(")'
will not be treated as unbalanced.
b:match_debug:
Define this variable if you want debugging information to be saved.
See the discussion debugging information,
below.
The format for b:match_words is similar to that of the
'matchpairs' option: it is a colon
(:)-separated list of groups; each group is a
comma(,)-separated list of patterns (regular expressions).
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".
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 usual
way of doing this is by adding an autocommand, either withing the
function Match_autocommands() in the file
matchit.vim or in some file that is sourced after you
source matchit.vim. Continuing the example above, the
autocommand should be something like :autocmd Matchit FileType myft let b:match_words =
\ '\<if\>,\<endif\>:\<while\>,\<continue\>,\<break\>,\<endwhile\>'
If your version of vim does not support the FileType
autocommand event then you can use BufNewFile,BufRead
*.myft instead of FileType myft. If you are using
Vim 5.3 or earlier then the line continuation here will not work: you
will have to modify this to make it a single line, and make similar
modifications to matchit.vim.
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). For
example, in the string
"<tag>label</tag>",
'<.*>'
matches the whole string whereas
'<.\{-}>'
and
'<[^>]*>'
match
"<tag>
and
"</tag>".
If "if" is to be paired with "end if" (Note
the space!) then word boundaries are not enough. Instead, define a
regular expression notend that will match anything but
"end" and use it as follows: let notend = '\(^\s*\|[^d\t ]\s\+\)'
let b:match_words = notend . '\<if\>,\<end\s\+if\>'
This is a simplified version of what is done for Ada. For details,
including how to do it while making notend a local
variable, see the autocommand for Ada in matchit.vim.
Similarly, you may want to define a start-of-line regular
expression :let sol = '\(^\|;\)\s*'
if keywords are only recognized after the start of a line or after a
semicolon (;), with optional white space.
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,
'\<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 notend or
sol (as in the previous paragraph) then remember to count
any '\(' patterns in this variable.
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.
(TODO) The special character '&' means "Put the
cursor on the following character." 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.
Defining the variable b:match_debug causes the script to
set the following variables, each time you hit the % key.
Severasl of these are only defined if b:match_words
includes backrefs.
b:match_pat:
b:match_words with backrefs parsed
b:match_match:
the bit of text that is recognized as a match
b:match_col:
the cursor column of the start of the matching text
b:match_wholeBR:
the comma-separated group of patterns that matches, with
backrefs unparsed
b:match_iniBR:
the first pattern in b:match_wholeBR
b:match_ini:
the first pattern in b:match_wholeBR, with backrefs
resolved from b:match_match
b:match_tail:
the remaining patterns in b:match_wholeBR, with
backrefs resolved from b:match_match
b:match_word:
the pattern from b:match_wholeBR that matches
b:match_match
b:match_table:
The back reference '\'.d refers to the same thing
as '\'.table[d] in b:match_word.
I think that the comma (,) and colon (:) are
used backwards compared to the syntax for 'matchpairs'. I
may change this in the next version.
Since the comma (,) and colon (:) are used
to separate patterns, they cannot be used as part of a pattern. I
suppose this could be solved by interpreting \, and
\: as literal characters, but this might be harder than it
sounds... (TODO: I will be adding ampersand (&) as
another special character.)
The script does not recognize \\ as an escaped
backslash. Thus \\1, \\(, and \\)
cause problems. This is a problem for LaTeX, where it would be nice
to add '\\(,\\)' to b:match_words.
The script may not treat ^ and $ as start-
and end-of-line in all cases. A bug report would help in diagnosing
this if, in fact, there is a problem.
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.)
Someone may already have worked out the language you are trying to
support. They may have neglected to send me the definitions, or I may
not have gotten around to including them in matchit.vim, or
you may not have the most recent version.
I may be on vacation, or way behind on my e-mail, or I may have
given up supporting this script. The list is always there, and there
are helpful people around the world (in many, if not all, time zones)
who might help you out.
Posting questions to the list has the side effect of advertising
this useful script (and thereby stroking my ego). For this reason,
please include the URL when posting questions to the list.
I read the list, so you are as likely to reach me that way as you
are by e-mailing me directly.
If, after all that, you still want to send me e-mail directly (compliments or
bug reports, for example) then
go ahead!