diff CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/idlelib/replace.py @ 68:5028fdace37b

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 16:23:26 -0400
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/idlelib/replace.py	Tue Mar 18 16:23:26 2025 -0400
@@ -0,0 +1,307 @@
+"""Replace dialog for IDLE. Inherits SearchDialogBase for GUI.
+Uses idlelib.searchengine.SearchEngine for search capability.
+Defines various replace related functions like replace, replace all,
+and replace+find.
+"""
+import re
+
+from tkinter import StringVar, TclError
+
+from idlelib.searchbase import SearchDialogBase
+from idlelib import searchengine
+
+
+def replace(text):
+    """Create or reuse a singleton ReplaceDialog instance.
+
+    The singleton dialog saves user entries and preferences
+    across instances.
+
+    Args:
+        text: Text widget containing the text to be searched.
+    """
+    root = text._root()
+    engine = searchengine.get(root)
+    if not hasattr(engine, "_replacedialog"):
+        engine._replacedialog = ReplaceDialog(root, engine)
+    dialog = engine._replacedialog
+    dialog.open(text)
+
+
+class ReplaceDialog(SearchDialogBase):
+    "Dialog for finding and replacing a pattern in text."
+
+    title = "Replace Dialog"
+    icon = "Replace"
+
+    def __init__(self, root, engine):
+        """Create search dialog for finding and replacing text.
+
+        Uses SearchDialogBase as the basis for the GUI and a
+        searchengine instance to prepare the search.
+
+        Attributes:
+            replvar: StringVar containing 'Replace with:' value.
+            replent: Entry widget for replvar.  Created in
+                create_entries().
+            ok: Boolean used in searchengine.search_text to indicate
+                whether the search includes the selection.
+        """
+        super().__init__(root, engine)
+        self.replvar = StringVar(root)
+
+    def open(self, text):
+        """Make dialog visible on top of others and ready to use.
+
+        Also, highlight the currently selected text and set the
+        search to include the current selection (self.ok).
+
+        Args:
+            text: Text widget being searched.
+        """
+        SearchDialogBase.open(self, text)
+        try:
+            first = text.index("sel.first")
+        except TclError:
+            first = None
+        try:
+            last = text.index("sel.last")
+        except TclError:
+            last = None
+        first = first or text.index("insert")
+        last = last or first
+        self.show_hit(first, last)
+        self.ok = True
+
+    def create_entries(self):
+        "Create base and additional label and text entry widgets."
+        SearchDialogBase.create_entries(self)
+        self.replent = self.make_entry("Replace with:", self.replvar)[0]
+
+    def create_command_buttons(self):
+        """Create base and additional command buttons.
+
+        The additional buttons are for Find, Replace,
+        Replace+Find, and Replace All.
+        """
+        SearchDialogBase.create_command_buttons(self)
+        self.make_button("Find", self.find_it)
+        self.make_button("Replace", self.replace_it)
+        self.make_button("Replace+Find", self.default_command, isdef=True)
+        self.make_button("Replace All", self.replace_all)
+
+    def find_it(self, event=None):
+        "Handle the Find button."
+        self.do_find(False)
+
+    def replace_it(self, event=None):
+        """Handle the Replace button.
+
+        If the find is successful, then perform replace.
+        """
+        if self.do_find(self.ok):
+            self.do_replace()
+
+    def default_command(self, event=None):
+        """Handle the Replace+Find button as the default command.
+
+        First performs a replace and then, if the replace was
+        successful, a find next.
+        """
+        if self.do_find(self.ok):
+            if self.do_replace():  # Only find next match if replace succeeded.
+                                   # A bad re can cause it to fail.
+                self.do_find(False)
+
+    def _replace_expand(self, m, repl):
+        "Expand replacement text if regular expression."
+        if self.engine.isre():
+            try:
+                new = m.expand(repl)
+            except re.error:
+                self.engine.report_error(repl, 'Invalid Replace Expression')
+                new = None
+        else:
+            new = repl
+
+        return new
+
+    def replace_all(self, event=None):
+        """Handle the Replace All button.
+
+        Search text for occurrences of the Find value and replace
+        each of them.  The 'wrap around' value controls the start
+        point for searching.  If wrap isn't set, then the searching
+        starts at the first occurrence after the current selection;
+        if wrap is set, the replacement starts at the first line.
+        The replacement is always done top-to-bottom in the text.
+        """
+        prog = self.engine.getprog()
+        if not prog:
+            return
+        repl = self.replvar.get()
+        text = self.text
+        res = self.engine.search_text(text, prog)
+        if not res:
+            self.bell()
+            return
+        text.tag_remove("sel", "1.0", "end")
+        text.tag_remove("hit", "1.0", "end")
+        line = res[0]
+        col = res[1].start()
+        if self.engine.iswrap():
+            line = 1
+            col = 0
+        ok = True
+        first = last = None
+        # XXX ought to replace circular instead of top-to-bottom when wrapping
+        text.undo_block_start()
+        while True:
+            res = self.engine.search_forward(text, prog, line, col,
+                                             wrap=False, ok=ok)
+            if not res:
+                break
+            line, m = res
+            chars = text.get("%d.0" % line, "%d.0" % (line+1))
+            orig = m.group()
+            new = self._replace_expand(m, repl)
+            if new is None:
+                break
+            i, j = m.span()
+            first = "%d.%d" % (line, i)
+            last = "%d.%d" % (line, j)
+            if new == orig:
+                text.mark_set("insert", last)
+            else:
+                text.mark_set("insert", first)
+                if first != last:
+                    text.delete(first, last)
+                if new:
+                    text.insert(first, new)
+            col = i + len(new)
+            ok = False
+        text.undo_block_stop()
+        if first and last:
+            self.show_hit(first, last)
+        self.close()
+
+    def do_find(self, ok=False):
+        """Search for and highlight next occurrence of pattern in text.
+
+        No text replacement is done with this option.
+        """
+        if not self.engine.getprog():
+            return False
+        text = self.text
+        res = self.engine.search_text(text, None, ok)
+        if not res:
+            self.bell()
+            return False
+        line, m = res
+        i, j = m.span()
+        first = "%d.%d" % (line, i)
+        last = "%d.%d" % (line, j)
+        self.show_hit(first, last)
+        self.ok = True
+        return True
+
+    def do_replace(self):
+        "Replace search pattern in text with replacement value."
+        prog = self.engine.getprog()
+        if not prog:
+            return False
+        text = self.text
+        try:
+            first = pos = text.index("sel.first")
+            last = text.index("sel.last")
+        except TclError:
+            pos = None
+        if not pos:
+            first = last = pos = text.index("insert")
+        line, col = searchengine.get_line_col(pos)
+        chars = text.get("%d.0" % line, "%d.0" % (line+1))
+        m = prog.match(chars, col)
+        if not prog:
+            return False
+        new = self._replace_expand(m, self.replvar.get())
+        if new is None:
+            return False
+        text.mark_set("insert", first)
+        text.undo_block_start()
+        if m.group():
+            text.delete(first, last)
+        if new:
+            text.insert(first, new)
+        text.undo_block_stop()
+        self.show_hit(first, text.index("insert"))
+        self.ok = False
+        return True
+
+    def show_hit(self, first, last):
+        """Highlight text between first and last indices.
+
+        Text is highlighted via the 'hit' tag and the marked
+        section is brought into view.
+
+        The colors from the 'hit' tag aren't currently shown
+        when the text is displayed.  This is due to the 'sel'
+        tag being added first, so the colors in the 'sel'
+        config are seen instead of the colors for 'hit'.
+        """
+        text = self.text
+        text.mark_set("insert", first)
+        text.tag_remove("sel", "1.0", "end")
+        text.tag_add("sel", first, last)
+        text.tag_remove("hit", "1.0", "end")
+        if first == last:
+            text.tag_add("hit", first)
+        else:
+            text.tag_add("hit", first, last)
+        text.see("insert")
+        text.update_idletasks()
+
+    def close(self, event=None):
+        "Close the dialog and remove hit tags."
+        SearchDialogBase.close(self, event)
+        self.text.tag_remove("hit", "1.0", "end")
+
+
+def _replace_dialog(parent):  # htest #
+    from tkinter import Toplevel, Text, END, SEL
+    from tkinter.ttk import Frame, Button
+
+    top = Toplevel(parent)
+    top.title("Test ReplaceDialog")
+    x, y = map(int, parent.geometry().split('+')[1:])
+    top.geometry("+%d+%d" % (x, y + 175))
+
+    # mock undo delegator methods
+    def undo_block_start():
+        pass
+
+    def undo_block_stop():
+        pass
+
+    frame = Frame(top)
+    frame.pack()
+    text = Text(frame, inactiveselectbackground='gray')
+    text.undo_block_start = undo_block_start
+    text.undo_block_stop = undo_block_stop
+    text.pack()
+    text.insert("insert","This is a sample sTring\nPlus MORE.")
+    text.focus_set()
+
+    def show_replace():
+        text.tag_add(SEL, "1.0", END)
+        replace(text)
+        text.tag_remove(SEL, "1.0", END)
+
+    button = Button(frame, text="Replace", command=show_replace)
+    button.pack()
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_replace', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(_replace_dialog)