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