blob: 8cc293c380dec67f0fb3bc3819976b8100a5d9d8 [file] [log] [blame]
csabella65474b92017-06-27 02:41:08 -04001"""Grep dialog for Find in Files functionality.
2
3 Inherits from SearchDialogBase for GUI and uses searchengine
4 to prepare search pattern.
5"""
David Scherer7aced172000-08-15 01:13:23 +00006import fnmatch
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04007import os
David Scherer7aced172000-08-15 01:13:23 +00008import sys
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04009
Terry Jan Reedy6f7b0f52016-07-10 20:21:31 -040010from tkinter import StringVar, BooleanVar
11from tkinter.ttk import Checkbutton
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040012
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040013from idlelib.searchbase import SearchDialogBase
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040014from idlelib import searchengine
15
16# Importing OutputWindow here fails due to import loop
Terry Jan Reedy47623822014-06-10 02:49:35 -040017# EditorWindow -> GrepDialop -> OutputWindow -> EditorWindow
David Scherer7aced172000-08-15 01:13:23 +000018
csabella65474b92017-06-27 02:41:08 -040019
David Scherer7aced172000-08-15 01:13:23 +000020def grep(text, io=None, flist=None):
csabella65474b92017-06-27 02:41:08 -040021 """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
David Scherer7aced172000-08-15 01:13:23 +000030 root = text._root()
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040031 engine = searchengine.get(root)
David Scherer7aced172000-08-15 01:13:23 +000032 if not hasattr(engine, "_grepdialog"):
33 engine._grepdialog = GrepDialog(root, engine, flist)
34 dialog = engine._grepdialog
Kurt B. Kaiseref58adf2003-06-07 03:21:17 +000035 searchphrase = text.get("sel.first", "sel.last")
36 dialog.open(text, searchphrase, io)
David Scherer7aced172000-08-15 01:13:23 +000037
csabella65474b92017-06-27 02:41:08 -040038
David Scherer7aced172000-08-15 01:13:23 +000039class GrepDialog(SearchDialogBase):
csabella65474b92017-06-27 02:41:08 -040040 "Dialog for searching multiple files."
David Scherer7aced172000-08-15 01:13:23 +000041
42 title = "Find in Files Dialog"
43 icon = "Grep"
44 needwrapbutton = 0
45
46 def __init__(self, root, engine, flist):
csabella65474b92017-06-27 02:41:08 -040047 """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 """
David Scherer7aced172000-08-15 01:13:23 +000057 SearchDialogBase.__init__(self, root, engine)
58 self.flist = flist
59 self.globvar = StringVar(root)
60 self.recvar = BooleanVar(root)
61
Kurt B. Kaiseref58adf2003-06-07 03:21:17 +000062 def open(self, text, searchphrase, io=None):
csabella65474b92017-06-27 02:41:08 -040063 "Make dialog visible on top of others and ready to use."
Kurt B. Kaiseref58adf2003-06-07 03:21:17 +000064 SearchDialogBase.open(self, text, searchphrase)
David Scherer7aced172000-08-15 01:13:23 +000065 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):
csabella65474b92017-06-27 02:41:08 -040076 "Create base entry widgets and add widget for search path."
David Scherer7aced172000-08-15 01:13:23 +000077 SearchDialogBase.create_entries(self)
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -040078 self.globent = self.make_entry("In files:", self.globvar)[0]
David Scherer7aced172000-08-15 01:13:23 +000079
80 def create_other_buttons(self):
csabella65474b92017-06-27 02:41:08 -040081 "Add check button to recurse down subdirectories."
Terry Jan Reedy6f7b0f52016-07-10 20:21:31 -040082 btn = Checkbutton(
83 self.make_frame()[0], variable=self.recvar,
David Scherer7aced172000-08-15 01:13:23 +000084 text="Recurse down subdirectories")
85 btn.pack(side="top", fill="both")
David Scherer7aced172000-08-15 01:13:23 +000086
87 def create_command_buttons(self):
csabella65474b92017-06-27 02:41:08 -040088 "Create base command buttons and add button for search."
David Scherer7aced172000-08-15 01:13:23 +000089 SearchDialogBase.create_command_buttons(self)
90 self.make_button("Search Files", self.default_command, 1)
91
92 def default_command(self, event=None):
csabella65474b92017-06-27 02:41:08 -040093 """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 """
David Scherer7aced172000-08-15 01:13:23 +0000100 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
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400107 from idlelib.outwin import OutputWindow # leave here!
David Scherer7aced172000-08-15 01:13:23 +0000108 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):
csabella65474b92017-06-27 02:41:08 -0400116 """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 """
David Scherer7aced172000-08-15 01:13:23 +0000123 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()
csabella65474b92017-06-27 02:41:08 -0400128 print(f"Searching {pat!r} in {path} ...")
David Scherer7aced172000-08-15 01:13:23 +0000129 hits = 0
Terry Jan Reedy47623822014-06-10 02:49:35 -0400130 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):
csabella65474b92017-06-27 02:41:08 -0400138 sys.stdout.write(f"{fn}: {lineno}: {line}\n")
Terry Jan Reedy47623822014-06-10 02:49:35 -0400139 hits += 1
140 except OSError as msg:
141 print(msg)
csabella65474b92017-06-27 02:41:08 -0400142 print(f"Hits found: {hits}\n(Hint: right-click to open locations.)"
143 if hits else "No hits.")
Terry Jan Reedy47623822014-06-10 02:49:35 -0400144 except AttributeError:
145 # Tk window has been closed, OutputWindow.text = None,
146 # so in OW.write, OW.text.insert fails.
147 pass
David Scherer7aced172000-08-15 01:13:23 +0000148
149 def findfiles(self, dir, base, rec):
csabella65474b92017-06-27 02:41:08 -0400150 """Return list of files in the dir that match the base pattern.
151
152 If rec is True, recursively iterate through subdirectories.
153 """
David Scherer7aced172000-08-15 01:13:23 +0000154 try:
155 names = os.listdir(dir or os.curdir)
Terry Jan Reedyc3111fc2014-02-23 00:37:16 -0500156 except OSError as msg:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000157 print(msg)
David Scherer7aced172000-08-15 01:13:23 +0000158 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
Terry Jan Reedy47623822014-06-10 02:49:35 -0400173
Terry Jan Reedycd567362014-10-17 01:31:35 -0400174def _grep_dialog(parent): # htest #
Terry Jan Reedy6f7b0f52016-07-10 20:21:31 -0400175 from tkinter import Toplevel, Text, SEL, END
176 from tkinter.ttk import Button
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -0400177 from idlelib.pyshell import PyShellFileList
Terry Jan Reedyb60adc52016-06-21 18:41:38 -0400178 top = Toplevel(parent)
179 top.title("Test GrepDialog")
Terry Jan Reedya7480322016-07-10 17:28:10 -0400180 x, y = map(int, parent.geometry().split('+')[1:])
csabella65474b92017-06-27 02:41:08 -0400181 top.geometry(f"+{x}+{y + 175}")
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -0400182
Terry Jan Reedyb60adc52016-06-21 18:41:38 -0400183 flist = PyShellFileList(top)
184 text = Text(top, height=5)
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -0400185 text.pack()
186
187 def show_grep_dialog():
188 text.tag_add(SEL, "1.0", END)
189 grep(text, flist=flist)
190 text.tag_remove(SEL, "1.0", END)
191
Terry Jan Reedyb60adc52016-06-21 18:41:38 -0400192 button = Button(top, text="Show GrepDialog", command=show_grep_dialog)
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -0400193 button.pack()
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -0400194
Terry Jan Reedyde3beb22013-06-22 18:26:51 -0400195if __name__ == "__main__":
Terry Jan Reedyee5ef302018-06-15 18:20:55 -0400196 from unittest import main
197 main('idlelib.idle_test.test_grep', verbosity=2, exit=False)
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -0400198
199 from idlelib.idle_test.htest import run
200 run(_grep_dialog)