@@ -128,18 +128,18 @@ syn match pythonDecoratorName "@\s*\h\%(\w\|\.\)*" display contains=pythonDeco
128128" Single line multiplication.
129129syn match pythonMatrixMultiply
130130 \ " \% (\w\| [])]\)\s *@"
131- \ contains= ALLBUT ,pythonDecoratorName,pythonDecorator,pythonClass,pythonFunction,pythonDoctestValue
131+ \ contains= ALLBUT ,pythonDecoratorName,pythonDecorator,pythonClass,pythonFunction,pythonDoctestValue, @p ythonFStringContained
132132 \ transparent
133133" Multiplication continued on the next line after backslash.
134134syn match pythonMatrixMultiply
135135 \ " [^\\ ]\\ \s *\n \% (\s *\.\.\.\s\)\=\s\+ @"
136- \ contains= ALLBUT ,pythonDecoratorName,pythonDecorator,pythonClass,pythonFunction,pythonDoctestValue
136+ \ contains= ALLBUT ,pythonDecoratorName,pythonDecorator,pythonClass,pythonFunction,pythonDoctestValue, @p ythonFStringContained
137137 \ transparent
138138" Multiplication in a parenthesized expression over multiple lines with @ at
139139" the start of each continued line; very similar to decorators and complex.
140140syn match pythonMatrixMultiply
141141 \ " ^\s *\% (\% (>>>\|\.\.\.\)\s\+\)\=\z s\% (\h\|\% (\h\| [[(]\) .\{ -}\% (\w\| [])]\)\)\s *\n \% (\s *\.\.\.\s\)\=\s\+ @\% (.\{ -}\n \% (\s *\.\.\.\s\)\=\s\+ @\) *"
142- \ contains= ALLBUT ,pythonDecoratorName,pythonDecorator,pythonClass,pythonFunction,pythonDoctestValue
142+ \ contains= ALLBUT ,pythonDecoratorName,pythonDecorator,pythonClass,pythonFunction,pythonDoctestValue, @p ythonFStringContained
143143 \ transparent
144144
145145syn match pythonClass " \h\w *" display contained
@@ -161,33 +161,22 @@ syn region pythonRawString matchgroup=pythonQuotes
161161syn region pythonRawString matchgroup =pythonTripleQuotes
162162 \ start = + [rR]\z (' ' '\| " " " \) + end=" \z 1" keepend
163163 \ contains= pythonSpaceError,pythonDoctest,@S pell
164-
165- " Formatted string literals (f-strings)
166- " https://docs.python.org/3/reference/lexical_analysis.html#f-strings
167- syn region pythonFString
168- \ matchgroup= pythonQuotes
169- \ start = + \c F\z (['" ]\)+
170- \ end = " \z 1"
171- \ skip = " \\\\ \| \\ \z 1"
172- \ contains= pythonFStringField,pythonFStringSkip,pythonEscape,pythonUnicodeEscape,@S pell
173- syn region pythonFString
174- \ matchgroup= pythonTripleQuotes
175- \ start = + \c F\z (' ' '\| " " " \)+
176- \ end = " \z 1"
177- \ keepend
178- \ contains= pythonFStringField,pythonFStringSkip,pythonEscape,pythonUnicodeEscape,pythonSpaceError,pythonDoctest,@S pell
179- syn region pythonRawFString
180- \ matchgroup= pythonQuotes
181- \ start = + \c\% (FR\| RF\)\z (['" ]\)+
182- \ end = " \z 1"
183- \ skip = " \\\\ \| \\ \z 1"
184- \ contains= pythonFStringField,pythonFStringSkip,@S pell
185- syn region pythonRawFString
186- \ matchgroup= pythonTripleQuotes
187- \ start = + \c\% (FR\| RF\)\z (' ' '\| " " " \)+
188- \ end = " \z 1"
189- \ keepend
190- \ contains= pythonFStringField,pythonFStringSkip,pythonSpaceError,pythonDoctest,@S pell
164+ syn region pythonFString matchgroup =pythonQuotes
165+ \ start = + [fF]\z (['" ]\)+
166+ \ end = " $\|\z 1" skip = " \\\\ \| \\ \z 1"
167+ \ contains= pythonEscape,pythonUnicodeEscape,pythonFStringEscapedBrace,pythonFStringReplacement,@S pell
168+ syn region pythonFString matchgroup =pythonTripleQuotes
169+ \ start = + [fF]\z (' ' '\| " " " \)+
170+ \ end = " \z 1" keepend
171+ \ contains= pythonEscape,pythonUnicodeEscape,pythonSpaceError,pythonFStringEscapedBrace,pythonFStringReplacement,pythonDoctest,@S pell
172+ syn region pythonRawFString matchgroup =pythonQuotes
173+ \ start = + \% ([fF][rR]\| [rR][fF]\)\z (['" ]\)+
174+ \ end = " $\|\z 1" skip = " \\\\ \| \\ \r \= $\| \\ \z 1"
175+ \ contains= pythonFStringEscapedBrace,pythonFStringReplacement,@S pell
176+ syn region pythonRawFString matchgroup =pythonTripleQuotes
177+ \ start = + \% ([fF][rR]\| [rR][fF]\)\z (' ' '\| " " " \)+
178+ \ end = " \z 1" keepend
179+ \ contains= pythonSpaceError,pythonFStringEscapedBrace,pythonFStringReplacement,pythonDoctest,@S pell
191180
192181" Bytes
193182syn region pythonBytes
@@ -213,24 +202,6 @@ syn region pythonRawBytes
213202 \ end = " \z 1"
214203 \ keepend
215204
216- " F-string replacement fields
217- "
218- " - Matched parentheses, brackets and braces are ignored
219- " - A bare # is ignored to end of line
220- " - A bare = (surrounded by optional whitespace) enables debugging
221- " - A bare ! prefixes a conversion field
222- " - A bare : begins a format specification
223- " - Matched braces inside a format specification are ignored
224- "
225- syn region pythonFStringField
226- \ matchgroup= pythonFStringDelimiter
227- \ start = /{/
228- \ skip = /([^)]*)\|\[[^]]*]\|{[^}]*}\|#.*$/
229- \ end = /\%(\s*=\s*\)\=\%(!\a\)\=\%(:\%({[^}]*}\|[^}]*\)\+\)\=}/
230- \ contained
231- " Doubled braces and Unicode escapes are not replacement fields
232- syn match pythonFStringSkip / {{\|\\ N{/ transparent contained contains =NONE
233-
234205syn match pythonEscape + \\ [abfnrtv'"\\ ]+ contained
235206syn match pythonEscape " \\\o\{ 1,3}" contained
236207syn match pythonEscape " \\ x\x\{ 2}" contained
@@ -239,6 +210,62 @@ syn match pythonUnicodeEscape "\%(\\u\x\{4}\|\\U\x\{8}\)" contained
239210" The specification: https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-4/#G135165
240211syn match pythonUnicodeEscape " \\ N{\a\+\% (\% (\s\a\+ [[:alnum:]]*\)\|\% (-[[:alnum:]]\+\)\) *}" contained
241212syn match pythonEscape " \\ $"
213+ syn match pythonEscape " \\\r\= $"
214+
215+ " f-strings
216+ " See https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals
217+ syn region pythonFStringReplacement matchgroup =pythonFStringBrace start =+ {+ end =+ }+ contained contains =pythonFStringExpression
218+
219+ " Skip contained '[:!]'
220+ " TODO: this should contain @pythonExpression rather than TOP
221+ syn region pythonFStringBrackets
222+ \ start = " {" end = " }"
223+ \ contained contains= TOP
224+ \ transparent
225+ syn region pythonFStringBrackets
226+ \ start = " (" end = " )"
227+ \ contained contains= TOP
228+ \ transparent
229+ syn region pythonFStringBrackets
230+ \ start = " \[ " end = " ]"
231+ \ contained contains= TOP
232+ \ transparent
233+
234+ syn region pythonFStringReplacementWhitespace start =" \s " end =" \z e\S " contained nextgroup =pythonFStringConversion,pythonFStringFormatSpec
235+
236+ " TODO: contains (yield_expr | star_expressions)
237+ " True False None yield from if else for lambda await
238+ syn cluster pythonExpression
239+ \ contains= pythonNumber,python .* String ,pythonOperator,pythonBuiltin,pythonAttribute,pythonComment,pythonFStringBrackets
240+
241+ syn region pythonFStringExpression
242+ \ start = " ." end = " \z e[=!:}]"
243+ \ contained contains= @p ythonExpression
244+ \ nextgroup= pythonFStringEquals,pythonFStringConversion,pythonFStringFormatSpec
245+
246+ " TODO: it would be better to match pythonOperator with priority but symbolic
247+ " operators are not currently matched
248+ " skip operators
249+ syn match pythonFStringEquals " [!<>=]\@ 1<!==\@ !"
250+ \ contained
251+ \ nextgroup= pythonFStringConversion,pythonFStringFormatSpec,pythonFStringReplacementWhitespace,pythonComment
252+ \ skipwhite skipempty
253+ hi def link pythonFStringEquals Special
254+
255+ syn match pythonFStringConversion " ![ars]"
256+ \ contained
257+ \ nextgroup= pythonFStringFormatSpec,pythonFStringReplacementWhitespace,pythonComment
258+ \ skipwhite skipempty
259+ hi def link pythonFStringConversion Special
260+
261+ syn region pythonFStringFormatSpec
262+ \ matchgroup= Delimiter start = " :" matchgroup= NONE end = " \z e}"
263+ \ contained contains= pythonFStringReplacement
264+ hi def link pythonFStringFormatSpec Special
265+
266+ syn match pythonFStringEscapedBrace " {{\| }}" contained
267+
268+ syn cluster pythonFStringContained contains =pythonFString.\+
242269
243270" It is very important to understand all details before changing the
244271" regular expressions below or their order.
@@ -303,7 +330,7 @@ if !exists("python_no_builtin_highlight")
303330 syn keyword pythonBuiltin tuple type vars zip __import__
304331 " avoid highlighting attributes as builtins
305332 syn match pythonAttribute / \.\h\w */ hs =s + 1
306- \ contains= ALLBUT ,pythonBuiltin,pythonClass,pythonFunction,pythonAsync
333+ \ contains= ALLBUT ,pythonBuiltin,pythonClass,pythonFunction,pythonAsync,pythonFStringExpression
307334 \ transparent
308335endif
309336
@@ -360,7 +387,7 @@ if !exists("python_no_doctest_highlight")
360387 if ! exists (" python_no_doctest_code_highlight" )
361388 syn region pythonDoctest
362389 \ start = " ^\s *>>>\s " end = " ^\s *$"
363- \ contained contains= ALLBUT ,pythonDoctest,pythonClass,pythonFunction,@S pell
390+ \ contained contains= ALLBUT ,pythonDoctest,pythonClass,pythonFunction,@p ythonFStringContained, @ S pell
364391 syn region pythonDoctestValue
365392 \ start = + ^\s * \% (>>>\s\|\.\.\.\s\| " " " \| '''\)\@ !\S\+ + end=" $ "
366393 \ contained
@@ -399,7 +426,9 @@ hi def link pythonQuotes String
399426hi def link pythonTripleQuotes pythonQuotes
400427hi def link pythonEscape Special
401428hi def link pythonUnicodeEscape pythonEscape
402- hi def link pythonFStringDelimiter Special
429+ " hi def link pythonFStringDelimiter Special
430+ hi def link pythonFStringEscapedBrace Special
431+ hi def link pythonFStringBrace Include
403432if ! exists (" python_no_number_highlight" )
404433 hi def link pythonNumber Number
405434endif
0 commit comments