home *** CD-ROM | disk | FTP | other *** search
/ Nebula / nebula.bin / SourceCode / TextORama / EmacsText.m < prev    next >
Text File  |  1993-01-19  |  10KB  |  400 lines

  1. /* EmacsText.m
  2.  *
  3.  *  EmacsText is a subclass of Text which adds support for
  4.  * keyboard bindings commonly used by the emacs editor.
  5.  *
  6.  * You may freely copy, distribute, and reuse the code in this example.
  7.  * NeXT disclaims any warranty of any kind, expressed or  implied, as to its
  8.  * fitness for any particular use.
  9.  *
  10.  * Written by:  Julie Zelenski
  11.  * Created:  Sept/91
  12.  */
  13.  
  14. #import "EmacsText.h"
  15. #import <appkit/nextstd.h>
  16.  
  17. @implementation EmacsText
  18.  
  19. /** This is the charCode offset for Control keys from Encoding Vectors Tech Doc **/
  20.  
  21. #define CONTROL_OFFSET (unsigned short)0x40
  22.  
  23.  
  24. /** Cursor Movement Commands **/
  25.  
  26. #define CTRL_A ('A'  - CONTROL_OFFSET)
  27. #define CTRL_B ('B'  - CONTROL_OFFSET)
  28. #define CTRL_E ('E'  - CONTROL_OFFSET)
  29. #define CTRL_F ('F'  - CONTROL_OFFSET)
  30. #define CTRL_N ('N'  - CONTROL_OFFSET)
  31. #define CTRL_P ('P'  - CONTROL_OFFSET)
  32. #define ALT_LESS ((unsigned short)0xa3)
  33. #define ALT_GREATER ((unsigned short) 0xb3)
  34. #define ALT_B ((unsigned short) 0xe5)
  35. #define ALT_F ((unsigned short) 0xa6)
  36.  
  37. /** Delete Commands  **/
  38.  
  39. #define CTRL_D ('D'  - CONTROL_OFFSET)
  40. #define CTRL_K ('K'  - CONTROL_OFFSET)
  41. #define CTRL_O ('O'  - CONTROL_OFFSET)
  42. #define CTRL_Y ('Y'  - CONTROL_OFFSET)
  43. #define ALT_D ((unsigned short) 0x44)
  44. #define ALT_H ((unsigned short) 0xe3)
  45.  
  46.  
  47.  
  48. typedef struct _sel{
  49.     unsigned short charCode;
  50.     SEL *selector;
  51.     SEL *positionSelector;
  52. } SelectorItem;
  53.  
  54. /*  Selector Item is
  55. {charCode,command selector,        "helper" selection}
  56. */
  57.  
  58. static SelectorItem emacsMetaKeys[] = {
  59. {ALT_B, &@selector(moveToPosition:),    &@selector(positionForWordBegin)},
  60. {ALT_F, &@selector(moveToPosition:),    &@selector(positionForWordEnd)},
  61. {ALT_LESS, &@selector(moveToPosition:),    &@selector(positionForDocumentBegin)},
  62. {ALT_GREATER, &@selector(moveToPosition:),    &@selector(positionForDocumentEnd)},
  63. {ALT_D, &@selector(deleteToPosition:),    &@selector(positionForWordEnd)},
  64. {ALT_H, &@selector(deleteToPosition:),    &@selector(positionForWordBegin)},
  65. {0}
  66. };
  67.  
  68. static SelectorItem emacsControlKeys[] = {
  69. {CTRL_A, &@selector(moveToPosition:),    &@selector(positionForLineBegin)},
  70. {CTRL_E, &@selector(moveToPosition:),    &@selector(positionForLineEnd)},
  71. {CTRL_K, &@selector(deleteToLineEnd),    0},
  72. {CTRL_D, &@selector(deleteToPosition:),    &@selector(nextPositionIfEmpty)},
  73. {CTRL_Y, &@selector(yank),        0},
  74. {0}
  75. };
  76.  
  77. unsigned short emacsFilter (unsigned short
  78.     charCode, int flags, unsigned short charSet)
  79. {
  80.     if (flags & NX_CONTROLMASK) {         
  81.     switch(charCode) {
  82.         case CTRL_F:
  83.         return NX_RIGHT;
  84.         case CTRL_B:
  85.             return NX_LEFT;
  86.         case CTRL_N:
  87.             return NX_DOWN;
  88.         case CTRL_P:
  89.             return NX_UP;
  90.         default: break;
  91.     }
  92.     } 
  93.     return NXEditorFilter(charCode, flags, charSet);
  94. }
  95.  
  96.  
  97. int GetPrevious(NXStream *s)
  98. {
  99.      int pos;
  100.      int ch;
  101.      
  102.      pos = NXTell(s);
  103.      if (pos <= 0) return EOF;
  104.      NXSeek(s, --pos, NX_FROMSTART);
  105.      ch = NXGetc(s);
  106.      NXUngetc(s);
  107.      return ch;
  108. }
  109.  
  110. - (int)positionForLineBeginActual
  111. /* Not currently in use.  Looks for newline to find actual paragraph begin.
  112.  */
  113. {
  114.     NXStream *s = [self stream];
  115.     int pos;
  116.     int ch;
  117.     
  118.     if (spN.cp < 0) return 0; // Is this the right thing to do here?
  119.  
  120.     NXSeek(s, sp0.cp, NX_FROMSTART);
  121.     while (((ch = GetPrevious(s)) != EOF) && (ch != '\n'));
  122.     pos = NXTell(s);
  123.     if (ch != EOF) pos++;
  124.     return pos;
  125. }
  126.  
  127. - (int)positionForLineEndActual
  128. /* Not currently in use.  Looks for newline to find actual paragraph end.
  129.  */
  130. {
  131.     NXStream *s = [self stream];
  132.  
  133.     int pos;
  134.     int ch;
  135.     int max = [self textLength];
  136.     
  137.     if (spN.cp < 0) return 0;     // boundary conditions?  Is this right idea?
  138.     if (spN.cp > max) return max;
  139.  
  140.     NXSeek(s, spN.cp, NX_FROMSTART);
  141.     while (((ch = NXGetc(s)) != EOF) && (ch != '\n'));
  142.     pos = NXTell(s);
  143.     if (ch != EOF) pos--;
  144.     return pos;
  145. }
  146.  
  147. - (int)positionForLineEndVisual
  148. /* This uses the break array to find the visual line end.  
  149.  * However, it subtracts one from the position because of that behavior 
  150.  * of the Text object that makes the position at the end of one line 
  151.  * the same character position a the beginning of next line.  Seems to
  152.  * be no way to position the insertion point at the end of the line.
  153.  * Bummer.
  154.  */
  155. {
  156.     int lineLength;
  157.     int line;
  158.     
  159.     line = (spN.line /sizeof(NXLineDesc));
  160.     lineLength = theBreaks->breaks[line] & 0x3fff;
  161.     lineLength--; // Notice the hack....
  162.     return (spN.c1st + lineLength);
  163. }
  164.  
  165. - (int)positionForLineBeginVisual
  166. {
  167.     return (sp0.c1st);
  168. }
  169.  
  170. /** BIG FAT HAIRY NOTE
  171.  * This is how to change CTRL-A, CTRL-E, CTRL-K to use paragraph ends
  172.  * (actual newlines) instead of visual line breaks.  Have the position
  173.  * for line end methods call to the position for actual rather than
  174.  * visual.
  175.  */
  176.  
  177. - (int)positionForLineBegin
  178. {
  179.     return [self positionForLineBeginVisual];
  180. }
  181.  
  182. - (int)positionForLineEnd
  183. {
  184.     return [self positionForLineEndVisual];
  185.  
  186. }
  187.  
  188. - (int)nextPositionIfEmpty
  189. {
  190.      if (sp0.cp == spN.cp) 
  191.     return spN.cp + 1;
  192.     else
  193.     return spN.cp;
  194. }
  195.  
  196. /* This is my quick decision on what characters count as a word, and which
  197.  * don't.  The correct way to do this is to parse the ClickTable, but the
  198.  * documentation is so incredibly sparse on this one....
  199.  */
  200.  
  201. #define NORMAL_CHAR(ch) (((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) ||((ch >= '0') && (ch <= '9')) || (ch == '\'')|| (ch == '_'))
  202.  
  203.  
  204. - (int)positionForWordEnd
  205. {
  206.     NXStream *s = [self stream];
  207.  
  208.     int pos;
  209.     int ch;
  210.     int max = [self textLength];
  211.     
  212.     if (spN.cp < 0) return 0;     // boundary conditions?  Is this right idea?
  213.     if (spN.cp > max) return max;
  214.  
  215.     NXSeek(s, spN.cp, NX_FROMSTART);
  216.     while ((ch = NXGetc(s)) != EOF && !NORMAL_CHAR(ch)); // skip white space
  217.     while ((ch = NXGetc(s)) != EOF && NORMAL_CHAR(ch));    // jump normal chars
  218.     pos = NXTell(s);
  219.     if (ch != EOF) pos--;
  220.     return pos;
  221. }
  222.  
  223. - (int)positionForWordBegin
  224. {
  225.     NXStream *s = [self stream];
  226.  
  227.     int pos;
  228.     int ch;
  229.     int max = [self textLength];
  230.     
  231.     if (spN.cp < 0) return 0;     // boundary conditions?  Is this right idea?
  232.     if (spN.cp > max) return max;
  233.  
  234.     NXSeek(s, sp0.cp, NX_FROMSTART);
  235.     while ((ch = GetPrevious(s)) != EOF && !NORMAL_CHAR(ch)); // skip white space
  236.     while ((ch = GetPrevious(s)) != EOF && NORMAL_CHAR(ch)); // jump normal chars
  237.     pos = NXTell(s);
  238.     if (ch != EOF) pos++;
  239.     return pos;
  240. }
  241.  
  242. - (int) positionForDocumentEnd
  243. {
  244.      return [self textLength];
  245. }
  246.  
  247. - (int) positionForDocumentBegin
  248. {
  249.      return 0;
  250. }
  251.  
  252. - moveToPosition:(SEL)command
  253. {
  254.     int pos;
  255.     
  256.     pos = (int)[self perform:command];
  257.     [self setSel:pos :pos];
  258.     [self scrollSelToVisible];
  259.     return self;
  260. }
  261.  
  262. - deleteToPosition:(SEL)command
  263. /* Entry point for delete forward/backward word
  264.  */
  265. {
  266.     int pos;
  267.     int start,end;
  268.     
  269.     pos = (int)[self perform:command];
  270.     if (pos > spN.cp) { // if position extends to the right
  271.         start = sp0.cp;
  272.     end = pos;
  273.     } else {        // else position extends to the left
  274.         start = pos;
  275.     end = spN.cp;
  276.     }
  277.     [self delete:start :end];
  278.     return self;
  279. }
  280.  
  281. - delete:(int)start :(int)end
  282. /* Entry point for all deletes done for Emacs bindings.  Turns off 
  283.  * autodisplay to avoid flicker and other unwanted drawing artifacts.
  284.  * Calls cut and uses the Pasteboard to implement yank.  It is possible
  285.  * to implement separate Emacs kill buffer, but it would be a bit of
  286.  * hassle, because you need a Change object to keep both the runs and
  287.  * the text that is yanked.  You can do it quick by storing only ASCII
  288.  * text, which is not a good idea.  (Actually, to be correct, this is
  289.  * all that Edit does, but who wants to use Edit for a role model?)
  290.  */
  291. {
  292.     if (end - start) {
  293.     [self setAutodisplay:NO];
  294.     [self setSel:start :end];
  295.     [self cut:self];
  296.     [[self setAutodisplay:YES] display];
  297.     }
  298.     return self;
  299. }
  300.  
  301.  
  302. - deleteToLineEnd
  303. /* Somewhat icky hack has to handle the special case for deleting at end 
  304.  * of line.  If in middle of line, don't delete the new line.  If at the 
  305.  * very end of the line, do delete the new line.
  306.  */
  307. {
  308.     int pos;
  309.     int start,end;
  310.     
  311.     pos = [self positionForLineEnd];
  312.     start = sp0.cp;
  313.     end = pos;
  314.     if (start == end) {// If already at end of line
  315.     int line;
  316.     int endsWithNewLine;
  317.     
  318.     line = (spN.line /sizeof(NXLineDesc));
  319.     endsWithNewLine = theBreaks->breaks[line] & 0x4000;
  320.  
  321.     if (endsWithNewLine) 
  322.         end++;
  323.     else  // Bail on case where at visual end of line, but no newline
  324.         return self;
  325.     }
  326.     [self delete:start :end];
  327.     return self;
  328. }
  329.  
  330.  
  331. - yank
  332. {
  333.     [self paste:self];
  334.     return self;
  335. }
  336.  
  337.  
  338. - (BOOL) emacsEvent:(NXEvent *)event
  339. {
  340.     SelectorItem *cur;
  341.     unsigned charCode = event->data.key.charCode;
  342.     
  343.     if (event->flags & NX_CONTROLMASK) {  
  344.         cur = emacsControlKeys;
  345.             
  346.     while (cur->charCode && (cur->charCode != charCode)) cur++;
  347.     if (cur->charCode) {
  348.         [self perform:*cur->selector 
  349.             withSel:(cur->positionSelector? *cur->positionSelector : 0)];
  350.         return YES;
  351.     }
  352.     }
  353.     if (event->flags & NX_ALTERNATEMASK) {  
  354.         cur = emacsMetaKeys;
  355.             
  356.     while (cur->charCode && (cur->charCode != charCode)) cur++;
  357.     if (cur->charCode) {
  358.         [self perform:*cur->selector 
  359.             withSel:(cur->positionSelector? *cur->positionSelector : 0)];
  360.         return YES;
  361.     }
  362.     }
  363.     return NO;
  364. }
  365.  
  366. - keyDown:(NXEvent *)event
  367. {
  368.     if ([self emacsEvent:event]) 
  369.     return self;
  370.     else
  371.     return [super keyDown:event];
  372. }
  373.  
  374. - (int)perform:(SEL)selector withSel:(SEL)helper 
  375. {
  376.     int   (*func)(id,SEL,SEL); 
  377.     
  378.     func = (int (*)(id,SEL,SEL))[self methodFor:selector];
  379.     return (* func)(self, selector, helper);
  380. }
  381.  
  382. - initFrame:(NXRect *)fRect
  383. {
  384.     NXRect r = *fRect;
  385.     [super initFrame:fRect];
  386.     [self setMonoFont:NO];
  387.     [self setBackgroundGray:NX_WHITE];
  388.     [self setOpaque:YES];
  389.     [self setCharFilter:(NXCharFilterFunc)emacsFilter];
  390.     [self notifyAncestorWhenFrameChanged: YES];
  391.     [self setVertResizable:YES];
  392.     [self setMinSize:&r.size];
  393.     r.size.height = 1.0e30;
  394.     [self setMaxSize:&r.size];
  395.     return self;
  396. }
  397.  
  398.  
  399. @end
  400.