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