Python f-strings syntax highlighting in Vim
Getting Python syntax highlighting to work in Vim requires very little code, to my surprise.
Here is everything that you need and an explanation below.
" in ~/.config/nvim/after/syntax or ~/.vim/after/syntaxsyn match pythonEscape +{{+ contained containedin=pythonfString,pythonfDocstringsyn match pythonEscape +}}+ contained containedin=pythonfString,pythonfDocstring syn region pythonfString matchgroup=pythonQuotes \ start=+[fF]\@1<=\z(['"]\)+ end="\z1" \ contains=@Spell,pythonEscape,pythonInterpolationsyn region pythonfDocstring matchgroup=pythonQuotes \ start=+[fF]\@1<=\z('''\|"""\)+ end="\z1" keepend \ contains=@Spell,pythonEscape,pythonSpaceError,pythonInterpolation,pythonDoctest syn region pythonInterpolation contained \ matchgroup=SpecialChar \ start=+{{\@!+ end=+}}\@!+ skip=+{{+ keepend \ contains=ALLBUT,pythonDecoratorName,pythonDecorator,pythonFunction,pythonDoctestValue,pythonDoctest syn match pythonStringModifier /:\(.[<^=>]\)\?[-+ ]\?#\?0\?[0-9]*[_,]\?\(\.[0-9]*\)\?[bcdeEfFgGnosxX%]\?/ contained containedin=pythonInterpolationsyn match pythonStringModifier /![sra]/ contained containedin=pythonInterpolation hi link pythonfString Stringhi link pythonfDocstring Stringhi link pythonStringModifier PreProc
Declaring a syntax region for f-strings
The first two lines define a new syntax region (see :h syn-region
) called
pythonfString
.
We then declare how it starts with the regex [fF]\@1<=\z(['"]\)
, which is
equivalent to (?:<=[fF])(['"])
in Perl regular expressions (see :h \@<=
).
The second line just handles the case of a docstring.
The string will end how it starts, so we can just reference the captured group
using \z1
(we need to prefix it with z
because it is an external pattern,
see :h \z(
).
The matchgroup
argument tells Vim which highlight group it should use to
highlight the start/end pattern. The group pythonQuotes
come from the default
syntax file.
Handling expressions inside f-strings
We also need to declare what this region contains.
For this we declare another region called pythonInterpolation
, which starts
with {
(but not with {{
, which will actually produce a literal {
) and
closes with }
. With that in mind, we use the regex {{\@!
because we don’t
want a match if the preceding token is present (see :h \@!
).
This region may contain only expressions, so stuff like a function declaration
does not make sense (notice there is a syntax for that, :helpgrep ALLBUT
)
Handling string modifiers
f-strings supports
str.format
syntax
for formatting, for example:
import mathprint(f"The value of pi is approximately {math.pi:.3f}.")
So I read the Python docs and wrote a regex based on it, but regexes are always easier to write than to read so I wouldn’t recommend you trying.
It’s also possible to convert a value as if wrapping them in functions such as
ascii()
, repr()
, str()
with !a
, !r
, !s
respectively, so we need to
handle this also.
For this, I declared a syntax group with :h syn-match
and pass the regexes
that should be used.
It should only be highlighted inside a pythonInterpolation
so we take
advantage of the containedin
argument (see :h syn-containedin
).
Highlighting declared groups
Finally, we link these new highlight groups with an appropriate/whichever you
like highlight group (see :h hi-link
and :h group-name
). I chose String
for f-strings and PreProc
for modifiers. And it should work as expected.