- Escape '%' with backslash in drop files
[MacVim/jjgod.git] / MMBackend.m
blob848f20f9e9e3a52e3b0a9be82e08941e62dbe026
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 // NOTE: Colors in MMBackend are stored as unsigned ints on the form 0xaarrggbb
16 // whereas colors in Vim are int without the alpha component.  Also note that
17 // 'transp' is assumed to be a value between 0 and 100.
18 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
19 #define MM_COLOR_WITH_TRANSP(col,transp) \
20     ((unsigned)( ((col)&0xffffff) \
21         | ((((unsigned)((((100-(transp))*255)/100)+.5f))&0xff)<<24) ))
24 // This constant controls how often the command queue may be flushed.  If it is
25 // too small the app might feel unresponsive; if it is too large there might be
26 // long periods without the screen updating (e.g. when sourcing a large session
27 // file).  (The unit is seconds.)
28 static float MMFlushTimeoutInterval = 0.1f;
30 static unsigned MMServerMax = 1000;
32 // NOTE: The default font is bundled with the application.
33 static NSString *MMDefaultFontName = @"DejaVu Sans Mono";
34 static float MMDefaultFontSize = 12.0f;
36 // TODO: Move to separate file.
37 static int eventModifierFlagsToVimModMask(int modifierFlags);
38 static int vimModMaskToEventModifierFlags(int mods);
39 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
40 static int eventButtonNumberToVimMouseButton(int buttonNumber);
41 static int specialKeyToNSKey(int key);
43 enum {
44     MMBlinkStateNone = 0,
45     MMBlinkStateOn,
46     MMBlinkStateOff
51 @interface NSString (MMServerNameCompare)
52 - (NSComparisonResult)serverNameCompare:(NSString *)string;
53 @end
57 @interface MMBackend (Private)
58 - (void)handleMessage:(int)msgid data:(NSData *)data;
59 + (NSDictionary *)specialKeys;
60 - (void)handleInsertText:(NSData *)data;
61 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
62 - (void)queueMessage:(int)msgid data:(NSData *)data;
63 - (void)connectionDidDie:(NSNotification *)notification;
64 - (void)blinkTimerFired:(NSTimer *)timer;
65 - (void)focusChange:(BOOL)on;
66 - (void)processInputBegin;
67 - (void)processInputEnd;
68 - (void)handleToggleToolbar;
69 - (void)handleScrollbarEvent:(NSData *)data;
70 - (void)handleSetFont:(NSData *)data;
71 - (void)handleDropFiles:(NSData *)data;
72 - (void)handleDropString:(NSData *)data;
73 @end
77 @interface MMBackend (ClientServer)
78 - (NSString *)connectionNameFromServerName:(NSString *)name;
79 - (NSConnection *)connectionForServerName:(NSString *)name;
80 - (NSConnection *)connectionForServerPort:(int)port;
81 - (void)serverConnectionDidDie:(NSNotification *)notification;
82 - (void)addClient:(NSDistantObject *)client;
83 - (NSString *)alternateServerNameForName:(NSString *)name;
84 @end
88 @implementation MMBackend
90 + (MMBackend *)sharedInstance
92     static MMBackend *singleton = nil;
93     return singleton ? singleton : (singleton = [MMBackend new]);
96 - (id)init
98     if ((self = [super init])) {
99         fontContainerRef = loadFonts();
101         queue = [[NSMutableArray alloc] init];
102 #if MM_USE_INPUT_QUEUE
103         inputQueue = [[NSMutableArray alloc] init];
104 #endif
105         drawData = [[NSMutableData alloc] initWithCapacity:1024];
106         connectionNameDict = [[NSMutableDictionary alloc] init];
107         clientProxyDict = [[NSMutableDictionary alloc] init];
108         serverReplyDict = [[NSMutableDictionary alloc] init];
110         NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
111                                                          ofType:@"plist"];
112         if (path) {
113             colorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
114                 retain];
115         } else {
116             NSLog(@"WARNING: Could not locate Colors.plist.");
117         }
119         path = [[NSBundle mainBundle] pathForResource:@"SystemColors"
120                                                ofType:@"plist"];
121         if (path) {
122             sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
123                 retain];
124         } else {
125             NSLog(@"WARNING: Could not locate SystemColors.plist.");
126         }
127     }
129     return self;
132 - (void)dealloc
134     //NSLog(@"%@ %s", [self className], _cmd);
136     [[NSNotificationCenter defaultCenter] removeObserver:self];
138     [blinkTimer release];  blinkTimer = nil;
139 #if MM_USE_INPUT_QUEUE
140     [inputQueue release];  inputQueue = nil;
141 #endif
142     [alternateServerName release];  alternateServerName = nil;
143     [serverReplyDict release];  serverReplyDict = nil;
144     [clientProxyDict release];  clientProxyDict = nil;
145     [connectionNameDict release];  connectionNameDict = nil;
146     [queue release];  queue = nil;
147     [drawData release];  drawData = nil;
148     [frontendProxy release];  frontendProxy = nil;
149     [connection release];  connection = nil;
150     [sysColorDict release];  sysColorDict = nil;
151     [colorDict release];  colorDict = nil;
153     [super dealloc];
156 - (void)setBackgroundColor:(int)color
158     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
161 - (void)setForegroundColor:(int)color
163     foregroundColor = MM_COLOR(color);
166 - (void)setSpecialColor:(int)color
168     specialColor = MM_COLOR(color);
171 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
173     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
174     defaultForegroundColor = MM_COLOR(fg);
176     NSMutableData *data = [NSMutableData data];
178     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
179     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
181     [self queueMessage:SetDefaultColorsMsgID data:data];
184 - (NSConnection *)connection
186     if (!connection) {
187         // NOTE!  If the name of the connection changes here it must also be
188         // updated in MMAppController.m.
189         NSString *name = [NSString stringWithFormat:@"%@-connection",
190                [[NSBundle mainBundle] bundleIdentifier]];
192         connection = [NSConnection connectionWithRegisteredName:name host:nil];
193         [connection retain];
194     }
196     // NOTE: 'connection' may be nil here.
197     return connection;
200 - (BOOL)checkin
202     if (![self connection]) {
203         NSBundle *mainBundle = [NSBundle mainBundle];
204 #if 0
205         NSString *path = [mainBundle bundlePath];
206         if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
207             NSLog(@"WARNING: Failed to launch GUI with path %@", path);
208             return NO;
209         }
210 #else
211         // HACK!  It would be preferable to launch the GUI using NSWorkspace,
212         // however I have not managed to figure out how to pass arguments using
213         // NSWorkspace.
214         //
215         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
216         // that the GUI won't be activated (or raised) so there is a hack in
217         // MMWindowController which always raises the app when a new window is
218         // opened.
219         NSMutableArray *args = [NSMutableArray arrayWithObjects:
220             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
221         NSString *exeName = [[mainBundle infoDictionary]
222                 objectForKey:@"CFBundleExecutable"];
223         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
224         if (!path) {
225             NSLog(@"ERROR: Could not find MacVim executable in bundle");
226             return NO;
227         }
229         [NSTask launchedTaskWithLaunchPath:path arguments:args];
230 #endif
232         // HACK!  The NSWorkspaceDidLaunchApplicationNotification does not work
233         // for tasks like this, so poll the mach bootstrap server until it
234         // returns a valid connection.  Also set a time-out date so that we
235         // don't get stuck doing this forever.
236         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
237         while (!connection &&
238                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
239         {
240             [[NSRunLoop currentRunLoop]
241                     runMode:NSDefaultRunLoopMode
242                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
244             // NOTE: This call will set 'connection' as a side-effect.
245             [self connection];
246         }
248         if (!connection) {
249             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
250             return NO;
251         }
252     }
254     id proxy = [connection rootProxy];
255     [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
257     [[NSNotificationCenter defaultCenter] addObserver:self
258             selector:@selector(connectionDidDie:)
259                 name:NSConnectionDidDieNotification object:connection];
261     int pid = [[NSProcessInfo processInfo] processIdentifier];
263     @try {
264         frontendProxy = [proxy connectBackend:self pid:pid];
265     }
266     @catch (NSException *e) {
267         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
268     }
270     if (frontendProxy) {
271         [frontendProxy retain];
272         [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
273     }
275     return connection && frontendProxy;
278 - (BOOL)openVimWindow
280     [self queueMessage:OpenVimWindowMsgID data:nil];
281     return YES;
284 - (void)clearAll
286     int type = ClearAllDrawType;
288     // Any draw commands in queue are effectively obsolete since this clearAll
289     // will negate any effect they have, therefore we may as well clear the
290     // draw queue.
291     [drawData setLength:0];
293     [drawData appendBytes:&type length:sizeof(int)];
296 - (void)clearBlockFromRow:(int)row1 column:(int)col1
297                     toRow:(int)row2 column:(int)col2
299     int type = ClearBlockDrawType;
301     [drawData appendBytes:&type length:sizeof(int)];
303     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
304     [drawData appendBytes:&row1 length:sizeof(int)];
305     [drawData appendBytes:&col1 length:sizeof(int)];
306     [drawData appendBytes:&row2 length:sizeof(int)];
307     [drawData appendBytes:&col2 length:sizeof(int)];
310 - (void)deleteLinesFromRow:(int)row count:(int)count
311               scrollBottom:(int)bottom left:(int)left right:(int)right
313     int type = DeleteLinesDrawType;
315     [drawData appendBytes:&type length:sizeof(int)];
317     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
318     [drawData appendBytes:&row length:sizeof(int)];
319     [drawData appendBytes:&count length:sizeof(int)];
320     [drawData appendBytes:&bottom length:sizeof(int)];
321     [drawData appendBytes:&left length:sizeof(int)];
322     [drawData appendBytes:&right length:sizeof(int)];
325 - (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
326                 flags:(int)flags
328     if (len <= 0) return;
330     int type = ReplaceStringDrawType;
332     [drawData appendBytes:&type length:sizeof(int)];
334     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
335     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
336     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
337     [drawData appendBytes:&row length:sizeof(int)];
338     [drawData appendBytes:&col length:sizeof(int)];
339     [drawData appendBytes:&flags length:sizeof(int)];
340     [drawData appendBytes:&len length:sizeof(int)];
341     [drawData appendBytes:s length:len];
344 - (void)insertLinesFromRow:(int)row count:(int)count
345               scrollBottom:(int)bottom left:(int)left right:(int)right
347     int type = InsertLinesDrawType;
349     [drawData appendBytes:&type length:sizeof(int)];
351     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
352     [drawData appendBytes:&row length:sizeof(int)];
353     [drawData appendBytes:&count length:sizeof(int)];
354     [drawData appendBytes:&bottom length:sizeof(int)];
355     [drawData appendBytes:&left length:sizeof(int)];
356     [drawData appendBytes:&right length:sizeof(int)];
359 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
360                fraction:(int)percent color:(int)color
362     int type = DrawCursorDrawType;
363     unsigned uc = MM_COLOR(color);
365     [drawData appendBytes:&type length:sizeof(int)];
367     [drawData appendBytes:&uc length:sizeof(unsigned)];
368     [drawData appendBytes:&row length:sizeof(int)];
369     [drawData appendBytes:&col length:sizeof(int)];
370     [drawData appendBytes:&shape length:sizeof(int)];
371     [drawData appendBytes:&percent length:sizeof(int)];
374 - (void)flushQueue:(BOOL)force
376     // NOTE! This method gets called a lot; if we were to flush every time it
377     // was called MacVim would feel unresponsive.  So there is a time out which
378     // ensures that the queue isn't flushed too often.
379     if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
380             < MMFlushTimeoutInterval)
381         return;
383     if ([drawData length] > 0) {
384         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
385         [drawData setLength:0];
386     }
388     if ([queue count] > 0) {
389         @try {
390             [frontendProxy processCommandQueue:queue];
391         }
392         @catch (NSException *e) {
393             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
394         }
396         [queue removeAllObjects];
398         [lastFlushDate release];
399         lastFlushDate = [[NSDate date] retain];
400     }
403 - (BOOL)waitForInput:(int)milliseconds
405     NSDate *date = milliseconds > 0 ?
406             [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
407             [NSDate distantFuture];
409     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
411     // I know of no way to figure out if the run loop exited because input was
412     // found or because of a time out, so I need to manually indicate when
413     // input was received in processInput:data: and then reset it every time
414     // here.
415     BOOL yn = inputReceived;
416     inputReceived = NO;
418     return yn;
421 - (void)exit
423 #ifdef MAC_CLIENTSERVER
424     // The default connection is used for the client/server code.
425     [[NSConnection defaultConnection] setRootObject:nil];
426     [[NSConnection defaultConnection] invalidate];
427 #endif
429     // By invalidating the NSConnection the MMWindowController immediately
430     // finds out that the connection is down and as a result
431     // [MMWindowController connectionDidDie:] is invoked.
432     //NSLog(@"%@ %s", [self className], _cmd);
433     [[NSNotificationCenter defaultCenter] removeObserver:self];
434     [connection invalidate];
436     if (fontContainerRef) {
437         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
438         fontContainerRef = 0;
439     }
443 - (void)selectTab:(int)index
445     //NSLog(@"%s%d", _cmd, index);
447     index -= 1;
448     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
449     [self queueMessage:SelectTabMsgID data:data];
452 - (void)updateTabBar
454     //NSLog(@"%s", _cmd);
456     NSMutableData *data = [NSMutableData data];
458     int idx = tabpage_index(curtab) - 1;
459     [data appendBytes:&idx length:sizeof(int)];
461     tabpage_T *tp;
462     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
463         // This function puts the label of the tab in the global 'NameBuff'.
464         get_tabline_label(tp, FALSE);
465         char_u *s = NameBuff;
466         int len = STRLEN(s);
467         if (len <= 0) continue;
469 #if MM_ENABLE_CONV
470         s = CONVERT_TO_UTF8(s);
471 #endif
473         // Count the number of windows in the tabpage.
474         //win_T *wp = tp->tp_firstwin;
475         //int wincount;
476         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
478         //[data appendBytes:&wincount length:sizeof(int)];
479         [data appendBytes:&len length:sizeof(int)];
480         [data appendBytes:s length:len];
482 #if MM_ENABLE_CONV
483         CONVERT_TO_UTF8_FREE(s);
484 #endif
485     }
487     [self queueMessage:UpdateTabBarMsgID data:data];
490 - (BOOL)tabBarVisible
492     return tabBarVisible;
495 - (void)showTabBar:(BOOL)enable
497     tabBarVisible = enable;
499     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
500     [self queueMessage:msgid data:nil];
503 - (void)setRows:(int)rows columns:(int)cols
505     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
507     int dim[] = { rows, cols };
508     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
510     [self queueMessage:SetTextDimensionsMsgID data:data];
513 - (void)setWindowTitle:(char *)title
515     NSMutableData *data = [NSMutableData data];
516     int len = strlen(title);
517     if (len <= 0) return;
519     [data appendBytes:&len length:sizeof(int)];
520     [data appendBytes:title length:len];
522     [self queueMessage:SetWindowTitleMsgID data:data];
525 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
526                             saving:(int)saving
528     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
529     //        saving);
531     char_u *s = NULL;
532     NSString *ds = dir ? [NSString stringWithUTF8String:dir] : nil;
533     NSString *ts = title ? [NSString stringWithUTF8String:title] : nil;
534     @try {
535         [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
537         // Wait until a reply is sent from MMVimController.
538         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
539                                  beforeDate:[NSDate distantFuture]];
541         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
542             char_u *ret = (char_u*)[dialogReturn UTF8String];
543 #if MM_ENABLE_CONV
544             ret = CONVERT_FROM_UTF8(ret);
545 #endif
546             s = vim_strsave(ret);
547 #if MM_ENABLE_CONV
548             CONVERT_FROM_UTF8_FREE(ret);
549 #endif
550         }
552         [dialogReturn release];  dialogReturn = nil;
553     }
554     @catch (NSException *e) {
555         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
556     }
558     return (char *)s;
561 - (oneway void)setDialogReturn:(in bycopy id)obj
563     // NOTE: This is called by
564     //   - [MMVimController panelDidEnd:::], and
565     //   - [MMVimController alertDidEnd:::],
566     // to indicate that a save/open panel or alert has finished.
568     if (obj != dialogReturn) {
569         [dialogReturn release];
570         dialogReturn = [obj retain];
571     }
574 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
575                      buttons:(char *)btns textField:(char *)txtfield
577     int retval = 0;
578     NSString *message = nil, *text = nil, *textFieldString = nil;
579     NSArray *buttons = nil;
580     int style = NSInformationalAlertStyle;
582     if (VIM_WARNING == type) style = NSWarningAlertStyle;
583     else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
585     if (btns) {
586         NSString *btnString = [NSString stringWithUTF8String:btns];
587         buttons = [btnString componentsSeparatedByString:@"\n"];
588     }
589     if (title)
590         message = [NSString stringWithUTF8String:title];
591     if (msg) {
592         text = [NSString stringWithUTF8String:msg];
593         if (!message) {
594             // HACK! If there is a '\n\n' or '\n' sequence in the message, then
595             // make the part up to there into the title.  We only do this
596             // because Vim has lots of dialogs without a title and they look
597             // ugly that way.
598             // TODO: Fix the actual dialog texts.
599             NSRange eolRange = [text rangeOfString:@"\n\n"];
600             if (NSNotFound == eolRange.location)
601                 eolRange = [text rangeOfString:@"\n"];
602             if (NSNotFound != eolRange.location) {
603                 message = [text substringToIndex:eolRange.location];
604                 text = [text substringFromIndex:NSMaxRange(eolRange)];
605             }
606         }
607     }
608     if (txtfield)
609         textFieldString = [NSString stringWithUTF8String:txtfield];
611     @try {
612         [frontendProxy presentDialogWithStyle:style message:message
613                               informativeText:text buttonTitles:buttons
614                               textFieldString:textFieldString];
616         // Wait until a reply is sent from MMVimController.
617         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
618                                  beforeDate:[NSDate distantFuture]];
620         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
621                 && [dialogReturn count]) {
622             retval = [[dialogReturn objectAtIndex:0] intValue];
623             if (txtfield && [dialogReturn count] > 1) {
624                 NSString *retString = [dialogReturn objectAtIndex:1];
625                 char_u *ret = (char_u*)[retString UTF8String];
626 #if MM_ENABLE_CONV
627                 ret = CONVERT_FROM_UTF8(ret);
628 #endif
629                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
630 #if MM_ENABLE_CONV
631                 CONVERT_FROM_UTF8_FREE(ret);
632 #endif
633             }
634         }
636         [dialogReturn release]; dialogReturn = nil;
637     }
638     @catch (NSException *e) {
639         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
640     }
642     return retval;
645 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
646                atIndex:(int)index
648     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
649     //        name, index);
651     int namelen = name ? strlen(name) : 0;
652     NSMutableData *data = [NSMutableData data];
654     [data appendBytes:&tag length:sizeof(int)];
655     [data appendBytes:&parentTag length:sizeof(int)];
656     [data appendBytes:&namelen length:sizeof(int)];
657     if (namelen > 0) [data appendBytes:name length:namelen];
658     [data appendBytes:&index length:sizeof(int)];
660     [self queueMessage:AddMenuMsgID data:data];
663 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
664                        tip:(char *)tip icon:(char *)icon
665              keyEquivalent:(int)key modifiers:(int)mods
666                     action:(NSString *)action atIndex:(int)index
668     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
669     //        parentTag, name, tip, index);
671     int namelen = name ? strlen(name) : 0;
672     int tiplen = tip ? strlen(tip) : 0;
673     int iconlen = icon ? strlen(icon) : 0;
674     int eventFlags = vimModMaskToEventModifierFlags(mods);
675     int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
676     NSMutableData *data = [NSMutableData data];
678     key = specialKeyToNSKey(key);
680     [data appendBytes:&tag length:sizeof(int)];
681     [data appendBytes:&parentTag length:sizeof(int)];
682     [data appendBytes:&namelen length:sizeof(int)];
683     if (namelen > 0) [data appendBytes:name length:namelen];
684     [data appendBytes:&tiplen length:sizeof(int)];
685     if (tiplen > 0) [data appendBytes:tip length:tiplen];
686     [data appendBytes:&iconlen length:sizeof(int)];
687     if (iconlen > 0) [data appendBytes:icon length:iconlen];
688     [data appendBytes:&actionlen length:sizeof(int)];
689     if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
690     [data appendBytes:&index length:sizeof(int)];
691     [data appendBytes:&key length:sizeof(int)];
692     [data appendBytes:&eventFlags length:sizeof(int)];
694     [self queueMessage:AddMenuItemMsgID data:data];
697 - (void)removeMenuItemWithTag:(int)tag
699     NSMutableData *data = [NSMutableData data];
700     [data appendBytes:&tag length:sizeof(int)];
702     [self queueMessage:RemoveMenuItemMsgID data:data];
705 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
707     NSMutableData *data = [NSMutableData data];
709     [data appendBytes:&tag length:sizeof(int)];
710     [data appendBytes:&enabled length:sizeof(int)];
712     [self queueMessage:EnableMenuItemMsgID data:data];
715 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
717     int len = strlen(name);
718     int row = -1, col = -1;
720     if (len <= 0) return;
722     if (!mouse && curwin) {
723         row = curwin->w_wrow;
724         col = curwin->w_wcol;
725     }
727     NSMutableData *data = [NSMutableData data];
729     [data appendBytes:&row length:sizeof(int)];
730     [data appendBytes:&col length:sizeof(int)];
731     [data appendBytes:&len length:sizeof(int)];
732     [data appendBytes:name length:len];
734     [self queueMessage:ShowPopupMenuMsgID data:data];
737 - (void)showToolbar:(int)enable flags:(int)flags
739     NSMutableData *data = [NSMutableData data];
741     [data appendBytes:&enable length:sizeof(int)];
742     [data appendBytes:&flags length:sizeof(int)];
744     [self queueMessage:ShowToolbarMsgID data:data];
747 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
749     NSMutableData *data = [NSMutableData data];
751     [data appendBytes:&ident length:sizeof(long)];
752     [data appendBytes:&type length:sizeof(int)];
754     [self queueMessage:CreateScrollbarMsgID data:data];
757 - (void)destroyScrollbarWithIdentifier:(long)ident
759     NSMutableData *data = [NSMutableData data];
760     [data appendBytes:&ident length:sizeof(long)];
762     [self queueMessage:DestroyScrollbarMsgID data:data];
765 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
767     NSMutableData *data = [NSMutableData data];
769     [data appendBytes:&ident length:sizeof(long)];
770     [data appendBytes:&visible length:sizeof(int)];
772     [self queueMessage:ShowScrollbarMsgID data:data];
775 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
777     NSMutableData *data = [NSMutableData data];
779     [data appendBytes:&ident length:sizeof(long)];
780     [data appendBytes:&pos length:sizeof(int)];
781     [data appendBytes:&len length:sizeof(int)];
783     [self queueMessage:SetScrollbarPositionMsgID data:data];
786 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
787                     identifier:(long)ident
789     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
790     float prop = (float)size/(max+1);
791     if (fval < 0) fval = 0;
792     else if (fval > 1.0f) fval = 1.0f;
793     if (prop < 0) prop = 0;
794     else if (prop > 1.0f) prop = 1.0f;
796     NSMutableData *data = [NSMutableData data];
798     [data appendBytes:&ident length:sizeof(long)];
799     [data appendBytes:&fval length:sizeof(float)];
800     [data appendBytes:&prop length:sizeof(float)];
802     [self queueMessage:SetScrollbarThumbMsgID data:data];
805 - (BOOL)setFontWithName:(char *)name
807     NSString *fontName = MMDefaultFontName;
808     float size = MMDefaultFontSize;
809     BOOL parseFailed = NO;
811     if (name) {
812         fontName = [NSString stringWithUTF8String:name];
814         if ([fontName isEqual:@"*"]) {
815             // :set gfn=* shows the font panel.
816             do_cmdline_cmd((char_u*)":action orderFrontFontPanel:");
817             return NO;
818         }
820         NSArray *components = [fontName componentsSeparatedByString:@":"];
821         if ([components count] == 2) {
822             NSString *sizeString = [components lastObject];
823             if ([sizeString length] > 0
824                     && [sizeString characterAtIndex:0] == 'h') {
825                 sizeString = [sizeString substringFromIndex:1];
826                 if ([sizeString length] > 0) {
827                     size = [sizeString floatValue];
828                     fontName = [components objectAtIndex:0];
829                 }
830             } else {
831                 parseFailed = YES;
832             }
833         } else if ([components count] > 2) {
834             parseFailed = YES;
835         }
836     }
838     if (!parseFailed && [fontName length] > 0) {
839         if (size < 6 || size > 100) {
840             // Font size 0.0 tells NSFont to use the 'user default size'.
841             size = 0.0f;
842         }
844         NSFont *font = [NSFont fontWithName:fontName size:size];
846         if (!font && MMDefaultFontName == fontName) {
847             // If for some reason the MacVim default font is not in the app
848             // bundle, then fall back on the system default font.
849             size = 0;
850             font = [NSFont userFixedPitchFontOfSize:size];
851             fontName = [font displayName];
852         }
854         if (font) {
855             //NSLog(@"Setting font '%@' of size %.2f", fontName, size);
856             int len = [fontName
857                     lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
858             if (len > 0) {
859                 NSMutableData *data = [NSMutableData data];
861                 [data appendBytes:&size length:sizeof(float)];
862                 [data appendBytes:&len length:sizeof(int)];
863                 [data appendBytes:[fontName UTF8String] length:len];
865                 [self queueMessage:SetFontMsgID data:data];
866                 return YES;
867             }
868         }
869     }
871     //NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f",
872     //        fontName, size);
873     return NO;
876 - (void)executeActionWithName:(NSString *)name
878     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
880     if (len > 0) {
881         NSMutableData *data = [NSMutableData data];
883         [data appendBytes:&len length:sizeof(int)];
884         [data appendBytes:[name UTF8String] length:len];
886         [self queueMessage:ExecuteActionMsgID data:data];
887     }
890 - (void)setMouseShape:(int)shape
892     NSMutableData *data = [NSMutableData data];
893     [data appendBytes:&shape length:sizeof(int)];
894     [self queueMessage:SetMouseShapeMsgID data:data];
897 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
899     // Vim specifies times in milliseconds, whereas Cocoa wants them in
900     // seconds.
901     blinkWaitInterval = .001f*wait;
902     blinkOnInterval = .001f*on;
903     blinkOffInterval = .001f*off;
906 - (void)startBlink
908     if (blinkTimer) {
909         [blinkTimer invalidate];
910         [blinkTimer release];
911         blinkTimer = nil;
912     }
914     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
915             && gui.in_focus) {
916         blinkState = MMBlinkStateOn;
917         blinkTimer =
918             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
919                                               target:self
920                                             selector:@selector(blinkTimerFired:)
921                                             userInfo:nil repeats:NO] retain];
922         gui_update_cursor(TRUE, FALSE);
923         [self flushQueue:YES];
924     }
927 - (void)stopBlink
929     if (MMBlinkStateOff == blinkState) {
930         gui_update_cursor(TRUE, FALSE);
931         [self flushQueue:YES];
932     }
934     blinkState = MMBlinkStateNone;
937 - (void)adjustLinespace:(int)linespace
939     NSMutableData *data = [NSMutableData data];
940     [data appendBytes:&linespace length:sizeof(int)];
941     [self queueMessage:AdjustLinespaceMsgID data:data];
944 - (void)activate
946     [self queueMessage:ActivateMsgID data:nil];
949 - (int)lookupColorWithKey:(NSString *)key
951     if (!(key && [key length] > 0))
952         return INVALCOLOR;
954     NSString *stripKey = [[[[key lowercaseString]
955         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
956             componentsSeparatedByString:@" "]
957                componentsJoinedByString:@""];
959     if (stripKey && [stripKey length] > 0) {
960         // First of all try to lookup key in the color dictionary; note that
961         // all keys in this dictionary are lowercase with no whitespace.
962         id obj = [colorDict objectForKey:stripKey];
963         if (obj) return [obj intValue];
965         // The key was not in the dictionary; is it perhaps of the form
966         // #rrggbb?
967         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
968             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
969             [scanner setScanLocation:1];
970             unsigned hex = 0;
971             if ([scanner scanHexInt:&hex]) {
972                 return (int)hex;
973             }
974         }
976         // As a last resort, check if it is one of the system defined colors.
977         // The keys in this dictionary are also lowercase with no whitespace.
978         obj = [sysColorDict objectForKey:stripKey];
979         if (obj) {
980             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
981             if (col) {
982                 float r, g, b, a;
983                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
984                 [col getRed:&r green:&g blue:&b alpha:&a];
985                 return (((int)(r*255+.5f) & 0xff) << 16)
986                      + (((int)(g*255+.5f) & 0xff) << 8)
987                      +  ((int)(b*255+.5f) & 0xff);
988             }
989         }
990     }
992     NSLog(@"WARNING: No color with key %@ found.", stripKey);
993     return INVALCOLOR;
996 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
998     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
999     id obj;
1001     while ((obj = [e nextObject])) {
1002         if ([value isEqual:obj])
1003             return YES;
1004     }
1006     return NO;
1009 - (void)enterFullscreen
1011     [self queueMessage:EnterFullscreenMsgID data:nil];
1014 - (void)leaveFullscreen
1016     [self queueMessage:LeaveFullscreenMsgID data:nil];
1019 - (oneway void)processInput:(int)msgid data:(in NSData *)data
1021     // NOTE: This method might get called whenever the run loop is tended to.
1022     // Thus it might get called whilst input is being processed.  Normally this
1023     // is not a problem, but if it gets called often then it might become
1024     // dangerous.  E.g. say a message causes the screen to be redrawn and then
1025     // another message is received causing another simultaneous screen redraw;
1026     // this is not good.  To deal with this problem at the moment, we simply
1027     // drop messages that are received while other input is being processed.
1028     if (inProcessInput) {
1029 #if MM_USE_INPUT_QUEUE
1030         [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1031         [inputQueue addObject:data];
1032 #else
1033         // Just drop the input
1034         //NSLog(@"WARNING: Dropping input in %s", _cmd);
1035 #endif
1036     } else {
1037         [self processInputBegin];
1038         [self handleMessage:msgid data:data];
1039         [self processInputEnd];
1040     }
1043 - (oneway void)processInputAndData:(in NSArray *)messages
1045     // NOTE: See comment in processInput:data:.
1046     unsigned i, count = [messages count];
1047     if (count % 2) {
1048         NSLog(@"WARNING: [messages count] is odd in %s", _cmd);
1049         return;
1050     }
1052     if (inProcessInput) {
1053 #if MM_USE_INPUT_QUEUE
1054         [inputQueue addObjectsFromArray:messages];
1055 #else
1056         // Just drop the input
1057         //NSLog(@"WARNING: Dropping input in %s", _cmd);
1058 #endif
1059     } else {
1060         [self processInputBegin];
1062         for (i = 0; i < count; i += 2) {
1063             int msgid = [[messages objectAtIndex:i] intValue];
1064             id data = [messages objectAtIndex:i+1];
1065             if ([data isEqual:[NSNull null]])
1066                 data = nil;
1068             [self handleMessage:msgid data:data];
1069         }
1071         [self processInputEnd];
1072     }
1075 - (BOOL)checkForModifiedBuffers
1077     buf_T *buf;
1078     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
1079         if (bufIsChanged(buf)) {
1080             return YES;
1081         }
1082     }
1084     return NO;
1087 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1089     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1090         // If there is no pasteboard, return YES to indicate that there is text
1091         // to copy.
1092         if (!pboard)
1093             return YES;
1095         clip_copy_selection();
1097         // Get the text to put on the pasteboard.
1098         long_u llen = 0; char_u *str = 0;
1099         int type = clip_convert_selection(&str, &llen, &clip_star);
1100         if (type < 0)
1101             return NO;
1102         
1103         // TODO: Avoid overflow.
1104         int len = (int)llen;
1105 #if MM_ENABLE_CONV
1106         if (output_conv.vc_type != CONV_NONE) {
1107             char_u *conv_str = string_convert(&output_conv, str, &len);
1108             if (conv_str) {
1109                 vim_free(str);
1110                 str = conv_str;
1111             }
1112         }
1113 #endif
1115         NSString *string = [[NSString alloc]
1116             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1118         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1119         [pboard declareTypes:types owner:nil];
1120         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1121     
1122         [string release];
1123         vim_free(str);
1125         return ok;
1126     }
1128     return NO;
1131 - (oneway void)addReply:(in bycopy NSString *)reply
1132                  server:(in byref id <MMVimServerProtocol>)server
1134     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1136     // Replies might come at any time and in any order so we keep them in an
1137     // array inside a dictionary with the send port used as key.
1139     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1140     // HACK! Assume connection uses mach ports.
1141     int port = [(NSMachPort*)[conn sendPort] machPort];
1142     NSNumber *key = [NSNumber numberWithInt:port];
1144     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1145     if (!replies) {
1146         replies = [NSMutableArray array];
1147         [serverReplyDict setObject:replies forKey:key];
1148     }
1150     [replies addObject:reply];
1153 - (void)addInput:(in bycopy NSString *)input
1154                  client:(in byref id <MMVimClientProtocol>)client
1156     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1158     char_u *s = (char_u*)[input UTF8String];
1160 #if MM_ENABLE_CONV
1161     s = CONVERT_FROM_UTF8(s);
1162 #endif
1164     server_to_input_buf(s);
1166 #if MM_ENABLE_CONV
1167     CONVERT_FROM_UTF8_FREE(s);
1168 #endif
1170     [self addClient:(id)client];
1172     inputReceived = YES;
1175 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1176                  client:(in byref id <MMVimClientProtocol>)client
1178     //NSLog(@"evaluateExpression:%@ client:%@", expr, (id)client);
1180     NSString *eval = nil;
1181     char_u *s = (char_u*)[expr UTF8String];
1183 #if MM_ENABLE_CONV
1184     s = CONVERT_FROM_UTF8(s);
1185 #endif
1187     char_u *res = eval_client_expr_to_string(s);
1189 #if MM_ENABLE_CONV
1190     CONVERT_FROM_UTF8_FREE(s);
1191 #endif
1193     if (res != NULL) {
1194         s = res;
1195 #if MM_ENABLE_CONV
1196         s = CONVERT_TO_UTF8(s);
1197 #endif
1198         eval = [NSString stringWithUTF8String:(char*)s];
1199 #if MM_ENABLE_CONV
1200         CONVERT_TO_UTF8_FREE(s);
1201 #endif
1202         vim_free(res);
1203     }
1205     [self addClient:(id)client];
1207     return eval;
1210 - (void)registerServerWithName:(NSString *)name
1212     NSString *svrName = name;
1213     NSConnection *svrConn = [NSConnection defaultConnection];
1214     unsigned i;
1216     for (i = 0; i < MMServerMax; ++i) {
1217         NSString *connName = [self connectionNameFromServerName:svrName];
1219         if ([svrConn registerName:connName]) {
1220             //NSLog(@"Registered server with name: %@", svrName);
1222             // TODO: Set request/reply time-outs to something else?
1223             //
1224             // Don't wait for requests (time-out means that the message is
1225             // dropped).
1226             [svrConn setRequestTimeout:0];
1227             //[svrConn setReplyTimeout:MMReplyTimeout];
1228             [svrConn setRootObject:self];
1230             char_u *s = (char_u*)[svrName UTF8String];
1231 #if MM_ENABLE_CONV
1232             s = CONVERT_FROM_UTF8(s);
1233 #endif
1234             // NOTE: 'serverName' is a global variable
1235             serverName = vim_strsave(s);
1236 #if MM_ENABLE_CONV
1237             CONVERT_FROM_UTF8_FREE(s);
1238 #endif
1239 #ifdef FEAT_EVAL
1240             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1241 #endif
1242 #ifdef FEAT_TITLE
1243             need_maketitle = TRUE;
1244 #endif
1245             [self queueMessage:SetServerNameMsgID data:
1246                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1247             break;
1248         }
1250         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1251     }
1254 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1255                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1256               silent:(BOOL)silent
1258     // NOTE: If 'name' equals 'serverName' then the request is local (client
1259     // and server are the same).  This case is not handled separately, so a
1260     // connection will be set up anyway (this simplifies the code).
1262     NSConnection *conn = [self connectionForServerName:name];
1263     if (!conn) {
1264         if (!silent) {
1265             char_u *s = (char_u*)[name UTF8String];
1266 #if MM_ENABLE_CONV
1267             s = CONVERT_FROM_UTF8(s);
1268 #endif
1269             EMSG2(_(e_noserver), s);
1270 #if MM_ENABLE_CONV
1271             CONVERT_FROM_UTF8_FREE(s);
1272 #endif
1273         }
1274         return NO;
1275     }
1277     if (port) {
1278         // HACK! Assume connection uses mach ports.
1279         *port = [(NSMachPort*)[conn sendPort] machPort];
1280     }
1282     id proxy = [conn rootProxy];
1283     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1285     @try {
1286         if (expr) {
1287             NSString *eval = [proxy evaluateExpression:string client:self];
1288             if (reply) {
1289                 if (eval) {
1290                     char_u *r = (char_u*)[eval UTF8String];
1291 #if MM_ENABLE_CONV
1292                     r = CONVERT_FROM_UTF8(r);
1293 #endif
1294                     *reply = vim_strsave(r);
1295 #if MM_ENABLE_CONV
1296                     CONVERT_FROM_UTF8_FREE(r);
1297 #endif
1298                 } else {
1299                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1300                 }
1301             }
1303             if (!eval)
1304                 return NO;
1305         } else {
1306             [proxy addInput:string client:self];
1307         }
1308     }
1309     @catch (NSException *e) {
1310         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1311         return NO;
1312     }
1314     return YES;
1317 - (NSArray *)serverList
1319     NSArray *list = nil;
1321     if ([self connection]) {
1322         id proxy = [connection rootProxy];
1323         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1325         @try {
1326             list = [proxy serverList];
1327         }
1328         @catch (NSException *e) {
1329             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1330         }
1331     } else {
1332         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1333     }
1335     return list;
1338 - (NSString *)peekForReplyOnPort:(int)port
1340     //NSLog(@"%s%d", _cmd, port);
1342     NSNumber *key = [NSNumber numberWithInt:port];
1343     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1344     if (replies && [replies count]) {
1345         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1346         //        [replies objectAtIndex:0]);
1347         return [replies objectAtIndex:0];
1348     }
1350     //NSLog(@"    No replies");
1351     return nil;
1354 - (NSString *)waitForReplyOnPort:(int)port
1356     //NSLog(@"%s%d", _cmd, port);
1357     
1358     NSConnection *conn = [self connectionForServerPort:port];
1359     if (!conn)
1360         return nil;
1362     NSNumber *key = [NSNumber numberWithInt:port];
1363     NSMutableArray *replies = nil;
1364     NSString *reply = nil;
1366     // Wait for reply as long as the connection to the server is valid (unless
1367     // user interrupts wait with Ctrl-C).
1368     while (!got_int && [conn isValid] &&
1369             !(replies = [serverReplyDict objectForKey:key])) {
1370         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1371                                  beforeDate:[NSDate distantFuture]];
1372     }
1374     if (replies) {
1375         if ([replies count] > 0) {
1376             reply = [[replies objectAtIndex:0] retain];
1377             //NSLog(@"    Got reply: %@", reply);
1378             [replies removeObjectAtIndex:0];
1379             [reply autorelease];
1380         }
1382         if ([replies count] == 0)
1383             [serverReplyDict removeObjectForKey:key];
1384     }
1386     return reply;
1389 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1391     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1392     if (client) {
1393         @try {
1394             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1395             [client addReply:reply server:self];
1396             return YES;
1397         }
1398         @catch (NSException *e) {
1399             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1400         }
1401     } else {
1402         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1403     }
1405     return NO;
1408 @end // MMBackend
1412 @implementation MMBackend (Private)
1414 - (void)handleMessage:(int)msgid data:(NSData *)data
1416     if (InsertTextMsgID == msgid) {
1417         [self handleInsertText:data];
1418     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1419         if (!data) return;
1420         const void *bytes = [data bytes];
1421         int mods = *((int*)bytes);  bytes += sizeof(int);
1422         int len = *((int*)bytes);  bytes += sizeof(int);
1423         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1424                                               encoding:NSUTF8StringEncoding];
1425         mods = eventModifierFlagsToVimModMask(mods);
1427         [self handleKeyDown:key modifiers:mods];
1429         [key release];
1430     } else if (SelectTabMsgID == msgid) {
1431         if (!data) return;
1432         const void *bytes = [data bytes];
1433         int idx = *((int*)bytes) + 1;
1434         //NSLog(@"Selecting tab %d", idx);
1435         send_tabline_event(idx);
1436     } else if (CloseTabMsgID == msgid) {
1437         if (!data) return;
1438         const void *bytes = [data bytes];
1439         int idx = *((int*)bytes) + 1;
1440         //NSLog(@"Closing tab %d", idx);
1441         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1442     } else if (AddNewTabMsgID == msgid) {
1443         //NSLog(@"Adding new tab");
1444         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1445     } else if (DraggedTabMsgID == msgid) {
1446         if (!data) return;
1447         const void *bytes = [data bytes];
1448         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1449         // based.
1450         int idx = *((int*)bytes);
1452         tabpage_move(idx);
1453     } else if (ScrollWheelMsgID == msgid) {
1454         if (!data) return;
1455         const void *bytes = [data bytes];
1457         int row = *((int*)bytes);  bytes += sizeof(int);
1458         int col = *((int*)bytes);  bytes += sizeof(int);
1459         int flags = *((int*)bytes);  bytes += sizeof(int);
1460         float dy = *((float*)bytes);  bytes += sizeof(float);
1462         int button = MOUSE_5;
1463         if (dy > 0) button = MOUSE_4;
1465         flags = eventModifierFlagsToVimMouseModMask(flags);
1467         gui_send_mouse_event(button, col, row, NO, flags);
1468     } else if (MouseDownMsgID == msgid) {
1469         if (!data) return;
1470         const void *bytes = [data bytes];
1472         int row = *((int*)bytes);  bytes += sizeof(int);
1473         int col = *((int*)bytes);  bytes += sizeof(int);
1474         int button = *((int*)bytes);  bytes += sizeof(int);
1475         int flags = *((int*)bytes);  bytes += sizeof(int);
1476         int count = *((int*)bytes);  bytes += sizeof(int);
1478         button = eventButtonNumberToVimMouseButton(button);
1479         flags = eventModifierFlagsToVimMouseModMask(flags);
1481         gui_send_mouse_event(button, col, row, count>1, flags);
1482     } else if (MouseUpMsgID == msgid) {
1483         if (!data) return;
1484         const void *bytes = [data bytes];
1486         int row = *((int*)bytes);  bytes += sizeof(int);
1487         int col = *((int*)bytes);  bytes += sizeof(int);
1488         int flags = *((int*)bytes);  bytes += sizeof(int);
1490         flags = eventModifierFlagsToVimMouseModMask(flags);
1492         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1493     } else if (MouseDraggedMsgID == msgid) {
1494         if (!data) return;
1495         const void *bytes = [data bytes];
1497         int row = *((int*)bytes);  bytes += sizeof(int);
1498         int col = *((int*)bytes);  bytes += sizeof(int);
1499         int flags = *((int*)bytes);  bytes += sizeof(int);
1501         flags = eventModifierFlagsToVimMouseModMask(flags);
1503         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1504     } else if (SetTextDimensionsMsgID == msgid) {
1505         if (!data) return;
1506         const void *bytes = [data bytes];
1507         int rows = *((int*)bytes);  bytes += sizeof(int);
1508         int cols = *((int*)bytes);  bytes += sizeof(int);
1510         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1511         // gui_resize_shell(), so we have to manually set the rows and columns
1512         // here.  (MacVim doesn't change the rows and columns to avoid
1513         // inconsistent states between Vim and MacVim.)
1514         [self setRows:rows columns:cols];
1516         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1517         gui_resize_shell(cols, rows);
1518     } else if (ExecuteMenuMsgID == msgid) {
1519         if (!data) return;
1520         const void *bytes = [data bytes];
1521         int tag = *((int*)bytes);  bytes += sizeof(int);
1523         vimmenu_T *menu = (vimmenu_T*)tag;
1524         // TODO!  Make sure 'menu' is a valid menu pointer!
1525         if (menu) {
1526             gui_menu_cb(menu);
1527         }
1528     } else if (ToggleToolbarMsgID == msgid) {
1529         [self handleToggleToolbar];
1530     } else if (ScrollbarEventMsgID == msgid) {
1531         [self handleScrollbarEvent:data];
1532     } else if (SetFontMsgID == msgid) {
1533         [self handleSetFont:data];
1534     } else if (VimShouldCloseMsgID == msgid) {
1535         gui_shell_closed();
1536     } else if (DropFilesMsgID == msgid) {
1537         [self handleDropFiles:data];
1538     } else if (DropStringMsgID == msgid) {
1539         [self handleDropString:data];
1540     } else if (GotFocusMsgID == msgid) {
1541         if (!gui.in_focus)
1542             [self focusChange:YES];
1543     } else if (LostFocusMsgID == msgid) {
1544         if (gui.in_focus)
1545             [self focusChange:NO];
1546     } else if (MouseMovedMsgID == msgid) {
1547         const void *bytes = [data bytes];
1548         int row = *((int*)bytes);  bytes += sizeof(int);
1549         int col = *((int*)bytes);  bytes += sizeof(int);
1551         gui_mouse_moved(col, row);
1552     } else if (SetMouseShapeMsgID == msgid) {
1553         const void *bytes = [data bytes];
1554         int shape = *((int*)bytes);  bytes += sizeof(int);
1555         update_mouseshape(shape);
1556     } else {
1557         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1558     }
1561 + (NSDictionary *)specialKeys
1563     static NSDictionary *specialKeys = nil;
1565     if (!specialKeys) {
1566         NSBundle *mainBundle = [NSBundle mainBundle];
1567         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1568                                               ofType:@"plist"];
1569         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1570     }
1572     return specialKeys;
1575 - (void)handleInsertText:(NSData *)data
1577     if (!data) return;
1579     NSString *key = [[NSString alloc] initWithData:data
1580                                           encoding:NSUTF8StringEncoding];
1581     char_u *str = (char_u*)[key UTF8String];
1582     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1584 #if MM_ENABLE_CONV
1585     char_u *conv_str = NULL;
1586     if (input_conv.vc_type != CONV_NONE) {
1587         conv_str = string_convert(&input_conv, str, &len);
1588         if (conv_str)
1589             str = conv_str;
1590     }
1591 #endif
1593     for (i = 0; i < len; ++i) {
1594         add_to_input_buf(str+i, 1);
1595         if (CSI == str[i]) {
1596             // NOTE: If the converted string contains the byte CSI, then it
1597             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1598             // won't work.
1599             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1600             add_to_input_buf(extra, 2);
1601         }
1602     }
1604 #if MM_ENABLE_CONV
1605     if (conv_str)
1606         vim_free(conv_str);
1607 #endif
1608     [key release];
1611 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1613     char_u special[3];
1614     char_u modChars[3];
1615     char_u *chars = (char_u*)[key UTF8String];
1616 #if MM_ENABLE_CONV
1617     char_u *conv_str = NULL;
1618 #endif
1619     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1621     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1622     // that new keys can easily be added.
1623     NSString *specialString = [[MMBackend specialKeys]
1624             objectForKey:key];
1625     if (specialString && [specialString length] > 1) {
1626         //NSLog(@"special key: %@", specialString);
1627         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1628                 [specialString characterAtIndex:1]);
1630         ikey = simplify_key(ikey, &mods);
1631         if (ikey == CSI)
1632             ikey = K_CSI;
1634         special[0] = CSI;
1635         special[1] = K_SECOND(ikey);
1636         special[2] = K_THIRD(ikey);
1638         chars = special;
1639         length = 3;
1640     } else if (1 == length && TAB == chars[0]) {
1641         // Tab is a trouble child:
1642         // - <Tab> is added to the input buffer as is
1643         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1644         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1645         //   to be converted to utf-8
1646         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1647         // - <C-Tab> is reserved by Mac OS X
1648         // - <D-Tab> is reserved by Mac OS X
1649         chars = special;
1650         special[0] = TAB;
1651         length = 1;
1653         if (mods & MOD_MASK_SHIFT) {
1654             mods &= ~MOD_MASK_SHIFT;
1655             special[0] = CSI;
1656             special[1] = K_SECOND(K_S_TAB);
1657             special[2] = K_THIRD(K_S_TAB);
1658             length = 3;
1659         } else if (mods & MOD_MASK_ALT) {
1660             int mtab = 0x80 | TAB;
1661             if (enc_utf8) {
1662                 // Convert to utf-8
1663                 special[0] = (mtab >> 6) + 0xc0;
1664                 special[1] = mtab & 0xbf;
1665                 length = 2;
1666             } else {
1667                 special[0] = mtab;
1668                 length = 1;
1669             }
1670             mods &= ~MOD_MASK_ALT;
1671         }
1672     } else if (length > 0) {
1673         unichar c = [key characterAtIndex:0];
1675         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1676         //        [key characterAtIndex:0], mods);
1678         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1679                 || (c == intr_char && intr_char != Ctrl_C))) {
1680             trash_input_buf();
1681             got_int = TRUE;
1682         }
1684         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1685         // cleared since they are already added to the key by the AppKit.
1686         // Unfortunately, the only way to deal with when to clear the modifiers
1687         // or not seems to be to have hard-wired rules like this.
1688         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1689                     || 0x9 == c || 0xd == c) ) {
1690             mods &= ~MOD_MASK_SHIFT;
1691             mods &= ~MOD_MASK_CTRL;
1692             //NSLog(@"clear shift ctrl");
1693         }
1695         // HACK!  All Option+key presses go via 'insert text' messages, except
1696         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1697         // not work to map to it.
1698         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1699             //NSLog(@"clear alt");
1700             mods &= ~MOD_MASK_ALT;
1701         }
1703 #if MM_ENABLE_CONV
1704         if (input_conv.vc_type != CONV_NONE) {
1705             conv_str = string_convert(&input_conv, chars, &length);
1706             if (conv_str)
1707                 chars = conv_str;
1708         }
1709 #endif
1710     }
1712     if (chars && length > 0) {
1713         if (mods) {
1714             //NSLog(@"adding mods: %d", mods);
1715             modChars[0] = CSI;
1716             modChars[1] = KS_MODIFIER;
1717             modChars[2] = mods;
1718             add_to_input_buf(modChars, 3);
1719         }
1721         //NSLog(@"add to input buf: 0x%x", chars[0]);
1722         // TODO: Check for CSI bytes?
1723         add_to_input_buf(chars, length);
1724     }
1726 #if MM_ENABLE_CONV
1727     if (conv_str)
1728         vim_free(conv_str);
1729 #endif
1732 - (void)queueMessage:(int)msgid data:(NSData *)data
1734     [queue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1735     if (data)
1736         [queue addObject:data];
1737     else
1738         [queue addObject:[NSData data]];
1741 - (void)connectionDidDie:(NSNotification *)notification
1743     // If the main connection to MacVim is lost this means that MacVim was
1744     // either quit (by the user chosing Quit on the MacVim menu), or it has
1745     // crashed.  In either case our only option is to quit now.
1746     // TODO: Write backup file?
1748     //NSLog(@"A Vim process lots its connection to MacVim; quitting.");
1749     getout(0);
1752 - (void)blinkTimerFired:(NSTimer *)timer
1754     NSTimeInterval timeInterval = 0;
1756     [blinkTimer release];
1757     blinkTimer = nil;
1759     if (MMBlinkStateOn == blinkState) {
1760         gui_undraw_cursor();
1761         blinkState = MMBlinkStateOff;
1762         timeInterval = blinkOffInterval;
1763     } else if (MMBlinkStateOff == blinkState) {
1764         gui_update_cursor(TRUE, FALSE);
1765         blinkState = MMBlinkStateOn;
1766         timeInterval = blinkOnInterval;
1767     }
1769     if (timeInterval > 0) {
1770         blinkTimer = 
1771             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1772                                             selector:@selector(blinkTimerFired:)
1773                                             userInfo:nil repeats:NO] retain];
1774         [self flushQueue:YES];
1775     }
1778 - (void)focusChange:(BOOL)on
1780     gui_focus_change(on);
1783 - (void)processInputBegin
1785     inProcessInput = YES;
1787     // Don't flush too soon or update speed will suffer.
1788     [lastFlushDate release];
1789     lastFlushDate = [[NSDate date] retain];
1792 - (void)processInputEnd
1794 #if MM_USE_INPUT_QUEUE
1795     int count = [inputQueue count];
1796     if (count % 2) {
1797         // TODO: This is troubling, but it is not hard to get Vim to end up
1798         // here.  Why does this happen?
1799         NSLog(@"WARNING: inputQueue has odd number of objects (%d)", count);
1800         [inputQueue removeAllObjects];
1801     } else if (count > 0) {
1802         // TODO: Dispatch these messages?  Maybe not; usually when the
1803         // 'inputQueue' is non-empty it means that a LOT of messages has been
1804         // sent simultaneously.  The only way this happens is when Vim is being
1805         // tormented, e.g. if the user holds down <D-`> to rapidly switch
1806         // windows.
1807         unsigned i;
1808         for (i = 0; i < count; i+=2) {
1809             int msgid = [[inputQueue objectAtIndex:i] intValue];
1810             NSLog(@"%s: Dropping message %s", _cmd, MessageStrings[msgid]);
1811         }
1813         [inputQueue removeAllObjects];
1814     }
1815 #endif
1817     inputReceived = YES;
1818     inProcessInput = NO;
1821 - (void)handleToggleToolbar
1823     // If 'go' contains 'T', then remove it, else add it.
1825     char_u go[sizeof(GO_ALL)+2];
1826     char_u *p;
1827     int len;
1829     STRCPY(go, p_go);
1830     p = vim_strchr(go, GO_TOOLBAR);
1831     len = STRLEN(go);
1833     if (p != NULL) {
1834         char_u *end = go + len;
1835         while (p < end) {
1836             p[0] = p[1];
1837             ++p;
1838         }
1839     } else {
1840         go[len] = GO_TOOLBAR;
1841         go[len+1] = NUL;
1842     }
1844     set_option_value((char_u*)"guioptions", 0, go, 0);
1846     // Force screen redraw (does it have to be this complicated?).
1847     redraw_all_later(CLEAR);
1848     update_screen(NOT_VALID);
1849     setcursor();
1850     out_flush();
1851     gui_update_cursor(FALSE, FALSE);
1852     gui_mch_flush();
1855 - (void)handleScrollbarEvent:(NSData *)data
1857     if (!data) return;
1859     const void *bytes = [data bytes];
1860     long ident = *((long*)bytes);  bytes += sizeof(long);
1861     int hitPart = *((int*)bytes);  bytes += sizeof(int);
1862     float fval = *((float*)bytes);  bytes += sizeof(float);
1863     scrollbar_T *sb = gui_find_scrollbar(ident);
1865     if (sb) {
1866         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1867         long value = sb_info->value;
1868         long size = sb_info->size;
1869         long max = sb_info->max;
1870         BOOL isStillDragging = NO;
1871         BOOL updateKnob = YES;
1873         switch (hitPart) {
1874         case NSScrollerDecrementPage:
1875             value -= (size > 2 ? size - 2 : 1);
1876             break;
1877         case NSScrollerIncrementPage:
1878             value += (size > 2 ? size - 2 : 1);
1879             break;
1880         case NSScrollerDecrementLine:
1881             --value;
1882             break;
1883         case NSScrollerIncrementLine:
1884             ++value;
1885             break;
1886         case NSScrollerKnob:
1887             isStillDragging = YES;
1888             // fall through ...
1889         case NSScrollerKnobSlot:
1890             value = (long)(fval * (max - size + 1));
1891             // fall through ...
1892         default:
1893             updateKnob = NO;
1894             break;
1895         }
1897         //NSLog(@"value %d -> %d", sb_info->value, value);
1898         gui_drag_scrollbar(sb, value, isStillDragging);
1900         if (updateKnob) {
1901             // Dragging the knob or option+clicking automatically updates
1902             // the knob position (on the actual NSScroller), so we only
1903             // need to set the knob position in the other cases.
1904             if (sb->wp) {
1905                 // Update both the left&right vertical scrollbars.
1906                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1907                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1908                 [self setScrollbarThumbValue:value size:size max:max
1909                                   identifier:identLeft];
1910                 [self setScrollbarThumbValue:value size:size max:max
1911                                   identifier:identRight];
1912             } else {
1913                 // Update the horizontal scrollbar.
1914                 [self setScrollbarThumbValue:value size:size max:max
1915                                   identifier:ident];
1916             }
1917         }
1918     }
1921 - (void)handleSetFont:(NSData *)data
1923     if (!data) return;
1925     const void *bytes = [data bytes];
1926     float pointSize = *((float*)bytes);  bytes += sizeof(float);
1927     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1928     bytes += sizeof(unsigned);  // len not used
1930     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1931     [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1932     char_u *s = (char_u*)[name UTF8String];
1934 #if MM_ENABLE_CONV
1935     s = CONVERT_FROM_UTF8(s);
1936 #endif
1938     set_option_value((char_u*)"guifont", 0, s, 0);
1940 #if MM_ENABLE_CONV
1941     CONVERT_FROM_UTF8_FREE(s);
1942 #endif
1944     // Force screen redraw (does it have to be this complicated?).
1945     redraw_all_later(CLEAR);
1946     update_screen(NOT_VALID);
1947     setcursor();
1948     out_flush();
1949     gui_update_cursor(FALSE, FALSE);
1950     gui_mch_flush();
1953 - (void)handleDropFiles:(NSData *)data
1955     if (!data) return;
1957 #ifdef FEAT_DND
1958     const void *bytes = [data bytes];
1959     const void *end = [data bytes] + [data length];
1960     int n = *((int*)bytes);  bytes += sizeof(int);
1962     if (State & CMDLINE) {
1963         // HACK!  If Vim is in command line mode then the files names
1964         // should be added to the command line, instead of opening the
1965         // files in tabs.  This is taken care of by gui_handle_drop().
1966         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1967         if (fnames) {
1968             int i = 0;
1969             while (bytes < end && i < n) {
1970                 int len = *((int*)bytes);  bytes += sizeof(int);
1971                 char_u *s = (char_u*)bytes;
1972 #if MM_ENABLE_CONV
1973                 s = CONVERT_FROM_UTF8(s);
1974 #endif
1975                 fnames[i++] = vim_strsave(s);
1976 #if MM_ENABLE_CONV
1977                 CONVERT_FROM_UTF8_FREE(s);
1978 #endif
1979                 bytes += len;
1980             }
1982             // NOTE!  This function will free 'fnames'.
1983             // HACK!  It is assumed that the 'x' and 'y' arguments are
1984             // unused when in command line mode.
1985             gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
1986         }
1987     } else {
1988         // HACK!  I'm not sure how to get Vim to open a list of files in
1989         // tabs, so instead I create a ':tab drop' command with all the
1990         // files to open and execute it.
1991         NSMutableString *cmd = (n > 1)
1992                 ? [NSMutableString stringWithString:@":tab drop"]
1993                 : [NSMutableString stringWithString:@":drop"];
1995         int i;
1996         for (i = 0; i < n && bytes < end; ++i) {
1997             int len = *((int*)bytes);  bytes += sizeof(int);
1998             NSString *file = [NSString stringWithUTF8String:bytes];
1999             file = [file stringByEscapingInvalidFilenameCharacters];
2000             bytes += len;
2002             [cmd appendString:@" "];
2003             [cmd appendString:file];
2004         }
2006         // By going to the last tabpage we ensure that the new tabs will
2007         // appear last (if this call is left out, the taborder becomes
2008         // messy).
2009         goto_tabpage(9999);
2011         char_u *s = (char_u*)[cmd UTF8String];
2012 #if MM_ENABLE_CONV
2013         s = CONVERT_FROM_UTF8(s);
2014 #endif
2015         do_cmdline_cmd(s);
2016 #if MM_ENABLE_CONV
2017         CONVERT_FROM_UTF8_FREE(s);
2018 #endif
2020         // Force screen redraw (does it have to be this complicated?).
2021         // (This code was taken from the end of gui_handle_drop().)
2022         update_screen(NOT_VALID);
2023         setcursor();
2024         out_flush();
2025         gui_update_cursor(FALSE, FALSE);
2026         gui_mch_flush();
2027     }
2028 #endif // FEAT_DND
2031 - (void)handleDropString:(NSData *)data
2033     if (!data) return;
2035 #ifdef FEAT_DND
2036     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2037     const void *bytes = [data bytes];
2038     int len = *((int*)bytes);  bytes += sizeof(int);
2039     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2041     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2042     NSRange range = { 0, [string length] };
2043     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2044                                          withString:@"\x0a" options:0
2045                                               range:range];
2046     if (0 == n) {
2047         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2048                                        options:0 range:range];
2049     }
2051     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2052     char_u *s = (char_u*)[string UTF8String];
2053 #if MM_ENABLE_CONV
2054     if (input_conv.vc_type != CONV_NONE)
2055         s = string_convert(&input_conv, s, &len);
2056 #endif
2057     dnd_yank_drag_data(s, len);
2058 #if MM_ENABLE_CONV
2059     if (input_conv.vc_type != CONV_NONE)
2060         vim_free(s);
2061 #endif
2062     add_to_input_buf(dropkey, sizeof(dropkey));
2063 #endif // FEAT_DND
2066 @end // MMBackend (Private)
2071 @implementation MMBackend (ClientServer)
2073 - (NSString *)connectionNameFromServerName:(NSString *)name
2075     NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2077     return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2078         lowercaseString];
2081 - (NSConnection *)connectionForServerName:(NSString *)name
2083     // TODO: Try 'name%d' if 'name' fails.
2084     NSString *connName = [self connectionNameFromServerName:name];
2085     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2087     if (!svrConn) {
2088         svrConn = [NSConnection connectionWithRegisteredName:connName
2089                                                            host:nil];
2090         // Try alternate server...
2091         if (!svrConn && alternateServerName) {
2092             //NSLog(@"  trying to connect to alternate server: %@",
2093             //        alternateServerName);
2094             connName = [self connectionNameFromServerName:alternateServerName];
2095             svrConn = [NSConnection connectionWithRegisteredName:connName
2096                                                             host:nil];
2097         }
2099         // Try looking for alternate servers...
2100         if (!svrConn) {
2101             //NSLog(@"  looking for alternate servers...");
2102             NSString *alt = [self alternateServerNameForName:name];
2103             if (alt != alternateServerName) {
2104                 //NSLog(@"  found alternate server: %@", string);
2105                 [alternateServerName release];
2106                 alternateServerName = [alt copy];
2107             }
2108         }
2110         // Try alternate server again...
2111         if (!svrConn && alternateServerName) {
2112             //NSLog(@"  trying to connect to alternate server: %@",
2113             //        alternateServerName);
2114             connName = [self connectionNameFromServerName:alternateServerName];
2115             svrConn = [NSConnection connectionWithRegisteredName:connName
2116                                                             host:nil];
2117         }
2119         if (svrConn) {
2120             [connectionNameDict setObject:svrConn forKey:connName];
2122             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2123             [[NSNotificationCenter defaultCenter] addObserver:self
2124                     selector:@selector(serverConnectionDidDie:)
2125                         name:NSConnectionDidDieNotification object:svrConn];
2126         }
2127     }
2129     return svrConn;
2132 - (NSConnection *)connectionForServerPort:(int)port
2134     NSConnection *conn;
2135     NSEnumerator *e = [connectionNameDict objectEnumerator];
2137     while ((conn = [e nextObject])) {
2138         // HACK! Assume connection uses mach ports.
2139         if (port == [(NSMachPort*)[conn sendPort] machPort])
2140             return conn;
2141     }
2143     return nil;
2146 - (void)serverConnectionDidDie:(NSNotification *)notification
2148     //NSLog(@"%s%@", _cmd, notification);
2150     NSConnection *svrConn = [notification object];
2152     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2153     [[NSNotificationCenter defaultCenter]
2154             removeObserver:self
2155                       name:NSConnectionDidDieNotification
2156                     object:svrConn];
2158     [connectionNameDict removeObjectsForKeys:
2159         [connectionNameDict allKeysForObject:svrConn]];
2161     // HACK! Assume connection uses mach ports.
2162     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2163     NSNumber *key = [NSNumber numberWithInt:port];
2165     [clientProxyDict removeObjectForKey:key];
2166     [serverReplyDict removeObjectForKey:key];
2169 - (void)addClient:(NSDistantObject *)client
2171     NSConnection *conn = [client connectionForProxy];
2172     // HACK! Assume connection uses mach ports.
2173     int port = [(NSMachPort*)[conn sendPort] machPort];
2174     NSNumber *key = [NSNumber numberWithInt:port];
2176     if (![clientProxyDict objectForKey:key]) {
2177         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2178         [clientProxyDict setObject:client forKey:key];
2179     }
2181     // NOTE: 'clientWindow' is a global variable which is used by <client>
2182     clientWindow = port;
2185 - (NSString *)alternateServerNameForName:(NSString *)name
2187     if (!(name && [name length] > 0))
2188         return nil;
2190     // Only look for alternates if 'name' doesn't end in a digit.
2191     unichar lastChar = [name characterAtIndex:[name length]-1];
2192     if (lastChar >= '0' && lastChar <= '9')
2193         return nil;
2195     // Look for alternates among all current servers.
2196     NSArray *list = [self serverList];
2197     if (!(list && [list count] > 0))
2198         return nil;
2200     // Filter out servers starting with 'name' and ending with a number. The
2201     // (?i) pattern ensures that the match is case insensitive.
2202     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2203     NSPredicate *pred = [NSPredicate predicateWithFormat:
2204             @"SELF MATCHES %@", pat];
2205     list = [list filteredArrayUsingPredicate:pred];
2206     if ([list count] > 0) {
2207         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2208         return [list objectAtIndex:0];
2209     }
2211     return nil;
2214 @end // MMBackend (ClientServer)
2219 @implementation NSString (MMServerNameCompare)
2220 - (NSComparisonResult)serverNameCompare:(NSString *)string
2222     return [self compare:string
2223                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2225 @end
2230 static int eventModifierFlagsToVimModMask(int modifierFlags)
2232     int modMask = 0;
2234     if (modifierFlags & NSShiftKeyMask)
2235         modMask |= MOD_MASK_SHIFT;
2236     if (modifierFlags & NSControlKeyMask)
2237         modMask |= MOD_MASK_CTRL;
2238     if (modifierFlags & NSAlternateKeyMask)
2239         modMask |= MOD_MASK_ALT;
2240     if (modifierFlags & NSCommandKeyMask)
2241         modMask |= MOD_MASK_CMD;
2243     return modMask;
2246 static int vimModMaskToEventModifierFlags(int mods)
2248     int flags = 0;
2250     if (mods & MOD_MASK_SHIFT)
2251         flags |= NSShiftKeyMask;
2252     if (mods & MOD_MASK_CTRL)
2253         flags |= NSControlKeyMask;
2254     if (mods & MOD_MASK_ALT)
2255         flags |= NSAlternateKeyMask;
2256     if (mods & MOD_MASK_CMD)
2257         flags |= NSCommandKeyMask;
2259     return flags;
2262 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2264     int modMask = 0;
2266     if (modifierFlags & NSShiftKeyMask)
2267         modMask |= MOUSE_SHIFT;
2268     if (modifierFlags & NSControlKeyMask)
2269         modMask |= MOUSE_CTRL;
2270     if (modifierFlags & NSAlternateKeyMask)
2271         modMask |= MOUSE_ALT;
2273     return modMask;
2276 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2278     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE,
2279             MOUSE_X1, MOUSE_X2 };
2281     return mouseButton[buttonNumber < 5 ? buttonNumber : 0];
2284 static int specialKeyToNSKey(int key)
2286     if (!IS_SPECIAL(key))
2287         return key;
2289     static struct {
2290         int special;
2291         int nskey;
2292     } sp2ns[] = {
2293         { K_UP, NSUpArrowFunctionKey },
2294         { K_DOWN, NSDownArrowFunctionKey },
2295         { K_LEFT, NSLeftArrowFunctionKey },
2296         { K_RIGHT, NSRightArrowFunctionKey },
2297         { K_F1, NSF1FunctionKey },
2298         { K_F2, NSF2FunctionKey },
2299         { K_F3, NSF3FunctionKey },
2300         { K_F4, NSF4FunctionKey },
2301         { K_F5, NSF5FunctionKey },
2302         { K_F6, NSF6FunctionKey },
2303         { K_F7, NSF7FunctionKey },
2304         { K_F8, NSF8FunctionKey },
2305         { K_F9, NSF9FunctionKey },
2306         { K_F10, NSF10FunctionKey },
2307         { K_F11, NSF11FunctionKey },
2308         { K_F12, NSF12FunctionKey },
2309         { K_F13, NSF13FunctionKey },
2310         { K_F14, NSF14FunctionKey },
2311         { K_F15, NSF15FunctionKey },
2312         { K_F16, NSF16FunctionKey },
2313         { K_F17, NSF17FunctionKey },
2314         { K_F18, NSF18FunctionKey },
2315         { K_F19, NSF19FunctionKey },
2316         { K_F20, NSF20FunctionKey },
2317         { K_F21, NSF21FunctionKey },
2318         { K_F22, NSF22FunctionKey },
2319         { K_F23, NSF23FunctionKey },
2320         { K_F24, NSF24FunctionKey },
2321         { K_F25, NSF25FunctionKey },
2322         { K_F26, NSF26FunctionKey },
2323         { K_F27, NSF27FunctionKey },
2324         { K_F28, NSF28FunctionKey },
2325         { K_F29, NSF29FunctionKey },
2326         { K_F30, NSF30FunctionKey },
2327         { K_F31, NSF31FunctionKey },
2328         { K_F32, NSF32FunctionKey },
2329         { K_F33, NSF33FunctionKey },
2330         { K_F34, NSF34FunctionKey },
2331         { K_F35, NSF35FunctionKey },
2332         { K_DEL, NSBackspaceCharacter },
2333         { K_BS, NSDeleteCharacter },
2334         { K_HOME, NSHomeFunctionKey },
2335         { K_END, NSEndFunctionKey },
2336         { K_PAGEUP, NSPageUpFunctionKey },
2337         { K_PAGEDOWN, NSPageDownFunctionKey }
2338     };
2340     int i;
2341     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2342         if (sp2ns[i].special == key)
2343             return sp2ns[i].nskey;
2344     }
2346     return 0;