0001 import re 0002 from Tkinter import * 0003 import tkMessageBox 0004 0005 def get(root): 0006 if not hasattr(root, "_searchengine"): 0007 root._searchengine = SearchEngine(root) 0008 # XXX This will never garbage-collect -- who cares 0009 return root._searchengine 0010 0011 class SearchEngine: 0012 0013 def __init__(self, root): 0014 self.root = root 0015 # State shared by search, replace, and grep; 0016 # the search dialogs bind these to UI elements. 0017 self.patvar = StringVar(root) # search pattern 0018 self.revar = BooleanVar(root) # regular expression? 0019 self.casevar = BooleanVar(root) # match case? 0020 self.wordvar = BooleanVar(root) # match whole word? 0021 self.wrapvar = BooleanVar(root) # wrap around buffer? 0022 self.wrapvar.set(1) # (on by default) 0023 self.backvar = BooleanVar(root) # search backwards? 0024 0025 # Access methods 0026 0027 def getpat(self): 0028 return self.patvar.get() 0029 0030 def setpat(self, pat): 0031 self.patvar.set(pat) 0032 0033 def isre(self): 0034 return self.revar.get() 0035 0036 def iscase(self): 0037 return self.casevar.get() 0038 0039 def isword(self): 0040 return self.wordvar.get() 0041 0042 def iswrap(self): 0043 return self.wrapvar.get() 0044 0045 def isback(self): 0046 return self.backvar.get() 0047 0048 # Higher level access methods 0049 0050 def getcookedpat(self): 0051 pat = self.getpat() 0052 if not self.isre(): 0053 pat = re.escape(pat) 0054 if self.isword(): 0055 pat = r"\b%s\b" % pat 0056 return pat 0057 0058 def getprog(self): 0059 pat = self.getpat() 0060 if not pat: 0061 self.report_error(pat, "Empty regular expression") 0062 return None 0063 pat = self.getcookedpat() 0064 flags = 0 0065 if not self.iscase(): 0066 flags = flags | re.IGNORECASE 0067 try: 0068 prog = re.compile(pat, flags) 0069 except re.error, what: 0070 try: 0071 msg, col = what 0072 except: 0073 msg = str(what) 0074 col = -1 0075 self.report_error(pat, msg, col) 0076 return None 0077 return prog 0078 0079 def report_error(self, pat, msg, col=-1): 0080 # Derived class could overrid this with something fancier 0081 msg = "Error: " + str(msg) 0082 if pat: 0083 msg = msg + "\np\Pattern: " + str(pat) 0084 if col >= 0: 0085 msg = msg + "\nOffset: " + str(col) 0086 tkMessageBox.showerror("Regular expression error", 0087 msg, master=self.root) 0088 0089 def setcookedpat(self, pat): 0090 if self.isre(): 0091 pat = re.escape(pat) 0092 self.setpat(pat) 0093 0094 def search_text(self, text, prog=None, ok=0): 0095 """Search a text widget for the pattern. 0096 0097 If prog is given, it should be the precompiled pattern. 0098 Return a tuple (lineno, matchobj); None if not found. 0099 0100 This obeys the wrap and direction (back) settings. 0101 0102 The search starts at the selection (if there is one) or 0103 at the insert mark (otherwise). If the search is forward, 0104 it starts at the right of the selection; for a backward 0105 search, it starts at the left end. An empty match exactly 0106 at either end of the selection (or at the insert mark if 0107 there is no selection) is ignored unless the ok flag is true 0108 -- this is done to guarantee progress. 0109 0110 If the search is allowed to wrap around, it will return the 0111 original selection if (and only if) it is the only match. 0112 0113 """ 0114 if not prog: 0115 prog = self.getprog() 0116 if not prog: 0117 return None # Compilation failed -- stop 0118 wrap = self.wrapvar.get() 0119 first, last = get_selection(text) 0120 if self.isback(): 0121 if ok: 0122 start = last 0123 else: 0124 start = first 0125 line, col = get_line_col(start) 0126 res = self.search_backward(text, prog, line, col, wrap, ok) 0127 else: 0128 if ok: 0129 start = first 0130 else: 0131 start = last 0132 line, col = get_line_col(start) 0133 res = self.search_forward(text, prog, line, col, wrap, ok) 0134 return res 0135 0136 def search_forward(self, text, prog, line, col, wrap, ok=0): 0137 wrapped = 0 0138 startline = line 0139 chars = text.get("%d.0" % line, "%d.0" % (line+1)) 0140 while chars: 0141 m = prog.search(chars[:-1], col) 0142 if m: 0143 if ok or m.end() > col: 0144 return line, m 0145 line = line + 1 0146 if wrapped and line > startline: 0147 break 0148 col = 0 0149 ok = 1 0150 chars = text.get("%d.0" % line, "%d.0" % (line+1)) 0151 if not chars and wrap: 0152 wrapped = 1 0153 wrap = 0 0154 line = 1 0155 chars = text.get("1.0", "2.0") 0156 return None 0157 0158 def search_backward(self, text, prog, line, col, wrap, ok=0): 0159 wrapped = 0 0160 startline = line 0161 chars = text.get("%d.0" % line, "%d.0" % (line+1)) 0162 while 1: 0163 m = search_reverse(prog, chars[:-1], col) 0164 if m: 0165 if ok or m.start() < col: 0166 return line, m 0167 line = line - 1 0168 if wrapped and line < startline: 0169 break 0170 ok = 1 0171 if line <= 0: 0172 if not wrap: 0173 break 0174 wrapped = 1 0175 wrap = 0 0176 pos = text.index("end-1c") 0177 line, col = map(int, pos.split(".")) 0178 chars = text.get("%d.0" % line, "%d.0" % (line+1)) 0179 col = len(chars) - 1 0180 return None 0181 0182 # Helper to search backwards in a string. 0183 # (Optimized for the case where the pattern isn't found.) 0184 0185 def search_reverse(prog, chars, col): 0186 m = prog.search(chars) 0187 if not m: 0188 return None 0189 found = None 0190 i, j = m.span() 0191 while i < col and j <= col: 0192 found = m 0193 if i == j: 0194 j = j+1 0195 m = prog.search(chars, j) 0196 if not m: 0197 break 0198 i, j = m.span() 0199 return found 0200 0201 # Helper to get selection end points, defaulting to insert mark. 0202 # Return a tuple of indices ("line.col" strings). 0203 0204 def get_selection(text): 0205 try: 0206 first = text.index("sel.first") 0207 last = text.index("sel.last") 0208 except TclError: 0209 first = last = None 0210 if not first: 0211 first = text.index("insert") 0212 if not last: 0213 last = first 0214 return first, last 0215 0216 # Helper to parse a text index into a (line, col) tuple. 0217 0218 def get_line_col(index): 0219 line, col = map(int, index.split(".")) # Fails on invalid index 0220 return line, col 0221
Generated by PyXR 0.9.4