Menu items bound to :macaction are properly recognized (fixes bug where all
[MacVim/jjgod.git] / MMBackend.m
blob4e003a85fedd85b4f2c844ece978bdf72c67cbef
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
11 #import "MMBackend.h"
15 // NOTE: Colors in MMBackend are stored as unsigned ints on the form 0xaarrggbb
16 // whereas colors in Vim are int without the alpha component.  Also note that
17 // 'transp' is assumed to be a value between 0 and 100.
18 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
19 #define MM_COLOR_WITH_TRANSP(col,transp) \
20     ((unsigned)( ((col)&0xffffff) \
21         | ((((unsigned)((((100-(transp))*255)/100)+.5f))&0xff)<<24) ))
24 // This constant controls how often the command queue may be flushed.  If it is
25 // too small the app might feel unresponsive; if it is too large there might be
26 // long periods without the screen updating (e.g. when sourcing a large session
27 // file).  (The unit is seconds.)
28 static float MMFlushTimeoutInterval = 0.1f;
30 static unsigned MMServerMax = 1000;
32 // NOTE: The default font is bundled with the application.
33 static NSString *MMDefaultFontName = @"DejaVu Sans Mono";
34 static float MMDefaultFontSize = 12.0f;
36 // TODO: Move to separate file.
37 static int eventModifierFlagsToVimModMask(int modifierFlags);
38 static int vimModMaskToEventModifierFlags(int mods);
39 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
40 static int eventButtonNumberToVimMouseButton(int buttonNumber);
41 static int specialKeyToNSKey(int key);
43 enum {
44     MMBlinkStateNone = 0,
45     MMBlinkStateOn,
46     MMBlinkStateOff
51 @interface NSString (MMServerNameCompare)
52 - (NSComparisonResult)serverNameCompare:(NSString *)string;
53 @end
57 @interface MMBackend (Private)
58 - (void)handleMessage:(int)msgid data:(NSData *)data;
59 + (NSDictionary *)specialKeys;
60 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
61 - (void)queueMessage:(int)msgid data:(NSData *)data;
62 - (void)connectionDidDie:(NSNotification *)notification;
63 - (void)blinkTimerFired:(NSTimer *)timer;
64 - (void)focusChange:(BOOL)on;
65 - (void)processInputBegin;
66 - (void)processInputEnd;
67 - (NSString *)connectionNameFromServerName:(NSString *)name;
68 - (NSConnection *)connectionForServerName:(NSString *)name;
69 - (NSConnection *)connectionForServerPort:(int)port;
70 - (void)serverConnectionDidDie:(NSNotification *)notification;
71 - (void)addClient:(NSDistantObject *)client;
72 - (NSString *)alternateServerNameForName:(NSString *)name;
73 @end
77 @implementation MMBackend
79 + (MMBackend *)sharedInstance
81     static MMBackend *singleton = nil;
82     return singleton ? singleton : (singleton = [MMBackend new]);
85 - (id)init
87     if ((self = [super init])) {
88         fontContainerRef = loadFonts();
90         queue = [[NSMutableArray alloc] init];
91 #if MM_USE_INPUT_QUEUE
92         inputQueue = [[NSMutableArray alloc] init];
93 #endif
94         drawData = [[NSMutableData alloc] initWithCapacity:1024];
95         connectionNameDict = [[NSMutableDictionary alloc] init];
96         clientProxyDict = [[NSMutableDictionary alloc] init];
97         serverReplyDict = [[NSMutableDictionary alloc] init];
99         NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
100                                                          ofType:@"plist"];
101         if (path) {
102             colorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
103                 retain];
104         } else {
105             NSLog(@"WARNING: Could not locate Colors.plist.");
106         }
108         path = [[NSBundle mainBundle] pathForResource:@"SystemColors"
109                                                ofType:@"plist"];
110         if (path) {
111             sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
112                 retain];
113         } else {
114             NSLog(@"WARNING: Could not locate SystemColors.plist.");
115         }
116     }
118     return self;
121 - (void)dealloc
123     //NSLog(@"%@ %s", [self className], _cmd);
125     [[NSNotificationCenter defaultCenter] removeObserver:self];
127     [blinkTimer release];  blinkTimer = nil;
128 #if MM_USE_INPUT_QUEUE
129     [inputQueue release];  inputQueue = nil;
130 #endif
131     [alternateServerName release];  alternateServerName = nil;
132     [serverReplyDict release];  serverReplyDict = nil;
133     [clientProxyDict release];  clientProxyDict = nil;
134     [connectionNameDict release];  connectionNameDict = nil;
135     [queue release];  queue = nil;
136     [drawData release];  drawData = nil;
137     [frontendProxy release];  frontendProxy = nil;
138     [connection release];  connection = nil;
139     [sysColorDict release];  sysColorDict = nil;
140     [colorDict release];  colorDict = nil;
142     [super dealloc];
145 - (void)setBackgroundColor:(int)color
147     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
150 - (void)setForegroundColor:(int)color
152     foregroundColor = MM_COLOR(color);
155 - (void)setSpecialColor:(int)color
157     specialColor = MM_COLOR(color);
160 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
162     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
163     defaultForegroundColor = MM_COLOR(fg);
165     NSMutableData *data = [NSMutableData data];
167     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
168     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
170     [self queueMessage:SetDefaultColorsMsgID data:data];
173 - (NSConnection *)connection
175     if (!connection) {
176         // NOTE!  If the name of the connection changes here it must also be
177         // updated in MMAppController.m.
178         NSString *name = [NSString stringWithFormat:@"%@-connection",
179                [[NSBundle mainBundle] bundleIdentifier]];
181         connection = [NSConnection connectionWithRegisteredName:name host:nil];
182         [connection retain];
183     }
185     // NOTE: 'connection' may be nil here.
186     return connection;
189 - (BOOL)checkin
191     if (![self connection]) {
192         NSBundle *mainBundle = [NSBundle mainBundle];
193 #if 0
194         NSString *path = [mainBundle bundlePath];
195         if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
196             NSLog(@"WARNING: Failed to launch GUI with path %@", path);
197             return NO;
198         }
199 #else
200         // HACK!  It would be preferable to launch the GUI using NSWorkspace,
201         // however I have not managed to figure out how to pass arguments using
202         // NSWorkspace.
203         //
204         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
205         // that the GUI won't be activated (or raised) so there is a hack in
206         // MMWindowController which always raises the app when a new window is
207         // opened.
208         NSMutableArray *args = [NSMutableArray arrayWithObjects:
209             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
210         NSString *exeName = [[mainBundle infoDictionary]
211                 objectForKey:@"CFBundleExecutable"];
212         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
213         if (!path) {
214             NSLog(@"ERROR: Could not find MacVim executable in bundle");
215             return NO;
216         }
218         [NSTask launchedTaskWithLaunchPath:path arguments:args];
219 #endif
221         // HACK!  The NSWorkspaceDidLaunchApplicationNotification does not work
222         // for tasks like this, so poll the mach bootstrap server until it
223         // returns a valid connection.  Also set a time-out date so that we
224         // don't get stuck doing this forever.
225         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
226         while (!connection &&
227                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
228         {
229             [[NSRunLoop currentRunLoop]
230                     runMode:NSDefaultRunLoopMode
231                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
233             // NOTE: This call will set 'connection' as a side-effect.
234             [self connection];
235         }
237         if (!connection) {
238             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
239             return NO;
240         }
241     }
243     id proxy = [connection rootProxy];
244     [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
246     [[NSNotificationCenter defaultCenter] addObserver:self
247             selector:@selector(connectionDidDie:)
248                 name:NSConnectionDidDieNotification object:connection];
250     int pid = [[NSProcessInfo processInfo] processIdentifier];
252     @try {
253         frontendProxy = [proxy connectBackend:self pid:pid];
254     }
255     @catch (NSException *e) {
256         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
257     }
259     if (frontendProxy) {
260         [frontendProxy retain];
261         [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
262     }
264     return connection && frontendProxy;
267 - (BOOL)openVimWindow
269     [self queueMessage:OpenVimWindowMsgID data:nil];
270     return YES;
273 - (void)clearAll
275     int type = ClearAllDrawType;
277     // Any draw commands in queue are effectively obsolete since this clearAll
278     // will negate any effect they have, therefore we may as well clear the
279     // draw queue.
280     [drawData setLength:0];
282     [drawData appendBytes:&type length:sizeof(int)];
285 - (void)clearBlockFromRow:(int)row1 column:(int)col1
286                     toRow:(int)row2 column:(int)col2
288     int type = ClearBlockDrawType;
290     [drawData appendBytes:&type length:sizeof(int)];
292     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
293     [drawData appendBytes:&row1 length:sizeof(int)];
294     [drawData appendBytes:&col1 length:sizeof(int)];
295     [drawData appendBytes:&row2 length:sizeof(int)];
296     [drawData appendBytes:&col2 length:sizeof(int)];
299 - (void)deleteLinesFromRow:(int)row count:(int)count
300               scrollBottom:(int)bottom left:(int)left right:(int)right
302     int type = DeleteLinesDrawType;
304     [drawData appendBytes:&type length:sizeof(int)];
306     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
307     [drawData appendBytes:&row length:sizeof(int)];
308     [drawData appendBytes:&count length:sizeof(int)];
309     [drawData appendBytes:&bottom length:sizeof(int)];
310     [drawData appendBytes:&left length:sizeof(int)];
311     [drawData appendBytes:&right length:sizeof(int)];
314 - (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
315                 flags:(int)flags
317     if (len <= 0) return;
319     int type = ReplaceStringDrawType;
321     [drawData appendBytes:&type length:sizeof(int)];
323     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
324     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
325     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
326     [drawData appendBytes:&row length:sizeof(int)];
327     [drawData appendBytes:&col length:sizeof(int)];
328     [drawData appendBytes:&flags length:sizeof(int)];
329     [drawData appendBytes:&len length:sizeof(int)];
330     [drawData appendBytes:s length:len];
333 - (void)insertLinesFromRow:(int)row count:(int)count
334               scrollBottom:(int)bottom left:(int)left right:(int)right
336     int type = InsertLinesDrawType;
338     [drawData appendBytes:&type length:sizeof(int)];
340     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
341     [drawData appendBytes:&row length:sizeof(int)];
342     [drawData appendBytes:&count length:sizeof(int)];
343     [drawData appendBytes:&bottom length:sizeof(int)];
344     [drawData appendBytes:&left length:sizeof(int)];
345     [drawData appendBytes:&right length:sizeof(int)];
348 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
349                fraction:(int)percent color:(int)color
351     int type = DrawCursorDrawType;
352     unsigned uc = MM_COLOR(color);
354     [drawData appendBytes:&type length:sizeof(int)];
356     [drawData appendBytes:&uc length:sizeof(unsigned)];
357     [drawData appendBytes:&row length:sizeof(int)];
358     [drawData appendBytes:&col length:sizeof(int)];
359     [drawData appendBytes:&shape length:sizeof(int)];
360     [drawData appendBytes:&percent length:sizeof(int)];
363 - (void)flushQueue:(BOOL)force
365     // NOTE! This method gets called a lot; if we were to flush every time it
366     // was called MacVim would feel unresponsive.  So there is a time out which
367     // ensures that the queue isn't flushed too often.
368     if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
369             < MMFlushTimeoutInterval)
370         return;
372     if ([drawData length] > 0) {
373         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
374         [drawData setLength:0];
375     }
377     if ([queue count] > 0) {
378         @try {
379             [frontendProxy processCommandQueue:queue];
380         }
381         @catch (NSException *e) {
382             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
383         }
385         [queue removeAllObjects];
387         [lastFlushDate release];
388         lastFlushDate = [[NSDate date] retain];
389     }
392 - (BOOL)waitForInput:(int)milliseconds
394     NSDate *date = milliseconds > 0 ?
395             [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
396             [NSDate distantFuture];
398     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
400     // I know of no way to figure out if the run loop exited because input was
401     // found or because of a time out, so I need to manually indicate when
402     // input was received in processInput:data: and then reset it every time
403     // here.
404     BOOL yn = inputReceived;
405     inputReceived = NO;
407     return yn;
410 - (void)exit
412 #ifdef MAC_CLIENTSERVER
413     // The default connection is used for the client/server code.
414     [[NSConnection defaultConnection] setRootObject:nil];
415     [[NSConnection defaultConnection] invalidate];
416 #endif
418     // By invalidating the NSConnection the MMWindowController immediately
419     // finds out that the connection is down and as a result
420     // [MMWindowController connectionDidDie:] is invoked.
421     //NSLog(@"%@ %s", [self className], _cmd);
422     [[NSNotificationCenter defaultCenter] removeObserver:self];
423     [connection invalidate];
425     if (fontContainerRef) {
426         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
427         fontContainerRef = 0;
428     }
432 - (void)selectTab:(int)index
434     //NSLog(@"%s%d", _cmd, index);
436     index -= 1;
437     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
438     [self queueMessage:SelectTabMsgID data:data];
441 - (void)updateTabBar
443     //NSLog(@"%s", _cmd);
445     NSMutableData *data = [NSMutableData data];
447     int idx = tabpage_index(curtab) - 1;
448     [data appendBytes:&idx length:sizeof(int)];
450     tabpage_T *tp;
451     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
452         // This function puts the label of the tab in the global 'NameBuff'.
453         get_tabline_label(tp, FALSE);
454         char_u *s = NameBuff;
455         int len = STRLEN(s);
456         if (len <= 0) continue;
458 #if MM_ENABLE_CONV
459         s = CONVERT_TO_UTF8(s);
460 #endif
462         // Count the number of windows in the tabpage.
463         //win_T *wp = tp->tp_firstwin;
464         //int wincount;
465         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
467         //[data appendBytes:&wincount length:sizeof(int)];
468         [data appendBytes:&len length:sizeof(int)];
469         [data appendBytes:s length:len];
471 #if MM_ENABLE_CONV
472         CONVERT_TO_UTF8_FREE(s);
473 #endif
474     }
476     [self queueMessage:UpdateTabBarMsgID data:data];
479 - (BOOL)tabBarVisible
481     return tabBarVisible;
484 - (void)showTabBar:(BOOL)enable
486     tabBarVisible = enable;
488     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
489     [self queueMessage:msgid data:nil];
492 - (void)setRows:(int)rows columns:(int)cols
494     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
496     int dim[] = { rows, cols };
497     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
499     [self queueMessage:SetTextDimensionsMsgID data:data];
502 - (void)setWindowTitle:(char *)title
504     NSMutableData *data = [NSMutableData data];
505     int len = strlen(title);
506     if (len <= 0) return;
508     [data appendBytes:&len length:sizeof(int)];
509     [data appendBytes:title length:len];
511     [self queueMessage:SetWindowTitleMsgID data:data];
514 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
515                             saving:(int)saving
517     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
518     //        saving);
520     char_u *s = NULL;
521     NSString *ds = dir ? [NSString stringWithUTF8String:dir] : nil;
522     NSString *ts = title ? [NSString stringWithUTF8String:title] : nil;
523     @try {
524         [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
526         // Wait until a reply is sent from MMVimController.
527         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
528                                  beforeDate:[NSDate distantFuture]];
530         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
531             char_u *ret = (char_u*)[dialogReturn UTF8String];
532 #if MM_ENABLE_CONV
533             ret = CONVERT_FROM_UTF8(ret);
534 #endif
535             s = vim_strsave(ret);
536 #if MM_ENABLE_CONV
537             CONVERT_FROM_UTF8_FREE(ret);
538 #endif
539         }
541         [dialogReturn release];  dialogReturn = nil;
542     }
543     @catch (NSException *e) {
544         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
545     }
547     return (char *)s;
550 - (oneway void)setDialogReturn:(in bycopy id)obj
552     // NOTE: This is called by
553     //   - [MMVimController panelDidEnd:::], and
554     //   - [MMVimController alertDidEnd:::],
555     // to indicate that a save/open panel or alert has finished.
557     if (obj != dialogReturn) {
558         [dialogReturn release];
559         dialogReturn = [obj retain];
560     }
563 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
564                      buttons:(char *)btns textField:(char *)txtfield
566     int retval = 0;
567     NSString *message = nil, *text = nil, *textFieldString = nil;
568     NSArray *buttons = nil;
569     int style = NSInformationalAlertStyle;
571     if (VIM_WARNING == type) style = NSWarningAlertStyle;
572     else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
574     if (btns) {
575         NSString *btnString = [NSString stringWithUTF8String:btns];
576         buttons = [btnString componentsSeparatedByString:@"\n"];
577     }
578     if (title)
579         message = [NSString stringWithUTF8String:title];
580     if (msg) {
581         text = [NSString stringWithUTF8String:msg];
582         if (!message) {
583             // HACK! If there is a '\n\n' or '\n' sequence in the message, then
584             // make the part up to there into the title.  We only do this
585             // because Vim has lots of dialogs without a title and they look
586             // ugly that way.
587             // TODO: Fix the actual dialog texts.
588             NSRange eolRange = [text rangeOfString:@"\n\n"];
589             if (NSNotFound == eolRange.location)
590                 eolRange = [text rangeOfString:@"\n"];
591             if (NSNotFound != eolRange.location) {
592                 message = [text substringToIndex:eolRange.location];
593                 text = [text substringFromIndex:NSMaxRange(eolRange)];
594             }
595         }
596     }
597     if (txtfield)
598         textFieldString = [NSString stringWithUTF8String:txtfield];
600     @try {
601         [frontendProxy presentDialogWithStyle:style message:message
602                               informativeText:text buttonTitles:buttons
603                               textFieldString:textFieldString];
605         // Wait until a reply is sent from MMVimController.
606         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
607                                  beforeDate:[NSDate distantFuture]];
609         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
610                 && [dialogReturn count]) {
611             retval = [[dialogReturn objectAtIndex:0] intValue];
612             if (txtfield && [dialogReturn count] > 1) {
613                 NSString *retString = [dialogReturn objectAtIndex:1];
614                 char_u *ret = (char_u*)[retString UTF8String];
615 #if MM_ENABLE_CONV
616                 ret = CONVERT_FROM_UTF8(ret);
617 #endif
618                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
619 #if MM_ENABLE_CONV
620                 CONVERT_FROM_UTF8_FREE(ret);
621 #endif
622             }
623         }
625         [dialogReturn release]; dialogReturn = nil;
626     }
627     @catch (NSException *e) {
628         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
629     }
631     return retval;
634 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
635                atIndex:(int)index
637     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
638     //        name, index);
640     int namelen = name ? strlen(name) : 0;
641     NSMutableData *data = [NSMutableData data];
643     [data appendBytes:&tag length:sizeof(int)];
644     [data appendBytes:&parentTag length:sizeof(int)];
645     [data appendBytes:&namelen length:sizeof(int)];
646     if (namelen > 0) [data appendBytes:name length:namelen];
647     [data appendBytes:&index length:sizeof(int)];
649     [self queueMessage:AddMenuMsgID data:data];
652 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
653                        tip:(char *)tip icon:(char *)icon
654              keyEquivalent:(int)key modifiers:(int)mods
655                     action:(NSString *)action atIndex:(int)index
657     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
658     //        parentTag, name, tip, index);
660     int namelen = name ? strlen(name) : 0;
661     int tiplen = tip ? strlen(tip) : 0;
662     int iconlen = icon ? strlen(icon) : 0;
663     int eventFlags = vimModMaskToEventModifierFlags(mods);
664     int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
665     NSMutableData *data = [NSMutableData data];
667     key = specialKeyToNSKey(key);
669     [data appendBytes:&tag length:sizeof(int)];
670     [data appendBytes:&parentTag length:sizeof(int)];
671     [data appendBytes:&namelen length:sizeof(int)];
672     if (namelen > 0) [data appendBytes:name length:namelen];
673     [data appendBytes:&tiplen length:sizeof(int)];
674     if (tiplen > 0) [data appendBytes:tip length:tiplen];
675     [data appendBytes:&iconlen length:sizeof(int)];
676     if (iconlen > 0) [data appendBytes:icon length:iconlen];
677     [data appendBytes:&actionlen length:sizeof(int)];
678     if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
679     [data appendBytes:&index length:sizeof(int)];
680     [data appendBytes:&key length:sizeof(int)];
681     [data appendBytes:&eventFlags length:sizeof(int)];
683     [self queueMessage:AddMenuItemMsgID data:data];
686 - (void)removeMenuItemWithTag:(int)tag
688     NSMutableData *data = [NSMutableData data];
689     [data appendBytes:&tag length:sizeof(int)];
691     [self queueMessage:RemoveMenuItemMsgID data:data];
694 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
696     NSMutableData *data = [NSMutableData data];
698     [data appendBytes:&tag length:sizeof(int)];
699     [data appendBytes:&enabled length:sizeof(int)];
701     [self queueMessage:EnableMenuItemMsgID data:data];
704 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
706     int len = strlen(name);
707     int row = -1, col = -1;
709     if (len <= 0) return;
711     if (!mouse && curwin) {
712         row = curwin->w_wrow;
713         col = curwin->w_wcol;
714     }
716     NSMutableData *data = [NSMutableData data];
718     [data appendBytes:&row length:sizeof(int)];
719     [data appendBytes:&col length:sizeof(int)];
720     [data appendBytes:&len length:sizeof(int)];
721     [data appendBytes:name length:len];
723     [self queueMessage:ShowPopupMenuMsgID data:data];
726 - (void)showToolbar:(int)enable flags:(int)flags
728     NSMutableData *data = [NSMutableData data];
730     [data appendBytes:&enable length:sizeof(int)];
731     [data appendBytes:&flags length:sizeof(int)];
733     [self queueMessage:ShowToolbarMsgID data:data];
736 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
738     NSMutableData *data = [NSMutableData data];
740     [data appendBytes:&ident length:sizeof(long)];
741     [data appendBytes:&type length:sizeof(int)];
743     [self queueMessage:CreateScrollbarMsgID data:data];
746 - (void)destroyScrollbarWithIdentifier:(long)ident
748     NSMutableData *data = [NSMutableData data];
749     [data appendBytes:&ident length:sizeof(long)];
751     [self queueMessage:DestroyScrollbarMsgID data:data];
754 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
756     NSMutableData *data = [NSMutableData data];
758     [data appendBytes:&ident length:sizeof(long)];
759     [data appendBytes:&visible length:sizeof(int)];
761     [self queueMessage:ShowScrollbarMsgID data:data];
764 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
766     NSMutableData *data = [NSMutableData data];
768     [data appendBytes:&ident length:sizeof(long)];
769     [data appendBytes:&pos length:sizeof(int)];
770     [data appendBytes:&len length:sizeof(int)];
772     [self queueMessage:SetScrollbarPositionMsgID data:data];
775 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
776                     identifier:(long)ident
778     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
779     float prop = (float)size/(max+1);
780     if (fval < 0) fval = 0;
781     else if (fval > 1.0f) fval = 1.0f;
782     if (prop < 0) prop = 0;
783     else if (prop > 1.0f) prop = 1.0f;
785     NSMutableData *data = [NSMutableData data];
787     [data appendBytes:&ident length:sizeof(long)];
788     [data appendBytes:&fval length:sizeof(float)];
789     [data appendBytes:&prop length:sizeof(float)];
791     [self queueMessage:SetScrollbarThumbMsgID data:data];
794 - (BOOL)setFontWithName:(char *)name
796     NSString *fontName = MMDefaultFontName;
797     float size = MMDefaultFontSize;
798     BOOL parseFailed = NO;
800     if (name) {
801         fontName = [NSString stringWithUTF8String:name];
803         if ([fontName isEqual:@"*"]) {
804             // :set gfn=* shows the font panel.
805             do_cmdline_cmd((char_u*)":action orderFrontFontPanel:");
806             return NO;
807         }
809         NSArray *components = [fontName componentsSeparatedByString:@":"];
810         if ([components count] == 2) {
811             NSString *sizeString = [components lastObject];
812             if ([sizeString length] > 0
813                     && [sizeString characterAtIndex:0] == 'h') {
814                 sizeString = [sizeString substringFromIndex:1];
815                 if ([sizeString length] > 0) {
816                     size = [sizeString floatValue];
817                     fontName = [components objectAtIndex:0];
818                 }
819             } else {
820                 parseFailed = YES;
821             }
822         } else if ([components count] > 2) {
823             parseFailed = YES;
824         }
825     }
827     if (!parseFailed && [fontName length] > 0) {
828         if (size < 6 || size > 100) {
829             // Font size 0.0 tells NSFont to use the 'user default size'.
830             size = 0.0f;
831         }
833         NSFont *font = [NSFont fontWithName:fontName size:size];
835         if (!font && MMDefaultFontName == fontName) {
836             // If for some reason the MacVim default font is not in the app
837             // bundle, then fall back on the system default font.
838             size = 0;
839             font = [NSFont userFixedPitchFontOfSize:size];
840             fontName = [font displayName];
841         }
843         if (font) {
844             //NSLog(@"Setting font '%@' of size %.2f", fontName, size);
845             int len = [fontName
846                     lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
847             if (len > 0) {
848                 NSMutableData *data = [NSMutableData data];
850                 [data appendBytes:&size length:sizeof(float)];
851                 [data appendBytes:&len length:sizeof(int)];
852                 [data appendBytes:[fontName UTF8String] length:len];
854                 [self queueMessage:SetFontMsgID data:data];
855                 return YES;
856             }
857         }
858     }
860     //NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f",
861     //        fontName, size);
862     return NO;
865 - (void)executeActionWithName:(NSString *)name
867     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
869     if (len > 0) {
870         NSMutableData *data = [NSMutableData data];
872         [data appendBytes:&len length:sizeof(int)];
873         [data appendBytes:[name UTF8String] length:len];
875         [self queueMessage:ExecuteActionMsgID data:data];
876     }
879 - (void)setMouseShape:(int)shape
881     NSMutableData *data = [NSMutableData data];
882     [data appendBytes:&shape length:sizeof(int)];
883     [self queueMessage:SetMouseShapeMsgID data:data];
886 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
888     // Vim specifies times in milliseconds, whereas Cocoa wants them in
889     // seconds.
890     blinkWaitInterval = .001f*wait;
891     blinkOnInterval = .001f*on;
892     blinkOffInterval = .001f*off;
895 - (void)startBlink
897     if (blinkTimer) {
898         [blinkTimer invalidate];
899         [blinkTimer release];
900         blinkTimer = nil;
901     }
903     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
904             && gui.in_focus) {
905         blinkState = MMBlinkStateOn;
906         blinkTimer =
907             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
908                                               target:self
909                                             selector:@selector(blinkTimerFired:)
910                                             userInfo:nil repeats:NO] retain];
911         gui_update_cursor(TRUE, FALSE);
912         [self flushQueue:YES];
913     }
916 - (void)stopBlink
918     if (MMBlinkStateOff == blinkState) {
919         gui_update_cursor(TRUE, FALSE);
920         [self flushQueue:YES];
921     }
923     blinkState = MMBlinkStateNone;
926 - (void)adjustLinespace:(int)linespace
928     NSMutableData *data = [NSMutableData data];
929     [data appendBytes:&linespace length:sizeof(int)];
930     [self queueMessage:AdjustLinespaceMsgID data:data];
933 - (void)activate
935     [self queueMessage:ActivateMsgID data:nil];
938 - (int)lookupColorWithKey:(NSString *)key
940     if (!(key && [key length] > 0))
941         return INVALCOLOR;
943     NSString *stripKey = [[[[key lowercaseString]
944         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
945             componentsSeparatedByString:@" "]
946                componentsJoinedByString:@""];
948     if (stripKey && [stripKey length] > 0) {
949         // First of all try to lookup key in the color dictionary; note that
950         // all keys in this dictionary are lowercase with no whitespace.
951         id obj = [colorDict objectForKey:stripKey];
952         if (obj) return [obj intValue];
954         // The key was not in the dictionary; is it perhaps of the form
955         // #rrggbb?
956         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
957             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
958             [scanner setScanLocation:1];
959             unsigned hex = 0;
960             if ([scanner scanHexInt:&hex]) {
961                 return (int)hex;
962             }
963         }
965         // As a last resort, check if it is one of the system defined colors.
966         // The keys in this dictionary are also lowercase with no whitespace.
967         obj = [sysColorDict objectForKey:stripKey];
968         if (obj) {
969             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
970             if (col) {
971                 float r, g, b, a;
972                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
973                 [col getRed:&r green:&g blue:&b alpha:&a];
974                 return (((int)(r*255+.5f) & 0xff) << 16)
975                      + (((int)(g*255+.5f) & 0xff) << 8)
976                      +  ((int)(b*255+.5f) & 0xff);
977             }
978         }
979     }
981     NSLog(@"WARNING: No color with key %@ found.", stripKey);
982     return INVALCOLOR;
985 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
987     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
988     id obj;
990     while ((obj = [e nextObject])) {
991         if ([value isEqual:obj])
992             return YES;
993     }
995     return NO;
998 - (void)enterFullscreen
1000     [self queueMessage:EnterFullscreenMsgID data:nil];
1003 - (void)leaveFullscreen
1005     [self queueMessage:LeaveFullscreenMsgID data:nil];
1008 - (oneway void)processInput:(int)msgid data:(in NSData *)data
1010     // NOTE: This method might get called whenever the run loop is tended to.
1011     // Thus it might get called whilst input is being processed.  Normally this
1012     // is not a problem, but if it gets called often then it might become
1013     // dangerous.  E.g. say a message causes the screen to be redrawn and then
1014     // another message is received causing another simultaneous screen redraw;
1015     // this is not good.  To deal with this problem at the moment, we simply
1016     // drop messages that are received while other input is being processed.
1017     if (inProcessInput) {
1018 #if MM_USE_INPUT_QUEUE
1019         [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1020         [inputQueue addObject:data];
1021 #else
1022         // Just drop the input
1023         //NSLog(@"WARNING: Dropping input in %s", _cmd);
1024 #endif
1025     } else {
1026         [self processInputBegin];
1027         [self handleMessage:msgid data:data];
1028         [self processInputEnd];
1029     }
1032 - (oneway void)processInputAndData:(in NSArray *)messages
1034     // NOTE: See comment in processInput:data:.
1035     unsigned i, count = [messages count];
1036     if (count % 2) {
1037         NSLog(@"WARNING: [messages count] is odd in %s", _cmd);
1038         return;
1039     }
1041     if (inProcessInput) {
1042 #if MM_USE_INPUT_QUEUE
1043         [inputQueue addObjectsFromArray:messages];
1044 #else
1045         // Just drop the input
1046         //NSLog(@"WARNING: Dropping input in %s", _cmd);
1047 #endif
1048     } else {
1049         [self processInputBegin];
1051         for (i = 0; i < count; i += 2) {
1052             int msgid = [[messages objectAtIndex:i] intValue];
1053             id data = [messages objectAtIndex:i+1];
1054             if ([data isEqual:[NSNull null]])
1055                 data = nil;
1057             [self handleMessage:msgid data:data];
1058         }
1060         [self processInputEnd];
1061     }
1064 - (BOOL)checkForModifiedBuffers
1066     buf_T *buf;
1067     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
1068         if (bufIsChanged(buf)) {
1069             return YES;
1070         }
1071     }
1073     return NO;
1076 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1078     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1079         // If there is no pasteboard, return YES to indicate that there is text
1080         // to copy.
1081         if (!pboard)
1082             return YES;
1084         clip_copy_selection();
1086         // Get the text to put on the pasteboard.
1087         long_u llen = 0; char_u *str = 0;
1088         int type = clip_convert_selection(&str, &llen, &clip_star);
1089         if (type < 0)
1090             return NO;
1091         
1092         // TODO: Avoid overflow.
1093         int len = (int)llen;
1094 #if MM_ENABLE_CONV
1095         if (output_conv.vc_type != CONV_NONE) {
1096             char_u *conv_str = string_convert(&output_conv, str, &len);
1097             if (conv_str) {
1098                 vim_free(str);
1099                 str = conv_str;
1100             }
1101         }
1102 #endif
1104         NSString *string = [[NSString alloc]
1105             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1107         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1108         [pboard declareTypes:types owner:nil];
1109         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1110     
1111         [string release];
1112         vim_free(str);
1114         return ok;
1115     }
1117     return NO;
1120 - (oneway void)addReply:(in bycopy NSString *)reply
1121                  server:(in byref id <MMVimServerProtocol>)server
1123     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1125     // Replies might come at any time and in any order so we keep them in an
1126     // array inside a dictionary with the send port used as key.
1128     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1129     // HACK! Assume connection uses mach ports.
1130     int port = [(NSMachPort*)[conn sendPort] machPort];
1131     NSNumber *key = [NSNumber numberWithInt:port];
1133     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1134     if (!replies) {
1135         replies = [NSMutableArray array];
1136         [serverReplyDict setObject:replies forKey:key];
1137     }
1139     [replies addObject:reply];
1142 - (void)addInput:(in bycopy NSString *)input
1143                  client:(in byref id <MMVimClientProtocol>)client
1145     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1147     char_u *s = (char_u*)[input UTF8String];
1149 #if MM_ENABLE_CONV
1150     s = CONVERT_FROM_UTF8(s);
1151 #endif
1153     server_to_input_buf(s);
1155 #if MM_ENABLE_CONV
1156     CONVERT_FROM_UTF8_FREE(s);
1157 #endif
1159     [self addClient:(id)client];
1161     inputReceived = YES;
1164 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1165                  client:(in byref id <MMVimClientProtocol>)client
1167     //NSLog(@"evaluateExpression:%@ client:%@", expr, (id)client);
1169     NSString *eval = nil;
1170     char_u *s = (char_u*)[expr UTF8String];
1172 #if MM_ENABLE_CONV
1173     s = CONVERT_FROM_UTF8(s);
1174 #endif
1176     char_u *res = eval_client_expr_to_string(s);
1178 #if MM_ENABLE_CONV
1179     CONVERT_FROM_UTF8_FREE(s);
1180 #endif
1182     if (res != NULL) {
1183         s = res;
1184 #if MM_ENABLE_CONV
1185         s = CONVERT_TO_UTF8(s);
1186 #endif
1187         eval = [NSString stringWithUTF8String:(char*)s];
1188 #if MM_ENABLE_CONV
1189         CONVERT_TO_UTF8_FREE(s);
1190 #endif
1191         vim_free(res);
1192     }
1194     [self addClient:(id)client];
1196     return eval;
1199 - (void)registerServerWithName:(NSString *)name
1201     NSString *svrName = name;
1202     NSConnection *svrConn = [NSConnection defaultConnection];
1203     unsigned i;
1205     for (i = 0; i < MMServerMax; ++i) {
1206         NSString *connName = [self connectionNameFromServerName:svrName];
1208         if ([svrConn registerName:connName]) {
1209             //NSLog(@"Registered server with name: %@", svrName);
1211             // TODO: Set request/reply time-outs to something else?
1212             //
1213             // Don't wait for requests (time-out means that the message is
1214             // dropped).
1215             [svrConn setRequestTimeout:0];
1216             //[svrConn setReplyTimeout:MMReplyTimeout];
1217             [svrConn setRootObject:self];
1219             char_u *s = (char_u*)[svrName UTF8String];
1220 #if MM_ENABLE_CONV
1221             s = CONVERT_FROM_UTF8(s);
1222 #endif
1223             // NOTE: 'serverName' is a global variable
1224             serverName = vim_strsave(s);
1225 #if MM_ENABLE_CONV
1226             CONVERT_FROM_UTF8_FREE(s);
1227 #endif
1228 #ifdef FEAT_EVAL
1229             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1230 #endif
1231 #ifdef FEAT_TITLE
1232             need_maketitle = TRUE;
1233 #endif
1234             [self queueMessage:SetServerNameMsgID data:
1235                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1236             break;
1237         }
1239         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1240     }
1243 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1244                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1245               silent:(BOOL)silent
1247     // NOTE: If 'name' equals 'serverName' then the request is local (client
1248     // and server are the same).  This case is not handled separately, so a
1249     // connection will be set up anyway (this simplifies the code).
1251     NSConnection *conn = [self connectionForServerName:name];
1252     if (!conn) {
1253         if (!silent) {
1254             char_u *s = (char_u*)[name UTF8String];
1255 #if MM_ENABLE_CONV
1256             s = CONVERT_FROM_UTF8(s);
1257 #endif
1258             EMSG2(_(e_noserver), s);
1259 #if MM_ENABLE_CONV
1260             CONVERT_FROM_UTF8_FREE(s);
1261 #endif
1262         }
1263         return NO;
1264     }
1266     if (port) {
1267         // HACK! Assume connection uses mach ports.
1268         *port = [(NSMachPort*)[conn sendPort] machPort];
1269     }
1271     id proxy = [conn rootProxy];
1272     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1274     @try {
1275         if (expr) {
1276             NSString *eval = [proxy evaluateExpression:string client:self];
1277             if (reply) {
1278                 if (eval) {
1279                     char_u *r = (char_u*)[eval UTF8String];
1280 #if MM_ENABLE_CONV
1281                     r = CONVERT_FROM_UTF8(r);
1282 #endif
1283                     *reply = vim_strsave(r);
1284 #if MM_ENABLE_CONV
1285                     CONVERT_FROM_UTF8_FREE(r);
1286 #endif
1287                 } else {
1288                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1289                 }
1290             }
1292             if (!eval)
1293                 return NO;
1294         } else {
1295             [proxy addInput:string client:self];
1296         }
1297     }
1298     @catch (NSException *e) {
1299         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1300         return NO;
1301     }
1303     return YES;
1306 - (NSArray *)serverList
1308     NSArray *list = nil;
1310     if ([self connection]) {
1311         id proxy = [connection rootProxy];
1312         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1314         @try {
1315             list = [proxy serverList];
1316         }
1317         @catch (NSException *e) {
1318             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1319         }
1320     } else {
1321         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1322     }
1324     return list;
1327 - (NSString *)peekForReplyOnPort:(int)port
1329     //NSLog(@"%s%d", _cmd, port);
1331     NSNumber *key = [NSNumber numberWithInt:port];
1332     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1333     if (replies && [replies count]) {
1334         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1335         //        [replies objectAtIndex:0]);
1336         return [replies objectAtIndex:0];
1337     }
1339     //NSLog(@"    No replies");
1340     return nil;
1343 - (NSString *)waitForReplyOnPort:(int)port
1345     //NSLog(@"%s%d", _cmd, port);
1346     
1347     NSConnection *conn = [self connectionForServerPort:port];
1348     if (!conn)
1349         return nil;
1351     NSNumber *key = [NSNumber numberWithInt:port];
1352     NSMutableArray *replies = nil;
1353     NSString *reply = nil;
1355     // Wait for reply as long as the connection to the server is valid (unless
1356     // user interrupts wait with Ctrl-C).
1357     while (!got_int && [conn isValid] &&
1358             !(replies = [serverReplyDict objectForKey:key])) {
1359         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1360                                  beforeDate:[NSDate distantFuture]];
1361     }
1363     if (replies) {
1364         if ([replies count] > 0) {
1365             reply = [[replies objectAtIndex:0] retain];
1366             //NSLog(@"    Got reply: %@", reply);
1367             [replies removeObjectAtIndex:0];
1368             [reply autorelease];
1369         }
1371         if ([replies count] == 0)
1372             [serverReplyDict removeObjectForKey:key];
1373     }
1375     return reply;
1378 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1380     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1381     if (client) {
1382         @try {
1383             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1384             [client addReply:reply server:self];
1385             return YES;
1386         }
1387         @catch (NSException *e) {
1388             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1389         }
1390     } else {
1391         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1392     }
1394     return NO;
1397 @end // MMBackend
1401 @implementation MMBackend (Private)
1403 - (void)handleMessage:(int)msgid data:(NSData *)data
1405     if (InsertTextMsgID == msgid) {
1406         if (!data) return;
1407         NSString *key = [[NSString alloc] initWithData:data
1408                                               encoding:NSUTF8StringEncoding];
1409         char_u *str = (char_u*)[key UTF8String];
1410         int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1412 #if MM_ENABLE_CONV
1413         char_u *conv_str = NULL;
1414         if (input_conv.vc_type != CONV_NONE) {
1415             conv_str = string_convert(&input_conv, str, &len);
1416             if (conv_str)
1417                 str = conv_str;
1418         }
1419 #endif
1421         for (i = 0; i < len; ++i) {
1422             add_to_input_buf(str+i, 1);
1423             if (CSI == str[i]) {
1424                 // NOTE: If the converted string contains the byte CSI, then it
1425                 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1426                 // won't work.
1427                 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1428                 add_to_input_buf(extra, 2);
1429             }
1430         }
1432 #if MM_ENABLE_CONV
1433         if (conv_str)
1434             vim_free(conv_str);
1435 #endif
1436         [key release];
1437     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1438         if (!data) return;
1439         const void *bytes = [data bytes];
1440         int mods = *((int*)bytes);  bytes += sizeof(int);
1441         int len = *((int*)bytes);  bytes += sizeof(int);
1442         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1443                                               encoding:NSUTF8StringEncoding];
1444         mods = eventModifierFlagsToVimModMask(mods);
1446         [self handleKeyDown:key modifiers:mods];
1448         [key release];
1449     } else if (SelectTabMsgID == msgid) {
1450         if (!data) return;
1451         const void *bytes = [data bytes];
1452         int idx = *((int*)bytes) + 1;
1453         //NSLog(@"Selecting tab %d", idx);
1454         send_tabline_event(idx);
1455     } else if (CloseTabMsgID == msgid) {
1456         if (!data) return;
1457         const void *bytes = [data bytes];
1458         int idx = *((int*)bytes) + 1;
1459         //NSLog(@"Closing tab %d", idx);
1460         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1461     } else if (AddNewTabMsgID == msgid) {
1462         //NSLog(@"Adding new tab");
1463         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1464     } else if (DraggedTabMsgID == msgid) {
1465         if (!data) return;
1466         const void *bytes = [data bytes];
1467         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1468         // based.
1469         int idx = *((int*)bytes);
1471         tabpage_move(idx);
1472     } else if (ScrollWheelMsgID == msgid) {
1473         if (!data) return;
1474         const void *bytes = [data bytes];
1476         int row = *((int*)bytes);  bytes += sizeof(int);
1477         int col = *((int*)bytes);  bytes += sizeof(int);
1478         int flags = *((int*)bytes);  bytes += sizeof(int);
1479         float dy = *((float*)bytes);  bytes += sizeof(float);
1481         int button = MOUSE_5;
1482         if (dy > 0) button = MOUSE_4;
1484         flags = eventModifierFlagsToVimMouseModMask(flags);
1486         gui_send_mouse_event(button, col, row, NO, flags);
1487     } else if (MouseDownMsgID == msgid) {
1488         if (!data) return;
1489         const void *bytes = [data bytes];
1491         int row = *((int*)bytes);  bytes += sizeof(int);
1492         int col = *((int*)bytes);  bytes += sizeof(int);
1493         int button = *((int*)bytes);  bytes += sizeof(int);
1494         int flags = *((int*)bytes);  bytes += sizeof(int);
1495         int count = *((int*)bytes);  bytes += sizeof(int);
1497         button = eventButtonNumberToVimMouseButton(button);
1498         flags = eventModifierFlagsToVimMouseModMask(flags);
1500         gui_send_mouse_event(button, col, row, count>1, flags);
1501     } else if (MouseUpMsgID == msgid) {
1502         if (!data) return;
1503         const void *bytes = [data bytes];
1505         int row = *((int*)bytes);  bytes += sizeof(int);
1506         int col = *((int*)bytes);  bytes += sizeof(int);
1507         int flags = *((int*)bytes);  bytes += sizeof(int);
1509         flags = eventModifierFlagsToVimMouseModMask(flags);
1511         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1512     } else if (MouseDraggedMsgID == msgid) {
1513         if (!data) return;
1514         const void *bytes = [data bytes];
1516         int row = *((int*)bytes);  bytes += sizeof(int);
1517         int col = *((int*)bytes);  bytes += sizeof(int);
1518         int flags = *((int*)bytes);  bytes += sizeof(int);
1520         flags = eventModifierFlagsToVimMouseModMask(flags);
1522         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1523     } else if (SetTextDimensionsMsgID == msgid) {
1524         if (!data) return;
1525         const void *bytes = [data bytes];
1526         int rows = *((int*)bytes);  bytes += sizeof(int);
1527         int cols = *((int*)bytes);  bytes += sizeof(int);
1529         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1530         // gui_resize_shell(), so we have to manually set the rows and columns
1531         // here.  (MacVim doesn't change the rows and columns to avoid
1532         // inconsistent states between Vim and MacVim.)
1533         [self setRows:rows columns:cols];
1535         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1536         gui_resize_shell(cols, rows);
1537     } else if (ExecuteMenuMsgID == msgid) {
1538         if (!data) return;
1539         const void *bytes = [data bytes];
1540         int tag = *((int*)bytes);  bytes += sizeof(int);
1542         vimmenu_T *menu = (vimmenu_T*)tag;
1543         // TODO!  Make sure 'menu' is a valid menu pointer!
1544         if (menu) {
1545             gui_menu_cb(menu);
1546         }
1547     } else if (ToggleToolbarMsgID == msgid) {
1548         char_u go[sizeof(GO_ALL)+2];
1549         char_u *p;
1550         int len;
1552         STRCPY(go, p_go);
1553         p = vim_strchr(go, GO_TOOLBAR);
1554         len = STRLEN(go);
1556         if (p != NULL) {
1557             char_u *end = go + len;
1558             while (p < end) {
1559                 p[0] = p[1];
1560                 ++p;
1561             }
1562         } else {
1563             go[len] = GO_TOOLBAR;
1564             go[len+1] = NUL;
1565         }
1567         set_option_value((char_u*)"guioptions", 0, go, 0);
1569         // Force screen redraw (does it have to be this complicated?).
1570         redraw_all_later(CLEAR);
1571         update_screen(NOT_VALID);
1572         setcursor();
1573         out_flush();
1574         gui_update_cursor(FALSE, FALSE);
1575         gui_mch_flush();
1576     } else if (ScrollbarEventMsgID == msgid) {
1577         if (!data) return;
1578         const void *bytes = [data bytes];
1579         long ident = *((long*)bytes);  bytes += sizeof(long);
1580         int hitPart = *((int*)bytes);  bytes += sizeof(int);
1581         float fval = *((float*)bytes);  bytes += sizeof(float);
1582         scrollbar_T *sb = gui_find_scrollbar(ident);
1584         if (sb) {
1585             scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1586             long value = sb_info->value;
1587             long size = sb_info->size;
1588             long max = sb_info->max;
1589             BOOL isStillDragging = NO;
1590             BOOL updateKnob = YES;
1592             switch (hitPart) {
1593             case NSScrollerDecrementPage:
1594                 value -= (size > 2 ? size - 2 : 1);
1595                 break;
1596             case NSScrollerIncrementPage:
1597                 value += (size > 2 ? size - 2 : 1);
1598                 break;
1599             case NSScrollerDecrementLine:
1600                 --value;
1601                 break;
1602             case NSScrollerIncrementLine:
1603                 ++value;
1604                 break;
1605             case NSScrollerKnob:
1606                 isStillDragging = YES;
1607                 // fall through ...
1608             case NSScrollerKnobSlot:
1609                 value = (long)(fval * (max - size + 1));
1610                 // fall through ...
1611             default:
1612                 updateKnob = NO;
1613                 break;
1614             }
1616             //NSLog(@"value %d -> %d", sb_info->value, value);
1617             gui_drag_scrollbar(sb, value, isStillDragging);
1619             if (updateKnob) {
1620                 // Dragging the knob or option+clicking automatically updates
1621                 // the knob position (on the actual NSScroller), so we only
1622                 // need to set the knob position in the other cases.
1623                 if (sb->wp) {
1624                     // Update both the left&right vertical scrollbars.
1625                     long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1626                     long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1627                     [self setScrollbarThumbValue:value size:size max:max
1628                                       identifier:identLeft];
1629                     [self setScrollbarThumbValue:value size:size max:max
1630                                       identifier:identRight];
1631                 } else {
1632                     // Update the horizontal scrollbar.
1633                     [self setScrollbarThumbValue:value size:size max:max
1634                                       identifier:ident];
1635                 }
1636             }
1637         }
1638     } else if (SetFontMsgID == msgid) {
1639         if (!data) return;
1640         const void *bytes = [data bytes];
1641         float pointSize = *((float*)bytes);  bytes += sizeof(float);
1642         //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1643         bytes += sizeof(unsigned);  // len not used
1645         NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1646         [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1647         char_u *s = (char_u*)[name UTF8String];
1649 #if MM_ENABLE_CONV
1650         s = CONVERT_FROM_UTF8(s);
1651 #endif
1653         set_option_value((char_u*)"guifont", 0, s, 0);
1655 #if MM_ENABLE_CONV
1656         CONVERT_FROM_UTF8_FREE(s);
1657 #endif
1659         // Force screen redraw (does it have to be this complicated?).
1660         redraw_all_later(CLEAR);
1661         update_screen(NOT_VALID);
1662         setcursor();
1663         out_flush();
1664         gui_update_cursor(FALSE, FALSE);
1665         gui_mch_flush();
1666     } else if (VimShouldCloseMsgID == msgid) {
1667         gui_shell_closed();
1668     } else if (DropFilesMsgID == msgid) {
1669 #ifdef FEAT_DND
1670         const void *bytes = [data bytes];
1671         const void *end = [data bytes] + [data length];
1672         int n = *((int*)bytes);  bytes += sizeof(int);
1674         if (State & CMDLINE) {
1675             // HACK!  If Vim is in command line mode then the files names
1676             // should be added to the command line, instead of opening the
1677             // files in tabs.  This is taken care of by gui_handle_drop().
1678             char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1679             if (fnames) {
1680                 int i = 0;
1681                 while (bytes < end && i < n) {
1682                     int len = *((int*)bytes);  bytes += sizeof(int);
1683                     char_u *s = (char_u*)bytes;
1684 #if MM_ENABLE_CONV
1685                     s = CONVERT_FROM_UTF8(s);
1686 #endif
1687                     fnames[i++] = vim_strsave(s);
1688 #if MM_ENABLE_CONV
1689                     CONVERT_FROM_UTF8_FREE(s);
1690 #endif
1691                     bytes += len;
1692                 }
1694                 // NOTE!  This function will free 'fnames'.
1695                 // HACK!  It is assumed that the 'x' and 'y' arguments are
1696                 // unused when in command line mode.
1697                 gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
1698             }
1699         } else {
1700             // HACK!  I'm not sure how to get Vim to open a list of files in
1701             // tabs, so instead I create a ':tab drop' command with all the
1702             // files to open and execute it.
1703             NSMutableString *cmd = (n > 1)
1704                     ? [NSMutableString stringWithString:@":tab drop"]
1705                     : [NSMutableString stringWithString:@":drop"];
1707             int i;
1708             for (i = 0; i < n && bytes < end; ++i) {
1709                 int len = *((int*)bytes);  bytes += sizeof(int);
1710                 NSMutableString *file =
1711                         [NSMutableString stringWithUTF8String:bytes];
1712                 [file replaceOccurrencesOfString:@" "
1713                                       withString:@"\\ "
1714                                          options:0
1715                                            range:NSMakeRange(0,[file length])];
1716                 bytes += len;
1718                 [cmd appendString:@" "];
1719                 [cmd appendString:file];
1720             }
1722             // By going to the last tabpage we ensure that the new tabs will
1723             // appear last (if this call is left out, the taborder becomes
1724             // messy).
1725             goto_tabpage(9999);
1727             char_u *s = (char_u*)[cmd UTF8String];
1728 #if MM_ENABLE_CONV
1729             s = CONVERT_FROM_UTF8(s);
1730 #endif
1731             do_cmdline_cmd(s);
1732 #if MM_ENABLE_CONV
1733             CONVERT_FROM_UTF8_FREE(s);
1734 #endif
1736             // Force screen redraw (does it have to be this complicated?).
1737             // (This code was taken from the end of gui_handle_drop().)
1738             update_screen(NOT_VALID);
1739             setcursor();
1740             out_flush();
1741             gui_update_cursor(FALSE, FALSE);
1742             gui_mch_flush();
1743         }
1744 #endif // FEAT_DND
1745     } else if (DropStringMsgID == msgid) {
1746 #ifdef FEAT_DND
1747         char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
1748         const void *bytes = [data bytes];
1749         int len = *((int*)bytes);  bytes += sizeof(int);
1750         NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
1752         // Replace unrecognized end-of-line sequences with \x0a (line feed).
1753         NSRange range = { 0, [string length] };
1754         unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
1755                                              withString:@"\x0a" options:0
1756                                                   range:range];
1757         if (0 == n) {
1758             n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
1759                                            options:0 range:range];
1760         }
1762         len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1763         char_u *s = (char_u*)[string UTF8String];
1764 #if MM_ENABLE_CONV
1765         if (input_conv.vc_type != CONV_NONE)
1766             s = string_convert(&input_conv, s, &len);
1767 #endif
1768         dnd_yank_drag_data(s, len);
1769 #if MM_ENABLE_CONV
1770         if (input_conv.vc_type != CONV_NONE)
1771             vim_free(s);
1772 #endif
1773         add_to_input_buf(dropkey, sizeof(dropkey));
1774 #endif // FEAT_DND
1775     } else if (GotFocusMsgID == msgid) {
1776         if (!gui.in_focus)
1777             [self focusChange:YES];
1778     } else if (LostFocusMsgID == msgid) {
1779         if (gui.in_focus)
1780             [self focusChange:NO];
1781     } else if (MouseMovedMsgID == msgid) {
1782         const void *bytes = [data bytes];
1783         int row = *((int*)bytes);  bytes += sizeof(int);
1784         int col = *((int*)bytes);  bytes += sizeof(int);
1786         gui_mouse_moved(col, row);
1787     } else if (SetMouseShapeMsgID == msgid) {
1788         const void *bytes = [data bytes];
1789         int shape = *((int*)bytes);  bytes += sizeof(int);
1790         update_mouseshape(shape);
1791     } else {
1792         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1793     }
1796 + (NSDictionary *)specialKeys
1798     static NSDictionary *specialKeys = nil;
1800     if (!specialKeys) {
1801         NSBundle *mainBundle = [NSBundle mainBundle];
1802         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1803                                               ofType:@"plist"];
1804         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1805     }
1807     return specialKeys;
1810 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1812     char_u special[3];
1813     char_u modChars[3];
1814     char_u *chars = (char_u*)[key UTF8String];
1815 #if MM_ENABLE_CONV
1816     char_u *conv_str = NULL;
1817 #endif
1818     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1820     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1821     // that new keys can easily be added.
1822     NSString *specialString = [[MMBackend specialKeys]
1823             objectForKey:key];
1824     if (specialString && [specialString length] > 1) {
1825         //NSLog(@"special key: %@", specialString);
1826         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1827                 [specialString characterAtIndex:1]);
1829         ikey = simplify_key(ikey, &mods);
1830         if (ikey == CSI)
1831             ikey = K_CSI;
1833         special[0] = CSI;
1834         special[1] = K_SECOND(ikey);
1835         special[2] = K_THIRD(ikey);
1837         chars = special;
1838         length = 3;
1839     } else if (1 == length && TAB == chars[0]) {
1840         // Tab is a trouble child:
1841         // - <Tab> is added to the input buffer as is
1842         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1843         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1844         //   to be converted to utf-8
1845         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1846         // - <C-Tab> is reserved by Mac OS X
1847         // - <D-Tab> is reserved by Mac OS X
1848         chars = special;
1849         special[0] = TAB;
1850         length = 1;
1852         if (mods & MOD_MASK_SHIFT) {
1853             mods &= ~MOD_MASK_SHIFT;
1854             special[0] = CSI;
1855             special[1] = K_SECOND(K_S_TAB);
1856             special[2] = K_THIRD(K_S_TAB);
1857             length = 3;
1858         } else if (mods & MOD_MASK_ALT) {
1859             int mtab = 0x80 | TAB;
1860             if (enc_utf8) {
1861                 // Convert to utf-8
1862                 special[0] = (mtab >> 6) + 0xc0;
1863                 special[1] = mtab & 0xbf;
1864                 length = 2;
1865             } else {
1866                 special[0] = mtab;
1867                 length = 1;
1868             }
1869             mods &= ~MOD_MASK_ALT;
1870         }
1871     } else if (length > 0) {
1872         unichar c = [key characterAtIndex:0];
1874         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1875         //        [key characterAtIndex:0], mods);
1877         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1878                 || (c == intr_char && intr_char != Ctrl_C))) {
1879             trash_input_buf();
1880             got_int = TRUE;
1881         }
1883         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1884         // cleared since they are already added to the key by the AppKit.
1885         // Unfortunately, the only way to deal with when to clear the modifiers
1886         // or not seems to be to have hard-wired rules like this.
1887         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1888                     || 0x9 == c || 0xd == c) ) {
1889             mods &= ~MOD_MASK_SHIFT;
1890             mods &= ~MOD_MASK_CTRL;
1891             //NSLog(@"clear shift ctrl");
1892         }
1894         // HACK!  All Option+key presses go via 'insert text' messages, except
1895         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1896         // not work to map to it.
1897         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1898             //NSLog(@"clear alt");
1899             mods &= ~MOD_MASK_ALT;
1900         }
1902 #if MM_ENABLE_CONV
1903         if (input_conv.vc_type != CONV_NONE) {
1904             conv_str = string_convert(&input_conv, chars, &length);
1905             if (conv_str)
1906                 chars = conv_str;
1907         }
1908 #endif
1909     }
1911     if (chars && length > 0) {
1912         if (mods) {
1913             //NSLog(@"adding mods: %d", mods);
1914             modChars[0] = CSI;
1915             modChars[1] = KS_MODIFIER;
1916             modChars[2] = mods;
1917             add_to_input_buf(modChars, 3);
1918         }
1920         //NSLog(@"add to input buf: 0x%x", chars[0]);
1921         // TODO: Check for CSI bytes?
1922         add_to_input_buf(chars, length);
1923     }
1925 #if MM_ENABLE_CONV
1926     if (conv_str)
1927         vim_free(conv_str);
1928 #endif
1931 - (void)queueMessage:(int)msgid data:(NSData *)data
1933     [queue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1934     if (data)
1935         [queue addObject:data];
1936     else
1937         [queue addObject:[NSData data]];
1940 - (void)connectionDidDie:(NSNotification *)notification
1942     // If the main connection to MacVim is lost this means that MacVim was
1943     // either quit (by the user chosing Quit on the MacVim menu), or it has
1944     // crashed.  In either case our only option is to quit now.
1945     // TODO: Write backup file?
1947     //NSLog(@"A Vim process lots its connection to MacVim; quitting.");
1948     getout(0);
1951 - (void)blinkTimerFired:(NSTimer *)timer
1953     NSTimeInterval timeInterval = 0;
1955     [blinkTimer release];
1956     blinkTimer = nil;
1958     if (MMBlinkStateOn == blinkState) {
1959         gui_undraw_cursor();
1960         blinkState = MMBlinkStateOff;
1961         timeInterval = blinkOffInterval;
1962     } else if (MMBlinkStateOff == blinkState) {
1963         gui_update_cursor(TRUE, FALSE);
1964         blinkState = MMBlinkStateOn;
1965         timeInterval = blinkOnInterval;
1966     }
1968     if (timeInterval > 0) {
1969         blinkTimer = 
1970             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1971                                             selector:@selector(blinkTimerFired:)
1972                                             userInfo:nil repeats:NO] retain];
1973         [self flushQueue:YES];
1974     }
1977 - (void)focusChange:(BOOL)on
1979     gui_focus_change(on);
1982 - (void)processInputBegin
1984     inProcessInput = YES;
1986     // Don't flush too soon or update speed will suffer.
1987     [lastFlushDate release];
1988     lastFlushDate = [[NSDate date] retain];
1991 - (void)processInputEnd
1993 #if MM_USE_INPUT_QUEUE
1994     int count = [inputQueue count];
1995     if (count % 2) {
1996         // TODO: This is troubling, but it is not hard to get Vim to end up
1997         // here.  Why does this happen?
1998         NSLog(@"WARNING: inputQueue has odd number of objects (%d)", count);
1999         [inputQueue removeAllObjects];
2000     } else if (count > 0) {
2001         // TODO: Dispatch these messages?  Maybe not; usually when the
2002         // 'inputQueue' is non-empty it means that a LOT of messages has been
2003         // sent simultaneously.  The only way this happens is when Vim is being
2004         // tormented, e.g. if the user holds down <D-`> to rapidly switch
2005         // windows.
2006         unsigned i;
2007         for (i = 0; i < count; i+=2) {
2008             int msgid = [[inputQueue objectAtIndex:i] intValue];
2009             NSLog(@"%s: Dropping message %s", _cmd, MessageStrings[msgid]);
2010         }
2012         [inputQueue removeAllObjects];
2013     }
2014 #endif
2016     inputReceived = YES;
2017     inProcessInput = NO;
2020 - (NSString *)connectionNameFromServerName:(NSString *)name
2022     NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2024     return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2025         lowercaseString];
2028 - (NSConnection *)connectionForServerName:(NSString *)name
2030     // TODO: Try 'name%d' if 'name' fails.
2031     NSString *connName = [self connectionNameFromServerName:name];
2032     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2034     if (!svrConn) {
2035         svrConn = [NSConnection connectionWithRegisteredName:connName
2036                                                            host:nil];
2037         // Try alternate server...
2038         if (!svrConn && alternateServerName) {
2039             //NSLog(@"  trying to connect to alternate server: %@",
2040             //        alternateServerName);
2041             connName = [self connectionNameFromServerName:alternateServerName];
2042             svrConn = [NSConnection connectionWithRegisteredName:connName
2043                                                             host:nil];
2044         }
2046         // Try looking for alternate servers...
2047         if (!svrConn) {
2048             //NSLog(@"  looking for alternate servers...");
2049             NSString *alt = [self alternateServerNameForName:name];
2050             if (alt != alternateServerName) {
2051                 //NSLog(@"  found alternate server: %@", string);
2052                 [alternateServerName release];
2053                 alternateServerName = [alt copy];
2054             }
2055         }
2057         // Try alternate server again...
2058         if (!svrConn && alternateServerName) {
2059             //NSLog(@"  trying to connect to alternate server: %@",
2060             //        alternateServerName);
2061             connName = [self connectionNameFromServerName:alternateServerName];
2062             svrConn = [NSConnection connectionWithRegisteredName:connName
2063                                                             host:nil];
2064         }
2066         if (svrConn) {
2067             [connectionNameDict setObject:svrConn forKey:connName];
2069             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2070             [[NSNotificationCenter defaultCenter] addObserver:self
2071                     selector:@selector(serverConnectionDidDie:)
2072                         name:NSConnectionDidDieNotification object:svrConn];
2073         }
2074     }
2076     return svrConn;
2079 - (NSConnection *)connectionForServerPort:(int)port
2081     NSConnection *conn;
2082     NSEnumerator *e = [connectionNameDict objectEnumerator];
2084     while ((conn = [e nextObject])) {
2085         // HACK! Assume connection uses mach ports.
2086         if (port == [(NSMachPort*)[conn sendPort] machPort])
2087             return conn;
2088     }
2090     return nil;
2093 - (void)serverConnectionDidDie:(NSNotification *)notification
2095     //NSLog(@"%s%@", _cmd, notification);
2097     NSConnection *svrConn = [notification object];
2099     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2100     [[NSNotificationCenter defaultCenter]
2101             removeObserver:self
2102                       name:NSConnectionDidDieNotification
2103                     object:svrConn];
2105     [connectionNameDict removeObjectsForKeys:
2106         [connectionNameDict allKeysForObject:svrConn]];
2108     // HACK! Assume connection uses mach ports.
2109     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2110     NSNumber *key = [NSNumber numberWithInt:port];
2112     [clientProxyDict removeObjectForKey:key];
2113     [serverReplyDict removeObjectForKey:key];
2116 - (void)addClient:(NSDistantObject *)client
2118     NSConnection *conn = [client connectionForProxy];
2119     // HACK! Assume connection uses mach ports.
2120     int port = [(NSMachPort*)[conn sendPort] machPort];
2121     NSNumber *key = [NSNumber numberWithInt:port];
2123     if (![clientProxyDict objectForKey:key]) {
2124         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2125         [clientProxyDict setObject:client forKey:key];
2126     }
2128     // NOTE: 'clientWindow' is a global variable which is used by <client>
2129     clientWindow = port;
2132 - (NSString *)alternateServerNameForName:(NSString *)name
2134     if (!(name && [name length] > 0))
2135         return nil;
2137     // Only look for alternates if 'name' doesn't end in a digit.
2138     unichar lastChar = [name characterAtIndex:[name length]-1];
2139     if (lastChar >= '0' && lastChar <= '9')
2140         return nil;
2142     // Look for alternates among all current servers.
2143     NSArray *list = [self serverList];
2144     if (!(list && [list count] > 0))
2145         return nil;
2147     // Filter out servers starting with 'name' and ending with a number. The
2148     // (?i) pattern ensures that the match is case insensitive.
2149     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2150     NSPredicate *pred = [NSPredicate predicateWithFormat:
2151             @"SELF MATCHES %@", pat];
2152     list = [list filteredArrayUsingPredicate:pred];
2153     if ([list count] > 0) {
2154         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2155         return [list objectAtIndex:0];
2156     }
2158     return nil;
2161 @end // MMBackend (Private)
2166 @implementation NSString (MMServerNameCompare)
2167 - (NSComparisonResult)serverNameCompare:(NSString *)string
2169     return [self compare:string
2170                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2172 @end
2177 static int eventModifierFlagsToVimModMask(int modifierFlags)
2179     int modMask = 0;
2181     if (modifierFlags & NSShiftKeyMask)
2182         modMask |= MOD_MASK_SHIFT;
2183     if (modifierFlags & NSControlKeyMask)
2184         modMask |= MOD_MASK_CTRL;
2185     if (modifierFlags & NSAlternateKeyMask)
2186         modMask |= MOD_MASK_ALT;
2187     if (modifierFlags & NSCommandKeyMask)
2188         modMask |= MOD_MASK_CMD;
2190     return modMask;
2193 static int vimModMaskToEventModifierFlags(int mods)
2195     int flags = 0;
2197     if (mods & MOD_MASK_SHIFT)
2198         flags |= NSShiftKeyMask;
2199     if (mods & MOD_MASK_CTRL)
2200         flags |= NSControlKeyMask;
2201     if (mods & MOD_MASK_ALT)
2202         flags |= NSAlternateKeyMask;
2203     if (mods & MOD_MASK_CMD)
2204         flags |= NSCommandKeyMask;
2206     return flags;
2209 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2211     int modMask = 0;
2213     if (modifierFlags & NSShiftKeyMask)
2214         modMask |= MOUSE_SHIFT;
2215     if (modifierFlags & NSControlKeyMask)
2216         modMask |= MOUSE_CTRL;
2217     if (modifierFlags & NSAlternateKeyMask)
2218         modMask |= MOUSE_ALT;
2220     return modMask;
2223 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2225     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE,
2226             MOUSE_X1, MOUSE_X2 };
2228     return mouseButton[buttonNumber < 5 ? buttonNumber : 0];
2231 static int specialKeyToNSKey(int key)
2233     if (!IS_SPECIAL(key))
2234         return key;
2236     static struct {
2237         int special;
2238         int nskey;
2239     } sp2ns[] = {
2240         { K_UP, NSUpArrowFunctionKey },
2241         { K_DOWN, NSDownArrowFunctionKey },
2242         { K_LEFT, NSLeftArrowFunctionKey },
2243         { K_RIGHT, NSRightArrowFunctionKey },
2244         { K_F1, NSF1FunctionKey },
2245         { K_F2, NSF2FunctionKey },
2246         { K_F3, NSF3FunctionKey },
2247         { K_F4, NSF4FunctionKey },
2248         { K_F5, NSF5FunctionKey },
2249         { K_F6, NSF6FunctionKey },
2250         { K_F7, NSF7FunctionKey },
2251         { K_F8, NSF8FunctionKey },
2252         { K_F9, NSF9FunctionKey },
2253         { K_F10, NSF10FunctionKey },
2254         { K_F11, NSF11FunctionKey },
2255         { K_F12, NSF12FunctionKey },
2256         { K_F13, NSF13FunctionKey },
2257         { K_F14, NSF14FunctionKey },
2258         { K_F15, NSF15FunctionKey },
2259         { K_F16, NSF16FunctionKey },
2260         { K_F17, NSF17FunctionKey },
2261         { K_F18, NSF18FunctionKey },
2262         { K_F19, NSF19FunctionKey },
2263         { K_F20, NSF20FunctionKey },
2264         { K_F21, NSF21FunctionKey },
2265         { K_F22, NSF22FunctionKey },
2266         { K_F23, NSF23FunctionKey },
2267         { K_F24, NSF24FunctionKey },
2268         { K_F25, NSF25FunctionKey },
2269         { K_F26, NSF26FunctionKey },
2270         { K_F27, NSF27FunctionKey },
2271         { K_F28, NSF28FunctionKey },
2272         { K_F29, NSF29FunctionKey },
2273         { K_F30, NSF30FunctionKey },
2274         { K_F31, NSF31FunctionKey },
2275         { K_F32, NSF32FunctionKey },
2276         { K_F33, NSF33FunctionKey },
2277         { K_F34, NSF34FunctionKey },
2278         { K_F35, NSF35FunctionKey },
2279         { K_DEL, NSBackspaceCharacter },
2280         { K_BS, NSDeleteCharacter },
2281         { K_HOME, NSHomeFunctionKey },
2282         { K_END, NSEndFunctionKey },
2283         { K_PAGEUP, NSPageUpFunctionKey },
2284         { K_PAGEDOWN, NSPageDownFunctionKey }
2285     };
2287     int i;
2288     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2289         if (sp2ns[i].special == key)
2290             return sp2ns[i].nskey;
2291     }
2293     return 0;