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