Mercurial > repos > rliterman > csp2
comparison CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/idlelib/replace.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 """Replace dialog for IDLE. Inherits SearchDialogBase for GUI. | |
2 Uses idlelib.searchengine.SearchEngine for search capability. | |
3 Defines various replace related functions like replace, replace all, | |
4 and replace+find. | |
5 """ | |
6 import re | |
7 | |
8 from tkinter import StringVar, TclError | |
9 | |
10 from idlelib.searchbase import SearchDialogBase | |
11 from idlelib import searchengine | |
12 | |
13 | |
14 def replace(text): | |
15 """Create or reuse a singleton ReplaceDialog instance. | |
16 | |
17 The singleton dialog saves user entries and preferences | |
18 across instances. | |
19 | |
20 Args: | |
21 text: Text widget containing the text to be searched. | |
22 """ | |
23 root = text._root() | |
24 engine = searchengine.get(root) | |
25 if not hasattr(engine, "_replacedialog"): | |
26 engine._replacedialog = ReplaceDialog(root, engine) | |
27 dialog = engine._replacedialog | |
28 dialog.open(text) | |
29 | |
30 | |
31 class ReplaceDialog(SearchDialogBase): | |
32 "Dialog for finding and replacing a pattern in text." | |
33 | |
34 title = "Replace Dialog" | |
35 icon = "Replace" | |
36 | |
37 def __init__(self, root, engine): | |
38 """Create search dialog for finding and replacing text. | |
39 | |
40 Uses SearchDialogBase as the basis for the GUI and a | |
41 searchengine instance to prepare the search. | |
42 | |
43 Attributes: | |
44 replvar: StringVar containing 'Replace with:' value. | |
45 replent: Entry widget for replvar. Created in | |
46 create_entries(). | |
47 ok: Boolean used in searchengine.search_text to indicate | |
48 whether the search includes the selection. | |
49 """ | |
50 super().__init__(root, engine) | |
51 self.replvar = StringVar(root) | |
52 | |
53 def open(self, text): | |
54 """Make dialog visible on top of others and ready to use. | |
55 | |
56 Also, highlight the currently selected text and set the | |
57 search to include the current selection (self.ok). | |
58 | |
59 Args: | |
60 text: Text widget being searched. | |
61 """ | |
62 SearchDialogBase.open(self, text) | |
63 try: | |
64 first = text.index("sel.first") | |
65 except TclError: | |
66 first = None | |
67 try: | |
68 last = text.index("sel.last") | |
69 except TclError: | |
70 last = None | |
71 first = first or text.index("insert") | |
72 last = last or first | |
73 self.show_hit(first, last) | |
74 self.ok = True | |
75 | |
76 def create_entries(self): | |
77 "Create base and additional label and text entry widgets." | |
78 SearchDialogBase.create_entries(self) | |
79 self.replent = self.make_entry("Replace with:", self.replvar)[0] | |
80 | |
81 def create_command_buttons(self): | |
82 """Create base and additional command buttons. | |
83 | |
84 The additional buttons are for Find, Replace, | |
85 Replace+Find, and Replace All. | |
86 """ | |
87 SearchDialogBase.create_command_buttons(self) | |
88 self.make_button("Find", self.find_it) | |
89 self.make_button("Replace", self.replace_it) | |
90 self.make_button("Replace+Find", self.default_command, isdef=True) | |
91 self.make_button("Replace All", self.replace_all) | |
92 | |
93 def find_it(self, event=None): | |
94 "Handle the Find button." | |
95 self.do_find(False) | |
96 | |
97 def replace_it(self, event=None): | |
98 """Handle the Replace button. | |
99 | |
100 If the find is successful, then perform replace. | |
101 """ | |
102 if self.do_find(self.ok): | |
103 self.do_replace() | |
104 | |
105 def default_command(self, event=None): | |
106 """Handle the Replace+Find button as the default command. | |
107 | |
108 First performs a replace and then, if the replace was | |
109 successful, a find next. | |
110 """ | |
111 if self.do_find(self.ok): | |
112 if self.do_replace(): # Only find next match if replace succeeded. | |
113 # A bad re can cause it to fail. | |
114 self.do_find(False) | |
115 | |
116 def _replace_expand(self, m, repl): | |
117 "Expand replacement text if regular expression." | |
118 if self.engine.isre(): | |
119 try: | |
120 new = m.expand(repl) | |
121 except re.error: | |
122 self.engine.report_error(repl, 'Invalid Replace Expression') | |
123 new = None | |
124 else: | |
125 new = repl | |
126 | |
127 return new | |
128 | |
129 def replace_all(self, event=None): | |
130 """Handle the Replace All button. | |
131 | |
132 Search text for occurrences of the Find value and replace | |
133 each of them. The 'wrap around' value controls the start | |
134 point for searching. If wrap isn't set, then the searching | |
135 starts at the first occurrence after the current selection; | |
136 if wrap is set, the replacement starts at the first line. | |
137 The replacement is always done top-to-bottom in the text. | |
138 """ | |
139 prog = self.engine.getprog() | |
140 if not prog: | |
141 return | |
142 repl = self.replvar.get() | |
143 text = self.text | |
144 res = self.engine.search_text(text, prog) | |
145 if not res: | |
146 self.bell() | |
147 return | |
148 text.tag_remove("sel", "1.0", "end") | |
149 text.tag_remove("hit", "1.0", "end") | |
150 line = res[0] | |
151 col = res[1].start() | |
152 if self.engine.iswrap(): | |
153 line = 1 | |
154 col = 0 | |
155 ok = True | |
156 first = last = None | |
157 # XXX ought to replace circular instead of top-to-bottom when wrapping | |
158 text.undo_block_start() | |
159 while True: | |
160 res = self.engine.search_forward(text, prog, line, col, | |
161 wrap=False, ok=ok) | |
162 if not res: | |
163 break | |
164 line, m = res | |
165 chars = text.get("%d.0" % line, "%d.0" % (line+1)) | |
166 orig = m.group() | |
167 new = self._replace_expand(m, repl) | |
168 if new is None: | |
169 break | |
170 i, j = m.span() | |
171 first = "%d.%d" % (line, i) | |
172 last = "%d.%d" % (line, j) | |
173 if new == orig: | |
174 text.mark_set("insert", last) | |
175 else: | |
176 text.mark_set("insert", first) | |
177 if first != last: | |
178 text.delete(first, last) | |
179 if new: | |
180 text.insert(first, new) | |
181 col = i + len(new) | |
182 ok = False | |
183 text.undo_block_stop() | |
184 if first and last: | |
185 self.show_hit(first, last) | |
186 self.close() | |
187 | |
188 def do_find(self, ok=False): | |
189 """Search for and highlight next occurrence of pattern in text. | |
190 | |
191 No text replacement is done with this option. | |
192 """ | |
193 if not self.engine.getprog(): | |
194 return False | |
195 text = self.text | |
196 res = self.engine.search_text(text, None, ok) | |
197 if not res: | |
198 self.bell() | |
199 return False | |
200 line, m = res | |
201 i, j = m.span() | |
202 first = "%d.%d" % (line, i) | |
203 last = "%d.%d" % (line, j) | |
204 self.show_hit(first, last) | |
205 self.ok = True | |
206 return True | |
207 | |
208 def do_replace(self): | |
209 "Replace search pattern in text with replacement value." | |
210 prog = self.engine.getprog() | |
211 if not prog: | |
212 return False | |
213 text = self.text | |
214 try: | |
215 first = pos = text.index("sel.first") | |
216 last = text.index("sel.last") | |
217 except TclError: | |
218 pos = None | |
219 if not pos: | |
220 first = last = pos = text.index("insert") | |
221 line, col = searchengine.get_line_col(pos) | |
222 chars = text.get("%d.0" % line, "%d.0" % (line+1)) | |
223 m = prog.match(chars, col) | |
224 if not prog: | |
225 return False | |
226 new = self._replace_expand(m, self.replvar.get()) | |
227 if new is None: | |
228 return False | |
229 text.mark_set("insert", first) | |
230 text.undo_block_start() | |
231 if m.group(): | |
232 text.delete(first, last) | |
233 if new: | |
234 text.insert(first, new) | |
235 text.undo_block_stop() | |
236 self.show_hit(first, text.index("insert")) | |
237 self.ok = False | |
238 return True | |
239 | |
240 def show_hit(self, first, last): | |
241 """Highlight text between first and last indices. | |
242 | |
243 Text is highlighted via the 'hit' tag and the marked | |
244 section is brought into view. | |
245 | |
246 The colors from the 'hit' tag aren't currently shown | |
247 when the text is displayed. This is due to the 'sel' | |
248 tag being added first, so the colors in the 'sel' | |
249 config are seen instead of the colors for 'hit'. | |
250 """ | |
251 text = self.text | |
252 text.mark_set("insert", first) | |
253 text.tag_remove("sel", "1.0", "end") | |
254 text.tag_add("sel", first, last) | |
255 text.tag_remove("hit", "1.0", "end") | |
256 if first == last: | |
257 text.tag_add("hit", first) | |
258 else: | |
259 text.tag_add("hit", first, last) | |
260 text.see("insert") | |
261 text.update_idletasks() | |
262 | |
263 def close(self, event=None): | |
264 "Close the dialog and remove hit tags." | |
265 SearchDialogBase.close(self, event) | |
266 self.text.tag_remove("hit", "1.0", "end") | |
267 | |
268 | |
269 def _replace_dialog(parent): # htest # | |
270 from tkinter import Toplevel, Text, END, SEL | |
271 from tkinter.ttk import Frame, Button | |
272 | |
273 top = Toplevel(parent) | |
274 top.title("Test ReplaceDialog") | |
275 x, y = map(int, parent.geometry().split('+')[1:]) | |
276 top.geometry("+%d+%d" % (x, y + 175)) | |
277 | |
278 # mock undo delegator methods | |
279 def undo_block_start(): | |
280 pass | |
281 | |
282 def undo_block_stop(): | |
283 pass | |
284 | |
285 frame = Frame(top) | |
286 frame.pack() | |
287 text = Text(frame, inactiveselectbackground='gray') | |
288 text.undo_block_start = undo_block_start | |
289 text.undo_block_stop = undo_block_stop | |
290 text.pack() | |
291 text.insert("insert","This is a sample sTring\nPlus MORE.") | |
292 text.focus_set() | |
293 | |
294 def show_replace(): | |
295 text.tag_add(SEL, "1.0", END) | |
296 replace(text) | |
297 text.tag_remove(SEL, "1.0", END) | |
298 | |
299 button = Button(frame, text="Replace", command=show_replace) | |
300 button.pack() | |
301 | |
302 if __name__ == '__main__': | |
303 from unittest import main | |
304 main('idlelib.idle_test.test_replace', verbosity=2, exit=False) | |
305 | |
306 from idlelib.idle_test.htest import run | |
307 run(_replace_dialog) |