|
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") |