123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- """Grep dialog for Find in Files functionality.
- Inherits from SearchDialogBase for GUI and uses searchengine
- to prepare search pattern.
- """
- import fnmatch
- import os
- import sys
- from tkinter import StringVar, BooleanVar
- from tkinter.ttk import Checkbutton # Frame imported in ...Base
- from idlelib.searchbase import SearchDialogBase
- from idlelib import searchengine
- # Importing OutputWindow here fails due to import loop
- # EditorWindow -> GrepDialog -> OutputWindow -> EditorWindow
- def grep(text, io=None, flist=None):
- """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
- default search phrase.
- 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"):
- engine._grepdialog = GrepDialog(root, engine, flist)
- dialog = engine._grepdialog
- searchphrase = text.get("sel.first", "sel.last")
- dialog.open(text, searchphrase, io)
- def walk_error(msg):
- "Handle os.walk error."
- print(msg)
- def findfiles(folder, pattern, recursive):
- """Generate file names in dir that match pattern.
- Args:
- folder: Root directory to search.
- pattern: File pattern to match.
- recursive: True to include subdirectories.
- """
- for dirpath, _, filenames in os.walk(folder, onerror=walk_error):
- yield from (os.path.join(dirpath, name)
- for name in filenames
- if fnmatch.fnmatch(name, pattern))
- if not recursive:
- break
- class GrepDialog(SearchDialogBase):
- "Dialog for searching multiple files."
- title = "Find in Files Dialog"
- icon = "Grep"
- needwrapbutton = 0
- def __init__(self, root, engine, flist):
- """Create search dialog for searching for a phrase in the file system.
- Uses SearchDialogBase as the basis for the GUI and a
- searchengine instance to prepare the search.
- Attributes:
- 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.
- """
- 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.
- 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 ""
- else:
- path = ""
- dir, base = os.path.split(path)
- head, tail = os.path.splitext(base)
- if not tail:
- tail = ".py"
- self.globvar.set(os.path.join(dir, "*" + tail))
- def create_entries(self):
- "Create base entry widgets and add widget for search path."
- SearchDialogBase.create_entries(self)
- self.globent = self.make_entry("In files:", self.globvar)[0]
- def create_other_buttons(self):
- "Add check button to recurse down subdirectories."
- btn = Checkbutton(
- self.make_frame()[0], variable=self.recvar,
- text="Recurse down subdirectories")
- btn.pack(side="top", fill="both")
- def create_command_buttons(self):
- "Create base command buttons and add button for Search Files."
- SearchDialogBase.create_command_buttons(self)
- 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
- to <Return>.
- If entry values are populated, set OutputWindow as stdout
- and perform search. The search dialog is closed automatically
- when the search begins.
- """
- prog = self.engine.getprog()
- if not prog:
- return
- path = self.globvar.get()
- if not path:
- self.top.bell()
- return
- from idlelib.outwin import OutputWindow # leave here!
- save = sys.stdout
- try:
- sys.stdout = OutputWindow(self.flist)
- self.grep_it(prog, path)
- finally:
- sys.stdout = save
- def grep_it(self, prog, path):
- """Search for prog within the lines of the files in path.
- For the each file in the path directory, open the file and
- 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.
- """
- folder, filepat = os.path.split(path)
- if not folder:
- folder = os.curdir
- filelist = sorted(findfiles(folder, filepat, self.recvar.get()))
- self.close()
- pat = self.engine.getpat()
- print(f"Searching {pat!r} in {path} ...")
- hits = 0
- try:
- for fn in filelist:
- try:
- with open(fn, errors='replace') as f:
- for lineno, line in enumerate(f, 1):
- if line[-1:] == '\n':
- line = line[:-1]
- if prog.search(line):
- sys.stdout.write(f"{fn}: {lineno}: {line}\n")
- hits += 1
- except OSError as msg:
- print(msg)
- print(f"Hits found: {hits}\n(Hint: right-click to open locations.)"
- if hits else "No hits.")
- except AttributeError:
- # Tk window has been closed, OutputWindow.text = None,
- # so in OW.write, OW.text.insert fails.
- pass
- def _grep_dialog(parent): # htest #
- from tkinter import Toplevel, Text, SEL, END
- from tkinter.ttk import Frame, Button
- from idlelib.pyshell import PyShellFileList
- top = Toplevel(parent)
- top.title("Test GrepDialog")
- x, y = map(int, parent.geometry().split('+')[1:])
- top.geometry(f"+{x}+{y + 175}")
- flist = PyShellFileList(top)
- frame = Frame(top)
- frame.pack()
- text = Text(frame, height=5)
- text.pack()
- def show_grep_dialog():
- text.tag_add(SEL, "1.0", END)
- grep(text, flist=flist)
- text.tag_remove(SEL, "1.0", END)
- button = Button(frame, text="Show GrepDialog", command=show_grep_dialog)
- button.pack()
- if __name__ == "__main__":
- from unittest import main
- main('idlelib.idle_test.test_grep', verbosity=2, exit=False)
- from idlelib.idle_test.htest import run
- run(_grep_dialog)
|