Commit | Line | Data |
---|---|---|
920dae64 AT |
1 | import time |
2 | import re | |
3 | import keyword | |
4 | import __builtin__ | |
5 | from Tkinter import * | |
6 | from Delegator import Delegator | |
7 | from configHandler import idleConf | |
8 | ||
9 | DEBUG = False | |
10 | ||
11 | def any(name, list): | |
12 | return "(?P<%s>" % name + "|".join(list) + ")" | |
13 | ||
14 | def make_pat(): | |
15 | kw = r"\b" + any("KEYWORD", keyword.kwlist) + r"\b" | |
16 | builtinlist = [str(name) for name in dir(__builtin__) | |
17 | if not name.startswith('_')] | |
18 | # self.file = file("file") : | |
19 | # 1st 'file' colorized normal, 2nd as builtin, 3rd as comment | |
20 | builtin = r"([^.'\"\\]\b|^)" + any("BUILTIN", builtinlist) + r"\b" | |
21 | comment = any("COMMENT", [r"#[^\n]*"]) | |
22 | sqstring = r"(\b[rR])?'[^'\\\n]*(\\.[^'\\\n]*)*'?" | |
23 | dqstring = r'(\b[rR])?"[^"\\\n]*(\\.[^"\\\n]*)*"?' | |
24 | sq3string = r"(\b[rR])?'''[^'\\]*((\\.|'(?!''))[^'\\]*)*(''')?" | |
25 | dq3string = r'(\b[rR])?"""[^"\\]*((\\.|"(?!""))[^"\\]*)*(""")?' | |
26 | string = any("STRING", [sq3string, dq3string, sqstring, dqstring]) | |
27 | return kw + "|" + builtin + "|" + comment + "|" + string +\ | |
28 | "|" + any("SYNC", [r"\n"]) | |
29 | ||
30 | prog = re.compile(make_pat(), re.S) | |
31 | idprog = re.compile(r"\s+(\w+)", re.S) | |
32 | asprog = re.compile(r".*?\b(as)\b", re.S) | |
33 | ||
34 | class ColorDelegator(Delegator): | |
35 | ||
36 | def __init__(self): | |
37 | Delegator.__init__(self) | |
38 | self.prog = prog | |
39 | self.idprog = idprog | |
40 | self.asprog = asprog | |
41 | self.LoadTagDefs() | |
42 | ||
43 | def setdelegate(self, delegate): | |
44 | if self.delegate is not None: | |
45 | self.unbind("<<toggle-auto-coloring>>") | |
46 | Delegator.setdelegate(self, delegate) | |
47 | if delegate is not None: | |
48 | self.config_colors() | |
49 | self.bind("<<toggle-auto-coloring>>", self.toggle_colorize_event) | |
50 | self.notify_range("1.0", "end") | |
51 | ||
52 | def config_colors(self): | |
53 | for tag, cnf in self.tagdefs.items(): | |
54 | if cnf: | |
55 | self.tag_configure(tag, **cnf) | |
56 | self.tag_raise('sel') | |
57 | ||
58 | def LoadTagDefs(self): | |
59 | theme = idleConf.GetOption('main','Theme','name') | |
60 | self.tagdefs = { | |
61 | "COMMENT": idleConf.GetHighlight(theme, "comment"), | |
62 | "KEYWORD": idleConf.GetHighlight(theme, "keyword"), | |
63 | "BUILTIN": idleConf.GetHighlight(theme, "builtin"), | |
64 | "STRING": idleConf.GetHighlight(theme, "string"), | |
65 | "DEFINITION": idleConf.GetHighlight(theme, "definition"), | |
66 | "SYNC": {'background':None,'foreground':None}, | |
67 | "TODO": {'background':None,'foreground':None}, | |
68 | "BREAK": idleConf.GetHighlight(theme, "break"), | |
69 | "ERROR": idleConf.GetHighlight(theme, "error"), | |
70 | # The following is used by ReplaceDialog: | |
71 | "hit": idleConf.GetHighlight(theme, "hit"), | |
72 | } | |
73 | ||
74 | if DEBUG: print 'tagdefs',self.tagdefs | |
75 | ||
76 | def insert(self, index, chars, tags=None): | |
77 | index = self.index(index) | |
78 | self.delegate.insert(index, chars, tags) | |
79 | self.notify_range(index, index + "+%dc" % len(chars)) | |
80 | ||
81 | def delete(self, index1, index2=None): | |
82 | index1 = self.index(index1) | |
83 | self.delegate.delete(index1, index2) | |
84 | self.notify_range(index1) | |
85 | ||
86 | after_id = None | |
87 | allow_colorizing = True | |
88 | colorizing = False | |
89 | ||
90 | def notify_range(self, index1, index2=None): | |
91 | self.tag_add("TODO", index1, index2) | |
92 | if self.after_id: | |
93 | if DEBUG: print "colorizing already scheduled" | |
94 | return | |
95 | if self.colorizing: | |
96 | self.stop_colorizing = True | |
97 | if DEBUG: print "stop colorizing" | |
98 | if self.allow_colorizing: | |
99 | if DEBUG: print "schedule colorizing" | |
100 | self.after_id = self.after(1, self.recolorize) | |
101 | ||
102 | close_when_done = None # Window to be closed when done colorizing | |
103 | ||
104 | def close(self, close_when_done=None): | |
105 | if self.after_id: | |
106 | after_id = self.after_id | |
107 | self.after_id = None | |
108 | if DEBUG: print "cancel scheduled recolorizer" | |
109 | self.after_cancel(after_id) | |
110 | self.allow_colorizing = False | |
111 | self.stop_colorizing = True | |
112 | if close_when_done: | |
113 | if not self.colorizing: | |
114 | close_when_done.destroy() | |
115 | else: | |
116 | self.close_when_done = close_when_done | |
117 | ||
118 | def toggle_colorize_event(self, event): | |
119 | if self.after_id: | |
120 | after_id = self.after_id | |
121 | self.after_id = None | |
122 | if DEBUG: print "cancel scheduled recolorizer" | |
123 | self.after_cancel(after_id) | |
124 | if self.allow_colorizing and self.colorizing: | |
125 | if DEBUG: print "stop colorizing" | |
126 | self.stop_colorizing = True | |
127 | self.allow_colorizing = not self.allow_colorizing | |
128 | if self.allow_colorizing and not self.colorizing: | |
129 | self.after_id = self.after(1, self.recolorize) | |
130 | if DEBUG: | |
131 | print "auto colorizing turned",\ | |
132 | self.allow_colorizing and "on" or "off" | |
133 | return "break" | |
134 | ||
135 | def recolorize(self): | |
136 | self.after_id = None | |
137 | if not self.delegate: | |
138 | if DEBUG: print "no delegate" | |
139 | return | |
140 | if not self.allow_colorizing: | |
141 | if DEBUG: print "auto colorizing is off" | |
142 | return | |
143 | if self.colorizing: | |
144 | if DEBUG: print "already colorizing" | |
145 | return | |
146 | try: | |
147 | self.stop_colorizing = False | |
148 | self.colorizing = True | |
149 | if DEBUG: print "colorizing..." | |
150 | t0 = time.clock() | |
151 | self.recolorize_main() | |
152 | t1 = time.clock() | |
153 | if DEBUG: print "%.3f seconds" % (t1-t0) | |
154 | finally: | |
155 | self.colorizing = False | |
156 | if self.allow_colorizing and self.tag_nextrange("TODO", "1.0"): | |
157 | if DEBUG: print "reschedule colorizing" | |
158 | self.after_id = self.after(1, self.recolorize) | |
159 | if self.close_when_done: | |
160 | top = self.close_when_done | |
161 | self.close_when_done = None | |
162 | top.destroy() | |
163 | ||
164 | def recolorize_main(self): | |
165 | next = "1.0" | |
166 | while True: | |
167 | item = self.tag_nextrange("TODO", next) | |
168 | if not item: | |
169 | break | |
170 | head, tail = item | |
171 | self.tag_remove("SYNC", head, tail) | |
172 | item = self.tag_prevrange("SYNC", head) | |
173 | if item: | |
174 | head = item[1] | |
175 | else: | |
176 | head = "1.0" | |
177 | ||
178 | chars = "" | |
179 | next = head | |
180 | lines_to_get = 1 | |
181 | ok = False | |
182 | while not ok: | |
183 | mark = next | |
184 | next = self.index(mark + "+%d lines linestart" % | |
185 | lines_to_get) | |
186 | lines_to_get = min(lines_to_get * 2, 100) | |
187 | ok = "SYNC" in self.tag_names(next + "-1c") | |
188 | line = self.get(mark, next) | |
189 | ##print head, "get", mark, next, "->", repr(line) | |
190 | if not line: | |
191 | return | |
192 | for tag in self.tagdefs.keys(): | |
193 | self.tag_remove(tag, mark, next) | |
194 | chars = chars + line | |
195 | m = self.prog.search(chars) | |
196 | while m: | |
197 | for key, value in m.groupdict().items(): | |
198 | if value: | |
199 | a, b = m.span(key) | |
200 | self.tag_add(key, | |
201 | head + "+%dc" % a, | |
202 | head + "+%dc" % b) | |
203 | if value in ("def", "class"): | |
204 | m1 = self.idprog.match(chars, b) | |
205 | if m1: | |
206 | a, b = m1.span(1) | |
207 | self.tag_add("DEFINITION", | |
208 | head + "+%dc" % a, | |
209 | head + "+%dc" % b) | |
210 | elif value == "import": | |
211 | # color all the "as" words on same line; | |
212 | # cheap approximation to the truth | |
213 | while True: | |
214 | m1 = self.asprog.match(chars, b) | |
215 | if not m1: | |
216 | break | |
217 | a, b = m1.span(1) | |
218 | self.tag_add("KEYWORD", | |
219 | head + "+%dc" % a, | |
220 | head + "+%dc" % b) | |
221 | m = self.prog.search(chars, m.end()) | |
222 | if "SYNC" in self.tag_names(next + "-1c"): | |
223 | head = next | |
224 | chars = "" | |
225 | else: | |
226 | ok = False | |
227 | if not ok: | |
228 | # We're in an inconsistent state, and the call to | |
229 | # update may tell us to stop. It may also change | |
230 | # the correct value for "next" (since this is a | |
231 | # line.col string, not a true mark). So leave a | |
232 | # crumb telling the next invocation to resume here | |
233 | # in case update tells us to leave. | |
234 | self.tag_add("TODO", next) | |
235 | self.update() | |
236 | if self.stop_colorizing: | |
237 | if DEBUG: print "colorizing stopped" | |
238 | return | |
239 | ||
240 | ||
241 | def main(): | |
242 | from Percolator import Percolator | |
243 | root = Tk() | |
244 | root.wm_protocol("WM_DELETE_WINDOW", root.quit) | |
245 | text = Text(background="white") | |
246 | text.pack(expand=1, fill="both") | |
247 | text.focus_set() | |
248 | p = Percolator(text) | |
249 | d = ColorDelegator() | |
250 | p.insertfilter(d) | |
251 | root.mainloop() | |
252 | ||
253 | if __name__ == "__main__": | |
254 | main() |