Mercurial > repos > rliterman > csp2
comparison 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 |
comparison
equal
deleted
inserted
replaced
67:0e9998148a16 | 69:33d812a61356 |
---|---|
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) |