Home | History | Annotate | Download | only in Vim
      1 from __future__ import with_statement
      2 
      3 import keyword
      4 import exceptions
      5 import __builtin__
      6 from string import Template
      7 from sys import subversion
      8 
      9 comment_header = '''" Auto-generated Vim syntax file for Python (%s: r%s).
     10 "
     11 " To use: copy or symlink to ~/.vim/syntax/python.vim'''
     12 
     13 statement_header = """
     14 if exists("b:current_syntax")
     15   finish
     16 endif"""
     17 
     18 statement_footer = '''
     19 " Uncomment the 'minlines' statement line and comment out the 'maxlines'
     20 " statement line; changes behaviour to look at least 2000 lines previously for
     21 " syntax matches instead of at most 200 lines
     22 syn sync match pythonSync grouphere NONE "):$"
     23 syn sync maxlines=200
     24 "syn sync minlines=2000
     25 
     26 let b:current_syntax = "python"'''
     27 
     28 looping = ('for', 'while')
     29 conditionals = ('if', 'elif', 'else')
     30 boolean_ops = ('and', 'in', 'is', 'not', 'or')
     31 import_stmts = ('import', 'from')
     32 object_defs = ('def', 'class')
     33 
     34 exception_names = sorted(exc for exc in dir(exceptions)
     35                                 if not exc.startswith('__'))
     36 
     37 # Need to include functions that start with '__' (e.g., __import__), but
     38 # nothing that comes with modules (e.g., __name__), so just exclude anything in
     39 # the 'exceptions' module since we want to ignore exceptions *and* what any
     40 # module would have
     41 builtin_names = sorted(builtin for builtin in dir(__builtin__)
     42                             if builtin not in dir(exceptions))
     43 
     44 escapes = (r'+\\[abfnrtv\'"\\]+', r'"\\\o\{1,3}"', r'"\\x\x\{2}"',
     45             r'"\(\\u\x\{4}\|\\U\x\{8}\)"', r'"\\$"')
     46 
     47 todos = ("TODO", "FIXME", "XXX")
     48 
     49 # XXX codify?
     50 numbers = (r'"\<0x\x\+[Ll]\=\>"', r'"\<\d\+[LljJ]\=\>"',
     51             '"\.\d\+\([eE][+-]\=\d\+\)\=[jJ]\=\>"',
     52             '"\<\d\+\.\([eE][+-]\=\d\+\)\=[jJ]\=\>"',
     53             '"\<\d\+\.\d\+\([eE][+-]\=\d\+\)\=[jJ]\=\>"')
     54 
     55 contained = lambda x: "%s contained" % x
     56 
     57 def str_regexes():
     58     """Generator to yield various combinations of strings regexes"""
     59     regex_template = Template('matchgroup=Normal ' +
     60                                 'start=+[uU]\=${raw}${sep}+ ' +
     61                                 'end=+${sep}+ ' +
     62                                 '${skip} ' +
     63                                 '${contains}')
     64     skip_regex = Template(r'skip=+\\\\\|\\${sep}+')
     65     for raw in ('', '[rR]'):
     66         for separator in ("'", '"', '"""', "'''"):
     67             if len(separator) == 1:
     68                 skip = skip_regex.substitute(sep=separator)
     69             else:
     70                 skip = ''
     71             contains = 'contains=pythonEscape' if not raw else ''
     72             yield regex_template.substitute(raw=raw, sep=separator, skip=skip,
     73                                             contains = contains)
     74 
     75 space_errors = (r'excludenl "\S\s\+$"ms=s+1', r'" \+\t"', r'"\t\+ "')
     76 
     77 statements = (
     78                 ('',
     79                     # XXX Might need to change pythonStatement since have
     80                     # specific Repeat, Conditional, Operator, etc. for 'while',
     81                     # etc.
     82                     [("Statement", "pythonStatement", "keyword",
     83                         (kw for kw in keyword.kwlist
     84                             if kw not in (looping + conditionals + boolean_ops +
     85                                         import_stmts + object_defs))
     86                       ),
     87                      ("Statement", "pythonStatement", "keyword",
     88                          (' '.join(object_defs) +
     89                              ' nextgroup=pythonFunction skipwhite')),
     90                      ("Function","pythonFunction", "match",
     91                          contained('"[a-zA-Z_][a-zA-Z0-9_]*"')),
     92                      ("Repeat", "pythonRepeat", "keyword", looping),
     93                      ("Conditional", "pythonConditional", "keyword",
     94                          conditionals),
     95                      ("Operator", "pythonOperator", "keyword", boolean_ops),
     96                      ("PreCondit", "pythonPreCondit", "keyword", import_stmts),
     97                      ("Comment", "pythonComment", "match",
     98                          '"#.*$" contains=pythonTodo'),
     99                      ("Todo", "pythonTodo", "keyword",
    100                          contained(' '.join(todos))),
    101                      ("String", "pythonString", "region", str_regexes()),
    102                      ("Special", "pythonEscape", "match",
    103                          (contained(esc) for esc in escapes
    104                              if not '$' in esc)),
    105                      ("Special", "pythonEscape", "match", r'"\\$"'),
    106                     ]
    107                 ),
    108                 ("python_highlight_numbers",
    109                     [("Number", "pythonNumber", "match", numbers)]
    110                 ),
    111                 ("python_highlight_builtins",
    112                     [("Function", "pythonBuiltin", "keyword", builtin_names)]
    113                 ),
    114                 ("python_highlight_exceptions",
    115                     [("Exception", "pythonException", "keyword",
    116                         exception_names)]
    117                 ),
    118                 ("python_highlight_space_errors",
    119                     [("Error", "pythonSpaceError", "match",
    120                         ("display " + err for err in space_errors))]
    121                 )
    122              )
    123 
    124 def syn_prefix(type_, kind):
    125     return 'syn %s %s    ' % (type_, kind)
    126 
    127 def fill_stmt(iterable, fill_len):
    128     """Yield a string that fills at most fill_len characters with strings
    129     returned by 'iterable' and separated by a space"""
    130     # Deal with trailing char to handle ' '.join() calculation
    131     fill_len += 1
    132     overflow = None
    133     it = iter(iterable)
    134     while True:
    135         buffer_ = []
    136         total_len = 0
    137         if overflow:
    138             buffer_.append(overflow)
    139             total_len += len(overflow) + 1
    140             overflow = None
    141         while total_len < fill_len:
    142             try:
    143                 new_item = it.next()
    144                 buffer_.append(new_item)
    145                 total_len += len(new_item) + 1
    146             except StopIteration:
    147                 if buffer_:
    148                     break
    149                 if overflow:
    150                     yield overflow
    151                 return
    152         if total_len > fill_len:
    153             overflow = buffer_.pop()
    154             total_len -= len(overflow) - 1
    155         ret = ' '.join(buffer_)
    156         assert len(ret) <= fill_len
    157         yield ret
    158 
    159 FILL = 80
    160 
    161 def main(file_path):
    162     with open(file_path, 'w') as FILE:
    163         # Comment for file
    164         print>>FILE, comment_header % subversion[1:]
    165         print>>FILE, ''
    166         # Statements at start of file
    167         print>>FILE, statement_header
    168         print>>FILE, ''
    169         # Generate case for python_highlight_all
    170         print>>FILE, 'if exists("python_highlight_all")'
    171         for statement_var, statement_parts in statements:
    172             if statement_var:
    173                 print>>FILE, '  let %s = 1' % statement_var
    174         else:
    175             print>>FILE, 'endif'
    176             print>>FILE, ''
    177         # Generate Python groups
    178         for statement_var, statement_parts in statements:
    179             if statement_var:
    180                 print>>FILE, 'if exists("%s")' % statement_var
    181                 indent = '  '
    182             else:
    183                 indent = ''
    184             for colour_group, group, type_, arguments in statement_parts:
    185                 if not isinstance(arguments, basestring):
    186                     prefix = syn_prefix(type_, group)
    187                     if type_ == 'keyword':
    188                         stmt_iter = fill_stmt(arguments,
    189                                             FILL - len(prefix) - len(indent))
    190                         try:
    191                             while True:
    192                                 print>>FILE, indent + prefix + stmt_iter.next()
    193                         except StopIteration:
    194                             print>>FILE, ''
    195                     else:
    196                         for argument in arguments:
    197                             print>>FILE, indent + prefix + argument
    198                         else:
    199                             print>>FILE, ''
    200 
    201                 else:
    202                     print>>FILE, indent + syn_prefix(type_, group) + arguments
    203                     print>>FILE, ''
    204             else:
    205                 if statement_var:
    206                     print>>FILE, 'endif'
    207                     print>>FILE, ''
    208             print>>FILE, ''
    209         # Associating Python group with Vim colour group
    210         for statement_var, statement_parts in statements:
    211             if statement_var:
    212                 print>>FILE, '  if exists("%s")' % statement_var
    213                 indent = '    '
    214             else:
    215                 indent = '  '
    216             for colour_group, group, type_, arguments in statement_parts:
    217                 print>>FILE, (indent + "hi def link %s %s" %
    218                                 (group, colour_group))
    219             else:
    220                 if statement_var:
    221                     print>>FILE, '  endif'
    222                 print>>FILE, ''
    223         # Statements at the end of the file
    224         print>>FILE, statement_footer
    225 
    226 if __name__ == '__main__':
    227     main("python.vim")
    228