Commit 0bb5e75c authored by Cheryl Sabella's avatar Cheryl Sabella Committed by GitHub

bpo-23216: IDLE: Add docstrings to search modules (GH-12141)

parent 7c994549
......@@ -14,11 +14,16 @@ from idlelib.searchbase import SearchDialogBase
from idlelib import searchengine
# Importing OutputWindow here fails due to import loop
# EditorWindow -> GrepDialop -> OutputWindow -> EditorWindow
# EditorWindow -> GrepDialog -> OutputWindow -> EditorWindow
def grep(text, io=None, flist=None):
"""Create or find singleton GrepDialog instance.
"""Open the Find in Files dialog.
Module-level function to access the singleton GrepDialog
instance and open the dialog. If text is selected, it is
used as the search phrase; otherwise, the previous entry
is used.
Args:
text: Text widget that contains the selected text for
......@@ -26,7 +31,6 @@ def grep(text, io=None, flist=None):
io: iomenu.IOBinding instance with default path to search.
flist: filelist.FileList instance for OutputWindow parent.
"""
root = text._root()
engine = searchengine.get(root)
if not hasattr(engine, "_grepdialog"):
......@@ -50,17 +54,29 @@ class GrepDialog(SearchDialogBase):
searchengine instance to prepare the search.
Attributes:
globvar: Value of Text Entry widget for path to search.
recvar: Boolean value of Checkbutton widget
for traversing through subdirectories.
flist: filelist.Filelist instance for OutputWindow parent.
globvar: String value of Entry widget for path to search.
globent: Entry widget for globvar. Created in
create_entries().
recvar: Boolean value of Checkbutton widget for
traversing through subdirectories.
"""
SearchDialogBase.__init__(self, root, engine)
super().__init__(root, engine)
self.flist = flist
self.globvar = StringVar(root)
self.recvar = BooleanVar(root)
def open(self, text, searchphrase, io=None):
"Make dialog visible on top of others and ready to use."
"""Make dialog visible on top of others and ready to use.
Extend the SearchDialogBase open() to set the initial value
for globvar.
Args:
text: Multicall object containing the text information.
searchphrase: String phrase to search.
io: iomenu.IOBinding instance containing file path.
"""
SearchDialogBase.open(self, text, searchphrase)
if io:
path = io.filename or ""
......@@ -85,9 +101,9 @@ class GrepDialog(SearchDialogBase):
btn.pack(side="top", fill="both")
def create_command_buttons(self):
"Create base command buttons and add button for search."
"Create base command buttons and add button for Search Files."
SearchDialogBase.create_command_buttons(self)
self.make_button("Search Files", self.default_command, 1)
self.make_button("Search Files", self.default_command, isdef=True)
def default_command(self, event=None):
"""Grep for search pattern in file path. The default command is bound
......@@ -119,6 +135,10 @@ class GrepDialog(SearchDialogBase):
search each line for the matching pattern. If the pattern is
found, write the file and line information to stdout (which
is an OutputWindow).
Args:
prog: The compiled, cooked search pattern.
path: String containing the search path.
"""
dir, base = os.path.split(path)
list = self.findfiles(dir, base, self.recvar.get())
......@@ -149,7 +169,13 @@ class GrepDialog(SearchDialogBase):
def findfiles(self, dir, base, rec):
"""Return list of files in the dir that match the base pattern.
Use the current directory if dir has no value.
If rec is True, recursively iterate through subdirectories.
Args:
dir: Directory path to search.
base: File search pattern.
rec: Boolean for recursive search through subdirectories.
"""
try:
names = os.listdir(dir or os.curdir)
......
"""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
......@@ -10,9 +10,16 @@ from tkinter import StringVar, TclError
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"):
......@@ -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")
......@@ -44,37 +71,50 @@ class ReplaceDialog(SearchDialogBase):
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)
......@@ -87,7 +127,15 @@ class ReplaceDialog(SearchDialogBase):
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
......@@ -104,12 +152,13 @@ class ReplaceDialog(SearchDialogBase):
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
......@@ -130,13 +179,17 @@ class ReplaceDialog(SearchDialogBase):
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
......@@ -149,10 +202,11 @@ class ReplaceDialog(SearchDialogBase):
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
......@@ -180,12 +234,20 @@ class ReplaceDialog(SearchDialogBase):
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")
......@@ -199,6 +261,7 @@ class ReplaceDialog(SearchDialogBase):
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")
......
"""Search dialog for Find, Find Again, and Find Selection
functionality.
Inherits from SearchDialogBase for GUI and uses searchengine
to prepare search pattern.
"""
from tkinter import TclError
from idlelib import searchengine
from idlelib.searchbase import SearchDialogBase
def _setup(text):
"Create or find the singleton SearchDialog instance."
"""Return the new or existing singleton SearchDialog 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, "_searchdialog"):
......@@ -12,31 +25,71 @@ def _setup(text):
return engine._searchdialog
def find(text):
"Handle the editor edit menu item and corresponding event."
"""Open the search dialog.
Module-level function to access the singleton SearchDialog
instance and open the dialog. If text is selected, it is
used as the search phrase; otherwise, the previous entry
is used. No search is done with this command.
"""
pat = text.get("sel.first", "sel.last")
return _setup(text).open(text, pat) # Open is inherited from SDBase.
def find_again(text):
"Handle the editor edit menu item and corresponding event."
"""Repeat the search for the last pattern and preferences.
Module-level function to access the singleton SearchDialog
instance to search again using the user entries and preferences
from the last dialog. If there was no prior search, open the
search dialog; otherwise, perform the search without showing the
dialog.
"""
return _setup(text).find_again(text)
def find_selection(text):
"Handle the editor edit menu item and corresponding event."
"""Search for the selected pattern in the text.
Module-level function to access the singleton SearchDialog
instance to search using the selected text. With a text
selection, perform the search without displaying the dialog.
Without a selection, use the prior entry as the search phrase
and don't display the dialog. If there has been no prior
search, open the search dialog.
"""
return _setup(text).find_selection(text)
class SearchDialog(SearchDialogBase):
"Dialog for finding a pattern in text."
def create_widgets(self):
"Create the base search dialog and add a button for Find Next."
SearchDialogBase.create_widgets(self)
self.make_button("Find Next", self.default_command, 1)
# TODO - why is this here and not in a create_command_buttons?
self.make_button("Find Next", self.default_command, isdef=True)
def default_command(self, event=None):
"Handle the Find Next button as the default command."
if not self.engine.getprog():
return
self.find_again(self.text)
def find_again(self, text):
"""Repeat the last search.
If no search was previously run, open a new search dialog. In
this case, no search is done.
If a seach was previously run, the search dialog won't be
shown and the options from the previous search (including the
search pattern) will be used to find the next occurrence
of the pattern. Next is relative based on direction.
Position the window to display the located occurrence in the
text.
Return True if the search was successful and False otherwise.
"""
if not self.engine.getpat():
self.open(text)
return False
......@@ -66,6 +119,13 @@ class SearchDialog(SearchDialogBase):
return False
def find_selection(self, text):
"""Search for selected text with previous dialog preferences.
Instead of using the same pattern for searching (as Find
Again does), this first resets the pattern to the currently
selected text. If the selected text isn't changed, then use
the prior search phrase.
"""
pat = text.get("sel.first", "sel.last")
if pat:
self.engine.setcookedpat(pat)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment