Moved
[MacVim/jjgod.git] / MMBackend.m
blob9459fda341c4af10064faaa91857536aac0dab24
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
11 #import "MMBackend.h"
15 // This constant controls how often the command queue may be flushed.  If it is
16 // too small the app might feel unresponsive; if it is too large there might be
17 // long periods without the screen updating (e.g. when sourcing a large session
18 // file).  (The unit is seconds.)
19 static float MMFlushTimeoutInterval = 0.1f;
21 static unsigned MMServerMax = 1000;
22 //static NSTimeInterval MMEvaluateExpressionTimeout = 3;
25 // TODO: Move to separate file.
26 static int eventModifierFlagsToVimModMask(int modifierFlags);
27 static int vimModMaskToEventModifierFlags(int mods);
28 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
29 static int eventButtonNumberToVimMouseButton(int buttonNumber);
30 static int specialKeyToNSKey(int key);
32 enum {
33     MMBlinkStateNone = 0,
34     MMBlinkStateOn,
35     MMBlinkStateOff
40 @interface NSString (MMServerNameCompare)
41 - (NSComparisonResult)serverNameCompare:(NSString *)string;
42 @end
46 @interface MMBackend (Private)
47 - (void)handleMessage:(int)msgid data:(NSData *)data;
48 + (NSDictionary *)specialKeys;
49 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
50 - (void)queueMessage:(int)msgid data:(NSData *)data;
51 - (void)connectionDidDie:(NSNotification *)notification;
52 - (void)blinkTimerFired:(NSTimer *)timer;
53 - (void)focusChange:(BOOL)on;
54 - (void)processInputBegin;
55 - (void)processInputEnd;
56 - (NSString *)connectionNameFromServerName:(NSString *)name;
57 - (NSConnection *)connectionForServerName:(NSString *)name;
58 - (NSConnection *)connectionForServerPort:(int)port;
59 - (void)serverConnectionDidDie:(NSNotification *)notification;
60 - (void)addClient:(NSDistantObject *)client;
61 - (NSString *)alternateServerNameForName:(NSString *)name;
62 @end
66 @implementation MMBackend
68 + (MMBackend *)sharedInstance
70     static MMBackend *singleton = nil;
71     return singleton ? singleton : (singleton = [MMBackend new]);
74 - (id)init
76     if ((self = [super init])) {
77         queue = [[NSMutableArray alloc] init];
78 #if MM_USE_INPUT_QUEUE
79         inputQueue = [[NSMutableArray alloc] init];
80 #endif
81         drawData = [[NSMutableData alloc] initWithCapacity:1024];
82         connectionNameDict = [[NSMutableDictionary alloc] init];
83         clientProxyDict = [[NSMutableDictionary alloc] init];
84         serverReplyDict = [[NSMutableDictionary alloc] init];
86         NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
87                                                          ofType:@"plist"];
88         if (path) {
89             colorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
90                 retain];
91         } else {
92             NSLog(@"WARNING: Could not locate Colors.plist.");
93         }
95         path = [[NSBundle mainBundle] pathForResource:@"SystemColors"
96                                                ofType:@"plist"];
97         if (path) {
98             sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
99                 retain];
100         } else {
101             NSLog(@"WARNING: Could not locate SystemColors.plist.");
102         }
103     }
105     return self;
108 - (void)dealloc
110     //NSLog(@"%@ %s", [self className], _cmd);
112     [[NSNotificationCenter defaultCenter] removeObserver:self];
114     [blinkTimer release];  blinkTimer = nil;
115 #if MM_USE_INPUT_QUEUE
116     [inputQueue release];  inputQueue = nil;
117 #endif
118     [alternateServerName release];  alternateServerName = nil;
119     [serverReplyDict release];  serverReplyDict = nil;
120     [clientProxyDict release];  clientProxyDict = nil;
121     [connectionNameDict release];  connectionNameDict = nil;
122     [queue release];  queue = nil;
123     [drawData release];  drawData = nil;
124     [frontendProxy release];  frontendProxy = nil;
125     [connection release];  connection = nil;
126     [sysColorDict release];  sysColorDict = nil;
127     [colorDict release];  colorDict = nil;
129     [super dealloc];
132 - (void)setBackgroundColor:(int)color
134     backgroundColor = color;
137 - (void)setForegroundColor:(int)color
139     foregroundColor = color;
142 - (void)setSpecialColor:(int)color
144     specialColor = color;
147 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
149     defaultBackgroundColor = bg;
150     defaultForegroundColor = fg;
152     NSMutableData *data = [NSMutableData data];
154     [data appendBytes:&bg length:sizeof(int)];
155     [data appendBytes:&fg length:sizeof(int)];
157     [self queueMessage:SetDefaultColorsMsgID data:data];
160 - (NSConnection *)connection
162     if (!connection) {
163         // NOTE!  If the name of the connection changes here it must also be
164         // updated in MMAppController.m.
165         NSString *name = [NSString stringWithFormat:@"%@-connection",
166                [[NSBundle mainBundle] bundleIdentifier]];
168         connection = [NSConnection connectionWithRegisteredName:name host:nil];
169         [connection retain];
170     }
172     // NOTE: 'connection' may be nil here.
173     return connection;
176 - (BOOL)checkin
178     if (![self connection]) {
179         NSBundle *mainBundle = [NSBundle mainBundle];
180 #if 0
181         NSString *path = [mainBundle bundlePath];
182         if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
183             NSLog(@"WARNING: Failed to launch GUI with path %@", path);
184             return NO;
185         }
186 #else
187         // HACK!  It would be preferable to launch the GUI using NSWorkspace,
188         // however I have not managed to figure out how to pass arguments using
189         // NSWorkspace.
190         //
191         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
192         // that the GUI won't be activated (or raised) so there is a hack in
193         // MMWindowController which always raises the app when a new window is
194         // opened.
195         NSMutableArray *args = [NSMutableArray arrayWithObjects:
196             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
197         NSString *exeName = [[mainBundle infoDictionary]
198                 objectForKey:@"CFBundleExecutable"];
199         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
200         if (!path) {
201             NSLog(@"ERROR: Could not find MacVim executable in bundle");
202             return NO;
203         }
205         [NSTask launchedTaskWithLaunchPath:path arguments:args];
206 #endif
208         // HACK!  The NSWorkspaceDidLaunchApplicationNotification does not work
209         // for tasks like this, so poll the mach bootstrap server until it
210         // returns a valid connection.  Also set a time-out date so that we
211         // don't get stuck doing this forever.
212         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
213         while (!connection &&
214                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
215         {
216             [[NSRunLoop currentRunLoop]
217                     runMode:NSDefaultRunLoopMode
218                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
220             // NOTE: This call will set 'connection' as a side-effect.
221             [self connection];
222         }
224         if (!connection) {
225             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
226             return NO;
227         }
228     }
230     id proxy = [connection rootProxy];
231     [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
233     [[NSNotificationCenter defaultCenter] addObserver:self
234             selector:@selector(connectionDidDie:)
235                 name:NSConnectionDidDieNotification object:connection];
237     int pid = [[NSProcessInfo processInfo] processIdentifier];
239     @try {
240         frontendProxy = [proxy connectBackend:self pid:pid];
241     }
242     @catch (NSException *e) {
243         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
244     }
246     if (frontendProxy) {
247         [frontendProxy retain];
248         [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
249     }
251     return connection && frontendProxy;
254 - (BOOL)openVimWindow
256     [self queueMessage:OpenVimWindowMsgID data:nil];
257     return YES;
260 - (void)clearAll
262     int type = ClearAllDrawType;
264     // Any draw commands in queue are effectively obsolete since this clearAll
265     // will negate any effect they have, therefore we may as well clear the
266     // draw queue.
267     [drawData setLength:0];
269     [drawData appendBytes:&type length:sizeof(int)];
271     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
274 - (void)clearBlockFromRow:(int)row1 column:(int)col1
275                     toRow:(int)row2 column:(int)col2
277     int type = ClearBlockDrawType;
279     [drawData appendBytes:&type length:sizeof(int)];
281     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
282     [drawData appendBytes:&row1 length:sizeof(int)];
283     [drawData appendBytes:&col1 length:sizeof(int)];
284     [drawData appendBytes:&row2 length:sizeof(int)];
285     [drawData appendBytes:&col2 length:sizeof(int)];
288 - (void)deleteLinesFromRow:(int)row count:(int)count
289               scrollBottom:(int)bottom left:(int)left right:(int)right
291     int type = DeleteLinesDrawType;
293     [drawData appendBytes:&type length:sizeof(int)];
295     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
296     [drawData appendBytes:&row length:sizeof(int)];
297     [drawData appendBytes:&count length:sizeof(int)];
298     [drawData appendBytes:&bottom length:sizeof(int)];
299     [drawData appendBytes:&left length:sizeof(int)];
300     [drawData appendBytes:&right length:sizeof(int)];
303 - (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
304                 flags:(int)flags
306     if (len <= 0) return;
308     int type = ReplaceStringDrawType;
310     [drawData appendBytes:&type length:sizeof(int)];
312     [drawData appendBytes:&backgroundColor length:sizeof(int)];
313     [drawData appendBytes:&foregroundColor length:sizeof(int)];
314     [drawData appendBytes:&specialColor length:sizeof(int)];
315     [drawData appendBytes:&row length:sizeof(int)];
316     [drawData appendBytes:&col length:sizeof(int)];
317     [drawData appendBytes:&flags length:sizeof(int)];
318     [drawData appendBytes:&len length:sizeof(int)];
319     [drawData appendBytes:s length:len];
322 - (void)insertLinesFromRow:(int)row count:(int)count
323               scrollBottom:(int)bottom left:(int)left right:(int)right
325     int type = InsertLinesDrawType;
327     [drawData appendBytes:&type length:sizeof(int)];
329     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
330     [drawData appendBytes:&row length:sizeof(int)];
331     [drawData appendBytes:&count length:sizeof(int)];
332     [drawData appendBytes:&bottom length:sizeof(int)];
333     [drawData appendBytes:&left length:sizeof(int)];
334     [drawData appendBytes:&right length:sizeof(int)];
337 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
338                fraction:(int)percent color:(int)color
340     int type = DrawCursorDrawType;
342     [drawData appendBytes:&type length:sizeof(int)];
344     [drawData appendBytes:&color length:sizeof(int)];
345     [drawData appendBytes:&row length:sizeof(int)];
346     [drawData appendBytes:&col length:sizeof(int)];
347     [drawData appendBytes:&shape length:sizeof(int)];
348     [drawData appendBytes:&percent length:sizeof(int)];
351 - (void)flushQueue:(BOOL)force
353     // NOTE! This method gets called a lot; if we were to flush every time it
354     // was called MacVim would feel unresponsive.  So there is a time out which
355     // ensures that the queue isn't flushed too often.
356     if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
357             < MMFlushTimeoutInterval)
358         return;
360     if ([drawData length] > 0) {
361         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
362         [drawData setLength:0];
363     }
365     if ([queue count] > 0) {
366         @try {
367             [frontendProxy processCommandQueue:queue];
368         }
369         @catch (NSException *e) {
370             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
371         }
373         [queue removeAllObjects];
375         [lastFlushDate release];
376         lastFlushDate = [[NSDate date] retain];
377     }
380 - (BOOL)waitForInput:(int)milliseconds
382     NSDate *date = milliseconds > 0 ?
383             [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
384             [NSDate distantFuture];
386     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
388     // I know of no way to figure out if the run loop exited because input was
389     // found or because of a time out, so I need to manually indicate when
390     // input was received in processInput:data: and then reset it every time
391     // here.
392     BOOL yn = inputReceived;
393     inputReceived = NO;
395     return yn;
398 - (void)exit
400 #ifdef MAC_CLIENTSERVER
401     // The default connection is used for the client/server code.
402     [[NSConnection defaultConnection] setRootObject:nil];
403     [[NSConnection defaultConnection] invalidate];
404 #endif
406     // By invalidating the NSConnection the MMWindowController immediately
407     // finds out that the connection is down and as a result
408     // [MMWindowController connectionDidDie:] is invoked.
409     //NSLog(@"%@ %s", [self className], _cmd);
410     [[NSNotificationCenter defaultCenter] removeObserver:self];
411     [connection invalidate];
414 - (void)selectTab:(int)index
416     //NSLog(@"%s%d", _cmd, index);
418     index -= 1;
419     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
420     [self queueMessage:SelectTabMsgID data:data];
423 - (void)updateTabBar
425     //NSLog(@"%s", _cmd);
427     NSMutableData *data = [NSMutableData data];
429     int idx = tabpage_index(curtab) - 1;
430     [data appendBytes:&idx length:sizeof(int)];
432     tabpage_T *tp;
433     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
434         // This function puts the label of the tab in the global 'NameBuff'.
435         get_tabline_label(tp, FALSE);
436         int len = strlen((char*)NameBuff);
437         if (len <= 0) continue;
439         // Count the number of windows in the tabpage.
440         //win_T *wp = tp->tp_firstwin;
441         //int wincount;
442         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
444         //[data appendBytes:&wincount length:sizeof(int)];
445         [data appendBytes:&len length:sizeof(int)];
446         [data appendBytes:NameBuff length:len];
447     }
449     [self queueMessage:UpdateTabBarMsgID data:data];
452 - (BOOL)tabBarVisible
454     return tabBarVisible;
457 - (void)showTabBar:(BOOL)enable
459     tabBarVisible = enable;
461     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
462     [self queueMessage:msgid data:nil];
465 - (void)setRows:(int)rows columns:(int)cols
467     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
469     int dim[] = { rows, cols };
470     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
472     [self queueMessage:SetTextDimensionsMsgID data:data];
475 - (void)setVimWindowTitle:(char *)title
477     NSMutableData *data = [NSMutableData data];
478     int len = strlen(title);
479     if (len <= 0) return;
481     [data appendBytes:&len length:sizeof(int)];
482     [data appendBytes:title length:len];
484     [self queueMessage:SetVimWindowTitleMsgID data:data];
487 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
488                             saving:(int)saving
490     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
491     //        saving);
493     char_u *s = NULL;
494     NSString *ds = dir
495             ? [NSString stringWithCString:dir encoding:NSUTF8StringEncoding]
496             : nil;
497     NSString *ts = title
498             ? [NSString stringWithCString:title encoding:NSUTF8StringEncoding]
499             : nil;
500     @try {
501         [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
503         // Wait until a reply is sent from MMVimController.
504         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
505                                  beforeDate:[NSDate distantFuture]];
507         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
508             s = vim_strsave((char_u*)[dialogReturn UTF8String]);
509         }
511         [dialogReturn release];  dialogReturn = nil;
512     }
513     @catch (NSException *e) {
514         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
515     }
517     return (char *)s;
520 - (oneway void)setDialogReturn:(in bycopy id)obj
522     // NOTE: This is called by
523     //   - [MMVimController panelDidEnd:::], and
524     //   - [MMVimController alertDidEnd:::],
525     // to indicate that a save/open panel or alert has finished.
527     if (obj != dialogReturn) {
528         [dialogReturn release];
529         dialogReturn = [obj retain];
530     }
533 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
534                      buttons:(char *)btns textField:(char *)txtfield
536     int retval = 0;
537     NSString *message = nil, *text = nil, *textFieldString = nil;
538     NSArray *buttons = nil;
539     int style = NSInformationalAlertStyle;
541     if (VIM_WARNING == type) style = NSWarningAlertStyle;
542     else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
544     if (btns) {
545         NSString *btnString = [NSString stringWithUTF8String:btns];
546         buttons = [btnString componentsSeparatedByString:@"\n"];
547     }
548     if (title)
549         message = [NSString stringWithUTF8String:title];
550     if (msg) {
551         text = [NSString stringWithUTF8String:msg];
552         if (!message) {
553             // HACK! If there is a '\n\n' or '\n' sequence in the message, then
554             // make the part up to there into the title.  We only do this
555             // because Vim has lots of dialogs without a title and they look
556             // ugly that way.
557             // TODO: Fix the actual dialog texts.
558             NSRange eolRange = [text rangeOfString:@"\n\n"];
559             if (NSNotFound == eolRange.location)
560                 eolRange = [text rangeOfString:@"\n"];
561             if (NSNotFound != eolRange.location) {
562                 message = [text substringToIndex:eolRange.location];
563                 text = [text substringFromIndex:NSMaxRange(eolRange)];
564             }
565         }
566     }
567     if (txtfield)
568         textFieldString = [NSString stringWithUTF8String:txtfield];
570     @try {
571         [frontendProxy presentDialogWithStyle:style message:message
572                               informativeText:text buttonTitles:buttons
573                               textFieldString:textFieldString];
575         // Wait until a reply is sent from MMVimController.
576         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
577                                  beforeDate:[NSDate distantFuture]];
579         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
580                 && [dialogReturn count]) {
581             retval = [[dialogReturn objectAtIndex:0] intValue];
582             if (txtfield && [dialogReturn count] > 1) {
583                 NSString *retString = [dialogReturn objectAtIndex:1];
584                 vim_strncpy((char_u*)txtfield, (char_u*)[retString UTF8String],
585                         IOSIZE - 1);
586             }
587         }
589         [dialogReturn release]; dialogReturn = nil;
590     }
591     @catch (NSException *e) {
592         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
593     }
595     return retval;
598 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
599                atIndex:(int)index
601     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
602     //        name, index);
604     int namelen = name ? strlen(name) : 0;
605     NSMutableData *data = [NSMutableData data];
607     [data appendBytes:&tag length:sizeof(int)];
608     [data appendBytes:&parentTag length:sizeof(int)];
609     [data appendBytes:&namelen length:sizeof(int)];
610     if (namelen > 0) [data appendBytes:name length:namelen];
611     [data appendBytes:&index length:sizeof(int)];
613     [self queueMessage:AddMenuMsgID data:data];
616 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
617                        tip:(char *)tip icon:(char *)icon
618              keyEquivalent:(int)key modifiers:(int)mods
619                     action:(NSString *)action atIndex:(int)index
621     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
622     //        parentTag, name, tip, index);
624     int namelen = name ? strlen(name) : 0;
625     int tiplen = tip ? strlen(tip) : 0;
626     int iconlen = icon ? strlen(icon) : 0;
627     int eventFlags = vimModMaskToEventModifierFlags(mods);
628     int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
629     NSMutableData *data = [NSMutableData data];
631     key = specialKeyToNSKey(key);
633     [data appendBytes:&tag length:sizeof(int)];
634     [data appendBytes:&parentTag length:sizeof(int)];
635     [data appendBytes:&namelen length:sizeof(int)];
636     if (namelen > 0) [data appendBytes:name length:namelen];
637     [data appendBytes:&tiplen length:sizeof(int)];
638     if (tiplen > 0) [data appendBytes:tip length:tiplen];
639     [data appendBytes:&iconlen length:sizeof(int)];
640     if (iconlen > 0) [data appendBytes:icon length:iconlen];
641     [data appendBytes:&actionlen length:sizeof(int)];
642     if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
643     [data appendBytes:&index length:sizeof(int)];
644     [data appendBytes:&key length:sizeof(int)];
645     [data appendBytes:&eventFlags length:sizeof(int)];
647     [self queueMessage:AddMenuItemMsgID data:data];
650 - (void)removeMenuItemWithTag:(int)tag
652     NSMutableData *data = [NSMutableData data];
653     [data appendBytes:&tag length:sizeof(int)];
655     [self queueMessage:RemoveMenuItemMsgID data:data];
658 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
660     NSMutableData *data = [NSMutableData data];
662     [data appendBytes:&tag length:sizeof(int)];
663     [data appendBytes:&enabled length:sizeof(int)];
665     [self queueMessage:EnableMenuItemMsgID data:data];
668 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
670     int len = strlen(name);
671     int row = -1, col = -1;
673     if (len <= 0) return;
675     if (!mouse && curwin) {
676         row = curwin->w_wrow;
677         col = curwin->w_wcol;
678     }
680     NSMutableData *data = [NSMutableData data];
682     [data appendBytes:&row length:sizeof(int)];
683     [data appendBytes:&col length:sizeof(int)];
684     [data appendBytes:&len length:sizeof(int)];
685     [data appendBytes:name length:len];
687     [self queueMessage:ShowPopupMenuMsgID data:data];
690 - (void)showToolbar:(int)enable flags:(int)flags
692     NSMutableData *data = [NSMutableData data];
694     [data appendBytes:&enable length:sizeof(int)];
695     [data appendBytes:&flags length:sizeof(int)];
697     [self queueMessage:ShowToolbarMsgID data:data];
700 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
702     NSMutableData *data = [NSMutableData data];
704     [data appendBytes:&ident length:sizeof(long)];
705     [data appendBytes:&type length:sizeof(int)];
707     [self queueMessage:CreateScrollbarMsgID data:data];
710 - (void)destroyScrollbarWithIdentifier:(long)ident
712     NSMutableData *data = [NSMutableData data];
713     [data appendBytes:&ident length:sizeof(long)];
715     [self queueMessage:DestroyScrollbarMsgID data:data];
718 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
720     NSMutableData *data = [NSMutableData data];
722     [data appendBytes:&ident length:sizeof(long)];
723     [data appendBytes:&visible length:sizeof(int)];
725     [self queueMessage:ShowScrollbarMsgID data:data];
728 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
730     NSMutableData *data = [NSMutableData data];
732     [data appendBytes:&ident length:sizeof(long)];
733     [data appendBytes:&pos length:sizeof(int)];
734     [data appendBytes:&len length:sizeof(int)];
736     [self queueMessage:SetScrollbarPositionMsgID data:data];
739 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
740                     identifier:(long)ident
742     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
743     float prop = (float)size/(max+1);
744     if (fval < 0) fval = 0;
745     else if (fval > 1.0f) fval = 1.0f;
746     if (prop < 0) prop = 0;
747     else if (prop > 1.0f) prop = 1.0f;
749     NSMutableData *data = [NSMutableData data];
751     [data appendBytes:&ident length:sizeof(long)];
752     [data appendBytes:&fval length:sizeof(float)];
753     [data appendBytes:&prop length:sizeof(float)];
755     [self queueMessage:SetScrollbarThumbMsgID data:data];
758 - (BOOL)setFontWithName:(char *)name
760     NSString *fontName;
761     float size = 0.0f;
762     BOOL parseFailed = NO;
764     if (name) {
765         fontName = [[[NSString alloc] initWithCString:name
766                 encoding:NSUTF8StringEncoding] autorelease];
768         if ([fontName isEqual:@"*"]) {
769             // :set gfn=* shows the font panel.
770             do_cmdline_cmd((char_u*)":action orderFrontFontPanel:");
771             return YES;
772         }
774         NSArray *components = [fontName componentsSeparatedByString:@":"];
775         if ([components count] == 2) {
776             NSString *sizeString = [components lastObject];
777             if ([sizeString length] > 0
778                     && [sizeString characterAtIndex:0] == 'h') {
779                 sizeString = [sizeString substringFromIndex:1];
780                 if ([sizeString length] > 0) {
781                     size = [sizeString floatValue];
782                     fontName = [components objectAtIndex:0];
783                 }
784             } else {
785                 parseFailed = YES;
786             }
787         } else if ([components count] > 2) {
788             parseFailed = YES;
789         }
790     } else {
791         fontName = [[NSFont userFixedPitchFontOfSize:0] displayName];
792     }
794     if (!parseFailed && [fontName length] > 0) {
795         if (size < 6 || size > 100) {
796             // Font size 0.0 tells NSFont to use the 'user default size'.
797             size = 0.0f;
798         }
800         NSFont *font = [NSFont fontWithName:fontName size:size];
801         if (font) {
802             //NSLog(@"Setting font '%@' of size %.2f", fontName, size);
803             int len = [fontName
804                     lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
805             if (len > 0) {
806                 NSMutableData *data = [NSMutableData data];
808                 [data appendBytes:&size length:sizeof(float)];
809                 [data appendBytes:&len length:sizeof(int)];
810                 [data appendBytes:[fontName UTF8String] length:len];
812                 [self queueMessage:SetFontMsgID data:data];
813                 return YES;
814             }
815         }
816     }
818     //NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f",
819     //        fontName, size);
820     return NO;
823 - (void)executeActionWithName:(NSString *)name
825     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
827     if (len > 0) {
828         NSMutableData *data = [NSMutableData data];
830         [data appendBytes:&len length:sizeof(int)];
831         [data appendBytes:[name UTF8String] length:len];
833         [self queueMessage:ExecuteActionMsgID data:data];
834     }
837 - (void)setMouseShape:(int)shape
839     NSMutableData *data = [NSMutableData data];
840     [data appendBytes:&shape length:sizeof(int)];
841     [self queueMessage:SetMouseShapeMsgID data:data];
844 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
846     // Vim specifies times in milliseconds, whereas Cocoa wants them in
847     // seconds.
848     blinkWaitInterval = .001f*wait;
849     blinkOnInterval = .001f*on;
850     blinkOffInterval = .001f*off;
853 - (void)startBlink
855     if (blinkTimer) {
856         [blinkTimer invalidate];
857         [blinkTimer release];
858         blinkTimer = nil;
859     }
861     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
862             && gui.in_focus) {
863         blinkState = MMBlinkStateOn;
864         blinkTimer =
865             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
866                                               target:self
867                                             selector:@selector(blinkTimerFired:)
868                                             userInfo:nil repeats:NO] retain];
869         gui_update_cursor(TRUE, FALSE);
870         [self flushQueue:YES];
871     }
874 - (void)stopBlink
876     if (MMBlinkStateOff == blinkState) {
877         gui_update_cursor(TRUE, FALSE);
878         [self flushQueue:YES];
879     }
881     blinkState = MMBlinkStateNone;
884 - (void)adjustLinespace:(int)linespace
886     NSMutableData *data = [NSMutableData data];
887     [data appendBytes:&linespace length:sizeof(int)];
888     [self queueMessage:AdjustLinespaceMsgID data:data];
891 - (void)activate
893     [self queueMessage:ActivateMsgID data:nil];
896 - (void)setServerName:(NSString *)name
898     NSData *data = [name dataUsingEncoding:NSUTF8StringEncoding];
899     [self queueMessage:SetServerNameMsgID data:data];
902 - (int)lookupColorWithKey:(NSString *)key
904     if (!(key && [key length] > 0))
905         return INVALCOLOR;
907     NSString *stripKey = [[[[key lowercaseString]
908         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
909             componentsSeparatedByString:@" "]
910                componentsJoinedByString:@""];
912     if (stripKey && [stripKey length] > 0) {
913         // First of all try to lookup key in the color dictionary; note that
914         // all keys in this dictionary are lowercase with no whitespace.
915         id obj = [colorDict objectForKey:stripKey];
916         if (obj) return [obj intValue];
918         // The key was not in the dictionary; is it perhaps of the form
919         // #rrggbb?
920         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
921             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
922             [scanner setScanLocation:1];
923             unsigned hex = 0;
924             if ([scanner scanHexInt:&hex]) {
925                 return (int)hex;
926             }
927         }
929         // As a last resort, check if it is one of the system defined colors.
930         // The keys in this dictionary are also lowercase with no whitespace.
931         obj = [sysColorDict objectForKey:stripKey];
932         if (obj) {
933             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
934             if (col) {
935                 float r, g, b, a;
936                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
937                 [col getRed:&r green:&g blue:&b alpha:&a];
938                 return (((int)(r*255+.5f) & 0xff) << 16)
939                      + (((int)(g*255+.5f) & 0xff) << 8)
940                      +  ((int)(b*255+.5f) & 0xff);
941             }
942         }
943     }
945     NSLog(@"WARNING: No color with key %@ found.", stripKey);
946     return INVALCOLOR;
949 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
951     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
952     id obj;
954     while ((obj = [e nextObject])) {
955         if ([value isEqual:obj])
956             return YES;
957     }
959     return NO;
962 - (oneway void)processInput:(int)msgid data:(in NSData *)data
964     // NOTE: This method might get called whenever the run loop is tended to.
965     // Thus it might get called whilst input is being processed.  Normally this
966     // is not a problem, but if it gets called often then it might become
967     // dangerous.  E.g. when a focus messages is received the screen is redrawn
968     // because the selection color changes and if another focus message is
969     // received whilst the first one is being processed Vim might crash.  To
970     // deal with this problem at the moment, we simply drop messages that are
971     // received while other input is being processed.
972     if (inProcessInput) {
973 #if MM_USE_INPUT_QUEUE
974         [inputQueue addObject:[NSNumber numberWithInt:msgid]];
975         [inputQueue addObject:data];
976 #else
977         // Just drop the input
978         //NSLog(@"WARNING: Dropping input in %s", _cmd);
979 #endif
980     } else {
981         [self processInputBegin];
982         [self handleMessage:msgid data:data];
983         [self processInputEnd];
984     }
987 - (oneway void)processInputAndData:(in NSArray *)messages
989     // NOTE: See comment in processInput:data:.
990     unsigned i, count = [messages count];
991     if (count % 2) {
992         NSLog(@"WARNING: [messages count] is odd in %s", _cmd);
993         return;
994     }
996     if (inProcessInput) {
997 #if MM_USE_INPUT_QUEUE
998         [inputQueue addObjectsFromArray:messages];
999 #else
1000         // Just drop the input
1001         //NSLog(@"WARNING: Dropping input in %s", _cmd);
1002 #endif
1003     } else {
1004         [self processInputBegin];
1006         for (i = 0; i < count; i += 2) {
1007             int msgid = [[messages objectAtIndex:i] intValue];
1008             id data = [messages objectAtIndex:i+1];
1009             if ([data isEqual:[NSNull null]])
1010                 data = nil;
1012             [self handleMessage:msgid data:data];
1013         }
1015         [self processInputEnd];
1016     }
1019 - (BOOL)checkForModifiedBuffers
1021     buf_T *buf;
1022     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
1023         if (bufIsChanged(buf)) {
1024             return YES;
1025         }
1026     }
1028     return NO;
1031 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1033     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1034         // If there is no pasteboard, return YES to indicate that there is text
1035         // to copy.
1036         if (!pboard)
1037             return YES;
1039         clip_copy_selection();
1041         // Get the text to put on the pasteboard.
1042         long_u len = 0; char_u *str = 0;
1043         int type = clip_convert_selection(&str, &len, &clip_star);
1044         if (type < 0)
1045             return NO;
1046         
1047         NSString *string = [[NSString alloc]
1048             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1050         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1051         [pboard declareTypes:types owner:nil];
1052         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1053     
1054         [string release];
1055         vim_free(str);
1057         return ok;
1058     }
1060     return NO;
1063 - (oneway void)addReply:(in bycopy NSString *)reply
1064                  server:(in byref id <MMVimServerProtocol>)server
1066     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1068     // Replies might come at any time and in any order so we keep them in an
1069     // array inside a dictionary with the send port used as key.
1071     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1072     // HACK! Assume connection uses mach ports.
1073     int port = [(NSMachPort*)[conn sendPort] machPort];
1074     NSNumber *key = [NSNumber numberWithInt:port];
1076     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1077     if (!replies) {
1078         replies = [NSMutableArray array];
1079         [serverReplyDict setObject:replies forKey:key];
1080     }
1082     [replies addObject:reply];
1085 - (void)addInput:(in bycopy NSString *)input
1086                  client:(in byref id <MMVimClientProtocol>)client
1088     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1090     server_to_input_buf((char_u*)[input UTF8String]);
1092     [self addClient:(id)client];
1094     inputReceived = YES;
1097 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1098                  client:(in byref id <MMVimClientProtocol>)client
1100     //NSLog(@"evaluateExpression:%@ client:%@", expr, (id)client);
1102     NSString *eval = nil;
1103     char_u *res = eval_client_expr_to_string((char_u*)[expr UTF8String]);
1105     if (res != NULL) {
1106         eval = [NSString stringWithUTF8String:(char*)res];
1107         vim_free(res);
1108     }
1110     [self addClient:(id)client];
1112     return eval;
1115 - (void)registerServerWithName:(NSString *)name
1117     NSString *svrName = name;
1118     NSConnection *svrConn = [NSConnection defaultConnection];
1119     unsigned i;
1121     for (i = 0; i < MMServerMax; ++i) {
1122         NSString *connName = [self connectionNameFromServerName:svrName];
1124         if ([svrConn registerName:connName]) {
1125             //NSLog(@"Registered server with name: %@", svrName);
1127             // TODO: Set request/reply time-outs to something else?
1128             //
1129             // Don't wait for requests (time-out means that the message is
1130             // dropped).
1131             [svrConn setRequestTimeout:0];
1132             //[svrConn setReplyTimeout:MMReplyTimeout];
1133             [svrConn setRootObject:self];
1135             // NOTE: 'serverName' is a global variable
1136             serverName = vim_strsave((char_u*)[svrName UTF8String]);
1137 #ifdef FEAT_EVAL
1138             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1139 #endif
1140 #ifdef FEAT_TITLE
1141             need_maketitle = TRUE;
1142 #endif
1143             [self queueMessage:SetServerNameMsgID data:
1144                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1145             break;
1146         }
1148         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1149     }
1152 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1153                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1154               silent:(BOOL)silent
1156     // NOTE: If 'name' equals 'serverName' then the request is local (client
1157     // and server are the same).  This case is not handled separately, so a
1158     // connection will be set up anyway (this simplifies the code).
1160     NSConnection *conn = [self connectionForServerName:name];
1161     if (!conn) {
1162         if (!silent)
1163             EMSG2(_(e_noserver), [name UTF8String]);
1164         return NO;
1165     }
1167     if (port) {
1168         // HACK! Assume connection uses mach ports.
1169         *port = [(NSMachPort*)[conn sendPort] machPort];
1170     }
1172     id proxy = [conn rootProxy];
1173     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1175     @try {
1176         if (expr) {
1177             NSString *eval = [proxy evaluateExpression:string client:self];
1178             if (reply) {
1179                 *reply = (eval ? vim_strsave((char_u*)[eval UTF8String])
1180                                : vim_strsave((char_u*)_(e_invexprmsg)));
1181             }
1183             if (!eval)
1184                 return NO;
1185         } else {
1186             [proxy addInput:string client:self];
1187         }
1188     }
1189     @catch (NSException *e) {
1190         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1191         return NO;
1192     }
1194     return YES;
1197 - (NSArray *)serverList
1199     NSArray *list = nil;
1201     if ([self connection]) {
1202         id proxy = [connection rootProxy];
1203         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1205         @try {
1206             list = [proxy serverList];
1207         }
1208         @catch (NSException *e) {
1209             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1210         }
1211     } else {
1212         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1213     }
1215     return list;
1218 - (NSString *)peekForReplyOnPort:(int)port
1220     //NSLog(@"%s%d", _cmd, port);
1222     NSNumber *key = [NSNumber numberWithInt:port];
1223     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1224     if (replies && [replies count]) {
1225         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1226         //        [replies objectAtIndex:0]);
1227         return [replies objectAtIndex:0];
1228     }
1230     //NSLog(@"    No replies");
1231     return nil;
1234 - (NSString *)waitForReplyOnPort:(int)port
1236     //NSLog(@"%s%d", _cmd, port);
1237     
1238     NSConnection *conn = [self connectionForServerPort:port];
1239     if (!conn)
1240         return nil;
1242     NSNumber *key = [NSNumber numberWithInt:port];
1243     NSMutableArray *replies = nil;
1244     NSString *reply = nil;
1246     // Wait for reply as long as the connection to the server is valid (unless
1247     // user interrupts wait with Ctrl-C).
1248     while (!got_int && [conn isValid] &&
1249             !(replies = [serverReplyDict objectForKey:key])) {
1250         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1251                                  beforeDate:[NSDate distantFuture]];
1252     }
1254     if (replies) {
1255         if ([replies count] > 0) {
1256             reply = [[replies objectAtIndex:0] retain];
1257             //NSLog(@"    Got reply: %@", reply);
1258             [replies removeObjectAtIndex:0];
1259             [reply autorelease];
1260         }
1262         if ([replies count] == 0)
1263             [serverReplyDict removeObjectForKey:key];
1264     }
1266     return reply;
1269 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1271     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1272     if (client) {
1273         @try {
1274             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1275             [client addReply:reply server:self];
1276             return YES;
1277         }
1278         @catch (NSException *e) {
1279             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1280         }
1281     } else {
1282         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1283     }
1285     return NO;
1288 @end // MMBackend
1292 @implementation MMBackend (Private)
1294 - (void)handleMessage:(int)msgid data:(NSData *)data
1296     if (InsertTextMsgID == msgid) {
1297         if (!data) return;
1298         NSString *key = [[NSString alloc] initWithData:data
1299                                               encoding:NSUTF8StringEncoding];
1300         char_u *str = (char_u*)[key UTF8String];
1301         int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1303 #if MM_ENABLE_CONV
1304         char_u *conv_str = NULL;
1305         if (input_conv.vc_type != CONV_NONE) {
1306             conv_str = string_convert(&input_conv, str, &len);
1307             if (conv_str)
1308                 str = conv_str;
1309         }
1310 #endif
1312         for (i = 0; i < len; ++i) {
1313             add_to_input_buf(str+i, 1);
1314             if (CSI == str[i]) {
1315                 // NOTE: If the converted string contains the byte CSI, then it
1316                 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1317                 // won't work.
1318                 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1319                 add_to_input_buf(extra, 2);
1320             }
1321         }
1323 #if MM_ENABLE_CONV
1324         if (conv_str)
1325             vim_free(conv_str);
1326 #endif
1327         [key release];
1328     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1329         if (!data) return;
1330         const void *bytes = [data bytes];
1331         int mods = *((int*)bytes);  bytes += sizeof(int);
1332         int len = *((int*)bytes);  bytes += sizeof(int);
1333         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1334                                               encoding:NSUTF8StringEncoding];
1335         mods = eventModifierFlagsToVimModMask(mods);
1337         [self handleKeyDown:key modifiers:mods];
1339         [key release];
1340     } else if (SelectTabMsgID == msgid) {
1341         if (!data) return;
1342         const void *bytes = [data bytes];
1343         int idx = *((int*)bytes) + 1;
1344         //NSLog(@"Selecting tab %d", idx);
1345         send_tabline_event(idx);
1346     } else if (CloseTabMsgID == msgid) {
1347         if (!data) return;
1348         const void *bytes = [data bytes];
1349         int idx = *((int*)bytes) + 1;
1350         //NSLog(@"Closing tab %d", idx);
1351         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1352     } else if (AddNewTabMsgID == msgid) {
1353         //NSLog(@"Adding new tab");
1354         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1355     } else if (DraggedTabMsgID == msgid) {
1356         if (!data) return;
1357         const void *bytes = [data bytes];
1358         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1359         // based.
1360         int idx = *((int*)bytes);
1362         tabpage_move(idx);
1363     } else if (ScrollWheelMsgID == msgid) {
1364         if (!data) return;
1365         const void *bytes = [data bytes];
1367         int row = *((int*)bytes);  bytes += sizeof(int);
1368         int col = *((int*)bytes);  bytes += sizeof(int);
1369         int flags = *((int*)bytes);  bytes += sizeof(int);
1370         float dy = *((float*)bytes);  bytes += sizeof(float);
1372         int button = MOUSE_5;
1373         if (dy > 0) button = MOUSE_4;
1375         flags = eventModifierFlagsToVimMouseModMask(flags);
1377         gui_send_mouse_event(button, col, row, NO, flags);
1378     } else if (MouseDownMsgID == msgid) {
1379         if (!data) return;
1380         const void *bytes = [data bytes];
1382         int row = *((int*)bytes);  bytes += sizeof(int);
1383         int col = *((int*)bytes);  bytes += sizeof(int);
1384         int button = *((int*)bytes);  bytes += sizeof(int);
1385         int flags = *((int*)bytes);  bytes += sizeof(int);
1386         int count = *((int*)bytes);  bytes += sizeof(int);
1388         button = eventButtonNumberToVimMouseButton(button);
1389         flags = eventModifierFlagsToVimMouseModMask(flags);
1391         gui_send_mouse_event(button, col, row, count>1, flags);
1392     } else if (MouseUpMsgID == msgid) {
1393         if (!data) return;
1394         const void *bytes = [data bytes];
1396         int row = *((int*)bytes);  bytes += sizeof(int);
1397         int col = *((int*)bytes);  bytes += sizeof(int);
1398         int flags = *((int*)bytes);  bytes += sizeof(int);
1400         flags = eventModifierFlagsToVimMouseModMask(flags);
1402         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1403     } else if (MouseDraggedMsgID == msgid) {
1404         if (!data) return;
1405         const void *bytes = [data bytes];
1407         int row = *((int*)bytes);  bytes += sizeof(int);
1408         int col = *((int*)bytes);  bytes += sizeof(int);
1409         int flags = *((int*)bytes);  bytes += sizeof(int);
1411         flags = eventModifierFlagsToVimMouseModMask(flags);
1413         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1414     } else if (SetTextDimensionsMsgID == msgid) {
1415         if (!data) return;
1416         const void *bytes = [data bytes];
1417         int rows = *((int*)bytes);  bytes += sizeof(int);
1418         int cols = *((int*)bytes);  bytes += sizeof(int);
1420         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1421         // gui_resize_shell(), so we have to manually set the rows and columns
1422         // here.  (MacVim doesn't change the rows and columns to avoid
1423         // inconsistent states between Vim and MacVim.)
1424         [self setRows:rows columns:cols];
1426         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1427         gui_resize_shell(cols, rows);
1428     } else if (ExecuteMenuMsgID == msgid) {
1429         if (!data) return;
1430         const void *bytes = [data bytes];
1431         int tag = *((int*)bytes);  bytes += sizeof(int);
1433         vimmenu_T *menu = (vimmenu_T*)tag;
1434         // TODO!  Make sure 'menu' is a valid menu pointer!
1435         if (menu) {
1436             gui_menu_cb(menu);
1437         }
1438     } else if (ToggleToolbarMsgID == msgid) {
1439         char_u go[sizeof(GO_ALL)+2];
1440         char_u *p;
1441         int len;
1443         STRCPY(go, p_go);
1444         p = vim_strchr(go, GO_TOOLBAR);
1445         len = STRLEN(go);
1447         if (p != NULL) {
1448             char_u *end = go + len;
1449             while (p < end) {
1450                 p[0] = p[1];
1451                 ++p;
1452             }
1453         } else {
1454             go[len] = GO_TOOLBAR;
1455             go[len+1] = NUL;
1456         }
1458         set_option_value((char_u*)"guioptions", 0, go, 0);
1460         // Force screen redraw (does it have to be this complicated?).
1461         redraw_all_later(CLEAR);
1462         update_screen(NOT_VALID);
1463         setcursor();
1464         out_flush();
1465         gui_update_cursor(FALSE, FALSE);
1466         gui_mch_flush();
1467     } else if (ScrollbarEventMsgID == msgid) {
1468         if (!data) return;
1469         const void *bytes = [data bytes];
1470         long ident = *((long*)bytes);  bytes += sizeof(long);
1471         int hitPart = *((int*)bytes);  bytes += sizeof(int);
1472         float fval = *((float*)bytes);  bytes += sizeof(float);
1473         scrollbar_T *sb = gui_find_scrollbar(ident);
1475         if (sb) {
1476             scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1477             long value = sb_info->value;
1478             long size = sb_info->size;
1479             long max = sb_info->max;
1480             BOOL isStillDragging = NO;
1481             BOOL updateKnob = YES;
1483             switch (hitPart) {
1484             case NSScrollerDecrementPage:
1485                 value -= (size > 2 ? size - 2 : 1);
1486                 break;
1487             case NSScrollerIncrementPage:
1488                 value += (size > 2 ? size - 2 : 1);
1489                 break;
1490             case NSScrollerDecrementLine:
1491                 --value;
1492                 break;
1493             case NSScrollerIncrementLine:
1494                 ++value;
1495                 break;
1496             case NSScrollerKnob:
1497                 isStillDragging = YES;
1498                 // fall through ...
1499             case NSScrollerKnobSlot:
1500                 value = (long)(fval * (max - size + 1));
1501                 // fall through ...
1502             default:
1503                 updateKnob = NO;
1504                 break;
1505             }
1507             //NSLog(@"value %d -> %d", sb_info->value, value);
1508             gui_drag_scrollbar(sb, value, isStillDragging);
1510             if (updateKnob) {
1511                 // Dragging the knob or option+clicking automatically updates
1512                 // the knob position (on the actual NSScroller), so we only
1513                 // need to set the knob position in the other cases.
1514                 if (sb->wp) {
1515                     // Update both the left&right vertical scrollbars.
1516                     long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1517                     long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1518                     [self setScrollbarThumbValue:value size:size max:max
1519                                       identifier:identLeft];
1520                     [self setScrollbarThumbValue:value size:size max:max
1521                                       identifier:identRight];
1522                 } else {
1523                     // Update the horizontal scrollbar.
1524                     [self setScrollbarThumbValue:value size:size max:max
1525                                       identifier:ident];
1526                 }
1527             }
1528         }
1529     } else if (SetFontMsgID == msgid) {
1530         if (!data) return;
1531         const void *bytes = [data bytes];
1532         float pointSize = *((float*)bytes);  bytes += sizeof(float);
1533         //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1534         bytes += sizeof(unsigned);  // len not used
1536         NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1537         [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1539         set_option_value((char_u*)"gfn", 0, (char_u*)[name UTF8String], 0);
1541         // Force screen redraw (does it have to be this complicated?).
1542         redraw_all_later(CLEAR);
1543         update_screen(NOT_VALID);
1544         setcursor();
1545         out_flush();
1546         gui_update_cursor(FALSE, FALSE);
1547         gui_mch_flush();
1548     } else if (VimShouldCloseMsgID == msgid) {
1549         gui_shell_closed();
1550     } else if (DropFilesMsgID == msgid) {
1551 #ifdef FEAT_DND
1552         const void *bytes = [data bytes];
1553         const void *end = [data bytes] + [data length];
1554         int n = *((int*)bytes);  bytes += sizeof(int);
1556         if (State & CMDLINE) {
1557             // HACK!  If Vim is in command line mode then the files names
1558             // should be added to the command line, instead of opening the
1559             // files in tabs.  This is taken care of by gui_handle_drop().
1560             char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1561             if (fnames) {
1562                 int i = 0;
1563                 while (bytes < end && i < n) {
1564                     int len = *((int*)bytes);  bytes += sizeof(int);
1565                     fnames[i++] = vim_strnsave((char_u*)bytes, len);
1566                     bytes += len;
1567                 }
1569                 // NOTE!  This function will free 'fnames'.
1570                 // HACK!  It is assumed that the 'x' and 'y' arguments are
1571                 // unused when in command line mode.
1572                 gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
1573             }
1574         } else {
1575             // HACK!  I'm not sure how to get Vim to open a list of files in
1576             // tabs, so instead I create a ':tab drop' command with all the
1577             // files to open and execute it.
1578             NSMutableString *cmd = (n > 1)
1579                     ? [NSMutableString stringWithString:@":tab drop"]
1580                     : [NSMutableString stringWithString:@":drop"];
1582             int i;
1583             for (i = 0; i < n && bytes < end; ++i) {
1584                 int len = *((int*)bytes);  bytes += sizeof(int);
1585                 NSMutableString *file =
1586                         [NSMutableString stringWithUTF8String:bytes];
1587                 [file replaceOccurrencesOfString:@" "
1588                                       withString:@"\\ "
1589                                          options:0
1590                                            range:NSMakeRange(0,[file length])];
1591                 bytes += len;
1593                 [cmd appendString:@" "];
1594                 [cmd appendString:file];
1595             }
1597             // By going to the last tabpage we ensure that the new tabs will
1598             // appear last (if this call is left out, the taborder becomes
1599             // messy).
1600             goto_tabpage(9999);
1602             do_cmdline_cmd((char_u*)[cmd UTF8String]);
1604             // Force screen redraw (does it have to be this complicated?).
1605             // (This code was taken from the end of gui_handle_drop().)
1606             update_screen(NOT_VALID);
1607             setcursor();
1608             out_flush();
1609             gui_update_cursor(FALSE, FALSE);
1610             gui_mch_flush();
1611         }
1612 #endif // FEAT_DND
1613     } else if (DropStringMsgID == msgid) {
1614 #ifdef FEAT_DND
1615         char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
1616         const void *bytes = [data bytes];
1617         int len = *((int*)bytes);  bytes += sizeof(int);
1618         NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
1620         // Replace unrecognized end-of-line sequences with \x0a (line feed).
1621         NSRange range = { 0, [string length] };
1622         unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
1623                                              withString:@"\x0a" options:0
1624                                                   range:range];
1625         if (0 == n) {
1626             n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
1627                                            options:0 range:range];
1628         }
1630         len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1631         dnd_yank_drag_data((char_u*)[string UTF8String], len);
1632         add_to_input_buf(dropkey, sizeof(dropkey));
1633 #endif // FEAT_DND
1634     } else if (GotFocusMsgID == msgid) {
1635         if (!gui.in_focus)
1636             [self focusChange:YES];
1637     } else if (LostFocusMsgID == msgid) {
1638         if (gui.in_focus)
1639             [self focusChange:NO];
1640     } else if (MouseMovedMsgID == msgid) {
1641         const void *bytes = [data bytes];
1642         int row = *((int*)bytes);  bytes += sizeof(int);
1643         int col = *((int*)bytes);  bytes += sizeof(int);
1645         gui_mouse_moved(col, row);
1646     } else if (SetMouseShapeMsgID == msgid) {
1647         const void *bytes = [data bytes];
1648         int shape = *((int*)bytes);  bytes += sizeof(int);
1649         update_mouseshape(shape);
1650     } else {
1651         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1652     }
1655 + (NSDictionary *)specialKeys
1657     static NSDictionary *specialKeys = nil;
1659     if (!specialKeys) {
1660         NSBundle *mainBundle = [NSBundle mainBundle];
1661         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1662                                               ofType:@"plist"];
1663         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1664     }
1666     return specialKeys;
1669 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1671     char_u special[3];
1672     char_u modChars[3];
1673     char_u *chars = (char_u*)[key UTF8String];
1674     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1676     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1677     // that new keys can easily be added.
1678     NSString *specialString = [[MMBackend specialKeys]
1679             objectForKey:key];
1680     if (specialString && [specialString length] > 1) {
1681         //NSLog(@"special key: %@", specialString);
1682         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1683                 [specialString characterAtIndex:1]);
1685         ikey = simplify_key(ikey, &mods);
1686         if (ikey == CSI)
1687             ikey = K_CSI;
1689         special[0] = CSI;
1690         special[1] = K_SECOND(ikey);
1691         special[2] = K_THIRD(ikey);
1693         chars = special;
1694         length = 3;
1695     } else if (1 == length && TAB == chars[0]) {
1696         // Tab is a trouble child:
1697         // - <Tab> is added to the input buffer as is
1698         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1699         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1700         //   to be converted to utf-8
1701         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1702         // - <C-Tab> is reserved by Mac OS X
1703         // - <D-Tab> is reserved by Mac OS X
1704         chars = special;
1705         special[0] = TAB;
1706         length = 1;
1708         if (mods & MOD_MASK_SHIFT) {
1709             mods &= ~MOD_MASK_SHIFT;
1710             special[0] = CSI;
1711             special[1] = K_SECOND(K_S_TAB);
1712             special[2] = K_THIRD(K_S_TAB);
1713             length = 3;
1714         } else if (mods & MOD_MASK_ALT) {
1715             int mtab = 0x80 | TAB;
1716             // Convert to utf-8
1717             special[0] = (mtab >> 6) + 0xc0;
1718             special[1] = mtab & 0xbf;
1719             length = 2;
1720             mods &= ~MOD_MASK_ALT;
1721         }
1722     } else if (length > 0) {
1723         unichar c = [key characterAtIndex:0];
1725         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1726         //        [key characterAtIndex:0], mods);
1728         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1729                 || (c == intr_char && intr_char != Ctrl_C))) {
1730             trash_input_buf();
1731             got_int = TRUE;
1732         }
1734         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1735         // cleared since they are already added to the key by the AppKit.
1736         // Unfortunately, the only way to deal with when to clear the modifiers
1737         // or not seems to be to have hard-wired rules like this.
1738         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1739                     || 0x9 == c) ) {
1740             mods &= ~MOD_MASK_SHIFT;
1741             mods &= ~MOD_MASK_CTRL;
1742             //NSLog(@"clear shift ctrl");
1743         }
1745         // HACK!  All Option+key presses go via 'insert text' messages, except
1746         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1747         // not work to map to it.
1748         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1749             //NSLog(@"clear alt");
1750             mods &= ~MOD_MASK_ALT;
1751         }
1752     }
1754     if (chars && length > 0) {
1755         if (mods) {
1756             //NSLog(@"adding mods: %d", mods);
1757             modChars[0] = CSI;
1758             modChars[1] = KS_MODIFIER;
1759             modChars[2] = mods;
1760             add_to_input_buf(modChars, 3);
1761         }
1763         //NSLog(@"add to input buf: 0x%x", chars[0]);
1764         // TODO: Check for CSI bytes?
1765         add_to_input_buf(chars, length);
1766     }
1769 - (void)queueMessage:(int)msgid data:(NSData *)data
1771     [queue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1772     if (data)
1773         [queue addObject:data];
1774     else
1775         [queue addObject:[NSData data]];
1778 - (void)connectionDidDie:(NSNotification *)notification
1780     // If the main connection to MacVim is lost this means that MacVim was
1781     // either quit (by the user chosing Quit on the MacVim menu), or it has
1782     // crashed.  In either case our only option is to quit now.
1783     // TODO: Write backup file?
1785     //NSLog(@"A Vim process lots its connection to MacVim; quitting.");
1786     getout(0);
1789 - (void)blinkTimerFired:(NSTimer *)timer
1791     NSTimeInterval timeInterval = 0;
1793     [blinkTimer release];
1794     blinkTimer = nil;
1796     if (MMBlinkStateOn == blinkState) {
1797         gui_undraw_cursor();
1798         blinkState = MMBlinkStateOff;
1799         timeInterval = blinkOffInterval;
1800     } else if (MMBlinkStateOff == blinkState) {
1801         gui_update_cursor(TRUE, FALSE);
1802         blinkState = MMBlinkStateOn;
1803         timeInterval = blinkOnInterval;
1804     }
1806     if (timeInterval > 0) {
1807         blinkTimer = 
1808             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1809                                             selector:@selector(blinkTimerFired:)
1810                                             userInfo:nil repeats:NO] retain];
1811         [self flushQueue:YES];
1812     }
1815 - (void)focusChange:(BOOL)on
1817     gui_focus_change(on);
1820 - (void)processInputBegin
1822     inProcessInput = YES;
1823     [lastFlushDate release];
1824     lastFlushDate = [[NSDate date] retain];
1827 - (void)processInputEnd
1829 #if MM_USE_INPUT_QUEUE
1830     int count = [inputQueue count];
1831     if (count % 2) {
1832         // TODO: This is troubling, but it is not hard to get Vim to end up
1833         // here.  Why does this happen?
1834         NSLog(@"WARNING: inputQueue has odd number of objects (%d)", count);
1835         [inputQueue removeAllObjects];
1836     } else if (count > 0) {
1837         // TODO: Dispatch these messages?  Maybe not; usually when the
1838         // 'inputQueue' is non-empty it means that a LOT of messages has been
1839         // sent simultaneously.  The only way this happens is when Vim is being
1840         // tormented, e.g. if the user holds down <D-`> to rapidly switch
1841         // windows.
1842         unsigned i;
1843         for (i = 0; i < count; i+=2) {
1844             int msgid = [[inputQueue objectAtIndex:i] intValue];
1845             NSLog(@"%s: Dropping message %s", _cmd, MessageStrings[msgid]);
1846         }
1848         [inputQueue removeAllObjects];
1849     }
1850 #endif
1852     inputReceived = YES;
1853     inProcessInput = NO;
1856 - (NSString *)connectionNameFromServerName:(NSString *)name
1858     NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
1860     return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
1861         lowercaseString];
1864 - (NSConnection *)connectionForServerName:(NSString *)name
1866     // TODO: Try 'name%d' if 'name' fails.
1867     NSString *connName = [self connectionNameFromServerName:name];
1868     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
1870     if (!svrConn) {
1871         svrConn = [NSConnection connectionWithRegisteredName:connName
1872                                                            host:nil];
1873         // Try alternate server...
1874         if (!svrConn && alternateServerName) {
1875             //NSLog(@"  trying to connect to alternate server: %@",
1876             //        alternateServerName);
1877             connName = [self connectionNameFromServerName:alternateServerName];
1878             svrConn = [NSConnection connectionWithRegisteredName:connName
1879                                                             host:nil];
1880         }
1882         // Try looking for alternate servers...
1883         if (!svrConn) {
1884             //NSLog(@"  looking for alternate servers...");
1885             NSString *alt = [self alternateServerNameForName:name];
1886             if (alt != alternateServerName) {
1887                 //NSLog(@"  found alternate server: %@", string);
1888                 [alternateServerName release];
1889                 alternateServerName = [alt copy];
1890             }
1891         }
1893         // Try alternate server again...
1894         if (!svrConn && alternateServerName) {
1895             //NSLog(@"  trying to connect to alternate server: %@",
1896             //        alternateServerName);
1897             connName = [self connectionNameFromServerName:alternateServerName];
1898             svrConn = [NSConnection connectionWithRegisteredName:connName
1899                                                             host:nil];
1900         }
1902         if (svrConn) {
1903             [connectionNameDict setObject:svrConn forKey:connName];
1905             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
1906             [[NSNotificationCenter defaultCenter] addObserver:self
1907                     selector:@selector(serverConnectionDidDie:)
1908                         name:NSConnectionDidDieNotification object:svrConn];
1909         }
1910     }
1912     return svrConn;
1915 - (NSConnection *)connectionForServerPort:(int)port
1917     NSConnection *conn;
1918     NSEnumerator *e = [connectionNameDict objectEnumerator];
1920     while ((conn = [e nextObject])) {
1921         // HACK! Assume connection uses mach ports.
1922         if (port == [(NSMachPort*)[conn sendPort] machPort])
1923             return conn;
1924     }
1926     return nil;
1929 - (void)serverConnectionDidDie:(NSNotification *)notification
1931     //NSLog(@"%s%@", _cmd, notification);
1933     NSConnection *svrConn = [notification object];
1935     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
1936     [[NSNotificationCenter defaultCenter]
1937             removeObserver:self
1938                       name:NSConnectionDidDieNotification
1939                     object:svrConn];
1941     [connectionNameDict removeObjectsForKeys:
1942         [connectionNameDict allKeysForObject:svrConn]];
1944     // HACK! Assume connection uses mach ports.
1945     int port = [(NSMachPort*)[svrConn sendPort] machPort];
1946     NSNumber *key = [NSNumber numberWithInt:port];
1948     [clientProxyDict removeObjectForKey:key];
1949     [serverReplyDict removeObjectForKey:key];
1952 - (void)addClient:(NSDistantObject *)client
1954     NSConnection *conn = [client connectionForProxy];
1955     // HACK! Assume connection uses mach ports.
1956     int port = [(NSMachPort*)[conn sendPort] machPort];
1957     NSNumber *key = [NSNumber numberWithInt:port];
1959     if (![clientProxyDict objectForKey:key]) {
1960         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
1961         [clientProxyDict setObject:client forKey:key];
1962     }
1964     // NOTE: 'clientWindow' is a global variable which is used by <client>
1965     clientWindow = port;
1968 - (NSString *)alternateServerNameForName:(NSString *)name
1970     if (!(name && [name length] > 0))
1971         return nil;
1973     // Only look for alternates if 'name' doesn't end in a digit.
1974     unichar lastChar = [name characterAtIndex:[name length]-1];
1975     if (lastChar >= '0' && lastChar <= '9')
1976         return nil;
1978     // Look for alternates among all current servers.
1979     NSArray *list = [self serverList];
1980     if (!(list && [list count] > 0))
1981         return nil;
1983     // Filter out servers starting with 'name' and ending with a number. The
1984     // (?i) pattern ensures that the match is case insensitive.
1985     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
1986     NSPredicate *pred = [NSPredicate predicateWithFormat:
1987             @"SELF MATCHES %@", pat];
1988     list = [list filteredArrayUsingPredicate:pred];
1989     if ([list count] > 0) {
1990         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
1991         return [list objectAtIndex:0];
1992     }
1994     return nil;
1997 @end // MMBackend (Private)
2002 @implementation NSString (MMServerNameCompare)
2003 - (NSComparisonResult)serverNameCompare:(NSString *)string
2005     return [self compare:string
2006                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2008 @end
2013 static int eventModifierFlagsToVimModMask(int modifierFlags)
2015     int modMask = 0;
2017     if (modifierFlags & NSShiftKeyMask)
2018         modMask |= MOD_MASK_SHIFT;
2019     if (modifierFlags & NSControlKeyMask)
2020         modMask |= MOD_MASK_CTRL;
2021     if (modifierFlags & NSAlternateKeyMask)
2022         modMask |= MOD_MASK_ALT;
2023     if (modifierFlags & NSCommandKeyMask)
2024         modMask |= MOD_MASK_CMD;
2026     return modMask;
2029 static int vimModMaskToEventModifierFlags(int mods)
2031     int flags = 0;
2033     if (mods & MOD_MASK_SHIFT)
2034         flags |= NSShiftKeyMask;
2035     if (mods & MOD_MASK_CTRL)
2036         flags |= NSControlKeyMask;
2037     if (mods & MOD_MASK_ALT)
2038         flags |= NSAlternateKeyMask;
2039     if (mods & MOD_MASK_CMD)
2040         flags |= NSCommandKeyMask;
2042     return flags;
2045 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2047     int modMask = 0;
2049     if (modifierFlags & NSShiftKeyMask)
2050         modMask |= MOUSE_SHIFT;
2051     if (modifierFlags & NSControlKeyMask)
2052         modMask |= MOUSE_CTRL;
2053     if (modifierFlags & NSAlternateKeyMask)
2054         modMask |= MOUSE_ALT;
2056     return modMask;
2059 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2061     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE,
2062             MOUSE_X1, MOUSE_X2 };
2064     return mouseButton[buttonNumber < 5 ? buttonNumber : 0];
2067 static int specialKeyToNSKey(int key)
2069     if (!IS_SPECIAL(key))
2070         return key;
2072     static struct {
2073         int special;
2074         int nskey;
2075     } sp2ns[] = {
2076         { K_UP, NSUpArrowFunctionKey },
2077         { K_DOWN, NSDownArrowFunctionKey },
2078         { K_LEFT, NSLeftArrowFunctionKey },
2079         { K_RIGHT, NSRightArrowFunctionKey },
2080         { K_F1, NSF1FunctionKey },
2081         { K_F2, NSF2FunctionKey },
2082         { K_F3, NSF3FunctionKey },
2083         { K_F4, NSF4FunctionKey },
2084         { K_F5, NSF5FunctionKey },
2085         { K_F6, NSF6FunctionKey },
2086         { K_F7, NSF7FunctionKey },
2087         { K_F8, NSF8FunctionKey },
2088         { K_F9, NSF9FunctionKey },
2089         { K_F10, NSF10FunctionKey },
2090         { K_F11, NSF11FunctionKey },
2091         { K_F12, NSF12FunctionKey },
2092         { K_F13, NSF13FunctionKey },
2093         { K_F14, NSF14FunctionKey },
2094         { K_F15, NSF15FunctionKey },
2095         { K_F16, NSF16FunctionKey },
2096         { K_F17, NSF17FunctionKey },
2097         { K_F18, NSF18FunctionKey },
2098         { K_F19, NSF19FunctionKey },
2099         { K_F20, NSF20FunctionKey },
2100         { K_F21, NSF21FunctionKey },
2101         { K_F22, NSF22FunctionKey },
2102         { K_F23, NSF23FunctionKey },
2103         { K_F24, NSF24FunctionKey },
2104         { K_F25, NSF25FunctionKey },
2105         { K_F26, NSF26FunctionKey },
2106         { K_F27, NSF27FunctionKey },
2107         { K_F28, NSF28FunctionKey },
2108         { K_F29, NSF29FunctionKey },
2109         { K_F30, NSF30FunctionKey },
2110         { K_F31, NSF31FunctionKey },
2111         { K_F32, NSF32FunctionKey },
2112         { K_F33, NSF33FunctionKey },
2113         { K_F34, NSF34FunctionKey },
2114         { K_F35, NSF35FunctionKey },
2115         { K_DEL, NSBackspaceCharacter },
2116         { K_BS, NSDeleteCharacter },
2117         { K_HOME, NSHomeFunctionKey },
2118         { K_END, NSEndFunctionKey },
2119         { K_PAGEUP, NSPageUpFunctionKey },
2120         { K_PAGEDOWN, NSPageDownFunctionKey }
2121     };
2123     int i;
2124     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2125         if (sp2ns[i].special == key)
2126             return sp2ns[i].nskey;
2127     }
2129     return 0;