This is ObjCText.m in view mode; [Download] [Up]
#import "ObjCText.h" #import <stdlib.h> #import <strings.h> #import <appkit/publicWraps.h> #import <appkit/Scroller.h> #import <appkit/ScrollView.h> #import <appkit/Application.h> #define ESC 27 #define CTRL_A (1) #define CTRL_B (2) #define CTRL_C (3) #define CTRL_D (4) #define CTRL_E (5) #define CTRL_F (6) #define CTRL_K (11) #define CTRL_N (14) #define CTRL_P (16) #define CTRL_V (22) // // following procedures were "stolen" from // Lee boynton's LispExample /* ** NXBGetc - a text stream macro to get the previous character. */ typedef struct { id text; NXTextBlock *block; } textInfo; static char getPrevious(NXStream *s) { textInfo *info = (textInfo *) s->info; NXTextBlock *block = info->block->prior; if (!block) { return EOF; } s->buf_base = block->text; s->buf_ptr = s->buf_base + block->chars; s->offset -= block->chars; info->block = block; return *(--s->buf_ptr); } #define NXBGetc(s) \ (((s)->buf_base == (s)->buf_ptr) ? getPrevious(s) : \ *(--((s)->buf_ptr)) ) int findMatchingOpenParen(id text, char thisChar, char thatChar, int curPos) { int count = 1; char ch; NXStream *s = [text stream]; NXSeek(s, curPos, NX_FROMSTART); while ((ch = NXBGetc(s)) != EOF) { if(ch == thatChar && !(--count)) return NXTell(s); else if (ch == thisChar) count++; } return -1; } int findMatchingCloseParen(id text, char thisChar, char thatChar, int curPos) { int count = 1; char ch; NXStream *s = [text stream]; NXSeek(s, curPos, NX_FROMSTART); while ((ch = NXGetc(s)) != EOF) { if (ch == thatChar && !(--count)) return NXTell(s); else if (ch == thisChar) count++; } return -1; } void highlightChar(id text, int charPosition) { NXSelPt selStart, selEnd; [text getSel:&selStart :&selEnd]; [text setSel:charPosition :charPosition+1]; [[text window] flushWindow]; NXPing(); [[text window] disableFlushWindow]; [text setSel:selStart.cp :selEnd.cp]; [[text window] reenableFlushWindow]; } @implementation ObjCText:Text - initFrame:(NXRect *) aRect ; { self = [super initFrame: aRect] ; return self ; } - keyDown:(NXEvent *)theEvent { // provide some emacs-style key bindings int i; static unsigned short val = '\0', lastChar = '\0' ; NXSelPt selStart, selEnd; [self getSel:&selStart :&selEnd]; lastChar = val ; // val was set in "previous" call of keyDown.. val = theEvent->data.key.charCode + (theEvent->data.key.charSet << 8); switch (val) { case ESC: // don't want ESC to cause a beep return self ; case '<': if(lastChar == ESC) { [self setSel: 0 :0]; [self scrollSelToVisible]; return self; } break; case '>': if(lastChar == ESC) { i = [self textLength]; [self setSel: i :i]; [self scrollSelToVisible]; return self; } break; case ')': i = findMatchingOpenParen(self,')','(',selEnd.cp); [super keyDown:theEvent]; if (i >= 0) highlightChar(self,i); return self; case '}': i = findMatchingOpenParen(self,'}','{',selEnd.cp); [super keyDown:theEvent]; if (i >= 0) highlightChar(self,i); return self; case ']': i = findMatchingOpenParen(self,']','[',selEnd.cp); [super keyDown:theEvent]; if (i >= 0) highlightChar(self,i); return self; case CTRL_A: // beginning of line i = [self lineFromPosition:selStart.cp]; i = [self positionFromLine: i]; [self setSel:i :i]; return self; case CTRL_E: // end of line i = [self lineFromPosition: selStart.cp] ; i = [self positionFromLine: i + 1] ; // beginning of next line if (i == -1) // is there no next line in the text? { i = [self textLength]; [self setSel: i :i]; } else // there is a next line in the text { [self setSel:i-1 :i-1]; } return self; case CTRL_B:// backwards one char if (selStart.cp > 1) [self setSel:selStart.cp - 1 :selStart.cp -1]; else NXBeep(); return self; case CTRL_F: // forwards one char if (selEnd.cp < [self textLength]) [self setSel:selEnd.cp+1 :selEnd.cp+1]; else NXBeep(); return self; case CTRL_D: // delete char under cursor [self setSel:selEnd.cp :selEnd.cp+1]; [self replaceSel:""]; [self setSel:selStart.cp :selStart.cp]; [[NXApp mainWindow] setDocEdited:YES]; return self; case CTRL_K: // clear to end of line i = [self lineFromPosition: selStart.cp] ; i = [self positionFromLine: i + 1] ; // beginning of next line if (i == -1) // is there no next line in the text? { i = [self textLength]; [self setSel: selStart.cp :i]; } else { [self setSel:selStart.cp :--i]; } [self replaceSel:""]; [self setSel:selStart.cp :selStart.cp]; [[NXApp mainWindow] setDocEdited:YES]; return self; case CTRL_P: // move directly up one line...fake down arrow theEvent->data.key.charCode = 173 ; theEvent->data.key.charSet = 1 ; theEvent->flags = NX_NUMERICPADMASK ; break ; case CTRL_N: // move directly down one line...fake up arrow theEvent->data.key.charCode = 175 ; theEvent->data.key.charSet = 1 ; theEvent->flags = NX_NUMERICPADMASK ; break ; case 'v': // move up one screenful if(lastChar != ESC) break ; case CTRL_V: // move down one screenful { id scroller ; NXRect aRect ; NXEvent fakeEvent ; fakeEvent = *theEvent ; fakeEvent.type = NX_MOUSEDOWN ; fakeEvent.data.mouse.reserved = 18 ; fakeEvent.data.mouse.eventNum = 9999 ; fakeEvent.data.mouse.click = 1 ; /*fakeEvent.data.mouse.unused = 0 ;*/ fakeEvent.flags = NX_ALTERNATEMASK ; scroller = [[superview superview] vertScroller] ; [scroller calcRect:&aRect forPart: val == 'v'? NX_DECLINE :NX_INCLINE ] ; [scroller convertPoint: &aRect.origin toView: nil] ; fakeEvent.location = aRect.origin ; DPSPostEvent(&fakeEvent,1) ; fakeEvent.type = NX_MOUSEUP ; fakeEvent.time += 10 ; // 10 /68ths of a second DPSPostEvent(&fakeEvent,0) ; return self ; } default: break; } return [super keyDown:theEvent]; } - mouseDown: (NXEvent *) anEvent ; { // redefine double-click behavior to // check for bracketed structures and quoted // items NXSelPt start, end ; char openers[] = "{([<\"'/`" ; char closers[] = "})]>\"'/`" ; char c,d, *place ; int otherPos ; NXStream *aStream ; if(anEvent->data.mouse.click == 2) { aStream = [self stream] ; [self getSel: &start :&end] ; if(start.cp > 0) { NXSeek(aStream,start.cp-1,NX_FROMSTART) ; c = NXGetc(aStream) ; if(place = index(closers,c)) // we have a structure { d = openers[(int) place - (int) closers] ;// grab matching char if( (otherPos = findMatchingOpenParen(self, c, d, start.cp -1)) != -1) { if(c == '`') // put selection "inside" ` ... ` pair [self setSel: otherPos + 1 :start.cp - 1] ; else [self setSel: otherPos :start.cp] ; return self ; } } else { NXSeek(aStream,start.cp,NX_FROMSTART) ; c = NXGetc(aStream) ; if(place = index(openers,c)) // we have a structure { d = closers[(int) place - (int) openers] ; if( (otherPos = findMatchingCloseParen(self, c, d, start.cp +1)) != -1) { if(c == '`') // put selection "inside" ` ... ` pair [self setSel: start.cp +1 :otherPos-1] ; else [self setSel: start.cp :otherPos] ; return self ; } } } } } else if(anEvent->data.mouse.click == 4) { // triple click == search back for backquote (or beginning of text) // forward for backquote (or end ot text), select int from, to ; aStream = [self stream] ; [self getSel: &start :&end] ; if(start.cp > 0) { from = start.cp - 1 ; NXSeek(aStream,from,NX_FROMSTART) ; while(from >= 1 && ((c = NXBGetc(aStream)) != '`')) from-- ; to = start.cp ; NXSeek(aStream,to,NX_FROMSTART) ; while(!NXAtEOS(aStream) && ((c = NXGetc(aStream)) != '`')) to++ ; [self setSel: from :to] ; return self ; } } return [super mouseDown: anEvent] ; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.