1"""Replace dialog for IDLE. Inherits SearchDialogBase for GUI. 2Uses idlelib.SearchEngine for search capability. 3Defines various replace related functions like replace, replace all, 4replace+find. 5""" 6import re 7 8from tkinter import StringVar, TclError 9 10from idlelib.searchbase import SearchDialogBase 11from idlelib import searchengine 12 13def replace(text): 14 """Returns a singleton ReplaceDialog instance.The single dialog 15 saves user entries and preferences across instances.""" 16 root = text._root() 17 engine = searchengine.get(root) 18 if not hasattr(engine, "_replacedialog"): 19 engine._replacedialog = ReplaceDialog(root, engine) 20 dialog = engine._replacedialog 21 dialog.open(text) 22 23 24class ReplaceDialog(SearchDialogBase): 25 26 title = "Replace Dialog" 27 icon = "Replace" 28 29 def __init__(self, root, engine): 30 SearchDialogBase.__init__(self, root, engine) 31 self.replvar = StringVar(root) 32 33 def open(self, text): 34 """Display the replace dialog""" 35 SearchDialogBase.open(self, text) 36 try: 37 first = text.index("sel.first") 38 except TclError: 39 first = None 40 try: 41 last = text.index("sel.last") 42 except TclError: 43 last = None 44 first = first or text.index("insert") 45 last = last or first 46 self.show_hit(first, last) 47 self.ok = 1 48 49 def create_entries(self): 50 """Create label and text entry widgets""" 51 SearchDialogBase.create_entries(self) 52 self.replent = self.make_entry("Replace with:", self.replvar)[0] 53 54 def create_command_buttons(self): 55 SearchDialogBase.create_command_buttons(self) 56 self.make_button("Find", self.find_it) 57 self.make_button("Replace", self.replace_it) 58 self.make_button("Replace+Find", self.default_command, 1) 59 self.make_button("Replace All", self.replace_all) 60 61 def find_it(self, event=None): 62 self.do_find(0) 63 64 def replace_it(self, event=None): 65 if self.do_find(self.ok): 66 self.do_replace() 67 68 def default_command(self, event=None): 69 "Replace and find next." 70 if self.do_find(self.ok): 71 if self.do_replace(): # Only find next match if replace succeeded. 72 # A bad re can cause it to fail. 73 self.do_find(0) 74 75 def _replace_expand(self, m, repl): 76 """ Helper function for expanding a regular expression 77 in the replace field, if needed. """ 78 if self.engine.isre(): 79 try: 80 new = m.expand(repl) 81 except re.error: 82 self.engine.report_error(repl, 'Invalid Replace Expression') 83 new = None 84 else: 85 new = repl 86 87 return new 88 89 def replace_all(self, event=None): 90 """Replace all instances of patvar with replvar in text""" 91 prog = self.engine.getprog() 92 if not prog: 93 return 94 repl = self.replvar.get() 95 text = self.text 96 res = self.engine.search_text(text, prog) 97 if not res: 98 self.bell() 99 return 100 text.tag_remove("sel", "1.0", "end") 101 text.tag_remove("hit", "1.0", "end") 102 line = res[0] 103 col = res[1].start() 104 if self.engine.iswrap(): 105 line = 1 106 col = 0 107 ok = 1 108 first = last = None 109 # XXX ought to replace circular instead of top-to-bottom when wrapping 110 text.undo_block_start() 111 while 1: 112 res = self.engine.search_forward(text, prog, line, col, 0, ok) 113 if not res: 114 break 115 line, m = res 116 chars = text.get("%d.0" % line, "%d.0" % (line+1)) 117 orig = m.group() 118 new = self._replace_expand(m, repl) 119 if new is None: 120 break 121 i, j = m.span() 122 first = "%d.%d" % (line, i) 123 last = "%d.%d" % (line, j) 124 if new == orig: 125 text.mark_set("insert", last) 126 else: 127 text.mark_set("insert", first) 128 if first != last: 129 text.delete(first, last) 130 if new: 131 text.insert(first, new) 132 col = i + len(new) 133 ok = 0 134 text.undo_block_stop() 135 if first and last: 136 self.show_hit(first, last) 137 self.close() 138 139 def do_find(self, ok=0): 140 if not self.engine.getprog(): 141 return False 142 text = self.text 143 res = self.engine.search_text(text, None, ok) 144 if not res: 145 self.bell() 146 return False 147 line, m = res 148 i, j = m.span() 149 first = "%d.%d" % (line, i) 150 last = "%d.%d" % (line, j) 151 self.show_hit(first, last) 152 self.ok = 1 153 return True 154 155 def do_replace(self): 156 prog = self.engine.getprog() 157 if not prog: 158 return False 159 text = self.text 160 try: 161 first = pos = text.index("sel.first") 162 last = text.index("sel.last") 163 except TclError: 164 pos = None 165 if not pos: 166 first = last = pos = text.index("insert") 167 line, col = searchengine.get_line_col(pos) 168 chars = text.get("%d.0" % line, "%d.0" % (line+1)) 169 m = prog.match(chars, col) 170 if not prog: 171 return False 172 new = self._replace_expand(m, self.replvar.get()) 173 if new is None: 174 return False 175 text.mark_set("insert", first) 176 text.undo_block_start() 177 if m.group(): 178 text.delete(first, last) 179 if new: 180 text.insert(first, new) 181 text.undo_block_stop() 182 self.show_hit(first, text.index("insert")) 183 self.ok = 0 184 return True 185 186 def show_hit(self, first, last): 187 """Highlight text from 'first' to 'last'. 188 'first', 'last' - Text indices""" 189 text = self.text 190 text.mark_set("insert", first) 191 text.tag_remove("sel", "1.0", "end") 192 text.tag_add("sel", first, last) 193 text.tag_remove("hit", "1.0", "end") 194 if first == last: 195 text.tag_add("hit", first) 196 else: 197 text.tag_add("hit", first, last) 198 text.see("insert") 199 text.update_idletasks() 200 201 def close(self, event=None): 202 SearchDialogBase.close(self, event) 203 self.text.tag_remove("hit", "1.0", "end") 204 205 206def _replace_dialog(parent): # htest # 207 from tkinter import Toplevel, Text, END, SEL 208 from tkinter.ttk import Button 209 210 box = Toplevel(parent) 211 box.title("Test ReplaceDialog") 212 x, y = map(int, parent.geometry().split('+')[1:]) 213 box.geometry("+%d+%d" % (x, y + 175)) 214 215 # mock undo delegator methods 216 def undo_block_start(): 217 pass 218 219 def undo_block_stop(): 220 pass 221 222 text = Text(box, inactiveselectbackground='gray') 223 text.undo_block_start = undo_block_start 224 text.undo_block_stop = undo_block_stop 225 text.pack() 226 text.insert("insert","This is a sample sTring\nPlus MORE.") 227 text.focus_set() 228 229 def show_replace(): 230 text.tag_add(SEL, "1.0", END) 231 replace(text) 232 text.tag_remove(SEL, "1.0", END) 233 234 button = Button(box, text="Replace", command=show_replace) 235 button.pack() 236 237if __name__ == '__main__': 238 import unittest 239 unittest.main('idlelib.idle_test.test_replace', 240 verbosity=2, exit=False) 241 242 from idlelib.idle_test.htest import run 243 run(_replace_dialog) 244