This is Finder.m in view mode; [Download] [Up]
#import "Finder.h"
#import "Eval.h"
#import "CodeBrowser.h"
#import "support.h"
#import <regex.h>
#import <strings.h>
#import <libc.h>
/**
** Finder.m
** A find panel with regular expression searching and
** the ability to store "bookmarks"
**/
#define MAXFINDERCELLLEN 80
// there is only one instance of finder, and it's
// id gets stuffed into this var:
id finderObject ;
@implementation Finder: Panel
{ id findLineNum,
findText,
replaceText,
caseSwitch,
regExpSwitch,
selScopeSwitch,
lineNumBrowser,
currentCodeBrowser ;
}
- (int)browser:sender fillMatrix:matrix inColumn:(int)column ;
{ // Delegate method for browser object.
if(currentCodeBrowser)
return [[currentCodeBrowser lineList] count] ;
return 0 ;
}
- browser:sender loadCell:cell atRow:(int)row inColumn:(int)column ;
{ int i,lineNum, charNum ;
char buf1[MAXFINDERCELLLEN+10], buf2[MAXFINDERCELLLEN + 17] ;
id theText = [currentCodeBrowser codeBrowserText] ;
lineNum = * (int *) [[currentCodeBrowser lineList] elementAt: row] ;
charNum = [theText positionFromLine: lineNum] ;
[theText getSubstring: buf1 start: charNum length: MAXFINDERCELLLEN] ;
for(i = 0 ; i < MAXFINDERCELLLEN ; i++)
{ if(buf1[i] == '\n') // don't grab more than 1 line...
{ buf1[i] = '\0' ;
break ;
}
}
buf1[i] = '\0' ; // make sure null terminated!
sprintf(buf2,"%-5d %s",lineNum,buf1) ;
[cell setStringValue: buf2] ;
[cell setLeaf:YES] ;
return self ;
}
- doFind: sender
{ // called by the Find menu option
[self makeKeyAndOrderFront: self] ;
[[findText controlView] selectText: self] ;
return self ;
}
- doFindLine: sender
{ // called by the fine line menu option
[self makeKeyAndOrderFront: self] ;
[self finderCurrent: self] ;
[findLineNum selectText: self] ;
return self ;
}
- find: (char *) text regEx: (BOOL) regEx caseSensitive: (BOOL) caseSensitive inSelection: (BOOL) inSelection ;
{ // find the next occurence of "text" in the window's text. It may contain
// a regular expression. The search may be case sensitive. The search may be
// confined to the current selection. Returns nil if text is not found, else
// makes the text the current selection. If text is NULL or the empty string,
// does nothing, but returns self.
int origLen, newLen ;
long firstStart, lastStart ;
BOOL wrapped = NO ;
char textBuf[1024] ;
if(![[NXApp mainWindow] isKindOf: [CodeBrowser class]])
return self ;
if(!text || (text[0] == '\0'))
return self ;
origLen = newLen = strlen(text) ;
if(!regEx)
newLen *= 2 ; // not a rexp:leave room to quote everything
{ int i,j ;
char specialChars[] = "[]^*$\\." ;
struct regex *ex ;
NXStream *aStream ;
NXSelPt start, end ;
char regexText[newLen+1] ;
id textView ;
if(!regEx)
{ for(i = 0,j = 0 ; text[i] ; i++,j++)
{ if(index(specialChars,text[i])) // if a special char,
regexText[j++] = '\\' ; // then turn off its special meaning by
regexText[j] = text[i] ; // quoting it.
}
regexText[j] = '\0' ;
}
else
strcpy(regexText,text) ;
ex = re_compile(regexText,caseSensitive) ;
textView = [[NXApp mainWindow] codeBrowserText] ;
[textView getSel: &start :&end] ;
if(!inSelection) // scope is entire file
{ aStream = [textView stream] ;
// search begins after current selection
firstStart = lastStart = (long) end.cp ;
}
else // scope is selection only
{ int len = end.cp - start.cp ;
{ char aBuf[len + 1] ;
aStream = NXOpenMemory(NULL, 0, NX_READWRITE) ;
[textView getSubstring: aBuf start: start.cp length: len] ;
NXWrite(aStream, aBuf, len) ;
// search begins at beginning of stream
firstStart = lastStart = 0 ;
}
}
NXSeek(aStream, lastStart, NX_FROMSTART) ;
EvalGets(aStream,textBuf) ;
do
{ if(re_match(textBuf,ex))
{ if(inSelection)
{ [textView setSel:(int) (lastStart + ex->start -
(int) textBuf + start.cp)
:(int) (lastStart + ex->end)-
(int) textBuf + start.cp] ;
NXCloseMemory(aStream, NX_FREEBUFFER) ;
}
else
[textView setSel:(int) (lastStart + ex->start - (int) textBuf)
:(int) (lastStart + ex->end)- (int) textBuf] ;
[textView scrollSelToVisible] ;
free(ex) ;
[[textView window] makeKeyAndOrderFront: self] ;
return self ;
}
if(NXAtEOS(aStream)) // wrap around to beginning
{ NXSeek(aStream, 0L,NX_FROMSTART) ;
wrapped = YES ;
}
lastStart = NXTell(aStream) ;
EvalGets(aStream,textBuf) ;
} while(!(wrapped && (lastStart >= firstStart))) ;
free(ex) ;
if(inSelection)
NXCloseMemory(aStream, NX_FREEBUFFER) ;
[self setTitle: "Not Found"] ;
[self perform:@selector(resetTitle:)
with:self afterDelay:3500 cancelPrevious:YES];
return nil ;
}
return self ;
}
- finderAddLine: sender ;
{ // add the current line to the list of lines
if([[NXApp mainWindow] isKindOf: [CodeBrowser class]])
{ NXSelPt start, end ;
int i ;
id textView = [[NXApp mainWindow] codeBrowserText] ;
[textView getSel: &start :&end] ;
i = [textView lineFromPosition: start.cp] ;
// the next line looks dangerous (storing a pointer to an automatic?)
// ...but the Storage object dereferences the pointer before it stores it.
[[[textView window] lineList] addElement: &i] ;
[lineNumBrowser loadColumnZero] ;
[[textView window] makeKeyAndOrderFront: self] ;
}
return self ;
}
- finderBrowserHit: sender ;
{ // go to the line indicated in the browser
/* if you want to fire on double clicks, use this:
NXEvent * anEvent ;
anEvent = [NXApp currentEvent] ;
if(anEvent->data.mouse.click == 2)
{ ...} */
if([[NXApp mainWindow] isKindOf: [CodeBrowser class]])
{ char aBuf[MAXFINDERCELLLEN+17] ;
int lineNum ;
[lineNumBrowser setPathSeparator: ' '] ;
[lineNumBrowser getPath: aBuf toColumn: 1] ;
if(sscanf(aBuf,"%d",&lineNum))
{ id textView = [[NXApp mainWindow] codeBrowserText] ;
[textView setSel: [textView positionFromLine: lineNum]
: [textView positionFromLine: lineNum + 1]] ;
[textView scrollSelToVisible] ;
[[textView window] makeKeyAndOrderFront: self] ;
}
}
return self ;
}
- finderCurrent: sender ;
{ // put line number of beginning of selection into
// the findLineNum
if([[NXApp mainWindow] isKindOf: [CodeBrowser class]])
{ NXSelPt start,end ;
id theText = [[NXApp mainWindow] codeBrowserText] ;
[theText getSel: &start :&end] ;
[findLineNum setIntValue: [theText lineFromPosition: start.cp]] ;
}
return self ;
}
- finderGoTo: sender ;
{ // set selection at beginning of line indicated by
// findLineNum...depending on the sate of the scopeSwitch,
// this will be a lineNum counted from the begining of the selection
if([[NXApp mainWindow] isKindOf: [CodeBrowser class]])
{ id textView = [[NXApp mainWindow] codeBrowserText] ;
int pos1, pos2, lineNum ;
lineNum = [findLineNum intValue] ;
if([[selScopeSwitch selectedCell] tag])
{ // if scope is selection...
NXSelPt start,end ;
int offset ;
[textView getSel: &start :&end] ;
offset = [textView lineFromPosition: start.cp] ;
lineNum += offset - 1 ;
}
pos1 = [textView positionFromLine:lineNum] ;
pos2 = [textView positionFromLine: lineNum + 1] ;
[textView setSel:pos1 :pos2] ;
[textView scrollSelToVisible] ;
[findLineNum selectText: self] ;
[[textView window] makeKeyAndOrderFront: self] ;
}
return self ;
}
- finderNext: sender ;
{ // find the next occurence of the "find" text.
// returns nil if unable.
return [self find: (char *) [findText stringValue]
regEx: [[regExpSwitch selectedCell] tag]
caseSensitive: ![[caseSwitch selectedCell] tag]
inSelection: [[selScopeSwitch selectedCell] tag]] ;
}
- finderRemLine: sender ;
{ // remove the browser-selected line from the list of lines
if([[NXApp mainWindow] isKindOf: [CodeBrowser class]])
{ [[[NXApp mainWindow] lineList] removeElementAt: [[lineNumBrowser matrixInColumn:0] selectedRow]] ;
[lineNumBrowser loadColumnZero] ;
}
return self ;
}
- finderReplace: sender ;
{ // replace the highlighted text with the current text
if([[NXApp mainWindow] isKindOf: [CodeBrowser class]])
{ [[[NXApp mainWindow] codeBrowserText] replaceSel: [replaceText stringValue]] ;
}
return self ;
}
- finderReplaceAll: sender ;
{ // replace all occurences of the find text with the replace text
if([[NXApp mainWindow] isKindOf: [CodeBrowser class]])
{ id textView = [[NXApp mainWindow] codeBrowserText] ;
NXSelPt start, end ;
char buf[80] ;
int repKnt = 0 ;
[[textView window] disableFlushWindow] ;
if(![[selScopeSwitch selectedCell] tag])
{ // Scope is entire file. We need to prevent entering
// a continuous loop in cases where a string is being
// replaced by another which contains (or matches) the first.
// To do this, we select the entire text, then do a find
// with inSelection set to YES, then move the selection forward.
NXSelPt saveStart,saveEnd ;
int saveLineNum, saveCharNum, saveSelLen ;
// after the replace, we want to be sure to put the selection back
// on the same line, and have the same length, as before the replace
[textView getSel: &saveStart :&saveEnd] ;
saveLineNum = [textView lineFromPosition: saveStart.cp] ;
saveCharNum = saveStart.cp - [textView positionFromLine: saveLineNum] ;
saveSelLen = saveEnd.cp - saveStart.cp ;
[textView setSel: 1 :[textView textLength]] ;
while([self find: (char *) [findText stringValue]
regEx: [[regExpSwitch selectedCell] tag]
caseSensitive: ![[caseSwitch selectedCell] tag]
inSelection: YES])
{ [self finderReplace: self] ;
[textView getSel: &start :&end] ;
[textView setSel: end.cp :[textView textLength]] ;
repKnt++ ;
}
saveStart.cp = [textView positionFromLine: saveLineNum] + saveCharNum ;
[textView setSel: saveStart.cp :saveStart.cp + saveSelLen] ;
}
else
{ // scope is current selection
int diff ;
NXSelPt newStart, newEnd ;
diff = strlen([replaceText stringValue]) -
strlen([findText stringValue]) ;
[textView getSel:&start :&end] ;
if([self finderNext: self])
{ do
{ end.cp += diff ;
[self finderReplace: self] ;
// must reset the (adjusted) selection
[textView getSel: &newStart :&newEnd] ;
[textView setSel: newStart.cp :end.cp] ;
repKnt++ ;
} while([self finderNext: self]) ;
[textView setSel: start.cp :end.cp] ;
}
}
[[textView window] reenableFlushWindow] ;
[[textView window] display] ;
sprintf(buf,"%d replacement%s",repKnt,repKnt == 1 ? "":"s") ;
[self setTitle: buf] ;
[self perform:@selector(resetTitle:)
with:self afterDelay:3500 cancelPrevious:YES];
}
return self ;
}
- finderReplaceAndFind: sender ;
{ // replace the highlighted text with the replacement text,
// find next occurence of find text
[self finderReplace: self] ;
return [self finderNext: self] ;
return self ;
}
- findLine ;
{ return findLineNum ;
}
- findText ;
{ return findText ;
}
- jump: sender ;
{ // scroll selection to visible
if([[NXApp mainWindow] isKindOf: [CodeBrowser class]])
{ [[[NXApp mainWindow] codeBrowserText] scrollSelToVisible] ;
}
return self ;
}
- lineNumBrowser ;
{ return lineNumBrowser ;
}
- reset: sender ;
{ // called from a CodeBrowser to cause me to
// reload the linenum browser
currentCodeBrowser = sender ;
[lineNumBrowser loadColumnZero] ;
return self ;
}
- resetTitle: sender ;
{ // Reset our title to "Find"...normally
// sent via a delayed message send.
[self setTitle: "Find"] ;
return self ;
}
@end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.