Home | History | Annotate | Download | only in idlelib
      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