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.