annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/idlelib/searchengine.py @ 69:33d812a61356

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 17:55:14 -0400
parents
children
rev   line source
jpayne@69 1 '''Define SearchEngine for search dialogs.'''
jpayne@69 2 import re
jpayne@69 3
jpayne@69 4 from tkinter import StringVar, BooleanVar, TclError
jpayne@69 5 import tkinter.messagebox as tkMessageBox
jpayne@69 6
jpayne@69 7 def get(root):
jpayne@69 8 '''Return the singleton SearchEngine instance for the process.
jpayne@69 9
jpayne@69 10 The single SearchEngine saves settings between dialog instances.
jpayne@69 11 If there is not a SearchEngine already, make one.
jpayne@69 12 '''
jpayne@69 13 if not hasattr(root, "_searchengine"):
jpayne@69 14 root._searchengine = SearchEngine(root)
jpayne@69 15 # This creates a cycle that persists until root is deleted.
jpayne@69 16 return root._searchengine
jpayne@69 17
jpayne@69 18
jpayne@69 19 class SearchEngine:
jpayne@69 20 """Handles searching a text widget for Find, Replace, and Grep."""
jpayne@69 21
jpayne@69 22 def __init__(self, root):
jpayne@69 23 '''Initialize Variables that save search state.
jpayne@69 24
jpayne@69 25 The dialogs bind these to the UI elements present in the dialogs.
jpayne@69 26 '''
jpayne@69 27 self.root = root # need for report_error()
jpayne@69 28 self.patvar = StringVar(root, '') # search pattern
jpayne@69 29 self.revar = BooleanVar(root, False) # regular expression?
jpayne@69 30 self.casevar = BooleanVar(root, False) # match case?
jpayne@69 31 self.wordvar = BooleanVar(root, False) # match whole word?
jpayne@69 32 self.wrapvar = BooleanVar(root, True) # wrap around buffer?
jpayne@69 33 self.backvar = BooleanVar(root, False) # search backwards?
jpayne@69 34
jpayne@69 35 # Access methods
jpayne@69 36
jpayne@69 37 def getpat(self):
jpayne@69 38 return self.patvar.get()
jpayne@69 39
jpayne@69 40 def setpat(self, pat):
jpayne@69 41 self.patvar.set(pat)
jpayne@69 42
jpayne@69 43 def isre(self):
jpayne@69 44 return self.revar.get()
jpayne@69 45
jpayne@69 46 def iscase(self):
jpayne@69 47 return self.casevar.get()
jpayne@69 48
jpayne@69 49 def isword(self):
jpayne@69 50 return self.wordvar.get()
jpayne@69 51
jpayne@69 52 def iswrap(self):
jpayne@69 53 return self.wrapvar.get()
jpayne@69 54
jpayne@69 55 def isback(self):
jpayne@69 56 return self.backvar.get()
jpayne@69 57
jpayne@69 58 # Higher level access methods
jpayne@69 59
jpayne@69 60 def setcookedpat(self, pat):
jpayne@69 61 "Set pattern after escaping if re."
jpayne@69 62 # called only in search.py: 66
jpayne@69 63 if self.isre():
jpayne@69 64 pat = re.escape(pat)
jpayne@69 65 self.setpat(pat)
jpayne@69 66
jpayne@69 67 def getcookedpat(self):
jpayne@69 68 pat = self.getpat()
jpayne@69 69 if not self.isre(): # if True, see setcookedpat
jpayne@69 70 pat = re.escape(pat)
jpayne@69 71 if self.isword():
jpayne@69 72 pat = r"\b%s\b" % pat
jpayne@69 73 return pat
jpayne@69 74
jpayne@69 75 def getprog(self):
jpayne@69 76 "Return compiled cooked search pattern."
jpayne@69 77 pat = self.getpat()
jpayne@69 78 if not pat:
jpayne@69 79 self.report_error(pat, "Empty regular expression")
jpayne@69 80 return None
jpayne@69 81 pat = self.getcookedpat()
jpayne@69 82 flags = 0
jpayne@69 83 if not self.iscase():
jpayne@69 84 flags = flags | re.IGNORECASE
jpayne@69 85 try:
jpayne@69 86 prog = re.compile(pat, flags)
jpayne@69 87 except re.error as what:
jpayne@69 88 args = what.args
jpayne@69 89 msg = args[0]
jpayne@69 90 col = args[1] if len(args) >= 2 else -1
jpayne@69 91 self.report_error(pat, msg, col)
jpayne@69 92 return None
jpayne@69 93 return prog
jpayne@69 94
jpayne@69 95 def report_error(self, pat, msg, col=-1):
jpayne@69 96 # Derived class could override this with something fancier
jpayne@69 97 msg = "Error: " + str(msg)
jpayne@69 98 if pat:
jpayne@69 99 msg = msg + "\nPattern: " + str(pat)
jpayne@69 100 if col >= 0:
jpayne@69 101 msg = msg + "\nOffset: " + str(col)
jpayne@69 102 tkMessageBox.showerror("Regular expression error",
jpayne@69 103 msg, master=self.root)
jpayne@69 104
jpayne@69 105 def search_text(self, text, prog=None, ok=0):
jpayne@69 106 '''Return (lineno, matchobj) or None for forward/backward search.
jpayne@69 107
jpayne@69 108 This function calls the right function with the right arguments.
jpayne@69 109 It directly return the result of that call.
jpayne@69 110
jpayne@69 111 Text is a text widget. Prog is a precompiled pattern.
jpayne@69 112 The ok parameter is a bit complicated as it has two effects.
jpayne@69 113
jpayne@69 114 If there is a selection, the search begin at either end,
jpayne@69 115 depending on the direction setting and ok, with ok meaning that
jpayne@69 116 the search starts with the selection. Otherwise, search begins
jpayne@69 117 at the insert mark.
jpayne@69 118
jpayne@69 119 To aid progress, the search functions do not return an empty
jpayne@69 120 match at the starting position unless ok is True.
jpayne@69 121 '''
jpayne@69 122
jpayne@69 123 if not prog:
jpayne@69 124 prog = self.getprog()
jpayne@69 125 if not prog:
jpayne@69 126 return None # Compilation failed -- stop
jpayne@69 127 wrap = self.wrapvar.get()
jpayne@69 128 first, last = get_selection(text)
jpayne@69 129 if self.isback():
jpayne@69 130 if ok:
jpayne@69 131 start = last
jpayne@69 132 else:
jpayne@69 133 start = first
jpayne@69 134 line, col = get_line_col(start)
jpayne@69 135 res = self.search_backward(text, prog, line, col, wrap, ok)
jpayne@69 136 else:
jpayne@69 137 if ok:
jpayne@69 138 start = first
jpayne@69 139 else:
jpayne@69 140 start = last
jpayne@69 141 line, col = get_line_col(start)
jpayne@69 142 res = self.search_forward(text, prog, line, col, wrap, ok)
jpayne@69 143 return res
jpayne@69 144
jpayne@69 145 def search_forward(self, text, prog, line, col, wrap, ok=0):
jpayne@69 146 wrapped = 0
jpayne@69 147 startline = line
jpayne@69 148 chars = text.get("%d.0" % line, "%d.0" % (line+1))
jpayne@69 149 while chars:
jpayne@69 150 m = prog.search(chars[:-1], col)
jpayne@69 151 if m:
jpayne@69 152 if ok or m.end() > col:
jpayne@69 153 return line, m
jpayne@69 154 line = line + 1
jpayne@69 155 if wrapped and line > startline:
jpayne@69 156 break
jpayne@69 157 col = 0
jpayne@69 158 ok = 1
jpayne@69 159 chars = text.get("%d.0" % line, "%d.0" % (line+1))
jpayne@69 160 if not chars and wrap:
jpayne@69 161 wrapped = 1
jpayne@69 162 wrap = 0
jpayne@69 163 line = 1
jpayne@69 164 chars = text.get("1.0", "2.0")
jpayne@69 165 return None
jpayne@69 166
jpayne@69 167 def search_backward(self, text, prog, line, col, wrap, ok=0):
jpayne@69 168 wrapped = 0
jpayne@69 169 startline = line
jpayne@69 170 chars = text.get("%d.0" % line, "%d.0" % (line+1))
jpayne@69 171 while 1:
jpayne@69 172 m = search_reverse(prog, chars[:-1], col)
jpayne@69 173 if m:
jpayne@69 174 if ok or m.start() < col:
jpayne@69 175 return line, m
jpayne@69 176 line = line - 1
jpayne@69 177 if wrapped and line < startline:
jpayne@69 178 break
jpayne@69 179 ok = 1
jpayne@69 180 if line <= 0:
jpayne@69 181 if not wrap:
jpayne@69 182 break
jpayne@69 183 wrapped = 1
jpayne@69 184 wrap = 0
jpayne@69 185 pos = text.index("end-1c")
jpayne@69 186 line, col = map(int, pos.split("."))
jpayne@69 187 chars = text.get("%d.0" % line, "%d.0" % (line+1))
jpayne@69 188 col = len(chars) - 1
jpayne@69 189 return None
jpayne@69 190
jpayne@69 191
jpayne@69 192 def search_reverse(prog, chars, col):
jpayne@69 193 '''Search backwards and return an re match object or None.
jpayne@69 194
jpayne@69 195 This is done by searching forwards until there is no match.
jpayne@69 196 Prog: compiled re object with a search method returning a match.
jpayne@69 197 Chars: line of text, without \\n.
jpayne@69 198 Col: stop index for the search; the limit for match.end().
jpayne@69 199 '''
jpayne@69 200 m = prog.search(chars)
jpayne@69 201 if not m:
jpayne@69 202 return None
jpayne@69 203 found = None
jpayne@69 204 i, j = m.span() # m.start(), m.end() == match slice indexes
jpayne@69 205 while i < col and j <= col:
jpayne@69 206 found = m
jpayne@69 207 if i == j:
jpayne@69 208 j = j+1
jpayne@69 209 m = prog.search(chars, j)
jpayne@69 210 if not m:
jpayne@69 211 break
jpayne@69 212 i, j = m.span()
jpayne@69 213 return found
jpayne@69 214
jpayne@69 215 def get_selection(text):
jpayne@69 216 '''Return tuple of 'line.col' indexes from selection or insert mark.
jpayne@69 217 '''
jpayne@69 218 try:
jpayne@69 219 first = text.index("sel.first")
jpayne@69 220 last = text.index("sel.last")
jpayne@69 221 except TclError:
jpayne@69 222 first = last = None
jpayne@69 223 if not first:
jpayne@69 224 first = text.index("insert")
jpayne@69 225 if not last:
jpayne@69 226 last = first
jpayne@69 227 return first, last
jpayne@69 228
jpayne@69 229 def get_line_col(index):
jpayne@69 230 '''Return (line, col) tuple of ints from 'line.col' string.'''
jpayne@69 231 line, col = map(int, index.split(".")) # Fails on invalid index
jpayne@69 232 return line, col
jpayne@69 233
jpayne@69 234
jpayne@69 235 if __name__ == "__main__":
jpayne@69 236 from unittest import main
jpayne@69 237 main('idlelib.idle_test.test_searchengine', verbosity=2)