Ensure starting glyph is valid at layout (fixes :set lines=9999 crash)
[MacVim/jjgod.git] / MMBackend.m
blob615a3c6dba5a61b5e1e85b8cc975548bd362ec7d
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)handleMessage:(int)msgid data:(NSData *)data;
60 + (NSDictionary *)specialKeys;
61 - (void)handleInsertText:(NSData *)data;
62 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
63 - (void)queueMessage:(int)msgid data:(NSData *)data;
64 - (void)connectionDidDie:(NSNotification *)notification;
65 - (void)blinkTimerFired:(NSTimer *)timer;
66 - (void)focusChange:(BOOL)on;
67 - (void)processInputBegin;
68 - (void)processInputEnd;
69 - (void)handleToggleToolbar;
70 - (void)handleScrollbarEvent:(NSData *)data;
71 - (void)handleSetFont:(NSData *)data;
72 - (void)handleDropFiles:(NSData *)data;
73 - (void)handleDropString:(NSData *)data;
74 @end
78 @interface MMBackend (ClientServer)
79 - (NSString *)connectionNameFromServerName:(NSString *)name;
80 - (NSConnection *)connectionForServerName:(NSString *)name;
81 - (NSConnection *)connectionForServerPort:(int)port;
82 - (void)serverConnectionDidDie:(NSNotification *)notification;
83 - (void)addClient:(NSDistantObject *)client;
84 - (NSString *)alternateServerNameForName:(NSString *)name;
85 @end
89 @implementation MMBackend
91 + (MMBackend *)sharedInstance
93     static MMBackend *singleton = nil;
94     return singleton ? singleton : (singleton = [MMBackend new]);
97 - (id)init
99     if ((self = [super init])) {
100         fontContainerRef = loadFonts();
102         queue = [[NSMutableArray alloc] init];
103 #if MM_USE_INPUT_QUEUE
104         inputQueue = [[NSMutableArray alloc] init];
105 #endif
106         drawData = [[NSMutableData alloc] initWithCapacity:1024];
107         connectionNameDict = [[NSMutableDictionary alloc] init];
108         clientProxyDict = [[NSMutableDictionary alloc] init];
109         serverReplyDict = [[NSMutableDictionary alloc] init];
111         NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
112                                                          ofType:@"plist"];
113         if (path) {
114             colorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
115                 retain];
116         } else {
117             NSLog(@"WARNING: Could not locate Colors.plist.");
118         }
120         path = [[NSBundle mainBundle] pathForResource:@"SystemColors"
121                                                ofType:@"plist"];
122         if (path) {
123             sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
124                 retain];
125         } else {
126             NSLog(@"WARNING: Could not locate SystemColors.plist.");
127         }
128     }
130     return self;
133 - (void)dealloc
135     //NSLog(@"%@ %s", [self className], _cmd);
137     [[NSNotificationCenter defaultCenter] removeObserver:self];
139     [blinkTimer release];  blinkTimer = nil;
140 #if MM_USE_INPUT_QUEUE
141     [inputQueue release];  inputQueue = nil;
142 #endif
143     [alternateServerName release];  alternateServerName = nil;
144     [serverReplyDict release];  serverReplyDict = nil;
145     [clientProxyDict release];  clientProxyDict = nil;
146     [connectionNameDict release];  connectionNameDict = nil;
147     [queue release];  queue = nil;
148     [drawData release];  drawData = nil;
149     [frontendProxy release];  frontendProxy = nil;
150     [connection release];  connection = nil;
151     [sysColorDict release];  sysColorDict = nil;
152     [colorDict release];  colorDict = nil;
154     [super dealloc];
157 - (void)setBackgroundColor:(int)color
159     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
162 - (void)setForegroundColor:(int)color
164     foregroundColor = MM_COLOR(color);
167 - (void)setSpecialColor:(int)color
169     specialColor = MM_COLOR(color);
172 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
174     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
175     defaultForegroundColor = MM_COLOR(fg);
177     NSMutableData *data = [NSMutableData data];
179     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
180     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
182     [self queueMessage:SetDefaultColorsMsgID data:data];
185 - (NSConnection *)connection
187     if (!connection) {
188         // NOTE!  If the name of the connection changes here it must also be
189         // updated in MMAppController.m.
190         NSString *name = [NSString stringWithFormat:@"%@-connection",
191                [[NSBundle mainBundle] bundleIdentifier]];
193         connection = [NSConnection connectionWithRegisteredName:name host:nil];
194         [connection retain];
195     }
197     // NOTE: 'connection' may be nil here.
198     return connection;
201 - (BOOL)checkin
203     if (![self connection]) {
204         NSBundle *mainBundle = [NSBundle mainBundle];
205 #if 0
206         NSString *path = [mainBundle bundlePath];
207         if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
208             NSLog(@"WARNING: Failed to launch GUI with path %@", path);
209             return NO;
210         }
211 #else
212         // HACK!  It would be preferable to launch the GUI using NSWorkspace,
213         // however I have not managed to figure out how to pass arguments using
214         // NSWorkspace.
215         //
216         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
217         // that the GUI won't be activated (or raised) so there is a hack in
218         // MMWindowController which always raises the app when a new window is
219         // opened.
220         NSMutableArray *args = [NSMutableArray arrayWithObjects:
221             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
222         NSString *exeName = [[mainBundle infoDictionary]
223                 objectForKey:@"CFBundleExecutable"];
224         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
225         if (!path) {
226             NSLog(@"ERROR: Could not find MacVim executable in bundle");
227             return NO;
228         }
230         [NSTask launchedTaskWithLaunchPath:path arguments:args];
231 #endif
233         // HACK!  The NSWorkspaceDidLaunchApplicationNotification does not work
234         // for tasks like this, so poll the mach bootstrap server until it
235         // returns a valid connection.  Also set a time-out date so that we
236         // don't get stuck doing this forever.
237         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
238         while (!connection &&
239                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
240         {
241             [[NSRunLoop currentRunLoop]
242                     runMode:NSDefaultRunLoopMode
243                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
245             // NOTE: This call will set 'connection' as a side-effect.
246             [self connection];
247         }
249         if (!connection) {
250             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
251             return NO;
252         }
253     }
255     id proxy = [connection rootProxy];
256     [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
258     [[NSNotificationCenter defaultCenter] addObserver:self
259             selector:@selector(connectionDidDie:)
260                 name:NSConnectionDidDieNotification object:connection];
262     int pid = [[NSProcessInfo processInfo] processIdentifier];
264     @try {
265         frontendProxy = [proxy connectBackend:self pid:pid];
266     }
267     @catch (NSException *e) {
268         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
269     }
271     if (frontendProxy) {
272         [frontendProxy retain];
273         [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
274     }
276     return connection && frontendProxy;
279 - (BOOL)openVimWindow
281     [self queueMessage:OpenVimWindowMsgID data:nil];
282     return YES;
285 - (void)clearAll
287     int type = ClearAllDrawType;
289     // Any draw commands in queue are effectively obsolete since this clearAll
290     // will negate any effect they have, therefore we may as well clear the
291     // draw queue.
292     [drawData setLength:0];
294     [drawData appendBytes:&type length:sizeof(int)];
297 - (void)clearBlockFromRow:(int)row1 column:(int)col1
298                     toRow:(int)row2 column:(int)col2
300     int type = ClearBlockDrawType;
302     [drawData appendBytes:&type length:sizeof(int)];
304     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
305     [drawData appendBytes:&row1 length:sizeof(int)];
306     [drawData appendBytes:&col1 length:sizeof(int)];
307     [drawData appendBytes:&row2 length:sizeof(int)];
308     [drawData appendBytes:&col2 length:sizeof(int)];
311 - (void)deleteLinesFromRow:(int)row count:(int)count
312               scrollBottom:(int)bottom left:(int)left right:(int)right
314     int type = DeleteLinesDrawType;
316     [drawData appendBytes:&type length:sizeof(int)];
318     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
319     [drawData appendBytes:&row length:sizeof(int)];
320     [drawData appendBytes:&count length:sizeof(int)];
321     [drawData appendBytes:&bottom length:sizeof(int)];
322     [drawData appendBytes:&left length:sizeof(int)];
323     [drawData appendBytes:&right length:sizeof(int)];
326 - (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
327                 flags:(int)flags
329     if (len <= 0) return;
331     int type = ReplaceStringDrawType;
333     [drawData appendBytes:&type length:sizeof(int)];
335     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
336     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
337     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
338     [drawData appendBytes:&row length:sizeof(int)];
339     [drawData appendBytes:&col length:sizeof(int)];
340     [drawData appendBytes:&flags length:sizeof(int)];
341     [drawData appendBytes:&len length:sizeof(int)];
342     [drawData appendBytes:s length:len];
345 - (void)insertLinesFromRow:(int)row count:(int)count
346               scrollBottom:(int)bottom left:(int)left right:(int)right
348     int type = InsertLinesDrawType;
350     [drawData appendBytes:&type length:sizeof(int)];
352     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
353     [drawData appendBytes:&row length:sizeof(int)];
354     [drawData appendBytes:&count length:sizeof(int)];
355     [drawData appendBytes:&bottom length:sizeof(int)];
356     [drawData appendBytes:&left length:sizeof(int)];
357     [drawData appendBytes:&right length:sizeof(int)];
360 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
361                fraction:(int)percent color:(int)color
363     int type = DrawCursorDrawType;
364     unsigned uc = MM_COLOR(color);
366     [drawData appendBytes:&type length:sizeof(int)];
368     [drawData appendBytes:&uc length:sizeof(unsigned)];
369     [drawData appendBytes:&row length:sizeof(int)];
370     [drawData appendBytes:&col length:sizeof(int)];
371     [drawData appendBytes:&shape length:sizeof(int)];
372     [drawData appendBytes:&percent length:sizeof(int)];
375 - (void)flushQueue:(BOOL)force
377     // NOTE! This method gets called a lot; if we were to flush every time it
378     // was called MacVim would feel unresponsive.  So there is a time out which
379     // ensures that the queue isn't flushed too often.
380     if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
381             < MMFlushTimeoutInterval
382             && [drawData length] < MMFlushQueueLenHint)
383         return;
385     if ([drawData length] > 0) {
386         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
387         [drawData setLength:0];
388     }
390     if ([queue count] > 0) {
391         @try {
392             [frontendProxy processCommandQueue:queue];
393         }
394         @catch (NSException *e) {
395             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
396         }
398         [queue removeAllObjects];
400         [lastFlushDate release];
401         lastFlushDate = [[NSDate date] retain];
402     }
405 - (BOOL)waitForInput:(int)milliseconds
407     NSDate *date = milliseconds > 0 ?
408             [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
409             [NSDate distantFuture];
411     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
413     // I know of no way to figure out if the run loop exited because input was
414     // found or because of a time out, so I need to manually indicate when
415     // input was received in processInput:data: and then reset it every time
416     // here.
417     BOOL yn = inputReceived;
418     inputReceived = NO;
420     return yn;
423 - (void)exit
425 #ifdef MAC_CLIENTSERVER
426     // The default connection is used for the client/server code.
427     [[NSConnection defaultConnection] setRootObject:nil];
428     [[NSConnection defaultConnection] invalidate];
429 #endif
431     // By invalidating the NSConnection the MMWindowController immediately
432     // finds out that the connection is down and as a result
433     // [MMWindowController connectionDidDie:] is invoked.
434     //NSLog(@"%@ %s", [self className], _cmd);
435     [[NSNotificationCenter defaultCenter] removeObserver:self];
436     [connection invalidate];
438     if (fontContainerRef) {
439         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
440         fontContainerRef = 0;
441     }
445 - (void)selectTab:(int)index
447     //NSLog(@"%s%d", _cmd, index);
449     index -= 1;
450     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
451     [self queueMessage:SelectTabMsgID data:data];
454 - (void)updateTabBar
456     //NSLog(@"%s", _cmd);
458     NSMutableData *data = [NSMutableData data];
460     int idx = tabpage_index(curtab) - 1;
461     [data appendBytes:&idx length:sizeof(int)];
463     tabpage_T *tp;
464     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
465         // This function puts the label of the tab in the global 'NameBuff'.
466         get_tabline_label(tp, FALSE);
467         char_u *s = NameBuff;
468         int len = STRLEN(s);
469         if (len <= 0) continue;
471 #if MM_ENABLE_CONV
472         s = CONVERT_TO_UTF8(s);
473 #endif
475         // Count the number of windows in the tabpage.
476         //win_T *wp = tp->tp_firstwin;
477         //int wincount;
478         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
480         //[data appendBytes:&wincount length:sizeof(int)];
481         [data appendBytes:&len length:sizeof(int)];
482         [data appendBytes:s length:len];
484 #if MM_ENABLE_CONV
485         CONVERT_TO_UTF8_FREE(s);
486 #endif
487     }
489     [self queueMessage:UpdateTabBarMsgID data:data];
492 - (BOOL)tabBarVisible
494     return tabBarVisible;
497 - (void)showTabBar:(BOOL)enable
499     tabBarVisible = enable;
501     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
502     [self queueMessage:msgid data:nil];
505 - (void)setRows:(int)rows columns:(int)cols
507     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
509     int dim[] = { rows, cols };
510     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
512     [self queueMessage:SetTextDimensionsMsgID data:data];
515 - (void)setWindowTitle:(char *)title
517     NSMutableData *data = [NSMutableData data];
518     int len = strlen(title);
519     if (len <= 0) return;
521     [data appendBytes:&len length:sizeof(int)];
522     [data appendBytes:title length:len];
524     [self queueMessage:SetWindowTitleMsgID data:data];
527 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
528                             saving:(int)saving
530     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
531     //        saving);
533     char_u *s = NULL;
534     NSString *ds = dir ? [NSString stringWithUTF8String:dir] : nil;
535     NSString *ts = title ? [NSString stringWithUTF8String:title] : nil;
536     @try {
537         [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
539         // Wait until a reply is sent from MMVimController.
540         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
541                                  beforeDate:[NSDate distantFuture]];
543         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
544             char_u *ret = (char_u*)[dialogReturn UTF8String];
545 #if MM_ENABLE_CONV
546             ret = CONVERT_FROM_UTF8(ret);
547 #endif
548             s = vim_strsave(ret);
549 #if MM_ENABLE_CONV
550             CONVERT_FROM_UTF8_FREE(ret);
551 #endif
552         }
554         [dialogReturn release];  dialogReturn = nil;
555     }
556     @catch (NSException *e) {
557         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
558     }
560     return (char *)s;
563 - (oneway void)setDialogReturn:(in bycopy id)obj
565     // NOTE: This is called by
566     //   - [MMVimController panelDidEnd:::], and
567     //   - [MMVimController alertDidEnd:::],
568     // to indicate that a save/open panel or alert has finished.
570     if (obj != dialogReturn) {
571         [dialogReturn release];
572         dialogReturn = [obj retain];
573     }
576 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
577                      buttons:(char *)btns textField:(char *)txtfield
579     int retval = 0;
580     NSString *message = nil, *text = nil, *textFieldString = nil;
581     NSArray *buttons = nil;
582     int style = NSInformationalAlertStyle;
584     if (VIM_WARNING == type) style = NSWarningAlertStyle;
585     else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
587     if (btns) {
588         NSString *btnString = [NSString stringWithUTF8String:btns];
589         buttons = [btnString componentsSeparatedByString:@"\n"];
590     }
591     if (title)
592         message = [NSString stringWithUTF8String:title];
593     if (msg) {
594         text = [NSString stringWithUTF8String:msg];
595         if (!message) {
596             // HACK! If there is a '\n\n' or '\n' sequence in the message, then
597             // make the part up to there into the title.  We only do this
598             // because Vim has lots of dialogs without a title and they look
599             // ugly that way.
600             // TODO: Fix the actual dialog texts.
601             NSRange eolRange = [text rangeOfString:@"\n\n"];
602             if (NSNotFound == eolRange.location)
603                 eolRange = [text rangeOfString:@"\n"];
604             if (NSNotFound != eolRange.location) {
605                 message = [text substringToIndex:eolRange.location];
606                 text = [text substringFromIndex:NSMaxRange(eolRange)];
607             }
608         }
609     }
610     if (txtfield)
611         textFieldString = [NSString stringWithUTF8String:txtfield];
613     @try {
614         [frontendProxy presentDialogWithStyle:style message:message
615                               informativeText:text buttonTitles:buttons
616                               textFieldString:textFieldString];
618         // Wait until a reply is sent from MMVimController.
619         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
620                                  beforeDate:[NSDate distantFuture]];
622         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
623                 && [dialogReturn count]) {
624             retval = [[dialogReturn objectAtIndex:0] intValue];
625             if (txtfield && [dialogReturn count] > 1) {
626                 NSString *retString = [dialogReturn objectAtIndex:1];
627                 char_u *ret = (char_u*)[retString UTF8String];
628 #if MM_ENABLE_CONV
629                 ret = CONVERT_FROM_UTF8(ret);
630 #endif
631                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
632 #if MM_ENABLE_CONV
633                 CONVERT_FROM_UTF8_FREE(ret);
634 #endif
635             }
636         }
638         [dialogReturn release]; dialogReturn = nil;
639     }
640     @catch (NSException *e) {
641         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
642     }
644     return retval;
647 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
648                atIndex:(int)index
650     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
651     //        name, index);
653     int namelen = name ? strlen(name) : 0;
654     NSMutableData *data = [NSMutableData data];
656     [data appendBytes:&tag length:sizeof(int)];
657     [data appendBytes:&parentTag length:sizeof(int)];
658     [data appendBytes:&namelen length:sizeof(int)];
659     if (namelen > 0) [data appendBytes:name length:namelen];
660     [data appendBytes:&index length:sizeof(int)];
662     [self queueMessage:AddMenuMsgID data:data];
665 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
666                        tip:(char *)tip icon:(char *)icon
667              keyEquivalent:(int)key modifiers:(int)mods
668                     action:(NSString *)action atIndex:(int)index
670     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
671     //        parentTag, name, tip, index);
673     int namelen = name ? strlen(name) : 0;
674     int tiplen = tip ? strlen(tip) : 0;
675     int iconlen = icon ? strlen(icon) : 0;
676     int eventFlags = vimModMaskToEventModifierFlags(mods);
677     int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
678     NSMutableData *data = [NSMutableData data];
680     key = specialKeyToNSKey(key);
682     [data appendBytes:&tag length:sizeof(int)];
683     [data appendBytes:&parentTag length:sizeof(int)];
684     [data appendBytes:&namelen length:sizeof(int)];
685     if (namelen > 0) [data appendBytes:name length:namelen];
686     [data appendBytes:&tiplen length:sizeof(int)];
687     if (tiplen > 0) [data appendBytes:tip length:tiplen];
688     [data appendBytes:&iconlen length:sizeof(int)];
689     if (iconlen > 0) [data appendBytes:icon length:iconlen];
690     [data appendBytes:&actionlen length:sizeof(int)];
691     if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
692     [data appendBytes:&index length:sizeof(int)];
693     [data appendBytes:&key length:sizeof(int)];
694     [data appendBytes:&eventFlags length:sizeof(int)];
696     [self queueMessage:AddMenuItemMsgID data:data];
699 - (void)removeMenuItemWithTag:(int)tag
701     NSMutableData *data = [NSMutableData data];
702     [data appendBytes:&tag length:sizeof(int)];
704     [self queueMessage:RemoveMenuItemMsgID data:data];
707 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
709     NSMutableData *data = [NSMutableData data];
711     [data appendBytes:&tag length:sizeof(int)];
712     [data appendBytes:&enabled length:sizeof(int)];
714     [self queueMessage:EnableMenuItemMsgID data:data];
717 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
719     int len = strlen(name);
720     int row = -1, col = -1;
722     if (len <= 0) return;
724     if (!mouse && curwin) {
725         row = curwin->w_wrow;
726         col = curwin->w_wcol;
727     }
729     NSMutableData *data = [NSMutableData data];
731     [data appendBytes:&row length:sizeof(int)];
732     [data appendBytes:&col length:sizeof(int)];
733     [data appendBytes:&len length:sizeof(int)];
734     [data appendBytes:name length:len];
736     [self queueMessage:ShowPopupMenuMsgID data:data];
739 - (void)showToolbar:(int)enable flags:(int)flags
741     NSMutableData *data = [NSMutableData data];
743     [data appendBytes:&enable length:sizeof(int)];
744     [data appendBytes:&flags length:sizeof(int)];
746     [self queueMessage:ShowToolbarMsgID data:data];
749 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
751     NSMutableData *data = [NSMutableData data];
753     [data appendBytes:&ident length:sizeof(long)];
754     [data appendBytes:&type length:sizeof(int)];
756     [self queueMessage:CreateScrollbarMsgID data:data];
759 - (void)destroyScrollbarWithIdentifier:(long)ident
761     NSMutableData *data = [NSMutableData data];
762     [data appendBytes:&ident length:sizeof(long)];
764     [self queueMessage:DestroyScrollbarMsgID data:data];
767 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
769     NSMutableData *data = [NSMutableData data];
771     [data appendBytes:&ident length:sizeof(long)];
772     [data appendBytes:&visible length:sizeof(int)];
774     [self queueMessage:ShowScrollbarMsgID data:data];
777 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
779     NSMutableData *data = [NSMutableData data];
781     [data appendBytes:&ident length:sizeof(long)];
782     [data appendBytes:&pos length:sizeof(int)];
783     [data appendBytes:&len length:sizeof(int)];
785     [self queueMessage:SetScrollbarPositionMsgID data:data];
788 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
789                     identifier:(long)ident
791     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
792     float prop = (float)size/(max+1);
793     if (fval < 0) fval = 0;
794     else if (fval > 1.0f) fval = 1.0f;
795     if (prop < 0) prop = 0;
796     else if (prop > 1.0f) prop = 1.0f;
798     NSMutableData *data = [NSMutableData data];
800     [data appendBytes:&ident length:sizeof(long)];
801     [data appendBytes:&fval length:sizeof(float)];
802     [data appendBytes:&prop length:sizeof(float)];
804     [self queueMessage:SetScrollbarThumbMsgID data:data];
807 - (BOOL)setFontWithName:(char *)name
809     NSString *fontName = MMDefaultFontName;
810     float size = MMDefaultFontSize;
811     BOOL parseFailed = NO;
813     if (name) {
814         fontName = [NSString stringWithUTF8String:name];
816         if ([fontName isEqual:@"*"]) {
817             // :set gfn=* shows the font panel.
818             do_cmdline_cmd((char_u*)":macaction orderFrontFontPanel:");
819             return NO;
820         }
822         NSArray *components = [fontName componentsSeparatedByString:@":"];
823         if ([components count] == 2) {
824             NSString *sizeString = [components lastObject];
825             if ([sizeString length] > 0
826                     && [sizeString characterAtIndex:0] == 'h') {
827                 sizeString = [sizeString substringFromIndex:1];
828                 if ([sizeString length] > 0) {
829                     size = [sizeString floatValue];
830                     fontName = [components objectAtIndex:0];
831                 }
832             } else {
833                 parseFailed = YES;
834             }
835         } else if ([components count] > 2) {
836             parseFailed = YES;
837         }
838     }
840     if (!parseFailed && [fontName length] > 0) {
841         if (size < 6 || size > 100) {
842             // Font size 0.0 tells NSFont to use the 'user default size'.
843             size = 0.0f;
844         }
846         NSFont *font = [NSFont fontWithName:fontName size:size];
848         if (!font && MMDefaultFontName == fontName) {
849             // If for some reason the MacVim default font is not in the app
850             // bundle, then fall back on the system default font.
851             size = 0;
852             font = [NSFont userFixedPitchFontOfSize:size];
853             fontName = [font displayName];
854         }
856         if (font) {
857             //NSLog(@"Setting font '%@' of size %.2f", fontName, size);
858             int len = [fontName
859                     lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
860             if (len > 0) {
861                 NSMutableData *data = [NSMutableData data];
863                 [data appendBytes:&size length:sizeof(float)];
864                 [data appendBytes:&len length:sizeof(int)];
865                 [data appendBytes:[fontName UTF8String] length:len];
867                 [self queueMessage:SetFontMsgID data:data];
868                 return YES;
869             }
870         }
871     }
873     //NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f",
874     //        fontName, size);
875     return NO;
878 - (void)executeActionWithName:(NSString *)name
880     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
882     if (len > 0) {
883         NSMutableData *data = [NSMutableData data];
885         [data appendBytes:&len length:sizeof(int)];
886         [data appendBytes:[name UTF8String] length:len];
888         [self queueMessage:ExecuteActionMsgID data:data];
889     }
892 - (void)setMouseShape:(int)shape
894     NSMutableData *data = [NSMutableData data];
895     [data appendBytes:&shape length:sizeof(int)];
896     [self queueMessage:SetMouseShapeMsgID data:data];
899 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
901     // Vim specifies times in milliseconds, whereas Cocoa wants them in
902     // seconds.
903     blinkWaitInterval = .001f*wait;
904     blinkOnInterval = .001f*on;
905     blinkOffInterval = .001f*off;
908 - (void)startBlink
910     if (blinkTimer) {
911         [blinkTimer invalidate];
912         [blinkTimer release];
913         blinkTimer = nil;
914     }
916     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
917             && gui.in_focus) {
918         blinkState = MMBlinkStateOn;
919         blinkTimer =
920             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
921                                               target:self
922                                             selector:@selector(blinkTimerFired:)
923                                             userInfo:nil repeats:NO] retain];
924         gui_update_cursor(TRUE, FALSE);
925         [self flushQueue:YES];
926     }
929 - (void)stopBlink
931     if (MMBlinkStateOff == blinkState) {
932         gui_update_cursor(TRUE, FALSE);
933         [self flushQueue:YES];
934     }
936     blinkState = MMBlinkStateNone;
939 - (void)adjustLinespace:(int)linespace
941     NSMutableData *data = [NSMutableData data];
942     [data appendBytes:&linespace length:sizeof(int)];
943     [self queueMessage:AdjustLinespaceMsgID data:data];
946 - (void)activate
948     [self queueMessage:ActivateMsgID data:nil];
951 - (int)lookupColorWithKey:(NSString *)key
953     if (!(key && [key length] > 0))
954         return INVALCOLOR;
956     NSString *stripKey = [[[[key lowercaseString]
957         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
958             componentsSeparatedByString:@" "]
959                componentsJoinedByString:@""];
961     if (stripKey && [stripKey length] > 0) {
962         // First of all try to lookup key in the color dictionary; note that
963         // all keys in this dictionary are lowercase with no whitespace.
964         id obj = [colorDict objectForKey:stripKey];
965         if (obj) return [obj intValue];
967         // The key was not in the dictionary; is it perhaps of the form
968         // #rrggbb?
969         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
970             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
971             [scanner setScanLocation:1];
972             unsigned hex = 0;
973             if ([scanner scanHexInt:&hex]) {
974                 return (int)hex;
975             }
976         }
978         // As a last resort, check if it is one of the system defined colors.
979         // The keys in this dictionary are also lowercase with no whitespace.
980         obj = [sysColorDict objectForKey:stripKey];
981         if (obj) {
982             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
983             if (col) {
984                 float r, g, b, a;
985                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
986                 [col getRed:&r green:&g blue:&b alpha:&a];
987                 return (((int)(r*255+.5f) & 0xff) << 16)
988                      + (((int)(g*255+.5f) & 0xff) << 8)
989                      +  ((int)(b*255+.5f) & 0xff);
990             }
991         }
992     }
994     NSLog(@"WARNING: No color with key %@ found.", stripKey);
995     return INVALCOLOR;
998 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
1000     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1001     id obj;
1003     while ((obj = [e nextObject])) {
1004         if ([value isEqual:obj])
1005             return YES;
1006     }
1008     return NO;
1011 - (void)enterFullscreen
1013     [self queueMessage:EnterFullscreenMsgID data:nil];
1016 - (void)leaveFullscreen
1018     [self queueMessage:LeaveFullscreenMsgID data:nil];
1021 - (void)updateModifiedFlag
1023     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1024     // vice versa.
1025     int msgid = [self checkForModifiedBuffers]
1026             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1028     [self queueMessage:msgid data:nil];
1031 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1033     // NOTE: This method might get called whenever the run loop is tended to.
1034     // Thus it might get called whilst input is being processed.  Normally this
1035     // is not a problem, but if it gets called often then it might become
1036     // dangerous.  E.g. say a message causes the screen to be redrawn and then
1037     // another message is received causing another simultaneous screen redraw;
1038     // this is not good.  To deal with this problem at the moment, we simply
1039     // drop messages that are received while other input is being processed.
1040     if (inProcessInput) {
1041 #if MM_USE_INPUT_QUEUE
1042         [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1043         [inputQueue addObject:data];
1044 #else
1045         // Just drop the input
1046         //NSLog(@"WARNING: Dropping input in %s", _cmd);
1047 #endif
1048     } else {
1049         [self processInputBegin];
1050         [self handleMessage:msgid data:data];
1051         [self processInputEnd];
1052     }
1055 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1057     // NOTE: See comment in processInput:data:.
1058     unsigned i, count = [messages count];
1059     if (count % 2) {
1060         NSLog(@"WARNING: [messages count] is odd in %s", _cmd);
1061         return;
1062     }
1064     if (inProcessInput) {
1065 #if MM_USE_INPUT_QUEUE
1066         [inputQueue addObjectsFromArray:messages];
1067 #else
1068         // Just drop the input
1069         //NSLog(@"WARNING: Dropping input in %s", _cmd);
1070 #endif
1071     } else {
1072         [self processInputBegin];
1074         for (i = 0; i < count; i += 2) {
1075             int msgid = [[messages objectAtIndex:i] intValue];
1076             id data = [messages objectAtIndex:i+1];
1077             if ([data isEqual:[NSNull null]])
1078                 data = nil;
1080             [self handleMessage:msgid data:data];
1081         }
1083         [self processInputEnd];
1084     }
1087 - (BOOL)checkForModifiedBuffers
1089     buf_T *buf;
1090     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
1091         if (bufIsChanged(buf)) {
1092             return YES;
1093         }
1094     }
1096     return NO;
1099 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1101     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1102         // If there is no pasteboard, return YES to indicate that there is text
1103         // to copy.
1104         if (!pboard)
1105             return YES;
1107         clip_copy_selection();
1109         // Get the text to put on the pasteboard.
1110         long_u llen = 0; char_u *str = 0;
1111         int type = clip_convert_selection(&str, &llen, &clip_star);
1112         if (type < 0)
1113             return NO;
1114         
1115         // TODO: Avoid overflow.
1116         int len = (int)llen;
1117 #if MM_ENABLE_CONV
1118         if (output_conv.vc_type != CONV_NONE) {
1119             char_u *conv_str = string_convert(&output_conv, str, &len);
1120             if (conv_str) {
1121                 vim_free(str);
1122                 str = conv_str;
1123             }
1124         }
1125 #endif
1127         NSString *string = [[NSString alloc]
1128             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1130         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1131         [pboard declareTypes:types owner:nil];
1132         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1133     
1134         [string release];
1135         vim_free(str);
1137         return ok;
1138     }
1140     return NO;
1143 - (oneway void)addReply:(in bycopy NSString *)reply
1144                  server:(in byref id <MMVimServerProtocol>)server
1146     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1148     // Replies might come at any time and in any order so we keep them in an
1149     // array inside a dictionary with the send port used as key.
1151     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1152     // HACK! Assume connection uses mach ports.
1153     int port = [(NSMachPort*)[conn sendPort] machPort];
1154     NSNumber *key = [NSNumber numberWithInt:port];
1156     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1157     if (!replies) {
1158         replies = [NSMutableArray array];
1159         [serverReplyDict setObject:replies forKey:key];
1160     }
1162     [replies addObject:reply];
1165 - (void)addInput:(in bycopy NSString *)input
1166                  client:(in byref id <MMVimClientProtocol>)client
1168     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1170     char_u *s = (char_u*)[input UTF8String];
1172 #if MM_ENABLE_CONV
1173     s = CONVERT_FROM_UTF8(s);
1174 #endif
1176     server_to_input_buf(s);
1178 #if MM_ENABLE_CONV
1179     CONVERT_FROM_UTF8_FREE(s);
1180 #endif
1182     [self addClient:(id)client];
1184     inputReceived = YES;
1187 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1188                  client:(in byref id <MMVimClientProtocol>)client
1190     //NSLog(@"evaluateExpression:%@ client:%@", expr, (id)client);
1192     NSString *eval = nil;
1193     char_u *s = (char_u*)[expr UTF8String];
1195 #if MM_ENABLE_CONV
1196     s = CONVERT_FROM_UTF8(s);
1197 #endif
1199     char_u *res = eval_client_expr_to_string(s);
1201 #if MM_ENABLE_CONV
1202     CONVERT_FROM_UTF8_FREE(s);
1203 #endif
1205     if (res != NULL) {
1206         s = res;
1207 #if MM_ENABLE_CONV
1208         s = CONVERT_TO_UTF8(s);
1209 #endif
1210         eval = [NSString stringWithUTF8String:(char*)s];
1211 #if MM_ENABLE_CONV
1212         CONVERT_TO_UTF8_FREE(s);
1213 #endif
1214         vim_free(res);
1215     }
1217     [self addClient:(id)client];
1219     return eval;
1222 - (void)registerServerWithName:(NSString *)name
1224     NSString *svrName = name;
1225     NSConnection *svrConn = [NSConnection defaultConnection];
1226     unsigned i;
1228     for (i = 0; i < MMServerMax; ++i) {
1229         NSString *connName = [self connectionNameFromServerName:svrName];
1231         if ([svrConn registerName:connName]) {
1232             //NSLog(@"Registered server with name: %@", svrName);
1234             // TODO: Set request/reply time-outs to something else?
1235             //
1236             // Don't wait for requests (time-out means that the message is
1237             // dropped).
1238             [svrConn setRequestTimeout:0];
1239             //[svrConn setReplyTimeout:MMReplyTimeout];
1240             [svrConn setRootObject:self];
1242             char_u *s = (char_u*)[svrName UTF8String];
1243 #if MM_ENABLE_CONV
1244             s = CONVERT_FROM_UTF8(s);
1245 #endif
1246             // NOTE: 'serverName' is a global variable
1247             serverName = vim_strsave(s);
1248 #if MM_ENABLE_CONV
1249             CONVERT_FROM_UTF8_FREE(s);
1250 #endif
1251 #ifdef FEAT_EVAL
1252             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1253 #endif
1254 #ifdef FEAT_TITLE
1255             need_maketitle = TRUE;
1256 #endif
1257             [self queueMessage:SetServerNameMsgID data:
1258                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1259             break;
1260         }
1262         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1263     }
1266 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1267                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1268               silent:(BOOL)silent
1270     // NOTE: If 'name' equals 'serverName' then the request is local (client
1271     // and server are the same).  This case is not handled separately, so a
1272     // connection will be set up anyway (this simplifies the code).
1274     NSConnection *conn = [self connectionForServerName:name];
1275     if (!conn) {
1276         if (!silent) {
1277             char_u *s = (char_u*)[name UTF8String];
1278 #if MM_ENABLE_CONV
1279             s = CONVERT_FROM_UTF8(s);
1280 #endif
1281             EMSG2(_(e_noserver), s);
1282 #if MM_ENABLE_CONV
1283             CONVERT_FROM_UTF8_FREE(s);
1284 #endif
1285         }
1286         return NO;
1287     }
1289     if (port) {
1290         // HACK! Assume connection uses mach ports.
1291         *port = [(NSMachPort*)[conn sendPort] machPort];
1292     }
1294     id proxy = [conn rootProxy];
1295     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1297     @try {
1298         if (expr) {
1299             NSString *eval = [proxy evaluateExpression:string client:self];
1300             if (reply) {
1301                 if (eval) {
1302                     char_u *r = (char_u*)[eval UTF8String];
1303 #if MM_ENABLE_CONV
1304                     r = CONVERT_FROM_UTF8(r);
1305 #endif
1306                     *reply = vim_strsave(r);
1307 #if MM_ENABLE_CONV
1308                     CONVERT_FROM_UTF8_FREE(r);
1309 #endif
1310                 } else {
1311                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1312                 }
1313             }
1315             if (!eval)
1316                 return NO;
1317         } else {
1318             [proxy addInput:string client:self];
1319         }
1320     }
1321     @catch (NSException *e) {
1322         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1323         return NO;
1324     }
1326     return YES;
1329 - (NSArray *)serverList
1331     NSArray *list = nil;
1333     if ([self connection]) {
1334         id proxy = [connection rootProxy];
1335         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1337         @try {
1338             list = [proxy serverList];
1339         }
1340         @catch (NSException *e) {
1341             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1342         }
1343     } else {
1344         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1345     }
1347     return list;
1350 - (NSString *)peekForReplyOnPort:(int)port
1352     //NSLog(@"%s%d", _cmd, port);
1354     NSNumber *key = [NSNumber numberWithInt:port];
1355     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1356     if (replies && [replies count]) {
1357         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1358         //        [replies objectAtIndex:0]);
1359         return [replies objectAtIndex:0];
1360     }
1362     //NSLog(@"    No replies");
1363     return nil;
1366 - (NSString *)waitForReplyOnPort:(int)port
1368     //NSLog(@"%s%d", _cmd, port);
1369     
1370     NSConnection *conn = [self connectionForServerPort:port];
1371     if (!conn)
1372         return nil;
1374     NSNumber *key = [NSNumber numberWithInt:port];
1375     NSMutableArray *replies = nil;
1376     NSString *reply = nil;
1378     // Wait for reply as long as the connection to the server is valid (unless
1379     // user interrupts wait with Ctrl-C).
1380     while (!got_int && [conn isValid] &&
1381             !(replies = [serverReplyDict objectForKey:key])) {
1382         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1383                                  beforeDate:[NSDate distantFuture]];
1384     }
1386     if (replies) {
1387         if ([replies count] > 0) {
1388             reply = [[replies objectAtIndex:0] retain];
1389             //NSLog(@"    Got reply: %@", reply);
1390             [replies removeObjectAtIndex:0];
1391             [reply autorelease];
1392         }
1394         if ([replies count] == 0)
1395             [serverReplyDict removeObjectForKey:key];
1396     }
1398     return reply;
1401 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1403     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1404     if (client) {
1405         @try {
1406             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1407             [client addReply:reply server:self];
1408             return YES;
1409         }
1410         @catch (NSException *e) {
1411             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1412         }
1413     } else {
1414         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1415     }
1417     return NO;
1420 @end // MMBackend
1424 @implementation MMBackend (Private)
1426 - (void)handleMessage:(int)msgid data:(NSData *)data
1428     if (InsertTextMsgID == msgid) {
1429         [self handleInsertText:data];
1430     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1431         if (!data) return;
1432         const void *bytes = [data bytes];
1433         int mods = *((int*)bytes);  bytes += sizeof(int);
1434         int len = *((int*)bytes);  bytes += sizeof(int);
1435         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1436                                               encoding:NSUTF8StringEncoding];
1437         mods = eventModifierFlagsToVimModMask(mods);
1439         [self handleKeyDown:key modifiers:mods];
1441         [key release];
1442     } else if (SelectTabMsgID == msgid) {
1443         if (!data) return;
1444         const void *bytes = [data bytes];
1445         int idx = *((int*)bytes) + 1;
1446         //NSLog(@"Selecting tab %d", idx);
1447         send_tabline_event(idx);
1448     } else if (CloseTabMsgID == msgid) {
1449         if (!data) return;
1450         const void *bytes = [data bytes];
1451         int idx = *((int*)bytes) + 1;
1452         //NSLog(@"Closing tab %d", idx);
1453         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1454     } else if (AddNewTabMsgID == msgid) {
1455         //NSLog(@"Adding new tab");
1456         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1457     } else if (DraggedTabMsgID == msgid) {
1458         if (!data) return;
1459         const void *bytes = [data bytes];
1460         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1461         // based.
1462         int idx = *((int*)bytes);
1464         tabpage_move(idx);
1465     } else if (ScrollWheelMsgID == msgid) {
1466         if (!data) return;
1467         const void *bytes = [data bytes];
1469         int row = *((int*)bytes);  bytes += sizeof(int);
1470         int col = *((int*)bytes);  bytes += sizeof(int);
1471         int flags = *((int*)bytes);  bytes += sizeof(int);
1472         float dy = *((float*)bytes);  bytes += sizeof(float);
1474         int button = MOUSE_5;
1475         if (dy > 0) button = MOUSE_4;
1477         flags = eventModifierFlagsToVimMouseModMask(flags);
1479         gui_send_mouse_event(button, col, row, NO, flags);
1480     } else if (MouseDownMsgID == msgid) {
1481         if (!data) return;
1482         const void *bytes = [data bytes];
1484         int row = *((int*)bytes);  bytes += sizeof(int);
1485         int col = *((int*)bytes);  bytes += sizeof(int);
1486         int button = *((int*)bytes);  bytes += sizeof(int);
1487         int flags = *((int*)bytes);  bytes += sizeof(int);
1488         int count = *((int*)bytes);  bytes += sizeof(int);
1490         button = eventButtonNumberToVimMouseButton(button);
1491         flags = eventModifierFlagsToVimMouseModMask(flags);
1493         gui_send_mouse_event(button, col, row, count>1, flags);
1494     } else if (MouseUpMsgID == msgid) {
1495         if (!data) return;
1496         const void *bytes = [data bytes];
1498         int row = *((int*)bytes);  bytes += sizeof(int);
1499         int col = *((int*)bytes);  bytes += sizeof(int);
1500         int flags = *((int*)bytes);  bytes += sizeof(int);
1502         flags = eventModifierFlagsToVimMouseModMask(flags);
1504         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1505     } else if (MouseDraggedMsgID == msgid) {
1506         if (!data) return;
1507         const void *bytes = [data bytes];
1509         int row = *((int*)bytes);  bytes += sizeof(int);
1510         int col = *((int*)bytes);  bytes += sizeof(int);
1511         int flags = *((int*)bytes);  bytes += sizeof(int);
1513         flags = eventModifierFlagsToVimMouseModMask(flags);
1515         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1516     } else if (SetTextDimensionsMsgID == msgid) {
1517         if (!data) return;
1518         const void *bytes = [data bytes];
1519         int rows = *((int*)bytes);  bytes += sizeof(int);
1520         int cols = *((int*)bytes);  bytes += sizeof(int);
1522         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1523         // gui_resize_shell(), so we have to manually set the rows and columns
1524         // here.  (MacVim doesn't change the rows and columns to avoid
1525         // inconsistent states between Vim and MacVim.)
1526         [self setRows:rows columns:cols];
1528         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1529         gui_resize_shell(cols, rows);
1530     } else if (ExecuteMenuMsgID == msgid) {
1531         if (!data) return;
1532         const void *bytes = [data bytes];
1533         int tag = *((int*)bytes);  bytes += sizeof(int);
1535         vimmenu_T *menu = (vimmenu_T*)tag;
1536         // TODO!  Make sure 'menu' is a valid menu pointer!
1537         if (menu) {
1538             gui_menu_cb(menu);
1539         }
1540     } else if (ToggleToolbarMsgID == msgid) {
1541         [self handleToggleToolbar];
1542     } else if (ScrollbarEventMsgID == msgid) {
1543         [self handleScrollbarEvent:data];
1544     } else if (SetFontMsgID == msgid) {
1545         [self handleSetFont:data];
1546     } else if (VimShouldCloseMsgID == msgid) {
1547         gui_shell_closed();
1548     } else if (DropFilesMsgID == msgid) {
1549         [self handleDropFiles:data];
1550     } else if (DropStringMsgID == msgid) {
1551         [self handleDropString:data];
1552     } else if (GotFocusMsgID == msgid) {
1553         if (!gui.in_focus)
1554             [self focusChange:YES];
1555     } else if (LostFocusMsgID == msgid) {
1556         if (gui.in_focus)
1557             [self focusChange:NO];
1558     } else if (MouseMovedMsgID == msgid) {
1559         const void *bytes = [data bytes];
1560         int row = *((int*)bytes);  bytes += sizeof(int);
1561         int col = *((int*)bytes);  bytes += sizeof(int);
1563         gui_mouse_moved(col, row);
1564     } else if (SetMouseShapeMsgID == msgid) {
1565         const void *bytes = [data bytes];
1566         int shape = *((int*)bytes);  bytes += sizeof(int);
1567         update_mouseshape(shape);
1568     } else {
1569         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1570     }
1573 + (NSDictionary *)specialKeys
1575     static NSDictionary *specialKeys = nil;
1577     if (!specialKeys) {
1578         NSBundle *mainBundle = [NSBundle mainBundle];
1579         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1580                                               ofType:@"plist"];
1581         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1582     }
1584     return specialKeys;
1587 - (void)handleInsertText:(NSData *)data
1589     if (!data) return;
1591     NSString *key = [[NSString alloc] initWithData:data
1592                                           encoding:NSUTF8StringEncoding];
1593     char_u *str = (char_u*)[key UTF8String];
1594     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1596 #if MM_ENABLE_CONV
1597     char_u *conv_str = NULL;
1598     if (input_conv.vc_type != CONV_NONE) {
1599         conv_str = string_convert(&input_conv, str, &len);
1600         if (conv_str)
1601             str = conv_str;
1602     }
1603 #endif
1605     for (i = 0; i < len; ++i) {
1606         add_to_input_buf(str+i, 1);
1607         if (CSI == str[i]) {
1608             // NOTE: If the converted string contains the byte CSI, then it
1609             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1610             // won't work.
1611             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1612             add_to_input_buf(extra, 2);
1613         }
1614     }
1616 #if MM_ENABLE_CONV
1617     if (conv_str)
1618         vim_free(conv_str);
1619 #endif
1620     [key release];
1623 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1625     char_u special[3];
1626     char_u modChars[3];
1627     char_u *chars = (char_u*)[key UTF8String];
1628 #if MM_ENABLE_CONV
1629     char_u *conv_str = NULL;
1630 #endif
1631     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1633     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1634     // that new keys can easily be added.
1635     NSString *specialString = [[MMBackend specialKeys]
1636             objectForKey:key];
1637     if (specialString && [specialString length] > 1) {
1638         //NSLog(@"special key: %@", specialString);
1639         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1640                 [specialString characterAtIndex:1]);
1642         ikey = simplify_key(ikey, &mods);
1643         if (ikey == CSI)
1644             ikey = K_CSI;
1646         special[0] = CSI;
1647         special[1] = K_SECOND(ikey);
1648         special[2] = K_THIRD(ikey);
1650         chars = special;
1651         length = 3;
1652     } else if (1 == length && TAB == chars[0]) {
1653         // Tab is a trouble child:
1654         // - <Tab> is added to the input buffer as is
1655         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1656         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1657         //   to be converted to utf-8
1658         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1659         // - <C-Tab> is reserved by Mac OS X
1660         // - <D-Tab> is reserved by Mac OS X
1661         chars = special;
1662         special[0] = TAB;
1663         length = 1;
1665         if (mods & MOD_MASK_SHIFT) {
1666             mods &= ~MOD_MASK_SHIFT;
1667             special[0] = CSI;
1668             special[1] = K_SECOND(K_S_TAB);
1669             special[2] = K_THIRD(K_S_TAB);
1670             length = 3;
1671         } else if (mods & MOD_MASK_ALT) {
1672             int mtab = 0x80 | TAB;
1673             if (enc_utf8) {
1674                 // Convert to utf-8
1675                 special[0] = (mtab >> 6) + 0xc0;
1676                 special[1] = mtab & 0xbf;
1677                 length = 2;
1678             } else {
1679                 special[0] = mtab;
1680                 length = 1;
1681             }
1682             mods &= ~MOD_MASK_ALT;
1683         }
1684     } else if (length > 0) {
1685         unichar c = [key characterAtIndex:0];
1687         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1688         //        [key characterAtIndex:0], mods);
1690         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1691                 || (c == intr_char && intr_char != Ctrl_C))) {
1692             trash_input_buf();
1693             got_int = TRUE;
1694         }
1696         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1697         // cleared since they are already added to the key by the AppKit.
1698         // Unfortunately, the only way to deal with when to clear the modifiers
1699         // or not seems to be to have hard-wired rules like this.
1700         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1701                     || 0x9 == c || 0xd == c) ) {
1702             mods &= ~MOD_MASK_SHIFT;
1703             mods &= ~MOD_MASK_CTRL;
1704             //NSLog(@"clear shift ctrl");
1705         }
1707         // HACK!  All Option+key presses go via 'insert text' messages, except
1708         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1709         // not work to map to it.
1710         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1711             //NSLog(@"clear alt");
1712             mods &= ~MOD_MASK_ALT;
1713         }
1715 #if MM_ENABLE_CONV
1716         if (input_conv.vc_type != CONV_NONE) {
1717             conv_str = string_convert(&input_conv, chars, &length);
1718             if (conv_str)
1719                 chars = conv_str;
1720         }
1721 #endif
1722     }
1724     if (chars && length > 0) {
1725         if (mods) {
1726             //NSLog(@"adding mods: %d", mods);
1727             modChars[0] = CSI;
1728             modChars[1] = KS_MODIFIER;
1729             modChars[2] = mods;
1730             add_to_input_buf(modChars, 3);
1731         }
1733         //NSLog(@"add to input buf: 0x%x", chars[0]);
1734         // TODO: Check for CSI bytes?
1735         add_to_input_buf(chars, length);
1736     }
1738 #if MM_ENABLE_CONV
1739     if (conv_str)
1740         vim_free(conv_str);
1741 #endif
1744 - (void)queueMessage:(int)msgid data:(NSData *)data
1746     [queue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1747     if (data)
1748         [queue addObject:data];
1749     else
1750         [queue addObject:[NSData data]];
1753 - (void)connectionDidDie:(NSNotification *)notification
1755     // If the main connection to MacVim is lost this means that MacVim was
1756     // either quit (by the user chosing Quit on the MacVim menu), or it has
1757     // crashed.  In either case our only option is to quit now.
1758     // TODO: Write backup file?
1760     //NSLog(@"A Vim process lots its connection to MacVim; quitting.");
1761     getout(0);
1764 - (void)blinkTimerFired:(NSTimer *)timer
1766     NSTimeInterval timeInterval = 0;
1768     [blinkTimer release];
1769     blinkTimer = nil;
1771     if (MMBlinkStateOn == blinkState) {
1772         gui_undraw_cursor();
1773         blinkState = MMBlinkStateOff;
1774         timeInterval = blinkOffInterval;
1775     } else if (MMBlinkStateOff == blinkState) {
1776         gui_update_cursor(TRUE, FALSE);
1777         blinkState = MMBlinkStateOn;
1778         timeInterval = blinkOnInterval;
1779     }
1781     if (timeInterval > 0) {
1782         blinkTimer = 
1783             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1784                                             selector:@selector(blinkTimerFired:)
1785                                             userInfo:nil repeats:NO] retain];
1786         [self flushQueue:YES];
1787     }
1790 - (void)focusChange:(BOOL)on
1792     gui_focus_change(on);
1795 - (void)processInputBegin
1797     inProcessInput = YES;
1799     // Don't flush too soon or update speed will suffer.
1800     [lastFlushDate release];
1801     lastFlushDate = [[NSDate date] retain];
1804 - (void)processInputEnd
1806 #if MM_USE_INPUT_QUEUE
1807     int count = [inputQueue count];
1808     if (count % 2) {
1809         // TODO: This is troubling, but it is not hard to get Vim to end up
1810         // here.  Why does this happen?
1811         NSLog(@"WARNING: inputQueue has odd number of objects (%d)", count);
1812         [inputQueue removeAllObjects];
1813     } else if (count > 0) {
1814         // TODO: Dispatch these messages?  Maybe not; usually when the
1815         // 'inputQueue' is non-empty it means that a LOT of messages has been
1816         // sent simultaneously.  The only way this happens is when Vim is being
1817         // tormented, e.g. if the user holds down <D-`> to rapidly switch
1818         // windows.
1819         unsigned i;
1820         for (i = 0; i < count; i+=2) {
1821             int msgid = [[inputQueue objectAtIndex:i] intValue];
1822             NSLog(@"%s: Dropping message %s", _cmd, MessageStrings[msgid]);
1823         }
1825         [inputQueue removeAllObjects];
1826     }
1827 #endif
1829     inputReceived = YES;
1830     inProcessInput = NO;
1833 - (void)handleToggleToolbar
1835     // If 'go' contains 'T', then remove it, else add it.
1837     char_u go[sizeof(GO_ALL)+2];
1838     char_u *p;
1839     int len;
1841     STRCPY(go, p_go);
1842     p = vim_strchr(go, GO_TOOLBAR);
1843     len = STRLEN(go);
1845     if (p != NULL) {
1846         char_u *end = go + len;
1847         while (p < end) {
1848             p[0] = p[1];
1849             ++p;
1850         }
1851     } else {
1852         go[len] = GO_TOOLBAR;
1853         go[len+1] = NUL;
1854     }
1856     set_option_value((char_u*)"guioptions", 0, go, 0);
1858     // Force screen redraw (does it have to be this complicated?).
1859     redraw_all_later(CLEAR);
1860     update_screen(NOT_VALID);
1861     setcursor();
1862     out_flush();
1863     gui_update_cursor(FALSE, FALSE);
1864     gui_mch_flush();
1867 - (void)handleScrollbarEvent:(NSData *)data
1869     if (!data) return;
1871     const void *bytes = [data bytes];
1872     long ident = *((long*)bytes);  bytes += sizeof(long);
1873     int hitPart = *((int*)bytes);  bytes += sizeof(int);
1874     float fval = *((float*)bytes);  bytes += sizeof(float);
1875     scrollbar_T *sb = gui_find_scrollbar(ident);
1877     if (sb) {
1878         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1879         long value = sb_info->value;
1880         long size = sb_info->size;
1881         long max = sb_info->max;
1882         BOOL isStillDragging = NO;
1883         BOOL updateKnob = YES;
1885         switch (hitPart) {
1886         case NSScrollerDecrementPage:
1887             value -= (size > 2 ? size - 2 : 1);
1888             break;
1889         case NSScrollerIncrementPage:
1890             value += (size > 2 ? size - 2 : 1);
1891             break;
1892         case NSScrollerDecrementLine:
1893             --value;
1894             break;
1895         case NSScrollerIncrementLine:
1896             ++value;
1897             break;
1898         case NSScrollerKnob:
1899             isStillDragging = YES;
1900             // fall through ...
1901         case NSScrollerKnobSlot:
1902             value = (long)(fval * (max - size + 1));
1903             // fall through ...
1904         default:
1905             updateKnob = NO;
1906             break;
1907         }
1909         //NSLog(@"value %d -> %d", sb_info->value, value);
1910         gui_drag_scrollbar(sb, value, isStillDragging);
1912         if (updateKnob) {
1913             // Dragging the knob or option+clicking automatically updates
1914             // the knob position (on the actual NSScroller), so we only
1915             // need to set the knob position in the other cases.
1916             if (sb->wp) {
1917                 // Update both the left&right vertical scrollbars.
1918                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1919                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1920                 [self setScrollbarThumbValue:value size:size max:max
1921                                   identifier:identLeft];
1922                 [self setScrollbarThumbValue:value size:size max:max
1923                                   identifier:identRight];
1924             } else {
1925                 // Update the horizontal scrollbar.
1926                 [self setScrollbarThumbValue:value size:size max:max
1927                                   identifier:ident];
1928             }
1929         }
1930     }
1933 - (void)handleSetFont:(NSData *)data
1935     if (!data) return;
1937     const void *bytes = [data bytes];
1938     float pointSize = *((float*)bytes);  bytes += sizeof(float);
1939     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1940     bytes += sizeof(unsigned);  // len not used
1942     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1943     [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1944     char_u *s = (char_u*)[name UTF8String];
1946 #if MM_ENABLE_CONV
1947     s = CONVERT_FROM_UTF8(s);
1948 #endif
1950     set_option_value((char_u*)"guifont", 0, s, 0);
1952 #if MM_ENABLE_CONV
1953     CONVERT_FROM_UTF8_FREE(s);
1954 #endif
1956     // Force screen redraw (does it have to be this complicated?).
1957     redraw_all_later(CLEAR);
1958     update_screen(NOT_VALID);
1959     setcursor();
1960     out_flush();
1961     gui_update_cursor(FALSE, FALSE);
1962     gui_mch_flush();
1965 - (void)handleDropFiles:(NSData *)data
1967     if (!data) return;
1969 #ifdef FEAT_DND
1970     const void *bytes = [data bytes];
1971     const void *end = [data bytes] + [data length];
1972     int n = *((int*)bytes);  bytes += sizeof(int);
1974     if (State & CMDLINE) {
1975         // HACK!  If Vim is in command line mode then the files names
1976         // should be added to the command line, instead of opening the
1977         // files in tabs.  This is taken care of by gui_handle_drop().
1978         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1979         if (fnames) {
1980             int i = 0;
1981             while (bytes < end && i < n) {
1982                 int len = *((int*)bytes);  bytes += sizeof(int);
1983                 char_u *s = (char_u*)bytes;
1984 #if MM_ENABLE_CONV
1985                 s = CONVERT_FROM_UTF8(s);
1986 #endif
1987                 fnames[i++] = vim_strsave(s);
1988 #if MM_ENABLE_CONV
1989                 CONVERT_FROM_UTF8_FREE(s);
1990 #endif
1991                 bytes += len;
1992             }
1994             // NOTE!  This function will free 'fnames'.
1995             // HACK!  It is assumed that the 'x' and 'y' arguments are
1996             // unused when in command line mode.
1997             gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
1998         }
1999     } else {
2000         // HACK!  I'm not sure how to get Vim to open a list of files in
2001         // tabs, so instead I create a ':tab drop' command with all the
2002         // files to open and execute it.
2003         NSMutableString *cmd = (n > 1)
2004                 ? [NSMutableString stringWithString:@":tab drop"]
2005                 : [NSMutableString stringWithString:@":drop"];
2007         int i;
2008         for (i = 0; i < n && bytes < end; ++i) {
2009             int len = *((int*)bytes);  bytes += sizeof(int);
2010             NSString *file = [NSString stringWithUTF8String:bytes];
2011             file = [file stringByEscapingSpecialFilenameCharacters];
2012             bytes += len;
2014             [cmd appendString:@" "];
2015             [cmd appendString:file];
2016         }
2018         // By going to the last tabpage we ensure that the new tabs will
2019         // appear last (if this call is left out, the taborder becomes
2020         // messy).
2021         goto_tabpage(9999);
2023         char_u *s = (char_u*)[cmd UTF8String];
2024 #if MM_ENABLE_CONV
2025         s = CONVERT_FROM_UTF8(s);
2026 #endif
2027         do_cmdline_cmd(s);
2028 #if MM_ENABLE_CONV
2029         CONVERT_FROM_UTF8_FREE(s);
2030 #endif
2032         // Force screen redraw (does it have to be this complicated?).
2033         // (This code was taken from the end of gui_handle_drop().)
2034         update_screen(NOT_VALID);
2035         setcursor();
2036         out_flush();
2037         gui_update_cursor(FALSE, FALSE);
2038         maketitle();
2039         gui_mch_flush();
2040     }
2041 #endif // FEAT_DND
2044 - (void)handleDropString:(NSData *)data
2046     if (!data) return;
2048 #ifdef FEAT_DND
2049     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2050     const void *bytes = [data bytes];
2051     int len = *((int*)bytes);  bytes += sizeof(int);
2052     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2054     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2055     NSRange range = { 0, [string length] };
2056     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2057                                          withString:@"\x0a" options:0
2058                                               range:range];
2059     if (0 == n) {
2060         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2061                                        options:0 range:range];
2062     }
2064     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2065     char_u *s = (char_u*)[string UTF8String];
2066 #if MM_ENABLE_CONV
2067     if (input_conv.vc_type != CONV_NONE)
2068         s = string_convert(&input_conv, s, &len);
2069 #endif
2070     dnd_yank_drag_data(s, len);
2071 #if MM_ENABLE_CONV
2072     if (input_conv.vc_type != CONV_NONE)
2073         vim_free(s);
2074 #endif
2075     add_to_input_buf(dropkey, sizeof(dropkey));
2076 #endif // FEAT_DND
2079 @end // MMBackend (Private)
2084 @implementation MMBackend (ClientServer)
2086 - (NSString *)connectionNameFromServerName:(NSString *)name
2088     NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2090     return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2091         lowercaseString];
2094 - (NSConnection *)connectionForServerName:(NSString *)name
2096     // TODO: Try 'name%d' if 'name' fails.
2097     NSString *connName = [self connectionNameFromServerName:name];
2098     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2100     if (!svrConn) {
2101         svrConn = [NSConnection connectionWithRegisteredName:connName
2102                                                            host:nil];
2103         // Try alternate server...
2104         if (!svrConn && alternateServerName) {
2105             //NSLog(@"  trying to connect to alternate server: %@",
2106             //        alternateServerName);
2107             connName = [self connectionNameFromServerName:alternateServerName];
2108             svrConn = [NSConnection connectionWithRegisteredName:connName
2109                                                             host:nil];
2110         }
2112         // Try looking for alternate servers...
2113         if (!svrConn) {
2114             //NSLog(@"  looking for alternate servers...");
2115             NSString *alt = [self alternateServerNameForName:name];
2116             if (alt != alternateServerName) {
2117                 //NSLog(@"  found alternate server: %@", string);
2118                 [alternateServerName release];
2119                 alternateServerName = [alt copy];
2120             }
2121         }
2123         // Try alternate server again...
2124         if (!svrConn && alternateServerName) {
2125             //NSLog(@"  trying to connect to alternate server: %@",
2126             //        alternateServerName);
2127             connName = [self connectionNameFromServerName:alternateServerName];
2128             svrConn = [NSConnection connectionWithRegisteredName:connName
2129                                                             host:nil];
2130         }
2132         if (svrConn) {
2133             [connectionNameDict setObject:svrConn forKey:connName];
2135             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2136             [[NSNotificationCenter defaultCenter] addObserver:self
2137                     selector:@selector(serverConnectionDidDie:)
2138                         name:NSConnectionDidDieNotification object:svrConn];
2139         }
2140     }
2142     return svrConn;
2145 - (NSConnection *)connectionForServerPort:(int)port
2147     NSConnection *conn;
2148     NSEnumerator *e = [connectionNameDict objectEnumerator];
2150     while ((conn = [e nextObject])) {
2151         // HACK! Assume connection uses mach ports.
2152         if (port == [(NSMachPort*)[conn sendPort] machPort])
2153             return conn;
2154     }
2156     return nil;
2159 - (void)serverConnectionDidDie:(NSNotification *)notification
2161     //NSLog(@"%s%@", _cmd, notification);
2163     NSConnection *svrConn = [notification object];
2165     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2166     [[NSNotificationCenter defaultCenter]
2167             removeObserver:self
2168                       name:NSConnectionDidDieNotification
2169                     object:svrConn];
2171     [connectionNameDict removeObjectsForKeys:
2172         [connectionNameDict allKeysForObject:svrConn]];
2174     // HACK! Assume connection uses mach ports.
2175     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2176     NSNumber *key = [NSNumber numberWithInt:port];
2178     [clientProxyDict removeObjectForKey:key];
2179     [serverReplyDict removeObjectForKey:key];
2182 - (void)addClient:(NSDistantObject *)client
2184     NSConnection *conn = [client connectionForProxy];
2185     // HACK! Assume connection uses mach ports.
2186     int port = [(NSMachPort*)[conn sendPort] machPort];
2187     NSNumber *key = [NSNumber numberWithInt:port];
2189     if (![clientProxyDict objectForKey:key]) {
2190         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2191         [clientProxyDict setObject:client forKey:key];
2192     }
2194     // NOTE: 'clientWindow' is a global variable which is used by <client>
2195     clientWindow = port;
2198 - (NSString *)alternateServerNameForName:(NSString *)name
2200     if (!(name && [name length] > 0))
2201         return nil;
2203     // Only look for alternates if 'name' doesn't end in a digit.
2204     unichar lastChar = [name characterAtIndex:[name length]-1];
2205     if (lastChar >= '0' && lastChar <= '9')
2206         return nil;
2208     // Look for alternates among all current servers.
2209     NSArray *list = [self serverList];
2210     if (!(list && [list count] > 0))
2211         return nil;
2213     // Filter out servers starting with 'name' and ending with a number. The
2214     // (?i) pattern ensures that the match is case insensitive.
2215     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2216     NSPredicate *pred = [NSPredicate predicateWithFormat:
2217             @"SELF MATCHES %@", pat];
2218     list = [list filteredArrayUsingPredicate:pred];
2219     if ([list count] > 0) {
2220         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2221         return [list objectAtIndex:0];
2222     }
2224     return nil;
2227 @end // MMBackend (ClientServer)
2232 @implementation NSString (MMServerNameCompare)
2233 - (NSComparisonResult)serverNameCompare:(NSString *)string
2235     return [self compare:string
2236                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2238 @end
2243 static int eventModifierFlagsToVimModMask(int modifierFlags)
2245     int modMask = 0;
2247     if (modifierFlags & NSShiftKeyMask)
2248         modMask |= MOD_MASK_SHIFT;
2249     if (modifierFlags & NSControlKeyMask)
2250         modMask |= MOD_MASK_CTRL;
2251     if (modifierFlags & NSAlternateKeyMask)
2252         modMask |= MOD_MASK_ALT;
2253     if (modifierFlags & NSCommandKeyMask)
2254         modMask |= MOD_MASK_CMD;
2256     return modMask;
2259 static int vimModMaskToEventModifierFlags(int mods)
2261     int flags = 0;
2263     if (mods & MOD_MASK_SHIFT)
2264         flags |= NSShiftKeyMask;
2265     if (mods & MOD_MASK_CTRL)
2266         flags |= NSControlKeyMask;
2267     if (mods & MOD_MASK_ALT)
2268         flags |= NSAlternateKeyMask;
2269     if (mods & MOD_MASK_CMD)
2270         flags |= NSCommandKeyMask;
2272     return flags;
2275 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2277     int modMask = 0;
2279     if (modifierFlags & NSShiftKeyMask)
2280         modMask |= MOUSE_SHIFT;
2281     if (modifierFlags & NSControlKeyMask)
2282         modMask |= MOUSE_CTRL;
2283     if (modifierFlags & NSAlternateKeyMask)
2284         modMask |= MOUSE_ALT;
2286     return modMask;
2289 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2291     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE,
2292             MOUSE_X1, MOUSE_X2 };
2294     return mouseButton[buttonNumber < 5 ? buttonNumber : 0];
2297 static int specialKeyToNSKey(int key)
2299     if (!IS_SPECIAL(key))
2300         return key;
2302     static struct {
2303         int special;
2304         int nskey;
2305     } sp2ns[] = {
2306         { K_UP, NSUpArrowFunctionKey },
2307         { K_DOWN, NSDownArrowFunctionKey },
2308         { K_LEFT, NSLeftArrowFunctionKey },
2309         { K_RIGHT, NSRightArrowFunctionKey },
2310         { K_F1, NSF1FunctionKey },
2311         { K_F2, NSF2FunctionKey },
2312         { K_F3, NSF3FunctionKey },
2313         { K_F4, NSF4FunctionKey },
2314         { K_F5, NSF5FunctionKey },
2315         { K_F6, NSF6FunctionKey },
2316         { K_F7, NSF7FunctionKey },
2317         { K_F8, NSF8FunctionKey },
2318         { K_F9, NSF9FunctionKey },
2319         { K_F10, NSF10FunctionKey },
2320         { K_F11, NSF11FunctionKey },
2321         { K_F12, NSF12FunctionKey },
2322         { K_F13, NSF13FunctionKey },
2323         { K_F14, NSF14FunctionKey },
2324         { K_F15, NSF15FunctionKey },
2325         { K_F16, NSF16FunctionKey },
2326         { K_F17, NSF17FunctionKey },
2327         { K_F18, NSF18FunctionKey },
2328         { K_F19, NSF19FunctionKey },
2329         { K_F20, NSF20FunctionKey },
2330         { K_F21, NSF21FunctionKey },
2331         { K_F22, NSF22FunctionKey },
2332         { K_F23, NSF23FunctionKey },
2333         { K_F24, NSF24FunctionKey },
2334         { K_F25, NSF25FunctionKey },
2335         { K_F26, NSF26FunctionKey },
2336         { K_F27, NSF27FunctionKey },
2337         { K_F28, NSF28FunctionKey },
2338         { K_F29, NSF29FunctionKey },
2339         { K_F30, NSF30FunctionKey },
2340         { K_F31, NSF31FunctionKey },
2341         { K_F32, NSF32FunctionKey },
2342         { K_F33, NSF33FunctionKey },
2343         { K_F34, NSF34FunctionKey },
2344         { K_F35, NSF35FunctionKey },
2345         { K_DEL, NSBackspaceCharacter },
2346         { K_BS, NSDeleteCharacter },
2347         { K_HOME, NSHomeFunctionKey },
2348         { K_END, NSEndFunctionKey },
2349         { K_PAGEUP, NSPageUpFunctionKey },
2350         { K_PAGEDOWN, NSPageDownFunctionKey }
2351     };
2353     int i;
2354     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2355         if (sp2ns[i].special == key)
2356             return sp2ns[i].nskey;
2357     }
2359     return 0;