- Merged patch from Nico (code style, :popup, release vimView)
[MacVim/jjgod.git] / MMBackend.m
blobaa1a1ab9d26acb7b62546ef5221865497532451f
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.
17 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
18 #define MM_COLOR_WITH_TRANSP(col,transp) \
19     ((unsigned)( ((col)&0xffffff) | (((unsigned)(255-(transp))&0xff)<<24) ))
22 // This constant controls how often the command queue may be flushed.  If it is
23 // too small the app might feel unresponsive; if it is too large there might be
24 // long periods without the screen updating (e.g. when sourcing a large session
25 // file).  (The unit is seconds.)
26 static float MMFlushTimeoutInterval = 0.1f;
28 static unsigned MMServerMax = 1000;
30 // NOTE: The default font is bundled with the application.
31 static NSString *MMDefaultFontName = @"DejaVu Sans Mono";
32 static float MMDefaultFontSize = 12.0f;
34 // TODO: Move to separate file.
35 static int eventModifierFlagsToVimModMask(int modifierFlags);
36 static int vimModMaskToEventModifierFlags(int mods);
37 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
38 static int eventButtonNumberToVimMouseButton(int buttonNumber);
39 static int specialKeyToNSKey(int key);
41 enum {
42     MMBlinkStateNone = 0,
43     MMBlinkStateOn,
44     MMBlinkStateOff
49 @interface NSString (MMServerNameCompare)
50 - (NSComparisonResult)serverNameCompare:(NSString *)string;
51 @end
55 @interface MMBackend (Private)
56 - (void)handleMessage:(int)msgid data:(NSData *)data;
57 + (NSDictionary *)specialKeys;
58 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
59 - (void)queueMessage:(int)msgid data:(NSData *)data;
60 - (void)connectionDidDie:(NSNotification *)notification;
61 - (void)blinkTimerFired:(NSTimer *)timer;
62 - (void)focusChange:(BOOL)on;
63 - (void)processInputBegin;
64 - (void)processInputEnd;
65 - (NSString *)connectionNameFromServerName:(NSString *)name;
66 - (NSConnection *)connectionForServerName:(NSString *)name;
67 - (NSConnection *)connectionForServerPort:(int)port;
68 - (void)serverConnectionDidDie:(NSNotification *)notification;
69 - (void)addClient:(NSDistantObject *)client;
70 - (NSString *)alternateServerNameForName:(NSString *)name;
71 @end
75 @implementation MMBackend
77 + (MMBackend *)sharedInstance
79     static MMBackend *singleton = nil;
80     return singleton ? singleton : (singleton = [MMBackend new]);
83 - (id)init
85     if ((self = [super init])) {
86         fontContainerRef = loadFonts();
88         queue = [[NSMutableArray alloc] init];
89 #if MM_USE_INPUT_QUEUE
90         inputQueue = [[NSMutableArray alloc] init];
91 #endif
92         drawData = [[NSMutableData alloc] initWithCapacity:1024];
93         connectionNameDict = [[NSMutableDictionary alloc] init];
94         clientProxyDict = [[NSMutableDictionary alloc] init];
95         serverReplyDict = [[NSMutableDictionary alloc] init];
97         NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
98                                                          ofType:@"plist"];
99         if (path) {
100             colorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
101                 retain];
102         } else {
103             NSLog(@"WARNING: Could not locate Colors.plist.");
104         }
106         path = [[NSBundle mainBundle] pathForResource:@"SystemColors"
107                                                ofType:@"plist"];
108         if (path) {
109             sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
110                 retain];
111         } else {
112             NSLog(@"WARNING: Could not locate SystemColors.plist.");
113         }
114     }
116     return self;
119 - (void)dealloc
121     //NSLog(@"%@ %s", [self className], _cmd);
123     [[NSNotificationCenter defaultCenter] removeObserver:self];
125     [blinkTimer release];  blinkTimer = nil;
126 #if MM_USE_INPUT_QUEUE
127     [inputQueue release];  inputQueue = nil;
128 #endif
129     [alternateServerName release];  alternateServerName = nil;
130     [serverReplyDict release];  serverReplyDict = nil;
131     [clientProxyDict release];  clientProxyDict = nil;
132     [connectionNameDict release];  connectionNameDict = nil;
133     [queue release];  queue = nil;
134     [drawData release];  drawData = nil;
135     [frontendProxy release];  frontendProxy = nil;
136     [connection release];  connection = nil;
137     [sysColorDict release];  sysColorDict = nil;
138     [colorDict release];  colorDict = nil;
140     [super dealloc];
143 - (void)setBackgroundColor:(int)color
145     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
148 - (void)setForegroundColor:(int)color
150     foregroundColor = MM_COLOR(color);
153 - (void)setSpecialColor:(int)color
155     specialColor = MM_COLOR(color);
158 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
160     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
161     defaultForegroundColor = MM_COLOR(fg);
163     NSMutableData *data = [NSMutableData data];
165     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
166     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
168     [self queueMessage:SetDefaultColorsMsgID data:data];
171 - (NSConnection *)connection
173     if (!connection) {
174         // NOTE!  If the name of the connection changes here it must also be
175         // updated in MMAppController.m.
176         NSString *name = [NSString stringWithFormat:@"%@-connection",
177                [[NSBundle mainBundle] bundleIdentifier]];
179         connection = [NSConnection connectionWithRegisteredName:name host:nil];
180         [connection retain];
181     }
183     // NOTE: 'connection' may be nil here.
184     return connection;
187 - (BOOL)checkin
189     if (![self connection]) {
190         NSBundle *mainBundle = [NSBundle mainBundle];
191 #if 0
192         NSString *path = [mainBundle bundlePath];
193         if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
194             NSLog(@"WARNING: Failed to launch GUI with path %@", path);
195             return NO;
196         }
197 #else
198         // HACK!  It would be preferable to launch the GUI using NSWorkspace,
199         // however I have not managed to figure out how to pass arguments using
200         // NSWorkspace.
201         //
202         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
203         // that the GUI won't be activated (or raised) so there is a hack in
204         // MMWindowController which always raises the app when a new window is
205         // opened.
206         NSMutableArray *args = [NSMutableArray arrayWithObjects:
207             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
208         NSString *exeName = [[mainBundle infoDictionary]
209                 objectForKey:@"CFBundleExecutable"];
210         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
211         if (!path) {
212             NSLog(@"ERROR: Could not find MacVim executable in bundle");
213             return NO;
214         }
216         [NSTask launchedTaskWithLaunchPath:path arguments:args];
217 #endif
219         // HACK!  The NSWorkspaceDidLaunchApplicationNotification does not work
220         // for tasks like this, so poll the mach bootstrap server until it
221         // returns a valid connection.  Also set a time-out date so that we
222         // don't get stuck doing this forever.
223         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
224         while (!connection &&
225                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
226         {
227             [[NSRunLoop currentRunLoop]
228                     runMode:NSDefaultRunLoopMode
229                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
231             // NOTE: This call will set 'connection' as a side-effect.
232             [self connection];
233         }
235         if (!connection) {
236             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
237             return NO;
238         }
239     }
241     id proxy = [connection rootProxy];
242     [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
244     [[NSNotificationCenter defaultCenter] addObserver:self
245             selector:@selector(connectionDidDie:)
246                 name:NSConnectionDidDieNotification object:connection];
248     int pid = [[NSProcessInfo processInfo] processIdentifier];
250     @try {
251         frontendProxy = [proxy connectBackend:self pid:pid];
252     }
253     @catch (NSException *e) {
254         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
255     }
257     if (frontendProxy) {
258         [frontendProxy retain];
259         [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
260     }
262     return connection && frontendProxy;
265 - (BOOL)openVimWindow
267     [self queueMessage:OpenVimWindowMsgID data:nil];
268     return YES;
271 - (void)clearAll
273     int type = ClearAllDrawType;
275     // Any draw commands in queue are effectively obsolete since this clearAll
276     // will negate any effect they have, therefore we may as well clear the
277     // draw queue.
278     [drawData setLength:0];
280     [drawData appendBytes:&type length:sizeof(int)];
283 - (void)clearBlockFromRow:(int)row1 column:(int)col1
284                     toRow:(int)row2 column:(int)col2
286     int type = ClearBlockDrawType;
288     [drawData appendBytes:&type length:sizeof(int)];
290     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
291     [drawData appendBytes:&row1 length:sizeof(int)];
292     [drawData appendBytes:&col1 length:sizeof(int)];
293     [drawData appendBytes:&row2 length:sizeof(int)];
294     [drawData appendBytes:&col2 length:sizeof(int)];
297 - (void)deleteLinesFromRow:(int)row count:(int)count
298               scrollBottom:(int)bottom left:(int)left right:(int)right
300     int type = DeleteLinesDrawType;
302     [drawData appendBytes:&type length:sizeof(int)];
304     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
305     [drawData appendBytes:&row length:sizeof(int)];
306     [drawData appendBytes:&count length:sizeof(int)];
307     [drawData appendBytes:&bottom length:sizeof(int)];
308     [drawData appendBytes:&left length:sizeof(int)];
309     [drawData appendBytes:&right length:sizeof(int)];
312 - (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
313                 flags:(int)flags
315     if (len <= 0) return;
317     int type = ReplaceStringDrawType;
319     [drawData appendBytes:&type length:sizeof(int)];
321     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
322     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
323     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
324     [drawData appendBytes:&row length:sizeof(int)];
325     [drawData appendBytes:&col length:sizeof(int)];
326     [drawData appendBytes:&flags length:sizeof(int)];
327     [drawData appendBytes:&len length:sizeof(int)];
328     [drawData appendBytes:s length:len];
331 - (void)insertLinesFromRow:(int)row count:(int)count
332               scrollBottom:(int)bottom left:(int)left right:(int)right
334     int type = InsertLinesDrawType;
336     [drawData appendBytes:&type length:sizeof(int)];
338     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
339     [drawData appendBytes:&row length:sizeof(int)];
340     [drawData appendBytes:&count length:sizeof(int)];
341     [drawData appendBytes:&bottom length:sizeof(int)];
342     [drawData appendBytes:&left length:sizeof(int)];
343     [drawData appendBytes:&right length:sizeof(int)];
346 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
347                fraction:(int)percent color:(int)color
349     int type = DrawCursorDrawType;
350     unsigned uc = MM_COLOR(color);
352     [drawData appendBytes:&type length:sizeof(int)];
354     [drawData appendBytes:&uc length:sizeof(unsigned)];
355     [drawData appendBytes:&row length:sizeof(int)];
356     [drawData appendBytes:&col length:sizeof(int)];
357     [drawData appendBytes:&shape length:sizeof(int)];
358     [drawData appendBytes:&percent length:sizeof(int)];
361 - (void)flushQueue:(BOOL)force
363     // NOTE! This method gets called a lot; if we were to flush every time it
364     // was called MacVim would feel unresponsive.  So there is a time out which
365     // ensures that the queue isn't flushed too often.
366     if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
367             < MMFlushTimeoutInterval)
368         return;
370     if ([drawData length] > 0) {
371         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
372         [drawData setLength:0];
373     }
375     if ([queue count] > 0) {
376         @try {
377             [frontendProxy processCommandQueue:queue];
378         }
379         @catch (NSException *e) {
380             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
381         }
383         [queue removeAllObjects];
385         [lastFlushDate release];
386         lastFlushDate = [[NSDate date] retain];
387     }
390 - (BOOL)waitForInput:(int)milliseconds
392     NSDate *date = milliseconds > 0 ?
393             [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
394             [NSDate distantFuture];
396     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
398     // I know of no way to figure out if the run loop exited because input was
399     // found or because of a time out, so I need to manually indicate when
400     // input was received in processInput:data: and then reset it every time
401     // here.
402     BOOL yn = inputReceived;
403     inputReceived = NO;
405     return yn;
408 - (void)exit
410 #ifdef MAC_CLIENTSERVER
411     // The default connection is used for the client/server code.
412     [[NSConnection defaultConnection] setRootObject:nil];
413     [[NSConnection defaultConnection] invalidate];
414 #endif
416     // By invalidating the NSConnection the MMWindowController immediately
417     // finds out that the connection is down and as a result
418     // [MMWindowController connectionDidDie:] is invoked.
419     //NSLog(@"%@ %s", [self className], _cmd);
420     [[NSNotificationCenter defaultCenter] removeObserver:self];
421     [connection invalidate];
423     if (fontContainerRef) {
424         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
425         fontContainerRef = 0;
426     }
430 - (void)selectTab:(int)index
432     //NSLog(@"%s%d", _cmd, index);
434     index -= 1;
435     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
436     [self queueMessage:SelectTabMsgID data:data];
439 - (void)updateTabBar
441     //NSLog(@"%s", _cmd);
443     NSMutableData *data = [NSMutableData data];
445     int idx = tabpage_index(curtab) - 1;
446     [data appendBytes:&idx length:sizeof(int)];
448     tabpage_T *tp;
449     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
450         // This function puts the label of the tab in the global 'NameBuff'.
451         get_tabline_label(tp, FALSE);
452         char_u *s = NameBuff;
453         int len = STRLEN(s);
454         if (len <= 0) continue;
456 #if MM_ENABLE_CONV
457         s = CONVERT_TO_UTF8(s);
458 #endif
460         // Count the number of windows in the tabpage.
461         //win_T *wp = tp->tp_firstwin;
462         //int wincount;
463         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
465         //[data appendBytes:&wincount length:sizeof(int)];
466         [data appendBytes:&len length:sizeof(int)];
467         [data appendBytes:s length:len];
469 #if MM_ENABLE_CONV
470         CONVERT_TO_UTF8_FREE(s);
471 #endif
472     }
474     [self queueMessage:UpdateTabBarMsgID data:data];
477 - (BOOL)tabBarVisible
479     return tabBarVisible;
482 - (void)showTabBar:(BOOL)enable
484     tabBarVisible = enable;
486     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
487     [self queueMessage:msgid data:nil];
490 - (void)setRows:(int)rows columns:(int)cols
492     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
494     int dim[] = { rows, cols };
495     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
497     [self queueMessage:SetTextDimensionsMsgID data:data];
500 - (void)setWindowTitle:(char *)title
502     NSMutableData *data = [NSMutableData data];
503     int len = strlen(title);
504     if (len <= 0) return;
506     [data appendBytes:&len length:sizeof(int)];
507     [data appendBytes:title length:len];
509     [self queueMessage:SetWindowTitleMsgID data:data];
512 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
513                             saving:(int)saving
515     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
516     //        saving);
518     char_u *s = NULL;
519     NSString *ds = dir ? [NSString stringWithUTF8String:dir] : nil;
520     NSString *ts = title ? [NSString stringWithUTF8String:title] : nil;
521     @try {
522         [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
524         // Wait until a reply is sent from MMVimController.
525         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
526                                  beforeDate:[NSDate distantFuture]];
528         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
529             char_u *ret = (char_u*)[dialogReturn UTF8String];
530 #if MM_ENABLE_CONV
531             ret = CONVERT_FROM_UTF8(ret);
532 #endif
533             s = vim_strsave(ret);
534 #if MM_ENABLE_CONV
535             CONVERT_FROM_UTF8_FREE(ret);
536 #endif
537         }
539         [dialogReturn release];  dialogReturn = nil;
540     }
541     @catch (NSException *e) {
542         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
543     }
545     return (char *)s;
548 - (oneway void)setDialogReturn:(in bycopy id)obj
550     // NOTE: This is called by
551     //   - [MMVimController panelDidEnd:::], and
552     //   - [MMVimController alertDidEnd:::],
553     // to indicate that a save/open panel or alert has finished.
555     if (obj != dialogReturn) {
556         [dialogReturn release];
557         dialogReturn = [obj retain];
558     }
561 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
562                      buttons:(char *)btns textField:(char *)txtfield
564     int retval = 0;
565     NSString *message = nil, *text = nil, *textFieldString = nil;
566     NSArray *buttons = nil;
567     int style = NSInformationalAlertStyle;
569     if (VIM_WARNING == type) style = NSWarningAlertStyle;
570     else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
572     if (btns) {
573         NSString *btnString = [NSString stringWithUTF8String:btns];
574         buttons = [btnString componentsSeparatedByString:@"\n"];
575     }
576     if (title)
577         message = [NSString stringWithUTF8String:title];
578     if (msg) {
579         text = [NSString stringWithUTF8String:msg];
580         if (!message) {
581             // HACK! If there is a '\n\n' or '\n' sequence in the message, then
582             // make the part up to there into the title.  We only do this
583             // because Vim has lots of dialogs without a title and they look
584             // ugly that way.
585             // TODO: Fix the actual dialog texts.
586             NSRange eolRange = [text rangeOfString:@"\n\n"];
587             if (NSNotFound == eolRange.location)
588                 eolRange = [text rangeOfString:@"\n"];
589             if (NSNotFound != eolRange.location) {
590                 message = [text substringToIndex:eolRange.location];
591                 text = [text substringFromIndex:NSMaxRange(eolRange)];
592             }
593         }
594     }
595     if (txtfield)
596         textFieldString = [NSString stringWithUTF8String:txtfield];
598     @try {
599         [frontendProxy presentDialogWithStyle:style message:message
600                               informativeText:text buttonTitles:buttons
601                               textFieldString:textFieldString];
603         // Wait until a reply is sent from MMVimController.
604         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
605                                  beforeDate:[NSDate distantFuture]];
607         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
608                 && [dialogReturn count]) {
609             retval = [[dialogReturn objectAtIndex:0] intValue];
610             if (txtfield && [dialogReturn count] > 1) {
611                 NSString *retString = [dialogReturn objectAtIndex:1];
612                 char_u *ret = (char_u*)[retString UTF8String];
613 #if MM_ENABLE_CONV
614                 ret = CONVERT_FROM_UTF8(ret);
615 #endif
616                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
617 #if MM_ENABLE_CONV
618                 CONVERT_FROM_UTF8_FREE(ret);
619 #endif
620             }
621         }
623         [dialogReturn release]; dialogReturn = nil;
624     }
625     @catch (NSException *e) {
626         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
627     }
629     return retval;
632 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
633                atIndex:(int)index
635     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
636     //        name, index);
638     int namelen = name ? strlen(name) : 0;
639     NSMutableData *data = [NSMutableData data];
641     [data appendBytes:&tag length:sizeof(int)];
642     [data appendBytes:&parentTag length:sizeof(int)];
643     [data appendBytes:&namelen length:sizeof(int)];
644     if (namelen > 0) [data appendBytes:name length:namelen];
645     [data appendBytes:&index length:sizeof(int)];
647     [self queueMessage:AddMenuMsgID data:data];
650 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
651                        tip:(char *)tip icon:(char *)icon
652              keyEquivalent:(int)key modifiers:(int)mods
653                     action:(NSString *)action atIndex:(int)index
655     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
656     //        parentTag, name, tip, index);
658     int namelen = name ? strlen(name) : 0;
659     int tiplen = tip ? strlen(tip) : 0;
660     int iconlen = icon ? strlen(icon) : 0;
661     int eventFlags = vimModMaskToEventModifierFlags(mods);
662     int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
663     NSMutableData *data = [NSMutableData data];
665     key = specialKeyToNSKey(key);
667     [data appendBytes:&tag length:sizeof(int)];
668     [data appendBytes:&parentTag length:sizeof(int)];
669     [data appendBytes:&namelen length:sizeof(int)];
670     if (namelen > 0) [data appendBytes:name length:namelen];
671     [data appendBytes:&tiplen length:sizeof(int)];
672     if (tiplen > 0) [data appendBytes:tip length:tiplen];
673     [data appendBytes:&iconlen length:sizeof(int)];
674     if (iconlen > 0) [data appendBytes:icon length:iconlen];
675     [data appendBytes:&actionlen length:sizeof(int)];
676     if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
677     [data appendBytes:&index length:sizeof(int)];
678     [data appendBytes:&key length:sizeof(int)];
679     [data appendBytes:&eventFlags length:sizeof(int)];
681     [self queueMessage:AddMenuItemMsgID data:data];
684 - (void)removeMenuItemWithTag:(int)tag
686     NSMutableData *data = [NSMutableData data];
687     [data appendBytes:&tag length:sizeof(int)];
689     [self queueMessage:RemoveMenuItemMsgID data:data];
692 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
694     NSMutableData *data = [NSMutableData data];
696     [data appendBytes:&tag length:sizeof(int)];
697     [data appendBytes:&enabled length:sizeof(int)];
699     [self queueMessage:EnableMenuItemMsgID data:data];
702 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
704     int len = strlen(name);
705     int row = -1, col = -1;
707     if (len <= 0) return;
709     if (!mouse && curwin) {
710         row = curwin->w_wrow;
711         col = curwin->w_wcol;
712     }
714     NSMutableData *data = [NSMutableData data];
716     [data appendBytes:&row length:sizeof(int)];
717     [data appendBytes:&col length:sizeof(int)];
718     [data appendBytes:&len length:sizeof(int)];
719     [data appendBytes:name length:len];
721     [self queueMessage:ShowPopupMenuMsgID data:data];
724 - (void)showToolbar:(int)enable flags:(int)flags
726     NSMutableData *data = [NSMutableData data];
728     [data appendBytes:&enable length:sizeof(int)];
729     [data appendBytes:&flags length:sizeof(int)];
731     [self queueMessage:ShowToolbarMsgID data:data];
734 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
736     NSMutableData *data = [NSMutableData data];
738     [data appendBytes:&ident length:sizeof(long)];
739     [data appendBytes:&type length:sizeof(int)];
741     [self queueMessage:CreateScrollbarMsgID data:data];
744 - (void)destroyScrollbarWithIdentifier:(long)ident
746     NSMutableData *data = [NSMutableData data];
747     [data appendBytes:&ident length:sizeof(long)];
749     [self queueMessage:DestroyScrollbarMsgID data:data];
752 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
754     NSMutableData *data = [NSMutableData data];
756     [data appendBytes:&ident length:sizeof(long)];
757     [data appendBytes:&visible length:sizeof(int)];
759     [self queueMessage:ShowScrollbarMsgID data:data];
762 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
764     NSMutableData *data = [NSMutableData data];
766     [data appendBytes:&ident length:sizeof(long)];
767     [data appendBytes:&pos length:sizeof(int)];
768     [data appendBytes:&len length:sizeof(int)];
770     [self queueMessage:SetScrollbarPositionMsgID data:data];
773 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
774                     identifier:(long)ident
776     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
777     float prop = (float)size/(max+1);
778     if (fval < 0) fval = 0;
779     else if (fval > 1.0f) fval = 1.0f;
780     if (prop < 0) prop = 0;
781     else if (prop > 1.0f) prop = 1.0f;
783     NSMutableData *data = [NSMutableData data];
785     [data appendBytes:&ident length:sizeof(long)];
786     [data appendBytes:&fval length:sizeof(float)];
787     [data appendBytes:&prop length:sizeof(float)];
789     [self queueMessage:SetScrollbarThumbMsgID data:data];
792 - (BOOL)setFontWithName:(char *)name
794     NSString *fontName = MMDefaultFontName;
795     float size = MMDefaultFontSize;
796     BOOL parseFailed = NO;
798     if (name) {
799         fontName = [NSString stringWithUTF8String:name];
801         if ([fontName isEqual:@"*"]) {
802             // :set gfn=* shows the font panel.
803             do_cmdline_cmd((char_u*)":action orderFrontFontPanel:");
804             return NO;
805         }
807         NSArray *components = [fontName componentsSeparatedByString:@":"];
808         if ([components count] == 2) {
809             NSString *sizeString = [components lastObject];
810             if ([sizeString length] > 0
811                     && [sizeString characterAtIndex:0] == 'h') {
812                 sizeString = [sizeString substringFromIndex:1];
813                 if ([sizeString length] > 0) {
814                     size = [sizeString floatValue];
815                     fontName = [components objectAtIndex:0];
816                 }
817             } else {
818                 parseFailed = YES;
819             }
820         } else if ([components count] > 2) {
821             parseFailed = YES;
822         }
823     }
825     if (!parseFailed && [fontName length] > 0) {
826         if (size < 6 || size > 100) {
827             // Font size 0.0 tells NSFont to use the 'user default size'.
828             size = 0.0f;
829         }
831         NSFont *font = [NSFont fontWithName:fontName size:size];
833         if (!font && MMDefaultFontName == fontName) {
834             // If for some reason the MacVim default font is not in the app
835             // bundle, then fall back on the system default font.
836             size = 0;
837             font = [NSFont userFixedPitchFontOfSize:size];
838             fontName = [font displayName];
839         }
841         if (font) {
842             //NSLog(@"Setting font '%@' of size %.2f", fontName, size);
843             int len = [fontName
844                     lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
845             if (len > 0) {
846                 NSMutableData *data = [NSMutableData data];
848                 [data appendBytes:&size length:sizeof(float)];
849                 [data appendBytes:&len length:sizeof(int)];
850                 [data appendBytes:[fontName UTF8String] length:len];
852                 [self queueMessage:SetFontMsgID data:data];
853                 return YES;
854             }
855         }
856     }
858     //NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f",
859     //        fontName, size);
860     return NO;
863 - (void)executeActionWithName:(NSString *)name
865     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
867     if (len > 0) {
868         NSMutableData *data = [NSMutableData data];
870         [data appendBytes:&len length:sizeof(int)];
871         [data appendBytes:[name UTF8String] length:len];
873         [self queueMessage:ExecuteActionMsgID data:data];
874     }
877 - (void)setMouseShape:(int)shape
879     NSMutableData *data = [NSMutableData data];
880     [data appendBytes:&shape length:sizeof(int)];
881     [self queueMessage:SetMouseShapeMsgID data:data];
884 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
886     // Vim specifies times in milliseconds, whereas Cocoa wants them in
887     // seconds.
888     blinkWaitInterval = .001f*wait;
889     blinkOnInterval = .001f*on;
890     blinkOffInterval = .001f*off;
893 - (void)startBlink
895     if (blinkTimer) {
896         [blinkTimer invalidate];
897         [blinkTimer release];
898         blinkTimer = nil;
899     }
901     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
902             && gui.in_focus) {
903         blinkState = MMBlinkStateOn;
904         blinkTimer =
905             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
906                                               target:self
907                                             selector:@selector(blinkTimerFired:)
908                                             userInfo:nil repeats:NO] retain];
909         gui_update_cursor(TRUE, FALSE);
910         [self flushQueue:YES];
911     }
914 - (void)stopBlink
916     if (MMBlinkStateOff == blinkState) {
917         gui_update_cursor(TRUE, FALSE);
918         [self flushQueue:YES];
919     }
921     blinkState = MMBlinkStateNone;
924 - (void)adjustLinespace:(int)linespace
926     NSMutableData *data = [NSMutableData data];
927     [data appendBytes:&linespace length:sizeof(int)];
928     [self queueMessage:AdjustLinespaceMsgID data:data];
931 - (void)activate
933     [self queueMessage:ActivateMsgID data:nil];
936 - (int)lookupColorWithKey:(NSString *)key
938     if (!(key && [key length] > 0))
939         return INVALCOLOR;
941     NSString *stripKey = [[[[key lowercaseString]
942         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
943             componentsSeparatedByString:@" "]
944                componentsJoinedByString:@""];
946     if (stripKey && [stripKey length] > 0) {
947         // First of all try to lookup key in the color dictionary; note that
948         // all keys in this dictionary are lowercase with no whitespace.
949         id obj = [colorDict objectForKey:stripKey];
950         if (obj) return [obj intValue];
952         // The key was not in the dictionary; is it perhaps of the form
953         // #rrggbb?
954         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
955             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
956             [scanner setScanLocation:1];
957             unsigned hex = 0;
958             if ([scanner scanHexInt:&hex]) {
959                 return (int)hex;
960             }
961         }
963         // As a last resort, check if it is one of the system defined colors.
964         // The keys in this dictionary are also lowercase with no whitespace.
965         obj = [sysColorDict objectForKey:stripKey];
966         if (obj) {
967             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
968             if (col) {
969                 float r, g, b, a;
970                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
971                 [col getRed:&r green:&g blue:&b alpha:&a];
972                 return (((int)(r*255+.5f) & 0xff) << 16)
973                      + (((int)(g*255+.5f) & 0xff) << 8)
974                      +  ((int)(b*255+.5f) & 0xff);
975             }
976         }
977     }
979     NSLog(@"WARNING: No color with key %@ found.", stripKey);
980     return INVALCOLOR;
983 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
985     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
986     id obj;
988     while ((obj = [e nextObject])) {
989         if ([value isEqual:obj])
990             return YES;
991     }
993     return NO;
996 - (void)enterFullscreen
998     [self queueMessage:EnterFullscreenMsgID data:nil];
1001 - (void)leaveFullscreen
1003     [self queueMessage:LeaveFullscreenMsgID data:nil];
1006 - (oneway void)processInput:(int)msgid data:(in NSData *)data
1008     // NOTE: This method might get called whenever the run loop is tended to.
1009     // Thus it might get called whilst input is being processed.  Normally this
1010     // is not a problem, but if it gets called often then it might become
1011     // dangerous.  E.g. say a message causes the screen to be redrawn and then
1012     // another message is received causing another simultaneous screen redraw;
1013     // this is not good.  To deal with this problem at the moment, we simply
1014     // drop messages that are received while other input is being processed.
1015     if (inProcessInput) {
1016 #if MM_USE_INPUT_QUEUE
1017         [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1018         [inputQueue addObject:data];
1019 #else
1020         // Just drop the input
1021         //NSLog(@"WARNING: Dropping input in %s", _cmd);
1022 #endif
1023     } else {
1024         [self processInputBegin];
1025         [self handleMessage:msgid data:data];
1026         [self processInputEnd];
1027     }
1030 - (oneway void)processInputAndData:(in NSArray *)messages
1032     // NOTE: See comment in processInput:data:.
1033     unsigned i, count = [messages count];
1034     if (count % 2) {
1035         NSLog(@"WARNING: [messages count] is odd in %s", _cmd);
1036         return;
1037     }
1039     if (inProcessInput) {
1040 #if MM_USE_INPUT_QUEUE
1041         [inputQueue addObjectsFromArray:messages];
1042 #else
1043         // Just drop the input
1044         //NSLog(@"WARNING: Dropping input in %s", _cmd);
1045 #endif
1046     } else {
1047         [self processInputBegin];
1049         for (i = 0; i < count; i += 2) {
1050             int msgid = [[messages objectAtIndex:i] intValue];
1051             id data = [messages objectAtIndex:i+1];
1052             if ([data isEqual:[NSNull null]])
1053                 data = nil;
1055             [self handleMessage:msgid data:data];
1056         }
1058         [self processInputEnd];
1059     }
1062 - (BOOL)checkForModifiedBuffers
1064     buf_T *buf;
1065     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
1066         if (bufIsChanged(buf)) {
1067             return YES;
1068         }
1069     }
1071     return NO;
1074 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1076     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1077         // If there is no pasteboard, return YES to indicate that there is text
1078         // to copy.
1079         if (!pboard)
1080             return YES;
1082         clip_copy_selection();
1084         // Get the text to put on the pasteboard.
1085         long_u llen = 0; char_u *str = 0;
1086         int type = clip_convert_selection(&str, &llen, &clip_star);
1087         if (type < 0)
1088             return NO;
1089         
1090         // TODO: Avoid overflow.
1091         int len = (int)llen;
1092 #if MM_ENABLE_CONV
1093         if (output_conv.vc_type != CONV_NONE) {
1094             char_u *conv_str = string_convert(&output_conv, str, &len);
1095             if (conv_str) {
1096                 vim_free(str);
1097                 str = conv_str;
1098             }
1099         }
1100 #endif
1102         NSString *string = [[NSString alloc]
1103             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1105         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1106         [pboard declareTypes:types owner:nil];
1107         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1108     
1109         [string release];
1110         vim_free(str);
1112         return ok;
1113     }
1115     return NO;
1118 - (oneway void)addReply:(in bycopy NSString *)reply
1119                  server:(in byref id <MMVimServerProtocol>)server
1121     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1123     // Replies might come at any time and in any order so we keep them in an
1124     // array inside a dictionary with the send port used as key.
1126     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1127     // HACK! Assume connection uses mach ports.
1128     int port = [(NSMachPort*)[conn sendPort] machPort];
1129     NSNumber *key = [NSNumber numberWithInt:port];
1131     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1132     if (!replies) {
1133         replies = [NSMutableArray array];
1134         [serverReplyDict setObject:replies forKey:key];
1135     }
1137     [replies addObject:reply];
1140 - (void)addInput:(in bycopy NSString *)input
1141                  client:(in byref id <MMVimClientProtocol>)client
1143     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1145     char_u *s = (char_u*)[input UTF8String];
1147 #if MM_ENABLE_CONV
1148     s = CONVERT_FROM_UTF8(s);
1149 #endif
1151     server_to_input_buf(s);
1153 #if MM_ENABLE_CONV
1154     CONVERT_FROM_UTF8_FREE(s);
1155 #endif
1157     [self addClient:(id)client];
1159     inputReceived = YES;
1162 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1163                  client:(in byref id <MMVimClientProtocol>)client
1165     //NSLog(@"evaluateExpression:%@ client:%@", expr, (id)client);
1167     NSString *eval = nil;
1168     char_u *s = (char_u*)[expr UTF8String];
1170 #if MM_ENABLE_CONV
1171     s = CONVERT_FROM_UTF8(s);
1172 #endif
1174     char_u *res = eval_client_expr_to_string(s);
1176 #if MM_ENABLE_CONV
1177     CONVERT_FROM_UTF8_FREE(s);
1178 #endif
1180     if (res != NULL) {
1181         s = res;
1182 #if MM_ENABLE_CONV
1183         s = CONVERT_TO_UTF8(s);
1184 #endif
1185         eval = [NSString stringWithUTF8String:(char*)s];
1186 #if MM_ENABLE_CONV
1187         CONVERT_TO_UTF8_FREE(s);
1188 #endif
1189         vim_free(res);
1190     }
1192     [self addClient:(id)client];
1194     return eval;
1197 - (void)registerServerWithName:(NSString *)name
1199     NSString *svrName = name;
1200     NSConnection *svrConn = [NSConnection defaultConnection];
1201     unsigned i;
1203     for (i = 0; i < MMServerMax; ++i) {
1204         NSString *connName = [self connectionNameFromServerName:svrName];
1206         if ([svrConn registerName:connName]) {
1207             //NSLog(@"Registered server with name: %@", svrName);
1209             // TODO: Set request/reply time-outs to something else?
1210             //
1211             // Don't wait for requests (time-out means that the message is
1212             // dropped).
1213             [svrConn setRequestTimeout:0];
1214             //[svrConn setReplyTimeout:MMReplyTimeout];
1215             [svrConn setRootObject:self];
1217             char_u *s = (char_u*)[svrName UTF8String];
1218 #if MM_ENABLE_CONV
1219             s = CONVERT_FROM_UTF8(s);
1220 #endif
1221             // NOTE: 'serverName' is a global variable
1222             serverName = vim_strsave(s);
1223 #if MM_ENABLE_CONV
1224             CONVERT_FROM_UTF8_FREE(s);
1225 #endif
1226 #ifdef FEAT_EVAL
1227             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1228 #endif
1229 #ifdef FEAT_TITLE
1230             need_maketitle = TRUE;
1231 #endif
1232             [self queueMessage:SetServerNameMsgID data:
1233                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1234             break;
1235         }
1237         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1238     }
1241 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1242                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1243               silent:(BOOL)silent
1245     // NOTE: If 'name' equals 'serverName' then the request is local (client
1246     // and server are the same).  This case is not handled separately, so a
1247     // connection will be set up anyway (this simplifies the code).
1249     NSConnection *conn = [self connectionForServerName:name];
1250     if (!conn) {
1251         if (!silent) {
1252             char_u *s = (char_u*)[name UTF8String];
1253 #if MM_ENABLE_CONV
1254             s = CONVERT_FROM_UTF8(s);
1255 #endif
1256             EMSG2(_(e_noserver), s);
1257 #if MM_ENABLE_CONV
1258             CONVERT_FROM_UTF8_FREE(s);
1259 #endif
1260         }
1261         return NO;
1262     }
1264     if (port) {
1265         // HACK! Assume connection uses mach ports.
1266         *port = [(NSMachPort*)[conn sendPort] machPort];
1267     }
1269     id proxy = [conn rootProxy];
1270     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1272     @try {
1273         if (expr) {
1274             NSString *eval = [proxy evaluateExpression:string client:self];
1275             if (reply) {
1276                 if (eval) {
1277                     char_u *r = (char_u*)[eval UTF8String];
1278 #if MM_ENABLE_CONV
1279                     r = CONVERT_FROM_UTF8(r);
1280 #endif
1281                     *reply = vim_strsave(r);
1282 #if MM_ENABLE_CONV
1283                     CONVERT_FROM_UTF8_FREE(r);
1284 #endif
1285                 } else {
1286                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1287                 }
1288             }
1290             if (!eval)
1291                 return NO;
1292         } else {
1293             [proxy addInput:string client:self];
1294         }
1295     }
1296     @catch (NSException *e) {
1297         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1298         return NO;
1299     }
1301     return YES;
1304 - (NSArray *)serverList
1306     NSArray *list = nil;
1308     if ([self connection]) {
1309         id proxy = [connection rootProxy];
1310         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1312         @try {
1313             list = [proxy serverList];
1314         }
1315         @catch (NSException *e) {
1316             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1317         }
1318     } else {
1319         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1320     }
1322     return list;
1325 - (NSString *)peekForReplyOnPort:(int)port
1327     //NSLog(@"%s%d", _cmd, port);
1329     NSNumber *key = [NSNumber numberWithInt:port];
1330     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1331     if (replies && [replies count]) {
1332         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1333         //        [replies objectAtIndex:0]);
1334         return [replies objectAtIndex:0];
1335     }
1337     //NSLog(@"    No replies");
1338     return nil;
1341 - (NSString *)waitForReplyOnPort:(int)port
1343     //NSLog(@"%s%d", _cmd, port);
1344     
1345     NSConnection *conn = [self connectionForServerPort:port];
1346     if (!conn)
1347         return nil;
1349     NSNumber *key = [NSNumber numberWithInt:port];
1350     NSMutableArray *replies = nil;
1351     NSString *reply = nil;
1353     // Wait for reply as long as the connection to the server is valid (unless
1354     // user interrupts wait with Ctrl-C).
1355     while (!got_int && [conn isValid] &&
1356             !(replies = [serverReplyDict objectForKey:key])) {
1357         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1358                                  beforeDate:[NSDate distantFuture]];
1359     }
1361     if (replies) {
1362         if ([replies count] > 0) {
1363             reply = [[replies objectAtIndex:0] retain];
1364             //NSLog(@"    Got reply: %@", reply);
1365             [replies removeObjectAtIndex:0];
1366             [reply autorelease];
1367         }
1369         if ([replies count] == 0)
1370             [serverReplyDict removeObjectForKey:key];
1371     }
1373     return reply;
1376 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1378     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1379     if (client) {
1380         @try {
1381             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1382             [client addReply:reply server:self];
1383             return YES;
1384         }
1385         @catch (NSException *e) {
1386             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1387         }
1388     } else {
1389         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1390     }
1392     return NO;
1395 @end // MMBackend
1399 @implementation MMBackend (Private)
1401 - (void)handleMessage:(int)msgid data:(NSData *)data
1403     if (InsertTextMsgID == msgid) {
1404         if (!data) return;
1405         NSString *key = [[NSString alloc] initWithData:data
1406                                               encoding:NSUTF8StringEncoding];
1407         char_u *str = (char_u*)[key UTF8String];
1408         int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1410 #if MM_ENABLE_CONV
1411         char_u *conv_str = NULL;
1412         if (input_conv.vc_type != CONV_NONE) {
1413             conv_str = string_convert(&input_conv, str, &len);
1414             if (conv_str)
1415                 str = conv_str;
1416         }
1417 #endif
1419         for (i = 0; i < len; ++i) {
1420             add_to_input_buf(str+i, 1);
1421             if (CSI == str[i]) {
1422                 // NOTE: If the converted string contains the byte CSI, then it
1423                 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1424                 // won't work.
1425                 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1426                 add_to_input_buf(extra, 2);
1427             }
1428         }
1430 #if MM_ENABLE_CONV
1431         if (conv_str)
1432             vim_free(conv_str);
1433 #endif
1434         [key release];
1435     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1436         if (!data) return;
1437         const void *bytes = [data bytes];
1438         int mods = *((int*)bytes);  bytes += sizeof(int);
1439         int len = *((int*)bytes);  bytes += sizeof(int);
1440         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1441                                               encoding:NSUTF8StringEncoding];
1442         mods = eventModifierFlagsToVimModMask(mods);
1444         [self handleKeyDown:key modifiers:mods];
1446         [key release];
1447     } else if (SelectTabMsgID == msgid) {
1448         if (!data) return;
1449         const void *bytes = [data bytes];
1450         int idx = *((int*)bytes) + 1;
1451         //NSLog(@"Selecting tab %d", idx);
1452         send_tabline_event(idx);
1453     } else if (CloseTabMsgID == msgid) {
1454         if (!data) return;
1455         const void *bytes = [data bytes];
1456         int idx = *((int*)bytes) + 1;
1457         //NSLog(@"Closing tab %d", idx);
1458         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1459     } else if (AddNewTabMsgID == msgid) {
1460         //NSLog(@"Adding new tab");
1461         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1462     } else if (DraggedTabMsgID == msgid) {
1463         if (!data) return;
1464         const void *bytes = [data bytes];
1465         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1466         // based.
1467         int idx = *((int*)bytes);
1469         tabpage_move(idx);
1470     } else if (ScrollWheelMsgID == msgid) {
1471         if (!data) return;
1472         const void *bytes = [data bytes];
1474         int row = *((int*)bytes);  bytes += sizeof(int);
1475         int col = *((int*)bytes);  bytes += sizeof(int);
1476         int flags = *((int*)bytes);  bytes += sizeof(int);
1477         float dy = *((float*)bytes);  bytes += sizeof(float);
1479         int button = MOUSE_5;
1480         if (dy > 0) button = MOUSE_4;
1482         flags = eventModifierFlagsToVimMouseModMask(flags);
1484         gui_send_mouse_event(button, col, row, NO, flags);
1485     } else if (MouseDownMsgID == msgid) {
1486         if (!data) return;
1487         const void *bytes = [data bytes];
1489         int row = *((int*)bytes);  bytes += sizeof(int);
1490         int col = *((int*)bytes);  bytes += sizeof(int);
1491         int button = *((int*)bytes);  bytes += sizeof(int);
1492         int flags = *((int*)bytes);  bytes += sizeof(int);
1493         int count = *((int*)bytes);  bytes += sizeof(int);
1495         button = eventButtonNumberToVimMouseButton(button);
1496         flags = eventModifierFlagsToVimMouseModMask(flags);
1498         gui_send_mouse_event(button, col, row, count>1, flags);
1499     } else if (MouseUpMsgID == msgid) {
1500         if (!data) return;
1501         const void *bytes = [data bytes];
1503         int row = *((int*)bytes);  bytes += sizeof(int);
1504         int col = *((int*)bytes);  bytes += sizeof(int);
1505         int flags = *((int*)bytes);  bytes += sizeof(int);
1507         flags = eventModifierFlagsToVimMouseModMask(flags);
1509         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1510     } else if (MouseDraggedMsgID == msgid) {
1511         if (!data) return;
1512         const void *bytes = [data bytes];
1514         int row = *((int*)bytes);  bytes += sizeof(int);
1515         int col = *((int*)bytes);  bytes += sizeof(int);
1516         int flags = *((int*)bytes);  bytes += sizeof(int);
1518         flags = eventModifierFlagsToVimMouseModMask(flags);
1520         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1521     } else if (SetTextDimensionsMsgID == msgid) {
1522         if (!data) return;
1523         const void *bytes = [data bytes];
1524         int rows = *((int*)bytes);  bytes += sizeof(int);
1525         int cols = *((int*)bytes);  bytes += sizeof(int);
1527         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1528         // gui_resize_shell(), so we have to manually set the rows and columns
1529         // here.  (MacVim doesn't change the rows and columns to avoid
1530         // inconsistent states between Vim and MacVim.)
1531         [self setRows:rows columns:cols];
1533         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1534         gui_resize_shell(cols, rows);
1535     } else if (ExecuteMenuMsgID == msgid) {
1536         if (!data) return;
1537         const void *bytes = [data bytes];
1538         int tag = *((int*)bytes);  bytes += sizeof(int);
1540         vimmenu_T *menu = (vimmenu_T*)tag;
1541         // TODO!  Make sure 'menu' is a valid menu pointer!
1542         if (menu) {
1543             gui_menu_cb(menu);
1544         }
1545     } else if (ToggleToolbarMsgID == msgid) {
1546         char_u go[sizeof(GO_ALL)+2];
1547         char_u *p;
1548         int len;
1550         STRCPY(go, p_go);
1551         p = vim_strchr(go, GO_TOOLBAR);
1552         len = STRLEN(go);
1554         if (p != NULL) {
1555             char_u *end = go + len;
1556             while (p < end) {
1557                 p[0] = p[1];
1558                 ++p;
1559             }
1560         } else {
1561             go[len] = GO_TOOLBAR;
1562             go[len+1] = NUL;
1563         }
1565         set_option_value((char_u*)"guioptions", 0, go, 0);
1567         // Force screen redraw (does it have to be this complicated?).
1568         redraw_all_later(CLEAR);
1569         update_screen(NOT_VALID);
1570         setcursor();
1571         out_flush();
1572         gui_update_cursor(FALSE, FALSE);
1573         gui_mch_flush();
1574     } else if (ScrollbarEventMsgID == msgid) {
1575         if (!data) return;
1576         const void *bytes = [data bytes];
1577         long ident = *((long*)bytes);  bytes += sizeof(long);
1578         int hitPart = *((int*)bytes);  bytes += sizeof(int);
1579         float fval = *((float*)bytes);  bytes += sizeof(float);
1580         scrollbar_T *sb = gui_find_scrollbar(ident);
1582         if (sb) {
1583             scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1584             long value = sb_info->value;
1585             long size = sb_info->size;
1586             long max = sb_info->max;
1587             BOOL isStillDragging = NO;
1588             BOOL updateKnob = YES;
1590             switch (hitPart) {
1591             case NSScrollerDecrementPage:
1592                 value -= (size > 2 ? size - 2 : 1);
1593                 break;
1594             case NSScrollerIncrementPage:
1595                 value += (size > 2 ? size - 2 : 1);
1596                 break;
1597             case NSScrollerDecrementLine:
1598                 --value;
1599                 break;
1600             case NSScrollerIncrementLine:
1601                 ++value;
1602                 break;
1603             case NSScrollerKnob:
1604                 isStillDragging = YES;
1605                 // fall through ...
1606             case NSScrollerKnobSlot:
1607                 value = (long)(fval * (max - size + 1));
1608                 // fall through ...
1609             default:
1610                 updateKnob = NO;
1611                 break;
1612             }
1614             //NSLog(@"value %d -> %d", sb_info->value, value);
1615             gui_drag_scrollbar(sb, value, isStillDragging);
1617             if (updateKnob) {
1618                 // Dragging the knob or option+clicking automatically updates
1619                 // the knob position (on the actual NSScroller), so we only
1620                 // need to set the knob position in the other cases.
1621                 if (sb->wp) {
1622                     // Update both the left&right vertical scrollbars.
1623                     long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1624                     long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1625                     [self setScrollbarThumbValue:value size:size max:max
1626                                       identifier:identLeft];
1627                     [self setScrollbarThumbValue:value size:size max:max
1628                                       identifier:identRight];
1629                 } else {
1630                     // Update the horizontal scrollbar.
1631                     [self setScrollbarThumbValue:value size:size max:max
1632                                       identifier:ident];
1633                 }
1634             }
1635         }
1636     } else if (SetFontMsgID == msgid) {
1637         if (!data) return;
1638         const void *bytes = [data bytes];
1639         float pointSize = *((float*)bytes);  bytes += sizeof(float);
1640         //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1641         bytes += sizeof(unsigned);  // len not used
1643         NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1644         [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1645         char_u *s = (char_u*)[name UTF8String];
1647 #if MM_ENABLE_CONV
1648         s = CONVERT_FROM_UTF8(s);
1649 #endif
1651         set_option_value((char_u*)"guifont", 0, s, 0);
1653 #if MM_ENABLE_CONV
1654         CONVERT_FROM_UTF8_FREE(s);
1655 #endif
1657         // Force screen redraw (does it have to be this complicated?).
1658         redraw_all_later(CLEAR);
1659         update_screen(NOT_VALID);
1660         setcursor();
1661         out_flush();
1662         gui_update_cursor(FALSE, FALSE);
1663         gui_mch_flush();
1664     } else if (VimShouldCloseMsgID == msgid) {
1665         gui_shell_closed();
1666     } else if (DropFilesMsgID == msgid) {
1667 #ifdef FEAT_DND
1668         const void *bytes = [data bytes];
1669         const void *end = [data bytes] + [data length];
1670         int n = *((int*)bytes);  bytes += sizeof(int);
1672         if (State & CMDLINE) {
1673             // HACK!  If Vim is in command line mode then the files names
1674             // should be added to the command line, instead of opening the
1675             // files in tabs.  This is taken care of by gui_handle_drop().
1676             char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1677             if (fnames) {
1678                 int i = 0;
1679                 while (bytes < end && i < n) {
1680                     int len = *((int*)bytes);  bytes += sizeof(int);
1681                     char_u *s = (char_u*)bytes;
1682 #if MM_ENABLE_CONV
1683                     s = CONVERT_FROM_UTF8(s);
1684 #endif
1685                     fnames[i++] = vim_strsave(s);
1686 #if MM_ENABLE_CONV
1687                     CONVERT_FROM_UTF8_FREE(s);
1688 #endif
1689                     bytes += len;
1690                 }
1692                 // NOTE!  This function will free 'fnames'.
1693                 // HACK!  It is assumed that the 'x' and 'y' arguments are
1694                 // unused when in command line mode.
1695                 gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
1696             }
1697         } else {
1698             // HACK!  I'm not sure how to get Vim to open a list of files in
1699             // tabs, so instead I create a ':tab drop' command with all the
1700             // files to open and execute it.
1701             NSMutableString *cmd = (n > 1)
1702                     ? [NSMutableString stringWithString:@":tab drop"]
1703                     : [NSMutableString stringWithString:@":drop"];
1705             int i;
1706             for (i = 0; i < n && bytes < end; ++i) {
1707                 int len = *((int*)bytes);  bytes += sizeof(int);
1708                 NSMutableString *file =
1709                         [NSMutableString stringWithUTF8String:bytes];
1710                 [file replaceOccurrencesOfString:@" "
1711                                       withString:@"\\ "
1712                                          options:0
1713                                            range:NSMakeRange(0,[file length])];
1714                 bytes += len;
1716                 [cmd appendString:@" "];
1717                 [cmd appendString:file];
1718             }
1720             // By going to the last tabpage we ensure that the new tabs will
1721             // appear last (if this call is left out, the taborder becomes
1722             // messy).
1723             goto_tabpage(9999);
1725             char_u *s = (char_u*)[cmd UTF8String];
1726 #if MM_ENABLE_CONV
1727             s = CONVERT_FROM_UTF8(s);
1728 #endif
1729             do_cmdline_cmd(s);
1730 #if MM_ENABLE_CONV
1731             CONVERT_FROM_UTF8_FREE(s);
1732 #endif
1734             // Force screen redraw (does it have to be this complicated?).
1735             // (This code was taken from the end of gui_handle_drop().)
1736             update_screen(NOT_VALID);
1737             setcursor();
1738             out_flush();
1739             gui_update_cursor(FALSE, FALSE);
1740             gui_mch_flush();
1741         }
1742 #endif // FEAT_DND
1743     } else if (DropStringMsgID == msgid) {
1744 #ifdef FEAT_DND
1745         char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
1746         const void *bytes = [data bytes];
1747         int len = *((int*)bytes);  bytes += sizeof(int);
1748         NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
1750         // Replace unrecognized end-of-line sequences with \x0a (line feed).
1751         NSRange range = { 0, [string length] };
1752         unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
1753                                              withString:@"\x0a" options:0
1754                                                   range:range];
1755         if (0 == n) {
1756             n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
1757                                            options:0 range:range];
1758         }
1760         len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1761         char_u *s = (char_u*)[string UTF8String];
1762 #if MM_ENABLE_CONV
1763         if (input_conv.vc_type != CONV_NONE)
1764             s = string_convert(&input_conv, s, &len);
1765 #endif
1766         dnd_yank_drag_data(s, len);
1767 #if MM_ENABLE_CONV
1768         if (input_conv.vc_type != CONV_NONE)
1769             vim_free(s);
1770 #endif
1771         add_to_input_buf(dropkey, sizeof(dropkey));
1772 #endif // FEAT_DND
1773     } else if (GotFocusMsgID == msgid) {
1774         if (!gui.in_focus)
1775             [self focusChange:YES];
1776     } else if (LostFocusMsgID == msgid) {
1777         if (gui.in_focus)
1778             [self focusChange:NO];
1779     } else if (MouseMovedMsgID == msgid) {
1780         const void *bytes = [data bytes];
1781         int row = *((int*)bytes);  bytes += sizeof(int);
1782         int col = *((int*)bytes);  bytes += sizeof(int);
1784         gui_mouse_moved(col, row);
1785     } else if (SetMouseShapeMsgID == msgid) {
1786         const void *bytes = [data bytes];
1787         int shape = *((int*)bytes);  bytes += sizeof(int);
1788         update_mouseshape(shape);
1789     } else {
1790         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1791     }
1794 + (NSDictionary *)specialKeys
1796     static NSDictionary *specialKeys = nil;
1798     if (!specialKeys) {
1799         NSBundle *mainBundle = [NSBundle mainBundle];
1800         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1801                                               ofType:@"plist"];
1802         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1803     }
1805     return specialKeys;
1808 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1810     char_u special[3];
1811     char_u modChars[3];
1812     char_u *chars = (char_u*)[key UTF8String];
1813 #if MM_ENABLE_CONV
1814     char_u *conv_str = NULL;
1815 #endif
1816     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1818     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1819     // that new keys can easily be added.
1820     NSString *specialString = [[MMBackend specialKeys]
1821             objectForKey:key];
1822     if (specialString && [specialString length] > 1) {
1823         //NSLog(@"special key: %@", specialString);
1824         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1825                 [specialString characterAtIndex:1]);
1827         ikey = simplify_key(ikey, &mods);
1828         if (ikey == CSI)
1829             ikey = K_CSI;
1831         special[0] = CSI;
1832         special[1] = K_SECOND(ikey);
1833         special[2] = K_THIRD(ikey);
1835         chars = special;
1836         length = 3;
1837     } else if (1 == length && TAB == chars[0]) {
1838         // Tab is a trouble child:
1839         // - <Tab> is added to the input buffer as is
1840         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1841         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1842         //   to be converted to utf-8
1843         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1844         // - <C-Tab> is reserved by Mac OS X
1845         // - <D-Tab> is reserved by Mac OS X
1846         chars = special;
1847         special[0] = TAB;
1848         length = 1;
1850         if (mods & MOD_MASK_SHIFT) {
1851             mods &= ~MOD_MASK_SHIFT;
1852             special[0] = CSI;
1853             special[1] = K_SECOND(K_S_TAB);
1854             special[2] = K_THIRD(K_S_TAB);
1855             length = 3;
1856         } else if (mods & MOD_MASK_ALT) {
1857             int mtab = 0x80 | TAB;
1858             if (enc_utf8) {
1859                 // Convert to utf-8
1860                 special[0] = (mtab >> 6) + 0xc0;
1861                 special[1] = mtab & 0xbf;
1862                 length = 2;
1863             } else {
1864                 special[0] = mtab;
1865                 length = 1;
1866             }
1867             mods &= ~MOD_MASK_ALT;
1868         }
1869     } else if (length > 0) {
1870         unichar c = [key characterAtIndex:0];
1872         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1873         //        [key characterAtIndex:0], mods);
1875         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1876                 || (c == intr_char && intr_char != Ctrl_C))) {
1877             trash_input_buf();
1878             got_int = TRUE;
1879         }
1881         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1882         // cleared since they are already added to the key by the AppKit.
1883         // Unfortunately, the only way to deal with when to clear the modifiers
1884         // or not seems to be to have hard-wired rules like this.
1885         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1886                     || 0x9 == c) ) {
1887             mods &= ~MOD_MASK_SHIFT;
1888             mods &= ~MOD_MASK_CTRL;
1889             //NSLog(@"clear shift ctrl");
1890         }
1892         // HACK!  All Option+key presses go via 'insert text' messages, except
1893         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1894         // not work to map to it.
1895         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1896             //NSLog(@"clear alt");
1897             mods &= ~MOD_MASK_ALT;
1898         }
1900 #if MM_ENABLE_CONV
1901         if (input_conv.vc_type != CONV_NONE) {
1902             conv_str = string_convert(&input_conv, chars, &length);
1903             if (conv_str)
1904                 chars = conv_str;
1905         }
1906 #endif
1907     }
1909     if (chars && length > 0) {
1910         if (mods) {
1911             //NSLog(@"adding mods: %d", mods);
1912             modChars[0] = CSI;
1913             modChars[1] = KS_MODIFIER;
1914             modChars[2] = mods;
1915             add_to_input_buf(modChars, 3);
1916         }
1918         //NSLog(@"add to input buf: 0x%x", chars[0]);
1919         // TODO: Check for CSI bytes?
1920         add_to_input_buf(chars, length);
1921     }
1923 #if MM_ENABLE_CONV
1924     if (conv_str)
1925         vim_free(conv_str);
1926 #endif
1929 - (void)queueMessage:(int)msgid data:(NSData *)data
1931     [queue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1932     if (data)
1933         [queue addObject:data];
1934     else
1935         [queue addObject:[NSData data]];
1938 - (void)connectionDidDie:(NSNotification *)notification
1940     // If the main connection to MacVim is lost this means that MacVim was
1941     // either quit (by the user chosing Quit on the MacVim menu), or it has
1942     // crashed.  In either case our only option is to quit now.
1943     // TODO: Write backup file?
1945     //NSLog(@"A Vim process lots its connection to MacVim; quitting.");
1946     getout(0);
1949 - (void)blinkTimerFired:(NSTimer *)timer
1951     NSTimeInterval timeInterval = 0;
1953     [blinkTimer release];
1954     blinkTimer = nil;
1956     if (MMBlinkStateOn == blinkState) {
1957         gui_undraw_cursor();
1958         blinkState = MMBlinkStateOff;
1959         timeInterval = blinkOffInterval;
1960     } else if (MMBlinkStateOff == blinkState) {
1961         gui_update_cursor(TRUE, FALSE);
1962         blinkState = MMBlinkStateOn;
1963         timeInterval = blinkOnInterval;
1964     }
1966     if (timeInterval > 0) {
1967         blinkTimer = 
1968             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1969                                             selector:@selector(blinkTimerFired:)
1970                                             userInfo:nil repeats:NO] retain];
1971         [self flushQueue:YES];
1972     }
1975 - (void)focusChange:(BOOL)on
1977     gui_focus_change(on);
1980 - (void)processInputBegin
1982     inProcessInput = YES;
1984     // Don't flush too soon or update speed will suffer.
1985     [lastFlushDate release];
1986     lastFlushDate = [[NSDate date] retain];
1989 - (void)processInputEnd
1991 #if MM_USE_INPUT_QUEUE
1992     int count = [inputQueue count];
1993     if (count % 2) {
1994         // TODO: This is troubling, but it is not hard to get Vim to end up
1995         // here.  Why does this happen?
1996         NSLog(@"WARNING: inputQueue has odd number of objects (%d)", count);
1997         [inputQueue removeAllObjects];
1998     } else if (count > 0) {
1999         // TODO: Dispatch these messages?  Maybe not; usually when the
2000         // 'inputQueue' is non-empty it means that a LOT of messages has been
2001         // sent simultaneously.  The only way this happens is when Vim is being
2002         // tormented, e.g. if the user holds down <D-`> to rapidly switch
2003         // windows.
2004         unsigned i;
2005         for (i = 0; i < count; i+=2) {
2006             int msgid = [[inputQueue objectAtIndex:i] intValue];
2007             NSLog(@"%s: Dropping message %s", _cmd, MessageStrings[msgid]);
2008         }
2010         [inputQueue removeAllObjects];
2011     }
2012 #endif
2014     inputReceived = YES;
2015     inProcessInput = NO;
2018 - (NSString *)connectionNameFromServerName:(NSString *)name
2020     NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2022     return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2023         lowercaseString];
2026 - (NSConnection *)connectionForServerName:(NSString *)name
2028     // TODO: Try 'name%d' if 'name' fails.
2029     NSString *connName = [self connectionNameFromServerName:name];
2030     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2032     if (!svrConn) {
2033         svrConn = [NSConnection connectionWithRegisteredName:connName
2034                                                            host:nil];
2035         // Try alternate server...
2036         if (!svrConn && alternateServerName) {
2037             //NSLog(@"  trying to connect to alternate server: %@",
2038             //        alternateServerName);
2039             connName = [self connectionNameFromServerName:alternateServerName];
2040             svrConn = [NSConnection connectionWithRegisteredName:connName
2041                                                             host:nil];
2042         }
2044         // Try looking for alternate servers...
2045         if (!svrConn) {
2046             //NSLog(@"  looking for alternate servers...");
2047             NSString *alt = [self alternateServerNameForName:name];
2048             if (alt != alternateServerName) {
2049                 //NSLog(@"  found alternate server: %@", string);
2050                 [alternateServerName release];
2051                 alternateServerName = [alt copy];
2052             }
2053         }
2055         // Try alternate server again...
2056         if (!svrConn && alternateServerName) {
2057             //NSLog(@"  trying to connect to alternate server: %@",
2058             //        alternateServerName);
2059             connName = [self connectionNameFromServerName:alternateServerName];
2060             svrConn = [NSConnection connectionWithRegisteredName:connName
2061                                                             host:nil];
2062         }
2064         if (svrConn) {
2065             [connectionNameDict setObject:svrConn forKey:connName];
2067             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2068             [[NSNotificationCenter defaultCenter] addObserver:self
2069                     selector:@selector(serverConnectionDidDie:)
2070                         name:NSConnectionDidDieNotification object:svrConn];
2071         }
2072     }
2074     return svrConn;
2077 - (NSConnection *)connectionForServerPort:(int)port
2079     NSConnection *conn;
2080     NSEnumerator *e = [connectionNameDict objectEnumerator];
2082     while ((conn = [e nextObject])) {
2083         // HACK! Assume connection uses mach ports.
2084         if (port == [(NSMachPort*)[conn sendPort] machPort])
2085             return conn;
2086     }
2088     return nil;
2091 - (void)serverConnectionDidDie:(NSNotification *)notification
2093     //NSLog(@"%s%@", _cmd, notification);
2095     NSConnection *svrConn = [notification object];
2097     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2098     [[NSNotificationCenter defaultCenter]
2099             removeObserver:self
2100                       name:NSConnectionDidDieNotification
2101                     object:svrConn];
2103     [connectionNameDict removeObjectsForKeys:
2104         [connectionNameDict allKeysForObject:svrConn]];
2106     // HACK! Assume connection uses mach ports.
2107     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2108     NSNumber *key = [NSNumber numberWithInt:port];
2110     [clientProxyDict removeObjectForKey:key];
2111     [serverReplyDict removeObjectForKey:key];
2114 - (void)addClient:(NSDistantObject *)client
2116     NSConnection *conn = [client connectionForProxy];
2117     // HACK! Assume connection uses mach ports.
2118     int port = [(NSMachPort*)[conn sendPort] machPort];
2119     NSNumber *key = [NSNumber numberWithInt:port];
2121     if (![clientProxyDict objectForKey:key]) {
2122         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2123         [clientProxyDict setObject:client forKey:key];
2124     }
2126     // NOTE: 'clientWindow' is a global variable which is used by <client>
2127     clientWindow = port;
2130 - (NSString *)alternateServerNameForName:(NSString *)name
2132     if (!(name && [name length] > 0))
2133         return nil;
2135     // Only look for alternates if 'name' doesn't end in a digit.
2136     unichar lastChar = [name characterAtIndex:[name length]-1];
2137     if (lastChar >= '0' && lastChar <= '9')
2138         return nil;
2140     // Look for alternates among all current servers.
2141     NSArray *list = [self serverList];
2142     if (!(list && [list count] > 0))
2143         return nil;
2145     // Filter out servers starting with 'name' and ending with a number. The
2146     // (?i) pattern ensures that the match is case insensitive.
2147     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2148     NSPredicate *pred = [NSPredicate predicateWithFormat:
2149             @"SELF MATCHES %@", pat];
2150     list = [list filteredArrayUsingPredicate:pred];
2151     if ([list count] > 0) {
2152         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2153         return [list objectAtIndex:0];
2154     }
2156     return nil;
2159 @end // MMBackend (Private)
2164 @implementation NSString (MMServerNameCompare)
2165 - (NSComparisonResult)serverNameCompare:(NSString *)string
2167     return [self compare:string
2168                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2170 @end
2175 static int eventModifierFlagsToVimModMask(int modifierFlags)
2177     int modMask = 0;
2179     if (modifierFlags & NSShiftKeyMask)
2180         modMask |= MOD_MASK_SHIFT;
2181     if (modifierFlags & NSControlKeyMask)
2182         modMask |= MOD_MASK_CTRL;
2183     if (modifierFlags & NSAlternateKeyMask)
2184         modMask |= MOD_MASK_ALT;
2185     if (modifierFlags & NSCommandKeyMask)
2186         modMask |= MOD_MASK_CMD;
2188     return modMask;
2191 static int vimModMaskToEventModifierFlags(int mods)
2193     int flags = 0;
2195     if (mods & MOD_MASK_SHIFT)
2196         flags |= NSShiftKeyMask;
2197     if (mods & MOD_MASK_CTRL)
2198         flags |= NSControlKeyMask;
2199     if (mods & MOD_MASK_ALT)
2200         flags |= NSAlternateKeyMask;
2201     if (mods & MOD_MASK_CMD)
2202         flags |= NSCommandKeyMask;
2204     return flags;
2207 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2209     int modMask = 0;
2211     if (modifierFlags & NSShiftKeyMask)
2212         modMask |= MOUSE_SHIFT;
2213     if (modifierFlags & NSControlKeyMask)
2214         modMask |= MOUSE_CTRL;
2215     if (modifierFlags & NSAlternateKeyMask)
2216         modMask |= MOUSE_ALT;
2218     return modMask;
2221 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2223     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE,
2224             MOUSE_X1, MOUSE_X2 };
2226     return mouseButton[buttonNumber < 5 ? buttonNumber : 0];
2229 static int specialKeyToNSKey(int key)
2231     if (!IS_SPECIAL(key))
2232         return key;
2234     static struct {
2235         int special;
2236         int nskey;
2237     } sp2ns[] = {
2238         { K_UP, NSUpArrowFunctionKey },
2239         { K_DOWN, NSDownArrowFunctionKey },
2240         { K_LEFT, NSLeftArrowFunctionKey },
2241         { K_RIGHT, NSRightArrowFunctionKey },
2242         { K_F1, NSF1FunctionKey },
2243         { K_F2, NSF2FunctionKey },
2244         { K_F3, NSF3FunctionKey },
2245         { K_F4, NSF4FunctionKey },
2246         { K_F5, NSF5FunctionKey },
2247         { K_F6, NSF6FunctionKey },
2248         { K_F7, NSF7FunctionKey },
2249         { K_F8, NSF8FunctionKey },
2250         { K_F9, NSF9FunctionKey },
2251         { K_F10, NSF10FunctionKey },
2252         { K_F11, NSF11FunctionKey },
2253         { K_F12, NSF12FunctionKey },
2254         { K_F13, NSF13FunctionKey },
2255         { K_F14, NSF14FunctionKey },
2256         { K_F15, NSF15FunctionKey },
2257         { K_F16, NSF16FunctionKey },
2258         { K_F17, NSF17FunctionKey },
2259         { K_F18, NSF18FunctionKey },
2260         { K_F19, NSF19FunctionKey },
2261         { K_F20, NSF20FunctionKey },
2262         { K_F21, NSF21FunctionKey },
2263         { K_F22, NSF22FunctionKey },
2264         { K_F23, NSF23FunctionKey },
2265         { K_F24, NSF24FunctionKey },
2266         { K_F25, NSF25FunctionKey },
2267         { K_F26, NSF26FunctionKey },
2268         { K_F27, NSF27FunctionKey },
2269         { K_F28, NSF28FunctionKey },
2270         { K_F29, NSF29FunctionKey },
2271         { K_F30, NSF30FunctionKey },
2272         { K_F31, NSF31FunctionKey },
2273         { K_F32, NSF32FunctionKey },
2274         { K_F33, NSF33FunctionKey },
2275         { K_F34, NSF34FunctionKey },
2276         { K_F35, NSF35FunctionKey },
2277         { K_DEL, NSBackspaceCharacter },
2278         { K_BS, NSDeleteCharacter },
2279         { K_HOME, NSHomeFunctionKey },
2280         { K_END, NSEndFunctionKey },
2281         { K_PAGEUP, NSPageUpFunctionKey },
2282         { K_PAGEDOWN, NSPageDownFunctionKey }
2283     };
2285     int i;
2286     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2287         if (sp2ns[i].special == key)
2288             return sp2ns[i].nskey;
2289     }
2291     return 0;