[3.7] bpo-23216: IDLE: Add docstrings to search modules (GH-12141) by miss-islington · Pull Request #12376 · python/cpython
@@ -1,7 +1,7 @@
"""Replace dialog for IDLE. Inherits SearchDialogBase for GUI.
Uses idlelib.SearchEngine for search capability.
Uses idlelib.searchengine.SearchEngine for search capability.
Defines various replace related functions like replace, replace all,
replace+find.
and replace+find.
"""
import re
Expand All @@ -10,9 +10,16 @@ from idlelib.searchbase import SearchDialogBase from idlelib import searchengine
def replace(text): """Returns a singleton ReplaceDialog instance.The single dialog saves user entries and preferences across instances.""" """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"): Expand All @@ -22,16 +29,36 @@ def replace(text):
class ReplaceDialog(SearchDialogBase): "Dialog for finding and replacing a pattern in text."
title = "Replace Dialog" icon = "Replace"
def __init__(self, root, engine): SearchDialogBase.__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): """Display the replace dialog""" """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") Expand All @@ -44,37 +71,50 @@ def open(self, text): first = first or text.index("insert") last = last or first self.show_hit(first, last) self.ok = 1 self.ok = True
def create_entries(self): """Create label and text entry widgets""" "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, 1) self.make_button("Replace+Find", self.default_command, isdef=True) self.make_button("Replace All", self.replace_all)
def find_it(self, event=None): self.do_find(0) "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): "Replace and find next." """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(0) self.do_find(False)
def _replace_expand(self, m, repl): """ Helper function for expanding a regular expression in the replace field, if needed. """ "Expand replacement text if regular expression." if self.engine.isre(): try: new = m.expand(repl) Expand All @@ -87,7 +127,15 @@ def _replace_expand(self, m, repl): return new
def replace_all(self, event=None): """Replace all instances of patvar with replvar in text""" """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 Expand All @@ -104,12 +152,13 @@ def replace_all(self, event=None): if self.engine.iswrap(): line = 1 col = 0 ok = 1 ok = True first = last = None # XXX ought to replace circular instead of top-to-bottom when wrapping text.undo_block_start() while 1: res = self.engine.search_forward(text, prog, line, col, 0, ok) while True: res = self.engine.search_forward(text, prog, line, col, wrap=False, ok=ok) if not res: break line, m = res Expand All @@ -130,13 +179,17 @@ def replace_all(self, event=None): if new: text.insert(first, new) col = i + len(new) ok = 0 ok = False text.undo_block_stop() if first and last: self.show_hit(first, last) self.close()
def do_find(self, ok=0): 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 Expand All @@ -149,10 +202,11 @@ def do_find(self, ok=0): first = "%d.%d" % (line, i) last = "%d.%d" % (line, j) self.show_hit(first, last) self.ok = 1 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 Expand Down Expand Up @@ -180,12 +234,20 @@ def do_replace(self): text.insert(first, new) text.undo_block_stop() self.show_hit(first, text.index("insert")) self.ok = 0 self.ok = False return True
def show_hit(self, first, last): """Highlight text from 'first' to 'last'. 'first', 'last' - Text indices""" """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") Expand All @@ -199,6 +261,7 @@ def show_hit(self, first, last): 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")
Expand Down
Expand All @@ -10,9 +10,16 @@ from idlelib.searchbase import SearchDialogBase from idlelib import searchengine
def replace(text): """Returns a singleton ReplaceDialog instance.The single dialog saves user entries and preferences across instances.""" """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"): Expand All @@ -22,16 +29,36 @@ def replace(text):
class ReplaceDialog(SearchDialogBase): "Dialog for finding and replacing a pattern in text."
title = "Replace Dialog" icon = "Replace"
def __init__(self, root, engine): SearchDialogBase.__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): """Display the replace dialog""" """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") Expand All @@ -44,37 +71,50 @@ def open(self, text): first = first or text.index("insert") last = last or first self.show_hit(first, last) self.ok = 1 self.ok = True
def create_entries(self): """Create label and text entry widgets""" "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, 1) self.make_button("Replace+Find", self.default_command, isdef=True) self.make_button("Replace All", self.replace_all)
def find_it(self, event=None): self.do_find(0) "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): "Replace and find next." """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(0) self.do_find(False)
def _replace_expand(self, m, repl): """ Helper function for expanding a regular expression in the replace field, if needed. """ "Expand replacement text if regular expression." if self.engine.isre(): try: new = m.expand(repl) Expand All @@ -87,7 +127,15 @@ def _replace_expand(self, m, repl): return new
def replace_all(self, event=None): """Replace all instances of patvar with replvar in text""" """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 Expand All @@ -104,12 +152,13 @@ def replace_all(self, event=None): if self.engine.iswrap(): line = 1 col = 0 ok = 1 ok = True first = last = None # XXX ought to replace circular instead of top-to-bottom when wrapping text.undo_block_start() while 1: res = self.engine.search_forward(text, prog, line, col, 0, ok) while True: res = self.engine.search_forward(text, prog, line, col, wrap=False, ok=ok) if not res: break line, m = res Expand All @@ -130,13 +179,17 @@ def replace_all(self, event=None): if new: text.insert(first, new) col = i + len(new) ok = 0 ok = False text.undo_block_stop() if first and last: self.show_hit(first, last) self.close()
def do_find(self, ok=0): 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 Expand All @@ -149,10 +202,11 @@ def do_find(self, ok=0): first = "%d.%d" % (line, i) last = "%d.%d" % (line, j) self.show_hit(first, last) self.ok = 1 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 Expand Down Expand Up @@ -180,12 +234,20 @@ def do_replace(self): text.insert(first, new) text.undo_block_stop() self.show_hit(first, text.index("insert")) self.ok = 0 self.ok = False return True
def show_hit(self, first, last): """Highlight text from 'first' to 'last'. 'first', 'last' - Text indices""" """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") Expand All @@ -199,6 +261,7 @@ def show_hit(self, first, last): 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")
Expand Down