blob: 57a30e49c7d4f630728e82c9ead4c854ac05d284 [file] [log] [blame]
from __future__ import with_statement
import keyword
import exceptions
import __builtin__
from string import Template
from sys import subversion
comment_header = '''" Auto-generated Vim syntax file for Python (%s: r%s).
"
" To use: copy or symlink to ~/.vim/syntax/python.vim'''
statement_header = """
if exists("b:current_syntax")
finish
endif"""
statement_footer = '''
" Uncomment the 'minlines' statement line and comment out the 'maxlines'
" statement line; changes behaviour to look at least 2000 lines previously for
" syntax matches instead of at most 200 lines
syn sync match pythonSync grouphere NONE "):$"
syn sync maxlines=200
"syn sync minlines=2000
let b:current_syntax = "python"'''
looping = ('for', 'while')
conditionals = ('if', 'elif', 'else')
boolean_ops = ('and', 'in', 'is', 'not', 'or')
import_stmts = ('import', 'from')
object_defs = ('def', 'class')
exception_names = sorted(exc for exc in dir(exceptions)
if not exc.startswith('__'))
# Need to include functions that start with '__' (e.g., __import__), but
# nothing that comes with modules (e.g., __name__), so just exclude anything in
# the 'exceptions' module since we want to ignore exceptions *and* what any
# module would have
builtin_names = sorted(builtin for builtin in dir(__builtin__)
if builtin not in dir(exceptions))
escapes = (r'+\\[abfnrtv\'"\\]+', r'"\\\o\{1,3}"', r'"\\x\x\{2}"',
r'"\(\\u\x\{4}\|\\U\x\{8}\)"', r'"\\$"')
todos = ("TODO", "FIXME", "XXX")
# XXX codify?
numbers = (r'"\<0x\x\+[Ll]\=\>"', r'"\<\d\+[LljJ]\=\>"',
'"\.\d\+\([eE][+-]\=\d\+\)\=[jJ]\=\>"',
'"\<\d\+\.\([eE][+-]\=\d\+\)\=[jJ]\=\>"',
'"\<\d\+\.\d\+\([eE][+-]\=\d\+\)\=[jJ]\=\>"')
contained = lambda x: "%s contained" % x
def str_regexes():
"""Generator to yield various combinations of strings regexes"""
regex_template = Template('matchgroup=Normal ' +
'start=+[uU]\=${raw}${sep}+ ' +
'end=+${sep}+ ' +
'${skip} ' +
'${contains}')
skip_regex = Template(r'skip=+\\\\\|\\${sep}+')
for raw in ('', '[rR]'):
for separator in ("'", '"', '"""', "'''"):
if len(separator) == 1:
skip = skip_regex.substitute(sep=separator)
else:
skip = ''
contains = 'contains=pythonEscape' if not raw else ''
yield regex_template.substitute(raw=raw, sep=separator, skip=skip,
contains = contains)
space_errors = (r'excludenl "\S\s\+$"ms=s+1', r'" \+\t"', r'"\t\+ "')
statements = (
('',
# XXX Might need to change pythonStatement since have
# specific Repeat, Conditional, Operator, etc. for 'while',
# etc.
[("Statement", "pythonStatement", "keyword",
(kw for kw in keyword.kwlist
if kw not in (looping + conditionals + boolean_ops +
import_stmts + object_defs))
),
("Statement", "pythonStatement", "keyword",
(' '.join(object_defs) +
' nextgroup=pythonFunction skipwhite')),
("Function","pythonFunction", "match",
contained('"[a-zA-Z_][a-zA-Z0-9_]*"')),
("Repeat", "pythonRepeat", "keyword", looping),
("Conditional", "pythonConditional", "keyword",
conditionals),
("Operator", "pythonOperator", "keyword", boolean_ops),
("PreCondit", "pythonPreCondit", "keyword", import_stmts),
("Comment", "pythonComment", "match",
'"#.*$" contains=pythonTodo'),
("Todo", "pythonTodo", "keyword",
contained(' '.join(todos))),
("String", "pythonString", "region", str_regexes()),
("Special", "pythonEscape", "match",
(contained(esc) for esc in escapes
if not '$' in esc)),
("Special", "pythonEscape", "match", r'"\\$"'),
]
),
("python_highlight_numbers",
[("Number", "pythonNumber", "match", numbers)]
),
("python_highlight_builtins",
[("Function", "pythonBuiltin", "keyword", builtin_names)]
),
("python_highlight_exceptions",
[("Exception", "pythonException", "keyword",
exception_names)]
),
("python_highlight_space_errors",
[("Error", "pythonSpaceError", "match",
("display " + err for err in space_errors))]
)
)
def syn_prefix(type_, kind):
return 'syn %s %s ' % (type_, kind)
def fill_stmt(iterable, fill_len):
"""Yield a string that fills at most fill_len characters with strings
returned by 'iterable' and separated by a space"""
# Deal with trailing char to handle ' '.join() calculation
fill_len += 1
overflow = None
it = iter(iterable)
while True:
buffer_ = []
total_len = 0
if overflow:
buffer_.append(overflow)
total_len += len(overflow) + 1
overflow = None
while total_len < fill_len:
try:
new_item = it.next()
buffer_.append(new_item)
total_len += len(new_item) + 1
except StopIteration:
if buffer_:
break
if overflow:
yield overflow
return
if total_len > fill_len:
overflow = buffer_.pop()
total_len -= len(overflow) - 1
ret = ' '.join(buffer_)
assert len(ret) <= fill_len
yield ret
FILL = 80
def main(file_path):
with open(file_path, 'w') as FILE:
# Comment for file
print>>FILE, comment_header % subversion[1:]
print>>FILE, ''
# Statements at start of file
print>>FILE, statement_header
print>>FILE, ''
# Generate case for python_highlight_all
print>>FILE, 'if exists("python_highlight_all")'
for statement_var, statement_parts in statements:
if statement_var:
print>>FILE, ' let %s = 1' % statement_var
else:
print>>FILE, 'endif'
print>>FILE, ''
# Generate Python groups
for statement_var, statement_parts in statements:
if statement_var:
print>>FILE, 'if exists("%s")' % statement_var
indent = ' '
else:
indent = ''
for colour_group, group, type_, arguments in statement_parts:
if not isinstance(arguments, basestring):
prefix = syn_prefix(type_, group)
if type_ == 'keyword':
stmt_iter = fill_stmt(arguments,
FILL - len(prefix) - len(indent))
try:
while True:
print>>FILE, indent + prefix + stmt_iter.next()
except StopIteration:
print>>FILE, ''
else:
for argument in arguments:
print>>FILE, indent + prefix + argument
else:
print>>FILE, ''
else:
print>>FILE, indent + syn_prefix(type_, group) + arguments
print>>FILE, ''
else:
if statement_var:
print>>FILE, 'endif'
print>>FILE, ''
print>>FILE, ''
# Associating Python group with Vim colour group
for statement_var, statement_parts in statements:
if statement_var:
print>>FILE, ' if exists("%s")' % statement_var
indent = ' '
else:
indent = ' '
for colour_group, group, type_, arguments in statement_parts:
print>>FILE, (indent + "hi def link %s %s" %
(group, colour_group))
else:
if statement_var:
print>>FILE, ' endif'
print>>FILE, ''
# Statements at the end of the file
print>>FILE, statement_footer
if __name__ == '__main__':
main("python.vim")