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.