codekingpro/portable-devtools
114k
1"""Grep dialog for Find in Files functionality.2 3 Inherits from SearchDialogBase for GUI and uses searchengine4 to prepare search pattern.5"""6import fnmatch7import os8import sys9 10from tkinter import StringVar, BooleanVar11from tkinter.ttk import Checkbutton # Frame imported in ...Base12 13from idlelib.searchbase import SearchDialogBase14from idlelib import searchengine15 16# Importing OutputWindow here fails due to import loop17# EditorWindow -> GrepDialog -> OutputWindow -> EditorWindow18 19 20def grep(text, io=None, flist=None):21 """Open the Find in Files dialog.22 23 Module-level function to access the singleton GrepDialog24 instance and open the dialog. If text is selected, it is25 used as the search phrase; otherwise, the previous entry26 is used.27 28 Args:29 text: Text widget that contains the selected text for30 default search phrase.31 io: iomenu.IOBinding instance with default path to search.32 flist: filelist.FileList instance for OutputWindow parent.33 """34 root = text._root()35 engine = searchengine.get(root)36 if not hasattr(engine, "_grepdialog"):37 engine._grepdialog = GrepDialog(root, engine, flist)38 dialog = engine._grepdialog39 searchphrase = text.get("sel.first", "sel.last")40 dialog.open(text, searchphrase, io)41 42 43def walk_error(msg):44 "Handle os.walk error."45 print(msg)46 47 48def findfiles(folder, pattern, recursive):49 """Generate file names in dir that match pattern.50 51 Args:52 folder: Root directory to search.53 pattern: File pattern to match.54 recursive: True to include subdirectories.55 """56 for dirpath, _, filenames in os.walk(folder, onerror=walk_error):57 yield from (os.path.join(dirpath, name)58 for name in filenames59 if fnmatch.fnmatch(name, pattern))60 if not recursive:61 break62 63 64class GrepDialog(SearchDialogBase):65 "Dialog for searching multiple files."66 67 title = "Find in Files Dialog"68 icon = "Grep"69 needwrapbutton = 070 71 def __init__(self, root, engine, flist):72 """Create search dialog for searching for a phrase in the file system.73 74 Uses SearchDialogBase as the basis for the GUI and a75 searchengine instance to prepare the search.76 77 Attributes:78 flist: filelist.Filelist instance for OutputWindow parent.79 globvar: String value of Entry widget for path to search.80 globent: Entry widget for globvar. Created in81 create_entries().82 recvar: Boolean value of Checkbutton widget for83 traversing through subdirectories.84 """85 super().__init__(root, engine)86 self.flist = flist87 self.globvar = StringVar(root)88 self.recvar = BooleanVar(root)89 90 def open(self, text, searchphrase, io=None):91 """Make dialog visible on top of others and ready to use.92 93 Extend the SearchDialogBase open() to set the initial value94 for globvar.95 96 Args:97 text: Multicall object containing the text information.98 searchphrase: String phrase to search.99 io: iomenu.IOBinding instance containing file path.100 """101 SearchDialogBase.open(self, text, searchphrase)102 if io:103 path = io.filename or ""104 else:105 path = ""106 dir, base = os.path.split(path)107 head, tail = os.path.splitext(base)108 if not tail:109 tail = ".py"110 self.globvar.set(os.path.join(dir, "*" + tail))111 112 def create_entries(self):113 "Create base entry widgets and add widget for search path."114 SearchDialogBase.create_entries(self)115 self.globent = self.make_entry("In files:", self.globvar)[0]116 117 def create_other_buttons(self):118 "Add check button to recurse down subdirectories."119 btn = Checkbutton(120 self.make_frame()[0], variable=self.recvar,121 text="Recurse down subdirectories")122 btn.pack(side="top", fill="both")123 124 def create_command_buttons(self):125 "Create base command buttons and add button for Search Files."126 SearchDialogBase.create_command_buttons(self)127 self.make_button("Search Files", self.default_command, isdef=True)128 129 def default_command(self, event=None):130 """Grep for search pattern in file path. The default command is bound131 to <Return>.132 133 If entry values are populated, set OutputWindow as stdout134 and perform search. The search dialog is closed automatically135 when the search begins.136 """137 prog = self.engine.getprog()138 if not prog:139 return140 path = self.globvar.get()141 if not path:142 self.top.bell()143 return144 from idlelib.outwin import OutputWindow # leave here!145 save = sys.stdout146 try:147 sys.stdout = OutputWindow(self.flist)148 self.grep_it(prog, path)149 finally:150 sys.stdout = save151 152 def grep_it(self, prog, path):153 """Search for prog within the lines of the files in path.154 155 For the each file in the path directory, open the file and156 search each line for the matching pattern. If the pattern is157 found, write the file and line information to stdout (which158 is an OutputWindow).159 160 Args:161 prog: The compiled, cooked search pattern.162 path: String containing the search path.163 """164 folder, filepat = os.path.split(path)165 if not folder:166 folder = os.curdir167 filelist = sorted(findfiles(folder, filepat, self.recvar.get()))168 self.close()169 pat = self.engine.getpat()170 print(f"Searching {pat!r} in {path} ...")171 hits = 0172 try:173 for fn in filelist:174 try:175 with open(fn, errors='replace') as f:176 for lineno, line in enumerate(f, 1):177 if line[-1:] == '\n':178 line = line[:-1]179 if prog.search(line):180 sys.stdout.write(f"{fn}: {lineno}: {line}\n")181 hits += 1182 except OSError as msg:183 print(msg)184 print(f"Hits found: {hits}\n(Hint: right-click to open locations.)"185 if hits else "No hits.")186 except AttributeError:187 # Tk window has been closed, OutputWindow.text = None,188 # so in OW.write, OW.text.insert fails.189 pass190 191 192def _grep_dialog(parent): # htest #193 from tkinter import Toplevel, Text, SEL194 from tkinter.ttk import Frame, Button195 from idlelib.pyshell import PyShellFileList196 197 top = Toplevel(parent)198 top.title("Test GrepDialog")199 x, y = map(int, parent.geometry().split('+')[1:])200 top.geometry(f"+{x}+{y + 175}")201 202 flist = PyShellFileList(top)203 frame = Frame(top)204 frame.pack()205 text = Text(frame, height=5)206 text.pack()207 text.insert('1.0', 'import grep')208 209 def show_grep_dialog():210 text.tag_add(SEL, "1.0", '1.end')211 grep(text, flist=flist)212 text.tag_remove(SEL, "1.0", '1.end')213 214 button = Button(frame, text="Show GrepDialog", command=show_grep_dialog)215 button.pack()216 217 218if __name__ == "__main__":219 from unittest import main220 main('idlelib.idle_test.test_grep', verbosity=2, exit=False)221 222 from idlelib.idle_test.htest import run223 run(_grep_dialog)224 