Looking to define a match group for modelines. vim defines two patterns for modelines:

The first form:

  • [text{white}] empty or any text followed by at least one blank character (<Space> or <Tab>); ex: always requires at least one blank character
  • {vi:|vim:|ex:} the string vi:, vim: or ex:
  • [white] optional white space
  • {options}a list of option settings, separated with white space or :, where each part between : is the argument for a :set command (can be empty)


   vi:noai:sw=3 ts=6 ~
   vim: tw=77 ~

The second form (this is compatible with some versions of Vi):

[text{white}]{vi:|vim:|Vim:|ex:}[white]se[t] {options}:[text]
  • [text{white}] empty or any text followed by at least one blank character (<Space> or <Tab>); ex: always requires at least one blank character
  • {vi:|vim:|Vim:|ex:} the string vi:, vim:, Vim: or ex:
  • [white] optional white space
  • se[t] the string set or se (note the space); When Vim is used it must be set .
  • {options} a list of options, separated with white space, which is the argument for a :set command
  • : a colon
  • [text] any text or empty


   /* vim: set ai tw=75: */ ~
   /* Vim: set ai tw=75: */ ~

I currently have a simple pattern to match the modelines I've written:

syntax match Modeline ".*\s+vim:\sset.*:.*"

but I would like to create a match group that matches on all lines Vim treats as modelines (the pattern above misses some of the nuance with ex for example). I imagine vim internally has regex expressions that match these patterns. I'd like to use those expressions in a match group.

  • What did you try?
    – romainl
    Commented Apr 15 at 5:12
  • I have a hard coded pattern to match the modelines I create, but I'm looking for a more robust solution that matches what vim identifies as a modeline. Updated comment. Commented Apr 15 at 5:37
  • 2
    The grammars you describe (which are also found in :help modeline) could easily be turned into a regular expression. I doubt there's a way to access Vim's internal modeline matching mechanism. I don't know but these things are typically not exposed. I'd write a regex that matches modelines as they are documented.
    – Friedrich
    Commented Apr 15 at 5:41
  • Show us that pattern so that we can help you improve it.
    – romainl
    Commented Apr 15 at 7:26
  • @Friedrich though, it might be possible to expose them in the source so that they can be used (assuming they're actually regexes in the source and not handrolled parsers)
    – D. Ben Knoble
    Commented Apr 15 at 15:36

3 Answers 3


Alternative contortions based on :help modeline that match
the same (13) lines in :help options.txt as does the other
, and are also accommodating for \sex: and no
leading text.

(Note that it is assumed that {vi,{V,v}im,ex}: is within
or after the leftmost 16 (arbitrary) characters and some
blanks, and we only seek set\s for Vim: because set\=\s
can be optional for {vi,vim,ex}:.)

syn match Modeline /\%#=1^\C\%(.\{-,16}\s\+\)\=\%(\%(vim\=\|\s\@1<=ex\):\|Vim:\s*set\s\).*$/

It may be more readable as a pair of definitions:

syn match Modeline /\%#=1^\C\%(.\{-,16}\s\+\)\=\%(vim\=:\|Vim:\s*set\s\).*$/
syn match Modeline /\%#=1^\C\%(.\{-,16}\s*\)\=\sex:.*$/

The following ‘core dump’ should also cover versioning
(:help modeline-version).

syn match Modeline /\%#=1^\C\%(.\{-,16}\s\+\)\=\%(\%(vim\=\%(m\@1<=[<=>]\=\d\+\)\=\|\s\@1<=ex\):\|Vim\%([<=>]\=\d\+\)\=:\s*set\s\).*$/

And still a ‘core dump’ :( as a triplet:

syn match Modeline /\%#=1^\C\%(.\{-,16}\s\+\)\=vim\=\%(m\@1<=[<=>]\=\d\+\)\=:.*$/
syn match Modeline /\%#=1^\C\%(.\{-,16}\s\+\)\=Vim\%([<=>]\=\d\+\)\=:\s*set\s.*$/
syn match Modeline /\%#=1^\C\%(.\{-,16}\s*\)\=\sex:.*$/

(The key to dense look-behinds \@1<=:

  • If a string without a trailing blank is matched with
    \%(.\{-,16}\s\+\)\=, fail ex: with \s\@1<=ex;
  • If vi: is matched with vim\=:, fail for versioning with

I did a bit of profiling with :help :syntime in v9.1 (with
1-16 patches). It turns out that the linked pattern can be
made about four times nimbler by rewriting it as follows:

syn match Modeline /\%#=1^.*\s\%(\%(vim\=\|ex\):\|Vim:\s*set\s\).*$/

In order to recreate the profiling setup I have used:

  1. Write this syntax file (~/.vim/syntax/modeline.vim),
    and change PATTERN:
syn match Modeline /PATTERN/
hi def link Modeline Todo
  1. Write this definition of g:Auto_Scroll_And_Report() in

def g:Auto_Scroll_And_Report()
    cursor(1, 1)
    const last: number = line('$')
    const walk: string = 'normal! ' .. "\<C-D>"
    &l:scroll = winheight(0) / 2
    syntime on

    while line('w$') != last
        execute walk
        sleep 25m

    syntime report
  1. Rename a copy of $VIMRUNTIME/doc/options.txt as
    /tmp/options.modeline and change &ft for its modeline:
  1. Repeatedly launch and profile Vim:
vim --clean /tmp/options.modeline -Nu NONE \
  -c 'set rtp+=~/.vim' \
  -c 'setf modeline' \
  -c 'syntax on' \
  -S /tmp/report.vim \
  -c 'call Auto_Scroll_And_Report()'


  • Thank you for the thorough breakdown and more comprehensive solution. I'll accept as answer after I try it out. Commented Apr 23 at 4:06

(Not an answer proper, but swaths of code don't belong to
comments either.)

Take a look at chk_modeline() in src/buffer.c and

Consider cloning vim/vim and vim/vim-history and tracing
code evolution as follows:

cd path/to/vim/vim/src/
git log --oneline -- testdir/test_modeline.vim

git log -L '/^chk_modeline(/,/^}$/':buffer.c HEAD

cd path/to/vim/vim-history/src/
git log -L '/^chk_modeline(/,/^}$/':buffer.c HEAD

git tag --list v[34]\*
git switch --detach v3.0
git log -L '/^chk_mline(/,/^}$/':fileio.c HEAD

git tag --list v1\*
git switch --detach v1.14

Now, you can start with a regexp prototype for v1.14 and
reiterate it as far as you need it.

  • I’d have to look how, but I think there’s a way to graft the two histories back together.
    – D. Ben Knoble
    Commented Apr 19 at 20:06
  • Thanks. I didn't know about the vim-history repo. This gives me a thread to pull on. Commented Apr 20 at 1:59
  • @D.BenKnoble github.com/vim/vim-history/blob/master/… Commented Apr 20 at 9:00
  • Thanks @ChristianBrabandt I had a feeling it was something like that
    – D. Ben Knoble
    Commented Apr 20 at 15:25

I followed @Friedrich's suggestion and built a pattern that matches many of the common modelines:

syntax match Modeline /.*\s\+\(vi\|vim\|ex\):.*\|.*\s\+\(vi\|vim\|Vim\|ex\):\s*set.*:.*/

it's missing the mandatory space before ex: but is working well in practice for me.

Edit: following some of the suggestions from below.

  • I can't really judge the overall correctness of the composed regexp, but there are a few parts that can be improved upon for the general case: a) since there is no support for obtaining \(capturing\) sub-expressions with \backreference in :syn-match, prefer its \%(non-capturing\) variant; b) the right outermost alternation may be redundant (ignorecase), namely, with :\s*.*\|:\s*set.*, set.* will be matched with .*; c) strive to avoid .*, especially at the start, here, allow for a comment marker and a few blanks before and after it, e.g. ^.\{-,16}; Commented Apr 20 at 15:11
  • [continued] d) consider factoring out common prefixes in alternations, instead of \%(vi\|vim\|ex\), prefer \C\%(vim\=\|ex\); e) :help modeline-version has been overlooked. Commented Apr 20 at 15:11
  • Thanks. I was not aware of the non capturing flag. For \C\%(vim\=\|ex\); e) instead of \%(vi\|vim\|ex\), as they are similar in length and I find the second form more clear in its intent, is there any other advantage to the first form I'm missing? Commented Apr 21 at 15:08
  • 1
    Well, with (1) \%(vi\|vim\|ex\):, it takes four tests to dismiss "emacs" by not finding "em": [v; v; e, em]; whereas it is three tests with (2) \%(vim\=\|ex\): [v; e, em]. Why bother, I hear; but, the longer the mismatch prefix is, the more is the number of redundant tests to follow. For a "view" word, it is seven tests [v, vi, vie; v, vi, vie; e] with (1) against four tests [v, vi, vie; e] with (2). And it can get interesting with words like superconductiVIty, hypersensitiVIty, ChancellorsVIlle, or circumflEX. Why repeat the tests that will never match? Commented Apr 22 at 18:02
  • Sorry about bringing up ‘superconductiVIty’ etc. out of context; it is obviously not applicable for .*\svim\=:. I was thinking about a more general pattern .*vim\=:. Commented Apr 23 at 13:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.