blob: 57a30e49c7d4f630728e82c9ead4c854ac05d284 [file] [log] [blame]
Brett Cannon20e192b2006-03-01 20:53:08 +00001from __future__ import with_statement
2
Brett Cannona4fe1822006-02-25 14:52:53 +00003import keyword
4import exceptions
5import __builtin__
6from string import Template
Brett Cannon8fff20f2008-01-29 04:18:04 +00007from sys import subversion
Brett Cannona4fe1822006-02-25 14:52:53 +00008
Brett Cannon8fff20f2008-01-29 04:18:04 +00009comment_header = '''" Auto-generated Vim syntax file for Python (%s: r%s).
Brett Cannona4fe1822006-02-25 14:52:53 +000010"
Brett Cannonc8939d22006-09-20 19:28:35 +000011" To use: copy or symlink to ~/.vim/syntax/python.vim'''
Brett Cannona4fe1822006-02-25 14:52:53 +000012
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
Brett Cannonc8939d22006-09-20 19:28:35 +000034exception_names = sorted(exc for exc in dir(exceptions)
Brett Cannona4fe1822006-02-25 14:52:53 +000035 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
Brett Cannonc8939d22006-09-20 19:28:35 +000041builtin_names = sorted(builtin for builtin in dir(__builtin__)
Brett Cannona4fe1822006-02-25 14:52:53 +000042 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 = ''
Brett Cannonacde7342006-03-01 04:28:00 +000071 contains = 'contains=pythonEscape' if not raw else ''
Brett Cannona4fe1822006-02-25 14:52:53 +000072 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
Tim Petersd6e7e732006-02-26 04:21:50 +0000131 fill_len += 1
Brett Cannona4fe1822006-02-25 14:52:53 +0000132 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
Brett Cannon20e192b2006-03-01 20:53:08 +0000149 if overflow:
150 yield overflow
151 return
Brett Cannona4fe1822006-02-25 14:52:53 +0000152 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):
Brett Cannon20e192b2006-03-01 20:53:08 +0000162 with open(file_path, 'w') as FILE:
Brett Cannona4fe1822006-02-25 14:52:53 +0000163 # Comment for file
Brett Cannon8fff20f2008-01-29 04:18:04 +0000164 print>>FILE, comment_header % subversion[1:]
Brett Cannona4fe1822006-02-25 14:52:53 +0000165 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
Brett Cannona4fe1822006-02-25 14:52:53 +0000225
226if __name__ == '__main__':
227 main("python.vim")