This is MiscEmacsText.m in view mode; [Download] [Up]
/** ** EmacsText.m ** ** A text subclass which provides emacs key bindings ** and "colored" program text. ** ** Modified by sl to be an emacs text only **/ #import "MiscEmacsText.h" #import <stdlib.h> #import <strings.h> #import <appkit/appkit.h> #import <defaults/defaults.h> #import <libc.h> #import <ctype.h> #define ESC (27) #define CTRL_SPACE (0) #define CTRL_A (1) #define CTRL_B (2) #define CTRL_D (4) #define CTRL_E (5) #define CTRL_K (11) #define CTRL_L (12) #define CTRL_N (14) #define CTRL_O (15) #define CTRL_P (16) #define CTRL_S (19) #define CTRL_T (20) #define CTRL_V (22) #define CTRL_W (23) #define CTRL_Y (25) /** ** Fragments of the following procedures were borrowed from ** Lee boynton's LispExample program, then hacked to bits. **/ static char killedText[10000] ; // buffer for "killed" text... // since it is statically allocated, this thing could blow up // if > 10000 chars are written to the kill buffer. If I had // I'd make this grow as needed.... static BOOL useEmacsBindings = 1 ; typedef struct { id text; NXTextBlock *block; } textInfo; BOOL evalGets(NXStream *aStream, char * aBuf) { // Procedure to get the next line (or remainder of // stream) from a stream. The newline is discarded. // If newline is present, returns YES, else if instead // the end of stream is found, returns NO (but aBuf may // still contain data). int c,i = 0; c = NXGetc(aStream) ; do { if(c == -1) // end of stream { aBuf[i] = '\0' ; return NO ; } else if(c == '\n') { aBuf[i] = '\0' ; return YES ; } else { aBuf[i++] = (char) c ; c = NXGetc(aStream) ; } } while(1) ; } static char getPrevious(NXStream *s) { // NXBGetc - a text stream macro to get the previous character. 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 MiscEmacsText:Text - awakeFromNib ; { // Do some setup work...I am my own delegate // [self setDelegate: self] ; NXAtom textTypes[5]; textTypes[0] = NXRTFPboardType; textTypes[1] = NXAsciiPboardType; textTypes[2] = NULL; [self registerForDraggedTypes:textTypes count:2]; return self; } #define MARK_UNSET -1 - keyDown:(NXEvent *)theEvent { // provide some emacs-style key bindings. We make int i ; static unsigned short val = '\0', lastChar = '\0'; static BOOL firstKey = TRUE, insertChar; static long mark = MARK_UNSET ; NXSelPt selStart, selEnd; if(!useEmacsBindings) return [super keyDown: theEvent] ; if(firstKey) // make sure killedText is empty { killedText[0] = '\0' ; firstKey = FALSE ; } [self getSel:&selStart :&selEnd]; lastChar = val; // val was set in "previous" call of keyDown.. val = theEvent->data.key.charCode + (theEvent->data.key.charSet << 8); insertChar = YES ; switch ((unsigned char) val) { case 0xa3: // ALT-< { [self setSel: 0 :0] ; [self scrollSelToVisible] ; insertChar = NO ; } break ; case 0xb3: // ALT-> { i = [self textLength] ; --i ; [self setSel: i :i] ; [self scrollSelToVisible] ; insertChar = NO ; } break ; case 0xd6: // ALT-v { // move up one screenful NXPoint scrollPoint = {0.0,0.0} ; NXRect clipRect ; [self convertPoint: &scrollPoint fromView: [superview superview]] ; // get the clipview's bounds [superview getBounds: &clipRect] ; scrollPoint.y -= clipRect.size.height ; // scroll up [self scrollPoint: &scrollPoint] ; insertChar = NO ; } break ; case 0xc8: // ALT-w { int subStringLength ; if(mark == MARK_UNSET) break ; [self getSel:&selStart :&selEnd]; if(selStart.cp < mark) [self getSubstring: killedText start: selStart.cp length: subStringLength = mark - selStart.cp] ; else [self getSubstring: killedText start: mark length: subStringLength = selStart.cp - mark] ; killedText[subStringLength] = '\0' ; insertChar = NO ; } break ; case CTRL_SPACE: // set mark [self getSel:&selStart :&selEnd]; mark = selStart.cp ; insertChar = NO ; break ; case CTRL_A: // beginning of line [self getSel:&selStart :&selEnd]; i = [self lineFromPosition:selStart.cp] ; i = [self positionFromLine: i] ; [self setSel:i :i]; insertChar = NO ; break ; case CTRL_E: // end of line [self getSel:&selStart :&selEnd]; i = [self lineFromPosition: selStart.cp] ; i = [self positionFromLine: i + 1] ; // beginning of next line if(i == -1) // then there is no next line... i = [self textLength] + 1 ; [self setSel:i -1 :i - 1]; insertChar = NO ; break ; case CTRL_B:// backwards one char [self getSel:&selStart :&selEnd]; if(selStart.cp > 1) [self setSel:selStart.cp - 1 :selStart.cp -1]; else NXBeep(); insertChar = NO ; break ; case CTRL_D: // delete char under cursor [self getSel:&selStart :&selEnd]; [self setSel:selEnd.cp :selEnd.cp+1]; [self replaceSel:""]; [self setSel:selStart.cp :selStart.cp] ; insertChar = NO ; break ; case CTRL_K: // clear to end of line, place killed text in killedText // if line is empty, then delete the newline { int currentEnd = 0 ; [self getSel:&selStart :&selEnd]; i = [self lineFromPosition: selStart.cp] ; i = [self positionFromLine: i + 1]; // i is index of beginning of next line if(i == -1) // then there is no next line... { i = [self textLength] ; } else // there is another line { if(i - selStart.cp > 1) // if line is not empty, i-- ; // don't include the newline. } // successive CTRL_K's append to killedText ; any other char // interrupts the sequence if(lastChar == CTRL_K) currentEnd = strlen(killedText) ; [self getSubstring: &killedText[currentEnd] start:selStart.cp length:i- selStart.cp] ; killedText[i - selStart.cp + currentEnd] = '\0' ; [self setSel:selStart.cp :i]; [self replaceSel:""]; [self setSel:selStart.cp :selStart.cp] ; insertChar = NO ; break ; } case CTRL_O: // "open" a line...i.e. add blank line before current [self getSel:&selStart :&selEnd]; [self setSel: selStart.cp :selStart.cp] ; [self replaceSel: "\n"] ; [self setSel: selStart.cp :selStart.cp] ; insertChar = NO ; break ; case CTRL_N: // move directly up or down one line case CTRL_P: // move directly up one line...fake up arrow { NXEvent fakeEvent ; bcopy((char *) theEvent,(char *) &fakeEvent,sizeof(NXEvent)) ; fakeEvent.data.key.charCode = val == CTRL_N ? 175 :173 ; fakeEvent.data.key.charSet = 1 ; fakeEvent.flags = NX_NUMERICPADMASK ; [super keyDown: &fakeEvent] ; insertChar = NO ; } break ; case CTRL_V: // move down one screenful { NXPoint scrollPoint = {0.0,0.0} ; NXRect clipRect ; [self convertPoint: &scrollPoint fromView: [superview superview]] ; // get the clipview's bounds [superview getBounds: &clipRect] ; scrollPoint.y += clipRect.size.height ; // scroll down [self scrollPoint: &scrollPoint] ; insertChar = NO ; break ; } case CTRL_W: // delete text from point to mark ; add to killedText if(mark == MARK_UNSET) break ; [self getSel:&selStart :&selEnd]; if(selStart.cp < mark) { [self getSubstring: killedText start: selStart.cp length:mark - selStart.cp] ; [self setSel: selStart.cp :mark] ; } else { [self getSubstring: killedText start: mark length: selStart.cp - mark] ; [self setSel: mark :selStart.cp] ; } [self replaceSel: ""] ; insertChar = NO ; break ; case CTRL_Y: // yank killedText [self getSel:&selStart :&selEnd]; [self setSel:selStart.cp :selStart.cp] ; [self replaceSel: killedText] ; insertChar = NO ; break ; default: break; } if(insertChar) [super keyDown: theEvent] ; return self ; } - mouseDown: (NXEvent *) anEvent ; { // augment double-click behavior to // check for bracketed structures and quoted // items. augment command-click behaviour to // select between backquotes. NXSelPt start, end ; char openers[] = "{([<\"'/`" ; char closers[] = "})]>\"'/`" ; char c,d, *place ; int otherPos ; NXStream *aStream ; if(anEvent->data.mouse.click == 1) [super mouseDown: anEvent] ; else 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 right side of 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 [super mouseDown: anEvent] ; } else { NXSeek(aStream,start.cp,NX_FROMSTART) ; c = NXGetc(aStream) ; if(place = index(openers,c)) // we have left side of 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] ; } } else [super mouseDown: anEvent] ; } } } else // deal with triple, quadruple, etc. clicks here... [super mouseDown: anEvent] ; if((anEvent->data.mouse.click == 1) && (anEvent->flags & NX_COMMANDMASK)) { // command click == search back for backquote (or beginning of text) // forward for backquote (or end ot text), then 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 ; } /* - textDidGetKeys: sender isEmpty: (BOOL) flag { [window setDocEdited: YES] ; return self ; } */ - useEmacsBindings: (BOOL) YESorNO ; { useEmacsBindings = YESorNO ; return self ; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.