1import fnmatch
2import os
3import sys
4
5from tkinter import StringVar, BooleanVar
6from tkinter.ttk import Checkbutton
7
8from idlelib.searchbase import SearchDialogBase
9from idlelib import searchengine
10
11# Importing OutputWindow here fails due to import loop
12# EditorWindow -> GrepDialop -> OutputWindow -> EditorWindow
13
14def grep(text, io=None, flist=None):
15    root = text._root()
16    engine = searchengine.get(root)
17    if not hasattr(engine, "_grepdialog"):
18        engine._grepdialog = GrepDialog(root, engine, flist)
19    dialog = engine._grepdialog
20    searchphrase = text.get("sel.first", "sel.last")
21    dialog.open(text, searchphrase, io)
22
23class GrepDialog(SearchDialogBase):
24
25    title = "Find in Files Dialog"
26    icon = "Grep"
27    needwrapbutton = 0
28
29    def __init__(self, root, engine, flist):
30        SearchDialogBase.__init__(self, root, engine)
31        self.flist = flist
32        self.globvar = StringVar(root)
33        self.recvar = BooleanVar(root)
34
35    def open(self, text, searchphrase, io=None):
36        SearchDialogBase.open(self, text, searchphrase)
37        if io:
38            path = io.filename or ""
39        else:
40            path = ""
41        dir, base = os.path.split(path)
42        head, tail = os.path.splitext(base)
43        if not tail:
44            tail = ".py"
45        self.globvar.set(os.path.join(dir, "*" + tail))
46
47    def create_entries(self):
48        SearchDialogBase.create_entries(self)
49        self.globent = self.make_entry("In files:", self.globvar)[0]
50
51    def create_other_buttons(self):
52        btn = Checkbutton(
53                self.make_frame()[0], variable=self.recvar,
54                text="Recurse down subdirectories")
55        btn.pack(side="top", fill="both")
56
57    def create_command_buttons(self):
58        SearchDialogBase.create_command_buttons(self)
59        self.make_button("Search Files", self.default_command, 1)
60
61    def default_command(self, event=None):
62        prog = self.engine.getprog()
63        if not prog:
64            return
65        path = self.globvar.get()
66        if not path:
67            self.top.bell()
68            return
69        from idlelib.outwin import OutputWindow  # leave here!
70        save = sys.stdout
71        try:
72            sys.stdout = OutputWindow(self.flist)
73            self.grep_it(prog, path)
74        finally:
75            sys.stdout = save
76
77    def grep_it(self, prog, path):
78        dir, base = os.path.split(path)
79        list = self.findfiles(dir, base, self.recvar.get())
80        list.sort()
81        self.close()
82        pat = self.engine.getpat()
83        print("Searching %r in %s ..." % (pat, path))
84        hits = 0
85        try:
86            for fn in list:
87                try:
88                    with open(fn, errors='replace') as f:
89                        for lineno, line in enumerate(f, 1):
90                            if line[-1:] == '\n':
91                                line = line[:-1]
92                            if prog.search(line):
93                                sys.stdout.write("%s: %s: %s\n" %
94                                                 (fn, lineno, line))
95                                hits += 1
96                except OSError as msg:
97                    print(msg)
98            print(("Hits found: %s\n"
99                  "(Hint: right-click to open locations.)"
100                  % hits) if hits else "No hits.")
101        except AttributeError:
102            # Tk window has been closed, OutputWindow.text = None,
103            # so in OW.write, OW.text.insert fails.
104            pass
105
106    def findfiles(self, dir, base, rec):
107        try:
108            names = os.listdir(dir or os.curdir)
109        except OSError as msg:
110            print(msg)
111            return []
112        list = []
113        subdirs = []
114        for name in names:
115            fn = os.path.join(dir, name)
116            if os.path.isdir(fn):
117                subdirs.append(fn)
118            else:
119                if fnmatch.fnmatch(name, base):
120                    list.append(fn)
121        if rec:
122            for subdir in subdirs:
123                list.extend(self.findfiles(subdir, base, rec))
124        return list
125
126    def close(self, event=None):
127        if self.top:
128            self.top.grab_release()
129            self.top.withdraw()
130
131
132def _grep_dialog(parent):  # htest #
133    from tkinter import Toplevel, Text, SEL, END
134    from tkinter.ttk import Button
135    from idlelib.pyshell import PyShellFileList
136    top = Toplevel(parent)
137    top.title("Test GrepDialog")
138    x, y = map(int, parent.geometry().split('+')[1:])
139    top.geometry("+%d+%d" % (x, y + 175))
140
141    flist = PyShellFileList(top)
142    text = Text(top, height=5)
143    text.pack()
144
145    def show_grep_dialog():
146        text.tag_add(SEL, "1.0", END)
147        grep(text, flist=flist)
148        text.tag_remove(SEL, "1.0", END)
149
150    button = Button(top, text="Show GrepDialog", command=show_grep_dialog)
151    button.pack()
152
153if __name__ == "__main__":
154    import unittest
155    unittest.main('idlelib.idle_test.test_grep', verbosity=2, exit=False)
156
157    from idlelib.idle_test.htest import run
158    run(_grep_dialog)
159