Home | History | Annotate | Download | only in idlelib
      1 import re
      2 from Tkinter import *
      3 import tkMessageBox
      4 
      5 def get(root):
      6     if not hasattr(root, "_searchengine"):
      7         root._searchengine = SearchEngine(root)
      8         # XXX This will never garbage-collect -- who cares
      9     return root._searchengine
     10 
     11 class SearchEngine:
     12 
     13     def __init__(self, root):
     14         self.root = root
     15         # State shared by search, replace, and grep;
     16         # the search dialogs bind these to UI elements.
     17         self.patvar = StringVar(root)           # search pattern
     18         self.revar = BooleanVar(root)           # regular expression?
     19         self.casevar = BooleanVar(root)         # match case?
     20         self.wordvar = BooleanVar(root)         # match whole word?
     21         self.wrapvar = BooleanVar(root)         # wrap around buffer?
     22         self.wrapvar.set(1)                     # (on by default)
     23         self.backvar = BooleanVar(root)         # search backwards?
     24 
     25     # Access methods
     26 
     27     def getpat(self):
     28         return self.patvar.get()
     29 
     30     def setpat(self, pat):
     31         self.patvar.set(pat)
     32 
     33     def isre(self):
     34         return self.revar.get()
     35 
     36     def iscase(self):
     37         return self.casevar.get()
     38 
     39     def isword(self):
     40         return self.wordvar.get()
     41 
     42     def iswrap(self):
     43         return self.wrapvar.get()
     44 
     45     def isback(self):
     46         return self.backvar.get()
     47 
     48     # Higher level access methods
     49 
     50     def getcookedpat(self):
     51         pat = self.getpat()
     52         if not self.isre():
     53             pat = re.escape(pat)
     54         if self.isword():
     55             pat = r"\b%s\b" % pat
     56         return pat
     57 
     58     def getprog(self):
     59         pat = self.getpat()
     60         if not pat:
     61             self.report_error(pat, "Empty regular expression")
     62             return None
     63         pat = self.getcookedpat()
     64         flags = 0
     65         if not self.iscase():
     66             flags = flags | re.IGNORECASE
     67         try:
     68             prog = re.compile(pat, flags)
     69         except re.error, what:
     70             try:
     71                 msg, col = what
     72             except:
     73                 msg = str(what)
     74                 col = -1
     75             self.report_error(pat, msg, col)
     76             return None
     77         return prog
     78 
     79     def report_error(self, pat, msg, col=-1):
     80         # Derived class could overrid this with something fancier
     81         msg = "Error: " + str(msg)
     82         if pat:
     83             msg = msg + "\np\Pattern: " + str(pat)
     84         if col >= 0:
     85             msg = msg + "\nOffset: " + str(col)
     86         tkMessageBox.showerror("Regular expression error",
     87                                msg, master=self.root)
     88 
     89     def setcookedpat(self, pat):
     90         if self.isre():
     91             pat = re.escape(pat)
     92         self.setpat(pat)
     93 
     94     def search_text(self, text, prog=None, ok=0):
     95         """Search a text widget for the pattern.
     96 
     97         If prog is given, it should be the precompiled pattern.
     98         Return a tuple (lineno, matchobj); None if not found.
     99 
    100         This obeys the wrap and direction (back) settings.
    101 
    102         The search starts at the selection (if there is one) or
    103         at the insert mark (otherwise).  If the search is forward,
    104         it starts at the right of the selection; for a backward
    105         search, it starts at the left end.  An empty match exactly
    106         at either end of the selection (or at the insert mark if
    107         there is no selection) is ignored  unless the ok flag is true
    108         -- this is done to guarantee progress.
    109 
    110         If the search is allowed to wrap around, it will return the
    111         original selection if (and only if) it is the only match.
    112 
    113         """
    114         if not prog:
    115             prog = self.getprog()
    116             if not prog:
    117                 return None # Compilation failed -- stop
    118         wrap = self.wrapvar.get()
    119         first, last = get_selection(text)
    120         if self.isback():
    121             if ok:
    122                 start = last
    123             else:
    124                 start = first
    125             line, col = get_line_col(start)
    126             res = self.search_backward(text, prog, line, col, wrap, ok)
    127         else:
    128             if ok:
    129                 start = first
    130             else:
    131                 start = last
    132             line, col = get_line_col(start)
    133             res = self.search_forward(text, prog, line, col, wrap, ok)
    134         return res
    135 
    136     def search_forward(self, text, prog, line, col, wrap, ok=0):
    137         wrapped = 0
    138         startline = line
    139         chars = text.get("%d.0" % line, "%d.0" % (line+1))
    140         while chars:
    141             m = prog.search(chars[:-1], col)
    142             if m:
    143                 if ok or m.end() > col:
    144                     return line, m
    145             line = line + 1
    146             if wrapped and line > startline:
    147                 break
    148             col = 0
    149             ok = 1
    150             chars = text.get("%d.0" % line, "%d.0" % (line+1))
    151             if not chars and wrap:
    152                 wrapped = 1
    153                 wrap = 0
    154                 line = 1
    155                 chars = text.get("1.0", "2.0")
    156         return None
    157 
    158     def search_backward(self, text, prog, line, col, wrap, ok=0):
    159         wrapped = 0
    160         startline = line
    161         chars = text.get("%d.0" % line, "%d.0" % (line+1))
    162         while 1:
    163             m = search_reverse(prog, chars[:-1], col)
    164             if m:
    165                 if ok or m.start() < col:
    166                     return line, m
    167             line = line - 1
    168             if wrapped and line < startline:
    169                 break
    170             ok = 1
    171             if line <= 0:
    172                 if not wrap:
    173                     break
    174                 wrapped = 1
    175                 wrap = 0
    176                 pos = text.index("end-1c")
    177                 line, col = map(int, pos.split("."))
    178             chars = text.get("%d.0" % line, "%d.0" % (line+1))
    179             col = len(chars) - 1
    180         return None
    181 
    182 # Helper to search backwards in a string.
    183 # (Optimized for the case where the pattern isn't found.)
    184 
    185 def search_reverse(prog, chars, col):
    186     m = prog.search(chars)
    187     if not m:
    188         return None
    189     found = None
    190     i, j = m.span()
    191     while i < col and j <= col:
    192         found = m
    193         if i == j:
    194             j = j+1
    195         m = prog.search(chars, j)
    196         if not m:
    197             break
    198         i, j = m.span()
    199     return found
    200 
    201 # Helper to get selection end points, defaulting to insert mark.
    202 # Return a tuple of indices ("line.col" strings).
    203 
    204 def get_selection(text):
    205     try:
    206         first = text.index("sel.first")
    207         last = text.index("sel.last")
    208     except TclError:
    209         first = last = None
    210     if not first:
    211         first = text.index("insert")
    212     if not last:
    213         last = first
    214     return first, last
    215 
    216 # Helper to parse a text index into a (line, col) tuple.
    217 
    218 def get_line_col(index):
    219     line, col = map(int, index.split(".")) # Fails on invalid index
    220     return line, col
    221