---
title: Writing Vim Syntax Plugins
teaser: Write syntax plugins to highlight and indent languages in Vim.
tags: vim,swift
author: Keith Smiley
published_on: 2014-10-06
---

One of Vim's most powerful features is the vast built-in and available
language support. With the rise of [new][go] [languages][clojure] and
the desire to write them in Vim with proper syntax highlighting, it's
useful to understand how to create our own syntax plugins for Vim in
order to write, extend, or contribute to open source syntax plugins.

[go]: http://golang.org/
[clojure]: http://clojure.org/

## Syntax Plugin Structure

Syntax plugins, like all Vim plugins, follow a basic folder structure.
This file structure became mainstream with the release of
[pathogen][pathogen] and works with all modern Vim plugin managers such
as [Vundle][vundle] and [vim-plug][vim-plug]. I'm going to be using
[`swift.vim`][swift.vim] as an example of how syntax plugins fit
together. Here is the relevant file structure (using [tree][tree]):

[swift.vim]: https://github.com/Keithbsmiley/swift.vim
[tree]: http://www.computerhope.com/unix/tree.htm
[pathogen]: https://github.com/tpope/vim-pathogen
[vundle]: https://github.com/gmarik/Vundle.vim
[vim-plug]: https://github.com/junegunn/vim-plug

    ├── ftdetect
    │   └── swift.vim
    ├── ftplugin
    │   └── swift.vim
    ├── syntax
    │   └── swift.vim
    └── indent
        └── swift.vim

## ftdetect

The first configuration file we encounter is in the `ftdetect` folder
(`:help ftdetect`). In this folder we have one or more files named after
the `filetype` they apply to. In this example we have a `swift.vim`
file. In `ftdetect` files we decide whether or not we want to set (or
override) the `filetype` of a file in different situations. For
`swift.vim` this file looks like this:

```vim
" ftdetect/swift.vim
autocmd BufNewFile,BufRead *.swift setfiletype swift
```

Here we're using `setfiletype` on any files named `*.swift` whenever we
create a new file or finish reading an existing file. `setfiletype` is a
friendlier way to do `set filetype=swift`. If the `filetype` has already
been set `setfiletype` will not override it. Depending on what our
syntax plugin does that might not always be what we want.

## ftplugin

Next we have the `ftplugin` folder (`:help ftplugin`). This lets us
execute arbitrary Vimscript or set other settings whenever the file is
sourced. Files in Vim's `runtimepath` (`:help rtp`) nested in `ftplugin`
folders are sourced automatically when the filename (before `.vim`)
matches the current `filetype` setting (we can read this setting with
`:set ft`). So after our `ftdetect` file sets the file type as `swift`,
Vim will source the `swift.vim` file nested in the `ftplugin` folder.
In `swift.vim` that file looks like this:

```vim
" ftplugin/swift.vim
setlocal commentstring=//\ %s
" @-@ adds the literal @ to iskeyword for @IBAction and similar
setlocal iskeyword+=?,!,@-@,#
setlocal tabstop=2
setlocal softtabstop=2
setlocal shiftwidth=2
setlocal completefunc=syntaxcomplete#Complete
```

All of these settings are things that apply to how we want Vim to act
when editing Swift files. The most interesting here is when we set
`iskeyword`. Here we're adding a few symbols so that when we execute
commands such as `diw` those symbols will be treated as just another
character in the word instead of treating them as delimiters breaking
words.

## syntax

Now we get to the meat of syntax plugins. Files nested under the
`syntax` folder named after the current `filetype` setting are sourced
in order to actually highlight the file. Syntax files contain rules in
order to match patterns in our file. Here are a few examples:

```vim
" syntax/swift.vim
" Match TODO comments
syntax keyword swiftTodos TODO XXX FIXME NOTE

" Match language specific keywords
syntax keyword swiftKeywords
      \ if
      \ let
      \ nil
      \ var
```

Both of these examples define new syntax matches for `keyword`s. Keyword
matches should be used for the language's reserved words that should
always be highlighted as such. Matches can also get more complicated:

```vim
" syntax/swift.vim
" Match all Swift number types
syntax match swiftNumber "\v<\d+>"
syntax match swiftNumber "\v<\d+\.\d+>"
syntax match swiftNumber "\v<\d*\.?\d+([Ee]-?)?\d+>"
syntax match swiftNumber "\v<0x\x+([Pp]-?)?\x+>"
syntax match swiftNumber "\v<0b[01]+>"
syntax match swiftNumber "\v<0o\o+>"
```

Here we've defined syntax matches with the `match` argument allowing us
to match regular expressions (see `:help magic` for what's going on
here). These specific regular expressions match all of Swift's number
types. Some examples of matched numbers with these:

    5
    5.5
    5e-2
    5.5E2
    0b1011
    0o567
    0xA2EF

Syntax matching can get even more complicated when we start matching
function declarations and other statements. For example:

```vim
" syntax/swift.vim
" Match strings
syntax region swiftString start=/"/ skip=/\\"/ end=/"/ oneline contains=swiftInterpolatedWrapper
syntax region swiftInterpolatedWrapper start="\v\\\(\s*" end="\v\s*\)" contained containedin=swiftString contains=swiftInterpolatedString
syntax match swiftInterpolatedString "\v\w+(\(\))?" contained containedin=swiftInterpolatedWrapper
```

Here we're matching Swift strings that are wrapped in double quotes. In
the first line we start out by matching the `swiftString` `region` which
defines `start`, `end`, and optional `skip` patterns. Here the skip
pattern allows us to have escaped double quotes in our strings. We also
pass some other arguments here. `oneline` specifies that this pattern
can only span a single line. `contains` specifies what other syntax
groups we've defined can exist within our region. In this case a string
can contain interpolated arguments similar to Ruby. In Swift
interpolated strings look like this:

```swift
"2 + 2 = \(2 + 2)"
```

The second line shows how we define the region enclosed within the
interpolated string. Our start region specifies the starting `\(`
pattern along with any number of optional spaces while our end region
matches spaces and the closing `)`. We specify that this is contained
within our strings so it is not matched elsewhere and that it contains
our third syntax group. In our final group we specify what can be
contained within the interpolated portion of our string.

When writing syntax files for entire languages, we'll have to create
matches using each of these types of groups to match different things.
The hardest part of this is dealing with collisions. We have to be very
aware that what we're changing won't break another portion of our
highlighting.

Once we have our matches we need to tell Vim how to highlight them in a
way it understands. Here are the highlighting associations for
`swift.vim`:

```vim
" syntax/swift.vim
" Set highlights
highlight default link swiftTodos Todo
highlight default link swiftShebang Comment
highlight default link swiftComment Comment
highlight default link swiftMarker Comment

highlight default link swiftString String
highlight default link swiftInterpolatedWrapper Delimiter
highlight default link swiftNumber Number
highlight default link swiftBoolean Boolean

highlight default link swiftOperator Operator
highlight default link swiftKeywords Keyword
highlight default link swiftAttributes PreProc
highlight default link swiftStructure Structure
highlight default link swiftType Type
highlight default link swiftImports Include
highlight default link swiftPreprocessor PreProc
```

Here we are linking one of our match group names to a Vim-specific
keyword based on what our group matches. We can see all available
matches under `:help group-name` where they are highlighted based off
our current color scheme. While it may be tempting to link a group to a
type based on its appearance in our color scheme, it's more important to
maintain the match's semantic meaning so they will work as expected in
other user's color schemes.

## indent

Finally we have the indent folder. Again, it contains a file named after
the `filetype` for how indentation should work as we're writing in Vim.
Personally I think handling indentation is the hardest part of writing
syntax plugins. The goal in these files is to do one specific thing. Set
our `indentexpr` (`:help inde`) to a function that calculates and
returns the level of indent for the current line. While that may sound
simple, taking the context of that line into account can be extremely
difficult. Also, just like with syntax files, we have to worry about
collisions. Let's look at some of this file for `swift.vim`.

```vim
" indent/swift.vim
setlocal indentexpr=SwiftIndent()

function! SwiftIndent()
  let line = getline(v:lnum)
  let previousNum = prevnonblank(v:lnum - 1)
  let previous = getline(previousNum)

  if previous =~ "{" && previous !~ "}" && line !~ "}" && line !~ ":$"
    return indent(previousNum) + &tabstop
  endif

  ...
endfunction
```

Here we're setting the `indentexpr` to our function. Then inside our
function we're getting some context around the line we're trying to
indent. The first thing we notice is the `v:lnum` variable (`:help
lnum-variable`). This is a predefined variable (`:help vim-variable`)
that is set automatically inside indent functions. It gives us the line
number for the current line to be indented. We can then use it along
with some other Vimscript to get the text on that line along with the
text on the last non-blank line above it. Using this information, along
with some pattern matching, we can build out cases where our lines
should be indented more, less, or the same as the surrounding lines.

## Wrapping up

Combining these four Vim plugin file types you can contribute to syntax
plugins you have found bugs in, create syntax plugins for [additions to
languages][eruby] or [entire languages][ruby]. Doing so you can help Vim
continue to stay awesome as new programming languages come and go.

[eruby]: https://github.com/vim-ruby/vim-ruby/blob/master/syntax/eruby.vim
[ruby]: https://github.com/vim-ruby/vim-ruby/blob/master/syntax/ruby.vim

## What's next

- Read through chapters 41-49 of [Learn Vimscript the Hard
  way][vimscript] (you really should read the whole thing)
- Look at [other][markdown] [syntax][go] [plugins][rust.vim] on GitHub
- Look at local syntax plugins with `:edit $VIMRUNTIME/syntax/LANGUAGE.vim`

[vimscript]: http://learnvimscriptthehardway.stevelosh.com/
[markdown]: https://github.com/tpope/vim-markdown
[go]: https://github.com/fatih/vim-go
[rust.vim]: https://github.com/wting/rust.vim
