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