• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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