Table of Contents
- Versions 1.0 to 1.2 -- Personal use only versions
- Version 1.3 -- Initial
inflicted upon release to CodeProject
- Version 1.4 -- Replaced demo.zip with an installable (and new name)
- Version 1.5 -- Added 'Programs' menu item to indicate location of grep.exe, misc cleanup
- Version 1.6 -- Fixed a bug with files that don't have a default system handler
I have always found the command-line utility grep to be useful for brute-force searching of patterns in text files (especially code source!), but the number of options is daunting and when the search is finished, I always have to manually find the matching file and open it up and scroll to the correct line. It just seemed that I was missing something.
The above is slightly more easily read as:
grep --with-filename --line-number -C2 --devices=skip --color=always \
--binary-files=without-match -P --recursive -i --exclude-dir='Debug' \
--exclude-dir='Release' --regexp='toolbar.*color' --include="*.cpp" \
--include="*.c" --include="*.h" .
followed by the output (with coloring).
This is a recursive search in the current directory for C/C++ files, avoiding the Release and Debug directories, for the pattern 'toolbar.*color'. The output has coloring and context to help identify the match. There was a match found on line 76 of MainFrm.cpp. I find this clear as mud.
So my solution is
GrepWrap, which is just a simple wrapper around GNU Grep. Note: in the original version of this article, I was using the name
GrepWin, which is the same as another tool by TortoisesVN.
It has nothing to do with other programs with similar names, such as TortoisesVN grepWin or Windows Grep. In fact, until I started to write this article, I was only vaguely aware of some of these others. However, I have looked at them and they do not meet my particular needs, although they seem to be fine utilities.
Note: I do not include grep.exe in this article as it is freely available by installing Cygwin. I strongly suggest installing Cygwin even if you have no interest in
GrepWrap as it has hundreds of incredibly useful utilities running from the mundane awk program to a complete X-Windows server! And the price is right...
I come from a Unix background where the use of command-line tools rather than a GUI was the normal mode of operation. When I changed jobs to my current employer, I found myself in a Microsoft shop; I was a fish out of water. I had MKS Toolkit on my home PC but my company wasn't interested in spending that kind of money on me at work. I subsequently discovered Cygwin--a large collection of Unix tools ported to the Windows environment. (Cygwin now says that they are emulating a Linux environment, but it looks like Unix to me). So I have my productivity enhancing toolset back again. But staying in the Windows environment, I became very fond of the tendency to have a GUI for everything. There's even a GUI for changing directories! (File Explorer)
The closest GUI I could find for searching through files was Windows Desktop Search. This was agreeably fast, but had its problems such as no regular expressions, no context preview of matching lines, no way to open the matching file at the line of interest, etc. I found a number of programs here on Code Project that emulated grep or called grep from the command line, but while intriguing, were not what I wanted.
So I decided to write my own wrapper. How hard could that be? Just double-park, blast out the trivial amount of code, and be done with it. Famous last words, right?
Using the Code
GrepWrap is easy enough. Basically, you select the starting folder for searching for files. If you have been there before, you can select the folder from a drop-down list. (Sorry, only one starting folder at a time and no wildcards.) You can Cut & Paste the folder name, or browse for it using the Select Folder button.
The "Search" pattern is a Perl-style regular expression. If you have used the pattern before, it is available in the drop-down list.
"Include Files" lists just those file patterns (Window-style file patterns) to search. If you tend to scan through a tree of source code, a pattern like
*.c;*.cpp;*.h may do the job. Previously used patterns are available through the drop-down list. If no files are listed, then all are searched (except binaries and devices).
If you want to exclude certain file patterns, you can do that too. More importantly, if there are directories that you do not want to recurse into, you can list them also. For instance, when searching source code, I generally don't want to recurse into the Debug or Release directories. Or the backup directory. Or Eclipse's .metadata directory, etc., aud nauseum. This can really speed up the search. Binary files are skipped automatically; this tool is intended for text files. It just isn't any good for searching through Word (*.doc) or Excel (*.xls) files. For that, you need something else.
Finally, there are the options. Usually, the only one of interest is "Caseless" mode for those times when you cannot remember if the line of interest had, for instance, "posix" or "POSIX".
At long last, press the "Search" button to start the search. Note: By design, entering a <
Carriage-Return> in a field will not start the search. Too many times, I accidentally started a search before I had set everything up.
For example, suppose I want to setup stuff for the toolbar and I know I have some code that does this. So I search for "TOOLBAR" and there it is:
The match to the regular expression is shown in red and lines that have a "
--" show a context. I can double-click on the line of interest (either context or matching line) and the default application will open. If that application is one of the ones
GrepWrap knows about, the cursor is placed on the line of interest.
Applications Known to GrepWrap
There are also a number of other applications known to
GrepWrap, and accessible via the context menu. The list is short but it is easy enough to add new ones via code modification.
Right now, the list of applications that
GrepWrap will tailor is fairly limited:
- devenv.exe (Visual Studio 2003/2005/2008, and probably 2010)
- XEmacs.exe (yes, I'm one of those...)
- Notepad.exe (ignores the line number, sorry)
Note: The default locations for these programs are hard-coded, but can be overridden in the registry.
Windows Registry Editor Version 5.00
"XEmacs Pathname"="\"C:\\Program Files\\XEmacs\\
"UltraEdit Pathname"="\"C:\\Program Files\\UltraEdit\\uedit32.exe\""
Or you can drag the line of interest to a target and the file will be opened with that target (assuming it supports Drag & Drop, of course). In the case of dropping on File Explorer, a copy of the file is made into the directory that was displayed.
For the specific case of setting the location of grep.exe, use the Programs menu item:
Adding New Targets/Applications
It is easy enough to tailor the behavior for other applications. Just look at the message map and add appropriate declarations for the function to open the application and the function to handle the context menu:
Default pathnames to the program also need to be defined.
And the code to get the latest version of the pathnames:
m_grepPath = AfxGetApp()->GetProfileString("Settings", "Grep Pathname",
m_xemacsPath = AfxGetApp()->GetProfileString("Settings", "XEmacs Pathname",
m_ultraeditPath = AfxGetApp()->GetProfileString("Settings", "UltraEdit Pathname",
m_notepadPath = AfxGetApp()->GetProfileString("Settings", "Notepad Pathname",
m_codewrightPath = AfxGetApp()->GetProfileString("Settings", "CodeWright Pathname",
AfxGetApp()->WriteProfileString("Settings", "Grep Pathname", m_grepPath);
AfxGetApp()->WriteProfileString("Settings", "XEmacs Pathname", m_xemacsPath);
AfxGetApp()->WriteProfileString("Settings", "UltraEdit Pathname", m_ultraeditPath);
AfxGetApp()->WriteProfileString("Settings", "Notepad Pathname", m_notepadPath);
AfxGetApp()->WriteProfileString("Settings", "CodeWright Pathname", m_codewrightPath);
Then define the appropriate code to open the application. Here is the code for
CString filename; int linenumber;
CString execName; CString baseName; CString fileBase; if(!m_GetExecParameters(filename, linenumber, execName, baseName, fileBase))
filename.Replace("/", "\\"); // use Windows-style separators
params.Format("-G%d \"%s\"", linenumber, filename);
m_RunShellExec(filename, m_codewrightPath, params);}
Here is the code for handling the context menu for
void CGrepWrapView::OnUpdateEditOpenUsingCodeWright(CCmdUI *pCmdUI)
CString filename; int linenumber;
CString execName; CString baseName; CString fileBase; m_GetExecParameters(filename, linenumber, execName, baseName, fileBase);
pCmdUI->SetText("Open using: CodeWright -Glinenumber filename");
msg.Format("Open using: CodeWright -G%d %s", linenumber, fileBase);
Are you kidding? Of course there are bugs. But I prefer to call them To Do items.
- Add a mechanism to allow the user to add arbitrary target applications
- Add the ability to trim or clear the history in the drop-down lists
- Add configuration context size (currently fixed at 3 lines before and after match)
Points of Interest
As with any double-park-and-code project, there were some things I wanted to try just to see if I could.
Regular Expressions in VBScript
The use of the regular expression engine in the Microsoft-supplied VBScript component I had seen but never used. I found regexp.h and regexp.cpp on the internet (attributed above) and it worked like a charm. It is not full Perl-style regular expressions, but it does the job and doesn't add a lot of weight to the application. I tend to use regular expressions even when other mechanisms are faster just because I am so used to them.
For instance, to help parse the output of grep (with color coding enabled), I have a series of simple regular expressions that I can use for testing. This one is used to distinguish a line that does not contain a match but is context (this is a paraphrasing of the actual code):
m_dashRe = new RegExp;
This may not be clear if you do not speak regex or termcap, but the pattern just looks for the embedded escape code to turn on the cyan color, followed by
-- followed by the escape code to disable coloring.
Piping Output from a Process
Using a separate process to perform grunt work and passing the results to the "main" process is something I used to do in Unix (before threads), but Joseph M Newcomer's Process.h/cpp, with minor modifications allowed me to do this almost transparently here. This is how grep is wrapped. I did have to add some code to throttle output from grep if I was still updating the screen, but that was not terribly difficult.
Drag & Drop
I've always wanted to try out Drag & Drop but didn't have a clue on how. Here, it was almost embarrassingly easy to drag from
GrepWrap to other applications. Thanks to "benbuck" (attributed above) for dragdrop.h/.cpp which worked with few modifications.
Rich Edit Control
However, the most effort was expended in the Rich Edit Control. I needed to have a constant-width font and the ability to highlight the matching characters for the regular expression. I could probably have hacked something with an embedded Explorer control, but I've done that and not been particular happy with that. It seemed grossly over-powered for my needs. So I had to learn about TOM (
IRichEditOle stuff and the Text Object Model). I also had to deal with being able to scroll the results even as new results were being added. If I didn't do it correctly, the whole screen would start to "quiver" and shake. Not a pretty sight.
The gotchas for the
- Appending text without losing all previous color-formatting
- Allowing cursor position to change while still appending matching text
These turned out to be very hard to do. I am not at all convinced that I have got it right, but it seems to work OK.
Parsing the output of grep was tedious, but I was able to turn on output coloring and parse the special escape sequences to identify the various parts of the output: filename, line number, matching text (of which there might be several!) and context lines (leading and trailing). The number of colors that grep can output is surprising, but I felt that just black and red were needed for this wrapper.
Acknowledgements and References
I am a big fan of code
re-use so I have code from several sources, some of which is seriously mangled from the original. I hope I have not left anybody out.