1import time 2import re 3import keyword 4import __builtin__ 5from idlelib.Delegator import Delegator 6from idlelib.configHandler import idleConf 7 8DEBUG = False 9 10def any(name, alternates): 11 "Return a named group pattern matching list of alternates." 12 return "(?P<%s>" % name + "|".join(alternates) + ")" 13 14def make_pat(): 15 kw = r"\b" + any("KEYWORD", keyword.kwlist) + r"\b" 16 builtinlist = [str(name) for name in dir(__builtin__) 17 if not name.startswith('_')] 18 # We don't know whether "print" is a function or a keyword, 19 # so we always treat is as a keyword (the most common case). 20 builtinlist.remove('print') 21 # self.file = file("file") : 22 # 1st 'file' colorized normal, 2nd as builtin, 3rd as string 23 builtin = r"([^.'\"\\#]\b|^)" + any("BUILTIN", builtinlist) + r"\b" 24 comment = any("COMMENT", [r"#[^\n]*"]) 25 stringprefix = r"(\br|u|ur|R|U|UR|Ur|uR|b|B|br|Br|bR|BR)?" 26 sqstring = stringprefix + r"'[^'\\\n]*(\\.[^'\\\n]*)*'?" 27 dqstring = stringprefix + r'"[^"\\\n]*(\\.[^"\\\n]*)*"?' 28 sq3string = stringprefix + r"'''[^'\\]*((\\.|'(?!''))[^'\\]*)*(''')?" 29 dq3string = stringprefix + r'"""[^"\\]*((\\.|"(?!""))[^"\\]*)*(""")?' 30 string = any("STRING", [sq3string, dq3string, sqstring, dqstring]) 31 return kw + "|" + builtin + "|" + comment + "|" + string +\ 32 "|" + any("SYNC", [r"\n"]) 33 34prog = re.compile(make_pat(), re.S) 35idprog = re.compile(r"\s+(\w+)", re.S) 36 37class ColorDelegator(Delegator): 38 39 def __init__(self): 40 Delegator.__init__(self) 41 self.prog = prog 42 self.idprog = idprog 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.CurrentTheme() 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 "ERROR": idleConf.GetHighlight(theme, "error"), 75 # The following is used by ReplaceDialog: 76 "hit": idleConf.GetHighlight(theme, "hit"), 77 } 78 79 if DEBUG: print 'tagdefs',self.tagdefs 80 81 def insert(self, index, chars, tags=None): 82 index = self.index(index) 83 self.delegate.insert(index, chars, tags) 84 self.notify_range(index, index + "+%dc" % len(chars)) 85 86 def delete(self, index1, index2=None): 87 index1 = self.index(index1) 88 self.delegate.delete(index1, index2) 89 self.notify_range(index1) 90 91 after_id = None 92 allow_colorizing = True 93 colorizing = False 94 95 def notify_range(self, index1, index2=None): 96 self.tag_add("TODO", index1, index2) 97 if self.after_id: 98 if DEBUG: print "colorizing already scheduled" 99 return 100 if self.colorizing: 101 self.stop_colorizing = True 102 if DEBUG: print "stop colorizing" 103 if self.allow_colorizing: 104 if DEBUG: print "schedule colorizing" 105 self.after_id = self.after(1, self.recolorize) 106 107 close_when_done = None # Window to be closed when done colorizing 108 109 def close(self, close_when_done=None): 110 if self.after_id: 111 after_id = self.after_id 112 self.after_id = None 113 if DEBUG: print "cancel scheduled recolorizer" 114 self.after_cancel(after_id) 115 self.allow_colorizing = False 116 self.stop_colorizing = True 117 if close_when_done: 118 if not self.colorizing: 119 close_when_done.destroy() 120 else: 121 self.close_when_done = close_when_done 122 123 def toggle_colorize_event(self, event): 124 if self.after_id: 125 after_id = self.after_id 126 self.after_id = None 127 if DEBUG: print "cancel scheduled recolorizer" 128 self.after_cancel(after_id) 129 if self.allow_colorizing and self.colorizing: 130 if DEBUG: print "stop colorizing" 131 self.stop_colorizing = True 132 self.allow_colorizing = not self.allow_colorizing 133 if self.allow_colorizing and not self.colorizing: 134 self.after_id = self.after(1, self.recolorize) 135 if DEBUG: 136 print "auto colorizing turned",\ 137 self.allow_colorizing and "on" or "off" 138 return "break" 139 140 def recolorize(self): 141 self.after_id = None 142 if not self.delegate: 143 if DEBUG: print "no delegate" 144 return 145 if not self.allow_colorizing: 146 if DEBUG: print "auto colorizing is off" 147 return 148 if self.colorizing: 149 if DEBUG: print "already colorizing" 150 return 151 try: 152 self.stop_colorizing = False 153 self.colorizing = True 154 if DEBUG: print "colorizing..." 155 t0 = time.clock() 156 self.recolorize_main() 157 t1 = time.clock() 158 if DEBUG: print "%.3f seconds" % (t1-t0) 159 finally: 160 self.colorizing = False 161 if self.allow_colorizing and self.tag_nextrange("TODO", "1.0"): 162 if DEBUG: print "reschedule colorizing" 163 self.after_id = self.after(1, self.recolorize) 164 if self.close_when_done: 165 top = self.close_when_done 166 self.close_when_done = None 167 top.destroy() 168 169 def recolorize_main(self): 170 next = "1.0" 171 while True: 172 item = self.tag_nextrange("TODO", next) 173 if not item: 174 break 175 head, tail = item 176 self.tag_remove("SYNC", head, tail) 177 item = self.tag_prevrange("SYNC", head) 178 if item: 179 head = item[1] 180 else: 181 head = "1.0" 182 183 chars = "" 184 next = head 185 lines_to_get = 1 186 ok = False 187 while not ok: 188 mark = next 189 next = self.index(mark + "+%d lines linestart" % 190 lines_to_get) 191 lines_to_get = min(lines_to_get * 2, 100) 192 ok = "SYNC" in self.tag_names(next + "-1c") 193 line = self.get(mark, next) 194 ##print head, "get", mark, next, "->", repr(line) 195 if not line: 196 return 197 for tag in self.tagdefs.keys(): 198 self.tag_remove(tag, mark, next) 199 chars = chars + line 200 m = self.prog.search(chars) 201 while m: 202 for key, value in m.groupdict().items(): 203 if value: 204 a, b = m.span(key) 205 self.tag_add(key, 206 head + "+%dc" % a, 207 head + "+%dc" % b) 208 if value in ("def", "class"): 209 m1 = self.idprog.match(chars, b) 210 if m1: 211 a, b = m1.span(1) 212 self.tag_add("DEFINITION", 213 head + "+%dc" % a, 214 head + "+%dc" % b) 215 m = self.prog.search(chars, m.end()) 216 if "SYNC" in self.tag_names(next + "-1c"): 217 head = next 218 chars = "" 219 else: 220 ok = False 221 if not ok: 222 # We're in an inconsistent state, and the call to 223 # update may tell us to stop. It may also change 224 # the correct value for "next" (since this is a 225 # line.col string, not a true mark). So leave a 226 # crumb telling the next invocation to resume here 227 # in case update tells us to leave. 228 self.tag_add("TODO", next) 229 self.update() 230 if self.stop_colorizing: 231 if DEBUG: print "colorizing stopped" 232 return 233 234 def removecolors(self): 235 for tag in self.tagdefs.keys(): 236 self.tag_remove(tag, "1.0", "end") 237 238def _color_delegator(parent): # htest # 239 from Tkinter import Toplevel, Text 240 from idlelib.Percolator import Percolator 241 242 top = Toplevel(parent) 243 top.title("Test ColorDelegator") 244 top.geometry("200x100+%d+%d" % (parent.winfo_rootx() + 200, 245 parent.winfo_rooty() + 150)) 246 source = "if somename: x = 'abc' # comment\nprint\n" 247 text = Text(top, background="white") 248 text.pack(expand=1, fill="both") 249 text.insert("insert", source) 250 text.focus_set() 251 252 p = Percolator(text) 253 d = ColorDelegator() 254 p.insertfilter(d) 255 256if __name__ == "__main__": 257 from idlelib.idle_test.htest import run 258 run(_color_delegator) 259