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