Bump version to 0.9.1.
[python/dscho.git] / Tools / idle / ColorDelegator.py
blob77edfe85854091eead9219a4518910302166b37e
1 import time
2 import string
3 import re
4 import keyword
5 from Tkinter import *
6 from Delegator import Delegator
7 from IdleConf import idleconf
9 #$ event <<toggle-auto-coloring>>
10 #$ win <Control-slash>
11 #$ unix <Control-slash>
13 __debug__ = 0
16 def any(name, list):
17 return "(?P<%s>" % name + string.join(list, "|") + ")"
19 def make_pat():
20 kw = r"\b" + any("KEYWORD", keyword.kwlist) + 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 + "|" + comment + "|" + string + "|" + any("SYNC", [r"\n"])
29 prog = re.compile(make_pat(), re.S)
30 idprog = re.compile(r"\s+(\w+)", re.S)
32 class ColorDelegator(Delegator):
34 def __init__(self):
35 Delegator.__init__(self)
36 self.prog = prog
37 self.idprog = idprog
39 def setdelegate(self, delegate):
40 if self.delegate is not None:
41 self.unbind("<<toggle-auto-coloring>>")
42 Delegator.setdelegate(self, delegate)
43 if delegate is not None:
44 self.config_colors()
45 self.bind("<<toggle-auto-coloring>>", self.toggle_colorize_event)
46 self.notify_range("1.0", "end")
48 def config_colors(self):
49 for tag, cnf in self.tagdefs.items():
50 if cnf:
51 apply(self.tag_configure, (tag,), cnf)
52 self.tag_raise('sel')
54 cconf = idleconf.getsection('Colors')
56 tagdefs = {
57 "COMMENT": cconf.getcolor("comment"),
58 "KEYWORD": cconf.getcolor("keyword"),
59 "STRING": cconf.getcolor("string"),
60 "DEFINITION": cconf.getcolor("definition"),
61 "SYNC": cconf.getcolor("sync"),
62 "TODO": cconf.getcolor("todo"),
63 "BREAK": cconf.getcolor("break"),
64 # The following is used by ReplaceDialog:
65 "hit": cconf.getcolor("hit"),
68 def insert(self, index, chars, tags=None):
69 index = self.index(index)
70 self.delegate.insert(index, chars, tags)
71 self.notify_range(index, index + "+%dc" % len(chars))
73 def delete(self, index1, index2=None):
74 index1 = self.index(index1)
75 self.delegate.delete(index1, index2)
76 self.notify_range(index1)
78 after_id = None
79 allow_colorizing = 1
80 colorizing = 0
82 def notify_range(self, index1, index2=None):
83 self.tag_add("TODO", index1, index2)
84 if self.after_id:
85 if __debug__: print "colorizing already scheduled"
86 return
87 if self.colorizing:
88 self.stop_colorizing = 1
89 if __debug__: print "stop colorizing"
90 if self.allow_colorizing:
91 if __debug__: print "schedule colorizing"
92 self.after_id = self.after(1, self.recolorize)
94 close_when_done = None # Window to be closed when done colorizing
96 def close(self, close_when_done=None):
97 if self.after_id:
98 after_id = self.after_id
99 self.after_id = None
100 if __debug__: print "cancel scheduled recolorizer"
101 self.after_cancel(after_id)
102 self.allow_colorizing = 0
103 self.stop_colorizing = 1
104 if close_when_done:
105 if not self.colorizing:
106 close_when_done.destroy()
107 else:
108 self.close_when_done = close_when_done
110 def toggle_colorize_event(self, event):
111 if self.after_id:
112 after_id = self.after_id
113 self.after_id = None
114 if __debug__: print "cancel scheduled recolorizer"
115 self.after_cancel(after_id)
116 if self.allow_colorizing and self.colorizing:
117 if __debug__: print "stop colorizing"
118 self.stop_colorizing = 1
119 self.allow_colorizing = not self.allow_colorizing
120 if self.allow_colorizing and not self.colorizing:
121 self.after_id = self.after(1, self.recolorize)
122 if __debug__:
123 print "auto colorizing turned", self.allow_colorizing and "on" or "off"
124 return "break"
126 def recolorize(self):
127 self.after_id = None
128 if not self.delegate:
129 if __debug__: print "no delegate"
130 return
131 if not self.allow_colorizing:
132 if __debug__: print "auto colorizing is off"
133 return
134 if self.colorizing:
135 if __debug__: print "already colorizing"
136 return
137 try:
138 self.stop_colorizing = 0
139 self.colorizing = 1
140 if __debug__: print "colorizing..."
141 t0 = time.clock()
142 self.recolorize_main()
143 t1 = time.clock()
144 if __debug__: print "%.3f seconds" % (t1-t0)
145 finally:
146 self.colorizing = 0
147 if self.allow_colorizing and self.tag_nextrange("TODO", "1.0"):
148 if __debug__: print "reschedule colorizing"
149 self.after_id = self.after(1, self.recolorize)
150 if self.close_when_done:
151 top = self.close_when_done
152 self.close_when_done = None
153 top.destroy()
155 def recolorize_main(self):
156 next = "1.0"
157 while 1:
158 item = self.tag_nextrange("TODO", next)
159 if not item:
160 break
161 head, tail = item
162 self.tag_remove("SYNC", head, tail)
163 item = self.tag_prevrange("SYNC", head)
164 if item:
165 head = item[1]
166 else:
167 head = "1.0"
169 chars = ""
170 next = head
171 lines_to_get = 1
172 ok = 0
173 while not ok:
174 mark = next
175 next = self.index(mark + "+%d lines linestart" %
176 lines_to_get)
177 lines_to_get = min(lines_to_get * 2, 100)
178 ok = "SYNC" in self.tag_names(next + "-1c")
179 line = self.get(mark, next)
180 ##print head, "get", mark, next, "->", `line`
181 if not line:
182 return
183 for tag in self.tagdefs.keys():
184 self.tag_remove(tag, mark, next)
185 chars = chars + line
186 m = self.prog.search(chars)
187 while m:
188 for key, value in m.groupdict().items():
189 if value:
190 a, b = m.span(key)
191 self.tag_add(key,
192 head + "+%dc" % a,
193 head + "+%dc" % b)
194 if value in ("def", "class"):
195 m1 = self.idprog.match(chars, b)
196 if m1:
197 a, b = m1.span(1)
198 self.tag_add("DEFINITION",
199 head + "+%dc" % a,
200 head + "+%dc" % b)
201 m = self.prog.search(chars, m.end())
202 if "SYNC" in self.tag_names(next + "-1c"):
203 head = next
204 chars = ""
205 else:
206 ok = 0
207 if not ok:
208 # We're in an inconsistent state, and the call to
209 # update may tell us to stop. It may also change
210 # the correct value for "next" (since this is a
211 # line.col string, not a true mark). So leave a
212 # crumb telling the next invocation to resume here
213 # in case update tells us to leave.
214 self.tag_add("TODO", next)
215 self.update()
216 if self.stop_colorizing:
217 if __debug__: print "colorizing stopped"
218 return
221 def main():
222 from Percolator import Percolator
223 root = Tk()
224 root.wm_protocol("WM_DELETE_WINDOW", root.quit)
225 text = Text(background="white")
226 text.pack(expand=1, fill="both")
227 text.focus_set()
228 p = Percolator(text)
229 d = ColorDelegator()
230 p.insertfilter(d)
231 root.mainloop()
233 if __name__ == "__main__":
234 main()