PageRenderTime 53ms CodeModel.GetById 15ms app.highlight 32ms RepoModel.GetById 1ms app.codeStats 1ms

/Lib/idlelib/ColorDelegator.py

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