Ditched '_find_SET()', since it was a no-value-added wrapper around
[python/dscho.git] / Tools / idle / ColorDelegator.py
blob68c2d33321c7127547aa4a15db25a1dd93f4e62a
1 import time
2 import string
3 import re
4 import keyword
5 from Tkinter import *
6 from Delegator import Delegator
7 import IdlePrefs
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 cprefs = IdlePrefs.ColorPrefs()
56 tagdefs = {
57 "COMMENT": {"foreground": cprefs.CComment[0],
58 "background": cprefs.CComment[1]},
59 "KEYWORD": {"foreground": cprefs.CKeyword[0],
60 "background": cprefs.CKeyword[1]},
61 "STRING": {"foreground": cprefs.CString[0],
62 "background": cprefs.CString[1]},
63 "DEFINITION": {"foreground": cprefs.CDefinition[0],
64 "background": cprefs.CDefinition[1]},
66 "SYNC": {"background": cprefs.CSync[0],
67 "background": cprefs.CSync[1]},
68 "TODO": {"background": cprefs.CTodo[0],
69 "background": cprefs.CTodo[1]},
71 "BREAK": {"background": cprefs.CBreak[0],
72 "background": cprefs.CBreak[1]},
74 # The following is used by ReplaceDialog:
75 "hit": {"foreground": cprefs.CHit[0],
76 "background": cprefs.CHit[1]},
79 def insert(self, index, chars, tags=None):
80 index = self.index(index)
81 self.delegate.insert(index, chars, tags)
82 self.notify_range(index, index + "+%dc" % len(chars))
84 def delete(self, index1, index2=None):
85 index1 = self.index(index1)
86 self.delegate.delete(index1, index2)
87 self.notify_range(index1)
89 after_id = None
90 allow_colorizing = 1
91 colorizing = 0
93 def notify_range(self, index1, index2=None):
94 self.tag_add("TODO", index1, index2)
95 if self.after_id:
96 if __debug__: print "colorizing already scheduled"
97 return
98 if self.colorizing:
99 self.stop_colorizing = 1
100 if __debug__: print "stop colorizing"
101 if self.allow_colorizing:
102 if __debug__: print "schedule colorizing"
103 self.after_id = self.after(1, self.recolorize)
105 close_when_done = None # Window to be closed when done colorizing
107 def close(self, close_when_done=None):
108 if self.after_id:
109 after_id = self.after_id
110 self.after_id = None
111 if __debug__: print "cancel scheduled recolorizer"
112 self.after_cancel(after_id)
113 self.allow_colorizing = 0
114 self.stop_colorizing = 1
115 if close_when_done:
116 if not self.colorizing:
117 close_when_done.destroy()
118 else:
119 self.close_when_done = close_when_done
121 def toggle_colorize_event(self, event):
122 if self.after_id:
123 after_id = self.after_id
124 self.after_id = None
125 if __debug__: print "cancel scheduled recolorizer"
126 self.after_cancel(after_id)
127 if self.allow_colorizing and self.colorizing:
128 if __debug__: print "stop colorizing"
129 self.stop_colorizing = 1
130 self.allow_colorizing = not self.allow_colorizing
131 if self.allow_colorizing and not self.colorizing:
132 self.after_id = self.after(1, self.recolorize)
133 if __debug__:
134 print "auto colorizing turned", self.allow_colorizing and "on" or "off"
135 return "break"
137 def recolorize(self):
138 self.after_id = None
139 if not self.delegate:
140 if __debug__: print "no delegate"
141 return
142 if not self.allow_colorizing:
143 if __debug__: print "auto colorizing is off"
144 return
145 if self.colorizing:
146 if __debug__: print "already colorizing"
147 return
148 try:
149 self.stop_colorizing = 0
150 self.colorizing = 1
151 if __debug__: print "colorizing..."
152 t0 = time.clock()
153 self.recolorize_main()
154 t1 = time.clock()
155 if __debug__: print "%.3f seconds" % (t1-t0)
156 finally:
157 self.colorizing = 0
158 if self.allow_colorizing and self.tag_nextrange("TODO", "1.0"):
159 if __debug__: print "reschedule colorizing"
160 self.after_id = self.after(1, self.recolorize)
161 if self.close_when_done:
162 top = self.close_when_done
163 self.close_when_done = None
164 top.destroy()
166 def recolorize_main(self):
167 next = "1.0"
168 while 1:
169 item = self.tag_nextrange("TODO", next)
170 if not item:
171 break
172 head, tail = item
173 self.tag_remove("SYNC", head, tail)
174 item = self.tag_prevrange("SYNC", head)
175 if item:
176 head = item[1]
177 else:
178 head = "1.0"
180 chars = ""
181 next = head
182 lines_to_get = 1
183 ok = 0
184 while not ok:
185 mark = next
186 next = self.index(mark + "+%d lines linestart" %
187 lines_to_get)
188 lines_to_get = min(lines_to_get * 2, 100)
189 ok = "SYNC" in self.tag_names(next + "-1c")
190 line = self.get(mark, next)
191 ##print head, "get", mark, next, "->", `line`
192 if not line:
193 return
194 for tag in self.tagdefs.keys():
195 self.tag_remove(tag, mark, next)
196 chars = chars + line
197 m = self.prog.search(chars)
198 while m:
199 for key, value in m.groupdict().items():
200 if value:
201 a, b = m.span(key)
202 self.tag_add(key,
203 head + "+%dc" % a,
204 head + "+%dc" % b)
205 if value in ("def", "class"):
206 m1 = self.idprog.match(chars, b)
207 if m1:
208 a, b = m1.span(1)
209 self.tag_add("DEFINITION",
210 head + "+%dc" % a,
211 head + "+%dc" % b)
212 m = self.prog.search(chars, m.end())
213 if "SYNC" in self.tag_names(next + "-1c"):
214 head = next
215 chars = ""
216 else:
217 ok = 0
218 if not ok:
219 # We're in an inconsistent state, and the call to
220 # update may tell us to stop. It may also change
221 # the correct value for "next" (since this is a
222 # line.col string, not a true mark). So leave a
223 # crumb telling the next invocation to resume here
224 # in case update tells us to leave.
225 self.tag_add("TODO", next)
226 self.update()
227 if self.stop_colorizing:
228 if __debug__: print "colorizing stopped"
229 return
232 def main():
233 from Percolator import Percolator
234 root = Tk()
235 root.wm_protocol("WM_DELETE_WINDOW", root.quit)
236 text = Text(background="white")
237 text.pack(expand=1, fill="both")
238 text.focus_set()
239 p = Percolator(text)
240 d = ColorDelegator()
241 p.insertfilter(d)
242 root.mainloop()
244 if __name__ == "__main__":
245 main()