This is MiscEmacsText.m in view mode; [Download] [Up]
/** ** EvalText.m ** ** A text subclass which provides emacs key bindings ** and "colored" program text. **/ #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_C (3) #define CTRL_D (4) #define CTRL_E (5) #define CTRL_F (6) #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_V (22) #define CTRL_W (23) #define CTRL_X (24) #define CTRL_Y (25) /** ** keyWords[] is a structure used by format: sender to ** identify keyWords in transcript text **/ static struct { char * word ; int len ; } keyWords[] = { {"int",3}, {"char",4}, {"float",5}, {"double",6}, {"struct",6}, {"union",5}, {"long",4}, {"short",5}, {"unsigned",8}, {"auto",4}, {"extern",6}, {"register",8}, {"typedef",7}, {"static",6}, {"goto",4}, {"return",6}, {"sizeof",6}, {"break",5}, {"continue",8}, {"if",2}, {"else",4}, {"for",3}, {"do",2}, {"while",5}, {"switch",6}, {"case",4}, {"default",7}, {"entry",5}, /* is this a keyword in ansi C ? */ {"const",5}, {"id",2}, {"void",4}, {NULL,0} } ; /** ** 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 // macros which write rtf to a stream called "cookedStream" // for creating fancy text for various code categories #define PREPROC NXPrintf(cookedStream,"\\f0%s\\cf0%s ",\ fontStuff[0][0],fontStuff[0][2]) #define DIRECTIVE NXPrintf(cookedStream,"\\f1%s\\cf1%s ",\ fontStuff[1][0],fontStuff[1][2]) #define METHODDEF NXPrintf(cookedStream,"\\f2%s\\cf2%s ",\ fontStuff[2][0],fontStuff[2][2]) #define COMMENT NXPrintf(cookedStream,"\\f3%s\\cf3%s ",\ fontStuff[3][0],fontStuff[3][2]) #define STRING NXPrintf(cookedStream,"\\f4%s\\cf4%s ",\ fontStuff[4][0],fontStuff[4][2]) #define KEYWORD NXPrintf(cookedStream,"\\f5%s\\cf5%s ",\ fontStuff[5][0],fontStuff[5][2]) #define NORMAL NXPrintf(cookedStream,"\\f6%s\\cf6%s ",\ fontStuff[6][0],fontStuff[6][2]) #define TEXTDEFAULTKNT 7 /* number of text categories */ - 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; } - clearMessage: sender ; { // Clear the message in the transcript. Called by keydown // via a delayed message send. // // [window message: ""] ; return self ; } - format: sender ; { // Color and set font attributes for text in the // transcript. If sender responds to // to the methodList: method, (which must should return a List), // then allocates and adds an EvalString with the first line // of each method name to this methodlist. char textBuf[1024] ; char fontStuff[TEXTDEFAULTKNT][3][40], defName[12] ; const char *defaults ; int i, j, keyWordLen, charNum, braceLevel = 0 ; BOOL inString = NO, inComment = NO, inSlashSlashComment = NO, inMethodDef = NO, inPreProc = NO, newline; NXStream *rawStream, *cookedStream ; id methodList = nil ; /* if([sender respondsTo: @selector(methodList)]) { methodList = [sender methodList] ; } */ rawStream = [self stream] ; cookedStream = NXOpenMemory(NULL,0,NX_READWRITE) ; NXPrintf(cookedStream,"{\\rtf0\\ansi{\\fonttbl") ; // get the font information from defaults database for(i = 0 ; i < TEXTDEFAULTKNT ; i++) { // Manufacture default name sprintf(defName,"text%1d",i) ; defaults = NXGetDefaultValue([NXApp appName],defName) ; // parse the default value sscanf(defaults,"%s %s %s %s", textBuf, fontStuff[i][0], fontStuff[i][1],fontStuff[i][2]) ; NXPrintf(cookedStream,"\\f%1d\\fnil %s;", i,textBuf) ; } NXPrintf(cookedStream,"}\n") ; NXPrintf(cookedStream,"{\\colortbl") ; for(i = 0 ; i < TEXTDEFAULTKNT ; i++) { NXPrintf(cookedStream,fontStuff[i][1]) ; NXPrintf(cookedStream,";") ; } NXPrintf(cookedStream,"}\n") ; charNum = 0 ; NORMAL ; do { NXSeek(rawStream, charNum, NX_FROMSTART) ; newline = evalGets(rawStream,textBuf) ; inString = NO ; // ANSI C forbids newline in string // now examine the string, adding rtf directives as required for(i = 0 ; textBuf[i] ;) { switch(textBuf[i]) { case '/': if(inString) NXPutc(cookedStream,textBuf[i++]) ; else if(textBuf[i + 1] == '*' || textBuf[i+1] == '/') { if(!inComment) { COMMENT ; inComment = YES ; if(textBuf[i+1] == '/') // behaves specially! inSlashSlashComment = YES ; } NXPutc(cookedStream,textBuf[i++]) ; } else NXPutc(cookedStream,textBuf[i++]) ; break ; case '*': if(inComment && textBuf[i+1] == '/' && !inSlashSlashComment) { NXPutc(cookedStream,textBuf[i++]) ; NXPutc(cookedStream,textBuf[i++]) ; if(inMethodDef) METHODDEF ; else NORMAL ; inComment = NO ; } else NXPutc(cookedStream,textBuf[i++]) ; break ; case '"': if(inComment || inPreProc) NXPutc(cookedStream,textBuf[i++]) ; else if(inString) { NXPutc(cookedStream,textBuf[i++]) ; if(textBuf[i - 2] != '\\') { inString = NO ; NORMAL ; } } else { if(i == 0 || textBuf[i - 1] != '\\') { inString = YES ; STRING ; } NXPutc(cookedStream,textBuf[i++]) ; } break ; case '}' : if(!inComment && !inString && !inPreProc) { if(braceLevel > 0) // don't allow this to go negative braceLevel-- ; } // must be escaped for rtf NXPutc(cookedStream,'\\') ; NXPutc(cookedStream,textBuf[i++]) ; break ; case '\\': // must be escaped for rtf NXPutc(cookedStream,'\\') ; NXPutc(cookedStream,textBuf[i++]) ; break ; case '{' : if(!inComment && !inString && !inPreProc) braceLevel++ ; if(braceLevel == 1 && inMethodDef) { inMethodDef = NO ; NORMAL ; } NXPutc(cookedStream,'\\') ; NXPutc(cookedStream,textBuf[i++]) ; break ; case '#': if(!inComment && i == 0) { // a hash in column 0 means a preprocessor line PREPROC ; inPreProc = YES ; } NXPutc(cookedStream,textBuf[i++]) ; break ; case '@': if(!inString && !inComment && !inPreProc) { // then this is a compiler directive DIRECTIVE ; // if((i == 0) && methodList) // if a compiler directive is in // column 0, put it into the methodList // // // [methodList addObject: [[EvalString alloc] init: textBuf]] ; // // NXPutc(cookedStream,textBuf[i++]) ; while(isalnum(textBuf[i])) NXPutc(cookedStream,textBuf[i++]) ; NORMAL ; } else NXPutc(cookedStream,textBuf[i++]) ; break ; case '-': case '+': if(!inComment && !inString && !inPreProc && braceLevel == 0) { for(j = 0 ; j < i ; j++) if(!isspace(textBuf[j]) && textBuf[j] != '\\') break ; if(j == i) { // this is not a truly accurate way of finding the beginning of a // method def, but it will work "most" of the time... inMethodDef = YES ; METHODDEF ; // // // if(methodList) // [methodList addObject: [[EvalString alloc] init: textBuf]] ; // // // } } if(textBuf[i] == '-') // must be escaped NXPutc(cookedStream,'\\') ; NXPutc(cookedStream,textBuf[i++]) ; break ; case ' ': case '\t': NXPutc(cookedStream,textBuf[i++]) ; break ; default: if(!inComment && !inString && !inMethodDef && !inPreProc && (i == 0 || !(isalnum(textBuf[i-1]) || textBuf[i-1] == '_'))) { // check for a keyword for(j = 0 ; keyWordLen = keyWords[j].len ; j++) { if(!strncmp(&textBuf[i],keyWords[j].word,keyWordLen)) { int followChar = i + keyWordLen ; int isKeyWord = !(isalnum(textBuf[followChar]) || textBuf[followChar]=='_') ; if(isKeyWord) KEYWORD ; NXPrintf(cookedStream,"%s\n",keyWords[j].word) ; if(isKeyWord) NORMAL ; i += keyWordLen ; break ; } } if(!keyWordLen) // no keyWord match found NXPutc(cookedStream,textBuf[i++]) ; } else NXPutc(cookedStream,textBuf[i++]) ; break ; } } // check for a preprocessor continuation line if(inPreProc && textBuf[i - 1] != '\\') { inPreProc = NO ; NORMAL ; } // terminate a slash-slash style comment if(inSlashSlashComment) { inSlashSlashComment = inComment = NO ; if(inMethodDef) METHODDEF ; else NORMAL ; } if(newline) // output a quoted newline { NXPutc(cookedStream,'\\') ; NXPutc(cookedStream,'\n') ; } charNum += strlen(textBuf) + 1; } while(!NXAtEOS(rawStream)) ; NXPutc(cookedStream,'}') ; NXSeek(cookedStream, 0, NX_FROMSTART) ; [self readRichText: cookedStream] ; NXCloseMemory(cookedStream,NX_FREEBUFFER) ; return self ; } #define MARK_UNSET -1 - keyDown:(NXEvent *)theEvent { // provide some emacs-style key bindings. We make // the assumption that we are subview of a scrollview. int i ; static unsigned short val = '\0', lastChar = '\0', nextChar ; static BOOL firstKey = TRUE, definingMacro = NO, insertChar, replayingMacro = NO ; static long mark = MARK_UNSET ; NXSelPt selStart, selEnd; static NXStream * macroStream ; if(!useEmacsBindings) return [super keyDown: theEvent] ; if(firstKey) // make sure killedText is empty { killedText[0] = '\0' ; firstKey = FALSE ; macroStream = NXOpenMemory(NULL, 0, NX_READWRITE) ; } [self getSel:&selStart :&selEnd]; do { lastChar = val ; // val was set in "previous" call of keyDown.. val = theEvent->data.key.charCode + (theEvent->data.key.charSet << 8); if(definingMacro) NXPutc(macroStream,theEvent->data.key.charCode) ; insertChar = YES ; switch ((unsigned char) val) { case ESC: // don't want ESC to cause a beep insertChar = NO ; break ; case '<': if(lastChar != ESC) break ; case 0xa3: // ALT-< or ESC < { [self setSel: 0 :0] ; [self scrollSelToVisible] ; insertChar = NO ; } break ; case '>': if(lastChar != ESC) break ; case 0xb3: // ALT-> or ESC > { i = [self textLength] ; --i ; [self setSel: i :i] ; [self scrollSelToVisible] ; insertChar = NO ; } break ; case '(': if((lastChar == CTRL_X) && !definingMacro) { // start defining macro // [window message: "Defining kbd macro..."] ; definingMacro = YES ; NXSeek(macroStream, 0, NX_FROMSTART) ; insertChar = NO ; } break ; case ')': if((lastChar == CTRL_X) && definingMacro) { // [window message: "Keyboard macro defined."] ; // [self perform: @selector(clearMessage:) with: self // afterDelay: 1500 cancelPrevious: YES] ; definingMacro = NO ; NXSeek(macroStream, -2, NX_FROMCURRENT) ; // seek back 2 to remove ctrl_X ) NXPutc(macroStream,'\0') ; // null-terminate the stream insertChar = NO ; } else { i = findMatchingOpenParen(self,')','(',selEnd.cp); [super keyDown:theEvent]; if (i >= 0) highlightChar(self,i); insertChar = NO ; } break ; case '}': i = findMatchingOpenParen(self,'}','{',selEnd.cp); [super keyDown:theEvent]; if(i >= 0) highlightChar(self,i); insertChar = NO ; break ; case ']': i = findMatchingOpenParen(self,']','[',selEnd.cp); [super keyDown:theEvent]; if (i >= 0) highlightChar(self,i); insertChar = NO ; break ; case 'e': if(lastChar == CTRL_X) { if(!definingMacro) { // replay macro replayingMacro = YES ; NXSeek(macroStream,0L,NX_FROMSTART) ; insertChar = NO ; } else NXSeek(macroStream, -1, NX_FROMCURRENT) ; // seek back 1 to remove ctrl_X } break ; case 'i': if(lastChar == CTRL_X) { if(!definingMacro) { // insert a file into the text int rval ; id openPanel = [OpenPanel new] ; [openPanel allowMultipleFiles: NO] ; rval = [openPanel runModalForTypes: NULL] ; if(rval == NX_OKTAG) { NXStream *fileStream ; char fName[2048] ; sprintf(fName,"%s/%s",[openPanel directory],*[openPanel filenames]) ; if((fileStream = NXMapFile(fName,NX_READONLY)) != NULL) { int len,unused ; char *buf ; // [window message: "Inserting file..."] ; NXGetMemoryBuffer(fileStream,&buf,&len,&unused) ; [self replaceSel: buf length: len] ; // [window message: ""] ; } else NXRunAlertPanel("Eval","Couldn't insert file: %s",NULL,NULL,NULL,fName) ; } } insertChar = NO ; } break ; case 'k': // CTRL_X k = close window if(lastChar == CTRL_X) { [window performClose: self] ; insertChar = NO ; } break ; case 's': if(lastChar == CTRL_X) { [window save: self] ; insertChar = NO ; } break ; case 'v': if(lastChar != ESC) break ; case 0xd6: // ALT-v or ESC 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 'w': if(lastChar != ESC) break ; case 0xc8: // ALT-w or ESC 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_C: if(lastChar == CTRL_X) { [NXApp terminate: self] ; 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_F: // CTRL_X CTRL_F = open file, CTRL_F forwards one char if(lastChar == CTRL_X) { [NXApp open: self] ; } else { [self getSel:&selStart :&selEnd]; if(selEnd.cp < [self textLength]) [self setSel:selEnd.cp+1 :selEnd.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 (and place at end of killedText) { 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_L: // center the current selection { // get the selection's location in the scrollview's coordinates NXPoint loc, scrollPoint = {0.0,0.0} ; NXRect scrollRect ; float delta ; id sView = [[self superview] superview] ; [self getSel:&selStart :&selEnd]; loc.x = selStart.x ; loc.y = selStart.y ; [self convertPoint: &loc toView: sView] ; // get the scrollview's bounds [sView getBounds: &scrollRect] ; // how far is loc.y from middle of scrollview?...reset delta to this value delta = scrollRect.size.height / 2.0 - loc.y ; [sView convertPoint: &scrollPoint toView: self] ; scrollPoint.y -= delta ; [self scrollPoint: &scrollPoint] ; 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_S: // ctrl x ctrl s = save if(lastChar == CTRL_X) { [window save: self] ; insertChar = NO ; } else { // // // // extern id finderObject ; // declared in Finder.m // // [finderObject doFind: self] ; // // } 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_X: // don't want CTRL_X to cause a beep 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] ; if(replayingMacro) { nextChar = NXGetc(macroStream) ; if(nextChar == '\0') { replayingMacro = NO ; NXPing() ; } else theEvent->data.key.charCode = nextChar ; } } while(replayingMacro) ; 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 @implementation MiscEmacsText(Dropping) - (BOOL)readDragData:(Pasteboard *)pboard { return YES; } - (NXDragOperation)draggingEntered:(id <NXDraggingInfo>)sender { NXDragOperation sourceMask; sourceMask = [sender draggingSourceOperationMask]; if (sourceMask & NX_DragOperationCopy) return NX_DragOperationCopy; return NX_DragOperationNone; } - (BOOL)performDragOperation:(id <NXDraggingInfo>)sender { return YES; } - (BOOL)prepareForDragOperation:(id <NXDraggingInfo>)sender { return YES; } - concludeDragOperation:(id <NXDraggingInfo>)sender { [self pasteFrom:[sender draggingPasteboard]]; return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.