:set gfn=* does not set p_guifont to "*" (fix mostly in Vim code)
[MacVim/jjgod.git] / MMBackend.m
blob4a77f15bb2ad7441cb7c5e9c2fdab4cfa2d079aa
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;
24 // NOTE: The default font is bundled with the application.
25 static NSString *MMDefaultFontName = @"DejaVu Sans Mono";
26 static float MMDefaultFontSize = 12.0f;
28 // TODO: Move to separate file.
29 static int eventModifierFlagsToVimModMask(int modifierFlags);
30 static int vimModMaskToEventModifierFlags(int mods);
31 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
32 static int eventButtonNumberToVimMouseButton(int buttonNumber);
33 static int specialKeyToNSKey(int key);
35 enum {
36     MMBlinkStateNone = 0,
37     MMBlinkStateOn,
38     MMBlinkStateOff
43 @interface NSString (MMServerNameCompare)
44 - (NSComparisonResult)serverNameCompare:(NSString *)string;
45 @end
49 @interface MMBackend (Private)
50 - (void)handleMessage:(int)msgid data:(NSData *)data;
51 + (NSDictionary *)specialKeys;
52 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
53 - (void)queueMessage:(int)msgid data:(NSData *)data;
54 - (void)connectionDidDie:(NSNotification *)notification;
55 - (void)blinkTimerFired:(NSTimer *)timer;
56 - (void)focusChange:(BOOL)on;
57 - (void)processInputBegin;
58 - (void)processInputEnd;
59 - (NSString *)connectionNameFromServerName:(NSString *)name;
60 - (NSConnection *)connectionForServerName:(NSString *)name;
61 - (NSConnection *)connectionForServerPort:(int)port;
62 - (void)serverConnectionDidDie:(NSNotification *)notification;
63 - (void)addClient:(NSDistantObject *)client;
64 - (NSString *)alternateServerNameForName:(NSString *)name;
65 @end
69 @implementation MMBackend
71 + (MMBackend *)sharedInstance
73     static MMBackend *singleton = nil;
74     return singleton ? singleton : (singleton = [MMBackend new]);
77 - (id)init
79     if ((self = [super init])) {
80         fontContainerRef = loadFonts();
82         queue = [[NSMutableArray alloc] init];
83 #if MM_USE_INPUT_QUEUE
84         inputQueue = [[NSMutableArray alloc] init];
85 #endif
86         drawData = [[NSMutableData alloc] initWithCapacity:1024];
87         connectionNameDict = [[NSMutableDictionary alloc] init];
88         clientProxyDict = [[NSMutableDictionary alloc] init];
89         serverReplyDict = [[NSMutableDictionary alloc] init];
91         NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
92                                                          ofType:@"plist"];
93         if (path) {
94             colorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
95                 retain];
96         } else {
97             NSLog(@"WARNING: Could not locate Colors.plist.");
98         }
100         path = [[NSBundle mainBundle] pathForResource:@"SystemColors"
101                                                ofType:@"plist"];
102         if (path) {
103             sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
104                 retain];
105         } else {
106             NSLog(@"WARNING: Could not locate SystemColors.plist.");
107         }
108     }
110     return self;
113 - (void)dealloc
115     //NSLog(@"%@ %s", [self className], _cmd);
117     [[NSNotificationCenter defaultCenter] removeObserver:self];
119     [blinkTimer release];  blinkTimer = nil;
120 #if MM_USE_INPUT_QUEUE
121     [inputQueue release];  inputQueue = nil;
122 #endif
123     [alternateServerName release];  alternateServerName = nil;
124     [serverReplyDict release];  serverReplyDict = nil;
125     [clientProxyDict release];  clientProxyDict = nil;
126     [connectionNameDict release];  connectionNameDict = nil;
127     [queue release];  queue = nil;
128     [drawData release];  drawData = nil;
129     [frontendProxy release];  frontendProxy = nil;
130     [connection release];  connection = nil;
131     [sysColorDict release];  sysColorDict = nil;
132     [colorDict release];  colorDict = nil;
134     [super dealloc];
137 - (void)setBackgroundColor:(int)color
139     backgroundColor = color;
142 - (void)setForegroundColor:(int)color
144     foregroundColor = color;
147 - (void)setSpecialColor:(int)color
149     specialColor = color;
152 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
154     defaultBackgroundColor = bg;
155     defaultForegroundColor = fg;
157     NSMutableData *data = [NSMutableData data];
159     [data appendBytes:&bg length:sizeof(int)];
160     [data appendBytes:&fg length:sizeof(int)];
162     [self queueMessage:SetDefaultColorsMsgID data:data];
165 - (NSConnection *)connection
167     if (!connection) {
168         // NOTE!  If the name of the connection changes here it must also be
169         // updated in MMAppController.m.
170         NSString *name = [NSString stringWithFormat:@"%@-connection",
171                [[NSBundle mainBundle] bundleIdentifier]];
173         connection = [NSConnection connectionWithRegisteredName:name host:nil];
174         [connection retain];
175     }
177     // NOTE: 'connection' may be nil here.
178     return connection;
181 - (BOOL)checkin
183     if (![self connection]) {
184         NSBundle *mainBundle = [NSBundle mainBundle];
185 #if 0
186         NSString *path = [mainBundle bundlePath];
187         if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
188             NSLog(@"WARNING: Failed to launch GUI with path %@", path);
189             return NO;
190         }
191 #else
192         // HACK!  It would be preferable to launch the GUI using NSWorkspace,
193         // however I have not managed to figure out how to pass arguments using
194         // NSWorkspace.
195         //
196         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
197         // that the GUI won't be activated (or raised) so there is a hack in
198         // MMWindowController which always raises the app when a new window is
199         // opened.
200         NSMutableArray *args = [NSMutableArray arrayWithObjects:
201             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
202         NSString *exeName = [[mainBundle infoDictionary]
203                 objectForKey:@"CFBundleExecutable"];
204         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
205         if (!path) {
206             NSLog(@"ERROR: Could not find MacVim executable in bundle");
207             return NO;
208         }
210         [NSTask launchedTaskWithLaunchPath:path arguments:args];
211 #endif
213         // HACK!  The NSWorkspaceDidLaunchApplicationNotification does not work
214         // for tasks like this, so poll the mach bootstrap server until it
215         // returns a valid connection.  Also set a time-out date so that we
216         // don't get stuck doing this forever.
217         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
218         while (!connection &&
219                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
220         {
221             [[NSRunLoop currentRunLoop]
222                     runMode:NSDefaultRunLoopMode
223                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
225             // NOTE: This call will set 'connection' as a side-effect.
226             [self connection];
227         }
229         if (!connection) {
230             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
231             return NO;
232         }
233     }
235     id proxy = [connection rootProxy];
236     [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
238     [[NSNotificationCenter defaultCenter] addObserver:self
239             selector:@selector(connectionDidDie:)
240                 name:NSConnectionDidDieNotification object:connection];
242     int pid = [[NSProcessInfo processInfo] processIdentifier];
244     @try {
245         frontendProxy = [proxy connectBackend:self pid:pid];
246     }
247     @catch (NSException *e) {
248         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
249     }
251     if (frontendProxy) {
252         [frontendProxy retain];
253         [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
254     }
256     return connection && frontendProxy;
259 - (BOOL)openVimWindow
261     [self queueMessage:OpenVimWindowMsgID data:nil];
262     return YES;
265 - (void)clearAll
267     int type = ClearAllDrawType;
269     // Any draw commands in queue are effectively obsolete since this clearAll
270     // will negate any effect they have, therefore we may as well clear the
271     // draw queue.
272     [drawData setLength:0];
274     [drawData appendBytes:&type length:sizeof(int)];
276     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
279 - (void)clearBlockFromRow:(int)row1 column:(int)col1
280                     toRow:(int)row2 column:(int)col2
282     int type = ClearBlockDrawType;
284     [drawData appendBytes:&type length:sizeof(int)];
286     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
287     [drawData appendBytes:&row1 length:sizeof(int)];
288     [drawData appendBytes:&col1 length:sizeof(int)];
289     [drawData appendBytes:&row2 length:sizeof(int)];
290     [drawData appendBytes:&col2 length:sizeof(int)];
293 - (void)deleteLinesFromRow:(int)row count:(int)count
294               scrollBottom:(int)bottom left:(int)left right:(int)right
296     int type = DeleteLinesDrawType;
298     [drawData appendBytes:&type length:sizeof(int)];
300     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
301     [drawData appendBytes:&row length:sizeof(int)];
302     [drawData appendBytes:&count length:sizeof(int)];
303     [drawData appendBytes:&bottom length:sizeof(int)];
304     [drawData appendBytes:&left length:sizeof(int)];
305     [drawData appendBytes:&right length:sizeof(int)];
308 - (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
309                 flags:(int)flags
311     if (len <= 0) return;
313     int type = ReplaceStringDrawType;
315     [drawData appendBytes:&type length:sizeof(int)];
317     [drawData appendBytes:&backgroundColor length:sizeof(int)];
318     [drawData appendBytes:&foregroundColor length:sizeof(int)];
319     [drawData appendBytes:&specialColor length:sizeof(int)];
320     [drawData appendBytes:&row length:sizeof(int)];
321     [drawData appendBytes:&col length:sizeof(int)];
322     [drawData appendBytes:&flags length:sizeof(int)];
323     [drawData appendBytes:&len length:sizeof(int)];
324     [drawData appendBytes:s length:len];
327 - (void)insertLinesFromRow:(int)row count:(int)count
328               scrollBottom:(int)bottom left:(int)left right:(int)right
330     int type = InsertLinesDrawType;
332     [drawData appendBytes:&type length:sizeof(int)];
334     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
335     [drawData appendBytes:&row length:sizeof(int)];
336     [drawData appendBytes:&count length:sizeof(int)];
337     [drawData appendBytes:&bottom length:sizeof(int)];
338     [drawData appendBytes:&left length:sizeof(int)];
339     [drawData appendBytes:&right length:sizeof(int)];
342 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
343                fraction:(int)percent color:(int)color
345     int type = DrawCursorDrawType;
347     [drawData appendBytes:&type length:sizeof(int)];
349     [drawData appendBytes:&color length:sizeof(int)];
350     [drawData appendBytes:&row length:sizeof(int)];
351     [drawData appendBytes:&col length:sizeof(int)];
352     [drawData appendBytes:&shape length:sizeof(int)];
353     [drawData appendBytes:&percent length:sizeof(int)];
356 - (void)flushQueue:(BOOL)force
358     // NOTE! This method gets called a lot; if we were to flush every time it
359     // was called MacVim would feel unresponsive.  So there is a time out which
360     // ensures that the queue isn't flushed too often.
361     if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
362             < MMFlushTimeoutInterval)
363         return;
365     if ([drawData length] > 0) {
366         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
367         [drawData setLength:0];
368     }
370     if ([queue count] > 0) {
371         @try {
372             [frontendProxy processCommandQueue:queue];
373         }
374         @catch (NSException *e) {
375             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
376         }
378         [queue removeAllObjects];
380         [lastFlushDate release];
381         lastFlushDate = [[NSDate date] retain];
382     }
385 - (BOOL)waitForInput:(int)milliseconds
387     NSDate *date = milliseconds > 0 ?
388             [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
389             [NSDate distantFuture];
391     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
393     // I know of no way to figure out if the run loop exited because input was
394     // found or because of a time out, so I need to manually indicate when
395     // input was received in processInput:data: and then reset it every time
396     // here.
397     BOOL yn = inputReceived;
398     inputReceived = NO;
400     return yn;
403 - (void)exit
405 #ifdef MAC_CLIENTSERVER
406     // The default connection is used for the client/server code.
407     [[NSConnection defaultConnection] setRootObject:nil];
408     [[NSConnection defaultConnection] invalidate];
409 #endif
411     // By invalidating the NSConnection the MMWindowController immediately
412     // finds out that the connection is down and as a result
413     // [MMWindowController connectionDidDie:] is invoked.
414     //NSLog(@"%@ %s", [self className], _cmd);
415     [[NSNotificationCenter defaultCenter] removeObserver:self];
416     [connection invalidate];
418     if (fontContainerRef) {
419         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
420         fontContainerRef = 0;
421     }
425 - (void)selectTab:(int)index
427     //NSLog(@"%s%d", _cmd, index);
429     index -= 1;
430     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
431     [self queueMessage:SelectTabMsgID data:data];
434 - (void)updateTabBar
436     //NSLog(@"%s", _cmd);
438     NSMutableData *data = [NSMutableData data];
440     int idx = tabpage_index(curtab) - 1;
441     [data appendBytes:&idx length:sizeof(int)];
443     tabpage_T *tp;
444     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
445         // This function puts the label of the tab in the global 'NameBuff'.
446         get_tabline_label(tp, FALSE);
447         int len = strlen((char*)NameBuff);
448         if (len <= 0) continue;
450         // Count the number of windows in the tabpage.
451         //win_T *wp = tp->tp_firstwin;
452         //int wincount;
453         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
455         //[data appendBytes:&wincount length:sizeof(int)];
456         [data appendBytes:&len length:sizeof(int)];
457         [data appendBytes:NameBuff length:len];
458     }
460     [self queueMessage:UpdateTabBarMsgID data:data];
463 - (BOOL)tabBarVisible
465     return tabBarVisible;
468 - (void)showTabBar:(BOOL)enable
470     tabBarVisible = enable;
472     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
473     [self queueMessage:msgid data:nil];
476 - (void)setRows:(int)rows columns:(int)cols
478     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
480     int dim[] = { rows, cols };
481     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
483     [self queueMessage:SetTextDimensionsMsgID data:data];
486 - (void)setVimWindowTitle:(char *)title
488     NSMutableData *data = [NSMutableData data];
489     int len = strlen(title);
490     if (len <= 0) return;
492     [data appendBytes:&len length:sizeof(int)];
493     [data appendBytes:title length:len];
495     [self queueMessage:SetVimWindowTitleMsgID data:data];
498 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
499                             saving:(int)saving
501     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
502     //        saving);
504     char_u *s = NULL;
505     NSString *ds = dir
506             ? [NSString stringWithCString:dir encoding:NSUTF8StringEncoding]
507             : nil;
508     NSString *ts = title
509             ? [NSString stringWithCString:title encoding:NSUTF8StringEncoding]
510             : nil;
511     @try {
512         [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
514         // Wait until a reply is sent from MMVimController.
515         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
516                                  beforeDate:[NSDate distantFuture]];
518         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
519             s = vim_strsave((char_u*)[dialogReturn UTF8String]);
520         }
522         [dialogReturn release];  dialogReturn = nil;
523     }
524     @catch (NSException *e) {
525         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
526     }
528     return (char *)s;
531 - (oneway void)setDialogReturn:(in bycopy id)obj
533     // NOTE: This is called by
534     //   - [MMVimController panelDidEnd:::], and
535     //   - [MMVimController alertDidEnd:::],
536     // to indicate that a save/open panel or alert has finished.
538     if (obj != dialogReturn) {
539         [dialogReturn release];
540         dialogReturn = [obj retain];
541     }
544 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
545                      buttons:(char *)btns textField:(char *)txtfield
547     int retval = 0;
548     NSString *message = nil, *text = nil, *textFieldString = nil;
549     NSArray *buttons = nil;
550     int style = NSInformationalAlertStyle;
552     if (VIM_WARNING == type) style = NSWarningAlertStyle;
553     else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
555     if (btns) {
556         NSString *btnString = [NSString stringWithUTF8String:btns];
557         buttons = [btnString componentsSeparatedByString:@"\n"];
558     }
559     if (title)
560         message = [NSString stringWithUTF8String:title];
561     if (msg) {
562         text = [NSString stringWithUTF8String:msg];
563         if (!message) {
564             // HACK! If there is a '\n\n' or '\n' sequence in the message, then
565             // make the part up to there into the title.  We only do this
566             // because Vim has lots of dialogs without a title and they look
567             // ugly that way.
568             // TODO: Fix the actual dialog texts.
569             NSRange eolRange = [text rangeOfString:@"\n\n"];
570             if (NSNotFound == eolRange.location)
571                 eolRange = [text rangeOfString:@"\n"];
572             if (NSNotFound != eolRange.location) {
573                 message = [text substringToIndex:eolRange.location];
574                 text = [text substringFromIndex:NSMaxRange(eolRange)];
575             }
576         }
577     }
578     if (txtfield)
579         textFieldString = [NSString stringWithUTF8String:txtfield];
581     @try {
582         [frontendProxy presentDialogWithStyle:style message:message
583                               informativeText:text buttonTitles:buttons
584                               textFieldString:textFieldString];
586         // Wait until a reply is sent from MMVimController.
587         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
588                                  beforeDate:[NSDate distantFuture]];
590         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
591                 && [dialogReturn count]) {
592             retval = [[dialogReturn objectAtIndex:0] intValue];
593             if (txtfield && [dialogReturn count] > 1) {
594                 NSString *retString = [dialogReturn objectAtIndex:1];
595                 vim_strncpy((char_u*)txtfield, (char_u*)[retString UTF8String],
596                         IOSIZE - 1);
597             }
598         }
600         [dialogReturn release]; dialogReturn = nil;
601     }
602     @catch (NSException *e) {
603         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
604     }
606     return retval;
609 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
610                atIndex:(int)index
612     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
613     //        name, index);
615     int namelen = name ? strlen(name) : 0;
616     NSMutableData *data = [NSMutableData data];
618     [data appendBytes:&tag length:sizeof(int)];
619     [data appendBytes:&parentTag length:sizeof(int)];
620     [data appendBytes:&namelen length:sizeof(int)];
621     if (namelen > 0) [data appendBytes:name length:namelen];
622     [data appendBytes:&index length:sizeof(int)];
624     [self queueMessage:AddMenuMsgID data:data];
627 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
628                        tip:(char *)tip icon:(char *)icon
629              keyEquivalent:(int)key modifiers:(int)mods
630                     action:(NSString *)action atIndex:(int)index
632     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
633     //        parentTag, name, tip, index);
635     int namelen = name ? strlen(name) : 0;
636     int tiplen = tip ? strlen(tip) : 0;
637     int iconlen = icon ? strlen(icon) : 0;
638     int eventFlags = vimModMaskToEventModifierFlags(mods);
639     int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
640     NSMutableData *data = [NSMutableData data];
642     key = specialKeyToNSKey(key);
644     [data appendBytes:&tag length:sizeof(int)];
645     [data appendBytes:&parentTag length:sizeof(int)];
646     [data appendBytes:&namelen length:sizeof(int)];
647     if (namelen > 0) [data appendBytes:name length:namelen];
648     [data appendBytes:&tiplen length:sizeof(int)];
649     if (tiplen > 0) [data appendBytes:tip length:tiplen];
650     [data appendBytes:&iconlen length:sizeof(int)];
651     if (iconlen > 0) [data appendBytes:icon length:iconlen];
652     [data appendBytes:&actionlen length:sizeof(int)];
653     if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
654     [data appendBytes:&index length:sizeof(int)];
655     [data appendBytes:&key length:sizeof(int)];
656     [data appendBytes:&eventFlags length:sizeof(int)];
658     [self queueMessage:AddMenuItemMsgID data:data];
661 - (void)removeMenuItemWithTag:(int)tag
663     NSMutableData *data = [NSMutableData data];
664     [data appendBytes:&tag length:sizeof(int)];
666     [self queueMessage:RemoveMenuItemMsgID data:data];
669 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
671     NSMutableData *data = [NSMutableData data];
673     [data appendBytes:&tag length:sizeof(int)];
674     [data appendBytes:&enabled length:sizeof(int)];
676     [self queueMessage:EnableMenuItemMsgID data:data];
679 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
681     int len = strlen(name);
682     int row = -1, col = -1;
684     if (len <= 0) return;
686     if (!mouse && curwin) {
687         row = curwin->w_wrow;
688         col = curwin->w_wcol;
689     }
691     NSMutableData *data = [NSMutableData data];
693     [data appendBytes:&row length:sizeof(int)];
694     [data appendBytes:&col length:sizeof(int)];
695     [data appendBytes:&len length:sizeof(int)];
696     [data appendBytes:name length:len];
698     [self queueMessage:ShowPopupMenuMsgID data:data];
701 - (void)showToolbar:(int)enable flags:(int)flags
703     NSMutableData *data = [NSMutableData data];
705     [data appendBytes:&enable length:sizeof(int)];
706     [data appendBytes:&flags length:sizeof(int)];
708     [self queueMessage:ShowToolbarMsgID data:data];
711 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
713     NSMutableData *data = [NSMutableData data];
715     [data appendBytes:&ident length:sizeof(long)];
716     [data appendBytes:&type length:sizeof(int)];
718     [self queueMessage:CreateScrollbarMsgID data:data];
721 - (void)destroyScrollbarWithIdentifier:(long)ident
723     NSMutableData *data = [NSMutableData data];
724     [data appendBytes:&ident length:sizeof(long)];
726     [self queueMessage:DestroyScrollbarMsgID data:data];
729 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
731     NSMutableData *data = [NSMutableData data];
733     [data appendBytes:&ident length:sizeof(long)];
734     [data appendBytes:&visible length:sizeof(int)];
736     [self queueMessage:ShowScrollbarMsgID data:data];
739 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
741     NSMutableData *data = [NSMutableData data];
743     [data appendBytes:&ident length:sizeof(long)];
744     [data appendBytes:&pos length:sizeof(int)];
745     [data appendBytes:&len length:sizeof(int)];
747     [self queueMessage:SetScrollbarPositionMsgID data:data];
750 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
751                     identifier:(long)ident
753     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
754     float prop = (float)size/(max+1);
755     if (fval < 0) fval = 0;
756     else if (fval > 1.0f) fval = 1.0f;
757     if (prop < 0) prop = 0;
758     else if (prop > 1.0f) prop = 1.0f;
760     NSMutableData *data = [NSMutableData data];
762     [data appendBytes:&ident length:sizeof(long)];
763     [data appendBytes:&fval length:sizeof(float)];
764     [data appendBytes:&prop length:sizeof(float)];
766     [self queueMessage:SetScrollbarThumbMsgID data:data];
769 - (BOOL)setFontWithName:(char *)name
771     NSString *fontName = MMDefaultFontName;
772     float size = MMDefaultFontSize;
773     BOOL parseFailed = NO;
775     if (name) {
776         fontName = [[[NSString alloc] initWithCString:name
777                 encoding:NSUTF8StringEncoding] autorelease];
779         if ([fontName isEqual:@"*"]) {
780             // :set gfn=* shows the font panel.
781             do_cmdline_cmd((char_u*)":action orderFrontFontPanel:");
782             return NO;
783         }
785         NSArray *components = [fontName componentsSeparatedByString:@":"];
786         if ([components count] == 2) {
787             NSString *sizeString = [components lastObject];
788             if ([sizeString length] > 0
789                     && [sizeString characterAtIndex:0] == 'h') {
790                 sizeString = [sizeString substringFromIndex:1];
791                 if ([sizeString length] > 0) {
792                     size = [sizeString floatValue];
793                     fontName = [components objectAtIndex:0];
794                 }
795             } else {
796                 parseFailed = YES;
797             }
798         } else if ([components count] > 2) {
799             parseFailed = YES;
800         }
801     }
803     if (!parseFailed && [fontName length] > 0) {
804         if (size < 6 || size > 100) {
805             // Font size 0.0 tells NSFont to use the 'user default size'.
806             size = 0.0f;
807         }
809         NSFont *font = [NSFont fontWithName:fontName size:size];
811         if (!font && MMDefaultFontName == fontName) {
812             // If for some reason the MacVim default font is not in the app
813             // bundle, then fall back on the system default font.
814             size = 0;
815             font = [NSFont userFixedPitchFontOfSize:size];
816             fontName = [font displayName];
817         }
819         if (font) {
820             //NSLog(@"Setting font '%@' of size %.2f", fontName, size);
821             int len = [fontName
822                     lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
823             if (len > 0) {
824                 NSMutableData *data = [NSMutableData data];
826                 [data appendBytes:&size length:sizeof(float)];
827                 [data appendBytes:&len length:sizeof(int)];
828                 [data appendBytes:[fontName UTF8String] length:len];
830                 [self queueMessage:SetFontMsgID data:data];
831                 return YES;
832             }
833         }
834     }
836     //NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f",
837     //        fontName, size);
838     return NO;
841 - (void)executeActionWithName:(NSString *)name
843     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
845     if (len > 0) {
846         NSMutableData *data = [NSMutableData data];
848         [data appendBytes:&len length:sizeof(int)];
849         [data appendBytes:[name UTF8String] length:len];
851         [self queueMessage:ExecuteActionMsgID data:data];
852     }
855 - (void)setMouseShape:(int)shape
857     NSMutableData *data = [NSMutableData data];
858     [data appendBytes:&shape length:sizeof(int)];
859     [self queueMessage:SetMouseShapeMsgID data:data];
862 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
864     // Vim specifies times in milliseconds, whereas Cocoa wants them in
865     // seconds.
866     blinkWaitInterval = .001f*wait;
867     blinkOnInterval = .001f*on;
868     blinkOffInterval = .001f*off;
871 - (void)startBlink
873     if (blinkTimer) {
874         [blinkTimer invalidate];
875         [blinkTimer release];
876         blinkTimer = nil;
877     }
879     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
880             && gui.in_focus) {
881         blinkState = MMBlinkStateOn;
882         blinkTimer =
883             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
884                                               target:self
885                                             selector:@selector(blinkTimerFired:)
886                                             userInfo:nil repeats:NO] retain];
887         gui_update_cursor(TRUE, FALSE);
888         [self flushQueue:YES];
889     }
892 - (void)stopBlink
894     if (MMBlinkStateOff == blinkState) {
895         gui_update_cursor(TRUE, FALSE);
896         [self flushQueue:YES];
897     }
899     blinkState = MMBlinkStateNone;
902 - (void)adjustLinespace:(int)linespace
904     NSMutableData *data = [NSMutableData data];
905     [data appendBytes:&linespace length:sizeof(int)];
906     [self queueMessage:AdjustLinespaceMsgID data:data];
909 - (void)activate
911     [self queueMessage:ActivateMsgID data:nil];
914 - (void)setServerName:(NSString *)name
916     NSData *data = [name dataUsingEncoding:NSUTF8StringEncoding];
917     [self queueMessage:SetServerNameMsgID data:data];
920 - (int)lookupColorWithKey:(NSString *)key
922     if (!(key && [key length] > 0))
923         return INVALCOLOR;
925     NSString *stripKey = [[[[key lowercaseString]
926         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
927             componentsSeparatedByString:@" "]
928                componentsJoinedByString:@""];
930     if (stripKey && [stripKey length] > 0) {
931         // First of all try to lookup key in the color dictionary; note that
932         // all keys in this dictionary are lowercase with no whitespace.
933         id obj = [colorDict objectForKey:stripKey];
934         if (obj) return [obj intValue];
936         // The key was not in the dictionary; is it perhaps of the form
937         // #rrggbb?
938         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
939             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
940             [scanner setScanLocation:1];
941             unsigned hex = 0;
942             if ([scanner scanHexInt:&hex]) {
943                 return (int)hex;
944             }
945         }
947         // As a last resort, check if it is one of the system defined colors.
948         // The keys in this dictionary are also lowercase with no whitespace.
949         obj = [sysColorDict objectForKey:stripKey];
950         if (obj) {
951             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
952             if (col) {
953                 float r, g, b, a;
954                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
955                 [col getRed:&r green:&g blue:&b alpha:&a];
956                 return (((int)(r*255+.5f) & 0xff) << 16)
957                      + (((int)(g*255+.5f) & 0xff) << 8)
958                      +  ((int)(b*255+.5f) & 0xff);
959             }
960         }
961     }
963     NSLog(@"WARNING: No color with key %@ found.", stripKey);
964     return INVALCOLOR;
967 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
969     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
970     id obj;
972     while ((obj = [e nextObject])) {
973         if ([value isEqual:obj])
974             return YES;
975     }
977     return NO;
980 - (oneway void)processInput:(int)msgid data:(in NSData *)data
982     // NOTE: This method might get called whenever the run loop is tended to.
983     // Thus it might get called whilst input is being processed.  Normally this
984     // is not a problem, but if it gets called often then it might become
985     // dangerous.  E.g. when a focus messages is received the screen is redrawn
986     // because the selection color changes and if another focus message is
987     // received whilst the first one is being processed Vim might crash.  To
988     // deal with this problem at the moment, we simply drop messages that are
989     // received while other input is being processed.
990     if (inProcessInput) {
991 #if MM_USE_INPUT_QUEUE
992         [inputQueue addObject:[NSNumber numberWithInt:msgid]];
993         [inputQueue addObject:data];
994 #else
995         // Just drop the input
996         //NSLog(@"WARNING: Dropping input in %s", _cmd);
997 #endif
998     } else {
999         [self processInputBegin];
1000         [self handleMessage:msgid data:data];
1001         [self processInputEnd];
1002     }
1005 - (oneway void)processInputAndData:(in NSArray *)messages
1007     // NOTE: See comment in processInput:data:.
1008     unsigned i, count = [messages count];
1009     if (count % 2) {
1010         NSLog(@"WARNING: [messages count] is odd in %s", _cmd);
1011         return;
1012     }
1014     if (inProcessInput) {
1015 #if MM_USE_INPUT_QUEUE
1016         [inputQueue addObjectsFromArray:messages];
1017 #else
1018         // Just drop the input
1019         //NSLog(@"WARNING: Dropping input in %s", _cmd);
1020 #endif
1021     } else {
1022         [self processInputBegin];
1024         for (i = 0; i < count; i += 2) {
1025             int msgid = [[messages objectAtIndex:i] intValue];
1026             id data = [messages objectAtIndex:i+1];
1027             if ([data isEqual:[NSNull null]])
1028                 data = nil;
1030             [self handleMessage:msgid data:data];
1031         }
1033         [self processInputEnd];
1034     }
1037 - (BOOL)checkForModifiedBuffers
1039     buf_T *buf;
1040     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
1041         if (bufIsChanged(buf)) {
1042             return YES;
1043         }
1044     }
1046     return NO;
1049 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1051     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1052         // If there is no pasteboard, return YES to indicate that there is text
1053         // to copy.
1054         if (!pboard)
1055             return YES;
1057         clip_copy_selection();
1059         // Get the text to put on the pasteboard.
1060         long_u len = 0; char_u *str = 0;
1061         int type = clip_convert_selection(&str, &len, &clip_star);
1062         if (type < 0)
1063             return NO;
1064         
1065         NSString *string = [[NSString alloc]
1066             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1068         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1069         [pboard declareTypes:types owner:nil];
1070         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1071     
1072         [string release];
1073         vim_free(str);
1075         return ok;
1076     }
1078     return NO;
1081 - (oneway void)addReply:(in bycopy NSString *)reply
1082                  server:(in byref id <MMVimServerProtocol>)server
1084     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1086     // Replies might come at any time and in any order so we keep them in an
1087     // array inside a dictionary with the send port used as key.
1089     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1090     // HACK! Assume connection uses mach ports.
1091     int port = [(NSMachPort*)[conn sendPort] machPort];
1092     NSNumber *key = [NSNumber numberWithInt:port];
1094     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1095     if (!replies) {
1096         replies = [NSMutableArray array];
1097         [serverReplyDict setObject:replies forKey:key];
1098     }
1100     [replies addObject:reply];
1103 - (void)addInput:(in bycopy NSString *)input
1104                  client:(in byref id <MMVimClientProtocol>)client
1106     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1108     server_to_input_buf((char_u*)[input UTF8String]);
1110     [self addClient:(id)client];
1112     inputReceived = YES;
1115 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1116                  client:(in byref id <MMVimClientProtocol>)client
1118     //NSLog(@"evaluateExpression:%@ client:%@", expr, (id)client);
1120     NSString *eval = nil;
1121     char_u *res = eval_client_expr_to_string((char_u*)[expr UTF8String]);
1123     if (res != NULL) {
1124         eval = [NSString stringWithUTF8String:(char*)res];
1125         vim_free(res);
1126     }
1128     [self addClient:(id)client];
1130     return eval;
1133 - (void)registerServerWithName:(NSString *)name
1135     NSString *svrName = name;
1136     NSConnection *svrConn = [NSConnection defaultConnection];
1137     unsigned i;
1139     for (i = 0; i < MMServerMax; ++i) {
1140         NSString *connName = [self connectionNameFromServerName:svrName];
1142         if ([svrConn registerName:connName]) {
1143             //NSLog(@"Registered server with name: %@", svrName);
1145             // TODO: Set request/reply time-outs to something else?
1146             //
1147             // Don't wait for requests (time-out means that the message is
1148             // dropped).
1149             [svrConn setRequestTimeout:0];
1150             //[svrConn setReplyTimeout:MMReplyTimeout];
1151             [svrConn setRootObject:self];
1153             // NOTE: 'serverName' is a global variable
1154             serverName = vim_strsave((char_u*)[svrName UTF8String]);
1155 #ifdef FEAT_EVAL
1156             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1157 #endif
1158 #ifdef FEAT_TITLE
1159             need_maketitle = TRUE;
1160 #endif
1161             [self queueMessage:SetServerNameMsgID data:
1162                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1163             break;
1164         }
1166         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1167     }
1170 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1171                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1172               silent:(BOOL)silent
1174     // NOTE: If 'name' equals 'serverName' then the request is local (client
1175     // and server are the same).  This case is not handled separately, so a
1176     // connection will be set up anyway (this simplifies the code).
1178     NSConnection *conn = [self connectionForServerName:name];
1179     if (!conn) {
1180         if (!silent)
1181             EMSG2(_(e_noserver), [name UTF8String]);
1182         return NO;
1183     }
1185     if (port) {
1186         // HACK! Assume connection uses mach ports.
1187         *port = [(NSMachPort*)[conn sendPort] machPort];
1188     }
1190     id proxy = [conn rootProxy];
1191     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1193     @try {
1194         if (expr) {
1195             NSString *eval = [proxy evaluateExpression:string client:self];
1196             if (reply) {
1197                 *reply = (eval ? vim_strsave((char_u*)[eval UTF8String])
1198                                : vim_strsave((char_u*)_(e_invexprmsg)));
1199             }
1201             if (!eval)
1202                 return NO;
1203         } else {
1204             [proxy addInput:string client:self];
1205         }
1206     }
1207     @catch (NSException *e) {
1208         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1209         return NO;
1210     }
1212     return YES;
1215 - (NSArray *)serverList
1217     NSArray *list = nil;
1219     if ([self connection]) {
1220         id proxy = [connection rootProxy];
1221         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1223         @try {
1224             list = [proxy serverList];
1225         }
1226         @catch (NSException *e) {
1227             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1228         }
1229     } else {
1230         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1231     }
1233     return list;
1236 - (NSString *)peekForReplyOnPort:(int)port
1238     //NSLog(@"%s%d", _cmd, port);
1240     NSNumber *key = [NSNumber numberWithInt:port];
1241     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1242     if (replies && [replies count]) {
1243         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1244         //        [replies objectAtIndex:0]);
1245         return [replies objectAtIndex:0];
1246     }
1248     //NSLog(@"    No replies");
1249     return nil;
1252 - (NSString *)waitForReplyOnPort:(int)port
1254     //NSLog(@"%s%d", _cmd, port);
1255     
1256     NSConnection *conn = [self connectionForServerPort:port];
1257     if (!conn)
1258         return nil;
1260     NSNumber *key = [NSNumber numberWithInt:port];
1261     NSMutableArray *replies = nil;
1262     NSString *reply = nil;
1264     // Wait for reply as long as the connection to the server is valid (unless
1265     // user interrupts wait with Ctrl-C).
1266     while (!got_int && [conn isValid] &&
1267             !(replies = [serverReplyDict objectForKey:key])) {
1268         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1269                                  beforeDate:[NSDate distantFuture]];
1270     }
1272     if (replies) {
1273         if ([replies count] > 0) {
1274             reply = [[replies objectAtIndex:0] retain];
1275             //NSLog(@"    Got reply: %@", reply);
1276             [replies removeObjectAtIndex:0];
1277             [reply autorelease];
1278         }
1280         if ([replies count] == 0)
1281             [serverReplyDict removeObjectForKey:key];
1282     }
1284     return reply;
1287 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1289     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1290     if (client) {
1291         @try {
1292             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1293             [client addReply:reply server:self];
1294             return YES;
1295         }
1296         @catch (NSException *e) {
1297             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1298         }
1299     } else {
1300         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1301     }
1303     return NO;
1306 @end // MMBackend
1310 @implementation MMBackend (Private)
1312 - (void)handleMessage:(int)msgid data:(NSData *)data
1314     if (InsertTextMsgID == msgid) {
1315         if (!data) return;
1316         NSString *key = [[NSString alloc] initWithData:data
1317                                               encoding:NSUTF8StringEncoding];
1318         char_u *str = (char_u*)[key UTF8String];
1319         int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1321 #if MM_ENABLE_CONV
1322         char_u *conv_str = NULL;
1323         if (input_conv.vc_type != CONV_NONE) {
1324             conv_str = string_convert(&input_conv, str, &len);
1325             if (conv_str)
1326                 str = conv_str;
1327         }
1328 #endif
1330         for (i = 0; i < len; ++i) {
1331             add_to_input_buf(str+i, 1);
1332             if (CSI == str[i]) {
1333                 // NOTE: If the converted string contains the byte CSI, then it
1334                 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1335                 // won't work.
1336                 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1337                 add_to_input_buf(extra, 2);
1338             }
1339         }
1341 #if MM_ENABLE_CONV
1342         if (conv_str)
1343             vim_free(conv_str);
1344 #endif
1345         [key release];
1346     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1347         if (!data) return;
1348         const void *bytes = [data bytes];
1349         int mods = *((int*)bytes);  bytes += sizeof(int);
1350         int len = *((int*)bytes);  bytes += sizeof(int);
1351         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1352                                               encoding:NSUTF8StringEncoding];
1353         mods = eventModifierFlagsToVimModMask(mods);
1355         [self handleKeyDown:key modifiers:mods];
1357         [key release];
1358     } else if (SelectTabMsgID == msgid) {
1359         if (!data) return;
1360         const void *bytes = [data bytes];
1361         int idx = *((int*)bytes) + 1;
1362         //NSLog(@"Selecting tab %d", idx);
1363         send_tabline_event(idx);
1364     } else if (CloseTabMsgID == msgid) {
1365         if (!data) return;
1366         const void *bytes = [data bytes];
1367         int idx = *((int*)bytes) + 1;
1368         //NSLog(@"Closing tab %d", idx);
1369         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1370     } else if (AddNewTabMsgID == msgid) {
1371         //NSLog(@"Adding new tab");
1372         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1373     } else if (DraggedTabMsgID == msgid) {
1374         if (!data) return;
1375         const void *bytes = [data bytes];
1376         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1377         // based.
1378         int idx = *((int*)bytes);
1380         tabpage_move(idx);
1381     } else if (ScrollWheelMsgID == msgid) {
1382         if (!data) return;
1383         const void *bytes = [data bytes];
1385         int row = *((int*)bytes);  bytes += sizeof(int);
1386         int col = *((int*)bytes);  bytes += sizeof(int);
1387         int flags = *((int*)bytes);  bytes += sizeof(int);
1388         float dy = *((float*)bytes);  bytes += sizeof(float);
1390         int button = MOUSE_5;
1391         if (dy > 0) button = MOUSE_4;
1393         flags = eventModifierFlagsToVimMouseModMask(flags);
1395         gui_send_mouse_event(button, col, row, NO, flags);
1396     } else if (MouseDownMsgID == msgid) {
1397         if (!data) return;
1398         const void *bytes = [data bytes];
1400         int row = *((int*)bytes);  bytes += sizeof(int);
1401         int col = *((int*)bytes);  bytes += sizeof(int);
1402         int button = *((int*)bytes);  bytes += sizeof(int);
1403         int flags = *((int*)bytes);  bytes += sizeof(int);
1404         int count = *((int*)bytes);  bytes += sizeof(int);
1406         button = eventButtonNumberToVimMouseButton(button);
1407         flags = eventModifierFlagsToVimMouseModMask(flags);
1409         gui_send_mouse_event(button, col, row, count>1, flags);
1410     } else if (MouseUpMsgID == msgid) {
1411         if (!data) return;
1412         const void *bytes = [data bytes];
1414         int row = *((int*)bytes);  bytes += sizeof(int);
1415         int col = *((int*)bytes);  bytes += sizeof(int);
1416         int flags = *((int*)bytes);  bytes += sizeof(int);
1418         flags = eventModifierFlagsToVimMouseModMask(flags);
1420         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1421     } else if (MouseDraggedMsgID == msgid) {
1422         if (!data) return;
1423         const void *bytes = [data bytes];
1425         int row = *((int*)bytes);  bytes += sizeof(int);
1426         int col = *((int*)bytes);  bytes += sizeof(int);
1427         int flags = *((int*)bytes);  bytes += sizeof(int);
1429         flags = eventModifierFlagsToVimMouseModMask(flags);
1431         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1432     } else if (SetTextDimensionsMsgID == msgid) {
1433         if (!data) return;
1434         const void *bytes = [data bytes];
1435         int rows = *((int*)bytes);  bytes += sizeof(int);
1436         int cols = *((int*)bytes);  bytes += sizeof(int);
1438         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1439         // gui_resize_shell(), so we have to manually set the rows and columns
1440         // here.  (MacVim doesn't change the rows and columns to avoid
1441         // inconsistent states between Vim and MacVim.)
1442         [self setRows:rows columns:cols];
1444         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1445         gui_resize_shell(cols, rows);
1446     } else if (ExecuteMenuMsgID == msgid) {
1447         if (!data) return;
1448         const void *bytes = [data bytes];
1449         int tag = *((int*)bytes);  bytes += sizeof(int);
1451         vimmenu_T *menu = (vimmenu_T*)tag;
1452         // TODO!  Make sure 'menu' is a valid menu pointer!
1453         if (menu) {
1454             gui_menu_cb(menu);
1455         }
1456     } else if (ToggleToolbarMsgID == msgid) {
1457         char_u go[sizeof(GO_ALL)+2];
1458         char_u *p;
1459         int len;
1461         STRCPY(go, p_go);
1462         p = vim_strchr(go, GO_TOOLBAR);
1463         len = STRLEN(go);
1465         if (p != NULL) {
1466             char_u *end = go + len;
1467             while (p < end) {
1468                 p[0] = p[1];
1469                 ++p;
1470             }
1471         } else {
1472             go[len] = GO_TOOLBAR;
1473             go[len+1] = NUL;
1474         }
1476         set_option_value((char_u*)"guioptions", 0, go, 0);
1478         // Force screen redraw (does it have to be this complicated?).
1479         redraw_all_later(CLEAR);
1480         update_screen(NOT_VALID);
1481         setcursor();
1482         out_flush();
1483         gui_update_cursor(FALSE, FALSE);
1484         gui_mch_flush();
1485     } else if (ScrollbarEventMsgID == msgid) {
1486         if (!data) return;
1487         const void *bytes = [data bytes];
1488         long ident = *((long*)bytes);  bytes += sizeof(long);
1489         int hitPart = *((int*)bytes);  bytes += sizeof(int);
1490         float fval = *((float*)bytes);  bytes += sizeof(float);
1491         scrollbar_T *sb = gui_find_scrollbar(ident);
1493         if (sb) {
1494             scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1495             long value = sb_info->value;
1496             long size = sb_info->size;
1497             long max = sb_info->max;
1498             BOOL isStillDragging = NO;
1499             BOOL updateKnob = YES;
1501             switch (hitPart) {
1502             case NSScrollerDecrementPage:
1503                 value -= (size > 2 ? size - 2 : 1);
1504                 break;
1505             case NSScrollerIncrementPage:
1506                 value += (size > 2 ? size - 2 : 1);
1507                 break;
1508             case NSScrollerDecrementLine:
1509                 --value;
1510                 break;
1511             case NSScrollerIncrementLine:
1512                 ++value;
1513                 break;
1514             case NSScrollerKnob:
1515                 isStillDragging = YES;
1516                 // fall through ...
1517             case NSScrollerKnobSlot:
1518                 value = (long)(fval * (max - size + 1));
1519                 // fall through ...
1520             default:
1521                 updateKnob = NO;
1522                 break;
1523             }
1525             //NSLog(@"value %d -> %d", sb_info->value, value);
1526             gui_drag_scrollbar(sb, value, isStillDragging);
1528             if (updateKnob) {
1529                 // Dragging the knob or option+clicking automatically updates
1530                 // the knob position (on the actual NSScroller), so we only
1531                 // need to set the knob position in the other cases.
1532                 if (sb->wp) {
1533                     // Update both the left&right vertical scrollbars.
1534                     long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1535                     long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1536                     [self setScrollbarThumbValue:value size:size max:max
1537                                       identifier:identLeft];
1538                     [self setScrollbarThumbValue:value size:size max:max
1539                                       identifier:identRight];
1540                 } else {
1541                     // Update the horizontal scrollbar.
1542                     [self setScrollbarThumbValue:value size:size max:max
1543                                       identifier:ident];
1544                 }
1545             }
1546         }
1547     } else if (SetFontMsgID == msgid) {
1548         if (!data) return;
1549         const void *bytes = [data bytes];
1550         float pointSize = *((float*)bytes);  bytes += sizeof(float);
1551         //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1552         bytes += sizeof(unsigned);  // len not used
1554         NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1555         [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1557         set_option_value((char_u*)"gfn", 0, (char_u*)[name UTF8String], 0);
1559         // Force screen redraw (does it have to be this complicated?).
1560         redraw_all_later(CLEAR);
1561         update_screen(NOT_VALID);
1562         setcursor();
1563         out_flush();
1564         gui_update_cursor(FALSE, FALSE);
1565         gui_mch_flush();
1566     } else if (VimShouldCloseMsgID == msgid) {
1567         gui_shell_closed();
1568     } else if (DropFilesMsgID == msgid) {
1569 #ifdef FEAT_DND
1570         const void *bytes = [data bytes];
1571         const void *end = [data bytes] + [data length];
1572         int n = *((int*)bytes);  bytes += sizeof(int);
1574         if (State & CMDLINE) {
1575             // HACK!  If Vim is in command line mode then the files names
1576             // should be added to the command line, instead of opening the
1577             // files in tabs.  This is taken care of by gui_handle_drop().
1578             char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1579             if (fnames) {
1580                 int i = 0;
1581                 while (bytes < end && i < n) {
1582                     int len = *((int*)bytes);  bytes += sizeof(int);
1583                     fnames[i++] = vim_strnsave((char_u*)bytes, len);
1584                     bytes += len;
1585                 }
1587                 // NOTE!  This function will free 'fnames'.
1588                 // HACK!  It is assumed that the 'x' and 'y' arguments are
1589                 // unused when in command line mode.
1590                 gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
1591             }
1592         } else {
1593             // HACK!  I'm not sure how to get Vim to open a list of files in
1594             // tabs, so instead I create a ':tab drop' command with all the
1595             // files to open and execute it.
1596             NSMutableString *cmd = (n > 1)
1597                     ? [NSMutableString stringWithString:@":tab drop"]
1598                     : [NSMutableString stringWithString:@":drop"];
1600             int i;
1601             for (i = 0; i < n && bytes < end; ++i) {
1602                 int len = *((int*)bytes);  bytes += sizeof(int);
1603                 NSMutableString *file =
1604                         [NSMutableString stringWithUTF8String:bytes];
1605                 [file replaceOccurrencesOfString:@" "
1606                                       withString:@"\\ "
1607                                          options:0
1608                                            range:NSMakeRange(0,[file length])];
1609                 bytes += len;
1611                 [cmd appendString:@" "];
1612                 [cmd appendString:file];
1613             }
1615             // By going to the last tabpage we ensure that the new tabs will
1616             // appear last (if this call is left out, the taborder becomes
1617             // messy).
1618             goto_tabpage(9999);
1620             do_cmdline_cmd((char_u*)[cmd UTF8String]);
1622             // Force screen redraw (does it have to be this complicated?).
1623             // (This code was taken from the end of gui_handle_drop().)
1624             update_screen(NOT_VALID);
1625             setcursor();
1626             out_flush();
1627             gui_update_cursor(FALSE, FALSE);
1628             gui_mch_flush();
1629         }
1630 #endif // FEAT_DND
1631     } else if (DropStringMsgID == msgid) {
1632 #ifdef FEAT_DND
1633         char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
1634         const void *bytes = [data bytes];
1635         int len = *((int*)bytes);  bytes += sizeof(int);
1636         NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
1638         // Replace unrecognized end-of-line sequences with \x0a (line feed).
1639         NSRange range = { 0, [string length] };
1640         unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
1641                                              withString:@"\x0a" options:0
1642                                                   range:range];
1643         if (0 == n) {
1644             n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
1645                                            options:0 range:range];
1646         }
1648         len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1649         dnd_yank_drag_data((char_u*)[string UTF8String], len);
1650         add_to_input_buf(dropkey, sizeof(dropkey));
1651 #endif // FEAT_DND
1652     } else if (GotFocusMsgID == msgid) {
1653         if (!gui.in_focus)
1654             [self focusChange:YES];
1655     } else if (LostFocusMsgID == msgid) {
1656         if (gui.in_focus)
1657             [self focusChange:NO];
1658     } else if (MouseMovedMsgID == msgid) {
1659         const void *bytes = [data bytes];
1660         int row = *((int*)bytes);  bytes += sizeof(int);
1661         int col = *((int*)bytes);  bytes += sizeof(int);
1663         gui_mouse_moved(col, row);
1664     } else if (SetMouseShapeMsgID == msgid) {
1665         const void *bytes = [data bytes];
1666         int shape = *((int*)bytes);  bytes += sizeof(int);
1667         update_mouseshape(shape);
1668     } else {
1669         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1670     }
1673 + (NSDictionary *)specialKeys
1675     static NSDictionary *specialKeys = nil;
1677     if (!specialKeys) {
1678         NSBundle *mainBundle = [NSBundle mainBundle];
1679         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1680                                               ofType:@"plist"];
1681         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1682     }
1684     return specialKeys;
1687 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1689     char_u special[3];
1690     char_u modChars[3];
1691     char_u *chars = (char_u*)[key UTF8String];
1692     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1694     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1695     // that new keys can easily be added.
1696     NSString *specialString = [[MMBackend specialKeys]
1697             objectForKey:key];
1698     if (specialString && [specialString length] > 1) {
1699         //NSLog(@"special key: %@", specialString);
1700         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1701                 [specialString characterAtIndex:1]);
1703         ikey = simplify_key(ikey, &mods);
1704         if (ikey == CSI)
1705             ikey = K_CSI;
1707         special[0] = CSI;
1708         special[1] = K_SECOND(ikey);
1709         special[2] = K_THIRD(ikey);
1711         chars = special;
1712         length = 3;
1713     } else if (1 == length && TAB == chars[0]) {
1714         // Tab is a trouble child:
1715         // - <Tab> is added to the input buffer as is
1716         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1717         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1718         //   to be converted to utf-8
1719         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1720         // - <C-Tab> is reserved by Mac OS X
1721         // - <D-Tab> is reserved by Mac OS X
1722         chars = special;
1723         special[0] = TAB;
1724         length = 1;
1726         if (mods & MOD_MASK_SHIFT) {
1727             mods &= ~MOD_MASK_SHIFT;
1728             special[0] = CSI;
1729             special[1] = K_SECOND(K_S_TAB);
1730             special[2] = K_THIRD(K_S_TAB);
1731             length = 3;
1732         } else if (mods & MOD_MASK_ALT) {
1733             int mtab = 0x80 | TAB;
1734             // Convert to utf-8
1735             special[0] = (mtab >> 6) + 0xc0;
1736             special[1] = mtab & 0xbf;
1737             length = 2;
1738             mods &= ~MOD_MASK_ALT;
1739         }
1740     } else if (length > 0) {
1741         unichar c = [key characterAtIndex:0];
1743         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1744         //        [key characterAtIndex:0], mods);
1746         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1747                 || (c == intr_char && intr_char != Ctrl_C))) {
1748             trash_input_buf();
1749             got_int = TRUE;
1750         }
1752         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1753         // cleared since they are already added to the key by the AppKit.
1754         // Unfortunately, the only way to deal with when to clear the modifiers
1755         // or not seems to be to have hard-wired rules like this.
1756         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1757                     || 0x9 == c) ) {
1758             mods &= ~MOD_MASK_SHIFT;
1759             mods &= ~MOD_MASK_CTRL;
1760             //NSLog(@"clear shift ctrl");
1761         }
1763         // HACK!  All Option+key presses go via 'insert text' messages, except
1764         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1765         // not work to map to it.
1766         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1767             //NSLog(@"clear alt");
1768             mods &= ~MOD_MASK_ALT;
1769         }
1770     }
1772     if (chars && length > 0) {
1773         if (mods) {
1774             //NSLog(@"adding mods: %d", mods);
1775             modChars[0] = CSI;
1776             modChars[1] = KS_MODIFIER;
1777             modChars[2] = mods;
1778             add_to_input_buf(modChars, 3);
1779         }
1781         //NSLog(@"add to input buf: 0x%x", chars[0]);
1782         // TODO: Check for CSI bytes?
1783         add_to_input_buf(chars, length);
1784     }
1787 - (void)queueMessage:(int)msgid data:(NSData *)data
1789     [queue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1790     if (data)
1791         [queue addObject:data];
1792     else
1793         [queue addObject:[NSData data]];
1796 - (void)connectionDidDie:(NSNotification *)notification
1798     // If the main connection to MacVim is lost this means that MacVim was
1799     // either quit (by the user chosing Quit on the MacVim menu), or it has
1800     // crashed.  In either case our only option is to quit now.
1801     // TODO: Write backup file?
1803     //NSLog(@"A Vim process lots its connection to MacVim; quitting.");
1804     getout(0);
1807 - (void)blinkTimerFired:(NSTimer *)timer
1809     NSTimeInterval timeInterval = 0;
1811     [blinkTimer release];
1812     blinkTimer = nil;
1814     if (MMBlinkStateOn == blinkState) {
1815         gui_undraw_cursor();
1816         blinkState = MMBlinkStateOff;
1817         timeInterval = blinkOffInterval;
1818     } else if (MMBlinkStateOff == blinkState) {
1819         gui_update_cursor(TRUE, FALSE);
1820         blinkState = MMBlinkStateOn;
1821         timeInterval = blinkOnInterval;
1822     }
1824     if (timeInterval > 0) {
1825         blinkTimer = 
1826             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1827                                             selector:@selector(blinkTimerFired:)
1828                                             userInfo:nil repeats:NO] retain];
1829         [self flushQueue:YES];
1830     }
1833 - (void)focusChange:(BOOL)on
1835     gui_focus_change(on);
1838 - (void)processInputBegin
1840     inProcessInput = YES;
1841     [lastFlushDate release];
1842     lastFlushDate = [[NSDate date] retain];
1845 - (void)processInputEnd
1847 #if MM_USE_INPUT_QUEUE
1848     int count = [inputQueue count];
1849     if (count % 2) {
1850         // TODO: This is troubling, but it is not hard to get Vim to end up
1851         // here.  Why does this happen?
1852         NSLog(@"WARNING: inputQueue has odd number of objects (%d)", count);
1853         [inputQueue removeAllObjects];
1854     } else if (count > 0) {
1855         // TODO: Dispatch these messages?  Maybe not; usually when the
1856         // 'inputQueue' is non-empty it means that a LOT of messages has been
1857         // sent simultaneously.  The only way this happens is when Vim is being
1858         // tormented, e.g. if the user holds down <D-`> to rapidly switch
1859         // windows.
1860         unsigned i;
1861         for (i = 0; i < count; i+=2) {
1862             int msgid = [[inputQueue objectAtIndex:i] intValue];
1863             NSLog(@"%s: Dropping message %s", _cmd, MessageStrings[msgid]);
1864         }
1866         [inputQueue removeAllObjects];
1867     }
1868 #endif
1870     inputReceived = YES;
1871     inProcessInput = NO;
1874 - (NSString *)connectionNameFromServerName:(NSString *)name
1876     NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
1878     return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
1879         lowercaseString];
1882 - (NSConnection *)connectionForServerName:(NSString *)name
1884     // TODO: Try 'name%d' if 'name' fails.
1885     NSString *connName = [self connectionNameFromServerName:name];
1886     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
1888     if (!svrConn) {
1889         svrConn = [NSConnection connectionWithRegisteredName:connName
1890                                                            host:nil];
1891         // Try alternate server...
1892         if (!svrConn && alternateServerName) {
1893             //NSLog(@"  trying to connect to alternate server: %@",
1894             //        alternateServerName);
1895             connName = [self connectionNameFromServerName:alternateServerName];
1896             svrConn = [NSConnection connectionWithRegisteredName:connName
1897                                                             host:nil];
1898         }
1900         // Try looking for alternate servers...
1901         if (!svrConn) {
1902             //NSLog(@"  looking for alternate servers...");
1903             NSString *alt = [self alternateServerNameForName:name];
1904             if (alt != alternateServerName) {
1905                 //NSLog(@"  found alternate server: %@", string);
1906                 [alternateServerName release];
1907                 alternateServerName = [alt copy];
1908             }
1909         }
1911         // Try alternate server again...
1912         if (!svrConn && alternateServerName) {
1913             //NSLog(@"  trying to connect to alternate server: %@",
1914             //        alternateServerName);
1915             connName = [self connectionNameFromServerName:alternateServerName];
1916             svrConn = [NSConnection connectionWithRegisteredName:connName
1917                                                             host:nil];
1918         }
1920         if (svrConn) {
1921             [connectionNameDict setObject:svrConn forKey:connName];
1923             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
1924             [[NSNotificationCenter defaultCenter] addObserver:self
1925                     selector:@selector(serverConnectionDidDie:)
1926                         name:NSConnectionDidDieNotification object:svrConn];
1927         }
1928     }
1930     return svrConn;
1933 - (NSConnection *)connectionForServerPort:(int)port
1935     NSConnection *conn;
1936     NSEnumerator *e = [connectionNameDict objectEnumerator];
1938     while ((conn = [e nextObject])) {
1939         // HACK! Assume connection uses mach ports.
1940         if (port == [(NSMachPort*)[conn sendPort] machPort])
1941             return conn;
1942     }
1944     return nil;
1947 - (void)serverConnectionDidDie:(NSNotification *)notification
1949     //NSLog(@"%s%@", _cmd, notification);
1951     NSConnection *svrConn = [notification object];
1953     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
1954     [[NSNotificationCenter defaultCenter]
1955             removeObserver:self
1956                       name:NSConnectionDidDieNotification
1957                     object:svrConn];
1959     [connectionNameDict removeObjectsForKeys:
1960         [connectionNameDict allKeysForObject:svrConn]];
1962     // HACK! Assume connection uses mach ports.
1963     int port = [(NSMachPort*)[svrConn sendPort] machPort];
1964     NSNumber *key = [NSNumber numberWithInt:port];
1966     [clientProxyDict removeObjectForKey:key];
1967     [serverReplyDict removeObjectForKey:key];
1970 - (void)addClient:(NSDistantObject *)client
1972     NSConnection *conn = [client connectionForProxy];
1973     // HACK! Assume connection uses mach ports.
1974     int port = [(NSMachPort*)[conn sendPort] machPort];
1975     NSNumber *key = [NSNumber numberWithInt:port];
1977     if (![clientProxyDict objectForKey:key]) {
1978         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
1979         [clientProxyDict setObject:client forKey:key];
1980     }
1982     // NOTE: 'clientWindow' is a global variable which is used by <client>
1983     clientWindow = port;
1986 - (NSString *)alternateServerNameForName:(NSString *)name
1988     if (!(name && [name length] > 0))
1989         return nil;
1991     // Only look for alternates if 'name' doesn't end in a digit.
1992     unichar lastChar = [name characterAtIndex:[name length]-1];
1993     if (lastChar >= '0' && lastChar <= '9')
1994         return nil;
1996     // Look for alternates among all current servers.
1997     NSArray *list = [self serverList];
1998     if (!(list && [list count] > 0))
1999         return nil;
2001     // Filter out servers starting with 'name' and ending with a number. The
2002     // (?i) pattern ensures that the match is case insensitive.
2003     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2004     NSPredicate *pred = [NSPredicate predicateWithFormat:
2005             @"SELF MATCHES %@", pat];
2006     list = [list filteredArrayUsingPredicate:pred];
2007     if ([list count] > 0) {
2008         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2009         return [list objectAtIndex:0];
2010     }
2012     return nil;
2015 @end // MMBackend (Private)
2020 @implementation NSString (MMServerNameCompare)
2021 - (NSComparisonResult)serverNameCompare:(NSString *)string
2023     return [self compare:string
2024                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2026 @end
2031 static int eventModifierFlagsToVimModMask(int modifierFlags)
2033     int modMask = 0;
2035     if (modifierFlags & NSShiftKeyMask)
2036         modMask |= MOD_MASK_SHIFT;
2037     if (modifierFlags & NSControlKeyMask)
2038         modMask |= MOD_MASK_CTRL;
2039     if (modifierFlags & NSAlternateKeyMask)
2040         modMask |= MOD_MASK_ALT;
2041     if (modifierFlags & NSCommandKeyMask)
2042         modMask |= MOD_MASK_CMD;
2044     return modMask;
2047 static int vimModMaskToEventModifierFlags(int mods)
2049     int flags = 0;
2051     if (mods & MOD_MASK_SHIFT)
2052         flags |= NSShiftKeyMask;
2053     if (mods & MOD_MASK_CTRL)
2054         flags |= NSControlKeyMask;
2055     if (mods & MOD_MASK_ALT)
2056         flags |= NSAlternateKeyMask;
2057     if (mods & MOD_MASK_CMD)
2058         flags |= NSCommandKeyMask;
2060     return flags;
2063 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2065     int modMask = 0;
2067     if (modifierFlags & NSShiftKeyMask)
2068         modMask |= MOUSE_SHIFT;
2069     if (modifierFlags & NSControlKeyMask)
2070         modMask |= MOUSE_CTRL;
2071     if (modifierFlags & NSAlternateKeyMask)
2072         modMask |= MOUSE_ALT;
2074     return modMask;
2077 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2079     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE,
2080             MOUSE_X1, MOUSE_X2 };
2082     return mouseButton[buttonNumber < 5 ? buttonNumber : 0];
2085 static int specialKeyToNSKey(int key)
2087     if (!IS_SPECIAL(key))
2088         return key;
2090     static struct {
2091         int special;
2092         int nskey;
2093     } sp2ns[] = {
2094         { K_UP, NSUpArrowFunctionKey },
2095         { K_DOWN, NSDownArrowFunctionKey },
2096         { K_LEFT, NSLeftArrowFunctionKey },
2097         { K_RIGHT, NSRightArrowFunctionKey },
2098         { K_F1, NSF1FunctionKey },
2099         { K_F2, NSF2FunctionKey },
2100         { K_F3, NSF3FunctionKey },
2101         { K_F4, NSF4FunctionKey },
2102         { K_F5, NSF5FunctionKey },
2103         { K_F6, NSF6FunctionKey },
2104         { K_F7, NSF7FunctionKey },
2105         { K_F8, NSF8FunctionKey },
2106         { K_F9, NSF9FunctionKey },
2107         { K_F10, NSF10FunctionKey },
2108         { K_F11, NSF11FunctionKey },
2109         { K_F12, NSF12FunctionKey },
2110         { K_F13, NSF13FunctionKey },
2111         { K_F14, NSF14FunctionKey },
2112         { K_F15, NSF15FunctionKey },
2113         { K_F16, NSF16FunctionKey },
2114         { K_F17, NSF17FunctionKey },
2115         { K_F18, NSF18FunctionKey },
2116         { K_F19, NSF19FunctionKey },
2117         { K_F20, NSF20FunctionKey },
2118         { K_F21, NSF21FunctionKey },
2119         { K_F22, NSF22FunctionKey },
2120         { K_F23, NSF23FunctionKey },
2121         { K_F24, NSF24FunctionKey },
2122         { K_F25, NSF25FunctionKey },
2123         { K_F26, NSF26FunctionKey },
2124         { K_F27, NSF27FunctionKey },
2125         { K_F28, NSF28FunctionKey },
2126         { K_F29, NSF29FunctionKey },
2127         { K_F30, NSF30FunctionKey },
2128         { K_F31, NSF31FunctionKey },
2129         { K_F32, NSF32FunctionKey },
2130         { K_F33, NSF33FunctionKey },
2131         { K_F34, NSF34FunctionKey },
2132         { K_F35, NSF35FunctionKey },
2133         { K_DEL, NSBackspaceCharacter },
2134         { K_BS, NSDeleteCharacter },
2135         { K_HOME, NSHomeFunctionKey },
2136         { K_END, NSEndFunctionKey },
2137         { K_PAGEUP, NSPageUpFunctionKey },
2138         { K_PAGEDOWN, NSPageDownFunctionKey }
2139     };
2141     int i;
2142     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2143         if (sp2ns[i].special == key)
2144             return sp2ns[i].nskey;
2145     }
2147     return 0;