1"""Grep dialog for Find in Files functionality. 2 3 Inherits from SearchDialogBase for GUI and uses searchengine 4 to prepare search pattern. 5""" 6import fnmatch 7import os 8import sys 9 10from tkinter import StringVar, BooleanVar 11from tkinter.ttk import Checkbutton # Frame imported in ...Base 12 13from idlelib.searchbase import SearchDialogBase 14from idlelib import searchengine 15 16# Importing OutputWindow here fails due to import loop 17# EditorWindow -> GrepDialop -> OutputWindow -> EditorWindow 18 19 20def grep(text, io=None, flist=None): 21 """Create or find singleton GrepDialog instance. 22 23 Args: 24 text: Text widget that contains the selected text for 25 default search phrase. 26 io: iomenu.IOBinding instance with default path to search. 27 flist: filelist.FileList instance for OutputWindow parent. 28 """ 29 30 root = text._root() 31 engine = searchengine.get(root) 32 if not hasattr(engine, "_grepdialog"): 33 engine._grepdialog = GrepDialog(root, engine, flist) 34 dialog = engine._grepdialog 35 searchphrase = text.get("sel.first", "sel.last") 36 dialog.open(text, searchphrase, io) 37 38 39class GrepDialog(SearchDialogBase): 40 "Dialog for searching multiple files." 41 42 title = "Find in Files Dialog" 43 icon = "Grep" 44 needwrapbutton = 0 45 46 def __init__(self, root, engine, flist): 47 """Create search dialog for searching for a phrase in the file system. 48 49 Uses SearchDialogBase as the basis for the GUI and a 50 searchengine instance to prepare the search. 51 52 Attributes: 53 globvar: Value of Text Entry widget for path to search. 54 recvar: Boolean value of Checkbutton widget 55 for traversing through subdirectories. 56 """ 57 SearchDialogBase.__init__(self, root, engine) 58 self.flist = flist 59 self.globvar = StringVar(root) 60 self.recvar = BooleanVar(root) 61 62 def open(self, text, searchphrase, io=None): 63 "Make dialog visible on top of others and ready to use." 64 SearchDialogBase.open(self, text, searchphrase) 65 if io: 66 path = io.filename or "" 67 else: 68 path = "" 69 dir, base = os.path.split(path) 70 head, tail = os.path.splitext(base) 71 if not tail: 72 tail = ".py" 73 self.globvar.set(os.path.join(dir, "*" + tail)) 74 75 def create_entries(self): 76 "Create base entry widgets and add widget for search path." 77 SearchDialogBase.create_entries(self) 78 self.globent = self.make_entry("In files:", self.globvar)[0] 79 80 def create_other_buttons(self): 81 "Add check button to recurse down subdirectories." 82 btn = Checkbutton( 83 self.make_frame()[0], variable=self.recvar, 84 text="Recurse down subdirectories") 85 btn.pack(side="top", fill="both") 86 87 def create_command_buttons(self): 88 "Create base command buttons and add button for search." 89 SearchDialogBase.create_command_buttons(self) 90 self.make_button("Search Files", self.default_command, 1) 91 92 def default_command(self, event=None): 93 """Grep for search pattern in file path. The default command is bound 94 to <Return>. 95 96 If entry values are populated, set OutputWindow as stdout 97 and perform search. The search dialog is closed automatically 98 when the search begins. 99 """ 100 prog = self.engine.getprog() 101 if not prog: 102 return 103 path = self.globvar.get() 104 if not path: 105 self.top.bell() 106 return 107 from idlelib.outwin import OutputWindow # leave here! 108 save = sys.stdout 109 try: 110 sys.stdout = OutputWindow(self.flist) 111 self.grep_it(prog, path) 112 finally: 113 sys.stdout = save 114 115 def grep_it(self, prog, path): 116 """Search for prog within the lines of the files in path. 117 118 For the each file in the path directory, open the file and 119 search each line for the matching pattern. If the pattern is 120 found, write the file and line information to stdout (which 121 is an OutputWindow). 122 """ 123 dir, base = os.path.split(path) 124 list = self.findfiles(dir, base, self.recvar.get()) 125 list.sort() 126 self.close() 127 pat = self.engine.getpat() 128 print(f"Searching {pat!r} in {path} ...") 129 hits = 0 130 try: 131 for fn in list: 132 try: 133 with open(fn, errors='replace') as f: 134 for lineno, line in enumerate(f, 1): 135 if line[-1:] == '\n': 136 line = line[:-1] 137 if prog.search(line): 138 sys.stdout.write(f"{fn}: {lineno}: {line}\n") 139 hits += 1 140 except OSError as msg: 141 print(msg) 142 print(f"Hits found: {hits}\n(Hint: right-click to open locations.)" 143 if hits else "No hits.") 144 except AttributeError: 145 # Tk window has been closed, OutputWindow.text = None, 146 # so in OW.write, OW.text.insert fails. 147 pass 148 149 def findfiles(self, dir, base, rec): 150 """Return list of files in the dir that match the base pattern. 151 152 If rec is True, recursively iterate through subdirectories. 153 """ 154 try: 155 names = os.listdir(dir or os.curdir) 156 except OSError as msg: 157 print(msg) 158 return [] 159 list = [] 160 subdirs = [] 161 for name in names: 162 fn = os.path.join(dir, name) 163 if os.path.isdir(fn): 164 subdirs.append(fn) 165 else: 166 if fnmatch.fnmatch(name, base): 167 list.append(fn) 168 if rec: 169 for subdir in subdirs: 170 list.extend(self.findfiles(subdir, base, rec)) 171 return list 172 173 174def _grep_dialog(parent): # htest # 175 from tkinter import Toplevel, Text, SEL, END 176 from tkinter.ttk import Frame, Button 177 from idlelib.pyshell import PyShellFileList 178 179 top = Toplevel(parent) 180 top.title("Test GrepDialog") 181 x, y = map(int, parent.geometry().split('+')[1:]) 182 top.geometry(f"+{x}+{y + 175}") 183 184 flist = PyShellFileList(top) 185 frame = Frame(top) 186 frame.pack() 187 text = Text(frame, height=5) 188 text.pack() 189 190 def show_grep_dialog(): 191 text.tag_add(SEL, "1.0", END) 192 grep(text, flist=flist) 193 text.tag_remove(SEL, "1.0", END) 194 195 button = Button(frame, text="Show GrepDialog", command=show_grep_dialog) 196 button.pack() 197 198if __name__ == "__main__": 199 from unittest import main 200 main('idlelib.idle_test.test_grep', verbosity=2, exit=False) 201 202 from idlelib.idle_test.htest import run 203 run(_grep_dialog) 204