]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
runtime(python): highlight ellipsis literals
authorJon Parise <jon@indelible.org>
Mon, 8 Sep 2025 20:29:53 +0000 (16:29 -0400)
committerChristian Brabandt <cb@256bit.org>
Mon, 8 Sep 2025 20:29:53 +0000 (16:29 -0400)
The ellipsis literal (`...`) can be used in multiple contexts:

- Placeholders:     `class Foo: ...`
- Containers:       `Tuple[int, ...]`
- Assignments:      `x = ...`

This is a trickier pattern to match because we can't rely on keyword
boundaries, so we instead look for exactly three dots (`...`).

This does mean that we will match the `...` portion of `x...x`, which
isn't valid Python syntax, but I think that's an acceptable trade-off
that avoids making this pattern much more complex.

Reference:
- https://docs.python.org/3/library/constants.html#Ellipsis

closes: #18107

Signed-off-by: Jon Parise <jon@indelible.org>
Signed-off-by: Christian Brabandt <cb@256bit.org>
runtime/syntax/python.vim
runtime/syntax/testdir/dumps/python_ellipsis_00.dump [new file with mode: 0644]
runtime/syntax/testdir/dumps/python_ellipsis_01.dump [new file with mode: 0644]
runtime/syntax/testdir/dumps/python_ellipsis_02.dump [new file with mode: 0644]
runtime/syntax/testdir/input/python_ellipsis.py [new file with mode: 0644]

index 645d4bc92d4eb373a7c5d0532351588d2e36f42e..542805d8497b1e3abe4be6f36fe0d12e58244e91 100644 (file)
@@ -313,6 +313,8 @@ if !exists("python_no_builtin_highlight")
   syn match   pythonAttribute  /\.\h\w*/hs=s+1
        \ contains=ALLBUT,pythonBuiltin,pythonClass,pythonFunction,pythonType,pythonAsync
        \ transparent
+  " the ellipsis literal `...` can be used in multiple syntactic contexts
+  syn match   pythonEllipsis   "\.\@1<!.\.\.\ze\.\@!" display
 endif
 
 " From the 'Python Library Reference' class hierarchy at the bottom.
@@ -368,10 +370,12 @@ if !exists("python_no_doctest_highlight")
   if !exists("python_no_doctest_code_highlight")
     syn region pythonDoctest
          \ start="^\s*>>>\s" end="^\s*$"
-         \ contained contains=ALLBUT,pythonDoctest,pythonClass,pythonFunction,pythonType,@Spell
+         \ contained contains=ALLBUT,pythonDoctest,pythonEllipsis,pythonClass,pythonFunction,pythonType,@Spell
     syn region pythonDoctestValue
          \ start=+^\s*\%(>>>\s\|\.\.\.\s\|"""\|'''\)\@!\S\++ end="$"
-         \ contained
+         \ contained contains=pythonEllipsis
+    syn match pythonEllipsis "\%(^\s*\)\@<!\.\@1<!\zs\.\.\.\ze\.\@!" display
+         \ contained containedin=pythonDoctest
   else
     syn region pythonDoctest
          \ start="^\s*>>>" end="^\s*$"
@@ -414,6 +418,7 @@ if !exists("python_no_number_highlight")
 endif
 if !exists("python_no_builtin_highlight")
   hi def link pythonBuiltin            Function
+  hi def link pythonEllipsis           pythonBuiltin
 endif
 if !exists("python_no_exception_highlight")
   hi def link pythonExceptions         Structure
diff --git a/runtime/syntax/testdir/dumps/python_ellipsis_00.dump b/runtime/syntax/testdir/dumps/python_ellipsis_00.dump
new file mode 100644 (file)
index 0000000..d3fcb64
--- /dev/null
@@ -0,0 +1,20 @@
+>#+0#0000e05#ffffff0| |E|l@1|i|p|s|i|s| |L|i|t|e|r|a|l| +0#0000000&@56
+|#+0#0000e05&| |h|t@1|p|s|:|/@1|d|o|c|s|.|p|y|t|h|o|n|.|o|r|g|/|3|/|l|i|b|r|a|r|y|/|c|o|n|s|t|a|n|t|s|.|h|t|m|l|#|E|l@1|i|p|s|i|s| +0#0000000&@15
+@75
+|#+0#0000e05&| |P|l|a|c|e|h|o|l|d|e|r|s| +0#0000000&@60
+|.+0#00e0e07&@2| +0#0000000&@71
+@8|.+0#00e0e07&@2| +0#0000000&@63
+|x| |=| |.+0#00e0e07&@2| +0#0000000&@67
+|y| |=| |.+0#00e0e07&@2| +0#0000000&|#+0#0000e05&| |C|o|m@1|e|n|t| +0#0000000&@57
+|c+0#af5f00255&|l|a|s@1| +0#0000000&|C+0#00e0003&|:+0#0000000&| |.+0#00e0e07&@2| +0#0000000&@62
+|l+0#af5f00255&|a|m|b|d|a|:+0#0000000&| |.+0#00e0e07&@2| +0#0000000&@63
+@75
+|#+0#0000e05&| |A|n@1|o|t|a|t|i|o|n|s| +0#0000000&@61
+|n|u|m|b|e|r|s|:| |T|u|p|l|e|[|i+0#00e0e07&|n|t|,+0#0000000&| |.+0#00e0e07&@2|]+0#0000000&| @50
+@75
+|#+0#0000e05&| |D|o|c|t|e|s|t|s| +0#0000000&@64
+|"+0#e000002&@2|A| |d|o|c|t|e|s|t| +0#0000000&@62
+@75
+|>+0#e000e06&@2| |c+0#af5f00255&|l|a|s@1| +0#e000e06&|A+0#00e0003&|:+0#e000e06&| +0#0000000&@62
+|.+0#e000e06&@2| @4|d+0#af5f00255&|e|f| +0#e000e06&|_+0#00e0e07&@1|i|n|i|t|_@1|(+0#0000000&|s+0#00e0e07&|e|l|f|)+0#0000000&|:+0#e000e06&| +0#0000000&@47
+@57|1|,|1| @10|T|o|p| 
diff --git a/runtime/syntax/testdir/dumps/python_ellipsis_01.dump b/runtime/syntax/testdir/dumps/python_ellipsis_01.dump
new file mode 100644 (file)
index 0000000..f1495fd
--- /dev/null
@@ -0,0 +1,20 @@
+| +0&#ffffff0@74
+|#+0#0000e05&| |D|o|c|t|e|s|t|s| +0#0000000&@64
+|"+0#e000002&@2|A| |d|o|c|t|e|s|t| +0#0000000&@62
+@75
+|>+0#e000e06&@2| |c+0#af5f00255&|l|a|s@1| +0#e000e06&|A+0#00e0003&|:+0#e000e06&| +0#0000000&@62
+>.+0#e000e06&@2| @4|d+0#af5f00255&|e|f| +0#e000e06&|_+0#00e0e07&@1|i|n|i|t|_@1|(+0#0000000&|s+0#00e0e07&|e|l|f|)+0#0000000&|:+0#e000e06&| +0#0000000&@47
+|.+0#e000e06&@2| @12|.+0#00e0e07&@2| +0#0000000&@55
+|>+0#e000e06&@2| |c+0#af5f00255&|l|a|s@1| +0#e000e06&|B+0#00e0003&|:+0#e000e06&| |.+0#00e0e07&@2| +0#0000000&@58
+|>+0#e000e06&@2| |x| |=| |.+0#00e0e07&@2| +0#0000000&@63
+|>+0#e000e06&@2| |r+0#af5f00255&|a|i|s|e| +0#e000e06&|V+0#00e0003&|a|l|u|e|E|r@1|o|r|(+0#0000000&|'+0#e000002&|m|u|l|t|i|\+0#e000e06&|n| +0#e000002&@3|l|i|n|e|\+0#e000e06&|n|d+0#e000002&|e|t|a|i|l|'|)+0#0000000&| @27
+|T+0#e000e06&|r|a|c|e|b|a|c|k| |(|m|o|s|t| |r|e|c|e|n|t| |c|a|l@1| |l|a|s|t|)|:| +0#0000000&@40
+| +0#e000e06&@3|.+0#00e0e07&@2| +0#0000000&@67
+|V+0#00e0003&|a|l|u|e|E|r@1|o|r|:+0#e000e06&| |m|u|l|t|i| +0#0000000&@57
+| +0#e000e06&@3|l|i|n|e| +0#0000000&@66
+|d+0#e000e06&|e|t|a|i|l| +0#0000000&@68
+|>+0#e000e06&@2| |p+0#00e0e07&|r|i|n|t|(+0#e000e06&|l+0#00e0e07&|i|s|t|(+0#e000e06&|r+0#00e0e07&|a|n|g|e|(+0#0000000&|2+0#e000002&|0|)+0#0000000&|)+0#e000e06&@1| @1|#+0#0000e05&| |d|o|c|t|e|s|t|:| |+|E|L@1|I|P|S|I|S| +0#0000000&@26
+|[+0#e000e06&|0|,| |1|,| |.+0#00e0e07&@2|,+0#e000e06&| |1|8|,| |1|9|]| +0#0000000&@55
+|>+0#e000e06&@2| |e+0#00e0e07&|x|e|c|(+0#0000000&|s|)| +0#e000e06&@1|#+0#0000e05&|d|o|c|t|e|s|t|:| |+|E|L@1|I|P|S|I|S| +0#0000000&@42
+|-+0#e000e06&|3|.|2|1|7|1|6|0|3|4|2|7|2|e|-|0|.+0#00e0e07&@2|7+0#e000e06&| +0#0000000&@53
+@57|1|9|,|1| @9|5|4|%| 
diff --git a/runtime/syntax/testdir/dumps/python_ellipsis_02.dump b/runtime/syntax/testdir/dumps/python_ellipsis_02.dump
new file mode 100644 (file)
index 0000000..7a7417a
--- /dev/null
@@ -0,0 +1,20 @@
+|-+0#e000e06#ffffff0|3|.|2|1|7|1|6|0|3|4|2|7|2|e|-|0|.+0#00e0e07&@2|7+0#e000e06&| +0#0000000&@53
+|"+0#e000002&@2| +0#0000000&@71
+@75
+|c+0#af5f00255&|l|a|s@1| +0#0000000&|C+0#00e0003&|:+0#0000000&| @66
+@8|"+0#e000002&@2| +0#0000000&@63
+| +0#e000e06&@7>>@2| |c+0#af5f00255&|l|a|s@1| +0#e000e06&|C+0#00e0003&|:+0#e000e06&| +0#0000000&@54
+| +0#e000e06&@7|.@2| @4|d+0#af5f00255&|e|f| +0#e000e06&|_+0#00e0e07&@1|i|n|i|t|_@1|(+0#0000000&|s+0#00e0e07&|e|l|f|)+0#0000000&|:+0#e000e06&| +0#0000000&@39
+| +0#e000e06&@7|.@2| @12|.+0#00e0e07&@2| +0#0000000&@47
+| +0#e000e06&@7|"+0#e000002&@2| +0#0000000&@63
+@75
+|#+0#0000e05&| |N|u|m|p|y| +0#0000000&@67
+|x|[|.+0#00e0e07&@2|,+0#0000000&| |0+0#e000002&|]+0#0000000&| @65
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|3|7|,|2|-|9| @7|B|o|t| 
diff --git a/runtime/syntax/testdir/input/python_ellipsis.py b/runtime/syntax/testdir/input/python_ellipsis.py
new file mode 100644 (file)
index 0000000..5253283
--- /dev/null
@@ -0,0 +1,43 @@
+# Ellipsis Literal
+# https://docs.python.org/3/library/constants.html#Ellipsis
+
+# Placeholders
+...
+       ...
+x = ...
+y = ... # Comment
+class C: ...
+lambda: ...
+
+# Annotations
+numbers: Tuple[int, ...]
+
+# Doctests
+"""A doctest
+
+>>> class A:
+...     def __init__(self):
+...            ...
+>>> class B: ...
+>>> x = ...
+>>> raise ValueError('multi\n    line\ndetail')
+Traceback (most recent call last):
+    ...
+ValueError: multi
+    line
+detail
+>>> print(list(range(20)))  # doctest: +ELLIPSIS
+[0, 1, ..., 18, 19]
+>>> exec(s)  #doctest: +ELLIPSIS
+-3.21716034272e-0...7
+"""
+
+class C:
+       """
+       >>> class C:
+       ...     def __init__(self):
+       ...             ...
+       """
+
+# Numpy
+x[..., 0]