• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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