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