Add input queue to backend, fixing process leak problem.
[MacVim.git] / src / MacVim / MMBackend.m
blobedc1c7738e6ece9fad2e9390a798718f39eaea09
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;
29 static int MMFlushQueueLenHint = 80*40;
31 static unsigned MMServerMax = 1000;
33 // NOTE: The default font is bundled with the application.
34 static NSString *MMDefaultFontName = @"DejaVu Sans Mono";
35 static float MMDefaultFontSize = 12.0f;
37 // TODO: Move to separate file.
38 static int eventModifierFlagsToVimModMask(int modifierFlags);
39 static int vimModMaskToEventModifierFlags(int mods);
40 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
41 static int eventButtonNumberToVimMouseButton(int buttonNumber);
42 static int specialKeyToNSKey(int key);
44 enum {
45     MMBlinkStateNone = 0,
46     MMBlinkStateOn,
47     MMBlinkStateOff
52 @interface NSString (MMServerNameCompare)
53 - (NSComparisonResult)serverNameCompare:(NSString *)string;
54 @end
58 @interface MMBackend (Private)
59 - (void)processInputQueue;
60 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
61 + (NSDictionary *)specialKeys;
62 - (void)handleInsertText:(NSData *)data;
63 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
64 - (void)queueMessage:(int)msgid data:(NSData *)data;
65 - (void)connectionDidDie:(NSNotification *)notification;
66 - (void)blinkTimerFired:(NSTimer *)timer;
67 - (void)focusChange:(BOOL)on;
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         outputQueue = [[NSMutableArray alloc] init];
102         inputQueue = [[NSMutableArray alloc] init];
103         drawData = [[NSMutableData alloc] initWithCapacity:1024];
104         connectionNameDict = [[NSMutableDictionary alloc] init];
105         clientProxyDict = [[NSMutableDictionary alloc] init];
106         serverReplyDict = [[NSMutableDictionary alloc] init];
108         NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
109                                                          ofType:@"plist"];
110         if (path) {
111             colorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
112                 retain];
113         } else {
114             NSLog(@"WARNING: Could not locate Colors.plist.");
115         }
117         path = [[NSBundle mainBundle] pathForResource:@"SystemColors"
118                                                ofType:@"plist"];
119         if (path) {
120             sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
121                 retain];
122         } else {
123             NSLog(@"WARNING: Could not locate SystemColors.plist.");
124         }
125     }
127     return self;
130 - (void)dealloc
132     //NSLog(@"%@ %s", [self className], _cmd);
133     [[NSNotificationCenter defaultCenter] removeObserver:self];
135     [blinkTimer release];  blinkTimer = nil;
136     [alternateServerName release];  alternateServerName = nil;
137     [serverReplyDict release];  serverReplyDict = nil;
138     [clientProxyDict release];  clientProxyDict = nil;
139     [connectionNameDict release];  connectionNameDict = nil;
140     [inputQueue release];  inputQueue = nil;
141     [outputQueue release];  outputQueue = nil;
142     [drawData release];  drawData = nil;
143     [frontendProxy release];  frontendProxy = nil;
144     [connection release];  connection = nil;
145     [sysColorDict release];  sysColorDict = nil;
146     [colorDict release];  colorDict = nil;
148     [super dealloc];
151 - (void)setBackgroundColor:(int)color
153     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
156 - (void)setForegroundColor:(int)color
158     foregroundColor = MM_COLOR(color);
161 - (void)setSpecialColor:(int)color
163     specialColor = MM_COLOR(color);
166 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
168     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
169     defaultForegroundColor = MM_COLOR(fg);
171     NSMutableData *data = [NSMutableData data];
173     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
174     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
176     [self queueMessage:SetDefaultColorsMsgID data:data];
179 - (NSConnection *)connection
181     if (!connection) {
182         // NOTE!  If the name of the connection changes here it must also be
183         // updated in MMAppController.m.
184         NSString *name = [NSString stringWithFormat:@"%@-connection",
185                [[NSBundle mainBundle] bundleIdentifier]];
187         connection = [NSConnection connectionWithRegisteredName:name host:nil];
188         [connection retain];
189     }
191     // NOTE: 'connection' may be nil here.
192     return connection;
195 - (BOOL)checkin
197     if (![self connection]) {
198         NSBundle *mainBundle = [NSBundle mainBundle];
199 #if 0
200         NSString *path = [mainBundle bundlePath];
201         if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
202             NSLog(@"WARNING: Failed to launch GUI with path %@", path);
203             return NO;
204         }
205 #else
206         // HACK!  It would be preferable to launch the GUI using NSWorkspace,
207         // however I have not managed to figure out how to pass arguments using
208         // NSWorkspace.
209         //
210         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
211         // that the GUI won't be activated (or raised) so there is a hack in
212         // MMWindowController which always raises the app when a new window is
213         // opened.
214         NSMutableArray *args = [NSMutableArray arrayWithObjects:
215             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
216         NSString *exeName = [[mainBundle infoDictionary]
217                 objectForKey:@"CFBundleExecutable"];
218         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
219         if (!path) {
220             NSLog(@"ERROR: Could not find MacVim executable in bundle");
221             return NO;
222         }
224         [NSTask launchedTaskWithLaunchPath:path arguments:args];
225 #endif
227         // HACK!  The NSWorkspaceDidLaunchApplicationNotification does not work
228         // for tasks like this, so poll the mach bootstrap server until it
229         // returns a valid connection.  Also set a time-out date so that we
230         // don't get stuck doing this forever.
231         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
232         while (!connection &&
233                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
234         {
235             [[NSRunLoop currentRunLoop]
236                     runMode:NSDefaultRunLoopMode
237                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
239             // NOTE: This call will set 'connection' as a side-effect.
240             [self connection];
241         }
243         if (!connection) {
244             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
245             return NO;
246         }
247     }
249     id proxy = [connection rootProxy];
250     [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
252     [[NSNotificationCenter defaultCenter] addObserver:self
253             selector:@selector(connectionDidDie:)
254                 name:NSConnectionDidDieNotification object:connection];
256     int pid = [[NSProcessInfo processInfo] processIdentifier];
258     @try {
259         frontendProxy = [proxy connectBackend:self pid:pid];
260     }
261     @catch (NSException *e) {
262         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
263     }
265     if (frontendProxy) {
266         [frontendProxy retain];
267         [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
268     }
270     return connection && frontendProxy;
273 - (BOOL)openVimWindow
275     [self queueMessage:OpenVimWindowMsgID data:nil];
276     return YES;
279 - (void)clearAll
281     int type = ClearAllDrawType;
283     // Any draw commands in queue are effectively obsolete since this clearAll
284     // will negate any effect they have, therefore we may as well clear the
285     // draw queue.
286     [drawData setLength:0];
288     [drawData appendBytes:&type length:sizeof(int)];
291 - (void)clearBlockFromRow:(int)row1 column:(int)col1
292                     toRow:(int)row2 column:(int)col2
294     int type = ClearBlockDrawType;
296     [drawData appendBytes:&type length:sizeof(int)];
298     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
299     [drawData appendBytes:&row1 length:sizeof(int)];
300     [drawData appendBytes:&col1 length:sizeof(int)];
301     [drawData appendBytes:&row2 length:sizeof(int)];
302     [drawData appendBytes:&col2 length:sizeof(int)];
305 - (void)deleteLinesFromRow:(int)row count:(int)count
306               scrollBottom:(int)bottom left:(int)left right:(int)right
308     int type = DeleteLinesDrawType;
310     [drawData appendBytes:&type length:sizeof(int)];
312     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
313     [drawData appendBytes:&row length:sizeof(int)];
314     [drawData appendBytes:&count length:sizeof(int)];
315     [drawData appendBytes:&bottom length:sizeof(int)];
316     [drawData appendBytes:&left length:sizeof(int)];
317     [drawData appendBytes:&right length:sizeof(int)];
320 - (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
321                 flags:(int)flags
323     if (len <= 0) return;
325     int type = ReplaceStringDrawType;
327     [drawData appendBytes:&type length:sizeof(int)];
329     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
330     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
331     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
332     [drawData appendBytes:&row length:sizeof(int)];
333     [drawData appendBytes:&col length:sizeof(int)];
334     [drawData appendBytes:&flags length:sizeof(int)];
335     [drawData appendBytes:&len length:sizeof(int)];
336     [drawData appendBytes:s length:len];
339 - (void)insertLinesFromRow:(int)row count:(int)count
340               scrollBottom:(int)bottom left:(int)left right:(int)right
342     int type = InsertLinesDrawType;
344     [drawData appendBytes:&type length:sizeof(int)];
346     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
347     [drawData appendBytes:&row length:sizeof(int)];
348     [drawData appendBytes:&count length:sizeof(int)];
349     [drawData appendBytes:&bottom length:sizeof(int)];
350     [drawData appendBytes:&left length:sizeof(int)];
351     [drawData appendBytes:&right length:sizeof(int)];
354 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
355                fraction:(int)percent color:(int)color
357     int type = DrawCursorDrawType;
358     unsigned uc = MM_COLOR(color);
360     [drawData appendBytes:&type length:sizeof(int)];
362     [drawData appendBytes:&uc length:sizeof(unsigned)];
363     [drawData appendBytes:&row length:sizeof(int)];
364     [drawData appendBytes:&col length:sizeof(int)];
365     [drawData appendBytes:&shape length:sizeof(int)];
366     [drawData appendBytes:&percent length:sizeof(int)];
369 - (void)update
371     // Tend to the run loop, returning immediately if there are no events
372     // waiting.
373     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
374                              beforeDate:[NSDate distantPast]];
376     // Keyboard and mouse input is handled directly, other input is queued and
377     // processed here.  This call may enter a blocking loop.
378     if ([inputQueue count] > 0)
379         [self processInputQueue];
382 - (void)flushQueue:(BOOL)force
384     // NOTE! This method gets called a lot; if we were to flush every time it
385     // got called MacVim would feel unresponsive.  So there is a time out which
386     // ensures that the queue isn't flushed too often.
387     if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
388             < MMFlushTimeoutInterval
389             && [drawData length] < MMFlushQueueLenHint)
390         return;
392     if ([drawData length] > 0) {
393         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
394         [drawData setLength:0];
395     }
397     if ([outputQueue count] > 0) {
398         @try {
399             [frontendProxy processCommandQueue:outputQueue];
400         }
401         @catch (NSException *e) {
402             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
403         }
405         [outputQueue removeAllObjects];
407         [lastFlushDate release];
408         lastFlushDate = [[NSDate date] retain];
409     }
412 - (BOOL)waitForInput:(int)milliseconds
414     //NSLog(@"|ENTER| %s%d",  _cmd, milliseconds);
415     NSDate *date = milliseconds > 0 ?
416             [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
417             [NSDate distantFuture];
419     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
421     // I know of no way to figure out if the run loop exited because input was
422     // found or because of a time out, so I need to manually indicate when
423     // input was received in processInput:data: and then reset it every time
424     // here.
425     BOOL yn = inputReceived;
426     inputReceived = NO;
428     //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
429     return yn;
432 - (void)exit
434 #ifdef MAC_CLIENTSERVER
435     // The default connection is used for the client/server code.
436     [[NSConnection defaultConnection] setRootObject:nil];
437     [[NSConnection defaultConnection] invalidate];
438 #endif
440     // By invalidating the NSConnection the MMWindowController immediately
441     // finds out that the connection is down and as a result
442     // [MMWindowController connectionDidDie:] is invoked.
443     //NSLog(@"%@ %s", [self className], _cmd);
444     [[NSNotificationCenter defaultCenter] removeObserver:self];
445     [connection invalidate];
447     if (fontContainerRef) {
448         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
449         fontContainerRef = 0;
450     }
454 - (void)selectTab:(int)index
456     //NSLog(@"%s%d", _cmd, index);
458     index -= 1;
459     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
460     [self queueMessage:SelectTabMsgID data:data];
463 - (void)updateTabBar
465     //NSLog(@"%s", _cmd);
467     NSMutableData *data = [NSMutableData data];
469     int idx = tabpage_index(curtab) - 1;
470     [data appendBytes:&idx length:sizeof(int)];
472     tabpage_T *tp;
473     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
474         // This function puts the label of the tab in the global 'NameBuff'.
475         get_tabline_label(tp, FALSE);
476         char_u *s = NameBuff;
477         int len = STRLEN(s);
478         if (len <= 0) continue;
480 #if MM_ENABLE_CONV
481         s = CONVERT_TO_UTF8(s);
482 #endif
484         // Count the number of windows in the tabpage.
485         //win_T *wp = tp->tp_firstwin;
486         //int wincount;
487         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
489         //[data appendBytes:&wincount length:sizeof(int)];
490         [data appendBytes:&len length:sizeof(int)];
491         [data appendBytes:s length:len];
493 #if MM_ENABLE_CONV
494         CONVERT_TO_UTF8_FREE(s);
495 #endif
496     }
498     [self queueMessage:UpdateTabBarMsgID data:data];
501 - (BOOL)tabBarVisible
503     return tabBarVisible;
506 - (void)showTabBar:(BOOL)enable
508     tabBarVisible = enable;
510     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
511     [self queueMessage:msgid data:nil];
514 - (void)setRows:(int)rows columns:(int)cols
516     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
518     int dim[] = { rows, cols };
519     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
521     [self queueMessage:SetTextDimensionsMsgID data:data];
524 - (void)setWindowTitle:(char *)title
526     NSMutableData *data = [NSMutableData data];
527     int len = strlen(title);
528     if (len <= 0) return;
530     [data appendBytes:&len length:sizeof(int)];
531     [data appendBytes:title length:len];
533     [self queueMessage:SetWindowTitleMsgID data:data];
536 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
537                             saving:(int)saving
539     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
540     //        saving);
542     char_u *s = NULL;
543     NSString *ds = dir ? [NSString stringWithUTF8String:dir] : nil;
544     NSString *ts = title ? [NSString stringWithUTF8String:title] : nil;
545     @try {
546         [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
548         // Wait until a reply is sent from MMVimController.
549         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
550                                  beforeDate:[NSDate distantFuture]];
552         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
553             char_u *ret = (char_u*)[dialogReturn UTF8String];
554 #if MM_ENABLE_CONV
555             ret = CONVERT_FROM_UTF8(ret);
556 #endif
557             s = vim_strsave(ret);
558 #if MM_ENABLE_CONV
559             CONVERT_FROM_UTF8_FREE(ret);
560 #endif
561         }
563         [dialogReturn release];  dialogReturn = nil;
564     }
565     @catch (NSException *e) {
566         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
567     }
569     return (char *)s;
572 - (oneway void)setDialogReturn:(in bycopy id)obj
574     // NOTE: This is called by
575     //   - [MMVimController panelDidEnd:::], and
576     //   - [MMVimController alertDidEnd:::],
577     // to indicate that a save/open panel or alert has finished.
579     if (obj != dialogReturn) {
580         [dialogReturn release];
581         dialogReturn = [obj retain];
582     }
585 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
586                      buttons:(char *)btns textField:(char *)txtfield
588     int retval = 0;
589     NSString *message = nil, *text = nil, *textFieldString = nil;
590     NSArray *buttons = nil;
591     int style = NSInformationalAlertStyle;
593     if (VIM_WARNING == type) style = NSWarningAlertStyle;
594     else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
596     if (btns) {
597         NSString *btnString = [NSString stringWithUTF8String:btns];
598         buttons = [btnString componentsSeparatedByString:@"\n"];
599     }
600     if (title)
601         message = [NSString stringWithUTF8String:title];
602     if (msg) {
603         text = [NSString stringWithUTF8String:msg];
604         if (!message) {
605             // HACK! If there is a '\n\n' or '\n' sequence in the message, then
606             // make the part up to there into the title.  We only do this
607             // because Vim has lots of dialogs without a title and they look
608             // ugly that way.
609             // TODO: Fix the actual dialog texts.
610             NSRange eolRange = [text rangeOfString:@"\n\n"];
611             if (NSNotFound == eolRange.location)
612                 eolRange = [text rangeOfString:@"\n"];
613             if (NSNotFound != eolRange.location) {
614                 message = [text substringToIndex:eolRange.location];
615                 text = [text substringFromIndex:NSMaxRange(eolRange)];
616             }
617         }
618     }
619     if (txtfield)
620         textFieldString = [NSString stringWithUTF8String:txtfield];
622     @try {
623         [frontendProxy presentDialogWithStyle:style message:message
624                               informativeText:text buttonTitles:buttons
625                               textFieldString:textFieldString];
627         // Wait until a reply is sent from MMVimController.
628         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
629                                  beforeDate:[NSDate distantFuture]];
631         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
632                 && [dialogReturn count]) {
633             retval = [[dialogReturn objectAtIndex:0] intValue];
634             if (txtfield && [dialogReturn count] > 1) {
635                 NSString *retString = [dialogReturn objectAtIndex:1];
636                 char_u *ret = (char_u*)[retString UTF8String];
637 #if MM_ENABLE_CONV
638                 ret = CONVERT_FROM_UTF8(ret);
639 #endif
640                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
641 #if MM_ENABLE_CONV
642                 CONVERT_FROM_UTF8_FREE(ret);
643 #endif
644             }
645         }
647         [dialogReturn release]; dialogReturn = nil;
648     }
649     @catch (NSException *e) {
650         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
651     }
653     return retval;
656 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
657                atIndex:(int)index
659     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
660     //        name, index);
662     int namelen = name ? strlen(name) : 0;
663     NSMutableData *data = [NSMutableData data];
665     [data appendBytes:&tag length:sizeof(int)];
666     [data appendBytes:&parentTag length:sizeof(int)];
667     [data appendBytes:&namelen length:sizeof(int)];
668     if (namelen > 0) [data appendBytes:name length:namelen];
669     [data appendBytes:&index length:sizeof(int)];
671     [self queueMessage:AddMenuMsgID data:data];
674 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
675                        tip:(char *)tip icon:(char *)icon
676              keyEquivalent:(int)key modifiers:(int)mods
677                     action:(NSString *)action atIndex:(int)index
679     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
680     //        parentTag, name, tip, index);
682     int namelen = name ? strlen(name) : 0;
683     int tiplen = tip ? strlen(tip) : 0;
684     int iconlen = icon ? strlen(icon) : 0;
685     int eventFlags = vimModMaskToEventModifierFlags(mods);
686     int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
687     NSMutableData *data = [NSMutableData data];
689     key = specialKeyToNSKey(key);
691     [data appendBytes:&tag length:sizeof(int)];
692     [data appendBytes:&parentTag length:sizeof(int)];
693     [data appendBytes:&namelen length:sizeof(int)];
694     if (namelen > 0) [data appendBytes:name length:namelen];
695     [data appendBytes:&tiplen length:sizeof(int)];
696     if (tiplen > 0) [data appendBytes:tip length:tiplen];
697     [data appendBytes:&iconlen length:sizeof(int)];
698     if (iconlen > 0) [data appendBytes:icon length:iconlen];
699     [data appendBytes:&actionlen length:sizeof(int)];
700     if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
701     [data appendBytes:&index length:sizeof(int)];
702     [data appendBytes:&key length:sizeof(int)];
703     [data appendBytes:&eventFlags length:sizeof(int)];
705     [self queueMessage:AddMenuItemMsgID data:data];
708 - (void)removeMenuItemWithTag:(int)tag
710     NSMutableData *data = [NSMutableData data];
711     [data appendBytes:&tag length:sizeof(int)];
713     [self queueMessage:RemoveMenuItemMsgID data:data];
716 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
718     NSMutableData *data = [NSMutableData data];
720     [data appendBytes:&tag length:sizeof(int)];
721     [data appendBytes:&enabled length:sizeof(int)];
723     [self queueMessage:EnableMenuItemMsgID data:data];
726 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
728     int len = strlen(name);
729     int row = -1, col = -1;
731     if (len <= 0) return;
733     if (!mouse && curwin) {
734         row = curwin->w_wrow;
735         col = curwin->w_wcol;
736     }
738     NSMutableData *data = [NSMutableData data];
740     [data appendBytes:&row length:sizeof(int)];
741     [data appendBytes:&col length:sizeof(int)];
742     [data appendBytes:&len length:sizeof(int)];
743     [data appendBytes:name length:len];
745     [self queueMessage:ShowPopupMenuMsgID data:data];
748 - (void)showToolbar:(int)enable flags:(int)flags
750     NSMutableData *data = [NSMutableData data];
752     [data appendBytes:&enable length:sizeof(int)];
753     [data appendBytes:&flags length:sizeof(int)];
755     [self queueMessage:ShowToolbarMsgID data:data];
758 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
760     NSMutableData *data = [NSMutableData data];
762     [data appendBytes:&ident length:sizeof(long)];
763     [data appendBytes:&type length:sizeof(int)];
765     [self queueMessage:CreateScrollbarMsgID data:data];
768 - (void)destroyScrollbarWithIdentifier:(long)ident
770     NSMutableData *data = [NSMutableData data];
771     [data appendBytes:&ident length:sizeof(long)];
773     [self queueMessage:DestroyScrollbarMsgID data:data];
776 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
778     NSMutableData *data = [NSMutableData data];
780     [data appendBytes:&ident length:sizeof(long)];
781     [data appendBytes:&visible length:sizeof(int)];
783     [self queueMessage:ShowScrollbarMsgID data:data];
786 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
788     NSMutableData *data = [NSMutableData data];
790     [data appendBytes:&ident length:sizeof(long)];
791     [data appendBytes:&pos length:sizeof(int)];
792     [data appendBytes:&len length:sizeof(int)];
794     [self queueMessage:SetScrollbarPositionMsgID data:data];
797 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
798                     identifier:(long)ident
800     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
801     float prop = (float)size/(max+1);
802     if (fval < 0) fval = 0;
803     else if (fval > 1.0f) fval = 1.0f;
804     if (prop < 0) prop = 0;
805     else if (prop > 1.0f) prop = 1.0f;
807     NSMutableData *data = [NSMutableData data];
809     [data appendBytes:&ident length:sizeof(long)];
810     [data appendBytes:&fval length:sizeof(float)];
811     [data appendBytes:&prop length:sizeof(float)];
813     [self queueMessage:SetScrollbarThumbMsgID data:data];
816 - (BOOL)setFontWithName:(char *)name
818     NSString *fontName = MMDefaultFontName;
819     float size = MMDefaultFontSize;
820     BOOL parseFailed = NO;
822     if (name) {
823         fontName = [NSString stringWithUTF8String:name];
825         if ([fontName isEqual:@"*"]) {
826             // :set gfn=* shows the font panel.
827             do_cmdline_cmd((char_u*)":macaction orderFrontFontPanel:");
828             return NO;
829         }
831         NSArray *components = [fontName componentsSeparatedByString:@":"];
832         if ([components count] == 2) {
833             NSString *sizeString = [components lastObject];
834             if ([sizeString length] > 0
835                     && [sizeString characterAtIndex:0] == 'h') {
836                 sizeString = [sizeString substringFromIndex:1];
837                 if ([sizeString length] > 0) {
838                     size = [sizeString floatValue];
839                     fontName = [components objectAtIndex:0];
840                 }
841             } else {
842                 parseFailed = YES;
843             }
844         } else if ([components count] > 2) {
845             parseFailed = YES;
846         }
848         if (!parseFailed) {
849             // Replace underscores with spaces.
850             fontName = [[fontName componentsSeparatedByString:@"_"]
851                                      componentsJoinedByString:@" "];
852         }
853     }
855     if (!parseFailed && [fontName length] > 0) {
856         if (size < 6 || size > 100) {
857             // Font size 0.0 tells NSFont to use the 'user default size'.
858             size = 0.0f;
859         }
861         NSFont *font = [NSFont fontWithName:fontName size:size];
863         if (!font && MMDefaultFontName == fontName) {
864             // If for some reason the MacVim default font is not in the app
865             // bundle, then fall back on the system default font.
866             size = 0;
867             font = [NSFont userFixedPitchFontOfSize:size];
868             fontName = [font displayName];
869         }
871         if (font) {
872             //NSLog(@"Setting font '%@' of size %.2f", fontName, size);
873             int len = [fontName
874                     lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
875             if (len > 0) {
876                 NSMutableData *data = [NSMutableData data];
878                 [data appendBytes:&size length:sizeof(float)];
879                 [data appendBytes:&len length:sizeof(int)];
880                 [data appendBytes:[fontName UTF8String] length:len];
882                 [self queueMessage:SetFontMsgID data:data];
883                 return YES;
884             }
885         }
886     }
888     //NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f",
889     //        fontName, size);
890     return NO;
893 - (void)executeActionWithName:(NSString *)name
895     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
897     if (len > 0) {
898         NSMutableData *data = [NSMutableData data];
900         [data appendBytes:&len length:sizeof(int)];
901         [data appendBytes:[name UTF8String] length:len];
903         [self queueMessage:ExecuteActionMsgID data:data];
904     }
907 - (void)setMouseShape:(int)shape
909     NSMutableData *data = [NSMutableData data];
910     [data appendBytes:&shape length:sizeof(int)];
911     [self queueMessage:SetMouseShapeMsgID data:data];
914 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
916     // Vim specifies times in milliseconds, whereas Cocoa wants them in
917     // seconds.
918     blinkWaitInterval = .001f*wait;
919     blinkOnInterval = .001f*on;
920     blinkOffInterval = .001f*off;
923 - (void)startBlink
925     if (blinkTimer) {
926         [blinkTimer invalidate];
927         [blinkTimer release];
928         blinkTimer = nil;
929     }
931     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
932             && gui.in_focus) {
933         blinkState = MMBlinkStateOn;
934         blinkTimer =
935             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
936                                               target:self
937                                             selector:@selector(blinkTimerFired:)
938                                             userInfo:nil repeats:NO] retain];
939         gui_update_cursor(TRUE, FALSE);
940         [self flushQueue:YES];
941     }
944 - (void)stopBlink
946     if (MMBlinkStateOff == blinkState) {
947         gui_update_cursor(TRUE, FALSE);
948         [self flushQueue:YES];
949     }
951     blinkState = MMBlinkStateNone;
954 - (void)adjustLinespace:(int)linespace
956     NSMutableData *data = [NSMutableData data];
957     [data appendBytes:&linespace length:sizeof(int)];
958     [self queueMessage:AdjustLinespaceMsgID data:data];
961 - (void)activate
963     [self queueMessage:ActivateMsgID data:nil];
966 - (int)lookupColorWithKey:(NSString *)key
968     if (!(key && [key length] > 0))
969         return INVALCOLOR;
971     NSString *stripKey = [[[[key lowercaseString]
972         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
973             componentsSeparatedByString:@" "]
974                componentsJoinedByString:@""];
976     if (stripKey && [stripKey length] > 0) {
977         // First of all try to lookup key in the color dictionary; note that
978         // all keys in this dictionary are lowercase with no whitespace.
979         id obj = [colorDict objectForKey:stripKey];
980         if (obj) return [obj intValue];
982         // The key was not in the dictionary; is it perhaps of the form
983         // #rrggbb?
984         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
985             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
986             [scanner setScanLocation:1];
987             unsigned hex = 0;
988             if ([scanner scanHexInt:&hex]) {
989                 return (int)hex;
990             }
991         }
993         // As a last resort, check if it is one of the system defined colors.
994         // The keys in this dictionary are also lowercase with no whitespace.
995         obj = [sysColorDict objectForKey:stripKey];
996         if (obj) {
997             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
998             if (col) {
999                 float r, g, b, a;
1000                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
1001                 [col getRed:&r green:&g blue:&b alpha:&a];
1002                 return (((int)(r*255+.5f) & 0xff) << 16)
1003                      + (((int)(g*255+.5f) & 0xff) << 8)
1004                      +  ((int)(b*255+.5f) & 0xff);
1005             }
1006         }
1007     }
1009     NSLog(@"WARNING: No color with key %@ found.", stripKey);
1010     return INVALCOLOR;
1013 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
1015     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1016     id obj;
1018     while ((obj = [e nextObject])) {
1019         if ([value isEqual:obj])
1020             return YES;
1021     }
1023     return NO;
1026 - (void)enterFullscreen
1028     [self queueMessage:EnterFullscreenMsgID data:nil];
1031 - (void)leaveFullscreen
1033     [self queueMessage:LeaveFullscreenMsgID data:nil];
1036 - (void)updateModifiedFlag
1038     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1039     // vice versa.
1040     int msgid = [self checkForModifiedBuffers]
1041             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1043     [self queueMessage:msgid data:nil];
1046 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1048     // NOTE: This method might get called whenever the run loop is tended to.
1049     // Normal keyboard and mouse input is added to input buffers, so there is
1050     // no risk in handling these events directly (they return immediately, and
1051     // do not call any other Vim functions).  However, other events such
1052     // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
1053     // events which would cause this method to be called recursively.  This
1054     // in turn leads to various difficulties that we do not want to have to
1055     // deal with.  To avoid recursive calls here we add all events except
1056     // keyboard and mouse events to an input queue which is processed whenever
1057     // gui_mch_update() is called (see processInputQueue).
1059     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1061     // Don't flush too soon after receiving input or update speed will suffer.
1062     [lastFlushDate release];
1063     lastFlushDate = [[NSDate date] retain];
1065     // Handle keyboard and mouse input now.  All other events are queued.
1066     if (InsertTextMsgID == msgid) {
1067         [self handleInsertText:data];
1068     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1069         if (!data) return;
1070         const void *bytes = [data bytes];
1071         int mods = *((int*)bytes);  bytes += sizeof(int);
1072         int len = *((int*)bytes);  bytes += sizeof(int);
1073         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1074                                               encoding:NSUTF8StringEncoding];
1075         mods = eventModifierFlagsToVimModMask(mods);
1077         [self handleKeyDown:key modifiers:mods];
1079         [key release];
1080     } else if (ScrollWheelMsgID == msgid) {
1081         if (!data) return;
1082         const void *bytes = [data bytes];
1084         int row = *((int*)bytes);  bytes += sizeof(int);
1085         int col = *((int*)bytes);  bytes += sizeof(int);
1086         int flags = *((int*)bytes);  bytes += sizeof(int);
1087         float dy = *((float*)bytes);  bytes += sizeof(float);
1089         int button = MOUSE_5;
1090         if (dy > 0) button = MOUSE_4;
1092         flags = eventModifierFlagsToVimMouseModMask(flags);
1094         gui_send_mouse_event(button, col, row, NO, flags);
1095     } else if (MouseDownMsgID == msgid) {
1096         if (!data) return;
1097         const void *bytes = [data bytes];
1099         int row = *((int*)bytes);  bytes += sizeof(int);
1100         int col = *((int*)bytes);  bytes += sizeof(int);
1101         int button = *((int*)bytes);  bytes += sizeof(int);
1102         int flags = *((int*)bytes);  bytes += sizeof(int);
1103         int count = *((int*)bytes);  bytes += sizeof(int);
1105         button = eventButtonNumberToVimMouseButton(button);
1106         flags = eventModifierFlagsToVimMouseModMask(flags);
1108         gui_send_mouse_event(button, col, row, count>1, flags);
1109     } else if (MouseUpMsgID == msgid) {
1110         if (!data) return;
1111         const void *bytes = [data bytes];
1113         int row = *((int*)bytes);  bytes += sizeof(int);
1114         int col = *((int*)bytes);  bytes += sizeof(int);
1115         int flags = *((int*)bytes);  bytes += sizeof(int);
1117         flags = eventModifierFlagsToVimMouseModMask(flags);
1119         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1120     } else if (MouseDraggedMsgID == msgid) {
1121         if (!data) return;
1122         const void *bytes = [data bytes];
1124         int row = *((int*)bytes);  bytes += sizeof(int);
1125         int col = *((int*)bytes);  bytes += sizeof(int);
1126         int flags = *((int*)bytes);  bytes += sizeof(int);
1128         flags = eventModifierFlagsToVimMouseModMask(flags);
1130         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1131     } else if (MouseMovedMsgID == msgid) {
1132         const void *bytes = [data bytes];
1133         int row = *((int*)bytes);  bytes += sizeof(int);
1134         int col = *((int*)bytes);  bytes += sizeof(int);
1136         gui_mouse_moved(col, row);
1137     } else {
1138         // Not keyboard or mouse event, queue it and handle later.
1139         //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
1140         [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1141         [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
1142     }
1144     // See waitForInput: for an explanation of this flag.
1145     inputReceived = YES;
1148 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1150     // TODO: Get rid of this method?
1151     //NSLog(@"%s%@", _cmd, messages);
1153     unsigned i, count = [messages count];
1154     for (i = 0; i < count; i += 2) {
1155         int msgid = [[messages objectAtIndex:i] intValue];
1156         id data = [messages objectAtIndex:i+1];
1157         if ([data isEqual:[NSNull null]])
1158             data = nil;
1160         [self processInput:msgid data:data];
1161     }
1164 - (BOOL)checkForModifiedBuffers
1166     buf_T *buf;
1167     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
1168         if (bufIsChanged(buf)) {
1169             return YES;
1170         }
1171     }
1173     return NO;
1176 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1178     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1179         // If there is no pasteboard, return YES to indicate that there is text
1180         // to copy.
1181         if (!pboard)
1182             return YES;
1184         clip_copy_selection();
1186         // Get the text to put on the pasteboard.
1187         long_u llen = 0; char_u *str = 0;
1188         int type = clip_convert_selection(&str, &llen, &clip_star);
1189         if (type < 0)
1190             return NO;
1191         
1192         // TODO: Avoid overflow.
1193         int len = (int)llen;
1194 #if MM_ENABLE_CONV
1195         if (output_conv.vc_type != CONV_NONE) {
1196             char_u *conv_str = string_convert(&output_conv, str, &len);
1197             if (conv_str) {
1198                 vim_free(str);
1199                 str = conv_str;
1200             }
1201         }
1202 #endif
1204         NSString *string = [[NSString alloc]
1205             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1207         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1208         [pboard declareTypes:types owner:nil];
1209         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1210     
1211         [string release];
1212         vim_free(str);
1214         return ok;
1215     }
1217     return NO;
1220 - (oneway void)addReply:(in bycopy NSString *)reply
1221                  server:(in byref id <MMVimServerProtocol>)server
1223     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1225     // Replies might come at any time and in any order so we keep them in an
1226     // array inside a dictionary with the send port used as key.
1228     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1229     // HACK! Assume connection uses mach ports.
1230     int port = [(NSMachPort*)[conn sendPort] machPort];
1231     NSNumber *key = [NSNumber numberWithInt:port];
1233     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1234     if (!replies) {
1235         replies = [NSMutableArray array];
1236         [serverReplyDict setObject:replies forKey:key];
1237     }
1239     [replies addObject:reply];
1242 - (void)addInput:(in bycopy NSString *)input
1243                  client:(in byref id <MMVimClientProtocol>)client
1245     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1247     char_u *s = (char_u*)[input UTF8String];
1249 #if MM_ENABLE_CONV
1250     s = CONVERT_FROM_UTF8(s);
1251 #endif
1253     server_to_input_buf(s);
1255 #if MM_ENABLE_CONV
1256     CONVERT_FROM_UTF8_FREE(s);
1257 #endif
1259     [self addClient:(id)client];
1261     inputReceived = YES;
1264 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1265                  client:(in byref id <MMVimClientProtocol>)client
1267     //NSLog(@"evaluateExpression:%@ client:%@", expr, (id)client);
1269     NSString *eval = nil;
1270     char_u *s = (char_u*)[expr UTF8String];
1272 #if MM_ENABLE_CONV
1273     s = CONVERT_FROM_UTF8(s);
1274 #endif
1276     char_u *res = eval_client_expr_to_string(s);
1278 #if MM_ENABLE_CONV
1279     CONVERT_FROM_UTF8_FREE(s);
1280 #endif
1282     if (res != NULL) {
1283         s = res;
1284 #if MM_ENABLE_CONV
1285         s = CONVERT_TO_UTF8(s);
1286 #endif
1287         eval = [NSString stringWithUTF8String:(char*)s];
1288 #if MM_ENABLE_CONV
1289         CONVERT_TO_UTF8_FREE(s);
1290 #endif
1291         vim_free(res);
1292     }
1294     [self addClient:(id)client];
1296     return eval;
1299 - (void)registerServerWithName:(NSString *)name
1301     NSString *svrName = name;
1302     NSConnection *svrConn = [NSConnection defaultConnection];
1303     unsigned i;
1305     for (i = 0; i < MMServerMax; ++i) {
1306         NSString *connName = [self connectionNameFromServerName:svrName];
1308         if ([svrConn registerName:connName]) {
1309             //NSLog(@"Registered server with name: %@", svrName);
1311             // TODO: Set request/reply time-outs to something else?
1312             //
1313             // Don't wait for requests (time-out means that the message is
1314             // dropped).
1315             [svrConn setRequestTimeout:0];
1316             //[svrConn setReplyTimeout:MMReplyTimeout];
1317             [svrConn setRootObject:self];
1319             char_u *s = (char_u*)[svrName UTF8String];
1320 #if MM_ENABLE_CONV
1321             s = CONVERT_FROM_UTF8(s);
1322 #endif
1323             // NOTE: 'serverName' is a global variable
1324             serverName = vim_strsave(s);
1325 #if MM_ENABLE_CONV
1326             CONVERT_FROM_UTF8_FREE(s);
1327 #endif
1328 #ifdef FEAT_EVAL
1329             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1330 #endif
1331 #ifdef FEAT_TITLE
1332             need_maketitle = TRUE;
1333 #endif
1334             [self queueMessage:SetServerNameMsgID data:
1335                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1336             break;
1337         }
1339         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1340     }
1343 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1344                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1345               silent:(BOOL)silent
1347     // NOTE: If 'name' equals 'serverName' then the request is local (client
1348     // and server are the same).  This case is not handled separately, so a
1349     // connection will be set up anyway (this simplifies the code).
1351     NSConnection *conn = [self connectionForServerName:name];
1352     if (!conn) {
1353         if (!silent) {
1354             char_u *s = (char_u*)[name UTF8String];
1355 #if MM_ENABLE_CONV
1356             s = CONVERT_FROM_UTF8(s);
1357 #endif
1358             EMSG2(_(e_noserver), s);
1359 #if MM_ENABLE_CONV
1360             CONVERT_FROM_UTF8_FREE(s);
1361 #endif
1362         }
1363         return NO;
1364     }
1366     if (port) {
1367         // HACK! Assume connection uses mach ports.
1368         *port = [(NSMachPort*)[conn sendPort] machPort];
1369     }
1371     id proxy = [conn rootProxy];
1372     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1374     @try {
1375         if (expr) {
1376             NSString *eval = [proxy evaluateExpression:string client:self];
1377             if (reply) {
1378                 if (eval) {
1379                     char_u *r = (char_u*)[eval UTF8String];
1380 #if MM_ENABLE_CONV
1381                     r = CONVERT_FROM_UTF8(r);
1382 #endif
1383                     *reply = vim_strsave(r);
1384 #if MM_ENABLE_CONV
1385                     CONVERT_FROM_UTF8_FREE(r);
1386 #endif
1387                 } else {
1388                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1389                 }
1390             }
1392             if (!eval)
1393                 return NO;
1394         } else {
1395             [proxy addInput:string client:self];
1396         }
1397     }
1398     @catch (NSException *e) {
1399         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1400         return NO;
1401     }
1403     return YES;
1406 - (NSArray *)serverList
1408     NSArray *list = nil;
1410     if ([self connection]) {
1411         id proxy = [connection rootProxy];
1412         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1414         @try {
1415             list = [proxy serverList];
1416         }
1417         @catch (NSException *e) {
1418             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1419         }
1420     } else {
1421         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1422     }
1424     return list;
1427 - (NSString *)peekForReplyOnPort:(int)port
1429     //NSLog(@"%s%d", _cmd, port);
1431     NSNumber *key = [NSNumber numberWithInt:port];
1432     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1433     if (replies && [replies count]) {
1434         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1435         //        [replies objectAtIndex:0]);
1436         return [replies objectAtIndex:0];
1437     }
1439     //NSLog(@"    No replies");
1440     return nil;
1443 - (NSString *)waitForReplyOnPort:(int)port
1445     //NSLog(@"%s%d", _cmd, port);
1446     
1447     NSConnection *conn = [self connectionForServerPort:port];
1448     if (!conn)
1449         return nil;
1451     NSNumber *key = [NSNumber numberWithInt:port];
1452     NSMutableArray *replies = nil;
1453     NSString *reply = nil;
1455     // Wait for reply as long as the connection to the server is valid (unless
1456     // user interrupts wait with Ctrl-C).
1457     while (!got_int && [conn isValid] &&
1458             !(replies = [serverReplyDict objectForKey:key])) {
1459         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1460                                  beforeDate:[NSDate distantFuture]];
1461     }
1463     if (replies) {
1464         if ([replies count] > 0) {
1465             reply = [[replies objectAtIndex:0] retain];
1466             //NSLog(@"    Got reply: %@", reply);
1467             [replies removeObjectAtIndex:0];
1468             [reply autorelease];
1469         }
1471         if ([replies count] == 0)
1472             [serverReplyDict removeObjectForKey:key];
1473     }
1475     return reply;
1478 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1480     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1481     if (client) {
1482         @try {
1483             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1484             [client addReply:reply server:self];
1485             return YES;
1486         }
1487         @catch (NSException *e) {
1488             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1489         }
1490     } else {
1491         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1492     }
1494     return NO;
1497 @end // MMBackend
1501 @implementation MMBackend (Private)
1503 - (void)processInputQueue
1505     // NOTE: One of the input events may cause this method to be called
1506     // recursively, so copy the input queue to a local variable and clear it
1507     // before starting to process input events (otherwise we could get stuck in
1508     // an endless loop).
1509     NSArray *q = [inputQueue copy];
1510     unsigned i, count = [q count];
1512     [inputQueue removeAllObjects];
1514     for (i = 0; i < count-1; i += 2) {
1515         int msgid = [[q objectAtIndex:i] intValue];
1516         id data = [q objectAtIndex:i+1];
1517         if ([data isEqual:[NSNull null]])
1518             data = nil;
1520         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1521         [self handleInputEvent:msgid data:data];
1522     }
1524     [q release];
1525     //NSLog(@"Clear input event queue");
1528 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1530     // NOTE: Be careful with what you do in this method.  Ideally, a message
1531     // should be handled by adding something to the input buffer and returning
1532     // immediately.  If you call a Vim function then it should not enter a loop
1533     // waiting for key presses or in any other way block the process.  The
1534     // reason for this being that only one message can be processed at a time,
1535     // so if another message is received while processing, then the new message
1536     // is dropped.  See also the comment in processInput:data:.
1538     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1540     if (SelectTabMsgID == msgid) {
1541         if (!data) return;
1542         const void *bytes = [data bytes];
1543         int idx = *((int*)bytes) + 1;
1544         //NSLog(@"Selecting tab %d", idx);
1545         send_tabline_event(idx);
1546     } else if (CloseTabMsgID == msgid) {
1547         if (!data) return;
1548         const void *bytes = [data bytes];
1549         int idx = *((int*)bytes) + 1;
1550         //NSLog(@"Closing tab %d", idx);
1551         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1552     } else if (AddNewTabMsgID == msgid) {
1553         //NSLog(@"Adding new tab");
1554         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1555     } else if (DraggedTabMsgID == msgid) {
1556         if (!data) return;
1557         const void *bytes = [data bytes];
1558         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1559         // based.
1560         int idx = *((int*)bytes);
1562         tabpage_move(idx);
1563     } else if (SetTextDimensionsMsgID == msgid) {
1564         if (!data) return;
1565         const void *bytes = [data bytes];
1566         int rows = *((int*)bytes);  bytes += sizeof(int);
1567         int cols = *((int*)bytes);  bytes += sizeof(int);
1569         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1570         // gui_resize_shell(), so we have to manually set the rows and columns
1571         // here.  (MacVim doesn't change the rows and columns to avoid
1572         // inconsistent states between Vim and MacVim.)
1573         [self setRows:rows columns:cols];
1575         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1576         gui_resize_shell(cols, rows);
1577     } else if (ExecuteMenuMsgID == msgid) {
1578         if (!data) return;
1579         const void *bytes = [data bytes];
1580         int tag = *((int*)bytes);  bytes += sizeof(int);
1582         vimmenu_T *menu = (vimmenu_T*)tag;
1583         // TODO!  Make sure 'menu' is a valid menu pointer!
1584         if (menu) {
1585             gui_menu_cb(menu);
1586         }
1587     } else if (ToggleToolbarMsgID == msgid) {
1588         [self handleToggleToolbar];
1589     } else if (ScrollbarEventMsgID == msgid) {
1590         [self handleScrollbarEvent:data];
1591     } else if (SetFontMsgID == msgid) {
1592         [self handleSetFont:data];
1593     } else if (VimShouldCloseMsgID == msgid) {
1594         gui_shell_closed();
1595     } else if (DropFilesMsgID == msgid) {
1596         [self handleDropFiles:data];
1597     } else if (DropStringMsgID == msgid) {
1598         [self handleDropString:data];
1599     } else if (GotFocusMsgID == msgid) {
1600         if (!gui.in_focus)
1601             [self focusChange:YES];
1602     } else if (LostFocusMsgID == msgid) {
1603         if (gui.in_focus)
1604             [self focusChange:NO];
1605     } else if (SetMouseShapeMsgID == msgid) {
1606         const void *bytes = [data bytes];
1607         int shape = *((int*)bytes);  bytes += sizeof(int);
1608         update_mouseshape(shape);
1609     } else {
1610         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1611     }
1614 + (NSDictionary *)specialKeys
1616     static NSDictionary *specialKeys = nil;
1618     if (!specialKeys) {
1619         NSBundle *mainBundle = [NSBundle mainBundle];
1620         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1621                                               ofType:@"plist"];
1622         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1623     }
1625     return specialKeys;
1628 - (void)handleInsertText:(NSData *)data
1630     if (!data) return;
1632     NSString *key = [[NSString alloc] initWithData:data
1633                                           encoding:NSUTF8StringEncoding];
1634     char_u *str = (char_u*)[key UTF8String];
1635     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1637 #if MM_ENABLE_CONV
1638     char_u *conv_str = NULL;
1639     if (input_conv.vc_type != CONV_NONE) {
1640         conv_str = string_convert(&input_conv, str, &len);
1641         if (conv_str)
1642             str = conv_str;
1643     }
1644 #endif
1646     for (i = 0; i < len; ++i) {
1647         add_to_input_buf(str+i, 1);
1648         if (CSI == str[i]) {
1649             // NOTE: If the converted string contains the byte CSI, then it
1650             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1651             // won't work.
1652             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1653             add_to_input_buf(extra, 2);
1654         }
1655     }
1657 #if MM_ENABLE_CONV
1658     if (conv_str)
1659         vim_free(conv_str);
1660 #endif
1661     [key release];
1664 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1666     char_u special[3];
1667     char_u modChars[3];
1668     char_u *chars = (char_u*)[key UTF8String];
1669 #if MM_ENABLE_CONV
1670     char_u *conv_str = NULL;
1671 #endif
1672     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1674     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1675     // that new keys can easily be added.
1676     NSString *specialString = [[MMBackend specialKeys]
1677             objectForKey:key];
1678     if (specialString && [specialString length] > 1) {
1679         //NSLog(@"special key: %@", specialString);
1680         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1681                 [specialString characterAtIndex:1]);
1683         ikey = simplify_key(ikey, &mods);
1684         if (ikey == CSI)
1685             ikey = K_CSI;
1687         special[0] = CSI;
1688         special[1] = K_SECOND(ikey);
1689         special[2] = K_THIRD(ikey);
1691         chars = special;
1692         length = 3;
1693     } else if (1 == length && TAB == chars[0]) {
1694         // Tab is a trouble child:
1695         // - <Tab> is added to the input buffer as is
1696         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1697         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1698         //   to be converted to utf-8
1699         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1700         // - <C-Tab> is reserved by Mac OS X
1701         // - <D-Tab> is reserved by Mac OS X
1702         chars = special;
1703         special[0] = TAB;
1704         length = 1;
1706         if (mods & MOD_MASK_SHIFT) {
1707             mods &= ~MOD_MASK_SHIFT;
1708             special[0] = CSI;
1709             special[1] = K_SECOND(K_S_TAB);
1710             special[2] = K_THIRD(K_S_TAB);
1711             length = 3;
1712         } else if (mods & MOD_MASK_ALT) {
1713             int mtab = 0x80 | TAB;
1714             if (enc_utf8) {
1715                 // Convert to utf-8
1716                 special[0] = (mtab >> 6) + 0xc0;
1717                 special[1] = mtab & 0xbf;
1718                 length = 2;
1719             } else {
1720                 special[0] = mtab;
1721                 length = 1;
1722             }
1723             mods &= ~MOD_MASK_ALT;
1724         }
1725     } else if (length > 0) {
1726         unichar c = [key characterAtIndex:0];
1728         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1729         //        [key characterAtIndex:0], mods);
1731         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1732                 || (c == intr_char && intr_char != Ctrl_C))) {
1733             trash_input_buf();
1734             got_int = TRUE;
1735         }
1737         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1738         // cleared since they are already added to the key by the AppKit.
1739         // Unfortunately, the only way to deal with when to clear the modifiers
1740         // or not seems to be to have hard-wired rules like this.
1741         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1742                     || 0x9 == c || 0xd == c) ) {
1743             mods &= ~MOD_MASK_SHIFT;
1744             mods &= ~MOD_MASK_CTRL;
1745             //NSLog(@"clear shift ctrl");
1746         }
1748         // HACK!  All Option+key presses go via 'insert text' messages, except
1749         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1750         // not work to map to it.
1751         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1752             //NSLog(@"clear alt");
1753             mods &= ~MOD_MASK_ALT;
1754         }
1756 #if MM_ENABLE_CONV
1757         if (input_conv.vc_type != CONV_NONE) {
1758             conv_str = string_convert(&input_conv, chars, &length);
1759             if (conv_str)
1760                 chars = conv_str;
1761         }
1762 #endif
1763     }
1765     if (chars && length > 0) {
1766         if (mods) {
1767             //NSLog(@"adding mods: %d", mods);
1768             modChars[0] = CSI;
1769             modChars[1] = KS_MODIFIER;
1770             modChars[2] = mods;
1771             add_to_input_buf(modChars, 3);
1772         }
1774         //NSLog(@"add to input buf: 0x%x", chars[0]);
1775         // TODO: Check for CSI bytes?
1776         add_to_input_buf(chars, length);
1777     }
1779 #if MM_ENABLE_CONV
1780     if (conv_str)
1781         vim_free(conv_str);
1782 #endif
1785 - (void)queueMessage:(int)msgid data:(NSData *)data
1787     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1788     if (data)
1789         [outputQueue addObject:data];
1790     else
1791         [outputQueue addObject:[NSData data]];
1794 - (void)connectionDidDie:(NSNotification *)notification
1796     // If the main connection to MacVim is lost this means that MacVim was
1797     // either quit (by the user chosing Quit on the MacVim menu), or it has
1798     // crashed.  In either case our only option is to quit now.
1799     // TODO: Write backup file?
1801     //NSLog(@"A Vim process lost its connection to MacVim; quitting.");
1802     getout(0);
1805 - (void)blinkTimerFired:(NSTimer *)timer
1807     NSTimeInterval timeInterval = 0;
1809     [blinkTimer release];
1810     blinkTimer = nil;
1812     if (MMBlinkStateOn == blinkState) {
1813         gui_undraw_cursor();
1814         blinkState = MMBlinkStateOff;
1815         timeInterval = blinkOffInterval;
1816     } else if (MMBlinkStateOff == blinkState) {
1817         gui_update_cursor(TRUE, FALSE);
1818         blinkState = MMBlinkStateOn;
1819         timeInterval = blinkOnInterval;
1820     }
1822     if (timeInterval > 0) {
1823         blinkTimer = 
1824             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1825                                             selector:@selector(blinkTimerFired:)
1826                                             userInfo:nil repeats:NO] retain];
1827         [self flushQueue:YES];
1828     }
1831 - (void)focusChange:(BOOL)on
1833     gui_focus_change(on);
1836 - (void)handleToggleToolbar
1838     // If 'go' contains 'T', then remove it, else add it.
1840     char_u go[sizeof(GO_ALL)+2];
1841     char_u *p;
1842     int len;
1844     STRCPY(go, p_go);
1845     p = vim_strchr(go, GO_TOOLBAR);
1846     len = STRLEN(go);
1848     if (p != NULL) {
1849         char_u *end = go + len;
1850         while (p < end) {
1851             p[0] = p[1];
1852             ++p;
1853         }
1854     } else {
1855         go[len] = GO_TOOLBAR;
1856         go[len+1] = NUL;
1857     }
1859     set_option_value((char_u*)"guioptions", 0, go, 0);
1861     // Force screen redraw (does it have to be this complicated?).
1862     redraw_all_later(CLEAR);
1863     update_screen(NOT_VALID);
1864     setcursor();
1865     out_flush();
1866     gui_update_cursor(FALSE, FALSE);
1867     gui_mch_flush();
1870 - (void)handleScrollbarEvent:(NSData *)data
1872     if (!data) return;
1874     const void *bytes = [data bytes];
1875     long ident = *((long*)bytes);  bytes += sizeof(long);
1876     int hitPart = *((int*)bytes);  bytes += sizeof(int);
1877     float fval = *((float*)bytes);  bytes += sizeof(float);
1878     scrollbar_T *sb = gui_find_scrollbar(ident);
1880     if (sb) {
1881         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1882         long value = sb_info->value;
1883         long size = sb_info->size;
1884         long max = sb_info->max;
1885         BOOL isStillDragging = NO;
1886         BOOL updateKnob = YES;
1888         switch (hitPart) {
1889         case NSScrollerDecrementPage:
1890             value -= (size > 2 ? size - 2 : 1);
1891             break;
1892         case NSScrollerIncrementPage:
1893             value += (size > 2 ? size - 2 : 1);
1894             break;
1895         case NSScrollerDecrementLine:
1896             --value;
1897             break;
1898         case NSScrollerIncrementLine:
1899             ++value;
1900             break;
1901         case NSScrollerKnob:
1902             isStillDragging = YES;
1903             // fall through ...
1904         case NSScrollerKnobSlot:
1905             value = (long)(fval * (max - size + 1));
1906             // fall through ...
1907         default:
1908             updateKnob = NO;
1909             break;
1910         }
1912         //NSLog(@"value %d -> %d", sb_info->value, value);
1913         gui_drag_scrollbar(sb, value, isStillDragging);
1915         if (updateKnob) {
1916             // Dragging the knob or option+clicking automatically updates
1917             // the knob position (on the actual NSScroller), so we only
1918             // need to set the knob position in the other cases.
1919             if (sb->wp) {
1920                 // Update both the left&right vertical scrollbars.
1921                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1922                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1923                 [self setScrollbarThumbValue:value size:size max:max
1924                                   identifier:identLeft];
1925                 [self setScrollbarThumbValue:value size:size max:max
1926                                   identifier:identRight];
1927             } else {
1928                 // Update the horizontal scrollbar.
1929                 [self setScrollbarThumbValue:value size:size max:max
1930                                   identifier:ident];
1931             }
1932         }
1933     }
1936 - (void)handleSetFont:(NSData *)data
1938     if (!data) return;
1940     const void *bytes = [data bytes];
1941     float pointSize = *((float*)bytes);  bytes += sizeof(float);
1942     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1943     bytes += sizeof(unsigned);  // len not used
1945     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1946     [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1947     char_u *s = (char_u*)[name UTF8String];
1949 #if MM_ENABLE_CONV
1950     s = CONVERT_FROM_UTF8(s);
1951 #endif
1953     set_option_value((char_u*)"guifont", 0, s, 0);
1955 #if MM_ENABLE_CONV
1956     CONVERT_FROM_UTF8_FREE(s);
1957 #endif
1959     // Force screen redraw (does it have to be this complicated?).
1960     redraw_all_later(CLEAR);
1961     update_screen(NOT_VALID);
1962     setcursor();
1963     out_flush();
1964     gui_update_cursor(FALSE, FALSE);
1965     gui_mch_flush();
1968 - (void)handleDropFiles:(NSData *)data
1970     if (!data) return;
1972 #ifdef FEAT_DND
1973     const void *bytes = [data bytes];
1974     const void *end = [data bytes] + [data length];
1975     int n = *((int*)bytes);  bytes += sizeof(int);
1977     if (State & CMDLINE) {
1978         // HACK!  If Vim is in command line mode then the files names
1979         // should be added to the command line, instead of opening the
1980         // files in tabs.  This is taken care of by gui_handle_drop().
1981         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1982         if (fnames) {
1983             int i = 0;
1984             while (bytes < end && i < n) {
1985                 int len = *((int*)bytes);  bytes += sizeof(int);
1986                 char_u *s = (char_u*)bytes;
1987 #if MM_ENABLE_CONV
1988                 s = CONVERT_FROM_UTF8(s);
1989 #endif
1990                 fnames[i++] = vim_strsave(s);
1991 #if MM_ENABLE_CONV
1992                 CONVERT_FROM_UTF8_FREE(s);
1993 #endif
1994                 bytes += len;
1995             }
1997             // NOTE!  This function will free 'fnames'.
1998             // HACK!  It is assumed that the 'x' and 'y' arguments are
1999             // unused when in command line mode.
2000             gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
2001         }
2002     } else {
2003         // HACK!  I'm not sure how to get Vim to open a list of files in
2004         // tabs, so instead I create a ':tab drop' command with all the
2005         // files to open and execute it.
2006         NSMutableString *cmd = (n > 1)
2007                 ? [NSMutableString stringWithString:@":tab drop"]
2008                 : [NSMutableString stringWithString:@":drop"];
2010         int i;
2011         for (i = 0; i < n && bytes < end; ++i) {
2012             int len = *((int*)bytes);  bytes += sizeof(int);
2013             NSString *file = [NSString stringWithUTF8String:bytes];
2014             file = [file stringByEscapingSpecialFilenameCharacters];
2015             bytes += len;
2017             [cmd appendString:@" "];
2018             [cmd appendString:file];
2019         }
2021         // By going to the last tabpage we ensure that the new tabs will
2022         // appear last (if this call is left out, the taborder becomes
2023         // messy).
2024         goto_tabpage(9999);
2026         char_u *s = (char_u*)[cmd UTF8String];
2027 #if MM_ENABLE_CONV
2028         s = CONVERT_FROM_UTF8(s);
2029 #endif
2030         do_cmdline_cmd(s);
2031 #if MM_ENABLE_CONV
2032         CONVERT_FROM_UTF8_FREE(s);
2033 #endif
2035         // Force screen redraw (does it have to be this complicated?).
2036         // (This code was taken from the end of gui_handle_drop().)
2037         update_screen(NOT_VALID);
2038         setcursor();
2039         out_flush();
2040         gui_update_cursor(FALSE, FALSE);
2041         maketitle();
2042         gui_mch_flush();
2043     }
2044 #endif // FEAT_DND
2047 - (void)handleDropString:(NSData *)data
2049     if (!data) return;
2051 #ifdef FEAT_DND
2052     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2053     const void *bytes = [data bytes];
2054     int len = *((int*)bytes);  bytes += sizeof(int);
2055     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2057     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2058     NSRange range = { 0, [string length] };
2059     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2060                                          withString:@"\x0a" options:0
2061                                               range:range];
2062     if (0 == n) {
2063         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2064                                        options:0 range:range];
2065     }
2067     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2068     char_u *s = (char_u*)[string UTF8String];
2069 #if MM_ENABLE_CONV
2070     if (input_conv.vc_type != CONV_NONE)
2071         s = string_convert(&input_conv, s, &len);
2072 #endif
2073     dnd_yank_drag_data(s, len);
2074 #if MM_ENABLE_CONV
2075     if (input_conv.vc_type != CONV_NONE)
2076         vim_free(s);
2077 #endif
2078     add_to_input_buf(dropkey, sizeof(dropkey));
2079 #endif // FEAT_DND
2082 @end // MMBackend (Private)
2087 @implementation MMBackend (ClientServer)
2089 - (NSString *)connectionNameFromServerName:(NSString *)name
2091     NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2093     return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2094         lowercaseString];
2097 - (NSConnection *)connectionForServerName:(NSString *)name
2099     // TODO: Try 'name%d' if 'name' fails.
2100     NSString *connName = [self connectionNameFromServerName:name];
2101     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2103     if (!svrConn) {
2104         svrConn = [NSConnection connectionWithRegisteredName:connName
2105                                                            host:nil];
2106         // Try alternate server...
2107         if (!svrConn && alternateServerName) {
2108             //NSLog(@"  trying to connect to alternate server: %@",
2109             //        alternateServerName);
2110             connName = [self connectionNameFromServerName:alternateServerName];
2111             svrConn = [NSConnection connectionWithRegisteredName:connName
2112                                                             host:nil];
2113         }
2115         // Try looking for alternate servers...
2116         if (!svrConn) {
2117             //NSLog(@"  looking for alternate servers...");
2118             NSString *alt = [self alternateServerNameForName:name];
2119             if (alt != alternateServerName) {
2120                 //NSLog(@"  found alternate server: %@", string);
2121                 [alternateServerName release];
2122                 alternateServerName = [alt copy];
2123             }
2124         }
2126         // Try alternate server again...
2127         if (!svrConn && alternateServerName) {
2128             //NSLog(@"  trying to connect to alternate server: %@",
2129             //        alternateServerName);
2130             connName = [self connectionNameFromServerName:alternateServerName];
2131             svrConn = [NSConnection connectionWithRegisteredName:connName
2132                                                             host:nil];
2133         }
2135         if (svrConn) {
2136             [connectionNameDict setObject:svrConn forKey:connName];
2138             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2139             [[NSNotificationCenter defaultCenter] addObserver:self
2140                     selector:@selector(serverConnectionDidDie:)
2141                         name:NSConnectionDidDieNotification object:svrConn];
2142         }
2143     }
2145     return svrConn;
2148 - (NSConnection *)connectionForServerPort:(int)port
2150     NSConnection *conn;
2151     NSEnumerator *e = [connectionNameDict objectEnumerator];
2153     while ((conn = [e nextObject])) {
2154         // HACK! Assume connection uses mach ports.
2155         if (port == [(NSMachPort*)[conn sendPort] machPort])
2156             return conn;
2157     }
2159     return nil;
2162 - (void)serverConnectionDidDie:(NSNotification *)notification
2164     //NSLog(@"%s%@", _cmd, notification);
2166     NSConnection *svrConn = [notification object];
2168     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2169     [[NSNotificationCenter defaultCenter]
2170             removeObserver:self
2171                       name:NSConnectionDidDieNotification
2172                     object:svrConn];
2174     [connectionNameDict removeObjectsForKeys:
2175         [connectionNameDict allKeysForObject:svrConn]];
2177     // HACK! Assume connection uses mach ports.
2178     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2179     NSNumber *key = [NSNumber numberWithInt:port];
2181     [clientProxyDict removeObjectForKey:key];
2182     [serverReplyDict removeObjectForKey:key];
2185 - (void)addClient:(NSDistantObject *)client
2187     NSConnection *conn = [client connectionForProxy];
2188     // HACK! Assume connection uses mach ports.
2189     int port = [(NSMachPort*)[conn sendPort] machPort];
2190     NSNumber *key = [NSNumber numberWithInt:port];
2192     if (![clientProxyDict objectForKey:key]) {
2193         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2194         [clientProxyDict setObject:client forKey:key];
2195     }
2197     // NOTE: 'clientWindow' is a global variable which is used by <client>
2198     clientWindow = port;
2201 - (NSString *)alternateServerNameForName:(NSString *)name
2203     if (!(name && [name length] > 0))
2204         return nil;
2206     // Only look for alternates if 'name' doesn't end in a digit.
2207     unichar lastChar = [name characterAtIndex:[name length]-1];
2208     if (lastChar >= '0' && lastChar <= '9')
2209         return nil;
2211     // Look for alternates among all current servers.
2212     NSArray *list = [self serverList];
2213     if (!(list && [list count] > 0))
2214         return nil;
2216     // Filter out servers starting with 'name' and ending with a number. The
2217     // (?i) pattern ensures that the match is case insensitive.
2218     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2219     NSPredicate *pred = [NSPredicate predicateWithFormat:
2220             @"SELF MATCHES %@", pat];
2221     list = [list filteredArrayUsingPredicate:pred];
2222     if ([list count] > 0) {
2223         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2224         return [list objectAtIndex:0];
2225     }
2227     return nil;
2230 @end // MMBackend (ClientServer)
2235 @implementation NSString (MMServerNameCompare)
2236 - (NSComparisonResult)serverNameCompare:(NSString *)string
2238     return [self compare:string
2239                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2241 @end
2246 static int eventModifierFlagsToVimModMask(int modifierFlags)
2248     int modMask = 0;
2250     if (modifierFlags & NSShiftKeyMask)
2251         modMask |= MOD_MASK_SHIFT;
2252     if (modifierFlags & NSControlKeyMask)
2253         modMask |= MOD_MASK_CTRL;
2254     if (modifierFlags & NSAlternateKeyMask)
2255         modMask |= MOD_MASK_ALT;
2256     if (modifierFlags & NSCommandKeyMask)
2257         modMask |= MOD_MASK_CMD;
2259     return modMask;
2262 static int vimModMaskToEventModifierFlags(int mods)
2264     int flags = 0;
2266     if (mods & MOD_MASK_SHIFT)
2267         flags |= NSShiftKeyMask;
2268     if (mods & MOD_MASK_CTRL)
2269         flags |= NSControlKeyMask;
2270     if (mods & MOD_MASK_ALT)
2271         flags |= NSAlternateKeyMask;
2272     if (mods & MOD_MASK_CMD)
2273         flags |= NSCommandKeyMask;
2275     return flags;
2278 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2280     int modMask = 0;
2282     if (modifierFlags & NSShiftKeyMask)
2283         modMask |= MOUSE_SHIFT;
2284     if (modifierFlags & NSControlKeyMask)
2285         modMask |= MOUSE_CTRL;
2286     if (modifierFlags & NSAlternateKeyMask)
2287         modMask |= MOUSE_ALT;
2289     return modMask;
2292 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2294     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE,
2295             MOUSE_X1, MOUSE_X2 };
2297     return mouseButton[buttonNumber < 5 ? buttonNumber : 0];
2300 static int specialKeyToNSKey(int key)
2302     if (!IS_SPECIAL(key))
2303         return key;
2305     static struct {
2306         int special;
2307         int nskey;
2308     } sp2ns[] = {
2309         { K_UP, NSUpArrowFunctionKey },
2310         { K_DOWN, NSDownArrowFunctionKey },
2311         { K_LEFT, NSLeftArrowFunctionKey },
2312         { K_RIGHT, NSRightArrowFunctionKey },
2313         { K_F1, NSF1FunctionKey },
2314         { K_F2, NSF2FunctionKey },
2315         { K_F3, NSF3FunctionKey },
2316         { K_F4, NSF4FunctionKey },
2317         { K_F5, NSF5FunctionKey },
2318         { K_F6, NSF6FunctionKey },
2319         { K_F7, NSF7FunctionKey },
2320         { K_F8, NSF8FunctionKey },
2321         { K_F9, NSF9FunctionKey },
2322         { K_F10, NSF10FunctionKey },
2323         { K_F11, NSF11FunctionKey },
2324         { K_F12, NSF12FunctionKey },
2325         { K_F13, NSF13FunctionKey },
2326         { K_F14, NSF14FunctionKey },
2327         { K_F15, NSF15FunctionKey },
2328         { K_F16, NSF16FunctionKey },
2329         { K_F17, NSF17FunctionKey },
2330         { K_F18, NSF18FunctionKey },
2331         { K_F19, NSF19FunctionKey },
2332         { K_F20, NSF20FunctionKey },
2333         { K_F21, NSF21FunctionKey },
2334         { K_F22, NSF22FunctionKey },
2335         { K_F23, NSF23FunctionKey },
2336         { K_F24, NSF24FunctionKey },
2337         { K_F25, NSF25FunctionKey },
2338         { K_F26, NSF26FunctionKey },
2339         { K_F27, NSF27FunctionKey },
2340         { K_F28, NSF28FunctionKey },
2341         { K_F29, NSF29FunctionKey },
2342         { K_F30, NSF30FunctionKey },
2343         { K_F31, NSF31FunctionKey },
2344         { K_F32, NSF32FunctionKey },
2345         { K_F33, NSF33FunctionKey },
2346         { K_F34, NSF34FunctionKey },
2347         { K_F35, NSF35FunctionKey },
2348         { K_DEL, NSBackspaceCharacter },
2349         { K_BS, NSDeleteCharacter },
2350         { K_HOME, NSHomeFunctionKey },
2351         { K_END, NSEndFunctionKey },
2352         { K_PAGEUP, NSPageUpFunctionKey },
2353         { K_PAGEDOWN, NSPageDownFunctionKey }
2354     };
2356     int i;
2357     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2358         if (sp2ns[i].special == key)
2359             return sp2ns[i].nskey;
2360     }
2362     return 0;