mirror of
				https://github.com/python/cpython.git
				synced 2025-10-26 11:14:33 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			220 lines
		
	
	
	
		
			6.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			220 lines
		
	
	
	
		
			6.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import re
 | |
| from tkinter import *
 | |
| import tkinter.messagebox as 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")
 | |
|                 line, col = map(int, pos.split("."))
 | |
|             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):
 | |
|     line, col = map(int, index.split(".")) # Fails on invalid index
 | |
|     return line, col
 | 
