Ctrl-PageUp/PageDown now get passed on to Vim
[MacVim/jjgod.git] / MMBackend.m
blob74f37662498f9fcb285027e5096d8663980ff4aa
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
11 #import "MMBackend.h"
15 // This constant controls how often the command queue may be flushed.  If it is
16 // too small the app might feel unresponsive; if it is too large there might be
17 // long periods without the screen updating (e.g. when sourcing a large session
18 // file).  (The unit is seconds.)
19 static float MMFlushTimeoutInterval = 0.1f;
21 static unsigned MMServerMax = 1000;
23 // NOTE: The default font is bundled with the application.
24 static NSString *MMDefaultFontName = @"DejaVu Sans Mono";
25 static float MMDefaultFontSize = 12.0f;
27 // TODO: Move to separate file.
28 static int eventModifierFlagsToVimModMask(int modifierFlags);
29 static int vimModMaskToEventModifierFlags(int mods);
30 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
31 static int eventButtonNumberToVimMouseButton(int buttonNumber);
32 static int specialKeyToNSKey(int key);
34 enum {
35     MMBlinkStateNone = 0,
36     MMBlinkStateOn,
37     MMBlinkStateOff
42 @interface NSString (MMServerNameCompare)
43 - (NSComparisonResult)serverNameCompare:(NSString *)string;
44 @end
48 @interface MMBackend (Private)
49 - (void)handleMessage:(int)msgid data:(NSData *)data;
50 + (NSDictionary *)specialKeys;
51 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
52 - (void)queueMessage:(int)msgid data:(NSData *)data;
53 - (void)connectionDidDie:(NSNotification *)notification;
54 - (void)blinkTimerFired:(NSTimer *)timer;
55 - (void)focusChange:(BOOL)on;
56 - (void)processInputBegin;
57 - (void)processInputEnd;
58 - (NSString *)connectionNameFromServerName:(NSString *)name;
59 - (NSConnection *)connectionForServerName:(NSString *)name;
60 - (NSConnection *)connectionForServerPort:(int)port;
61 - (void)serverConnectionDidDie:(NSNotification *)notification;
62 - (void)addClient:(NSDistantObject *)client;
63 - (NSString *)alternateServerNameForName:(NSString *)name;
64 @end
68 @implementation MMBackend
70 + (MMBackend *)sharedInstance
72     static MMBackend *singleton = nil;
73     return singleton ? singleton : (singleton = [MMBackend new]);
76 - (id)init
78     if ((self = [super init])) {
79         fontContainerRef = loadFonts();
81         queue = [[NSMutableArray alloc] init];
82 #if MM_USE_INPUT_QUEUE
83         inputQueue = [[NSMutableArray alloc] init];
84 #endif
85         drawData = [[NSMutableData alloc] initWithCapacity:1024];
86         connectionNameDict = [[NSMutableDictionary alloc] init];
87         clientProxyDict = [[NSMutableDictionary alloc] init];
88         serverReplyDict = [[NSMutableDictionary alloc] init];
90         NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
91                                                          ofType:@"plist"];
92         if (path) {
93             colorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
94                 retain];
95         } else {
96             NSLog(@"WARNING: Could not locate Colors.plist.");
97         }
99         path = [[NSBundle mainBundle] pathForResource:@"SystemColors"
100                                                ofType:@"plist"];
101         if (path) {
102             sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
103                 retain];
104         } else {
105             NSLog(@"WARNING: Could not locate SystemColors.plist.");
106         }
107     }
109     return self;
112 - (void)dealloc
114     //NSLog(@"%@ %s", [self className], _cmd);
116     [[NSNotificationCenter defaultCenter] removeObserver:self];
118     [blinkTimer release];  blinkTimer = nil;
119 #if MM_USE_INPUT_QUEUE
120     [inputQueue release];  inputQueue = nil;
121 #endif
122     [alternateServerName release];  alternateServerName = nil;
123     [serverReplyDict release];  serverReplyDict = nil;
124     [clientProxyDict release];  clientProxyDict = nil;
125     [connectionNameDict release];  connectionNameDict = nil;
126     [queue release];  queue = nil;
127     [drawData release];  drawData = nil;
128     [frontendProxy release];  frontendProxy = nil;
129     [connection release];  connection = nil;
130     [sysColorDict release];  sysColorDict = nil;
131     [colorDict release];  colorDict = nil;
133     [super dealloc];
136 - (void)setBackgroundColor:(int)color
138     backgroundColor = color;
141 - (void)setForegroundColor:(int)color
143     foregroundColor = color;
146 - (void)setSpecialColor:(int)color
148     specialColor = color;
151 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
153     defaultBackgroundColor = bg;
154     defaultForegroundColor = fg;
156     NSMutableData *data = [NSMutableData data];
158     [data appendBytes:&bg length:sizeof(int)];
159     [data appendBytes:&fg length:sizeof(int)];
161     [self queueMessage:SetDefaultColorsMsgID data:data];
164 - (NSConnection *)connection
166     if (!connection) {
167         // NOTE!  If the name of the connection changes here it must also be
168         // updated in MMAppController.m.
169         NSString *name = [NSString stringWithFormat:@"%@-connection",
170                [[NSBundle mainBundle] bundleIdentifier]];
172         connection = [NSConnection connectionWithRegisteredName:name host:nil];
173         [connection retain];
174     }
176     // NOTE: 'connection' may be nil here.
177     return connection;
180 - (BOOL)checkin
182     if (![self connection]) {
183         NSBundle *mainBundle = [NSBundle mainBundle];
184 #if 0
185         NSString *path = [mainBundle bundlePath];
186         if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
187             NSLog(@"WARNING: Failed to launch GUI with path %@", path);
188             return NO;
189         }
190 #else
191         // HACK!  It would be preferable to launch the GUI using NSWorkspace,
192         // however I have not managed to figure out how to pass arguments using
193         // NSWorkspace.
194         //
195         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
196         // that the GUI won't be activated (or raised) so there is a hack in
197         // MMWindowController which always raises the app when a new window is
198         // opened.
199         NSMutableArray *args = [NSMutableArray arrayWithObjects:
200             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
201         NSString *exeName = [[mainBundle infoDictionary]
202                 objectForKey:@"CFBundleExecutable"];
203         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
204         if (!path) {
205             NSLog(@"ERROR: Could not find MacVim executable in bundle");
206             return NO;
207         }
209         [NSTask launchedTaskWithLaunchPath:path arguments:args];
210 #endif
212         // HACK!  The NSWorkspaceDidLaunchApplicationNotification does not work
213         // for tasks like this, so poll the mach bootstrap server until it
214         // returns a valid connection.  Also set a time-out date so that we
215         // don't get stuck doing this forever.
216         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
217         while (!connection &&
218                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
219         {
220             [[NSRunLoop currentRunLoop]
221                     runMode:NSDefaultRunLoopMode
222                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
224             // NOTE: This call will set 'connection' as a side-effect.
225             [self connection];
226         }
228         if (!connection) {
229             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
230             return NO;
231         }
232     }
234     id proxy = [connection rootProxy];
235     [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
237     [[NSNotificationCenter defaultCenter] addObserver:self
238             selector:@selector(connectionDidDie:)
239                 name:NSConnectionDidDieNotification object:connection];
241     int pid = [[NSProcessInfo processInfo] processIdentifier];
243     @try {
244         frontendProxy = [proxy connectBackend:self pid:pid];
245     }
246     @catch (NSException *e) {
247         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
248     }
250     if (frontendProxy) {
251         [frontendProxy retain];
252         [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
253     }
255     return connection && frontendProxy;
258 - (BOOL)openVimWindow
260     [self queueMessage:OpenVimWindowMsgID data:nil];
261     return YES;
264 - (void)clearAll
266     int type = ClearAllDrawType;
268     // Any draw commands in queue are effectively obsolete since this clearAll
269     // will negate any effect they have, therefore we may as well clear the
270     // draw queue.
271     [drawData setLength:0];
273     [drawData appendBytes:&type length:sizeof(int)];
275     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
278 - (void)clearBlockFromRow:(int)row1 column:(int)col1
279                     toRow:(int)row2 column:(int)col2
281     int type = ClearBlockDrawType;
283     [drawData appendBytes:&type length:sizeof(int)];
285     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
286     [drawData appendBytes:&row1 length:sizeof(int)];
287     [drawData appendBytes:&col1 length:sizeof(int)];
288     [drawData appendBytes:&row2 length:sizeof(int)];
289     [drawData appendBytes:&col2 length:sizeof(int)];
292 - (void)deleteLinesFromRow:(int)row count:(int)count
293               scrollBottom:(int)bottom left:(int)left right:(int)right
295     int type = DeleteLinesDrawType;
297     [drawData appendBytes:&type length:sizeof(int)];
299     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
300     [drawData appendBytes:&row length:sizeof(int)];
301     [drawData appendBytes:&count length:sizeof(int)];
302     [drawData appendBytes:&bottom length:sizeof(int)];
303     [drawData appendBytes:&left length:sizeof(int)];
304     [drawData appendBytes:&right length:sizeof(int)];
307 - (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
308                 flags:(int)flags
310     if (len <= 0) return;
312     int type = ReplaceStringDrawType;
314     [drawData appendBytes:&type length:sizeof(int)];
316     [drawData appendBytes:&backgroundColor length:sizeof(int)];
317     [drawData appendBytes:&foregroundColor length:sizeof(int)];
318     [drawData appendBytes:&specialColor length:sizeof(int)];
319     [drawData appendBytes:&row length:sizeof(int)];
320     [drawData appendBytes:&col length:sizeof(int)];
321     [drawData appendBytes:&flags length:sizeof(int)];
322     [drawData appendBytes:&len length:sizeof(int)];
323     [drawData appendBytes:s length:len];
326 - (void)insertLinesFromRow:(int)row count:(int)count
327               scrollBottom:(int)bottom left:(int)left right:(int)right
329     int type = InsertLinesDrawType;
331     [drawData appendBytes:&type length:sizeof(int)];
333     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
334     [drawData appendBytes:&row length:sizeof(int)];
335     [drawData appendBytes:&count length:sizeof(int)];
336     [drawData appendBytes:&bottom length:sizeof(int)];
337     [drawData appendBytes:&left length:sizeof(int)];
338     [drawData appendBytes:&right length:sizeof(int)];
341 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
342                fraction:(int)percent color:(int)color
344     int type = DrawCursorDrawType;
346     [drawData appendBytes:&type length:sizeof(int)];
348     [drawData appendBytes:&color length:sizeof(int)];
349     [drawData appendBytes:&row length:sizeof(int)];
350     [drawData appendBytes:&col length:sizeof(int)];
351     [drawData appendBytes:&shape length:sizeof(int)];
352     [drawData appendBytes:&percent length:sizeof(int)];
355 - (void)flushQueue:(BOOL)force
357     // NOTE! This method gets called a lot; if we were to flush every time it
358     // was called MacVim would feel unresponsive.  So there is a time out which
359     // ensures that the queue isn't flushed too often.
360     if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
361             < MMFlushTimeoutInterval)
362         return;
364     if ([drawData length] > 0) {
365         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
366         [drawData setLength:0];
367     }
369     if ([queue count] > 0) {
370         @try {
371             [frontendProxy processCommandQueue:queue];
372         }
373         @catch (NSException *e) {
374             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
375         }
377         [queue removeAllObjects];
379         [lastFlushDate release];
380         lastFlushDate = [[NSDate date] retain];
381     }
384 - (BOOL)waitForInput:(int)milliseconds
386     NSDate *date = milliseconds > 0 ?
387             [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
388             [NSDate distantFuture];
390     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
392     // I know of no way to figure out if the run loop exited because input was
393     // found or because of a time out, so I need to manually indicate when
394     // input was received in processInput:data: and then reset it every time
395     // here.
396     BOOL yn = inputReceived;
397     inputReceived = NO;
399     return yn;
402 - (void)exit
404 #ifdef MAC_CLIENTSERVER
405     // The default connection is used for the client/server code.
406     [[NSConnection defaultConnection] setRootObject:nil];
407     [[NSConnection defaultConnection] invalidate];
408 #endif
410     // By invalidating the NSConnection the MMWindowController immediately
411     // finds out that the connection is down and as a result
412     // [MMWindowController connectionDidDie:] is invoked.
413     //NSLog(@"%@ %s", [self className], _cmd);
414     [[NSNotificationCenter defaultCenter] removeObserver:self];
415     [connection invalidate];
417     if (fontContainerRef) {
418         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
419         fontContainerRef = 0;
420     }
424 - (void)selectTab:(int)index
426     //NSLog(@"%s%d", _cmd, index);
428     index -= 1;
429     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
430     [self queueMessage:SelectTabMsgID data:data];
433 - (void)updateTabBar
435     //NSLog(@"%s", _cmd);
437     NSMutableData *data = [NSMutableData data];
439     int idx = tabpage_index(curtab) - 1;
440     [data appendBytes:&idx length:sizeof(int)];
442     tabpage_T *tp;
443     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
444         // This function puts the label of the tab in the global 'NameBuff'.
445         get_tabline_label(tp, FALSE);
446         char_u *s = NameBuff;
447         int len = STRLEN(s);
448         if (len <= 0) continue;
450 #if MM_ENABLE_CONV
451         s = CONVERT_TO_UTF8(s);
452 #endif
454         // Count the number of windows in the tabpage.
455         //win_T *wp = tp->tp_firstwin;
456         //int wincount;
457         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
459         //[data appendBytes:&wincount length:sizeof(int)];
460         [data appendBytes:&len length:sizeof(int)];
461         [data appendBytes:s length:len];
463 #if MM_ENABLE_CONV
464         CONVERT_TO_UTF8_FREE(s);
465 #endif
466     }
468     [self queueMessage:UpdateTabBarMsgID data:data];
471 - (BOOL)tabBarVisible
473     return tabBarVisible;
476 - (void)showTabBar:(BOOL)enable
478     tabBarVisible = enable;
480     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
481     [self queueMessage:msgid data:nil];
484 - (void)setRows:(int)rows columns:(int)cols
486     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
488     int dim[] = { rows, cols };
489     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
491     [self queueMessage:SetTextDimensionsMsgID data:data];
494 - (void)setWindowTitle:(char *)title
496     NSMutableData *data = [NSMutableData data];
497     int len = strlen(title);
498     if (len <= 0) return;
500     [data appendBytes:&len length:sizeof(int)];
501     [data appendBytes:title length:len];
503     [self queueMessage:SetWindowTitleMsgID data:data];
506 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
507                             saving:(int)saving
509     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
510     //        saving);
512     char_u *s = NULL;
513     NSString *ds = dir ? [NSString stringWithUTF8String:dir] : nil;
514     NSString *ts = title ? [NSString stringWithUTF8String:title] : nil;
515     @try {
516         [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
518         // Wait until a reply is sent from MMVimController.
519         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
520                                  beforeDate:[NSDate distantFuture]];
522         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
523             char_u *ret = (char_u*)[dialogReturn UTF8String];
524 #if MM_ENABLE_CONV
525             ret = CONVERT_FROM_UTF8(ret);
526 #endif
527             s = vim_strsave(ret);
528 #if MM_ENABLE_CONV
529             CONVERT_FROM_UTF8_FREE(ret);
530 #endif
531         }
533         [dialogReturn release];  dialogReturn = nil;
534     }
535     @catch (NSException *e) {
536         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
537     }
539     return (char *)s;
542 - (oneway void)setDialogReturn:(in bycopy id)obj
544     // NOTE: This is called by
545     //   - [MMVimController panelDidEnd:::], and
546     //   - [MMVimController alertDidEnd:::],
547     // to indicate that a save/open panel or alert has finished.
549     if (obj != dialogReturn) {
550         [dialogReturn release];
551         dialogReturn = [obj retain];
552     }
555 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
556                      buttons:(char *)btns textField:(char *)txtfield
558     int retval = 0;
559     NSString *message = nil, *text = nil, *textFieldString = nil;
560     NSArray *buttons = nil;
561     int style = NSInformationalAlertStyle;
563     if (VIM_WARNING == type) style = NSWarningAlertStyle;
564     else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
566     if (btns) {
567         NSString *btnString = [NSString stringWithUTF8String:btns];
568         buttons = [btnString componentsSeparatedByString:@"\n"];
569     }
570     if (title)
571         message = [NSString stringWithUTF8String:title];
572     if (msg) {
573         text = [NSString stringWithUTF8String:msg];
574         if (!message) {
575             // HACK! If there is a '\n\n' or '\n' sequence in the message, then
576             // make the part up to there into the title.  We only do this
577             // because Vim has lots of dialogs without a title and they look
578             // ugly that way.
579             // TODO: Fix the actual dialog texts.
580             NSRange eolRange = [text rangeOfString:@"\n\n"];
581             if (NSNotFound == eolRange.location)
582                 eolRange = [text rangeOfString:@"\n"];
583             if (NSNotFound != eolRange.location) {
584                 message = [text substringToIndex:eolRange.location];
585                 text = [text substringFromIndex:NSMaxRange(eolRange)];
586             }
587         }
588     }
589     if (txtfield)
590         textFieldString = [NSString stringWithUTF8String:txtfield];
592     @try {
593         [frontendProxy presentDialogWithStyle:style message:message
594                               informativeText:text buttonTitles:buttons
595                               textFieldString:textFieldString];
597         // Wait until a reply is sent from MMVimController.
598         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
599                                  beforeDate:[NSDate distantFuture]];
601         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
602                 && [dialogReturn count]) {
603             retval = [[dialogReturn objectAtIndex:0] intValue];
604             if (txtfield && [dialogReturn count] > 1) {
605                 NSString *retString = [dialogReturn objectAtIndex:1];
606                 char_u *ret = (char_u*)[retString UTF8String];
607 #if MM_ENABLE_CONV
608                 ret = CONVERT_FROM_UTF8(ret);
609 #endif
610                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
611 #if MM_ENABLE_CONV
612                 CONVERT_FROM_UTF8_FREE(ret);
613 #endif
614             }
615         }
617         [dialogReturn release]; dialogReturn = nil;
618     }
619     @catch (NSException *e) {
620         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
621     }
623     return retval;
626 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
627                atIndex:(int)index
629     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
630     //        name, index);
632     int namelen = name ? strlen(name) : 0;
633     NSMutableData *data = [NSMutableData data];
635     [data appendBytes:&tag length:sizeof(int)];
636     [data appendBytes:&parentTag length:sizeof(int)];
637     [data appendBytes:&namelen length:sizeof(int)];
638     if (namelen > 0) [data appendBytes:name length:namelen];
639     [data appendBytes:&index length:sizeof(int)];
641     [self queueMessage:AddMenuMsgID data:data];
644 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
645                        tip:(char *)tip icon:(char *)icon
646              keyEquivalent:(int)key modifiers:(int)mods
647                     action:(NSString *)action atIndex:(int)index
649     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
650     //        parentTag, name, tip, index);
652     int namelen = name ? strlen(name) : 0;
653     int tiplen = tip ? strlen(tip) : 0;
654     int iconlen = icon ? strlen(icon) : 0;
655     int eventFlags = vimModMaskToEventModifierFlags(mods);
656     int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
657     NSMutableData *data = [NSMutableData data];
659     key = specialKeyToNSKey(key);
661     [data appendBytes:&tag length:sizeof(int)];
662     [data appendBytes:&parentTag length:sizeof(int)];
663     [data appendBytes:&namelen length:sizeof(int)];
664     if (namelen > 0) [data appendBytes:name length:namelen];
665     [data appendBytes:&tiplen length:sizeof(int)];
666     if (tiplen > 0) [data appendBytes:tip length:tiplen];
667     [data appendBytes:&iconlen length:sizeof(int)];
668     if (iconlen > 0) [data appendBytes:icon length:iconlen];
669     [data appendBytes:&actionlen length:sizeof(int)];
670     if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
671     [data appendBytes:&index length:sizeof(int)];
672     [data appendBytes:&key length:sizeof(int)];
673     [data appendBytes:&eventFlags length:sizeof(int)];
675     [self queueMessage:AddMenuItemMsgID data:data];
678 - (void)removeMenuItemWithTag:(int)tag
680     NSMutableData *data = [NSMutableData data];
681     [data appendBytes:&tag length:sizeof(int)];
683     [self queueMessage:RemoveMenuItemMsgID data:data];
686 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
688     NSMutableData *data = [NSMutableData data];
690     [data appendBytes:&tag length:sizeof(int)];
691     [data appendBytes:&enabled length:sizeof(int)];
693     [self queueMessage:EnableMenuItemMsgID data:data];
696 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
698     int len = strlen(name);
699     int row = -1, col = -1;
701     if (len <= 0) return;
703     if (!mouse && curwin) {
704         row = curwin->w_wrow;
705         col = curwin->w_wcol;
706     }
708     NSMutableData *data = [NSMutableData data];
710     [data appendBytes:&row length:sizeof(int)];
711     [data appendBytes:&col length:sizeof(int)];
712     [data appendBytes:&len length:sizeof(int)];
713     [data appendBytes:name length:len];
715     [self queueMessage:ShowPopupMenuMsgID data:data];
718 - (void)showToolbar:(int)enable flags:(int)flags
720     NSMutableData *data = [NSMutableData data];
722     [data appendBytes:&enable length:sizeof(int)];
723     [data appendBytes:&flags length:sizeof(int)];
725     [self queueMessage:ShowToolbarMsgID data:data];
728 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
730     NSMutableData *data = [NSMutableData data];
732     [data appendBytes:&ident length:sizeof(long)];
733     [data appendBytes:&type length:sizeof(int)];
735     [self queueMessage:CreateScrollbarMsgID data:data];
738 - (void)destroyScrollbarWithIdentifier:(long)ident
740     NSMutableData *data = [NSMutableData data];
741     [data appendBytes:&ident length:sizeof(long)];
743     [self queueMessage:DestroyScrollbarMsgID data:data];
746 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
748     NSMutableData *data = [NSMutableData data];
750     [data appendBytes:&ident length:sizeof(long)];
751     [data appendBytes:&visible length:sizeof(int)];
753     [self queueMessage:ShowScrollbarMsgID data:data];
756 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
758     NSMutableData *data = [NSMutableData data];
760     [data appendBytes:&ident length:sizeof(long)];
761     [data appendBytes:&pos length:sizeof(int)];
762     [data appendBytes:&len length:sizeof(int)];
764     [self queueMessage:SetScrollbarPositionMsgID data:data];
767 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
768                     identifier:(long)ident
770     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
771     float prop = (float)size/(max+1);
772     if (fval < 0) fval = 0;
773     else if (fval > 1.0f) fval = 1.0f;
774     if (prop < 0) prop = 0;
775     else if (prop > 1.0f) prop = 1.0f;
777     NSMutableData *data = [NSMutableData data];
779     [data appendBytes:&ident length:sizeof(long)];
780     [data appendBytes:&fval length:sizeof(float)];
781     [data appendBytes:&prop length:sizeof(float)];
783     [self queueMessage:SetScrollbarThumbMsgID data:data];
786 - (BOOL)setFontWithName:(char *)name
788     NSString *fontName = MMDefaultFontName;
789     float size = MMDefaultFontSize;
790     BOOL parseFailed = NO;
792     if (name) {
793         fontName = [NSString stringWithUTF8String:name];
795         if ([fontName isEqual:@"*"]) {
796             // :set gfn=* shows the font panel.
797             do_cmdline_cmd((char_u*)":action orderFrontFontPanel:");
798             return NO;
799         }
801         NSArray *components = [fontName componentsSeparatedByString:@":"];
802         if ([components count] == 2) {
803             NSString *sizeString = [components lastObject];
804             if ([sizeString length] > 0
805                     && [sizeString characterAtIndex:0] == 'h') {
806                 sizeString = [sizeString substringFromIndex:1];
807                 if ([sizeString length] > 0) {
808                     size = [sizeString floatValue];
809                     fontName = [components objectAtIndex:0];
810                 }
811             } else {
812                 parseFailed = YES;
813             }
814         } else if ([components count] > 2) {
815             parseFailed = YES;
816         }
817     }
819     if (!parseFailed && [fontName length] > 0) {
820         if (size < 6 || size > 100) {
821             // Font size 0.0 tells NSFont to use the 'user default size'.
822             size = 0.0f;
823         }
825         NSFont *font = [NSFont fontWithName:fontName size:size];
827         if (!font && MMDefaultFontName == fontName) {
828             // If for some reason the MacVim default font is not in the app
829             // bundle, then fall back on the system default font.
830             size = 0;
831             font = [NSFont userFixedPitchFontOfSize:size];
832             fontName = [font displayName];
833         }
835         if (font) {
836             //NSLog(@"Setting font '%@' of size %.2f", fontName, size);
837             int len = [fontName
838                     lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
839             if (len > 0) {
840                 NSMutableData *data = [NSMutableData data];
842                 [data appendBytes:&size length:sizeof(float)];
843                 [data appendBytes:&len length:sizeof(int)];
844                 [data appendBytes:[fontName UTF8String] length:len];
846                 [self queueMessage:SetFontMsgID data:data];
847                 return YES;
848             }
849         }
850     }
852     //NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f",
853     //        fontName, size);
854     return NO;
857 - (void)executeActionWithName:(NSString *)name
859     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
861     if (len > 0) {
862         NSMutableData *data = [NSMutableData data];
864         [data appendBytes:&len length:sizeof(int)];
865         [data appendBytes:[name UTF8String] length:len];
867         [self queueMessage:ExecuteActionMsgID data:data];
868     }
871 - (void)setMouseShape:(int)shape
873     NSMutableData *data = [NSMutableData data];
874     [data appendBytes:&shape length:sizeof(int)];
875     [self queueMessage:SetMouseShapeMsgID data:data];
878 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
880     // Vim specifies times in milliseconds, whereas Cocoa wants them in
881     // seconds.
882     blinkWaitInterval = .001f*wait;
883     blinkOnInterval = .001f*on;
884     blinkOffInterval = .001f*off;
887 - (void)startBlink
889     if (blinkTimer) {
890         [blinkTimer invalidate];
891         [blinkTimer release];
892         blinkTimer = nil;
893     }
895     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
896             && gui.in_focus) {
897         blinkState = MMBlinkStateOn;
898         blinkTimer =
899             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
900                                               target:self
901                                             selector:@selector(blinkTimerFired:)
902                                             userInfo:nil repeats:NO] retain];
903         gui_update_cursor(TRUE, FALSE);
904         [self flushQueue:YES];
905     }
908 - (void)stopBlink
910     if (MMBlinkStateOff == blinkState) {
911         gui_update_cursor(TRUE, FALSE);
912         [self flushQueue:YES];
913     }
915     blinkState = MMBlinkStateNone;
918 - (void)adjustLinespace:(int)linespace
920     NSMutableData *data = [NSMutableData data];
921     [data appendBytes:&linespace length:sizeof(int)];
922     [self queueMessage:AdjustLinespaceMsgID data:data];
925 - (void)activate
927     [self queueMessage:ActivateMsgID data:nil];
930 - (int)lookupColorWithKey:(NSString *)key
932     if (!(key && [key length] > 0))
933         return INVALCOLOR;
935     NSString *stripKey = [[[[key lowercaseString]
936         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
937             componentsSeparatedByString:@" "]
938                componentsJoinedByString:@""];
940     if (stripKey && [stripKey length] > 0) {
941         // First of all try to lookup key in the color dictionary; note that
942         // all keys in this dictionary are lowercase with no whitespace.
943         id obj = [colorDict objectForKey:stripKey];
944         if (obj) return [obj intValue];
946         // The key was not in the dictionary; is it perhaps of the form
947         // #rrggbb?
948         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
949             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
950             [scanner setScanLocation:1];
951             unsigned hex = 0;
952             if ([scanner scanHexInt:&hex]) {
953                 return (int)hex;
954             }
955         }
957         // As a last resort, check if it is one of the system defined colors.
958         // The keys in this dictionary are also lowercase with no whitespace.
959         obj = [sysColorDict objectForKey:stripKey];
960         if (obj) {
961             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
962             if (col) {
963                 float r, g, b, a;
964                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
965                 [col getRed:&r green:&g blue:&b alpha:&a];
966                 return (((int)(r*255+.5f) & 0xff) << 16)
967                      + (((int)(g*255+.5f) & 0xff) << 8)
968                      +  ((int)(b*255+.5f) & 0xff);
969             }
970         }
971     }
973     NSLog(@"WARNING: No color with key %@ found.", stripKey);
974     return INVALCOLOR;
977 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
979     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
980     id obj;
982     while ((obj = [e nextObject])) {
983         if ([value isEqual:obj])
984             return YES;
985     }
987     return NO;
990 - (oneway void)processInput:(int)msgid data:(in NSData *)data
992     // NOTE: This method might get called whenever the run loop is tended to.
993     // Thus it might get called whilst input is being processed.  Normally this
994     // is not a problem, but if it gets called often then it might become
995     // dangerous.  E.g. say a message causes the screen to be redrawn and then
996     // another message is received causing another simultaneous screen redraw;
997     // this is not good.  To deal with this problem at the moment, we simply
998     // drop messages that are received while other input is being processed.
999     if (inProcessInput) {
1000 #if MM_USE_INPUT_QUEUE
1001         [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1002         [inputQueue addObject:data];
1003 #else
1004         // Just drop the input
1005         //NSLog(@"WARNING: Dropping input in %s", _cmd);
1006 #endif
1007     } else {
1008         [self processInputBegin];
1009         [self handleMessage:msgid data:data];
1010         [self processInputEnd];
1011     }
1014 - (oneway void)processInputAndData:(in NSArray *)messages
1016     // NOTE: See comment in processInput:data:.
1017     unsigned i, count = [messages count];
1018     if (count % 2) {
1019         NSLog(@"WARNING: [messages count] is odd in %s", _cmd);
1020         return;
1021     }
1023     if (inProcessInput) {
1024 #if MM_USE_INPUT_QUEUE
1025         [inputQueue addObjectsFromArray:messages];
1026 #else
1027         // Just drop the input
1028         //NSLog(@"WARNING: Dropping input in %s", _cmd);
1029 #endif
1030     } else {
1031         [self processInputBegin];
1033         for (i = 0; i < count; i += 2) {
1034             int msgid = [[messages objectAtIndex:i] intValue];
1035             id data = [messages objectAtIndex:i+1];
1036             if ([data isEqual:[NSNull null]])
1037                 data = nil;
1039             [self handleMessage:msgid data:data];
1040         }
1042         [self processInputEnd];
1043     }
1046 - (BOOL)checkForModifiedBuffers
1048     buf_T *buf;
1049     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
1050         if (bufIsChanged(buf)) {
1051             return YES;
1052         }
1053     }
1055     return NO;
1058 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1060     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1061         // If there is no pasteboard, return YES to indicate that there is text
1062         // to copy.
1063         if (!pboard)
1064             return YES;
1066         clip_copy_selection();
1068         // Get the text to put on the pasteboard.
1069         long_u llen = 0; char_u *str = 0;
1070         int type = clip_convert_selection(&str, &llen, &clip_star);
1071         if (type < 0)
1072             return NO;
1073         
1074         // TODO: Avoid overflow.
1075         int len = (int)llen;
1076 #if MM_ENABLE_CONV
1077         if (output_conv.vc_type != CONV_NONE) {
1078             char_u *conv_str = string_convert(&output_conv, str, &len);
1079             if (conv_str) {
1080                 vim_free(str);
1081                 str = conv_str;
1082             }
1083         }
1084 #endif
1086         NSString *string = [[NSString alloc]
1087             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1089         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1090         [pboard declareTypes:types owner:nil];
1091         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1092     
1093         [string release];
1094         vim_free(str);
1096         return ok;
1097     }
1099     return NO;
1102 - (oneway void)addReply:(in bycopy NSString *)reply
1103                  server:(in byref id <MMVimServerProtocol>)server
1105     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1107     // Replies might come at any time and in any order so we keep them in an
1108     // array inside a dictionary with the send port used as key.
1110     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1111     // HACK! Assume connection uses mach ports.
1112     int port = [(NSMachPort*)[conn sendPort] machPort];
1113     NSNumber *key = [NSNumber numberWithInt:port];
1115     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1116     if (!replies) {
1117         replies = [NSMutableArray array];
1118         [serverReplyDict setObject:replies forKey:key];
1119     }
1121     [replies addObject:reply];
1124 - (void)addInput:(in bycopy NSString *)input
1125                  client:(in byref id <MMVimClientProtocol>)client
1127     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1129     char_u *s = (char_u*)[input UTF8String];
1131 #if MM_ENABLE_CONV
1132     s = CONVERT_FROM_UTF8(s);
1133 #endif
1135     server_to_input_buf(s);
1137 #if MM_ENABLE_CONV
1138     CONVERT_FROM_UTF8_FREE(s);
1139 #endif
1141     [self addClient:(id)client];
1143     inputReceived = YES;
1146 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1147                  client:(in byref id <MMVimClientProtocol>)client
1149     //NSLog(@"evaluateExpression:%@ client:%@", expr, (id)client);
1151     NSString *eval = nil;
1152     char_u *s = (char_u*)[expr UTF8String];
1154 #if MM_ENABLE_CONV
1155     s = CONVERT_FROM_UTF8(s);
1156 #endif
1158     char_u *res = eval_client_expr_to_string(s);
1160 #if MM_ENABLE_CONV
1161     CONVERT_FROM_UTF8_FREE(s);
1162 #endif
1164     if (res != NULL) {
1165         s = res;
1166 #if MM_ENABLE_CONV
1167         s = CONVERT_TO_UTF8(s);
1168 #endif
1169         eval = [NSString stringWithUTF8String:(char*)s];
1170 #if MM_ENABLE_CONV
1171         CONVERT_TO_UTF8_FREE(s);
1172 #endif
1173         vim_free(res);
1174     }
1176     [self addClient:(id)client];
1178     return eval;
1181 - (void)registerServerWithName:(NSString *)name
1183     NSString *svrName = name;
1184     NSConnection *svrConn = [NSConnection defaultConnection];
1185     unsigned i;
1187     for (i = 0; i < MMServerMax; ++i) {
1188         NSString *connName = [self connectionNameFromServerName:svrName];
1190         if ([svrConn registerName:connName]) {
1191             //NSLog(@"Registered server with name: %@", svrName);
1193             // TODO: Set request/reply time-outs to something else?
1194             //
1195             // Don't wait for requests (time-out means that the message is
1196             // dropped).
1197             [svrConn setRequestTimeout:0];
1198             //[svrConn setReplyTimeout:MMReplyTimeout];
1199             [svrConn setRootObject:self];
1201             char_u *s = (char_u*)[svrName UTF8String];
1202 #if MM_ENABLE_CONV
1203             s = CONVERT_FROM_UTF8(s);
1204 #endif
1205             // NOTE: 'serverName' is a global variable
1206             serverName = vim_strsave(s);
1207 #if MM_ENABLE_CONV
1208             CONVERT_FROM_UTF8_FREE(s);
1209 #endif
1210 #ifdef FEAT_EVAL
1211             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1212 #endif
1213 #ifdef FEAT_TITLE
1214             need_maketitle = TRUE;
1215 #endif
1216             [self queueMessage:SetServerNameMsgID data:
1217                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1218             break;
1219         }
1221         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1222     }
1225 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1226                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1227               silent:(BOOL)silent
1229     // NOTE: If 'name' equals 'serverName' then the request is local (client
1230     // and server are the same).  This case is not handled separately, so a
1231     // connection will be set up anyway (this simplifies the code).
1233     NSConnection *conn = [self connectionForServerName:name];
1234     if (!conn) {
1235         if (!silent) {
1236             char_u *s = (char_u*)[name UTF8String];
1237 #if MM_ENABLE_CONV
1238             s = CONVERT_FROM_UTF8(s);
1239 #endif
1240             EMSG2(_(e_noserver), s);
1241 #if MM_ENABLE_CONV
1242             CONVERT_FROM_UTF8_FREE(s);
1243 #endif
1244         }
1245         return NO;
1246     }
1248     if (port) {
1249         // HACK! Assume connection uses mach ports.
1250         *port = [(NSMachPort*)[conn sendPort] machPort];
1251     }
1253     id proxy = [conn rootProxy];
1254     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1256     @try {
1257         if (expr) {
1258             NSString *eval = [proxy evaluateExpression:string client:self];
1259             if (reply) {
1260                 if (eval) {
1261                     char_u *r = (char_u*)[eval UTF8String];
1262 #if MM_ENABLE_CONV
1263                     r = CONVERT_FROM_UTF8(r);
1264 #endif
1265                     *reply = vim_strsave(r);
1266 #if MM_ENABLE_CONV
1267                     CONVERT_FROM_UTF8_FREE(r);
1268 #endif
1269                 } else {
1270                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1271                 }
1272             }
1274             if (!eval)
1275                 return NO;
1276         } else {
1277             [proxy addInput:string client:self];
1278         }
1279     }
1280     @catch (NSException *e) {
1281         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1282         return NO;
1283     }
1285     return YES;
1288 - (NSArray *)serverList
1290     NSArray *list = nil;
1292     if ([self connection]) {
1293         id proxy = [connection rootProxy];
1294         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1296         @try {
1297             list = [proxy serverList];
1298         }
1299         @catch (NSException *e) {
1300             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1301         }
1302     } else {
1303         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1304     }
1306     return list;
1309 - (NSString *)peekForReplyOnPort:(int)port
1311     //NSLog(@"%s%d", _cmd, port);
1313     NSNumber *key = [NSNumber numberWithInt:port];
1314     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1315     if (replies && [replies count]) {
1316         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1317         //        [replies objectAtIndex:0]);
1318         return [replies objectAtIndex:0];
1319     }
1321     //NSLog(@"    No replies");
1322     return nil;
1325 - (NSString *)waitForReplyOnPort:(int)port
1327     //NSLog(@"%s%d", _cmd, port);
1328     
1329     NSConnection *conn = [self connectionForServerPort:port];
1330     if (!conn)
1331         return nil;
1333     NSNumber *key = [NSNumber numberWithInt:port];
1334     NSMutableArray *replies = nil;
1335     NSString *reply = nil;
1337     // Wait for reply as long as the connection to the server is valid (unless
1338     // user interrupts wait with Ctrl-C).
1339     while (!got_int && [conn isValid] &&
1340             !(replies = [serverReplyDict objectForKey:key])) {
1341         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1342                                  beforeDate:[NSDate distantFuture]];
1343     }
1345     if (replies) {
1346         if ([replies count] > 0) {
1347             reply = [[replies objectAtIndex:0] retain];
1348             //NSLog(@"    Got reply: %@", reply);
1349             [replies removeObjectAtIndex:0];
1350             [reply autorelease];
1351         }
1353         if ([replies count] == 0)
1354             [serverReplyDict removeObjectForKey:key];
1355     }
1357     return reply;
1360 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1362     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1363     if (client) {
1364         @try {
1365             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1366             [client addReply:reply server:self];
1367             return YES;
1368         }
1369         @catch (NSException *e) {
1370             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1371         }
1372     } else {
1373         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1374     }
1376     return NO;
1379 @end // MMBackend
1383 @implementation MMBackend (Private)
1385 - (void)handleMessage:(int)msgid data:(NSData *)data
1387     if (InsertTextMsgID == msgid) {
1388         if (!data) return;
1389         NSString *key = [[NSString alloc] initWithData:data
1390                                               encoding:NSUTF8StringEncoding];
1391         char_u *str = (char_u*)[key UTF8String];
1392         int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1394 #if MM_ENABLE_CONV
1395         char_u *conv_str = NULL;
1396         if (input_conv.vc_type != CONV_NONE) {
1397             conv_str = string_convert(&input_conv, str, &len);
1398             if (conv_str)
1399                 str = conv_str;
1400         }
1401 #endif
1403         for (i = 0; i < len; ++i) {
1404             add_to_input_buf(str+i, 1);
1405             if (CSI == str[i]) {
1406                 // NOTE: If the converted string contains the byte CSI, then it
1407                 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1408                 // won't work.
1409                 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1410                 add_to_input_buf(extra, 2);
1411             }
1412         }
1414 #if MM_ENABLE_CONV
1415         if (conv_str)
1416             vim_free(conv_str);
1417 #endif
1418         [key release];
1419     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1420         if (!data) return;
1421         const void *bytes = [data bytes];
1422         int mods = *((int*)bytes);  bytes += sizeof(int);
1423         int len = *((int*)bytes);  bytes += sizeof(int);
1424         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1425                                               encoding:NSUTF8StringEncoding];
1426         mods = eventModifierFlagsToVimModMask(mods);
1428         [self handleKeyDown:key modifiers:mods];
1430         [key release];
1431     } else if (SelectTabMsgID == msgid) {
1432         if (!data) return;
1433         const void *bytes = [data bytes];
1434         int idx = *((int*)bytes) + 1;
1435         //NSLog(@"Selecting tab %d", idx);
1436         send_tabline_event(idx);
1437     } else if (CloseTabMsgID == msgid) {
1438         if (!data) return;
1439         const void *bytes = [data bytes];
1440         int idx = *((int*)bytes) + 1;
1441         //NSLog(@"Closing tab %d", idx);
1442         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1443     } else if (AddNewTabMsgID == msgid) {
1444         //NSLog(@"Adding new tab");
1445         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1446     } else if (DraggedTabMsgID == msgid) {
1447         if (!data) return;
1448         const void *bytes = [data bytes];
1449         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1450         // based.
1451         int idx = *((int*)bytes);
1453         tabpage_move(idx);
1454     } else if (ScrollWheelMsgID == msgid) {
1455         if (!data) return;
1456         const void *bytes = [data bytes];
1458         int row = *((int*)bytes);  bytes += sizeof(int);
1459         int col = *((int*)bytes);  bytes += sizeof(int);
1460         int flags = *((int*)bytes);  bytes += sizeof(int);
1461         float dy = *((float*)bytes);  bytes += sizeof(float);
1463         int button = MOUSE_5;
1464         if (dy > 0) button = MOUSE_4;
1466         flags = eventModifierFlagsToVimMouseModMask(flags);
1468         gui_send_mouse_event(button, col, row, NO, flags);
1469     } else if (MouseDownMsgID == msgid) {
1470         if (!data) return;
1471         const void *bytes = [data bytes];
1473         int row = *((int*)bytes);  bytes += sizeof(int);
1474         int col = *((int*)bytes);  bytes += sizeof(int);
1475         int button = *((int*)bytes);  bytes += sizeof(int);
1476         int flags = *((int*)bytes);  bytes += sizeof(int);
1477         int count = *((int*)bytes);  bytes += sizeof(int);
1479         button = eventButtonNumberToVimMouseButton(button);
1480         flags = eventModifierFlagsToVimMouseModMask(flags);
1482         gui_send_mouse_event(button, col, row, count>1, flags);
1483     } else if (MouseUpMsgID == msgid) {
1484         if (!data) return;
1485         const void *bytes = [data bytes];
1487         int row = *((int*)bytes);  bytes += sizeof(int);
1488         int col = *((int*)bytes);  bytes += sizeof(int);
1489         int flags = *((int*)bytes);  bytes += sizeof(int);
1491         flags = eventModifierFlagsToVimMouseModMask(flags);
1493         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1494     } else if (MouseDraggedMsgID == 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_DRAG, col, row, NO, flags);
1505     } else if (SetTextDimensionsMsgID == msgid) {
1506         if (!data) return;
1507         const void *bytes = [data bytes];
1508         int rows = *((int*)bytes);  bytes += sizeof(int);
1509         int cols = *((int*)bytes);  bytes += sizeof(int);
1511         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1512         // gui_resize_shell(), so we have to manually set the rows and columns
1513         // here.  (MacVim doesn't change the rows and columns to avoid
1514         // inconsistent states between Vim and MacVim.)
1515         [self setRows:rows columns:cols];
1517         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1518         gui_resize_shell(cols, rows);
1519     } else if (ExecuteMenuMsgID == msgid) {
1520         if (!data) return;
1521         const void *bytes = [data bytes];
1522         int tag = *((int*)bytes);  bytes += sizeof(int);
1524         vimmenu_T *menu = (vimmenu_T*)tag;
1525         // TODO!  Make sure 'menu' is a valid menu pointer!
1526         if (menu) {
1527             gui_menu_cb(menu);
1528         }
1529     } else if (ToggleToolbarMsgID == msgid) {
1530         char_u go[sizeof(GO_ALL)+2];
1531         char_u *p;
1532         int len;
1534         STRCPY(go, p_go);
1535         p = vim_strchr(go, GO_TOOLBAR);
1536         len = STRLEN(go);
1538         if (p != NULL) {
1539             char_u *end = go + len;
1540             while (p < end) {
1541                 p[0] = p[1];
1542                 ++p;
1543             }
1544         } else {
1545             go[len] = GO_TOOLBAR;
1546             go[len+1] = NUL;
1547         }
1549         set_option_value((char_u*)"guioptions", 0, go, 0);
1551         // Force screen redraw (does it have to be this complicated?).
1552         redraw_all_later(CLEAR);
1553         update_screen(NOT_VALID);
1554         setcursor();
1555         out_flush();
1556         gui_update_cursor(FALSE, FALSE);
1557         gui_mch_flush();
1558     } else if (ScrollbarEventMsgID == msgid) {
1559         if (!data) return;
1560         const void *bytes = [data bytes];
1561         long ident = *((long*)bytes);  bytes += sizeof(long);
1562         int hitPart = *((int*)bytes);  bytes += sizeof(int);
1563         float fval = *((float*)bytes);  bytes += sizeof(float);
1564         scrollbar_T *sb = gui_find_scrollbar(ident);
1566         if (sb) {
1567             scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1568             long value = sb_info->value;
1569             long size = sb_info->size;
1570             long max = sb_info->max;
1571             BOOL isStillDragging = NO;
1572             BOOL updateKnob = YES;
1574             switch (hitPart) {
1575             case NSScrollerDecrementPage:
1576                 value -= (size > 2 ? size - 2 : 1);
1577                 break;
1578             case NSScrollerIncrementPage:
1579                 value += (size > 2 ? size - 2 : 1);
1580                 break;
1581             case NSScrollerDecrementLine:
1582                 --value;
1583                 break;
1584             case NSScrollerIncrementLine:
1585                 ++value;
1586                 break;
1587             case NSScrollerKnob:
1588                 isStillDragging = YES;
1589                 // fall through ...
1590             case NSScrollerKnobSlot:
1591                 value = (long)(fval * (max - size + 1));
1592                 // fall through ...
1593             default:
1594                 updateKnob = NO;
1595                 break;
1596             }
1598             //NSLog(@"value %d -> %d", sb_info->value, value);
1599             gui_drag_scrollbar(sb, value, isStillDragging);
1601             if (updateKnob) {
1602                 // Dragging the knob or option+clicking automatically updates
1603                 // the knob position (on the actual NSScroller), so we only
1604                 // need to set the knob position in the other cases.
1605                 if (sb->wp) {
1606                     // Update both the left&right vertical scrollbars.
1607                     long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1608                     long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1609                     [self setScrollbarThumbValue:value size:size max:max
1610                                       identifier:identLeft];
1611                     [self setScrollbarThumbValue:value size:size max:max
1612                                       identifier:identRight];
1613                 } else {
1614                     // Update the horizontal scrollbar.
1615                     [self setScrollbarThumbValue:value size:size max:max
1616                                       identifier:ident];
1617                 }
1618             }
1619         }
1620     } else if (SetFontMsgID == msgid) {
1621         if (!data) return;
1622         const void *bytes = [data bytes];
1623         float pointSize = *((float*)bytes);  bytes += sizeof(float);
1624         //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1625         bytes += sizeof(unsigned);  // len not used
1627         NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1628         [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1629         char_u *s = (char_u*)[name UTF8String];
1631 #if MM_ENABLE_CONV
1632         s = CONVERT_FROM_UTF8(s);
1633 #endif
1635         set_option_value((char_u*)"guifont", 0, s, 0);
1637 #if MM_ENABLE_CONV
1638         CONVERT_FROM_UTF8_FREE(s);
1639 #endif
1641         // Force screen redraw (does it have to be this complicated?).
1642         redraw_all_later(CLEAR);
1643         update_screen(NOT_VALID);
1644         setcursor();
1645         out_flush();
1646         gui_update_cursor(FALSE, FALSE);
1647         gui_mch_flush();
1648     } else if (VimShouldCloseMsgID == msgid) {
1649         gui_shell_closed();
1650     } else if (DropFilesMsgID == msgid) {
1651 #ifdef FEAT_DND
1652         const void *bytes = [data bytes];
1653         const void *end = [data bytes] + [data length];
1654         int n = *((int*)bytes);  bytes += sizeof(int);
1656         if (State & CMDLINE) {
1657             // HACK!  If Vim is in command line mode then the files names
1658             // should be added to the command line, instead of opening the
1659             // files in tabs.  This is taken care of by gui_handle_drop().
1660             char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1661             if (fnames) {
1662                 int i = 0;
1663                 while (bytes < end && i < n) {
1664                     int len = *((int*)bytes);  bytes += sizeof(int);
1665                     char_u *s = (char_u*)bytes;
1666 #if MM_ENABLE_CONV
1667                     s = CONVERT_FROM_UTF8(s);
1668 #endif
1669                     fnames[i++] = vim_strsave(s);
1670 #if MM_ENABLE_CONV
1671                     CONVERT_FROM_UTF8_FREE(s);
1672 #endif
1673                     bytes += len;
1674                 }
1676                 // NOTE!  This function will free 'fnames'.
1677                 // HACK!  It is assumed that the 'x' and 'y' arguments are
1678                 // unused when in command line mode.
1679                 gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
1680             }
1681         } else {
1682             // HACK!  I'm not sure how to get Vim to open a list of files in
1683             // tabs, so instead I create a ':tab drop' command with all the
1684             // files to open and execute it.
1685             NSMutableString *cmd = (n > 1)
1686                     ? [NSMutableString stringWithString:@":tab drop"]
1687                     : [NSMutableString stringWithString:@":drop"];
1689             int i;
1690             for (i = 0; i < n && bytes < end; ++i) {
1691                 int len = *((int*)bytes);  bytes += sizeof(int);
1692                 NSMutableString *file =
1693                         [NSMutableString stringWithUTF8String:bytes];
1694                 [file replaceOccurrencesOfString:@" "
1695                                       withString:@"\\ "
1696                                          options:0
1697                                            range:NSMakeRange(0,[file length])];
1698                 bytes += len;
1700                 [cmd appendString:@" "];
1701                 [cmd appendString:file];
1702             }
1704             // By going to the last tabpage we ensure that the new tabs will
1705             // appear last (if this call is left out, the taborder becomes
1706             // messy).
1707             goto_tabpage(9999);
1709             char_u *s = (char_u*)[cmd UTF8String];
1710 #if MM_ENABLE_CONV
1711             s = CONVERT_FROM_UTF8(s);
1712 #endif
1713             do_cmdline_cmd(s);
1714 #if MM_ENABLE_CONV
1715             CONVERT_FROM_UTF8_FREE(s);
1716 #endif
1718             // Force screen redraw (does it have to be this complicated?).
1719             // (This code was taken from the end of gui_handle_drop().)
1720             update_screen(NOT_VALID);
1721             setcursor();
1722             out_flush();
1723             gui_update_cursor(FALSE, FALSE);
1724             gui_mch_flush();
1725         }
1726 #endif // FEAT_DND
1727     } else if (DropStringMsgID == msgid) {
1728 #ifdef FEAT_DND
1729         char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
1730         const void *bytes = [data bytes];
1731         int len = *((int*)bytes);  bytes += sizeof(int);
1732         NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
1734         // Replace unrecognized end-of-line sequences with \x0a (line feed).
1735         NSRange range = { 0, [string length] };
1736         unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
1737                                              withString:@"\x0a" options:0
1738                                                   range:range];
1739         if (0 == n) {
1740             n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
1741                                            options:0 range:range];
1742         }
1744         len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1745         char_u *s = (char_u*)[string UTF8String];
1746 #if MM_ENABLE_CONV
1747         if (input_conv.vc_type != CONV_NONE)
1748             s = string_convert(&input_conv, s, &len);
1749 #endif
1750         dnd_yank_drag_data(s, len);
1751 #if MM_ENABLE_CONV
1752         if (input_conv.vc_type != CONV_NONE)
1753             vim_free(s);
1754 #endif
1755         add_to_input_buf(dropkey, sizeof(dropkey));
1756 #endif // FEAT_DND
1757     } else if (GotFocusMsgID == msgid) {
1758         if (!gui.in_focus)
1759             [self focusChange:YES];
1760     } else if (LostFocusMsgID == msgid) {
1761         if (gui.in_focus)
1762             [self focusChange:NO];
1763     } else if (MouseMovedMsgID == msgid) {
1764         const void *bytes = [data bytes];
1765         int row = *((int*)bytes);  bytes += sizeof(int);
1766         int col = *((int*)bytes);  bytes += sizeof(int);
1768         gui_mouse_moved(col, row);
1769     } else if (SetMouseShapeMsgID == msgid) {
1770         const void *bytes = [data bytes];
1771         int shape = *((int*)bytes);  bytes += sizeof(int);
1772         update_mouseshape(shape);
1773     } else {
1774         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1775     }
1778 + (NSDictionary *)specialKeys
1780     static NSDictionary *specialKeys = nil;
1782     if (!specialKeys) {
1783         NSBundle *mainBundle = [NSBundle mainBundle];
1784         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1785                                               ofType:@"plist"];
1786         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1787     }
1789     return specialKeys;
1792 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1794     char_u special[3];
1795     char_u modChars[3];
1796     char_u *chars = (char_u*)[key UTF8String];
1797 #if MM_ENABLE_CONV
1798     char_u *conv_str = NULL;
1799 #endif
1800     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1802     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1803     // that new keys can easily be added.
1804     NSString *specialString = [[MMBackend specialKeys]
1805             objectForKey:key];
1806     if (specialString && [specialString length] > 1) {
1807         //NSLog(@"special key: %@", specialString);
1808         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1809                 [specialString characterAtIndex:1]);
1811         ikey = simplify_key(ikey, &mods);
1812         if (ikey == CSI)
1813             ikey = K_CSI;
1815         special[0] = CSI;
1816         special[1] = K_SECOND(ikey);
1817         special[2] = K_THIRD(ikey);
1819         chars = special;
1820         length = 3;
1821     } else if (1 == length && TAB == chars[0]) {
1822         // Tab is a trouble child:
1823         // - <Tab> is added to the input buffer as is
1824         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1825         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1826         //   to be converted to utf-8
1827         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1828         // - <C-Tab> is reserved by Mac OS X
1829         // - <D-Tab> is reserved by Mac OS X
1830         chars = special;
1831         special[0] = TAB;
1832         length = 1;
1834         if (mods & MOD_MASK_SHIFT) {
1835             mods &= ~MOD_MASK_SHIFT;
1836             special[0] = CSI;
1837             special[1] = K_SECOND(K_S_TAB);
1838             special[2] = K_THIRD(K_S_TAB);
1839             length = 3;
1840         } else if (mods & MOD_MASK_ALT) {
1841             int mtab = 0x80 | TAB;
1842             if (enc_utf8) {
1843                 // Convert to utf-8
1844                 special[0] = (mtab >> 6) + 0xc0;
1845                 special[1] = mtab & 0xbf;
1846                 length = 2;
1847             } else {
1848                 special[0] = mtab;
1849                 length = 1;
1850             }
1851             mods &= ~MOD_MASK_ALT;
1852         }
1853     } else if (length > 0) {
1854         unichar c = [key characterAtIndex:0];
1856         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1857         //        [key characterAtIndex:0], mods);
1859         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1860                 || (c == intr_char && intr_char != Ctrl_C))) {
1861             trash_input_buf();
1862             got_int = TRUE;
1863         }
1865         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1866         // cleared since they are already added to the key by the AppKit.
1867         // Unfortunately, the only way to deal with when to clear the modifiers
1868         // or not seems to be to have hard-wired rules like this.
1869         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1870                     || 0x9 == c) ) {
1871             mods &= ~MOD_MASK_SHIFT;
1872             mods &= ~MOD_MASK_CTRL;
1873             //NSLog(@"clear shift ctrl");
1874         }
1876         // HACK!  All Option+key presses go via 'insert text' messages, except
1877         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1878         // not work to map to it.
1879         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1880             //NSLog(@"clear alt");
1881             mods &= ~MOD_MASK_ALT;
1882         }
1884 #if MM_ENABLE_CONV
1885         if (input_conv.vc_type != CONV_NONE) {
1886             conv_str = string_convert(&input_conv, chars, &length);
1887             if (conv_str)
1888                 chars = conv_str;
1889         }
1890 #endif
1891     }
1893     if (chars && length > 0) {
1894         if (mods) {
1895             //NSLog(@"adding mods: %d", mods);
1896             modChars[0] = CSI;
1897             modChars[1] = KS_MODIFIER;
1898             modChars[2] = mods;
1899             add_to_input_buf(modChars, 3);
1900         }
1902         //NSLog(@"add to input buf: 0x%x", chars[0]);
1903         // TODO: Check for CSI bytes?
1904         add_to_input_buf(chars, length);
1905     }
1907 #if MM_ENABLE_CONV
1908     if (conv_str)
1909         vim_free(conv_str);
1910 #endif
1913 - (void)queueMessage:(int)msgid data:(NSData *)data
1915     [queue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1916     if (data)
1917         [queue addObject:data];
1918     else
1919         [queue addObject:[NSData data]];
1922 - (void)connectionDidDie:(NSNotification *)notification
1924     // If the main connection to MacVim is lost this means that MacVim was
1925     // either quit (by the user chosing Quit on the MacVim menu), or it has
1926     // crashed.  In either case our only option is to quit now.
1927     // TODO: Write backup file?
1929     //NSLog(@"A Vim process lots its connection to MacVim; quitting.");
1930     getout(0);
1933 - (void)blinkTimerFired:(NSTimer *)timer
1935     NSTimeInterval timeInterval = 0;
1937     [blinkTimer release];
1938     blinkTimer = nil;
1940     if (MMBlinkStateOn == blinkState) {
1941         gui_undraw_cursor();
1942         blinkState = MMBlinkStateOff;
1943         timeInterval = blinkOffInterval;
1944     } else if (MMBlinkStateOff == blinkState) {
1945         gui_update_cursor(TRUE, FALSE);
1946         blinkState = MMBlinkStateOn;
1947         timeInterval = blinkOnInterval;
1948     }
1950     if (timeInterval > 0) {
1951         blinkTimer = 
1952             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1953                                             selector:@selector(blinkTimerFired:)
1954                                             userInfo:nil repeats:NO] retain];
1955         [self flushQueue:YES];
1956     }
1959 - (void)focusChange:(BOOL)on
1961     gui_focus_change(on);
1964 - (void)processInputBegin
1966     inProcessInput = YES;
1968     // Don't flush too soon or update speed will suffer.
1969     [lastFlushDate release];
1970     lastFlushDate = [[NSDate date] retain];
1973 - (void)processInputEnd
1975 #if MM_USE_INPUT_QUEUE
1976     int count = [inputQueue count];
1977     if (count % 2) {
1978         // TODO: This is troubling, but it is not hard to get Vim to end up
1979         // here.  Why does this happen?
1980         NSLog(@"WARNING: inputQueue has odd number of objects (%d)", count);
1981         [inputQueue removeAllObjects];
1982     } else if (count > 0) {
1983         // TODO: Dispatch these messages?  Maybe not; usually when the
1984         // 'inputQueue' is non-empty it means that a LOT of messages has been
1985         // sent simultaneously.  The only way this happens is when Vim is being
1986         // tormented, e.g. if the user holds down <D-`> to rapidly switch
1987         // windows.
1988         unsigned i;
1989         for (i = 0; i < count; i+=2) {
1990             int msgid = [[inputQueue objectAtIndex:i] intValue];
1991             NSLog(@"%s: Dropping message %s", _cmd, MessageStrings[msgid]);
1992         }
1994         [inputQueue removeAllObjects];
1995     }
1996 #endif
1998     inputReceived = YES;
1999     inProcessInput = NO;
2002 - (NSString *)connectionNameFromServerName:(NSString *)name
2004     NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2006     return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2007         lowercaseString];
2010 - (NSConnection *)connectionForServerName:(NSString *)name
2012     // TODO: Try 'name%d' if 'name' fails.
2013     NSString *connName = [self connectionNameFromServerName:name];
2014     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2016     if (!svrConn) {
2017         svrConn = [NSConnection connectionWithRegisteredName:connName
2018                                                            host:nil];
2019         // Try alternate server...
2020         if (!svrConn && alternateServerName) {
2021             //NSLog(@"  trying to connect to alternate server: %@",
2022             //        alternateServerName);
2023             connName = [self connectionNameFromServerName:alternateServerName];
2024             svrConn = [NSConnection connectionWithRegisteredName:connName
2025                                                             host:nil];
2026         }
2028         // Try looking for alternate servers...
2029         if (!svrConn) {
2030             //NSLog(@"  looking for alternate servers...");
2031             NSString *alt = [self alternateServerNameForName:name];
2032             if (alt != alternateServerName) {
2033                 //NSLog(@"  found alternate server: %@", string);
2034                 [alternateServerName release];
2035                 alternateServerName = [alt copy];
2036             }
2037         }
2039         // Try alternate server again...
2040         if (!svrConn && alternateServerName) {
2041             //NSLog(@"  trying to connect to alternate server: %@",
2042             //        alternateServerName);
2043             connName = [self connectionNameFromServerName:alternateServerName];
2044             svrConn = [NSConnection connectionWithRegisteredName:connName
2045                                                             host:nil];
2046         }
2048         if (svrConn) {
2049             [connectionNameDict setObject:svrConn forKey:connName];
2051             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2052             [[NSNotificationCenter defaultCenter] addObserver:self
2053                     selector:@selector(serverConnectionDidDie:)
2054                         name:NSConnectionDidDieNotification object:svrConn];
2055         }
2056     }
2058     return svrConn;
2061 - (NSConnection *)connectionForServerPort:(int)port
2063     NSConnection *conn;
2064     NSEnumerator *e = [connectionNameDict objectEnumerator];
2066     while ((conn = [e nextObject])) {
2067         // HACK! Assume connection uses mach ports.
2068         if (port == [(NSMachPort*)[conn sendPort] machPort])
2069             return conn;
2070     }
2072     return nil;
2075 - (void)serverConnectionDidDie:(NSNotification *)notification
2077     //NSLog(@"%s%@", _cmd, notification);
2079     NSConnection *svrConn = [notification object];
2081     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2082     [[NSNotificationCenter defaultCenter]
2083             removeObserver:self
2084                       name:NSConnectionDidDieNotification
2085                     object:svrConn];
2087     [connectionNameDict removeObjectsForKeys:
2088         [connectionNameDict allKeysForObject:svrConn]];
2090     // HACK! Assume connection uses mach ports.
2091     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2092     NSNumber *key = [NSNumber numberWithInt:port];
2094     [clientProxyDict removeObjectForKey:key];
2095     [serverReplyDict removeObjectForKey:key];
2098 - (void)addClient:(NSDistantObject *)client
2100     NSConnection *conn = [client connectionForProxy];
2101     // HACK! Assume connection uses mach ports.
2102     int port = [(NSMachPort*)[conn sendPort] machPort];
2103     NSNumber *key = [NSNumber numberWithInt:port];
2105     if (![clientProxyDict objectForKey:key]) {
2106         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2107         [clientProxyDict setObject:client forKey:key];
2108     }
2110     // NOTE: 'clientWindow' is a global variable which is used by <client>
2111     clientWindow = port;
2114 - (NSString *)alternateServerNameForName:(NSString *)name
2116     if (!(name && [name length] > 0))
2117         return nil;
2119     // Only look for alternates if 'name' doesn't end in a digit.
2120     unichar lastChar = [name characterAtIndex:[name length]-1];
2121     if (lastChar >= '0' && lastChar <= '9')
2122         return nil;
2124     // Look for alternates among all current servers.
2125     NSArray *list = [self serverList];
2126     if (!(list && [list count] > 0))
2127         return nil;
2129     // Filter out servers starting with 'name' and ending with a number. The
2130     // (?i) pattern ensures that the match is case insensitive.
2131     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2132     NSPredicate *pred = [NSPredicate predicateWithFormat:
2133             @"SELF MATCHES %@", pat];
2134     list = [list filteredArrayUsingPredicate:pred];
2135     if ([list count] > 0) {
2136         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2137         return [list objectAtIndex:0];
2138     }
2140     return nil;
2143 @end // MMBackend (Private)
2148 @implementation NSString (MMServerNameCompare)
2149 - (NSComparisonResult)serverNameCompare:(NSString *)string
2151     return [self compare:string
2152                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2154 @end
2159 static int eventModifierFlagsToVimModMask(int modifierFlags)
2161     int modMask = 0;
2163     if (modifierFlags & NSShiftKeyMask)
2164         modMask |= MOD_MASK_SHIFT;
2165     if (modifierFlags & NSControlKeyMask)
2166         modMask |= MOD_MASK_CTRL;
2167     if (modifierFlags & NSAlternateKeyMask)
2168         modMask |= MOD_MASK_ALT;
2169     if (modifierFlags & NSCommandKeyMask)
2170         modMask |= MOD_MASK_CMD;
2172     return modMask;
2175 static int vimModMaskToEventModifierFlags(int mods)
2177     int flags = 0;
2179     if (mods & MOD_MASK_SHIFT)
2180         flags |= NSShiftKeyMask;
2181     if (mods & MOD_MASK_CTRL)
2182         flags |= NSControlKeyMask;
2183     if (mods & MOD_MASK_ALT)
2184         flags |= NSAlternateKeyMask;
2185     if (mods & MOD_MASK_CMD)
2186         flags |= NSCommandKeyMask;
2188     return flags;
2191 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2193     int modMask = 0;
2195     if (modifierFlags & NSShiftKeyMask)
2196         modMask |= MOUSE_SHIFT;
2197     if (modifierFlags & NSControlKeyMask)
2198         modMask |= MOUSE_CTRL;
2199     if (modifierFlags & NSAlternateKeyMask)
2200         modMask |= MOUSE_ALT;
2202     return modMask;
2205 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2207     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE,
2208             MOUSE_X1, MOUSE_X2 };
2210     return mouseButton[buttonNumber < 5 ? buttonNumber : 0];
2213 static int specialKeyToNSKey(int key)
2215     if (!IS_SPECIAL(key))
2216         return key;
2218     static struct {
2219         int special;
2220         int nskey;
2221     } sp2ns[] = {
2222         { K_UP, NSUpArrowFunctionKey },
2223         { K_DOWN, NSDownArrowFunctionKey },
2224         { K_LEFT, NSLeftArrowFunctionKey },
2225         { K_RIGHT, NSRightArrowFunctionKey },
2226         { K_F1, NSF1FunctionKey },
2227         { K_F2, NSF2FunctionKey },
2228         { K_F3, NSF3FunctionKey },
2229         { K_F4, NSF4FunctionKey },
2230         { K_F5, NSF5FunctionKey },
2231         { K_F6, NSF6FunctionKey },
2232         { K_F7, NSF7FunctionKey },
2233         { K_F8, NSF8FunctionKey },
2234         { K_F9, NSF9FunctionKey },
2235         { K_F10, NSF10FunctionKey },
2236         { K_F11, NSF11FunctionKey },
2237         { K_F12, NSF12FunctionKey },
2238         { K_F13, NSF13FunctionKey },
2239         { K_F14, NSF14FunctionKey },
2240         { K_F15, NSF15FunctionKey },
2241         { K_F16, NSF16FunctionKey },
2242         { K_F17, NSF17FunctionKey },
2243         { K_F18, NSF18FunctionKey },
2244         { K_F19, NSF19FunctionKey },
2245         { K_F20, NSF20FunctionKey },
2246         { K_F21, NSF21FunctionKey },
2247         { K_F22, NSF22FunctionKey },
2248         { K_F23, NSF23FunctionKey },
2249         { K_F24, NSF24FunctionKey },
2250         { K_F25, NSF25FunctionKey },
2251         { K_F26, NSF26FunctionKey },
2252         { K_F27, NSF27FunctionKey },
2253         { K_F28, NSF28FunctionKey },
2254         { K_F29, NSF29FunctionKey },
2255         { K_F30, NSF30FunctionKey },
2256         { K_F31, NSF31FunctionKey },
2257         { K_F32, NSF32FunctionKey },
2258         { K_F33, NSF33FunctionKey },
2259         { K_F34, NSF34FunctionKey },
2260         { K_F35, NSF35FunctionKey },
2261         { K_DEL, NSBackspaceCharacter },
2262         { K_BS, NSDeleteCharacter },
2263         { K_HOME, NSHomeFunctionKey },
2264         { K_END, NSEndFunctionKey },
2265         { K_PAGEUP, NSPageUpFunctionKey },
2266         { K_PAGEDOWN, NSPageDownFunctionKey }
2267     };
2269     int i;
2270     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2271         if (sp2ns[i].special == key)
2272             return sp2ns[i].nskey;
2273     }
2275     return 0;