| 
									
										
										
										
											2000-08-15 01:13:23 +00:00
										 |  |  | import re | 
					
						
							|  |  |  | from Tkinter import * | 
					
						
							|  |  |  | import tkMessageBox | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get(root): | 
					
						
							|  |  |  |     if not hasattr(root, "_searchengine"): | 
					
						
							|  |  |  |         root._searchengine = SearchEngine(root) | 
					
						
							|  |  |  |         # XXX This will never garbage-collect -- who cares | 
					
						
							|  |  |  |     return root._searchengine | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class SearchEngine: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, root): | 
					
						
							|  |  |  |         self.root = root | 
					
						
							|  |  |  |         # State shared by search, replace, and grep; | 
					
						
							|  |  |  |         # the search dialogs bind these to UI elements. | 
					
						
							|  |  |  |         self.patvar = StringVar(root)           # search pattern | 
					
						
							|  |  |  |         self.revar = BooleanVar(root)           # regular expression? | 
					
						
							|  |  |  |         self.casevar = BooleanVar(root)         # match case? | 
					
						
							|  |  |  |         self.wordvar = BooleanVar(root)         # match whole word? | 
					
						
							|  |  |  |         self.wrapvar = BooleanVar(root)         # wrap around buffer? | 
					
						
							|  |  |  |         self.wrapvar.set(1)                     # (on by default) | 
					
						
							|  |  |  |         self.backvar = BooleanVar(root)         # search backwards? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Access methods | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def getpat(self): | 
					
						
							|  |  |  |         return self.patvar.get() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def setpat(self, pat): | 
					
						
							|  |  |  |         self.patvar.set(pat) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def isre(self): | 
					
						
							|  |  |  |         return self.revar.get() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def iscase(self): | 
					
						
							|  |  |  |         return self.casevar.get() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def isword(self): | 
					
						
							|  |  |  |         return self.wordvar.get() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def iswrap(self): | 
					
						
							|  |  |  |         return self.wrapvar.get() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def isback(self): | 
					
						
							|  |  |  |         return self.backvar.get() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Higher level access methods | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def getcookedpat(self): | 
					
						
							|  |  |  |         pat = self.getpat() | 
					
						
							|  |  |  |         if not self.isre(): | 
					
						
							|  |  |  |             pat = re.escape(pat) | 
					
						
							|  |  |  |         if self.isword(): | 
					
						
							|  |  |  |             pat = r"\b%s\b" % pat | 
					
						
							|  |  |  |         return pat | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def getprog(self): | 
					
						
							|  |  |  |         pat = self.getpat() | 
					
						
							|  |  |  |         if not pat: | 
					
						
							|  |  |  |             self.report_error(pat, "Empty regular expression") | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  |         pat = self.getcookedpat() | 
					
						
							|  |  |  |         flags = 0 | 
					
						
							|  |  |  |         if not self.iscase(): | 
					
						
							|  |  |  |             flags = flags | re.IGNORECASE | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             prog = re.compile(pat, flags) | 
					
						
							|  |  |  |         except re.error, what: | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 msg, col = what | 
					
						
							|  |  |  |             except: | 
					
						
							|  |  |  |                 msg = str(what) | 
					
						
							|  |  |  |                 col = -1 | 
					
						
							|  |  |  |             self.report_error(pat, msg, col) | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  |         return prog | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def report_error(self, pat, msg, col=-1): | 
					
						
							|  |  |  |         # Derived class could overrid this with something fancier | 
					
						
							|  |  |  |         msg = "Error: " + str(msg) | 
					
						
							|  |  |  |         if pat: | 
					
						
							|  |  |  |             msg = msg + "\np\Pattern: " + str(pat) | 
					
						
							|  |  |  |         if col >= 0: | 
					
						
							|  |  |  |             msg = msg + "\nOffset: " + str(col) | 
					
						
							|  |  |  |         tkMessageBox.showerror("Regular expression error", | 
					
						
							|  |  |  |                                msg, master=self.root) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def setcookedpat(self, pat): | 
					
						
							|  |  |  |         if self.isre(): | 
					
						
							|  |  |  |             pat = re.escape(pat) | 
					
						
							|  |  |  |         self.setpat(pat) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def search_text(self, text, prog=None, ok=0): | 
					
						
							|  |  |  |         """Search a text widget for the pattern.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         If prog is given, it should be the precompiled pattern. | 
					
						
							|  |  |  |         Return a tuple (lineno, matchobj); None if not found. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         This obeys the wrap and direction (back) settings. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         The search starts at the selection (if there is one) or | 
					
						
							|  |  |  |         at the insert mark (otherwise).  If the search is forward, | 
					
						
							|  |  |  |         it starts at the right of the selection; for a backward | 
					
						
							|  |  |  |         search, it starts at the left end.  An empty match exactly | 
					
						
							|  |  |  |         at either end of the selection (or at the insert mark if | 
					
						
							|  |  |  |         there is no selection) is ignored  unless the ok flag is true | 
					
						
							|  |  |  |         -- this is done to guarantee progress. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         If the search is allowed to wrap around, it will return the | 
					
						
							|  |  |  |         original selection if (and only if) it is the only match. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         if not prog: | 
					
						
							|  |  |  |             prog = self.getprog() | 
					
						
							|  |  |  |             if not prog: | 
					
						
							|  |  |  |                 return None # Compilation failed -- stop | 
					
						
							|  |  |  |         wrap = self.wrapvar.get() | 
					
						
							|  |  |  |         first, last = get_selection(text) | 
					
						
							|  |  |  |         if self.isback(): | 
					
						
							|  |  |  |             if ok: | 
					
						
							|  |  |  |                 start = last | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 start = first | 
					
						
							|  |  |  |             line, col = get_line_col(start) | 
					
						
							|  |  |  |             res = self.search_backward(text, prog, line, col, wrap, ok) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             if ok: | 
					
						
							|  |  |  |                 start = first | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 start = last | 
					
						
							|  |  |  |             line, col = get_line_col(start) | 
					
						
							|  |  |  |             res = self.search_forward(text, prog, line, col, wrap, ok) | 
					
						
							|  |  |  |         return res | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def search_forward(self, text, prog, line, col, wrap, ok=0): | 
					
						
							|  |  |  |         wrapped = 0 | 
					
						
							|  |  |  |         startline = line | 
					
						
							|  |  |  |         chars = text.get("%d.0" % line, "%d.0" % (line+1)) | 
					
						
							|  |  |  |         while chars: | 
					
						
							|  |  |  |             m = prog.search(chars[:-1], col) | 
					
						
							|  |  |  |             if m: | 
					
						
							|  |  |  |                 if ok or m.end() > col: | 
					
						
							|  |  |  |                     return line, m | 
					
						
							|  |  |  |             line = line + 1 | 
					
						
							|  |  |  |             if wrapped and line > startline: | 
					
						
							|  |  |  |                 break | 
					
						
							|  |  |  |             col = 0 | 
					
						
							|  |  |  |             ok = 1 | 
					
						
							|  |  |  |             chars = text.get("%d.0" % line, "%d.0" % (line+1)) | 
					
						
							|  |  |  |             if not chars and wrap: | 
					
						
							|  |  |  |                 wrapped = 1 | 
					
						
							|  |  |  |                 wrap = 0 | 
					
						
							|  |  |  |                 line = 1 | 
					
						
							|  |  |  |                 chars = text.get("1.0", "2.0") | 
					
						
							|  |  |  |         return None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def search_backward(self, text, prog, line, col, wrap, ok=0): | 
					
						
							|  |  |  |         wrapped = 0 | 
					
						
							|  |  |  |         startline = line | 
					
						
							|  |  |  |         chars = text.get("%d.0" % line, "%d.0" % (line+1)) | 
					
						
							|  |  |  |         while 1: | 
					
						
							|  |  |  |             m = search_reverse(prog, chars[:-1], col) | 
					
						
							|  |  |  |             if m: | 
					
						
							|  |  |  |                 if ok or m.start() < col: | 
					
						
							|  |  |  |                     return line, m | 
					
						
							|  |  |  |             line = line - 1 | 
					
						
							|  |  |  |             if wrapped and line < startline: | 
					
						
							|  |  |  |                 break | 
					
						
							|  |  |  |             ok = 1 | 
					
						
							|  |  |  |             if line <= 0: | 
					
						
							|  |  |  |                 if not wrap: | 
					
						
							|  |  |  |                     break | 
					
						
							|  |  |  |                 wrapped = 1 | 
					
						
							|  |  |  |                 wrap = 0 | 
					
						
							|  |  |  |                 pos = text.index("end-1c") | 
					
						
							| 
									
										
										
										
											2002-09-18 03:14:11 +00:00
										 |  |  |                 line, col = map(int, pos.split(".")) | 
					
						
							| 
									
										
										
										
											2000-08-15 01:13:23 +00:00
										 |  |  |             chars = text.get("%d.0" % line, "%d.0" % (line+1)) | 
					
						
							|  |  |  |             col = len(chars) - 1 | 
					
						
							|  |  |  |         return None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Helper to search backwards in a string. | 
					
						
							|  |  |  | # (Optimized for the case where the pattern isn't found.) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def search_reverse(prog, chars, col): | 
					
						
							|  |  |  |     m = prog.search(chars) | 
					
						
							|  |  |  |     if not m: | 
					
						
							|  |  |  |         return None | 
					
						
							|  |  |  |     found = None | 
					
						
							|  |  |  |     i, j = m.span() | 
					
						
							|  |  |  |     while i < col and j <= col: | 
					
						
							|  |  |  |         found = m | 
					
						
							|  |  |  |         if i == j: | 
					
						
							|  |  |  |             j = j+1 | 
					
						
							|  |  |  |         m = prog.search(chars, j) | 
					
						
							|  |  |  |         if not m: | 
					
						
							|  |  |  |             break | 
					
						
							|  |  |  |         i, j = m.span() | 
					
						
							|  |  |  |     return found | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Helper to get selection end points, defaulting to insert mark. | 
					
						
							|  |  |  | # Return a tuple of indices ("line.col" strings). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_selection(text): | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         first = text.index("sel.first") | 
					
						
							|  |  |  |         last = text.index("sel.last") | 
					
						
							|  |  |  |     except TclError: | 
					
						
							|  |  |  |         first = last = None | 
					
						
							|  |  |  |     if not first: | 
					
						
							|  |  |  |         first = text.index("insert") | 
					
						
							|  |  |  |     if not last: | 
					
						
							|  |  |  |         last = first | 
					
						
							|  |  |  |     return first, last | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Helper to parse a text index into a (line, col) tuple. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_line_col(index): | 
					
						
							| 
									
										
										
										
											2002-09-18 03:14:11 +00:00
										 |  |  |     line, col = map(int, index.split(".")) # Fails on invalid index | 
					
						
							| 
									
										
										
										
											2000-08-15 01:13:23 +00:00
										 |  |  |     return line, col |