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