Changed DiffText and Constant highlight groups
[MacVim/jjgod.git] / src / MacVim / MMBackend.m
blob93c1e32c9fe95195efdbc1dc3b3ae17634ff386e
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
11 #import "MMBackend.h"
15 // NOTE: Colors in MMBackend are stored as unsigned ints on the form 0xaarrggbb
16 // whereas colors in Vim are int without the alpha component.  Also note that
17 // 'transp' is assumed to be a value between 0 and 100.
18 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
19 #define MM_COLOR_WITH_TRANSP(col,transp) \
20     ((unsigned)( ((col)&0xffffff) \
21         | ((((unsigned)((((100-(transp))*255)/100)+.5f))&0xff)<<24) ))
24 // This constant controls how often the command queue may be flushed.  If it is
25 // too small the app might feel unresponsive; if it is too large there might be
26 // long periods without the screen updating (e.g. when sourcing a large session
27 // file).  (The unit is seconds.)
28 static float MMFlushTimeoutInterval = 0.1f;
29 static int MMFlushQueueLenHint = 80*40;
31 static unsigned MMServerMax = 1000;
33 // NOTE: The default font is bundled with the application.
34 static NSString *MMDefaultFontName = @"DejaVu Sans Mono";
35 static float MMDefaultFontSize = 12.0f;
37 // TODO: Move to separate file.
38 static int eventModifierFlagsToVimModMask(int modifierFlags);
39 static int vimModMaskToEventModifierFlags(int mods);
40 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
41 static int eventButtonNumberToVimMouseButton(int buttonNumber);
42 static int specialKeyToNSKey(int key);
44 enum {
45     MMBlinkStateNone = 0,
46     MMBlinkStateOn,
47     MMBlinkStateOff
52 @interface NSString (MMServerNameCompare)
53 - (NSComparisonResult)serverNameCompare:(NSString *)string;
54 @end
58 @interface MMBackend (Private)
59 - (void)handleMessage:(int)msgid data:(NSData *)data;
60 + (NSDictionary *)specialKeys;
61 - (void)handleInsertText:(NSData *)data;
62 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
63 - (void)queueMessage:(int)msgid data:(NSData *)data;
64 - (void)connectionDidDie:(NSNotification *)notification;
65 - (void)blinkTimerFired:(NSTimer *)timer;
66 - (void)focusChange:(BOOL)on;
67 - (void)handleToggleToolbar;
68 - (void)handleScrollbarEvent:(NSData *)data;
69 - (void)handleSetFont:(NSData *)data;
70 - (void)handleDropFiles:(NSData *)data;
71 - (void)handleDropString:(NSData *)data;
72 @end
76 @interface MMBackend (ClientServer)
77 - (NSString *)connectionNameFromServerName:(NSString *)name;
78 - (NSConnection *)connectionForServerName:(NSString *)name;
79 - (NSConnection *)connectionForServerPort:(int)port;
80 - (void)serverConnectionDidDie:(NSNotification *)notification;
81 - (void)addClient:(NSDistantObject *)client;
82 - (NSString *)alternateServerNameForName:(NSString *)name;
83 @end
87 @implementation MMBackend
89 + (MMBackend *)sharedInstance
91     static MMBackend *singleton = nil;
92     return singleton ? singleton : (singleton = [MMBackend new]);
95 - (id)init
97     if ((self = [super init])) {
98         fontContainerRef = loadFonts();
100         queue = [[NSMutableArray alloc] init];
101         drawData = [[NSMutableData alloc] initWithCapacity:1024];
102         connectionNameDict = [[NSMutableDictionary alloc] init];
103         clientProxyDict = [[NSMutableDictionary alloc] init];
104         serverReplyDict = [[NSMutableDictionary alloc] init];
106         NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
107                                                          ofType:@"plist"];
108         if (path) {
109             colorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
110                 retain];
111         } else {
112             NSLog(@"WARNING: Could not locate Colors.plist.");
113         }
115         path = [[NSBundle mainBundle] pathForResource:@"SystemColors"
116                                                ofType:@"plist"];
117         if (path) {
118             sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
119                 retain];
120         } else {
121             NSLog(@"WARNING: Could not locate SystemColors.plist.");
122         }
123     }
125     return self;
128 - (void)dealloc
130     //NSLog(@"%@ %s", [self className], _cmd);
132     [[NSNotificationCenter defaultCenter] removeObserver:self];
134     [blinkTimer release];  blinkTimer = nil;
135     [alternateServerName release];  alternateServerName = nil;
136     [serverReplyDict release];  serverReplyDict = nil;
137     [clientProxyDict release];  clientProxyDict = nil;
138     [connectionNameDict release];  connectionNameDict = nil;
139     [queue release];  queue = nil;
140     [drawData release];  drawData = nil;
141     [frontendProxy release];  frontendProxy = nil;
142     [connection release];  connection = nil;
143     [sysColorDict release];  sysColorDict = nil;
144     [colorDict release];  colorDict = nil;
146     [super dealloc];
149 - (void)setBackgroundColor:(int)color
151     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
154 - (void)setForegroundColor:(int)color
156     foregroundColor = MM_COLOR(color);
159 - (void)setSpecialColor:(int)color
161     specialColor = MM_COLOR(color);
164 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
166     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
167     defaultForegroundColor = MM_COLOR(fg);
169     NSMutableData *data = [NSMutableData data];
171     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
172     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
174     [self queueMessage:SetDefaultColorsMsgID data:data];
177 - (NSConnection *)connection
179     if (!connection) {
180         // NOTE!  If the name of the connection changes here it must also be
181         // updated in MMAppController.m.
182         NSString *name = [NSString stringWithFormat:@"%@-connection",
183                [[NSBundle mainBundle] bundleIdentifier]];
185         connection = [NSConnection connectionWithRegisteredName:name host:nil];
186         [connection retain];
187     }
189     // NOTE: 'connection' may be nil here.
190     return connection;
193 - (BOOL)checkin
195     if (![self connection]) {
196         NSBundle *mainBundle = [NSBundle mainBundle];
197 #if 0
198         NSString *path = [mainBundle bundlePath];
199         if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
200             NSLog(@"WARNING: Failed to launch GUI with path %@", path);
201             return NO;
202         }
203 #else
204         // HACK!  It would be preferable to launch the GUI using NSWorkspace,
205         // however I have not managed to figure out how to pass arguments using
206         // NSWorkspace.
207         //
208         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
209         // that the GUI won't be activated (or raised) so there is a hack in
210         // MMWindowController which always raises the app when a new window is
211         // opened.
212         NSMutableArray *args = [NSMutableArray arrayWithObjects:
213             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
214         NSString *exeName = [[mainBundle infoDictionary]
215                 objectForKey:@"CFBundleExecutable"];
216         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
217         if (!path) {
218             NSLog(@"ERROR: Could not find MacVim executable in bundle");
219             return NO;
220         }
222         [NSTask launchedTaskWithLaunchPath:path arguments:args];
223 #endif
225         // HACK!  The NSWorkspaceDidLaunchApplicationNotification does not work
226         // for tasks like this, so poll the mach bootstrap server until it
227         // returns a valid connection.  Also set a time-out date so that we
228         // don't get stuck doing this forever.
229         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
230         while (!connection &&
231                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
232         {
233             [[NSRunLoop currentRunLoop]
234                     runMode:NSDefaultRunLoopMode
235                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
237             // NOTE: This call will set 'connection' as a side-effect.
238             [self connection];
239         }
241         if (!connection) {
242             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
243             return NO;
244         }
245     }
247     id proxy = [connection rootProxy];
248     [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
250     [[NSNotificationCenter defaultCenter] addObserver:self
251             selector:@selector(connectionDidDie:)
252                 name:NSConnectionDidDieNotification object:connection];
254     int pid = [[NSProcessInfo processInfo] processIdentifier];
256     @try {
257         frontendProxy = [proxy connectBackend:self pid:pid];
258     }
259     @catch (NSException *e) {
260         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
261     }
263     if (frontendProxy) {
264         [frontendProxy retain];
265         [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
266     }
268     return connection && frontendProxy;
271 - (BOOL)openVimWindow
273     [self queueMessage:OpenVimWindowMsgID data:nil];
274     return YES;
277 - (void)clearAll
279     int type = ClearAllDrawType;
281     // Any draw commands in queue are effectively obsolete since this clearAll
282     // will negate any effect they have, therefore we may as well clear the
283     // draw queue.
284     [drawData setLength:0];
286     [drawData appendBytes:&type length:sizeof(int)];
289 - (void)clearBlockFromRow:(int)row1 column:(int)col1
290                     toRow:(int)row2 column:(int)col2
292     int type = ClearBlockDrawType;
294     [drawData appendBytes:&type length:sizeof(int)];
296     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
297     [drawData appendBytes:&row1 length:sizeof(int)];
298     [drawData appendBytes:&col1 length:sizeof(int)];
299     [drawData appendBytes:&row2 length:sizeof(int)];
300     [drawData appendBytes:&col2 length:sizeof(int)];
303 - (void)deleteLinesFromRow:(int)row count:(int)count
304               scrollBottom:(int)bottom left:(int)left right:(int)right
306     int type = DeleteLinesDrawType;
308     [drawData appendBytes:&type length:sizeof(int)];
310     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
311     [drawData appendBytes:&row length:sizeof(int)];
312     [drawData appendBytes:&count length:sizeof(int)];
313     [drawData appendBytes:&bottom length:sizeof(int)];
314     [drawData appendBytes:&left length:sizeof(int)];
315     [drawData appendBytes:&right length:sizeof(int)];
318 - (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
319                 flags:(int)flags
321     if (len <= 0) return;
323     int type = ReplaceStringDrawType;
325     [drawData appendBytes:&type length:sizeof(int)];
327     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
328     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
329     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
330     [drawData appendBytes:&row length:sizeof(int)];
331     [drawData appendBytes:&col length:sizeof(int)];
332     [drawData appendBytes:&flags length:sizeof(int)];
333     [drawData appendBytes:&len length:sizeof(int)];
334     [drawData appendBytes:s length:len];
337 - (void)insertLinesFromRow:(int)row count:(int)count
338               scrollBottom:(int)bottom left:(int)left right:(int)right
340     int type = InsertLinesDrawType;
342     [drawData appendBytes:&type length:sizeof(int)];
344     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
345     [drawData appendBytes:&row length:sizeof(int)];
346     [drawData appendBytes:&count length:sizeof(int)];
347     [drawData appendBytes:&bottom length:sizeof(int)];
348     [drawData appendBytes:&left length:sizeof(int)];
349     [drawData appendBytes:&right length:sizeof(int)];
352 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
353                fraction:(int)percent color:(int)color
355     int type = DrawCursorDrawType;
356     unsigned uc = MM_COLOR(color);
358     [drawData appendBytes:&type length:sizeof(int)];
360     [drawData appendBytes:&uc length:sizeof(unsigned)];
361     [drawData appendBytes:&row length:sizeof(int)];
362     [drawData appendBytes:&col length:sizeof(int)];
363     [drawData appendBytes:&shape length:sizeof(int)];
364     [drawData appendBytes:&percent length:sizeof(int)];
367 - (void)flushQueue:(BOOL)force
369     // NOTE! This method gets called a lot; if we were to flush every time it
370     // was called MacVim would feel unresponsive.  So there is a time out which
371     // ensures that the queue isn't flushed too often.
372     if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
373             < MMFlushTimeoutInterval
374             && [drawData length] < MMFlushQueueLenHint)
375         return;
377     if ([drawData length] > 0) {
378         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
379         [drawData setLength:0];
380     }
382     if ([queue count] > 0) {
383         @try {
384             [frontendProxy processCommandQueue:queue];
385         }
386         @catch (NSException *e) {
387             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
388         }
390         [queue removeAllObjects];
392         [lastFlushDate release];
393         lastFlushDate = [[NSDate date] retain];
394     }
397 - (BOOL)waitForInput:(int)milliseconds
399     NSDate *date = milliseconds > 0 ?
400             [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
401             [NSDate distantFuture];
403     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
405     // I know of no way to figure out if the run loop exited because input was
406     // found or because of a time out, so I need to manually indicate when
407     // input was received in processInput:data: and then reset it every time
408     // here.
409     BOOL yn = inputReceived;
410     inputReceived = NO;
412     return yn;
415 - (void)exit
417 #ifdef MAC_CLIENTSERVER
418     // The default connection is used for the client/server code.
419     [[NSConnection defaultConnection] setRootObject:nil];
420     [[NSConnection defaultConnection] invalidate];
421 #endif
423     // By invalidating the NSConnection the MMWindowController immediately
424     // finds out that the connection is down and as a result
425     // [MMWindowController connectionDidDie:] is invoked.
426     //NSLog(@"%@ %s", [self className], _cmd);
427     [[NSNotificationCenter defaultCenter] removeObserver:self];
428     [connection invalidate];
430     if (fontContainerRef) {
431         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
432         fontContainerRef = 0;
433     }
437 - (void)selectTab:(int)index
439     //NSLog(@"%s%d", _cmd, index);
441     index -= 1;
442     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
443     [self queueMessage:SelectTabMsgID data:data];
446 - (void)updateTabBar
448     //NSLog(@"%s", _cmd);
450     NSMutableData *data = [NSMutableData data];
452     int idx = tabpage_index(curtab) - 1;
453     [data appendBytes:&idx length:sizeof(int)];
455     tabpage_T *tp;
456     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
457         // This function puts the label of the tab in the global 'NameBuff'.
458         get_tabline_label(tp, FALSE);
459         char_u *s = NameBuff;
460         int len = STRLEN(s);
461         if (len <= 0) continue;
463 #if MM_ENABLE_CONV
464         s = CONVERT_TO_UTF8(s);
465 #endif
467         // Count the number of windows in the tabpage.
468         //win_T *wp = tp->tp_firstwin;
469         //int wincount;
470         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
472         //[data appendBytes:&wincount length:sizeof(int)];
473         [data appendBytes:&len length:sizeof(int)];
474         [data appendBytes:s length:len];
476 #if MM_ENABLE_CONV
477         CONVERT_TO_UTF8_FREE(s);
478 #endif
479     }
481     [self queueMessage:UpdateTabBarMsgID data:data];
484 - (BOOL)tabBarVisible
486     return tabBarVisible;
489 - (void)showTabBar:(BOOL)enable
491     tabBarVisible = enable;
493     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
494     [self queueMessage:msgid data:nil];
497 - (void)setRows:(int)rows columns:(int)cols
499     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
501     int dim[] = { rows, cols };
502     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
504     [self queueMessage:SetTextDimensionsMsgID data:data];
507 - (void)setWindowTitle:(char *)title
509     NSMutableData *data = [NSMutableData data];
510     int len = strlen(title);
511     if (len <= 0) return;
513     [data appendBytes:&len length:sizeof(int)];
514     [data appendBytes:title length:len];
516     [self queueMessage:SetWindowTitleMsgID data:data];
519 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
520                             saving:(int)saving
522     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
523     //        saving);
525     char_u *s = NULL;
526     NSString *ds = dir ? [NSString stringWithUTF8String:dir] : nil;
527     NSString *ts = title ? [NSString stringWithUTF8String:title] : nil;
528     @try {
529         [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
531         // Wait until a reply is sent from MMVimController.
532         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
533                                  beforeDate:[NSDate distantFuture]];
535         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
536             char_u *ret = (char_u*)[dialogReturn UTF8String];
537 #if MM_ENABLE_CONV
538             ret = CONVERT_FROM_UTF8(ret);
539 #endif
540             s = vim_strsave(ret);
541 #if MM_ENABLE_CONV
542             CONVERT_FROM_UTF8_FREE(ret);
543 #endif
544         }
546         [dialogReturn release];  dialogReturn = nil;
547     }
548     @catch (NSException *e) {
549         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
550     }
552     return (char *)s;
555 - (oneway void)setDialogReturn:(in bycopy id)obj
557     // NOTE: This is called by
558     //   - [MMVimController panelDidEnd:::], and
559     //   - [MMVimController alertDidEnd:::],
560     // to indicate that a save/open panel or alert has finished.
562     if (obj != dialogReturn) {
563         [dialogReturn release];
564         dialogReturn = [obj retain];
565     }
568 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
569                      buttons:(char *)btns textField:(char *)txtfield
571     int retval = 0;
572     NSString *message = nil, *text = nil, *textFieldString = nil;
573     NSArray *buttons = nil;
574     int style = NSInformationalAlertStyle;
576     if (VIM_WARNING == type) style = NSWarningAlertStyle;
577     else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
579     if (btns) {
580         NSString *btnString = [NSString stringWithUTF8String:btns];
581         buttons = [btnString componentsSeparatedByString:@"\n"];
582     }
583     if (title)
584         message = [NSString stringWithUTF8String:title];
585     if (msg) {
586         text = [NSString stringWithUTF8String:msg];
587         if (!message) {
588             // HACK! If there is a '\n\n' or '\n' sequence in the message, then
589             // make the part up to there into the title.  We only do this
590             // because Vim has lots of dialogs without a title and they look
591             // ugly that way.
592             // TODO: Fix the actual dialog texts.
593             NSRange eolRange = [text rangeOfString:@"\n\n"];
594             if (NSNotFound == eolRange.location)
595                 eolRange = [text rangeOfString:@"\n"];
596             if (NSNotFound != eolRange.location) {
597                 message = [text substringToIndex:eolRange.location];
598                 text = [text substringFromIndex:NSMaxRange(eolRange)];
599             }
600         }
601     }
602     if (txtfield)
603         textFieldString = [NSString stringWithUTF8String:txtfield];
605     @try {
606         [frontendProxy presentDialogWithStyle:style message:message
607                               informativeText:text buttonTitles:buttons
608                               textFieldString:textFieldString];
610         // Wait until a reply is sent from MMVimController.
611         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
612                                  beforeDate:[NSDate distantFuture]];
614         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
615                 && [dialogReturn count]) {
616             retval = [[dialogReturn objectAtIndex:0] intValue];
617             if (txtfield && [dialogReturn count] > 1) {
618                 NSString *retString = [dialogReturn objectAtIndex:1];
619                 char_u *ret = (char_u*)[retString UTF8String];
620 #if MM_ENABLE_CONV
621                 ret = CONVERT_FROM_UTF8(ret);
622 #endif
623                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
624 #if MM_ENABLE_CONV
625                 CONVERT_FROM_UTF8_FREE(ret);
626 #endif
627             }
628         }
630         [dialogReturn release]; dialogReturn = nil;
631     }
632     @catch (NSException *e) {
633         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
634     }
636     return retval;
639 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
640                atIndex:(int)index
642     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
643     //        name, index);
645     int namelen = name ? strlen(name) : 0;
646     NSMutableData *data = [NSMutableData data];
648     [data appendBytes:&tag length:sizeof(int)];
649     [data appendBytes:&parentTag length:sizeof(int)];
650     [data appendBytes:&namelen length:sizeof(int)];
651     if (namelen > 0) [data appendBytes:name length:namelen];
652     [data appendBytes:&index length:sizeof(int)];
654     [self queueMessage:AddMenuMsgID data:data];
657 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
658                        tip:(char *)tip icon:(char *)icon
659              keyEquivalent:(int)key modifiers:(int)mods
660                     action:(NSString *)action atIndex:(int)index
662     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
663     //        parentTag, name, tip, index);
665     int namelen = name ? strlen(name) : 0;
666     int tiplen = tip ? strlen(tip) : 0;
667     int iconlen = icon ? strlen(icon) : 0;
668     int eventFlags = vimModMaskToEventModifierFlags(mods);
669     int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
670     NSMutableData *data = [NSMutableData data];
672     key = specialKeyToNSKey(key);
674     [data appendBytes:&tag length:sizeof(int)];
675     [data appendBytes:&parentTag length:sizeof(int)];
676     [data appendBytes:&namelen length:sizeof(int)];
677     if (namelen > 0) [data appendBytes:name length:namelen];
678     [data appendBytes:&tiplen length:sizeof(int)];
679     if (tiplen > 0) [data appendBytes:tip length:tiplen];
680     [data appendBytes:&iconlen length:sizeof(int)];
681     if (iconlen > 0) [data appendBytes:icon length:iconlen];
682     [data appendBytes:&actionlen length:sizeof(int)];
683     if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
684     [data appendBytes:&index length:sizeof(int)];
685     [data appendBytes:&key length:sizeof(int)];
686     [data appendBytes:&eventFlags length:sizeof(int)];
688     [self queueMessage:AddMenuItemMsgID data:data];
691 - (void)removeMenuItemWithTag:(int)tag
693     NSMutableData *data = [NSMutableData data];
694     [data appendBytes:&tag length:sizeof(int)];
696     [self queueMessage:RemoveMenuItemMsgID data:data];
699 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
701     NSMutableData *data = [NSMutableData data];
703     [data appendBytes:&tag length:sizeof(int)];
704     [data appendBytes:&enabled length:sizeof(int)];
706     [self queueMessage:EnableMenuItemMsgID data:data];
709 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
711     int len = strlen(name);
712     int row = -1, col = -1;
714     if (len <= 0) return;
716     if (!mouse && curwin) {
717         row = curwin->w_wrow;
718         col = curwin->w_wcol;
719     }
721     NSMutableData *data = [NSMutableData data];
723     [data appendBytes:&row length:sizeof(int)];
724     [data appendBytes:&col length:sizeof(int)];
725     [data appendBytes:&len length:sizeof(int)];
726     [data appendBytes:name length:len];
728     [self queueMessage:ShowPopupMenuMsgID data:data];
731 - (void)showToolbar:(int)enable flags:(int)flags
733     NSMutableData *data = [NSMutableData data];
735     [data appendBytes:&enable length:sizeof(int)];
736     [data appendBytes:&flags length:sizeof(int)];
738     [self queueMessage:ShowToolbarMsgID data:data];
741 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
743     NSMutableData *data = [NSMutableData data];
745     [data appendBytes:&ident length:sizeof(long)];
746     [data appendBytes:&type length:sizeof(int)];
748     [self queueMessage:CreateScrollbarMsgID data:data];
751 - (void)destroyScrollbarWithIdentifier:(long)ident
753     NSMutableData *data = [NSMutableData data];
754     [data appendBytes:&ident length:sizeof(long)];
756     [self queueMessage:DestroyScrollbarMsgID data:data];
759 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
761     NSMutableData *data = [NSMutableData data];
763     [data appendBytes:&ident length:sizeof(long)];
764     [data appendBytes:&visible length:sizeof(int)];
766     [self queueMessage:ShowScrollbarMsgID data:data];
769 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
771     NSMutableData *data = [NSMutableData data];
773     [data appendBytes:&ident length:sizeof(long)];
774     [data appendBytes:&pos length:sizeof(int)];
775     [data appendBytes:&len length:sizeof(int)];
777     [self queueMessage:SetScrollbarPositionMsgID data:data];
780 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
781                     identifier:(long)ident
783     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
784     float prop = (float)size/(max+1);
785     if (fval < 0) fval = 0;
786     else if (fval > 1.0f) fval = 1.0f;
787     if (prop < 0) prop = 0;
788     else if (prop > 1.0f) prop = 1.0f;
790     NSMutableData *data = [NSMutableData data];
792     [data appendBytes:&ident length:sizeof(long)];
793     [data appendBytes:&fval length:sizeof(float)];
794     [data appendBytes:&prop length:sizeof(float)];
796     [self queueMessage:SetScrollbarThumbMsgID data:data];
799 - (BOOL)setFontWithName:(char *)name
801     NSString *fontName = MMDefaultFontName;
802     float size = MMDefaultFontSize;
803     BOOL parseFailed = NO;
805     if (name) {
806         fontName = [NSString stringWithUTF8String:name];
808         if ([fontName isEqual:@"*"]) {
809             // :set gfn=* shows the font panel.
810             do_cmdline_cmd((char_u*)":macaction orderFrontFontPanel:");
811             return NO;
812         }
814         NSArray *components = [fontName componentsSeparatedByString:@":"];
815         if ([components count] == 2) {
816             NSString *sizeString = [components lastObject];
817             if ([sizeString length] > 0
818                     && [sizeString characterAtIndex:0] == 'h') {
819                 sizeString = [sizeString substringFromIndex:1];
820                 if ([sizeString length] > 0) {
821                     size = [sizeString floatValue];
822                     fontName = [components objectAtIndex:0];
823                 }
824             } else {
825                 parseFailed = YES;
826             }
827         } else if ([components count] > 2) {
828             parseFailed = YES;
829         }
831         if (!parseFailed) {
832             // Replace underscores with spaces.
833             fontName = [[fontName componentsSeparatedByString:@"_"]
834                                      componentsJoinedByString:@" "];
835         }
836     }
838     if (!parseFailed && [fontName length] > 0) {
839         if (size < 6 || size > 100) {
840             // Font size 0.0 tells NSFont to use the 'user default size'.
841             size = 0.0f;
842         }
844         NSFont *font = [NSFont fontWithName:fontName size:size];
846         if (!font && MMDefaultFontName == fontName) {
847             // If for some reason the MacVim default font is not in the app
848             // bundle, then fall back on the system default font.
849             size = 0;
850             font = [NSFont userFixedPitchFontOfSize:size];
851             fontName = [font displayName];
852         }
854         if (font) {
855             //NSLog(@"Setting font '%@' of size %.2f", fontName, size);
856             int len = [fontName
857                     lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
858             if (len > 0) {
859                 NSMutableData *data = [NSMutableData data];
861                 [data appendBytes:&size length:sizeof(float)];
862                 [data appendBytes:&len length:sizeof(int)];
863                 [data appendBytes:[fontName UTF8String] length:len];
865                 [self queueMessage:SetFontMsgID data:data];
866                 return YES;
867             }
868         }
869     }
871     //NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f",
872     //        fontName, size);
873     return NO;
876 - (void)executeActionWithName:(NSString *)name
878     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
880     if (len > 0) {
881         NSMutableData *data = [NSMutableData data];
883         [data appendBytes:&len length:sizeof(int)];
884         [data appendBytes:[name UTF8String] length:len];
886         [self queueMessage:ExecuteActionMsgID data:data];
887     }
890 - (void)setMouseShape:(int)shape
892     NSMutableData *data = [NSMutableData data];
893     [data appendBytes:&shape length:sizeof(int)];
894     [self queueMessage:SetMouseShapeMsgID data:data];
897 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
899     // Vim specifies times in milliseconds, whereas Cocoa wants them in
900     // seconds.
901     blinkWaitInterval = .001f*wait;
902     blinkOnInterval = .001f*on;
903     blinkOffInterval = .001f*off;
906 - (void)startBlink
908     if (blinkTimer) {
909         [blinkTimer invalidate];
910         [blinkTimer release];
911         blinkTimer = nil;
912     }
914     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
915             && gui.in_focus) {
916         blinkState = MMBlinkStateOn;
917         blinkTimer =
918             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
919                                               target:self
920                                             selector:@selector(blinkTimerFired:)
921                                             userInfo:nil repeats:NO] retain];
922         gui_update_cursor(TRUE, FALSE);
923         [self flushQueue:YES];
924     }
927 - (void)stopBlink
929     if (MMBlinkStateOff == blinkState) {
930         gui_update_cursor(TRUE, FALSE);
931         [self flushQueue:YES];
932     }
934     blinkState = MMBlinkStateNone;
937 - (void)adjustLinespace:(int)linespace
939     NSMutableData *data = [NSMutableData data];
940     [data appendBytes:&linespace length:sizeof(int)];
941     [self queueMessage:AdjustLinespaceMsgID data:data];
944 - (void)activate
946     [self queueMessage:ActivateMsgID data:nil];
949 - (int)lookupColorWithKey:(NSString *)key
951     if (!(key && [key length] > 0))
952         return INVALCOLOR;
954     NSString *stripKey = [[[[key lowercaseString]
955         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
956             componentsSeparatedByString:@" "]
957                componentsJoinedByString:@""];
959     if (stripKey && [stripKey length] > 0) {
960         // First of all try to lookup key in the color dictionary; note that
961         // all keys in this dictionary are lowercase with no whitespace.
962         id obj = [colorDict objectForKey:stripKey];
963         if (obj) return [obj intValue];
965         // The key was not in the dictionary; is it perhaps of the form
966         // #rrggbb?
967         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
968             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
969             [scanner setScanLocation:1];
970             unsigned hex = 0;
971             if ([scanner scanHexInt:&hex]) {
972                 return (int)hex;
973             }
974         }
976         // As a last resort, check if it is one of the system defined colors.
977         // The keys in this dictionary are also lowercase with no whitespace.
978         obj = [sysColorDict objectForKey:stripKey];
979         if (obj) {
980             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
981             if (col) {
982                 float r, g, b, a;
983                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
984                 [col getRed:&r green:&g blue:&b alpha:&a];
985                 return (((int)(r*255+.5f) & 0xff) << 16)
986                      + (((int)(g*255+.5f) & 0xff) << 8)
987                      +  ((int)(b*255+.5f) & 0xff);
988             }
989         }
990     }
992     NSLog(@"WARNING: No color with key %@ found.", stripKey);
993     return INVALCOLOR;
996 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
998     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
999     id obj;
1001     while ((obj = [e nextObject])) {
1002         if ([value isEqual:obj])
1003             return YES;
1004     }
1006     return NO;
1009 - (void)enterFullscreen
1011     [self queueMessage:EnterFullscreenMsgID data:nil];
1014 - (void)leaveFullscreen
1016     [self queueMessage:LeaveFullscreenMsgID data:nil];
1019 - (void)updateModifiedFlag
1021     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1022     // vice versa.
1023     int msgid = [self checkForModifiedBuffers]
1024             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1026     [self queueMessage:msgid data:nil];
1029 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1031     // NOTE: This method might get called whenever the run loop is tended to.
1032     // Thus it might get called whilst input is being processed.  Normally this
1033     // is not a problem, but if it gets called often then it might become
1034     // dangerous.  E.g. say a message causes the screen to be redrawn and then
1035     // another message is received causing another simultaneous screen redraw;
1036     // this is not good.  To deal with this problem at the moment, we simply
1037     // drop messages that are received while other input is being processed,
1038     // unless the message represents keyboard input.
1040     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1042     if (inProcessInput && !(InsertTextMsgID == msgid || KeyDownMsgID == msgid
1043                 || CmdKeyMsgID == msgid)) {
1044         // Just drop the input
1045         //NSLog(@"WARNING: Dropping input in %s", _cmd);
1046     } else {
1047         // Don't flush too soon or update speed will suffer.
1048         [lastFlushDate release];
1049         lastFlushDate = [[NSDate date] retain];
1051         inProcessInput = YES;
1052         [self handleMessage:msgid data:data];
1053         inProcessInput = NO;
1055         inputReceived = YES;
1056     }
1059 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1061     // TODO: Get rid of this method?
1063     unsigned i, count = [messages count];
1064     for (i = 0; i < count; i += 2) {
1065         int msgid = [[messages objectAtIndex:i] intValue];
1066         id data = [messages objectAtIndex:i+1];
1067         if ([data isEqual:[NSNull null]])
1068             data = nil;
1070         [self processInput:msgid data:data];
1071     }
1074 - (BOOL)checkForModifiedBuffers
1076     buf_T *buf;
1077     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
1078         if (bufIsChanged(buf)) {
1079             return YES;
1080         }
1081     }
1083     return NO;
1086 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1088     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1089         // If there is no pasteboard, return YES to indicate that there is text
1090         // to copy.
1091         if (!pboard)
1092             return YES;
1094         clip_copy_selection();
1096         // Get the text to put on the pasteboard.
1097         long_u llen = 0; char_u *str = 0;
1098         int type = clip_convert_selection(&str, &llen, &clip_star);
1099         if (type < 0)
1100             return NO;
1101         
1102         // TODO: Avoid overflow.
1103         int len = (int)llen;
1104 #if MM_ENABLE_CONV
1105         if (output_conv.vc_type != CONV_NONE) {
1106             char_u *conv_str = string_convert(&output_conv, str, &len);
1107             if (conv_str) {
1108                 vim_free(str);
1109                 str = conv_str;
1110             }
1111         }
1112 #endif
1114         NSString *string = [[NSString alloc]
1115             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1117         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1118         [pboard declareTypes:types owner:nil];
1119         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1120     
1121         [string release];
1122         vim_free(str);
1124         return ok;
1125     }
1127     return NO;
1130 - (oneway void)addReply:(in bycopy NSString *)reply
1131                  server:(in byref id <MMVimServerProtocol>)server
1133     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1135     // Replies might come at any time and in any order so we keep them in an
1136     // array inside a dictionary with the send port used as key.
1138     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1139     // HACK! Assume connection uses mach ports.
1140     int port = [(NSMachPort*)[conn sendPort] machPort];
1141     NSNumber *key = [NSNumber numberWithInt:port];
1143     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1144     if (!replies) {
1145         replies = [NSMutableArray array];
1146         [serverReplyDict setObject:replies forKey:key];
1147     }
1149     [replies addObject:reply];
1152 - (void)addInput:(in bycopy NSString *)input
1153                  client:(in byref id <MMVimClientProtocol>)client
1155     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1157     char_u *s = (char_u*)[input UTF8String];
1159 #if MM_ENABLE_CONV
1160     s = CONVERT_FROM_UTF8(s);
1161 #endif
1163     server_to_input_buf(s);
1165 #if MM_ENABLE_CONV
1166     CONVERT_FROM_UTF8_FREE(s);
1167 #endif
1169     [self addClient:(id)client];
1171     inputReceived = YES;
1174 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1175                  client:(in byref id <MMVimClientProtocol>)client
1177     //NSLog(@"evaluateExpression:%@ client:%@", expr, (id)client);
1179     NSString *eval = nil;
1180     char_u *s = (char_u*)[expr UTF8String];
1182 #if MM_ENABLE_CONV
1183     s = CONVERT_FROM_UTF8(s);
1184 #endif
1186     char_u *res = eval_client_expr_to_string(s);
1188 #if MM_ENABLE_CONV
1189     CONVERT_FROM_UTF8_FREE(s);
1190 #endif
1192     if (res != NULL) {
1193         s = res;
1194 #if MM_ENABLE_CONV
1195         s = CONVERT_TO_UTF8(s);
1196 #endif
1197         eval = [NSString stringWithUTF8String:(char*)s];
1198 #if MM_ENABLE_CONV
1199         CONVERT_TO_UTF8_FREE(s);
1200 #endif
1201         vim_free(res);
1202     }
1204     [self addClient:(id)client];
1206     return eval;
1209 - (void)registerServerWithName:(NSString *)name
1211     NSString *svrName = name;
1212     NSConnection *svrConn = [NSConnection defaultConnection];
1213     unsigned i;
1215     for (i = 0; i < MMServerMax; ++i) {
1216         NSString *connName = [self connectionNameFromServerName:svrName];
1218         if ([svrConn registerName:connName]) {
1219             //NSLog(@"Registered server with name: %@", svrName);
1221             // TODO: Set request/reply time-outs to something else?
1222             //
1223             // Don't wait for requests (time-out means that the message is
1224             // dropped).
1225             [svrConn setRequestTimeout:0];
1226             //[svrConn setReplyTimeout:MMReplyTimeout];
1227             [svrConn setRootObject:self];
1229             char_u *s = (char_u*)[svrName UTF8String];
1230 #if MM_ENABLE_CONV
1231             s = CONVERT_FROM_UTF8(s);
1232 #endif
1233             // NOTE: 'serverName' is a global variable
1234             serverName = vim_strsave(s);
1235 #if MM_ENABLE_CONV
1236             CONVERT_FROM_UTF8_FREE(s);
1237 #endif
1238 #ifdef FEAT_EVAL
1239             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1240 #endif
1241 #ifdef FEAT_TITLE
1242             need_maketitle = TRUE;
1243 #endif
1244             [self queueMessage:SetServerNameMsgID data:
1245                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1246             break;
1247         }
1249         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1250     }
1253 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1254                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1255               silent:(BOOL)silent
1257     // NOTE: If 'name' equals 'serverName' then the request is local (client
1258     // and server are the same).  This case is not handled separately, so a
1259     // connection will be set up anyway (this simplifies the code).
1261     NSConnection *conn = [self connectionForServerName:name];
1262     if (!conn) {
1263         if (!silent) {
1264             char_u *s = (char_u*)[name UTF8String];
1265 #if MM_ENABLE_CONV
1266             s = CONVERT_FROM_UTF8(s);
1267 #endif
1268             EMSG2(_(e_noserver), s);
1269 #if MM_ENABLE_CONV
1270             CONVERT_FROM_UTF8_FREE(s);
1271 #endif
1272         }
1273         return NO;
1274     }
1276     if (port) {
1277         // HACK! Assume connection uses mach ports.
1278         *port = [(NSMachPort*)[conn sendPort] machPort];
1279     }
1281     id proxy = [conn rootProxy];
1282     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1284     @try {
1285         if (expr) {
1286             NSString *eval = [proxy evaluateExpression:string client:self];
1287             if (reply) {
1288                 if (eval) {
1289                     char_u *r = (char_u*)[eval UTF8String];
1290 #if MM_ENABLE_CONV
1291                     r = CONVERT_FROM_UTF8(r);
1292 #endif
1293                     *reply = vim_strsave(r);
1294 #if MM_ENABLE_CONV
1295                     CONVERT_FROM_UTF8_FREE(r);
1296 #endif
1297                 } else {
1298                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1299                 }
1300             }
1302             if (!eval)
1303                 return NO;
1304         } else {
1305             [proxy addInput:string client:self];
1306         }
1307     }
1308     @catch (NSException *e) {
1309         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1310         return NO;
1311     }
1313     return YES;
1316 - (NSArray *)serverList
1318     NSArray *list = nil;
1320     if ([self connection]) {
1321         id proxy = [connection rootProxy];
1322         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1324         @try {
1325             list = [proxy serverList];
1326         }
1327         @catch (NSException *e) {
1328             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1329         }
1330     } else {
1331         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1332     }
1334     return list;
1337 - (NSString *)peekForReplyOnPort:(int)port
1339     //NSLog(@"%s%d", _cmd, port);
1341     NSNumber *key = [NSNumber numberWithInt:port];
1342     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1343     if (replies && [replies count]) {
1344         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1345         //        [replies objectAtIndex:0]);
1346         return [replies objectAtIndex:0];
1347     }
1349     //NSLog(@"    No replies");
1350     return nil;
1353 - (NSString *)waitForReplyOnPort:(int)port
1355     //NSLog(@"%s%d", _cmd, port);
1356     
1357     NSConnection *conn = [self connectionForServerPort:port];
1358     if (!conn)
1359         return nil;
1361     NSNumber *key = [NSNumber numberWithInt:port];
1362     NSMutableArray *replies = nil;
1363     NSString *reply = nil;
1365     // Wait for reply as long as the connection to the server is valid (unless
1366     // user interrupts wait with Ctrl-C).
1367     while (!got_int && [conn isValid] &&
1368             !(replies = [serverReplyDict objectForKey:key])) {
1369         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1370                                  beforeDate:[NSDate distantFuture]];
1371     }
1373     if (replies) {
1374         if ([replies count] > 0) {
1375             reply = [[replies objectAtIndex:0] retain];
1376             //NSLog(@"    Got reply: %@", reply);
1377             [replies removeObjectAtIndex:0];
1378             [reply autorelease];
1379         }
1381         if ([replies count] == 0)
1382             [serverReplyDict removeObjectForKey:key];
1383     }
1385     return reply;
1388 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1390     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1391     if (client) {
1392         @try {
1393             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1394             [client addReply:reply server:self];
1395             return YES;
1396         }
1397         @catch (NSException *e) {
1398             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1399         }
1400     } else {
1401         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1402     }
1404     return NO;
1407 @end // MMBackend
1411 @implementation MMBackend (Private)
1413 - (void)handleMessage:(int)msgid data:(NSData *)data
1415     // NOTE: Be careful with what you do in this method.  Ideally, a message
1416     // should be handled by adding something to the input buffer and returning
1417     // immediately.  If you call a Vim function then it should not enter a loop
1418     // waiting for key presses or in any other way block the process.  The
1419     // reason for this being that only one message can be processed at a time,
1420     // so if another message is received while processing, then the new message
1421     // is dropped.  See also the comment in processInput:data:.
1423     if (InsertTextMsgID == msgid) {
1424         [self handleInsertText:data];
1425     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1426         if (!data) return;
1427         const void *bytes = [data bytes];
1428         int mods = *((int*)bytes);  bytes += sizeof(int);
1429         int len = *((int*)bytes);  bytes += sizeof(int);
1430         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1431                                               encoding:NSUTF8StringEncoding];
1432         mods = eventModifierFlagsToVimModMask(mods);
1434         [self handleKeyDown:key modifiers:mods];
1436         [key release];
1437     } else if (SelectTabMsgID == msgid) {
1438         if (!data) return;
1439         const void *bytes = [data bytes];
1440         int idx = *((int*)bytes) + 1;
1441         //NSLog(@"Selecting tab %d", idx);
1442         send_tabline_event(idx);
1443     } else if (CloseTabMsgID == msgid) {
1444         if (!data) return;
1445         const void *bytes = [data bytes];
1446         int idx = *((int*)bytes) + 1;
1447         //NSLog(@"Closing tab %d", idx);
1448         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1449     } else if (AddNewTabMsgID == msgid) {
1450         //NSLog(@"Adding new tab");
1451         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1452     } else if (DraggedTabMsgID == msgid) {
1453         if (!data) return;
1454         const void *bytes = [data bytes];
1455         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1456         // based.
1457         int idx = *((int*)bytes);
1459         tabpage_move(idx);
1460     } else if (ScrollWheelMsgID == msgid) {
1461         if (!data) return;
1462         const void *bytes = [data bytes];
1464         int row = *((int*)bytes);  bytes += sizeof(int);
1465         int col = *((int*)bytes);  bytes += sizeof(int);
1466         int flags = *((int*)bytes);  bytes += sizeof(int);
1467         float dy = *((float*)bytes);  bytes += sizeof(float);
1469         int button = MOUSE_5;
1470         if (dy > 0) button = MOUSE_4;
1472         flags = eventModifierFlagsToVimMouseModMask(flags);
1474         gui_send_mouse_event(button, col, row, NO, flags);
1475     } else if (MouseDownMsgID == msgid) {
1476         if (!data) return;
1477         const void *bytes = [data bytes];
1479         int row = *((int*)bytes);  bytes += sizeof(int);
1480         int col = *((int*)bytes);  bytes += sizeof(int);
1481         int button = *((int*)bytes);  bytes += sizeof(int);
1482         int flags = *((int*)bytes);  bytes += sizeof(int);
1483         int count = *((int*)bytes);  bytes += sizeof(int);
1485         button = eventButtonNumberToVimMouseButton(button);
1486         flags = eventModifierFlagsToVimMouseModMask(flags);
1488         gui_send_mouse_event(button, col, row, count>1, flags);
1489     } else if (MouseUpMsgID == msgid) {
1490         if (!data) return;
1491         const void *bytes = [data bytes];
1493         int row = *((int*)bytes);  bytes += sizeof(int);
1494         int col = *((int*)bytes);  bytes += sizeof(int);
1495         int flags = *((int*)bytes);  bytes += sizeof(int);
1497         flags = eventModifierFlagsToVimMouseModMask(flags);
1499         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1500     } else if (MouseDraggedMsgID == msgid) {
1501         if (!data) return;
1502         const void *bytes = [data bytes];
1504         int row = *((int*)bytes);  bytes += sizeof(int);
1505         int col = *((int*)bytes);  bytes += sizeof(int);
1506         int flags = *((int*)bytes);  bytes += sizeof(int);
1508         flags = eventModifierFlagsToVimMouseModMask(flags);
1510         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1511     } else if (SetTextDimensionsMsgID == msgid) {
1512         if (!data) return;
1513         const void *bytes = [data bytes];
1514         int rows = *((int*)bytes);  bytes += sizeof(int);
1515         int cols = *((int*)bytes);  bytes += sizeof(int);
1517         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1518         // gui_resize_shell(), so we have to manually set the rows and columns
1519         // here.  (MacVim doesn't change the rows and columns to avoid
1520         // inconsistent states between Vim and MacVim.)
1521         [self setRows:rows columns:cols];
1523         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1524         gui_resize_shell(cols, rows);
1525     } else if (ExecuteMenuMsgID == msgid) {
1526         if (!data) return;
1527         const void *bytes = [data bytes];
1528         int tag = *((int*)bytes);  bytes += sizeof(int);
1530         vimmenu_T *menu = (vimmenu_T*)tag;
1531         // TODO!  Make sure 'menu' is a valid menu pointer!
1532         if (menu) {
1533             gui_menu_cb(menu);
1534         }
1535     } else if (ToggleToolbarMsgID == msgid) {
1536         [self handleToggleToolbar];
1537     } else if (ScrollbarEventMsgID == msgid) {
1538         [self handleScrollbarEvent:data];
1539     } else if (SetFontMsgID == msgid) {
1540         [self handleSetFont:data];
1541     } else if (VimShouldCloseMsgID == msgid) {
1542         gui_shell_closed();
1543     } else if (DropFilesMsgID == msgid) {
1544         [self handleDropFiles:data];
1545     } else if (DropStringMsgID == msgid) {
1546         [self handleDropString:data];
1547     } else if (GotFocusMsgID == msgid) {
1548         if (!gui.in_focus)
1549             [self focusChange:YES];
1550     } else if (LostFocusMsgID == msgid) {
1551         if (gui.in_focus)
1552             [self focusChange:NO];
1553     } else if (MouseMovedMsgID == msgid) {
1554         const void *bytes = [data bytes];
1555         int row = *((int*)bytes);  bytes += sizeof(int);
1556         int col = *((int*)bytes);  bytes += sizeof(int);
1558         gui_mouse_moved(col, row);
1559     } else if (SetMouseShapeMsgID == msgid) {
1560         const void *bytes = [data bytes];
1561         int shape = *((int*)bytes);  bytes += sizeof(int);
1562         update_mouseshape(shape);
1563     } else {
1564         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1565     }
1568 + (NSDictionary *)specialKeys
1570     static NSDictionary *specialKeys = nil;
1572     if (!specialKeys) {
1573         NSBundle *mainBundle = [NSBundle mainBundle];
1574         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1575                                               ofType:@"plist"];
1576         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1577     }
1579     return specialKeys;
1582 - (void)handleInsertText:(NSData *)data
1584     if (!data) return;
1586     NSString *key = [[NSString alloc] initWithData:data
1587                                           encoding:NSUTF8StringEncoding];
1588     char_u *str = (char_u*)[key UTF8String];
1589     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1591 #if MM_ENABLE_CONV
1592     char_u *conv_str = NULL;
1593     if (input_conv.vc_type != CONV_NONE) {
1594         conv_str = string_convert(&input_conv, str, &len);
1595         if (conv_str)
1596             str = conv_str;
1597     }
1598 #endif
1600     for (i = 0; i < len; ++i) {
1601         add_to_input_buf(str+i, 1);
1602         if (CSI == str[i]) {
1603             // NOTE: If the converted string contains the byte CSI, then it
1604             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1605             // won't work.
1606             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1607             add_to_input_buf(extra, 2);
1608         }
1609     }
1611 #if MM_ENABLE_CONV
1612     if (conv_str)
1613         vim_free(conv_str);
1614 #endif
1615     [key release];
1618 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1620     char_u special[3];
1621     char_u modChars[3];
1622     char_u *chars = (char_u*)[key UTF8String];
1623 #if MM_ENABLE_CONV
1624     char_u *conv_str = NULL;
1625 #endif
1626     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1628     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1629     // that new keys can easily be added.
1630     NSString *specialString = [[MMBackend specialKeys]
1631             objectForKey:key];
1632     if (specialString && [specialString length] > 1) {
1633         //NSLog(@"special key: %@", specialString);
1634         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1635                 [specialString characterAtIndex:1]);
1637         ikey = simplify_key(ikey, &mods);
1638         if (ikey == CSI)
1639             ikey = K_CSI;
1641         special[0] = CSI;
1642         special[1] = K_SECOND(ikey);
1643         special[2] = K_THIRD(ikey);
1645         chars = special;
1646         length = 3;
1647     } else if (1 == length && TAB == chars[0]) {
1648         // Tab is a trouble child:
1649         // - <Tab> is added to the input buffer as is
1650         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1651         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1652         //   to be converted to utf-8
1653         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1654         // - <C-Tab> is reserved by Mac OS X
1655         // - <D-Tab> is reserved by Mac OS X
1656         chars = special;
1657         special[0] = TAB;
1658         length = 1;
1660         if (mods & MOD_MASK_SHIFT) {
1661             mods &= ~MOD_MASK_SHIFT;
1662             special[0] = CSI;
1663             special[1] = K_SECOND(K_S_TAB);
1664             special[2] = K_THIRD(K_S_TAB);
1665             length = 3;
1666         } else if (mods & MOD_MASK_ALT) {
1667             int mtab = 0x80 | TAB;
1668             if (enc_utf8) {
1669                 // Convert to utf-8
1670                 special[0] = (mtab >> 6) + 0xc0;
1671                 special[1] = mtab & 0xbf;
1672                 length = 2;
1673             } else {
1674                 special[0] = mtab;
1675                 length = 1;
1676             }
1677             mods &= ~MOD_MASK_ALT;
1678         }
1679     } else if (length > 0) {
1680         unichar c = [key characterAtIndex:0];
1682         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1683         //        [key characterAtIndex:0], mods);
1685         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1686                 || (c == intr_char && intr_char != Ctrl_C))) {
1687             trash_input_buf();
1688             got_int = TRUE;
1689         }
1691         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1692         // cleared since they are already added to the key by the AppKit.
1693         // Unfortunately, the only way to deal with when to clear the modifiers
1694         // or not seems to be to have hard-wired rules like this.
1695         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1696                     || 0x9 == c || 0xd == c) ) {
1697             mods &= ~MOD_MASK_SHIFT;
1698             mods &= ~MOD_MASK_CTRL;
1699             //NSLog(@"clear shift ctrl");
1700         }
1702         // HACK!  All Option+key presses go via 'insert text' messages, except
1703         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1704         // not work to map to it.
1705         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1706             //NSLog(@"clear alt");
1707             mods &= ~MOD_MASK_ALT;
1708         }
1710 #if MM_ENABLE_CONV
1711         if (input_conv.vc_type != CONV_NONE) {
1712             conv_str = string_convert(&input_conv, chars, &length);
1713             if (conv_str)
1714                 chars = conv_str;
1715         }
1716 #endif
1717     }
1719     if (chars && length > 0) {
1720         if (mods) {
1721             //NSLog(@"adding mods: %d", mods);
1722             modChars[0] = CSI;
1723             modChars[1] = KS_MODIFIER;
1724             modChars[2] = mods;
1725             add_to_input_buf(modChars, 3);
1726         }
1728         //NSLog(@"add to input buf: 0x%x", chars[0]);
1729         // TODO: Check for CSI bytes?
1730         add_to_input_buf(chars, length);
1731     }
1733 #if MM_ENABLE_CONV
1734     if (conv_str)
1735         vim_free(conv_str);
1736 #endif
1739 - (void)queueMessage:(int)msgid data:(NSData *)data
1741     [queue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1742     if (data)
1743         [queue addObject:data];
1744     else
1745         [queue addObject:[NSData data]];
1748 - (void)connectionDidDie:(NSNotification *)notification
1750     // If the main connection to MacVim is lost this means that MacVim was
1751     // either quit (by the user chosing Quit on the MacVim menu), or it has
1752     // crashed.  In either case our only option is to quit now.
1753     // TODO: Write backup file?
1755     //NSLog(@"A Vim process lots its connection to MacVim; quitting.");
1756     getout(0);
1759 - (void)blinkTimerFired:(NSTimer *)timer
1761     NSTimeInterval timeInterval = 0;
1763     [blinkTimer release];
1764     blinkTimer = nil;
1766     if (MMBlinkStateOn == blinkState) {
1767         gui_undraw_cursor();
1768         blinkState = MMBlinkStateOff;
1769         timeInterval = blinkOffInterval;
1770     } else if (MMBlinkStateOff == blinkState) {
1771         gui_update_cursor(TRUE, FALSE);
1772         blinkState = MMBlinkStateOn;
1773         timeInterval = blinkOnInterval;
1774     }
1776     if (timeInterval > 0) {
1777         blinkTimer = 
1778             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1779                                             selector:@selector(blinkTimerFired:)
1780                                             userInfo:nil repeats:NO] retain];
1781         [self flushQueue:YES];
1782     }
1785 - (void)focusChange:(BOOL)on
1787     gui_focus_change(on);
1790 - (void)handleToggleToolbar
1792     // If 'go' contains 'T', then remove it, else add it.
1794     char_u go[sizeof(GO_ALL)+2];
1795     char_u *p;
1796     int len;
1798     STRCPY(go, p_go);
1799     p = vim_strchr(go, GO_TOOLBAR);
1800     len = STRLEN(go);
1802     if (p != NULL) {
1803         char_u *end = go + len;
1804         while (p < end) {
1805             p[0] = p[1];
1806             ++p;
1807         }
1808     } else {
1809         go[len] = GO_TOOLBAR;
1810         go[len+1] = NUL;
1811     }
1813     set_option_value((char_u*)"guioptions", 0, go, 0);
1815     // Force screen redraw (does it have to be this complicated?).
1816     redraw_all_later(CLEAR);
1817     update_screen(NOT_VALID);
1818     setcursor();
1819     out_flush();
1820     gui_update_cursor(FALSE, FALSE);
1821     gui_mch_flush();
1824 - (void)handleScrollbarEvent:(NSData *)data
1826     if (!data) return;
1828     const void *bytes = [data bytes];
1829     long ident = *((long*)bytes);  bytes += sizeof(long);
1830     int hitPart = *((int*)bytes);  bytes += sizeof(int);
1831     float fval = *((float*)bytes);  bytes += sizeof(float);
1832     scrollbar_T *sb = gui_find_scrollbar(ident);
1834     if (sb) {
1835         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1836         long value = sb_info->value;
1837         long size = sb_info->size;
1838         long max = sb_info->max;
1839         BOOL isStillDragging = NO;
1840         BOOL updateKnob = YES;
1842         switch (hitPart) {
1843         case NSScrollerDecrementPage:
1844             value -= (size > 2 ? size - 2 : 1);
1845             break;
1846         case NSScrollerIncrementPage:
1847             value += (size > 2 ? size - 2 : 1);
1848             break;
1849         case NSScrollerDecrementLine:
1850             --value;
1851             break;
1852         case NSScrollerIncrementLine:
1853             ++value;
1854             break;
1855         case NSScrollerKnob:
1856             isStillDragging = YES;
1857             // fall through ...
1858         case NSScrollerKnobSlot:
1859             value = (long)(fval * (max - size + 1));
1860             // fall through ...
1861         default:
1862             updateKnob = NO;
1863             break;
1864         }
1866         //NSLog(@"value %d -> %d", sb_info->value, value);
1867         gui_drag_scrollbar(sb, value, isStillDragging);
1869         if (updateKnob) {
1870             // Dragging the knob or option+clicking automatically updates
1871             // the knob position (on the actual NSScroller), so we only
1872             // need to set the knob position in the other cases.
1873             if (sb->wp) {
1874                 // Update both the left&right vertical scrollbars.
1875                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1876                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1877                 [self setScrollbarThumbValue:value size:size max:max
1878                                   identifier:identLeft];
1879                 [self setScrollbarThumbValue:value size:size max:max
1880                                   identifier:identRight];
1881             } else {
1882                 // Update the horizontal scrollbar.
1883                 [self setScrollbarThumbValue:value size:size max:max
1884                                   identifier:ident];
1885             }
1886         }
1887     }
1890 - (void)handleSetFont:(NSData *)data
1892     if (!data) return;
1894     const void *bytes = [data bytes];
1895     float pointSize = *((float*)bytes);  bytes += sizeof(float);
1896     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1897     bytes += sizeof(unsigned);  // len not used
1899     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1900     [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1901     char_u *s = (char_u*)[name UTF8String];
1903 #if MM_ENABLE_CONV
1904     s = CONVERT_FROM_UTF8(s);
1905 #endif
1907     set_option_value((char_u*)"guifont", 0, s, 0);
1909 #if MM_ENABLE_CONV
1910     CONVERT_FROM_UTF8_FREE(s);
1911 #endif
1913     // Force screen redraw (does it have to be this complicated?).
1914     redraw_all_later(CLEAR);
1915     update_screen(NOT_VALID);
1916     setcursor();
1917     out_flush();
1918     gui_update_cursor(FALSE, FALSE);
1919     gui_mch_flush();
1922 - (void)handleDropFiles:(NSData *)data
1924     if (!data) return;
1926 #ifdef FEAT_DND
1927     const void *bytes = [data bytes];
1928     const void *end = [data bytes] + [data length];
1929     int n = *((int*)bytes);  bytes += sizeof(int);
1931     if (State & CMDLINE) {
1932         // HACK!  If Vim is in command line mode then the files names
1933         // should be added to the command line, instead of opening the
1934         // files in tabs.  This is taken care of by gui_handle_drop().
1935         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1936         if (fnames) {
1937             int i = 0;
1938             while (bytes < end && i < n) {
1939                 int len = *((int*)bytes);  bytes += sizeof(int);
1940                 char_u *s = (char_u*)bytes;
1941 #if MM_ENABLE_CONV
1942                 s = CONVERT_FROM_UTF8(s);
1943 #endif
1944                 fnames[i++] = vim_strsave(s);
1945 #if MM_ENABLE_CONV
1946                 CONVERT_FROM_UTF8_FREE(s);
1947 #endif
1948                 bytes += len;
1949             }
1951             // NOTE!  This function will free 'fnames'.
1952             // HACK!  It is assumed that the 'x' and 'y' arguments are
1953             // unused when in command line mode.
1954             gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
1955         }
1956     } else {
1957         // HACK!  I'm not sure how to get Vim to open a list of files in
1958         // tabs, so instead I create a ':tab drop' command with all the
1959         // files to open and execute it.
1960         NSMutableString *cmd = (n > 1)
1961                 ? [NSMutableString stringWithString:@":tab drop"]
1962                 : [NSMutableString stringWithString:@":drop"];
1964         int i;
1965         for (i = 0; i < n && bytes < end; ++i) {
1966             int len = *((int*)bytes);  bytes += sizeof(int);
1967             NSString *file = [NSString stringWithUTF8String:bytes];
1968             file = [file stringByEscapingSpecialFilenameCharacters];
1969             bytes += len;
1971             [cmd appendString:@" "];
1972             [cmd appendString:file];
1973         }
1975         // By going to the last tabpage we ensure that the new tabs will
1976         // appear last (if this call is left out, the taborder becomes
1977         // messy).
1978         goto_tabpage(9999);
1980         char_u *s = (char_u*)[cmd UTF8String];
1981 #if MM_ENABLE_CONV
1982         s = CONVERT_FROM_UTF8(s);
1983 #endif
1984         do_cmdline_cmd(s);
1985 #if MM_ENABLE_CONV
1986         CONVERT_FROM_UTF8_FREE(s);
1987 #endif
1989         // Force screen redraw (does it have to be this complicated?).
1990         // (This code was taken from the end of gui_handle_drop().)
1991         update_screen(NOT_VALID);
1992         setcursor();
1993         out_flush();
1994         gui_update_cursor(FALSE, FALSE);
1995         maketitle();
1996         gui_mch_flush();
1997     }
1998 #endif // FEAT_DND
2001 - (void)handleDropString:(NSData *)data
2003     if (!data) return;
2005 #ifdef FEAT_DND
2006     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2007     const void *bytes = [data bytes];
2008     int len = *((int*)bytes);  bytes += sizeof(int);
2009     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2011     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2012     NSRange range = { 0, [string length] };
2013     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2014                                          withString:@"\x0a" options:0
2015                                               range:range];
2016     if (0 == n) {
2017         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2018                                        options:0 range:range];
2019     }
2021     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2022     char_u *s = (char_u*)[string UTF8String];
2023 #if MM_ENABLE_CONV
2024     if (input_conv.vc_type != CONV_NONE)
2025         s = string_convert(&input_conv, s, &len);
2026 #endif
2027     dnd_yank_drag_data(s, len);
2028 #if MM_ENABLE_CONV
2029     if (input_conv.vc_type != CONV_NONE)
2030         vim_free(s);
2031 #endif
2032     add_to_input_buf(dropkey, sizeof(dropkey));
2033 #endif // FEAT_DND
2036 @end // MMBackend (Private)
2041 @implementation MMBackend (ClientServer)
2043 - (NSString *)connectionNameFromServerName:(NSString *)name
2045     NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2047     return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2048         lowercaseString];
2051 - (NSConnection *)connectionForServerName:(NSString *)name
2053     // TODO: Try 'name%d' if 'name' fails.
2054     NSString *connName = [self connectionNameFromServerName:name];
2055     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2057     if (!svrConn) {
2058         svrConn = [NSConnection connectionWithRegisteredName:connName
2059                                                            host:nil];
2060         // Try alternate server...
2061         if (!svrConn && alternateServerName) {
2062             //NSLog(@"  trying to connect to alternate server: %@",
2063             //        alternateServerName);
2064             connName = [self connectionNameFromServerName:alternateServerName];
2065             svrConn = [NSConnection connectionWithRegisteredName:connName
2066                                                             host:nil];
2067         }
2069         // Try looking for alternate servers...
2070         if (!svrConn) {
2071             //NSLog(@"  looking for alternate servers...");
2072             NSString *alt = [self alternateServerNameForName:name];
2073             if (alt != alternateServerName) {
2074                 //NSLog(@"  found alternate server: %@", string);
2075                 [alternateServerName release];
2076                 alternateServerName = [alt copy];
2077             }
2078         }
2080         // Try alternate server again...
2081         if (!svrConn && alternateServerName) {
2082             //NSLog(@"  trying to connect to alternate server: %@",
2083             //        alternateServerName);
2084             connName = [self connectionNameFromServerName:alternateServerName];
2085             svrConn = [NSConnection connectionWithRegisteredName:connName
2086                                                             host:nil];
2087         }
2089         if (svrConn) {
2090             [connectionNameDict setObject:svrConn forKey:connName];
2092             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2093             [[NSNotificationCenter defaultCenter] addObserver:self
2094                     selector:@selector(serverConnectionDidDie:)
2095                         name:NSConnectionDidDieNotification object:svrConn];
2096         }
2097     }
2099     return svrConn;
2102 - (NSConnection *)connectionForServerPort:(int)port
2104     NSConnection *conn;
2105     NSEnumerator *e = [connectionNameDict objectEnumerator];
2107     while ((conn = [e nextObject])) {
2108         // HACK! Assume connection uses mach ports.
2109         if (port == [(NSMachPort*)[conn sendPort] machPort])
2110             return conn;
2111     }
2113     return nil;
2116 - (void)serverConnectionDidDie:(NSNotification *)notification
2118     //NSLog(@"%s%@", _cmd, notification);
2120     NSConnection *svrConn = [notification object];
2122     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2123     [[NSNotificationCenter defaultCenter]
2124             removeObserver:self
2125                       name:NSConnectionDidDieNotification
2126                     object:svrConn];
2128     [connectionNameDict removeObjectsForKeys:
2129         [connectionNameDict allKeysForObject:svrConn]];
2131     // HACK! Assume connection uses mach ports.
2132     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2133     NSNumber *key = [NSNumber numberWithInt:port];
2135     [clientProxyDict removeObjectForKey:key];
2136     [serverReplyDict removeObjectForKey:key];
2139 - (void)addClient:(NSDistantObject *)client
2141     NSConnection *conn = [client connectionForProxy];
2142     // HACK! Assume connection uses mach ports.
2143     int port = [(NSMachPort*)[conn sendPort] machPort];
2144     NSNumber *key = [NSNumber numberWithInt:port];
2146     if (![clientProxyDict objectForKey:key]) {
2147         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2148         [clientProxyDict setObject:client forKey:key];
2149     }
2151     // NOTE: 'clientWindow' is a global variable which is used by <client>
2152     clientWindow = port;
2155 - (NSString *)alternateServerNameForName:(NSString *)name
2157     if (!(name && [name length] > 0))
2158         return nil;
2160     // Only look for alternates if 'name' doesn't end in a digit.
2161     unichar lastChar = [name characterAtIndex:[name length]-1];
2162     if (lastChar >= '0' && lastChar <= '9')
2163         return nil;
2165     // Look for alternates among all current servers.
2166     NSArray *list = [self serverList];
2167     if (!(list && [list count] > 0))
2168         return nil;
2170     // Filter out servers starting with 'name' and ending with a number. The
2171     // (?i) pattern ensures that the match is case insensitive.
2172     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2173     NSPredicate *pred = [NSPredicate predicateWithFormat:
2174             @"SELF MATCHES %@", pat];
2175     list = [list filteredArrayUsingPredicate:pred];
2176     if ([list count] > 0) {
2177         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2178         return [list objectAtIndex:0];
2179     }
2181     return nil;
2184 @end // MMBackend (ClientServer)
2189 @implementation NSString (MMServerNameCompare)
2190 - (NSComparisonResult)serverNameCompare:(NSString *)string
2192     return [self compare:string
2193                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2195 @end
2200 static int eventModifierFlagsToVimModMask(int modifierFlags)
2202     int modMask = 0;
2204     if (modifierFlags & NSShiftKeyMask)
2205         modMask |= MOD_MASK_SHIFT;
2206     if (modifierFlags & NSControlKeyMask)
2207         modMask |= MOD_MASK_CTRL;
2208     if (modifierFlags & NSAlternateKeyMask)
2209         modMask |= MOD_MASK_ALT;
2210     if (modifierFlags & NSCommandKeyMask)
2211         modMask |= MOD_MASK_CMD;
2213     return modMask;
2216 static int vimModMaskToEventModifierFlags(int mods)
2218     int flags = 0;
2220     if (mods & MOD_MASK_SHIFT)
2221         flags |= NSShiftKeyMask;
2222     if (mods & MOD_MASK_CTRL)
2223         flags |= NSControlKeyMask;
2224     if (mods & MOD_MASK_ALT)
2225         flags |= NSAlternateKeyMask;
2226     if (mods & MOD_MASK_CMD)
2227         flags |= NSCommandKeyMask;
2229     return flags;
2232 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2234     int modMask = 0;
2236     if (modifierFlags & NSShiftKeyMask)
2237         modMask |= MOUSE_SHIFT;
2238     if (modifierFlags & NSControlKeyMask)
2239         modMask |= MOUSE_CTRL;
2240     if (modifierFlags & NSAlternateKeyMask)
2241         modMask |= MOUSE_ALT;
2243     return modMask;
2246 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2248     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE,
2249             MOUSE_X1, MOUSE_X2 };
2251     return mouseButton[buttonNumber < 5 ? buttonNumber : 0];
2254 static int specialKeyToNSKey(int key)
2256     if (!IS_SPECIAL(key))
2257         return key;
2259     static struct {
2260         int special;
2261         int nskey;
2262     } sp2ns[] = {
2263         { K_UP, NSUpArrowFunctionKey },
2264         { K_DOWN, NSDownArrowFunctionKey },
2265         { K_LEFT, NSLeftArrowFunctionKey },
2266         { K_RIGHT, NSRightArrowFunctionKey },
2267         { K_F1, NSF1FunctionKey },
2268         { K_F2, NSF2FunctionKey },
2269         { K_F3, NSF3FunctionKey },
2270         { K_F4, NSF4FunctionKey },
2271         { K_F5, NSF5FunctionKey },
2272         { K_F6, NSF6FunctionKey },
2273         { K_F7, NSF7FunctionKey },
2274         { K_F8, NSF8FunctionKey },
2275         { K_F9, NSF9FunctionKey },
2276         { K_F10, NSF10FunctionKey },
2277         { K_F11, NSF11FunctionKey },
2278         { K_F12, NSF12FunctionKey },
2279         { K_F13, NSF13FunctionKey },
2280         { K_F14, NSF14FunctionKey },
2281         { K_F15, NSF15FunctionKey },
2282         { K_F16, NSF16FunctionKey },
2283         { K_F17, NSF17FunctionKey },
2284         { K_F18, NSF18FunctionKey },
2285         { K_F19, NSF19FunctionKey },
2286         { K_F20, NSF20FunctionKey },
2287         { K_F21, NSF21FunctionKey },
2288         { K_F22, NSF22FunctionKey },
2289         { K_F23, NSF23FunctionKey },
2290         { K_F24, NSF24FunctionKey },
2291         { K_F25, NSF25FunctionKey },
2292         { K_F26, NSF26FunctionKey },
2293         { K_F27, NSF27FunctionKey },
2294         { K_F28, NSF28FunctionKey },
2295         { K_F29, NSF29FunctionKey },
2296         { K_F30, NSF30FunctionKey },
2297         { K_F31, NSF31FunctionKey },
2298         { K_F32, NSF32FunctionKey },
2299         { K_F33, NSF33FunctionKey },
2300         { K_F34, NSF34FunctionKey },
2301         { K_F35, NSF35FunctionKey },
2302         { K_DEL, NSBackspaceCharacter },
2303         { K_BS, NSDeleteCharacter },
2304         { K_HOME, NSHomeFunctionKey },
2305         { K_END, NSEndFunctionKey },
2306         { K_PAGEUP, NSPageUpFunctionKey },
2307         { K_PAGEDOWN, NSPageDownFunctionKey }
2308     };
2310     int i;
2311     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2312         if (sp2ns[i].special == key)
2313             return sp2ns[i].nskey;
2314     }
2316     return 0;