1from Tkinter import *
2
3from idlelib import SearchEngine
4from idlelib.SearchDialogBase import SearchDialogBase
5import re
6
7
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
16
17class 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)[0]
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):
61            if self.do_replace():   # Only find next match if replace succeeded.
62                                    # A bad re can cause 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        return new
77
78    def replace_all(self, event=None):
79        prog = self.engine.getprog()
80        if not prog:
81            return
82        repl = self.replvar.get()
83        text = self.text
84        res = self.engine.search_text(text, prog)
85        if not res:
86            text.bell()
87            return
88        text.tag_remove("sel", "1.0", "end")
89        text.tag_remove("hit", "1.0", "end")
90        line = res[0]
91        col = res[1].start()
92        if self.engine.iswrap():
93            line = 1
94            col = 0
95        ok = 1
96        first = last = None
97        # XXX ought to replace circular instead of top-to-bottom when wrapping
98        text.undo_block_start()
99        while 1:
100            res = self.engine.search_forward(text, prog, line, col, 0, ok)
101            if not res:
102                break
103            line, m = res
104            chars = text.get("%d.0" % line, "%d.0" % (line+1))
105            orig = m.group()
106            new = self._replace_expand(m, repl)
107            if new is None:
108                break
109            i, j = m.span()
110            first = "%d.%d" % (line, i)
111            last = "%d.%d" % (line, j)
112            if new == orig:
113                text.mark_set("insert", last)
114            else:
115                text.mark_set("insert", first)
116                if first != last:
117                    text.delete(first, last)
118                if new:
119                    text.insert(first, new)
120            col = i + len(new)
121            ok = 0
122        text.undo_block_stop()
123        if first and last:
124            self.show_hit(first, last)
125        self.close()
126
127    def do_find(self, ok=0):
128        if not self.engine.getprog():
129            return False
130        text = self.text
131        res = self.engine.search_text(text, None, ok)
132        if not res:
133            text.bell()
134            return False
135        line, m = res
136        i, j = m.span()
137        first = "%d.%d" % (line, i)
138        last = "%d.%d" % (line, j)
139        self.show_hit(first, last)
140        self.ok = 1
141        return True
142
143    def do_replace(self):
144        prog = self.engine.getprog()
145        if not prog:
146            return False
147        text = self.text
148        try:
149            first = pos = text.index("sel.first")
150            last = text.index("sel.last")
151        except TclError:
152            pos = None
153        if not pos:
154            first = last = pos = text.index("insert")
155        line, col = SearchEngine.get_line_col(pos)
156        chars = text.get("%d.0" % line, "%d.0" % (line+1))
157        m = prog.match(chars, col)
158        if not prog:
159            return False
160        new = self._replace_expand(m, self.replvar.get())
161        if new is None:
162            return False
163        text.mark_set("insert", first)
164        text.undo_block_start()
165        if m.group():
166            text.delete(first, last)
167        if new:
168            text.insert(first, new)
169        text.undo_block_stop()
170        self.show_hit(first, text.index("insert"))
171        self.ok = 0
172        return True
173
174    def show_hit(self, first, last):
175        text = self.text
176        text.mark_set("insert", first)
177        text.tag_remove("sel", "1.0", "end")
178        text.tag_add("sel", first, last)
179        text.tag_remove("hit", "1.0", "end")
180        if first == last:
181            text.tag_add("hit", first)
182        else:
183            text.tag_add("hit", first, last)
184        text.see("insert")
185        text.update_idletasks()
186
187    def close(self, event=None):
188        SearchDialogBase.close(self, event)
189        self.text.tag_remove("hit", "1.0", "end")
190
191def _replace_dialog(parent):
192    root = Tk()
193    root.title("Test ReplaceDialog")
194    width, height, x, y = list(map(int, re.split('[x+]', parent.geometry())))
195    root.geometry("+%d+%d"%(x, y + 150))
196
197    # mock undo delegator methods
198    def undo_block_start():
199        pass
200
201    def undo_block_stop():
202        pass
203
204    text = Text(root)
205    text.undo_block_start = undo_block_start
206    text.undo_block_stop = undo_block_stop
207    text.pack()
208    text.insert("insert","This is a sample string.\n"*10)
209
210    def show_replace():
211        text.tag_add(SEL, "1.0", END)
212        replace(text)
213        text.tag_remove(SEL, "1.0", END)
214
215    button = Button(root, text="Replace", command=show_replace)
216    button.pack()
217
218if __name__ == '__main__':
219    from idlelib.idle_test.htest import run
220    run(_replace_dialog)
221