\subsubsection{Idea}

This recipe implements a search and find utility with {\tt find} and {\tt
grep}. It may be used to search a code base of, say, Objective C code for
certain code snippets for use in writing an application. It provides support
for coding by ``copy-and-paste.''

The basic idea is to run {\tt find} on a directory in order to locate a set of
files that match a shell pattern or a regular expression. We run {\tt grep}
for each file and look for a certain pattern. Matching lines including some
context are collected and displayed for viewing. The user may inspect an
entire file if she wishes.

We use a subclass of {\tt NSWindow} to provide these features. Every such
window consists of three components: the upper component where the user enters
the pattern, the regular expression, the shell pattern, the context length and
extra switches for {\tt grep} into two forms. The lower half of the window
contains the second and third component, which are placed in an {\tt
NSSplitView}, namely a scrollview that displays the result of the search and a
scrollview for viewing the contents of a match. There is a button between the
first and the second component. The user clicks this button to execute the
query. It also doubles as a progress indicator during the search, where it
fills with blue starting at the left and moving to the right as the search
progresses.

The results of a query are displayed with buttons and textviews. The button's
title shows what file matched and what line. The textview shows the match
including the context. The program displays the entire file in the lower
scrollview when the user clicks the button. The context is selected and
scrolled so that it is visible. The query thus produces an alternating
sequence of buttons and textviews, one pair for each match.

We need four classes to implement this recipe. The first class is a subclass
of {\tt NSView} called {\tt Flipped.} It implements a view whose coordinate
system has its origin in the upper left corner, with the y-axis extending
downwards. Then there is {\tt RangeButton}, a subclass of {\tt NSButton} that
can store a range. It is used in the view that displays the results of the
query and it stores the location and the length of the range of a match. There
is a controller for interfacing with the application. Finally, the class {\tt
BrowseIt} implements the query window described above.

\subsubsection{Implementation}

Start with the usual headers. The class {\tt Flipped} implents a view with a
flipped coordinate system. We'll be attaching subviews to this view. It does
no drawing itself.

\begin{lstlisting}{}
#include <Foundation/Foundation.h>
#include <AppKit/AppKit.h>

@interface Flipped : NSView

- (BOOL)isFlipped;

@end

@implementation Flipped

- (BOOL)isFlipped
{
    return YES;
}

@end
\end{lstlisting}

The class {\tt RangeButton} is trivial as it only adds an instance variable to
store the range and a method to retrieve it.

\begin{lstlisting}{}
@interface RangeButton : NSButton
{
    NSRange range;
}

- (NSRange)range;
- setRange:(NSRange)aRange;

@end
\end{lstlisting}

The method {\tt range} reads the instance variable and the method {\tt
setRange} writes it.

\begin{lstlisting}{}
@implementation RangeButton

- (NSRange)range
{
    return range;
}

- setRange:(NSRange)aRange
{
    range = aRange;
    return self;
}

@end
\end{lstlisting}

The headers for the controller class and the custom window are next. The
controller reads the location of the {\tt find} and {\tt grep} binaries from
the user defaults on startup and remembers them during the lifetime of the
application. This is the purpose of the two instance variables and the
accessors {\tt find} and {\tt grep}. There is an important method that runs a
command with some arguments and collects its standard output and standard
error streams and returns its exit status. We will be using this method to run
{\tt find} and {\tt grep}. There is a method that opens a new query window in
response to a click on the corresponding menu item. It lets the user choose
the directory to search in an openpanel.

\begin{lstlisting}{}
@interface Controller : NSObject
{
    NSString *find, *grep;
}

- (NSString *)find;
- (NSString *)grep;

- (int)readFromCommand:(NSString *)cmd 
             arguments:(NSMutableArray *)args
                result:(NSArray **)rptr;

- (void)applicationDidFinishLaunching:(NSNotification *)notif;
- open:(id)sender;

@end
\end{lstlisting}

The following series of definitions pertains to the user interface of the
program and to the behavior of the custom window. We will not be displaying
tabs in text views and define {\tt TABREP} to be a string of spaces that
replace a single tab. We define the width and height of the window with {\tt
DIMENSION}. We will be assembling two forms in the upper third of the window
and we define an enumerated type that indicates the purpose of a formcell from
those two forms. This lets us assemble the forms in a loop instead of coding
every cell in turn. We define the titles of the form cells. The last define is
for the point size of the fixed pitch font that we will use on buttons and in
textviews.

\begin{lstlisting}{}
#define TABREP @"        "

#define DIMENSION 500

typedef enum {
    ARG_PATTERN = 0, 
    ARG_NAME,
    ARG_REGEX,
    ARG_CONTEXT,
    ARG_SWITCHES,
    ARG_COUNT
} ARG;

static NSString *argTitles[ARG_COUNT] = {
    @"Pattern",
    @"Name",
    @"Regex",
    @"Context",
    @"Switches"
};

#define DISPSIZE 12
\end{lstlisting}

The custom window contains many instance variables that make it easy to
reference the objects in its view hierarchy. It stores the controller because
it will use it to find files and grep for patterns. It also remembers what
directory it is supposed to process. It stores the formcells that contain the
values of the switches for {\tt find} and {\tt grep}. It stores the search
button because it will lock focus on it to draw the progress indicator. It
also stores the split view and the upper scrollview, which will hold the query
results, and the lower scrollview, which is used to view entire files. The
query results will be attached to the document view of the upper scrollview as
they come in and the variables {\tt attachAtY} and {\tt maxwidth} tell us
where they go (what height) and what the maximum width of a result is,
respectively. We'll be computing the necessary widths and heights of text
views and what rectangle we need to scroll to for display of a match in the
file viewer. That's why we store the fixed pitch font and its bounding box.

\begin{lstlisting}{}
@interface BrowseIt : NSWindow
{
    Controller *con;

    NSString *directory;

    NSFormCell *args[ARG_COUNT];
    NSButton *sbutton;
    NSSplitView *split;
    NSScrollView *scrollUpper, *scrollLower;

    float attachAtY, maxwidth;
    
    NSFont *sfont;
    NSSize bbox;
}
\end{lstlisting}

A custom window (we will also refer to it by the term ``browse window'') is
initialized with the value of the directory that we will search and the
controller that provides the facility to run commands and collect the
output. There are two shorthand methods to set the document view of the upper
and lower scrollviews.

\begin{lstlisting}{}
- initWithDirectory:(NSString *)dir 
         controller:(Controller *)theController;

- setUpper:(id)uv;
- setLower:(id)lv;
\end{lstlisting}

We store the parameters of the last successful query in the user defaults, and
read them back in for use as the initial values when we open a new browse
window. This is the purpose of {\tt readArgs} and {\tt writeArgs.}

\begin{lstlisting}{}
- readArgs;
- writeArgs;
\end{lstlisting}

The browse window will be the delegate of the split view that it contains. We
implement two methods that prevent the user from completely miniaturizing the
upper or the lower scrollview.

\begin{lstlisting}{}
- (float)splitView:(NSSplitView *)sender 
constrainMinCoordinate:(float)proposedMin ofSubviewAt:(int)offset;
- (float)splitView:(NSSplitView *)sender 
constrainMaxCoordinate:(float)proposedMax ofSubviewAt:(int)offset;
\end{lstlisting}

We have a method that takes the output lines of a single successful grep on a
single file and assembles the corresponding series of buttons and textviews.

\begin{lstlisting}{}
- processMatchFor:(NSString *)fname data:(NSArray *)lines;
\end{lstlisting}

There is a method to display the contents of the file represented by a button
in the lower scrollview.

\begin{lstlisting}{}
- loadIt:(id)sender;
\end{lstlisting}

We will respond to a certain number of error conditions. The method {\tt
errView} returns a view that holds the error message and can be placed in one
of the two scrollviews.

\begin{lstlisting}{}
- (NSTextView *)errView:(NSString *)msg;
\end{lstlisting}

The method {\tt search:} is the action of the search button. It runs {\tt
find} and iterates over the files that it outputs, invoking first {\tt grep}
and then {\tt processMatchFor:data:} for each file in turn.

\begin{lstlisting}{}
- search:(id)sender;
\end{lstlisting}

There is a deallocation method that frees the directory string. Deallocation
of views will be handled by placing them in an autorelease pool upon creation,
so that they are freed when they are removed from the view hierarchy or when
the window is closed.

\begin{lstlisting}{}
- (void)dealloc;

@end
\end{lstlisting}

We discuss the implementation of the controller before the implementation of
the browse window. There are two accessors that retrieve the location of the
{\tt find} and {\tt grep} commands, which was set on startup.

\begin{lstlisting}{}
@implementation Controller

- (NSString *)find
{
    return find;
}

- (NSString *)grep
{
    return grep;
}
\end{lstlisting}

We now discuss the very important method that runs tasks and collects their
output and error streams. It returns two arrays of lines by reference in the
variable {\tt rptr}. The first step is to create a task object and set
its launch path and its arguments. (The call to {\tt NSLog} can aid in
debugging.)

\begin{lstlisting}{}
- (int)readFromCommand:(NSString *)cmd 
             arguments:(NSMutableArray *)args
                result:(NSArray **)rptr
{
    NSTask *aTask = [NSTask new];

    [aTask setLaunchPath:cmd];
    [aTask setArguments:args];

    // NSLog(@"%@ %@", cmd, args);
\end{lstlisting}

We need a pipe to the output and error streams, so we create the appropriate
objects. We retrieve two file handles for reading from the two pipes.

\begin{lstlisting}{}
    NSPipe 
        *outPipe = [NSPipe pipe],
        *errPipe = [NSPipe pipe];
    NSFileHandle 
        *outReader = [outPipe fileHandleForReading],
        *errReader = [errPipe fileHandleForReading];
\end{lstlisting}

We are now almost ready to lauch the task. We connect the two pipes to the two
streams and declare the variables that will hold the data that they produce.

\begin{lstlisting}{}
    [aTask setStandardOutput:outPipe];
    [aTask setStandardError:errPipe];

    NSData *outData, *errData;
\end{lstlisting}

The actual run of the task takes place inside an exception handler. We try to
run the task and read the data from the two output streams. We wait until the
task exits and close the file descriptors that we used to read data from the
task.

\begin{lstlisting}{}
    NS_DURING

    [aTask launch];
    outData = [outReader readDataToEndOfFile];
    errData = [errReader readDataToEndOfFile];
    [aTask waitUntilExit];

    close([outReader fileDescriptor]);
    close([errReader fileDescriptor]);
\end{lstlisting}

The error handler terminates the task if something went wrong and the task is
still running. It sets the two arrays of output and error lines to contain the
reason for the exception and returns $-1.$

\begin{lstlisting}{}
    NS_HANDLER
    if([aTask isRunning]==YES){
        [aTask terminate];
    }

    close([outReader fileDescriptor]);
    close([errReader fileDescriptor]);

    NSString *reason = [localException reason];
    NSArray *ret = [NSArray arrayWithObject:reason];

    rptr[0] = ret;
    rptr[1] = ret;
    return -1;
    NS_ENDHANDLER
\end{lstlisting}

What remains will only be executed if we were successful in launching the task
and reading from its two data streams. We check the two data objects for the
presence of data. If there are data, then we convert them into a C string and
split this string into lines. We do not include the last return character
because it would produce an empty string at the end of the array.

\begin{lstlisting}{}
    int p;    
    for(p = 0; p<2; p++){
        NSData *data = (p>0 ? errData : outData);
        rptr[p] = 
            (![data length] ? [NSArray array] :
             [[NSString stringWithCString:[data bytes]
                        length:[data length]-1]
                 componentsSeparatedByString:@"\n"]);
    }
\end{lstlisting}

The method returns the exit status of the task, which is an important value
that can tell us whether the task succeeded or not and what problems there
were, if any.

\begin{lstlisting}{}
    return [aTask terminationStatus];
}
\end{lstlisting}

This almost completes the implementation of the controller. The penultimate
method is invoked when the application finishes launching and it reads the
location of the two binaries for finding and grepping from the user
defaults. It requests the defaults object and the file manager for this
purpose.

\begin{lstlisting}{}
- (void)applicationDidFinishLaunching:(NSNotification *)notif
{
    NSUserDefaults *ud = 
        [NSUserDefaults standardUserDefaults];
    NSFileManager *fm = 
        [NSFileManager defaultManager];
\end{lstlisting}

It looks for the find binary in the defaults and sets it to a default value if
there was no entry. It raises an exception if the binary is not executable. 

\begin{lstlisting}{}
    if((find = [ud stringForKey:@"find"])==nil){
        find = @"/usr/bin/find";
    }
    if([fm isExecutableFileAtPath:find]==NO){
        [NSException raise:NSGenericException
                     format:@"bad find binary: %@", find];
    }
    [find retain];
\end{lstlisting}

The grep binary is handled the same way: look for it among the defaults,
assign a default value if it is not found, and check that it is executable.

\begin{lstlisting}{}
    if((grep = [ud stringForKey:@"grep"])==nil){
        grep = @"/usr/bin/grep";
    }
    if([fm isExecutableFileAtPath:grep]==NO){
        [NSException raise:NSGenericException
                     format:@"bad grep binary: %@", grep];
    }
    [grep retain];
}
\end{lstlisting}

The last method responds to an entry on the application's main menu and lets
the user choose the directory that she wants to search. It runs a pretty
standard openpanel dialogue to get this value. It obtains the openpanel and
sets the title. The user may choose directories but not files and no multiple
selections are allowed.

\begin{lstlisting}{}
- open:(id)sender
{
    NSOpenPanel *openPanel = [NSOpenPanel openPanel];
  
    [openPanel setTitle:@"Open directory"];
    [openPanel setAllowsMultipleSelection:NO];
    [openPanel setCanChooseDirectories:YES];
    [openPanel setCanChooseFiles:NO];
\end{lstlisting}

Should there be an entry for the key ``Directory'' among the user defaults,
then we try to open this directory. (The value for this key is updated after
successful queries.) We use the current directory if there was no entry.

\begin{lstlisting}{}
    NSFileManager *fm = [NSFileManager defaultManager];
    NSString *dir =
        [[NSUserDefaults standardUserDefaults]
            stringForKey:@"Directory"];
    if(dir==nil){
        dir = [fm currentDirectoryPath];
    }
\end{lstlisting}

We only set the openpanel's directory if it is indeed a directory of the file
system.

\begin{lstlisting}{}
    BOOL isDir;
    if([fm fileExistsAtPath:dir isDirectory:&isDir]==YES &&
       isDir==YES){
        [openPanel setDirectory:dir];
    }
\end{lstlisting}

The last step is to run the panel. We create a new browse window for the
chosen directory if the user clicked the okay button. We center the window on
the screen and order it to the front.

\begin{lstlisting}{}
    if([openPanel runModalForTypes:nil]==NSOKButton){
        BrowseIt *bwin =
            [[BrowseIt alloc] 
                initWithDirectory:[openPanel filename]
                controller:self];
        [bwin center];
        [bwin makeKeyAndOrderFront:self];
    }
    
    return self;
}

@end
\end{lstlisting}

We may now discuss the implementation of the browse window. The initializer is
first. It is very simple conceptually, since it only needs to assemble the
views that go into the content view of the window. The first step is to store
and retain the directory. The controller is also stored but does not need to
be retained. Next we declare and set the frame of the new window and its style
mask, making it closable, titled, and resizable.

\begin{lstlisting}{}
@implementation BrowseIt

- initWithDirectory:(NSString *)dir
         controller:(Controller *)theController
{
    directory = dir;
    [directory retain];

    con = theController;

    NSRect wframe =
        NSMakeRect(0, 0, DIMENSION, DIMENSION);
    int mask = NSTitledWindowMask | NSClosableWindowMask | 
        NSResizableWindowMask;
    [super
        initWithContentRect:wframe
        styleMask:mask
        backing:NSBackingStoreBuffered
        defer:NO];
\end{lstlisting}

We set the minimum size of the window and its title, which contains the name
of the directory being searched.

\begin{lstlisting}{}
    [self setMinSize:wframe.size];
    [self setTitle:
              [NSString 
                  stringWithFormat:@"browse %@", 
                  directory]];
\end{lstlisting}

We must assemble the subviews that make up the interface. Start with the two
forms in the upper third. We declare the frame for the forms and initialize it
to be the right width; the height and the origin are set later. We declare
{\tt arg} to iterate over the form entries (the parameters) that we declared
earlier. The variable {\tt f} will be used later to iterate over the two
forms, left and right, that is.

\begin{lstlisting}{}
    NSRect fframe =
        NSMakeRect(0, 0, DIMENSION, 0);
    NSForm *form[2];

    form[0] = [[NSForm alloc] initWithFrame:wframe];
    form[1] = [[NSForm alloc] initWithFrame:wframe];

    int arg, f;
\end{lstlisting}

We iterate over the arguments that we require and place them in the two
forms, reading the statically declared titles and choosing the left form for
the first half and the right form for the second.

\begin{lstlisting}{}
    for(arg=0; arg<ARG_COUNT; arg++){
        args[arg] =
            [form[arg<=ARG_COUNT/2 ? 0 : 1] 
                 addEntry:[NSString 
                              stringWithFormat:
                                  @"%@:", argTitles[arg]]];
    }
\end{lstlisting}

It remains to customize the forms and their cells to serve our needs. We ask
the first cell for the size of the font that it uses and obtain a fixed pitch
font of this size.

\begin{lstlisting}{}
    NSFormCell *cell = [form[0] cellAtIndex:0];
    float psize = [[cell font] pointSize];
    NSFont *fixed = [NSFont userFixedPitchFontOfSize:psize];
\end{lstlisting}

Now we iterate over the two forms, setting the label and the content font
first.

\begin{lstlisting}{}
    for(f=0; f<2; f++){
        [form[f] setTextFont:fixed];
        [form[f] setTitleFont:fixed];
\end{lstlisting}

The titles are supposed to be right justified. A single cell is as wide as
half the window and ten points higher than its font.

\begin{lstlisting}{}
        [form[f] setTitleAlignment:NSRightTextAlignment];

        [form[f] setCellSize:NSMakeSize(DIMENSION/2, psize+10)];
\end{lstlisting}

We move each form to the right location. The origin of the left form is at the
window's border and the right form is next to the left. The upper boundary of
the form coincides with the upper boundary of the window.

\begin{lstlisting}{}
        fframe.origin.x = f*DIMENSION/2;
        fframe.origin.y = DIMENSION-[form[f] frame].size.height;
        [form[f] setFrameOrigin:fframe.origin];
\end{lstlisting}

The forms are widthsizable. The left form is attached to the left border of
the window, and the right form to the right.

\begin{lstlisting}{}
        mask = NSViewWidthSizable | NSViewMinYMargin |
            (f>0 ? NSViewMinXMargin : NSViewMaxXMargin);
        [form[f] setAutoresizingMask:mask];
\end{lstlisting}

The forms are made subviews of the window's content view and put in an
autorelease pool.

\begin{lstlisting}{}
        [[self contentView] addSubview:form[f]];
        [form[f] autorelease];
    }
\end{lstlisting}

We connect the left form to the right so that the user may tab through the
fields.

\begin{lstlisting}{}
    [form[0] setNextKeyView:form[1]];
\end{lstlisting}

The search button lies below the two forms and is as wide as the window. We
initialize its frame rectangle and allocate the button.

\begin{lstlisting}{}
    NSRect bframe =
        NSMakeRect(0, 0, DIMENSION, psize+10);
    sbutton =
        [[NSButton alloc] initWithFrame:bframe];
\end{lstlisting}

The title of the button is ``search'' and it gets the same font as the
formcells. The window is its target and the action that it triggers when
clicked is {\tt search:}.

\begin{lstlisting}{}
    [sbutton setTitle:@"Search"];
    [sbutton setFont:fixed];
    [sbutton setTarget:self];
    [sbutton setAction:@selector(search:)];
\end{lstlisting}

We wish to obtain the appropriate height of the button and move it to its
place below the two forms. Hence we ask it for the appropriate size, of which
we use the value for the height. The origin is below the two forms. We set the
button's frame.

\begin{lstlisting}{}
    [sbutton sizeToFit];

    bframe = [sbutton frame];
    bframe =
        NSMakeRect(0, [form[0] frame].origin.y-
                   bframe.size.height,
                   DIMENSION, bframe.size.height);

    [sbutton setFrame:bframe];
\end{lstlisting}

The button is widthsizable and remains attached to the upper boundary of the
window.

\begin{lstlisting}{}
    mask = NSViewWidthSizable | NSViewMinYMargin;
    [sbutton setAutoresizingMask:mask];
\end{lstlisting}

The button becomes the key view should the user tab out of the last field of
the right form. Tabbing again takes the user back to the first field of the
left form, which is the initial first responder of the window.

\begin{lstlisting}{}
    [form[1] setNextKeyView:sbutton];
    [sbutton setNextKeyView:form[0]];

    [self setInitialFirstResponder:form[0]];
\end{lstlisting}

We attach the button to the window's content view.

\begin{lstlisting}{}
    [[self contentView] addSubview:sbutton];
\end{lstlisting}

The lower part of the window is occupied by a split view, which we now
create. This view will hold two scrollviews and occupies the space left over
after the forms and the button have been taken into account. Its delegate is
the window itself, which implements size restraint messages during resize
operations.

\begin{lstlisting}{}
    NSRect spframe = 
        NSMakeRect(0, 0, DIMENSION, bframe.origin.y);
    split = [[NSSplitView alloc] initWithFrame:spframe];
    [split setDelegate:self];
\end{lstlisting}

The splitview is widthsizable and heightsizable. It expands to fill the entire
lower part of the window. We attach it to the view hierarchy.

\begin{lstlisting}{}
    [split setAutoresizingMask:
               NSViewWidthSizable | NSViewHeightSizable];
    [[self contentView] addSubview:split];
\end{lstlisting}

It remains to create the two scrollviews. Both are half as high as the split
view and as wide as the window. The upper scrollview is placed in the upper
half of the splitview.

\begin{lstlisting}{}
    NSRect scframe = 
        NSMakeRect(0, spframe.size.height/2, 
                   DIMENSION, spframe.size.height/2);
    scrollUpper = [[NSScrollView alloc] initWithFrame:scframe];
\end{lstlisting}

It should resize with the splitview, has a vertical scroller and its
background is white. We place it in the splitview.

\begin{lstlisting}{}
    [scrollUpper
        setAutoresizingMask:
            NSViewWidthSizable | NSViewHeightSizable];
    
    [scrollUpper setHasVerticalScroller:YES];
    [scrollUpper setBackgroundColor:[NSColor whiteColor]];
    
    [split addSubview:scrollUpper];
\end{lstlisting}

The lower scrollview has the same dimensions as the upper one, but is placed
in the lower left corner of the splitview.

\begin{lstlisting}{}
    scframe = 
        NSMakeRect(0, 0,
                   DIMENSION, spframe.size.height/2);
    scrollLower = [[NSScrollView alloc] initWithFrame:scframe];
\end{lstlisting}

Its sizing behavior is the same as that of the upper scrollview, as are its
choice of scrollers and its background color. It is also placed in the view
hierarchy.

\begin{lstlisting}{}
    [scrollLower
        setAutoresizingMask:
            NSViewWidthSizable | NSViewHeightSizable];
    
    [scrollLower setHasVerticalScroller:YES];
    [scrollLower setBackgroundColor:[NSColor whiteColor]];
    
    [split addSubview:scrollLower];
\end{lstlisting}

All of these views go into an autorelease pool, so that they are freed upon
removal from the view hierarchy and upon closure of the window.

\begin{lstlisting}{}
    [split autorelease];
    [sbutton autorelease];
    [scrollUpper autorelease];
    [scrollLower autorelease];
\end{lstlisting}

We are now done assembling the window and read the arguments of the last
successful query, if any, into the formcells of the two forms.

\begin{lstlisting}{}
    [self readArgs];
\end{lstlisting}

We select the text in the first cell so that the user can immediately start
typing when the window appears.

\begin{lstlisting}{}
    [form[0] selectTextAtIndex:0];
\end{lstlisting}

We'll be using a fixed pitch font for the textviews that display matches. We
now obtain a font of the right size and the dimensions of its bounding box for
later use. This ends the initializer.

\begin{lstlisting}{}
    sfont = [NSFont userFixedPitchFontOfSize:DISPSIZE];
    bbox.width = [sfont boundingRectForFont].size.width;
    bbox.height = [sfont defaultLineHeightForFont];

    return self;
}
\end{lstlisting}

New document views of the two scrollviews need to be autoreleased so that they
are later freed at the appropriate times. We define two methods that send an
autorelease message to a view before placing it in the upper or lower
scrollview.

\begin{lstlisting}{}
- setUpper:(id)uv
{
    TEST_AUTORELEASE(uv);
    [scrollUpper 
        setDocumentView:uv];
    return self;
}

- setLower:(id)lv
{
    TEST_AUTORELEASE(lv);
    [scrollLower 
        setDocumentView:lv];
    return self;
}
\end{lstlisting}

The process of reading the search parameters from the user defaults is very
simple. First obtain the defaults object, then retrieve the values by their
titles and write them into the appropriate form cell.

\begin{lstlisting}{}
- readArgs
{
    NSUserDefaults *ud = 
        [NSUserDefaults standardUserDefaults];

    int p;
    for(p=0; p<ARG_COUNT; p++){
        [args[p] setStringValue:
                 [ud stringForKey:argTitles[p]]];
    }

    return self;
}
\end{lstlisting}

The search parameters are written to the user defaults after every successful
search. This is done by first retrieving the user defaults. 

\begin{lstlisting}{}
- writeArgs
{
    NSUserDefaults *ud = 
        [NSUserDefaults standardUserDefaults];
\end{lstlisting}

Then we iterate over the cells that hold the parameters, checking their values
in turn. Values that are not empty are recorded in the user defaults.

\begin{lstlisting}{}
    int p;
    for(p=0; p<ARG_COUNT; p++){
        NSString *val = [args[p] stringValue];
        if(val!=nil){
            [ud setObject:val
                forKey:argTitles[p]];
        }
    }
\end{lstlisting}

We also record the current directory. The write process ends with an
invocation of {\tt synchronize}, so that the parameter values will be used the
next time a new window is opened.

\begin{lstlisting}{}
    [ud setObject:directory forKey:@"Directory"];

    [ud synchronize];

    return self;
}
\end{lstlisting}

Recall that the browse window is the splitview's delegate. We implement two
methods that the split view will invoke when the divider is moved; one to
constrain the minumum coordinate and one the maximum coordinate. We set these
to be a sixth of the splitview's height away from its lower and upper boundary
respectively. This keeps the user from moving the divider to a position
where the upper or lower scrollview can no longer be scrolled in a useful
way.

\begin{lstlisting}{}
- (float)splitView:(NSSplitView *)sender 
constrainMinCoordinate:(float)proposedMin ofSubviewAt:(int)offset
{
    return [sender frame].size.height/6;
}

- (float)splitView:(NSSplitView *)sender 
constrainMaxCoordinate:(float)proposedMax ofSubviewAt:(int)offset
{
    return [sender frame].size.height*5/6;
}
\end{lstlisting}

The method {\tt processMatchFor:data:} plays a key role in the application. It
is invoked when the grep was successful and the file contained lines that
match the pattern. Here is an example of the output from the grep command.

\begin{verbatim}
grep -n -C 1 dealloc tasks/browseit/code/browseit.m 
102-
103:- (void)dealloc;
104-
--
844-
845:- (void)dealloc
846-{
847-    [directory release];
848:    [super dealloc];
849-}
\end{verbatim}

The input of this method is the name of the file and the output lines. A line
either contains match and context data or it is a divider between sections. We
skip those dividers. We read each line in turn and produce a button and a
textview for the section whenever the line number of the current line is not
one more than the previous line number, which is how we recognize sections. We
start by getting the document view of the upper scrollview. We'll attach
sections to this view. This method merely attaches sections at the bottom edge
of the previous section, which is stored in the instance variable {\tt
attachAtY}. The caller is responsible for resizing the document view once all
sections have been attached. We declare variables for iterating over the
output lines. Actually our loop will include an extra iteration (a divider) at
the very end, which is how we process the last section. We will store the
range of the current section, i.e. where it begins and how many lines it
contains. We store the lines of each section in a mutable data object. We will
also compute the length of the longest line ({\tt maxlen}) of each section so
that we may choose the appropriate width of the textview.

\begin{lstlisting}{}
- processMatchFor:(NSString *)fname data:(NSArray *)lines
{
    NSView *docView = [scrollUpper documentView];
    int l, m = [lines count];

    NSRange range = NSMakeRange(-1, 0);
    NSMutableArray *data;

    int maxlen, curlen;
\end{lstlisting}

We iterate over the lines including an extra iteration at the end, where we
use a divider as the line's content. We extract the current line, replace tabs
by spaces and convert it into a C string.

\begin{lstlisting}{}
    for(l=0; l<m+1; l++){
        NSString *line = 
            (l<m ? [lines objectAtIndex:l] : @"--");
        const char *str = 
            [[line stringByReplacingString:@"\t" 
                   withString:TABREP] UTF8String];
\end{lstlisting}

Recall that the line number starts the line. We extract its value and skip
over the digits by which it is represented. If there were no digits, then we
have a divider, and we skip all dividers except for the last one.

\begin{lstlisting}{}
        int lnumber = atoi(str), digits = 0;
        while(*str && isdigit(*str)){
            str++; digits++;
        }

        if(!digits && l<m){
            continue;
        }
\end{lstlisting}

The actual content of the line is stored in the variable {\tt rest}. Note that
we must skip over the colon that marks matches and the dash that marks
context. We have a new section if we are at the very end or if there was a
skip in the line number.

\begin{lstlisting}{}
        NSString *rest =
            [NSString stringWithCString:str+1];
        curlen = [rest length];
       
        if(l==m || lnumber > range.location+range.length){
\end{lstlisting}

First we create the textview that holds the match and its context. We compute
its size from the longest line (width) and the total number of lines in this
section (height). We allocate it and initialize it with the right frame. We
will set the origin later. It should go below the previously attached views.

\begin{lstlisting}{}
            // text

            NSSize textbsize = 
                NSMakeSize(maxlen*bbox.width,
                           [data count]*bbox.height);

            NSTextView *tv =
                [[NSTextView alloc] 
                    initWithFrame:
                        NSMakeRect(0, 0, 
                                   textbsize.width,
                                   textbsize.height)];
\end{lstlisting}

We set it not to be editable, to use the fixed pitch font, and to contain the
current set of lines and put it into an autorelease pool for automatic
deallocation upon removal from the view hierarchy or a window closure.

\begin{lstlisting}{}
            [tv setEditable:NO];
            [tv setFont:sfont];
            [tv setString:[data componentsJoinedByString:@"\n"]];
            [tv autorelease];
\end{lstlisting}

Next we create the button that will take the user to the entire file if
clicked. It is situated above the textview. We will adjust the dimensions and
the origin of the button later.

\begin{lstlisting}{}
            // button

            RangeButton *rb =
                [[RangeButton alloc] 
                    initWithFrame:NSMakeRect(0, 
                                             textbsize.height, 
                                             50, 50)];
\end{lstlisting}

The button's title lists the name of the file and the line number. The range
that it represents is the current range. It is a momentary push button.

\begin{lstlisting}{}
            [rb setTitle:
                    [NSString stringWithFormat:@"%@: %d",
                              fname, (int)range.location]];
            [rb setButtonType:NSMomentaryPushButton];
            [rb setRange:range];
\end{lstlisting}

The window i.e. {\tt self} is the target of the button and its action is the
method that loads a file into the lower scrollview. The button represents the
current file, which is relative to the directory that is stored in the
corresponding instance variable.

\begin{lstlisting}{}
            [rb setTarget:self];
            [rb setAction:@selector(loadIt:)];
            [[rb cell] setRepresentedObject:fname];
\end{lstlisting}

We are just about done with the button and ask it to set its frame to its
preferred size. The button also goes into an autorelease pool.

\begin{lstlisting}{}
            [rb sizeToFit];
            NSSize btnsize = [rb frame].size;
            
            [rb autorelease];
\end{lstlisting}

The instance variable {\tt maxwidth} stores the maximum width of the buttons
and textviews created during the current find process. It must be updated if
the new button or textview is wider than its value. We build a chain of views
moving downwards along the y-axis of the document view and attach the new
sections at the bottom of the chain.

\begin{lstlisting}{}
            if(maxwidth<btnsize.width){
                maxwidth = btnsize.width;
            }
            if(maxwidth<textbsize.width){
                maxwidth = textbsize.width;
            }
\end{lstlisting}

We attach the button and the textview at the bottom of the chain. We also move
the attachement point down by the combined height of the new objects. This
completes the construction of a new section.

\begin{lstlisting}{}
            [rb setFrameOrigin:
                    NSMakePoint(0, attachAtY)];
            [tv setFrameOrigin:
                    NSMakePoint(0, attachAtY+btnsize.height)];

            attachAtY += btnsize.height+textbsize.height;
    
            [docView addSubview:tv]; 
            [docView addSubview:rb];
        }
\end{lstlisting}

Recall that we store state as we iterate over the output lines, namely the
contents of the current section and its range. We must reset these values at
the beginning and after a section has been completed. These are the two cases
that the following {\tt if} statement detects. We reset the section line array
to the current line, set the range to be a one-line range at the current line
and the maximum number of characters to be those of the current line.

\begin{lstlisting}{}
        if(!l || 
           (l<m && lnumber > range.location+range.length)){
            data = [NSMutableArray arrayWithObject:rest];
            range = NSMakeRange(lnumber, 1);
            maxlen = curlen;
        }
\end{lstlisting}

There is also housekeeping to do when we are inside a section. We add the
current line to the array of lines, increase the length of the range by one
and update the maximum width in characters if necessary. This concludes the
processing method, which is invoked with the grep output for every file that
matches the name or regular expression given in the form.

\begin{lstlisting}{}
        else{    
            [data addObject:rest];
            range.length++;
            if(curlen>maxlen){
                maxlen = curlen;
            }
        }
    }

    return self;
}
\end{lstlisting}

We have seen that the buttons for each section have the browse window as the
target. A click on one of these buttons should load the entire file that the
section excerpts and scroll to the excerpt. The method {\tt loadIt:} is the
action of these buttons and implements the desired load behavior. It starts by
computing the full path to the file and attempts to load it into memory (into
a string object).

\begin{lstlisting}{}
- loadIt:(id)sender
{
    NSString *fullName = 
        [directory 
            stringByAppendingPathComponent:
                [[sender cell] representedObject]];
    NSString *contents =
        [NSString stringWithContentsOfFile:fullName];
\end{lstlisting}

A descriptive error message will be displayed in the lower scrollview if the
file cannot be loaded. We replace all tabs by spaces if the file did load
correctly.

\begin{lstlisting}{}
    if(contents==nil){
        NSString *estr = 
            [NSString 
                stringWithFormat:@"Couldn't read: \"%@\"", 
                fullName];
        
        [self setLower:[self errView:estr]];
        return self;
    }

    NSString *noTabs =
        [contents stringByReplacingString:@"\t" 
                  withString:TABREP];
\end{lstlisting}

We now prepare to iterate over the characters in the file in order to compute
several statistics. We need to transform the range that is to be selected from
a range of lines into a range of characters. We also need to know the widest
line of the file so that we may choose the right size of textview. The
variable {\tt ptr} is used to iterate over the string and {\tt prev} holds the
offset in characters of the previous line. The variables {\tt from} and {\tt
to} describe the extent of the selection.

\begin{lstlisting}{}
    const char *str = [noTabs UTF8String],
        *ptr = str, *prev = str;
    int lines = 0, curlen = 0, maxlen = -1;

    NSRange lineRange = [sender range], selRange;
    int 
        from = lineRange.location, 
        to = lineRange.location+lineRange.length-1;
\end{lstlisting}

Start iterating over the characters. We have a line feed if we see a newline
character or if we are at the end of the string, in which case the length of
the line should include the last character.

\begin{lstlisting}{}
    while(*ptr){
        if(*ptr=='\n' || !ptr[1]){
            lines++; 
            if(!ptr[1]){
                curlen++;
            }
\end{lstlisting}

If the length of the current line is larger than the recorded maximum, then
the maximum is updated.

\begin{lstlisting}{}
            if(curlen>maxlen){
                maxlen = curlen;
            }
            curlen = 0;
\end{lstlisting}

We record the character offset if we have found the first line. It starts one
character after the previous newline. Similarly, the length of the range in
characters is the difference between the current offset and the start of the
range. The current newline becomes the previous line now that we are done.

\begin{lstlisting}{}
            if(lines==from){
                selRange.location = 1+prev-str;
            }
            if(lines==to){
                selRange.length =
                    (ptr-str) - selRange.location;
            }
            prev = ptr;
\end{lstlisting}

Characters in the interior of a line merely increase the length of the
line. We move to the next character in all cases.

\begin{lstlisting}{}
        else{
            curlen++;
        }
        ptr++;
    }
\end{lstlisting}

We may create the textview once all relevant statistics have been
computed. Its dimensions are determined by the length of the widest line and
the total number of lines. We allocate the view and initialize it with the
right frame size.

\begin{lstlisting}{}
    NSSize textbsize = 
        NSMakeSize(maxlen*bbox.width, lines*bbox.height);

    NSTextView *tv =
        [[NSTextView alloc] 
            initWithFrame:
                NSMakeRect(0, 0, 
                           textbsize.width,
                           textbsize.height)];
\end{lstlisting}

The view is not editable and uses the fixed pitch font that is also used in
the section view. Its contents are the contents of the file with tabs replaced
by spaces. We select the lines that were shown in the section above and place
the new textview in the lower scrollview.

\begin{lstlisting}{}
    [tv setEditable:NO];
    [tv setFont:sfont];
    [tv setString:noTabs];
    [tv setSelectedRange:selRange];

    [self setLower:tv];
\end{lstlisting}

We now compute the rectangle that should be visible in the scrollview. It is
given by the upper part of the selected range. We could have used {\tt
scrollRangeToVisible:} for this purpose, but its current implementation only
shows the first line, and we wish to show as many lines as possible. We
compute the visible height of the content view of the lower scrollview, minus
one line. (This really is the content view and not the document view.) We
compute the rectangle {\tt vrect}, which encloses the selection. If the
rectangle is higher than what can be displayed, then its height is set to the
maximum possible value. Finally we ask the textview to scroll the rectangle
into the visible part of the scrollview. This completes the method that loads
files in response to button clicks on range buttons.

\begin{lstlisting}{}
    float visheight = 
        [[scrollLower contentView] frame].size.height
        -bbox.height;
    NSRect vrect = 
        NSMakeRect(0, from*bbox.height, 
                   0, (to-from)*bbox.height);
    if(vrect.size.height>visheight){
        vrect.size.height = visheight;
    }
    [tv scrollRectToVisible:vrect];

    return self;
}
\end{lstlisting}

We have already encountered the method {\tt errView:} on several
occasions. Recall that it manufactures a textview containing an error message
for placement in the upper or lower scrollview. This method is kept simple and
can certainly be improved. We make a textview whose frame has the default
width in use throughout the application and is half as high as this
default. We choose an eighteen-point system font for our messages.

\begin{lstlisting}{}
- (NSTextView *)errView:(NSString *)msg
{
    #define FSIZE 18
    NSTextView *ev =
        [[NSTextView alloc]
            initWithFrame:
                NSMakeRect(0, 0, 
                           DIMENSION, DIMENSION/2)];
\end{lstlisting}

Error messages are of course not editable and we record this fact. The
background color of the view is white and it uses the font mentioned above.

\begin{lstlisting}{}
    [ev setFont:[NSFont systemFontOfSize:FSIZE]];
    [ev setBackgroundColor:[NSColor whiteColor]];
    [ev setEditable:NO];
\end{lstlisting}

We prefix the error message with the current date and time. This is so that
the message changes even if the user repeatedly clicked the search button or a
section button. The change in the date shows the user that processing has
occurred.

\begin{lstlisting}{}
    NSString *dateStr =
        [[NSDate date]
            descriptionWithCalendarFormat:@"%a, %d-%b-%Y "
            @"%H:%M:%S" 
            timeZone:[NSTimeZone localTimeZone]
            locale:nil],
        *estr = 
        [NSString stringWithFormat:@"%@:\n%@", dateStr, msg];
\end{lstlisting}

We store the error message with the prefix in the textview and return the
result.

\begin{lstlisting}{}
    [ev setString:estr];
    return ev;
}
\end{lstlisting}

The penultimate method of a browse window and of this program is the action
method {\tt search:}. It invokes {\tt find} and runs {\tt grep} on all
matching files, using {\tt processMatchFor:data:} to assemble the
button-textview pairs that go into the upper scrollview. Start by constructing
the arguments to {\tt find}. We indicate that {\tt find} should follow
symbolic links and list only files and not directories.

\begin{lstlisting}{}
- search:(id)sender
{
    NSMutableArray *findArgs =
        [NSMutableArray 
            arrayWithObjects: directory, 
            @"-follow", @"-type", @"f", nil];
\end{lstlisting}

We read the values for the name and the regular expression from the form
cells. (Note that the name applies to the name of the file and the regular
expression applies to the whole path.) We add the name pattern if there was
one. Similarly, if the user entered a regular expression, then it is added to
the find arguments.

\begin{lstlisting}{}
    NSString
        *name = [args[ARG_NAME] stringValue],
        *regex = [args[ARG_REGEX] stringValue];

    if([name length]>0){
        [findArgs addObject:@"-name"];
        [findArgs addObject:name];
    }
    if([regex length]>0){
        [findArgs addObject:@"-regex"];
        [findArgs addObject:regex];
    }
\end{lstlisting}

We must specify an action for find to take on matching files. We choose the
action {\tt printf} with the directive {\tt P}, which prints the file's ``name
with the name of the command line argument under which it was found removed,''
so that the user sees path components starting with the components in the
directory that is being searched.

\begin{lstlisting}{}
    [findArgs addObject:@"-printf"];
    [findArgs addObject:@"%P\n"]; 
\end{lstlisting}

We are now ready to invoke {\tt find.} We ask the controller to run the find
program and collect its standard output and standard error streams in two
arrays. The array {\tt files} contains the standard output.

\begin{lstlisting}{}
    NSArray *data[2], *files;
    int res = 
        [con readFromCommand:[con find]
             arguments:findArgs result:data];
    files = data[0];
\end{lstlisting}

There are two possible error conditions. The first occurs if {\tt find} exited
with a non-zero exit code, in which case we collect its standard error for
display with {\tt errView:}. It is also an error if {\tt find} did not find
any files. The error message goes into the upper scrollview in either of these
cases.

\begin{lstlisting}{}
    NSString *estr = nil;
    if(res){
        estr = [data[1] componentsJoinedByString:@"\n"];
    }
    else if(![files count]){
        estr = @"no files found";
    }
    if(estr!=nil){
        [self setUpper:[self errView:estr]];
        [self setLower:nil];
        return self;
    }
\end{lstlisting}

We sort the files if there was no error.

\begin{lstlisting}{}
    NSArray *sorted = 
        [files sortedArrayUsingSelector:
                   @selector(caseInsensitiveCompare:)];
\end{lstlisting}

We now prepare the arguments to {\tt grep}. Only the last argument (the file
being searched) will change as we iterate over the files; all the other
arguments stay constant. The first argument is absolutely essential: it tells
{\tt grep} to include line numbers in its output, which we use to parse it
into sections. If the user entered a context value (number of lines
surrounding a match that should be displayed), then this value is used.

\begin{lstlisting}{}
    NSMutableArray *grepArgs =
        [NSMutableArray arrayWithObject:@"-n"];

    NSString *context = [args[ARG_CONTEXT] stringValue];
    if([context length]){
        [grepArgs addObject:@"-C"];
        [grepArgs addObject:context];
    }
\end{lstlisting}

We also have a form cell for additional switches that can be passed on to {\tt
grep}, like the switch {\tt -i} for case-insenstive matching or {\tt -E} for
extended regular expressions (very useful). We read the value from the cell
and split it on the space character; all switches that are not empty are then
added to the arguments for {\tt grep.}

\begin{lstlisting}{}
    NSEnumerator *switchEnum = 
        [[[args[ARG_SWITCHES] stringValue]
             componentsSeparatedByString:@" "]
            objectEnumerator];
    NSString *switx;
    while((switx=[switchEnum nextObject])!=nil){
        if([switx length]){
            [grepArgs addObject:switx];
        }
    }
\end{lstlisting}

We create the document view of the upper scrollview before we start the
loop. Its dimensions will be set later, once all sections have been
computed. We allocate and initialize it and put it into the scrollview. The
section buttons and textviews will be attached to this view.

\begin{lstlisting}{}
    NSRect initialFrame =
        NSMakeRect(0, 0, DIMENSION, 0);
    NSView *pv = [[Flipped alloc] initWithFrame:initialFrame];
    [self setUpper:pv];
\end{lstlisting}

The last argument to {\tt grep} that does not change is the pattern.

\begin{lstlisting}{}
    [grepArgs addObject:[args[ARG_PATTERN] stringValue]];
\end{lstlisting}

Recall that {\tt grep} signals when it finds a match in a binary file. We
collect these matches into an array that we display for the user when there
were no matches in text files.

\begin{lstlisting}{}
    NSMutableArray *binaries =
        [NSMutableArray arrayWithCapacity:1];
\end{lstlisting}

We may now start iterating over the file names. The variable {\tt fpos}
indicates the current position and the variable {\tt fmax} the number of
files. We set {\tt attachAtY} to be the bottom margin of the document view and
the maximum width of a section to be zero. Recall that we must lock focus on
the search button because we will use it as a progress indicator, filling it
with blue starting at the left and moving to the right as we process files.

\begin{lstlisting}{}
    unsigned int fpos, fmax = [files count];
    unsigned matchCount = 0;

    NSRect sframe = [sbutton frame];
    [sbutton lockFocus];
    [[NSColor blueColor] set];

    attachAtY = 0; maxwidth = 0;
\end{lstlisting}

The first thing we do inside the loop is to draw the progress indicator. The
ratio {\tt fpos/fmax} indicates what width to fill. We draw a rectangle and
flush the window so that the user sees the indicator advance. We create an
autorelease pool that will hold objects created during processing, so that we
can immediately release those that are no longer referenced after an item has
been processed, as opposed to releasing them in the application's run loop.

\begin{lstlisting}{}
    for(fpos=0; fpos<fmax; fpos++){
        NSRect toFill =
            NSMakeRect(0, 0, 
                       sframe.size.width*fpos/fmax,
                       sframe.size.height);
        NSRectFill(toFill);
        [self flushWindow];

        NSAutoreleasePool *pool = [NSAutoreleasePool new];
\end{lstlisting}

The last argument to {\tt grep}, which is not constant, is the full path to te
file being searched. We ask the directory string to provide the path that is
obtained by appending the relative path to the file.

\begin{lstlisting}{}
        NSString *name = [sorted objectAtIndex:fpos];

        [grepArgs 
            addObject:
                [directory 
                    stringByAppendingPathComponent:name]];
\end{lstlisting}

We run {\tt grep} with the array of arguments and extract the contents of the
standard output stream.

\begin{lstlisting}{}
        res = [con readFromCommand:[con grep]
                   arguments:grepArgs result:data];
        NSArray *grepped = data[0];
\end{lstlisting}

The exit status of {\tt grep} is 2 if an error occurred. If this is the case,
then we build an error message that contains the name of the file and the
error message from {\tt grep}, or rather, the contents of its standard error
stream. This error message is placed in the upper scrollview. We must unlock
focus from the search button and restore it to its former state before we exit
the routine.

\begin{lstlisting}{}
        if(res==2){
            estr = 
                [NSString 
                    stringWithFormat:@"File is: \"%@\"\n%@", name,
                    [data[1] componentsJoinedByString:@"\n"]];

            [self setUpper:[self errView:estr]];
            [self setLower:nil];

            [sbutton unlockFocus];
            [sbutton display];

            return self;
        }
\end{lstlisting}

The exit status of {\tt grep} is zero if there was a match. There are two
possibilities: there was a text file match or a match in a binary file. The
former includes match and context data that are prefixed with line numbers;
the latter, a message that there was a match. Hence we may distinguish the two
cases by checking if the first line begins with a digit. If so, we process the
output into sections, assembling buttons and textviews with {\tt
processMatchFor:data:}. If not, the file is added to the array of binaries.

\begin{lstlisting}{}
        else if(!res){
            // skip binary data
            const char *first = 
                [[grepped objectAtIndex:0] UTF8String];

            if(isdigit(*first)){
                [self processMatchFor:name data:grepped];
                matchCount++;
            }
            else{
                [binaries addObject:name];
            }
        }
\end{lstlisting}

The loop ends with the removal of the file name from the arguments; i.e. we
pop the name that we pushed at the end of the array at the beginning of the
loop. We also release memory that was allocated during processing of the
current file and is no longer used. We unlock focus and restore the search
button's appearance once we are done processing all the files.

\begin{lstlisting}{}
        [grepArgs removeLastObject];

        [pool release];
    }

    [sbutton unlockFocus];
    [sbutton display];
\end{lstlisting}

It is quite possible that there were no matches. We must inform the user of
this event. We produce an error message to this effect. It includes the list
of binary matches if there were any and goes into the upper scrollview.

\begin{lstlisting}{}
    if(!matchCount){
        estr = @"no text matches found";
        if([binaries count]){
            NSString 
                *blist =
                [binaries componentsJoinedByString:@"\n"],
                *fmt = @"\nbinary matches:\n%@";
            estr = 
                [estr stringByAppendingFormat:fmt, blist];
        }
        
        [self setUpper:[self errView:estr]];
        [self setLower:nil];
    }
\end{lstlisting}

There is some clean-up work to do if we did find matches. We now know the
required total size of the document view and resize it accordingly. We tell
the document view to scroll to the first match, so that the user sees the
first file that matched in the upper left corner of the scrollview's content
view.

\begin{lstlisting}{}
    else{
        [pv setFrameSize:
                NSMakeSize(maxwidth, attachAtY)];
        [pv scrollRectToVisible:
                NSMakeRect(0, 0, 1, 1)];
\end{lstlisting}

The search action writes the search arguments into the user defaults database
if the query was successful.

\begin{lstlisting}{}

        [self writeArgs];
    }

    return self;
}
\end{lstlisting}

A browse window has a method for deallocation, which frees the space occupied
by the directory string. Note that the memory from the buttons and textviews
should be reclaimed automatically upon removal from the view hierarchy or
window closure, since they were placed in an autorelease pool just after being
created.

\begin{lstlisting}{}
- (void)dealloc
{
    [directory release];
    [super dealloc];
}

@end
\end{lstlisting}

The main function has the usual format. It instantiates an autorelease pool,
the controller and the application object.

\begin{lstlisting}{}
int main(int argc, char** argv, char **env)
{
    NSAutoreleasePool *pool = [NSAutoreleasePool new];
    Controller *con = [Controller new];
    NSApplication *app = [NSApplication sharedApplication];
\end{lstlisting}

The application's menu includes an entry that lets the user open a directory
with a browse window for searching. There is an entry for the action {\tt
copy:}, so that the user may copy text from the section textviews or the file
textview into another application. The item ``Quit'' as usual quits the
program.

\begin{lstlisting}{}
    NSMenu *menu = [NSMenu new];
    [menu addItemWithTitle: @"Open"
          action:@selector(open:)
          keyEquivalent:@""];
    [menu addItemWithTitle: @"Copy"
          action:@selector(copy:)
          keyEquivalent:@""];
    [menu addItemWithTitle: @"Quit"
          action:@selector(terminate:)
          keyEquivalent:@"q"];
    [NSApp setMainMenu:menu];
\end{lstlisting}

It remains to set the application's delegate and run the
application. Hopefully this little program illustrates successfully how a
productivity tool may be implemented in a program of a few hundred lines.

\begin{lstlisting}{}
    [app setDelegate:con];
    [app run];    

    [pool release];
    exit(0);
}
\end{lstlisting}

Making the number of spaces that replace a tab customizable is left as an
exercise.