• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from __future__ import with_statement
2
3import keyword
4import exceptions
5import __builtin__
6from string import Template
7from sys import subversion
8
9comment_header = '''" Auto-generated Vim syntax file for Python (%s: r%s).
10"
11" To use: copy or symlink to ~/.vim/syntax/python.vim'''
12
13statement_header = """
14if exists("b:current_syntax")
15  finish
16endif"""
17
18statement_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
22syn sync match pythonSync grouphere NONE "):$"
23syn sync maxlines=200
24"syn sync minlines=2000
25
26let b:current_syntax = "python"'''
27
28looping = ('for', 'while')
29conditionals = ('if', 'elif', 'else')
30boolean_ops = ('and', 'in', 'is', 'not', 'or')
31import_stmts = ('import', 'from')
32object_defs = ('def', 'class')
33
34exception_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
41builtin_names = sorted(builtin for builtin in dir(__builtin__)
42                            if builtin not in dir(exceptions))
43
44escapes = (r'+\\[abfnrtv\'"\\]+', r'"\\\o\{1,3}"', r'"\\x\x\{2}"',
45            r'"\(\\u\x\{4}\|\\U\x\{8}\)"', r'"\\$"')
46
47todos = ("TODO", "FIXME", "XXX")
48
49# XXX codify?
50numbers = (r'"\<0x\x\+[Ll]\=\>"', r'"\<\d\+[LljJ]\=\>"',
51            '"\.\d\+\([eE][+-]\=\d\+\)\=[jJ]\=\>"',
52            '"\<\d\+\.\([eE][+-]\=\d\+\)\=[jJ]\=\>"',
53            '"\<\d\+\.\d\+\([eE][+-]\=\d\+\)\=[jJ]\=\>"')
54
55contained = lambda x: "%s contained" % x
56
57def 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
75space_errors = (r'excludenl "\S\s\+$"ms=s+1', r'" \+\t"', r'"\t\+ "')
76
77statements = (
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
124def syn_prefix(type_, kind):
125    return 'syn %s %s    ' % (type_, kind)
126
127def 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
159FILL = 80
160
161def 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
226if __name__ == '__main__':
227    main("python.vim")
228