annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/idlelib/searchengine.py @ 68:5028fdace37b

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