blob: e73f2c50396505b91301d337e620d781034051a6 [file] [log] [blame]
Georg Brandl14fc4272008-05-17 18:39:55 +00001from tkinter import *
Guido van Rossum36e0a922007-07-20 04:05:57 +00002
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +00003from idlelib import SearchEngine
4from idlelib.SearchDialogBase import SearchDialogBase
Andrew Svetlov5ad514d2012-08-04 21:38:22 +03005import re
6
David Scherer7aced172000-08-15 01:13:23 +00007
8def replace(text):
9 root = text._root()
10 engine = SearchEngine.get(root)
11 if not hasattr(engine, "_replacedialog"):
12 engine._replacedialog = ReplaceDialog(root, engine)
13 dialog = engine._replacedialog
14 dialog.open(text)
15
Andrew Svetlov5ad514d2012-08-04 21:38:22 +030016
David Scherer7aced172000-08-15 01:13:23 +000017class ReplaceDialog(SearchDialogBase):
18
19 title = "Replace Dialog"
20 icon = "Replace"
21
22 def __init__(self, root, engine):
23 SearchDialogBase.__init__(self, root, engine)
24 self.replvar = StringVar(root)
25
26 def open(self, text):
27 SearchDialogBase.open(self, text)
28 try:
29 first = text.index("sel.first")
30 except TclError:
31 first = None
32 try:
33 last = text.index("sel.last")
34 except TclError:
35 last = None
36 first = first or text.index("insert")
37 last = last or first
38 self.show_hit(first, last)
39 self.ok = 1
40
41 def create_entries(self):
42 SearchDialogBase.create_entries(self)
43 self.replent = self.make_entry("Replace with:", self.replvar)
44
45 def create_command_buttons(self):
46 SearchDialogBase.create_command_buttons(self)
47 self.make_button("Find", self.find_it)
48 self.make_button("Replace", self.replace_it)
49 self.make_button("Replace+Find", self.default_command, 1)
50 self.make_button("Replace All", self.replace_all)
51
52 def find_it(self, event=None):
53 self.do_find(0)
54
55 def replace_it(self, event=None):
56 if self.do_find(self.ok):
57 self.do_replace()
58
59 def default_command(self, event=None):
60 if self.do_find(self.ok):
Andrew Svetlov5ad514d2012-08-04 21:38:22 +030061 if self.do_replace(): # Only find next match if replace succeeded.
62 # A bad re can cause a it to fail.
63 self.do_find(0)
64
65 def _replace_expand(self, m, repl):
66 """ Helper function for expanding a regular expression
67 in the replace field, if needed. """
68 if self.engine.isre():
69 try:
70 new = m.expand(repl)
71 except re.error:
72 self.engine.report_error(repl, 'Invalid Replace Expression')
73 new = None
74 else:
75 new = repl
76
77 return new
David Scherer7aced172000-08-15 01:13:23 +000078
79 def replace_all(self, event=None):
80 prog = self.engine.getprog()
81 if not prog:
82 return
83 repl = self.replvar.get()
84 text = self.text
85 res = self.engine.search_text(text, prog)
86 if not res:
87 text.bell()
88 return
89 text.tag_remove("sel", "1.0", "end")
90 text.tag_remove("hit", "1.0", "end")
91 line = res[0]
92 col = res[1].start()
93 if self.engine.iswrap():
94 line = 1
95 col = 0
96 ok = 1
97 first = last = None
98 # XXX ought to replace circular instead of top-to-bottom when wrapping
99 text.undo_block_start()
100 while 1:
101 res = self.engine.search_forward(text, prog, line, col, 0, ok)
102 if not res:
103 break
104 line, m = res
105 chars = text.get("%d.0" % line, "%d.0" % (line+1))
106 orig = m.group()
Andrew Svetlov5ad514d2012-08-04 21:38:22 +0300107 new = self._replace_expand(m, repl)
108 if new is None:
109 break
David Scherer7aced172000-08-15 01:13:23 +0000110 i, j = m.span()
111 first = "%d.%d" % (line, i)
112 last = "%d.%d" % (line, j)
113 if new == orig:
114 text.mark_set("insert", last)
115 else:
116 text.mark_set("insert", first)
117 if first != last:
118 text.delete(first, last)
119 if new:
120 text.insert(first, new)
121 col = i + len(new)
122 ok = 0
123 text.undo_block_stop()
124 if first and last:
125 self.show_hit(first, last)
Benjamin Petersoncf658c22013-04-03 22:35:12 -0400126 self.close()
David Scherer7aced172000-08-15 01:13:23 +0000127
128 def do_find(self, ok=0):
129 if not self.engine.getprog():
Kurt B. Kaiserce86b102002-09-18 02:56:10 +0000130 return False
David Scherer7aced172000-08-15 01:13:23 +0000131 text = self.text
132 res = self.engine.search_text(text, None, ok)
133 if not res:
134 text.bell()
Kurt B. Kaiserce86b102002-09-18 02:56:10 +0000135 return False
David Scherer7aced172000-08-15 01:13:23 +0000136 line, m = res
137 i, j = m.span()
138 first = "%d.%d" % (line, i)
139 last = "%d.%d" % (line, j)
140 self.show_hit(first, last)
141 self.ok = 1
Kurt B. Kaiserce86b102002-09-18 02:56:10 +0000142 return True
David Scherer7aced172000-08-15 01:13:23 +0000143
144 def do_replace(self):
145 prog = self.engine.getprog()
146 if not prog:
Kurt B. Kaiserce86b102002-09-18 02:56:10 +0000147 return False
David Scherer7aced172000-08-15 01:13:23 +0000148 text = self.text
149 try:
150 first = pos = text.index("sel.first")
151 last = text.index("sel.last")
152 except TclError:
153 pos = None
154 if not pos:
155 first = last = pos = text.index("insert")
156 line, col = SearchEngine.get_line_col(pos)
157 chars = text.get("%d.0" % line, "%d.0" % (line+1))
158 m = prog.match(chars, col)
159 if not prog:
Kurt B. Kaiserce86b102002-09-18 02:56:10 +0000160 return False
Andrew Svetlov5ad514d2012-08-04 21:38:22 +0300161 new = self._replace_expand(m, self.replvar.get())
162 if new is None:
163 return False
David Scherer7aced172000-08-15 01:13:23 +0000164 text.mark_set("insert", first)
165 text.undo_block_start()
166 if m.group():
167 text.delete(first, last)
168 if new:
169 text.insert(first, new)
170 text.undo_block_stop()
171 self.show_hit(first, text.index("insert"))
172 self.ok = 0
Kurt B. Kaiserce86b102002-09-18 02:56:10 +0000173 return True
David Scherer7aced172000-08-15 01:13:23 +0000174
David Scherer7aced172000-08-15 01:13:23 +0000175 def show_hit(self, first, last):
176 text = self.text
177 text.mark_set("insert", first)
178 text.tag_remove("sel", "1.0", "end")
179 text.tag_add("sel", first, last)
180 text.tag_remove("hit", "1.0", "end")
181 if first == last:
182 text.tag_add("hit", first)
183 else:
184 text.tag_add("hit", first, last)
185 text.see("insert")
186 text.update_idletasks()
187
188 def close(self, event=None):
189 SearchDialogBase.close(self, event)
190 self.text.tag_remove("hit", "1.0", "end")