| """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) |