Add eval & add input methods to MacVim, apply to file open
[MacVim.git] / src / MacVim / MMBackend.m
blob4d77f760d880a8e3eb13dc6af311bdcc8db43aab
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)processInputQueue;
60 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
61 + (NSDictionary *)specialKeys;
62 - (void)handleInsertText:(NSData *)data;
63 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
64 - (void)queueMessage:(int)msgid data:(NSData *)data;
65 - (void)connectionDidDie:(NSNotification *)notification;
66 - (void)blinkTimerFired:(NSTimer *)timer;
67 - (void)focusChange:(BOOL)on;
68 - (void)handleToggleToolbar;
69 - (void)handleScrollbarEvent:(NSData *)data;
70 - (void)handleSetFont:(NSData *)data;
71 - (void)handleDropFiles:(NSData *)data;
72 - (void)handleDropString:(NSData *)data;
73 - (BOOL)checkForModifiedBuffers;
74 - (void)addInput:(NSString *)input;
75 @end
79 @interface MMBackend (ClientServer)
80 - (NSString *)connectionNameFromServerName:(NSString *)name;
81 - (NSConnection *)connectionForServerName:(NSString *)name;
82 - (NSConnection *)connectionForServerPort:(int)port;
83 - (void)serverConnectionDidDie:(NSNotification *)notification;
84 - (void)addClient:(NSDistantObject *)client;
85 - (NSString *)alternateServerNameForName:(NSString *)name;
86 @end
90 @implementation MMBackend
92 + (MMBackend *)sharedInstance
94     static MMBackend *singleton = nil;
95     return singleton ? singleton : (singleton = [MMBackend new]);
98 - (id)init
100     if ((self = [super init])) {
101         fontContainerRef = loadFonts();
103         outputQueue = [[NSMutableArray alloc] init];
104         inputQueue = [[NSMutableArray alloc] init];
105         drawData = [[NSMutableData alloc] initWithCapacity:1024];
106         connectionNameDict = [[NSMutableDictionary alloc] init];
107         clientProxyDict = [[NSMutableDictionary alloc] init];
108         serverReplyDict = [[NSMutableDictionary alloc] init];
110         NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
111                                                          ofType:@"plist"];
112         if (path) {
113             colorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
114                 retain];
115         } else {
116             NSLog(@"WARNING: Could not locate Colors.plist.");
117         }
119         path = [[NSBundle mainBundle] pathForResource:@"SystemColors"
120                                                ofType:@"plist"];
121         if (path) {
122             sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
123                 retain];
124         } else {
125             NSLog(@"WARNING: Could not locate SystemColors.plist.");
126         }
127     }
129     return self;
132 - (void)dealloc
134     //NSLog(@"%@ %s", [self className], _cmd);
135     [[NSNotificationCenter defaultCenter] removeObserver:self];
137     [blinkTimer release];  blinkTimer = nil;
138     [alternateServerName release];  alternateServerName = nil;
139     [serverReplyDict release];  serverReplyDict = nil;
140     [clientProxyDict release];  clientProxyDict = nil;
141     [connectionNameDict release];  connectionNameDict = nil;
142     [inputQueue release];  inputQueue = nil;
143     [outputQueue release];  outputQueue = nil;
144     [drawData release];  drawData = nil;
145     [frontendProxy release];  frontendProxy = nil;
146     [connection release];  connection = nil;
147     [sysColorDict release];  sysColorDict = nil;
148     [colorDict release];  colorDict = nil;
150     [super dealloc];
153 - (void)setBackgroundColor:(int)color
155     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
158 - (void)setForegroundColor:(int)color
160     foregroundColor = MM_COLOR(color);
163 - (void)setSpecialColor:(int)color
165     specialColor = MM_COLOR(color);
168 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
170     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
171     defaultForegroundColor = MM_COLOR(fg);
173     NSMutableData *data = [NSMutableData data];
175     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
176     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
178     [self queueMessage:SetDefaultColorsMsgID data:data];
181 - (NSConnection *)connection
183     if (!connection) {
184         // NOTE!  If the name of the connection changes here it must also be
185         // updated in MMAppController.m.
186         NSString *name = [NSString stringWithFormat:@"%@-connection",
187                [[NSBundle mainBundle] bundleIdentifier]];
189         connection = [NSConnection connectionWithRegisteredName:name host:nil];
190         [connection retain];
191     }
193     // NOTE: 'connection' may be nil here.
194     return connection;
197 - (BOOL)checkin
199     if (![self connection]) {
200         NSBundle *mainBundle = [NSBundle mainBundle];
201 #if 0
202         NSString *path = [mainBundle bundlePath];
203         if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
204             NSLog(@"WARNING: Failed to launch GUI with path %@", path);
205             return NO;
206         }
207 #else
208         // HACK!  It would be preferable to launch the GUI using NSWorkspace,
209         // however I have not managed to figure out how to pass arguments using
210         // NSWorkspace.
211         //
212         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
213         // that the GUI won't be activated (or raised) so there is a hack in
214         // MMWindowController which always raises the app when a new window is
215         // opened.
216         NSMutableArray *args = [NSMutableArray arrayWithObjects:
217             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
218         NSString *exeName = [[mainBundle infoDictionary]
219                 objectForKey:@"CFBundleExecutable"];
220         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
221         if (!path) {
222             NSLog(@"ERROR: Could not find MacVim executable in bundle");
223             return NO;
224         }
226         [NSTask launchedTaskWithLaunchPath:path arguments:args];
227 #endif
229         // HACK!  The NSWorkspaceDidLaunchApplicationNotification does not work
230         // for tasks like this, so poll the mach bootstrap server until it
231         // returns a valid connection.  Also set a time-out date so that we
232         // don't get stuck doing this forever.
233         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
234         while (!connection &&
235                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
236         {
237             [[NSRunLoop currentRunLoop]
238                     runMode:NSDefaultRunLoopMode
239                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
241             // NOTE: This call will set 'connection' as a side-effect.
242             [self connection];
243         }
245         if (!connection) {
246             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
247             return NO;
248         }
249     }
251     id proxy = [connection rootProxy];
252     [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
254     [[NSNotificationCenter defaultCenter] addObserver:self
255             selector:@selector(connectionDidDie:)
256                 name:NSConnectionDidDieNotification object:connection];
258     int pid = [[NSProcessInfo processInfo] processIdentifier];
260     @try {
261         frontendProxy = [proxy connectBackend:self pid:pid];
262     }
263     @catch (NSException *e) {
264         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
265     }
267     if (frontendProxy) {
268         [frontendProxy retain];
269         [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
270     }
272     return connection && frontendProxy;
275 - (BOOL)openVimWindow
277     [self queueMessage:OpenVimWindowMsgID data:nil];
278     return YES;
281 - (void)clearAll
283     int type = ClearAllDrawType;
285     // Any draw commands in queue are effectively obsolete since this clearAll
286     // will negate any effect they have, therefore we may as well clear the
287     // draw queue.
288     [drawData setLength:0];
290     [drawData appendBytes:&type length:sizeof(int)];
293 - (void)clearBlockFromRow:(int)row1 column:(int)col1
294                     toRow:(int)row2 column:(int)col2
296     int type = ClearBlockDrawType;
298     [drawData appendBytes:&type length:sizeof(int)];
300     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
301     [drawData appendBytes:&row1 length:sizeof(int)];
302     [drawData appendBytes:&col1 length:sizeof(int)];
303     [drawData appendBytes:&row2 length:sizeof(int)];
304     [drawData appendBytes:&col2 length:sizeof(int)];
307 - (void)deleteLinesFromRow:(int)row count:(int)count
308               scrollBottom:(int)bottom left:(int)left right:(int)right
310     int type = DeleteLinesDrawType;
312     [drawData appendBytes:&type length:sizeof(int)];
314     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
315     [drawData appendBytes:&row length:sizeof(int)];
316     [drawData appendBytes:&count length:sizeof(int)];
317     [drawData appendBytes:&bottom length:sizeof(int)];
318     [drawData appendBytes:&left length:sizeof(int)];
319     [drawData appendBytes:&right length:sizeof(int)];
322 - (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
323                 flags:(int)flags
325     if (len <= 0) return;
327     int type = ReplaceStringDrawType;
329     [drawData appendBytes:&type length:sizeof(int)];
331     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
332     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
333     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
334     [drawData appendBytes:&row length:sizeof(int)];
335     [drawData appendBytes:&col length:sizeof(int)];
336     [drawData appendBytes:&flags length:sizeof(int)];
337     [drawData appendBytes:&len length:sizeof(int)];
338     [drawData appendBytes:s length:len];
341 - (void)insertLinesFromRow:(int)row count:(int)count
342               scrollBottom:(int)bottom left:(int)left right:(int)right
344     int type = InsertLinesDrawType;
346     [drawData appendBytes:&type length:sizeof(int)];
348     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
349     [drawData appendBytes:&row length:sizeof(int)];
350     [drawData appendBytes:&count length:sizeof(int)];
351     [drawData appendBytes:&bottom length:sizeof(int)];
352     [drawData appendBytes:&left length:sizeof(int)];
353     [drawData appendBytes:&right length:sizeof(int)];
356 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
357                fraction:(int)percent color:(int)color
359     int type = DrawCursorDrawType;
360     unsigned uc = MM_COLOR(color);
362     [drawData appendBytes:&type length:sizeof(int)];
364     [drawData appendBytes:&uc length:sizeof(unsigned)];
365     [drawData appendBytes:&row length:sizeof(int)];
366     [drawData appendBytes:&col length:sizeof(int)];
367     [drawData appendBytes:&shape length:sizeof(int)];
368     [drawData appendBytes:&percent length:sizeof(int)];
371 - (void)update
373     // Tend to the run loop, returning immediately if there are no events
374     // waiting.
375     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
376                              beforeDate:[NSDate distantPast]];
378     // Keyboard and mouse input is handled directly, other input is queued and
379     // processed here.  This call may enter a blocking loop.
380     if ([inputQueue count] > 0)
381         [self processInputQueue];
384 - (void)flushQueue:(BOOL)force
386     // NOTE! This method gets called a lot; if we were to flush every time it
387     // got called MacVim would feel unresponsive.  So there is a time out which
388     // ensures that the queue isn't flushed too often.
389     if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
390             < MMFlushTimeoutInterval
391             && [drawData length] < MMFlushQueueLenHint)
392         return;
394     if ([drawData length] > 0) {
395         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
396         [drawData setLength:0];
397     }
399     if ([outputQueue count] > 0) {
400         @try {
401             [frontendProxy processCommandQueue:outputQueue];
402         }
403         @catch (NSException *e) {
404             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
405         }
407         [outputQueue removeAllObjects];
409         [lastFlushDate release];
410         lastFlushDate = [[NSDate date] retain];
411     }
414 - (BOOL)waitForInput:(int)milliseconds
416     //NSLog(@"|ENTER| %s%d",  _cmd, milliseconds);
417     NSDate *date = milliseconds > 0 ?
418             [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
419             [NSDate distantFuture];
421     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
423     // I know of no way to figure out if the run loop exited because input was
424     // found or because of a time out, so I need to manually indicate when
425     // input was received in processInput:data: and then reset it every time
426     // here.
427     BOOL yn = inputReceived;
428     inputReceived = NO;
430     if ([inputQueue count] > 0)
431         [self processInputQueue];
433     //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
434     return yn;
437 - (void)exit
439 #ifdef MAC_CLIENTSERVER
440     // The default connection is used for the client/server code.
441     [[NSConnection defaultConnection] setRootObject:nil];
442     [[NSConnection defaultConnection] invalidate];
443 #endif
445     // By invalidating the NSConnection the MMWindowController immediately
446     // finds out that the connection is down and as a result
447     // [MMWindowController connectionDidDie:] is invoked.
448     //NSLog(@"%@ %s", [self className], _cmd);
449     [[NSNotificationCenter defaultCenter] removeObserver:self];
450     [connection invalidate];
452     if (fontContainerRef) {
453         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
454         fontContainerRef = 0;
455     }
459 - (void)selectTab:(int)index
461     //NSLog(@"%s%d", _cmd, index);
463     index -= 1;
464     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
465     [self queueMessage:SelectTabMsgID data:data];
468 - (void)updateTabBar
470     //NSLog(@"%s", _cmd);
472     NSMutableData *data = [NSMutableData data];
474     int idx = tabpage_index(curtab) - 1;
475     [data appendBytes:&idx length:sizeof(int)];
477     tabpage_T *tp;
478     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
479         // This function puts the label of the tab in the global 'NameBuff'.
480         get_tabline_label(tp, FALSE);
481         char_u *s = NameBuff;
482         int len = STRLEN(s);
483         if (len <= 0) continue;
485 #ifdef FEAT_MBYTE
486         s = CONVERT_TO_UTF8(s);
487 #endif
489         // Count the number of windows in the tabpage.
490         //win_T *wp = tp->tp_firstwin;
491         //int wincount;
492         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
494         //[data appendBytes:&wincount length:sizeof(int)];
495         [data appendBytes:&len length:sizeof(int)];
496         [data appendBytes:s length:len];
498 #ifdef FEAT_MBYTE
499         CONVERT_TO_UTF8_FREE(s);
500 #endif
501     }
503     [self queueMessage:UpdateTabBarMsgID data:data];
506 - (BOOL)tabBarVisible
508     return tabBarVisible;
511 - (void)showTabBar:(BOOL)enable
513     tabBarVisible = enable;
515     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
516     [self queueMessage:msgid data:nil];
519 - (void)setRows:(int)rows columns:(int)cols
521     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
523     int dim[] = { rows, cols };
524     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
526     [self queueMessage:SetTextDimensionsMsgID data:data];
529 - (void)setWindowTitle:(char *)title
531     NSMutableData *data = [NSMutableData data];
532     int len = strlen(title);
533     if (len <= 0) return;
535     [data appendBytes:&len length:sizeof(int)];
536     [data appendBytes:title length:len];
538     [self queueMessage:SetWindowTitleMsgID data:data];
541 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
542                             saving:(int)saving
544     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
545     //        saving);
547     char_u *s = NULL;
548     NSString *ds = dir ? [NSString stringWithUTF8String:dir] : nil;
549     NSString *ts = title ? [NSString stringWithUTF8String:title] : nil;
550     @try {
551         [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
553         // Wait until a reply is sent from MMVimController.
554         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
555                                  beforeDate:[NSDate distantFuture]];
557         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
558             char_u *ret = (char_u*)[dialogReturn UTF8String];
559 #ifdef FEAT_MBYTE
560             ret = CONVERT_FROM_UTF8(ret);
561 #endif
562             s = vim_strsave(ret);
563 #ifdef FEAT_MBYTE
564             CONVERT_FROM_UTF8_FREE(ret);
565 #endif
566         }
568         [dialogReturn release];  dialogReturn = nil;
569     }
570     @catch (NSException *e) {
571         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
572     }
574     return (char *)s;
577 - (oneway void)setDialogReturn:(in bycopy id)obj
579     // NOTE: This is called by
580     //   - [MMVimController panelDidEnd:::], and
581     //   - [MMVimController alertDidEnd:::],
582     // to indicate that a save/open panel or alert has finished.
584     if (obj != dialogReturn) {
585         [dialogReturn release];
586         dialogReturn = [obj retain];
587     }
590 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
591                      buttons:(char *)btns textField:(char *)txtfield
593     int retval = 0;
594     NSString *message = nil, *text = nil, *textFieldString = nil;
595     NSArray *buttons = nil;
596     int style = NSInformationalAlertStyle;
598     if (VIM_WARNING == type) style = NSWarningAlertStyle;
599     else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
601     if (btns) {
602         NSString *btnString = [NSString stringWithUTF8String:btns];
603         buttons = [btnString componentsSeparatedByString:@"\n"];
604     }
605     if (title)
606         message = [NSString stringWithUTF8String:title];
607     if (msg) {
608         text = [NSString stringWithUTF8String:msg];
609         if (!message) {
610             // HACK! If there is a '\n\n' or '\n' sequence in the message, then
611             // make the part up to there into the title.  We only do this
612             // because Vim has lots of dialogs without a title and they look
613             // ugly that way.
614             // TODO: Fix the actual dialog texts.
615             NSRange eolRange = [text rangeOfString:@"\n\n"];
616             if (NSNotFound == eolRange.location)
617                 eolRange = [text rangeOfString:@"\n"];
618             if (NSNotFound != eolRange.location) {
619                 message = [text substringToIndex:eolRange.location];
620                 text = [text substringFromIndex:NSMaxRange(eolRange)];
621             }
622         }
623     }
624     if (txtfield)
625         textFieldString = [NSString stringWithUTF8String:txtfield];
627     @try {
628         [frontendProxy presentDialogWithStyle:style message:message
629                               informativeText:text buttonTitles:buttons
630                               textFieldString:textFieldString];
632         // Wait until a reply is sent from MMVimController.
633         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
634                                  beforeDate:[NSDate distantFuture]];
636         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
637                 && [dialogReturn count]) {
638             retval = [[dialogReturn objectAtIndex:0] intValue];
639             if (txtfield && [dialogReturn count] > 1) {
640                 NSString *retString = [dialogReturn objectAtIndex:1];
641                 char_u *ret = (char_u*)[retString UTF8String];
642 #ifdef FEAT_MBYTE
643                 ret = CONVERT_FROM_UTF8(ret);
644 #endif
645                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
646 #ifdef FEAT_MBYTE
647                 CONVERT_FROM_UTF8_FREE(ret);
648 #endif
649             }
650         }
652         [dialogReturn release]; dialogReturn = nil;
653     }
654     @catch (NSException *e) {
655         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
656     }
658     return retval;
661 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
662                atIndex:(int)index
664     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
665     //        name, index);
667     int namelen = name ? strlen(name) : 0;
668     NSMutableData *data = [NSMutableData data];
670     [data appendBytes:&tag length:sizeof(int)];
671     [data appendBytes:&parentTag length:sizeof(int)];
672     [data appendBytes:&namelen length:sizeof(int)];
673     if (namelen > 0) [data appendBytes:name length:namelen];
674     [data appendBytes:&index length:sizeof(int)];
676     [self queueMessage:AddMenuMsgID data:data];
679 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
680                        tip:(char *)tip icon:(char *)icon
681              keyEquivalent:(int)key modifiers:(int)mods
682                     action:(NSString *)action atIndex:(int)index
684     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
685     //        parentTag, name, tip, index);
687     int namelen = name ? strlen(name) : 0;
688     int tiplen = tip ? strlen(tip) : 0;
689     int iconlen = icon ? strlen(icon) : 0;
690     int eventFlags = vimModMaskToEventModifierFlags(mods);
691     int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
692     NSMutableData *data = [NSMutableData data];
694     key = specialKeyToNSKey(key);
696     [data appendBytes:&tag length:sizeof(int)];
697     [data appendBytes:&parentTag length:sizeof(int)];
698     [data appendBytes:&namelen length:sizeof(int)];
699     if (namelen > 0) [data appendBytes:name length:namelen];
700     [data appendBytes:&tiplen length:sizeof(int)];
701     if (tiplen > 0) [data appendBytes:tip length:tiplen];
702     [data appendBytes:&iconlen length:sizeof(int)];
703     if (iconlen > 0) [data appendBytes:icon length:iconlen];
704     [data appendBytes:&actionlen length:sizeof(int)];
705     if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
706     [data appendBytes:&index length:sizeof(int)];
707     [data appendBytes:&key length:sizeof(int)];
708     [data appendBytes:&eventFlags length:sizeof(int)];
710     [self queueMessage:AddMenuItemMsgID data:data];
713 - (void)removeMenuItemWithTag:(int)tag
715     NSMutableData *data = [NSMutableData data];
716     [data appendBytes:&tag length:sizeof(int)];
718     [self queueMessage:RemoveMenuItemMsgID data:data];
721 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
723     NSMutableData *data = [NSMutableData data];
725     [data appendBytes:&tag length:sizeof(int)];
726     [data appendBytes:&enabled length:sizeof(int)];
728     [self queueMessage:EnableMenuItemMsgID data:data];
731 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
733     int len = strlen(name);
734     int row = -1, col = -1;
736     if (len <= 0) return;
738     if (!mouse && curwin) {
739         row = curwin->w_wrow;
740         col = curwin->w_wcol;
741     }
743     NSMutableData *data = [NSMutableData data];
745     [data appendBytes:&row length:sizeof(int)];
746     [data appendBytes:&col length:sizeof(int)];
747     [data appendBytes:&len length:sizeof(int)];
748     [data appendBytes:name length:len];
750     [self queueMessage:ShowPopupMenuMsgID data:data];
753 - (void)showToolbar:(int)enable flags:(int)flags
755     NSMutableData *data = [NSMutableData data];
757     [data appendBytes:&enable length:sizeof(int)];
758     [data appendBytes:&flags length:sizeof(int)];
760     [self queueMessage:ShowToolbarMsgID data:data];
763 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
765     NSMutableData *data = [NSMutableData data];
767     [data appendBytes:&ident length:sizeof(long)];
768     [data appendBytes:&type length:sizeof(int)];
770     [self queueMessage:CreateScrollbarMsgID data:data];
773 - (void)destroyScrollbarWithIdentifier:(long)ident
775     NSMutableData *data = [NSMutableData data];
776     [data appendBytes:&ident length:sizeof(long)];
778     [self queueMessage:DestroyScrollbarMsgID data:data];
781 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
783     NSMutableData *data = [NSMutableData data];
785     [data appendBytes:&ident length:sizeof(long)];
786     [data appendBytes:&visible length:sizeof(int)];
788     [self queueMessage:ShowScrollbarMsgID data:data];
791 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
793     NSMutableData *data = [NSMutableData data];
795     [data appendBytes:&ident length:sizeof(long)];
796     [data appendBytes:&pos length:sizeof(int)];
797     [data appendBytes:&len length:sizeof(int)];
799     [self queueMessage:SetScrollbarPositionMsgID data:data];
802 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
803                     identifier:(long)ident
805     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
806     float prop = (float)size/(max+1);
807     if (fval < 0) fval = 0;
808     else if (fval > 1.0f) fval = 1.0f;
809     if (prop < 0) prop = 0;
810     else if (prop > 1.0f) prop = 1.0f;
812     NSMutableData *data = [NSMutableData data];
814     [data appendBytes:&ident length:sizeof(long)];
815     [data appendBytes:&fval length:sizeof(float)];
816     [data appendBytes:&prop length:sizeof(float)];
818     [self queueMessage:SetScrollbarThumbMsgID data:data];
821 - (BOOL)setFontWithName:(char *)name
823     NSString *fontName = MMDefaultFontName;
824     float size = MMDefaultFontSize;
825     BOOL parseFailed = NO;
827     if (name) {
828         fontName = [NSString stringWithUTF8String:name];
830         if ([fontName isEqual:@"*"]) {
831             // :set gfn=* shows the font panel.
832             do_cmdline_cmd((char_u*)":macaction orderFrontFontPanel:");
833             return NO;
834         }
836         NSArray *components = [fontName componentsSeparatedByString:@":"];
837         if ([components count] == 2) {
838             NSString *sizeString = [components lastObject];
839             if ([sizeString length] > 0
840                     && [sizeString characterAtIndex:0] == 'h') {
841                 sizeString = [sizeString substringFromIndex:1];
842                 if ([sizeString length] > 0) {
843                     size = [sizeString floatValue];
844                     fontName = [components objectAtIndex:0];
845                 }
846             } else {
847                 parseFailed = YES;
848             }
849         } else if ([components count] > 2) {
850             parseFailed = YES;
851         }
853         if (!parseFailed) {
854             // Replace underscores with spaces.
855             fontName = [[fontName componentsSeparatedByString:@"_"]
856                                      componentsJoinedByString:@" "];
857         }
858     }
860     if (!parseFailed && [fontName length] > 0) {
861         if (size < 6 || size > 100) {
862             // Font size 0.0 tells NSFont to use the 'user default size'.
863             size = 0.0f;
864         }
866         NSFont *font = [NSFont fontWithName:fontName size:size];
868         if (!font && MMDefaultFontName == fontName) {
869             // If for some reason the MacVim default font is not in the app
870             // bundle, then fall back on the system default font.
871             size = 0;
872             font = [NSFont userFixedPitchFontOfSize:size];
873             fontName = [font displayName];
874         }
876         if (font) {
877             //NSLog(@"Setting font '%@' of size %.2f", fontName, size);
878             int len = [fontName
879                     lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
880             if (len > 0) {
881                 NSMutableData *data = [NSMutableData data];
883                 [data appendBytes:&size length:sizeof(float)];
884                 [data appendBytes:&len length:sizeof(int)];
885                 [data appendBytes:[fontName UTF8String] length:len];
887                 [self queueMessage:SetFontMsgID data:data];
888                 return YES;
889             }
890         }
891     }
893     //NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f",
894     //        fontName, size);
895     return NO;
898 - (void)executeActionWithName:(NSString *)name
900     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
902     if (len > 0) {
903         NSMutableData *data = [NSMutableData data];
905         [data appendBytes:&len length:sizeof(int)];
906         [data appendBytes:[name UTF8String] length:len];
908         [self queueMessage:ExecuteActionMsgID data:data];
909     }
912 - (void)setMouseShape:(int)shape
914     NSMutableData *data = [NSMutableData data];
915     [data appendBytes:&shape length:sizeof(int)];
916     [self queueMessage:SetMouseShapeMsgID data:data];
919 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
921     // Vim specifies times in milliseconds, whereas Cocoa wants them in
922     // seconds.
923     blinkWaitInterval = .001f*wait;
924     blinkOnInterval = .001f*on;
925     blinkOffInterval = .001f*off;
928 - (void)startBlink
930     if (blinkTimer) {
931         [blinkTimer invalidate];
932         [blinkTimer release];
933         blinkTimer = nil;
934     }
936     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
937             && gui.in_focus) {
938         blinkState = MMBlinkStateOn;
939         blinkTimer =
940             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
941                                               target:self
942                                             selector:@selector(blinkTimerFired:)
943                                             userInfo:nil repeats:NO] retain];
944         gui_update_cursor(TRUE, FALSE);
945         [self flushQueue:YES];
946     }
949 - (void)stopBlink
951     if (MMBlinkStateOff == blinkState) {
952         gui_update_cursor(TRUE, FALSE);
953         [self flushQueue:YES];
954     }
956     blinkState = MMBlinkStateNone;
959 - (void)adjustLinespace:(int)linespace
961     NSMutableData *data = [NSMutableData data];
962     [data appendBytes:&linespace length:sizeof(int)];
963     [self queueMessage:AdjustLinespaceMsgID data:data];
966 - (void)activate
968     [self queueMessage:ActivateMsgID data:nil];
971 - (int)lookupColorWithKey:(NSString *)key
973     if (!(key && [key length] > 0))
974         return INVALCOLOR;
976     NSString *stripKey = [[[[key lowercaseString]
977         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
978             componentsSeparatedByString:@" "]
979                componentsJoinedByString:@""];
981     if (stripKey && [stripKey length] > 0) {
982         // First of all try to lookup key in the color dictionary; note that
983         // all keys in this dictionary are lowercase with no whitespace.
984         id obj = [colorDict objectForKey:stripKey];
985         if (obj) return [obj intValue];
987         // The key was not in the dictionary; is it perhaps of the form
988         // #rrggbb?
989         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
990             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
991             [scanner setScanLocation:1];
992             unsigned hex = 0;
993             if ([scanner scanHexInt:&hex]) {
994                 return (int)hex;
995             }
996         }
998         // As a last resort, check if it is one of the system defined colors.
999         // The keys in this dictionary are also lowercase with no whitespace.
1000         obj = [sysColorDict objectForKey:stripKey];
1001         if (obj) {
1002             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
1003             if (col) {
1004                 float r, g, b, a;
1005                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
1006                 [col getRed:&r green:&g blue:&b alpha:&a];
1007                 return (((int)(r*255+.5f) & 0xff) << 16)
1008                      + (((int)(g*255+.5f) & 0xff) << 8)
1009                      +  ((int)(b*255+.5f) & 0xff);
1010             }
1011         }
1012     }
1014     NSLog(@"WARNING: No color with key %@ found.", stripKey);
1015     return INVALCOLOR;
1018 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
1020     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1021     id obj;
1023     while ((obj = [e nextObject])) {
1024         if ([value isEqual:obj])
1025             return YES;
1026     }
1028     return NO;
1031 - (void)enterFullscreen
1033     [self queueMessage:EnterFullscreenMsgID data:nil];
1036 - (void)leaveFullscreen
1038     [self queueMessage:LeaveFullscreenMsgID data:nil];
1041 - (void)updateModifiedFlag
1043     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1044     // vice versa.
1045     int msgid = [self checkForModifiedBuffers]
1046             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1048     [self queueMessage:msgid data:nil];
1051 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1053     // NOTE: This method might get called whenever the run loop is tended to.
1054     // Normal keyboard and mouse input is added to input buffers, so there is
1055     // no risk in handling these events directly (they return immediately, and
1056     // do not call any other Vim functions).  However, other events such
1057     // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
1058     // events which would cause this method to be called recursively.  This
1059     // in turn leads to various difficulties that we do not want to have to
1060     // deal with.  To avoid recursive calls here we add all events except
1061     // keyboard and mouse events to an input queue which is processed whenever
1062     // gui_mch_update() is called (see processInputQueue).
1064     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1066     // Don't flush too soon after receiving input or update speed will suffer.
1067     [lastFlushDate release];
1068     lastFlushDate = [[NSDate date] retain];
1070     // Handle keyboard and mouse input now.  All other events are queued.
1071     if (InsertTextMsgID == msgid) {
1072         [self handleInsertText:data];
1073     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1074         if (!data) return;
1075         const void *bytes = [data bytes];
1076         int mods = *((int*)bytes);  bytes += sizeof(int);
1077         int len = *((int*)bytes);  bytes += sizeof(int);
1078         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1079                                               encoding:NSUTF8StringEncoding];
1080         mods = eventModifierFlagsToVimModMask(mods);
1082         [self handleKeyDown:key modifiers:mods];
1084         [key release];
1085     } else if (ScrollWheelMsgID == msgid) {
1086         if (!data) return;
1087         const void *bytes = [data bytes];
1089         int row = *((int*)bytes);  bytes += sizeof(int);
1090         int col = *((int*)bytes);  bytes += sizeof(int);
1091         int flags = *((int*)bytes);  bytes += sizeof(int);
1092         float dy = *((float*)bytes);  bytes += sizeof(float);
1094         int button = MOUSE_5;
1095         if (dy > 0) button = MOUSE_4;
1097         flags = eventModifierFlagsToVimMouseModMask(flags);
1099         gui_send_mouse_event(button, col, row, NO, flags);
1100     } else if (MouseDownMsgID == msgid) {
1101         if (!data) return;
1102         const void *bytes = [data bytes];
1104         int row = *((int*)bytes);  bytes += sizeof(int);
1105         int col = *((int*)bytes);  bytes += sizeof(int);
1106         int button = *((int*)bytes);  bytes += sizeof(int);
1107         int flags = *((int*)bytes);  bytes += sizeof(int);
1108         int count = *((int*)bytes);  bytes += sizeof(int);
1110         button = eventButtonNumberToVimMouseButton(button);
1111         if (button >= 0) {
1112             flags = eventModifierFlagsToVimMouseModMask(flags);
1113             gui_send_mouse_event(button, col, row, count>1, flags);
1114         }
1115     } else if (MouseUpMsgID == msgid) {
1116         if (!data) return;
1117         const void *bytes = [data bytes];
1119         int row = *((int*)bytes);  bytes += sizeof(int);
1120         int col = *((int*)bytes);  bytes += sizeof(int);
1121         int flags = *((int*)bytes);  bytes += sizeof(int);
1123         flags = eventModifierFlagsToVimMouseModMask(flags);
1125         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1126     } else if (MouseDraggedMsgID == msgid) {
1127         if (!data) return;
1128         const void *bytes = [data bytes];
1130         int row = *((int*)bytes);  bytes += sizeof(int);
1131         int col = *((int*)bytes);  bytes += sizeof(int);
1132         int flags = *((int*)bytes);  bytes += sizeof(int);
1134         flags = eventModifierFlagsToVimMouseModMask(flags);
1136         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1137     } else if (MouseMovedMsgID == msgid) {
1138         const void *bytes = [data bytes];
1139         int row = *((int*)bytes);  bytes += sizeof(int);
1140         int col = *((int*)bytes);  bytes += sizeof(int);
1142         gui_mouse_moved(col, row);
1143     } else if (AddInputMsgID == msgid) {
1144         NSString *string = [[NSString alloc] initWithData:data
1145                 encoding:NSUTF8StringEncoding];
1146         if (string) {
1147             [self addInput:string];
1148             [string release];
1149         }
1150     } else {
1151         // Not keyboard or mouse event, queue it and handle later.
1152         //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
1153         [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1154         [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
1155     }
1157     // See waitForInput: for an explanation of this flag.
1158     inputReceived = YES;
1161 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1163     // TODO: Get rid of this method?
1164     //NSLog(@"%s%@", _cmd, messages);
1166     unsigned i, count = [messages count];
1167     for (i = 0; i < count; i += 2) {
1168         int msgid = [[messages objectAtIndex:i] intValue];
1169         id data = [messages objectAtIndex:i+1];
1170         if ([data isEqual:[NSNull null]])
1171             data = nil;
1173         [self processInput:msgid data:data];
1174     }
1177 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1179     NSString *eval = nil;
1180     char_u *s = (char_u*)[expr UTF8String];
1182 #ifdef FEAT_MBYTE
1183     s = CONVERT_FROM_UTF8(s);
1184 #endif
1186     char_u *res = eval_client_expr_to_string(s);
1188 #ifdef FEAT_MBYTE
1189     CONVERT_FROM_UTF8_FREE(s);
1190 #endif
1192     if (res != NULL) {
1193         s = res;
1194 #ifdef FEAT_MBYTE
1195         s = CONVERT_TO_UTF8(s);
1196 #endif
1197         eval = [NSString stringWithUTF8String:(char*)s];
1198 #ifdef FEAT_MBYTE
1199         CONVERT_TO_UTF8_FREE(s);
1200 #endif
1201         vim_free(res);
1202     }
1204     return eval;
1207 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1209     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1210         // If there is no pasteboard, return YES to indicate that there is text
1211         // to copy.
1212         if (!pboard)
1213             return YES;
1215         clip_copy_selection();
1217         // Get the text to put on the pasteboard.
1218         long_u llen = 0; char_u *str = 0;
1219         int type = clip_convert_selection(&str, &llen, &clip_star);
1220         if (type < 0)
1221             return NO;
1222         
1223         // TODO: Avoid overflow.
1224         int len = (int)llen;
1225 #ifdef FEAT_MBYTE
1226         if (output_conv.vc_type != CONV_NONE) {
1227             char_u *conv_str = string_convert(&output_conv, str, &len);
1228             if (conv_str) {
1229                 vim_free(str);
1230                 str = conv_str;
1231             }
1232         }
1233 #endif
1235         NSString *string = [[NSString alloc]
1236             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1238         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1239         [pboard declareTypes:types owner:nil];
1240         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1241     
1242         [string release];
1243         vim_free(str);
1245         return ok;
1246     }
1248     return NO;
1251 - (oneway void)addReply:(in bycopy NSString *)reply
1252                  server:(in byref id <MMVimServerProtocol>)server
1254     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1256     // Replies might come at any time and in any order so we keep them in an
1257     // array inside a dictionary with the send port used as key.
1259     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1260     // HACK! Assume connection uses mach ports.
1261     int port = [(NSMachPort*)[conn sendPort] machPort];
1262     NSNumber *key = [NSNumber numberWithInt:port];
1264     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1265     if (!replies) {
1266         replies = [NSMutableArray array];
1267         [serverReplyDict setObject:replies forKey:key];
1268     }
1270     [replies addObject:reply];
1273 - (void)addInput:(in bycopy NSString *)input
1274                  client:(in byref id <MMVimClientProtocol>)client
1276     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1278     [self addInput:input];
1279     [self addClient:(id)client];
1281     inputReceived = YES;
1284 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1285                  client:(in byref id <MMVimClientProtocol>)client
1287     [self addClient:(id)client];
1288     return [self evaluateExpression:expr];
1291 - (void)registerServerWithName:(NSString *)name
1293     NSString *svrName = name;
1294     NSConnection *svrConn = [NSConnection defaultConnection];
1295     unsigned i;
1297     for (i = 0; i < MMServerMax; ++i) {
1298         NSString *connName = [self connectionNameFromServerName:svrName];
1300         if ([svrConn registerName:connName]) {
1301             //NSLog(@"Registered server with name: %@", svrName);
1303             // TODO: Set request/reply time-outs to something else?
1304             //
1305             // Don't wait for requests (time-out means that the message is
1306             // dropped).
1307             [svrConn setRequestTimeout:0];
1308             //[svrConn setReplyTimeout:MMReplyTimeout];
1309             [svrConn setRootObject:self];
1311             char_u *s = (char_u*)[svrName UTF8String];
1312 #ifdef FEAT_MBYTE
1313             s = CONVERT_FROM_UTF8(s);
1314 #endif
1315             // NOTE: 'serverName' is a global variable
1316             serverName = vim_strsave(s);
1317 #ifdef FEAT_MBYTE
1318             CONVERT_FROM_UTF8_FREE(s);
1319 #endif
1320 #ifdef FEAT_EVAL
1321             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1322 #endif
1323 #ifdef FEAT_TITLE
1324             need_maketitle = TRUE;
1325 #endif
1326             [self queueMessage:SetServerNameMsgID data:
1327                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1328             break;
1329         }
1331         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1332     }
1335 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1336                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1337               silent:(BOOL)silent
1339     // NOTE: If 'name' equals 'serverName' then the request is local (client
1340     // and server are the same).  This case is not handled separately, so a
1341     // connection will be set up anyway (this simplifies the code).
1343     NSConnection *conn = [self connectionForServerName:name];
1344     if (!conn) {
1345         if (!silent) {
1346             char_u *s = (char_u*)[name UTF8String];
1347 #ifdef FEAT_MBYTE
1348             s = CONVERT_FROM_UTF8(s);
1349 #endif
1350             EMSG2(_(e_noserver), s);
1351 #ifdef FEAT_MBYTE
1352             CONVERT_FROM_UTF8_FREE(s);
1353 #endif
1354         }
1355         return NO;
1356     }
1358     if (port) {
1359         // HACK! Assume connection uses mach ports.
1360         *port = [(NSMachPort*)[conn sendPort] machPort];
1361     }
1363     id proxy = [conn rootProxy];
1364     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1366     @try {
1367         if (expr) {
1368             NSString *eval = [proxy evaluateExpression:string client:self];
1369             if (reply) {
1370                 if (eval) {
1371                     char_u *r = (char_u*)[eval UTF8String];
1372 #ifdef FEAT_MBYTE
1373                     r = CONVERT_FROM_UTF8(r);
1374 #endif
1375                     *reply = vim_strsave(r);
1376 #ifdef FEAT_MBYTE
1377                     CONVERT_FROM_UTF8_FREE(r);
1378 #endif
1379                 } else {
1380                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1381                 }
1382             }
1384             if (!eval)
1385                 return NO;
1386         } else {
1387             [proxy addInput:string client:self];
1388         }
1389     }
1390     @catch (NSException *e) {
1391         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1392         return NO;
1393     }
1395     return YES;
1398 - (NSArray *)serverList
1400     NSArray *list = nil;
1402     if ([self connection]) {
1403         id proxy = [connection rootProxy];
1404         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1406         @try {
1407             list = [proxy serverList];
1408         }
1409         @catch (NSException *e) {
1410             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1411         }
1412     } else {
1413         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1414     }
1416     return list;
1419 - (NSString *)peekForReplyOnPort:(int)port
1421     //NSLog(@"%s%d", _cmd, port);
1423     NSNumber *key = [NSNumber numberWithInt:port];
1424     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1425     if (replies && [replies count]) {
1426         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1427         //        [replies objectAtIndex:0]);
1428         return [replies objectAtIndex:0];
1429     }
1431     //NSLog(@"    No replies");
1432     return nil;
1435 - (NSString *)waitForReplyOnPort:(int)port
1437     //NSLog(@"%s%d", _cmd, port);
1438     
1439     NSConnection *conn = [self connectionForServerPort:port];
1440     if (!conn)
1441         return nil;
1443     NSNumber *key = [NSNumber numberWithInt:port];
1444     NSMutableArray *replies = nil;
1445     NSString *reply = nil;
1447     // Wait for reply as long as the connection to the server is valid (unless
1448     // user interrupts wait with Ctrl-C).
1449     while (!got_int && [conn isValid] &&
1450             !(replies = [serverReplyDict objectForKey:key])) {
1451         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1452                                  beforeDate:[NSDate distantFuture]];
1453     }
1455     if (replies) {
1456         if ([replies count] > 0) {
1457             reply = [[replies objectAtIndex:0] retain];
1458             //NSLog(@"    Got reply: %@", reply);
1459             [replies removeObjectAtIndex:0];
1460             [reply autorelease];
1461         }
1463         if ([replies count] == 0)
1464             [serverReplyDict removeObjectForKey:key];
1465     }
1467     return reply;
1470 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1472     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1473     if (client) {
1474         @try {
1475             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1476             [client addReply:reply server:self];
1477             return YES;
1478         }
1479         @catch (NSException *e) {
1480             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1481         }
1482     } else {
1483         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1484     }
1486     return NO;
1489 @end // MMBackend
1493 @implementation MMBackend (Private)
1495 - (void)processInputQueue
1497     // NOTE: One of the input events may cause this method to be called
1498     // recursively, so copy the input queue to a local variable and clear it
1499     // before starting to process input events (otherwise we could get stuck in
1500     // an endless loop).
1501     NSArray *q = [inputQueue copy];
1502     unsigned i, count = [q count];
1504     [inputQueue removeAllObjects];
1506     for (i = 0; i < count-1; i += 2) {
1507         int msgid = [[q objectAtIndex:i] intValue];
1508         id data = [q objectAtIndex:i+1];
1509         if ([data isEqual:[NSNull null]])
1510             data = nil;
1512         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1513         [self handleInputEvent:msgid data:data];
1514     }
1516     [q release];
1517     //NSLog(@"Clear input event queue");
1520 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1522     // NOTE: Be careful with what you do in this method.  Ideally, a message
1523     // should be handled by adding something to the input buffer and returning
1524     // immediately.  If you call a Vim function then it should not enter a loop
1525     // waiting for key presses or in any other way block the process.  The
1526     // reason for this being that only one message can be processed at a time,
1527     // so if another message is received while processing, then the new message
1528     // is dropped.  See also the comment in processInput:data:.
1530     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1532     if (SelectTabMsgID == msgid) {
1533         if (!data) return;
1534         const void *bytes = [data bytes];
1535         int idx = *((int*)bytes) + 1;
1536         //NSLog(@"Selecting tab %d", idx);
1537         send_tabline_event(idx);
1538     } else if (CloseTabMsgID == msgid) {
1539         if (!data) return;
1540         const void *bytes = [data bytes];
1541         int idx = *((int*)bytes) + 1;
1542         //NSLog(@"Closing tab %d", idx);
1543         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1544     } else if (AddNewTabMsgID == msgid) {
1545         //NSLog(@"Adding new tab");
1546         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1547     } else if (DraggedTabMsgID == msgid) {
1548         if (!data) return;
1549         const void *bytes = [data bytes];
1550         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1551         // based.
1552         int idx = *((int*)bytes);
1554         tabpage_move(idx);
1555     } else if (SetTextDimensionsMsgID == msgid) {
1556         if (!data) return;
1557         const void *bytes = [data bytes];
1558         int rows = *((int*)bytes);  bytes += sizeof(int);
1559         int cols = *((int*)bytes);  bytes += sizeof(int);
1561         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1562         // gui_resize_shell(), so we have to manually set the rows and columns
1563         // here.  (MacVim doesn't change the rows and columns to avoid
1564         // inconsistent states between Vim and MacVim.)
1565         [self setRows:rows columns:cols];
1567         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1568         gui_resize_shell(cols, rows);
1569     } else if (ExecuteMenuMsgID == msgid) {
1570         if (!data) return;
1571         const void *bytes = [data bytes];
1572         int tag = *((int*)bytes);  bytes += sizeof(int);
1574         vimmenu_T *menu = (vimmenu_T*)tag;
1575         // TODO!  Make sure 'menu' is a valid menu pointer!
1576         if (menu) {
1577             gui_menu_cb(menu);
1578         }
1579     } else if (ToggleToolbarMsgID == msgid) {
1580         [self handleToggleToolbar];
1581     } else if (ScrollbarEventMsgID == msgid) {
1582         [self handleScrollbarEvent:data];
1583     } else if (SetFontMsgID == msgid) {
1584         [self handleSetFont:data];
1585     } else if (VimShouldCloseMsgID == msgid) {
1586         gui_shell_closed();
1587     } else if (DropFilesMsgID == msgid) {
1588         [self handleDropFiles:data];
1589     } else if (DropStringMsgID == msgid) {
1590         [self handleDropString:data];
1591     } else if (GotFocusMsgID == msgid) {
1592         if (!gui.in_focus)
1593             [self focusChange:YES];
1594     } else if (LostFocusMsgID == msgid) {
1595         if (gui.in_focus)
1596             [self focusChange:NO];
1597     } else if (SetMouseShapeMsgID == msgid) {
1598         const void *bytes = [data bytes];
1599         int shape = *((int*)bytes);  bytes += sizeof(int);
1600         update_mouseshape(shape);
1601     } else {
1602         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1603     }
1606 + (NSDictionary *)specialKeys
1608     static NSDictionary *specialKeys = nil;
1610     if (!specialKeys) {
1611         NSBundle *mainBundle = [NSBundle mainBundle];
1612         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1613                                               ofType:@"plist"];
1614         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1615     }
1617     return specialKeys;
1620 - (void)handleInsertText:(NSData *)data
1622     if (!data) return;
1624     NSString *key = [[NSString alloc] initWithData:data
1625                                           encoding:NSUTF8StringEncoding];
1626     char_u *str = (char_u*)[key UTF8String];
1627     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1629 #ifdef FEAT_MBYTE
1630     char_u *conv_str = NULL;
1631     if (input_conv.vc_type != CONV_NONE) {
1632         conv_str = string_convert(&input_conv, str, &len);
1633         if (conv_str)
1634             str = conv_str;
1635     }
1636 #endif
1638     for (i = 0; i < len; ++i) {
1639         add_to_input_buf(str+i, 1);
1640         if (CSI == str[i]) {
1641             // NOTE: If the converted string contains the byte CSI, then it
1642             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1643             // won't work.
1644             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1645             add_to_input_buf(extra, 2);
1646         }
1647     }
1649 #ifdef FEAT_MBYTE
1650     if (conv_str)
1651         vim_free(conv_str);
1652 #endif
1653     [key release];
1656 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1658     char_u special[3];
1659     char_u modChars[3];
1660     char_u *chars = (char_u*)[key UTF8String];
1661 #ifdef FEAT_MBYTE
1662     char_u *conv_str = NULL;
1663 #endif
1664     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1666     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1667     // that new keys can easily be added.
1668     NSString *specialString = [[MMBackend specialKeys]
1669             objectForKey:key];
1670     if (specialString && [specialString length] > 1) {
1671         //NSLog(@"special key: %@", specialString);
1672         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1673                 [specialString characterAtIndex:1]);
1675         ikey = simplify_key(ikey, &mods);
1676         if (ikey == CSI)
1677             ikey = K_CSI;
1679         special[0] = CSI;
1680         special[1] = K_SECOND(ikey);
1681         special[2] = K_THIRD(ikey);
1683         chars = special;
1684         length = 3;
1685     } else if (1 == length && TAB == chars[0]) {
1686         // Tab is a trouble child:
1687         // - <Tab> is added to the input buffer as is
1688         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1689         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1690         //   to be converted to utf-8
1691         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1692         // - <C-Tab> is reserved by Mac OS X
1693         // - <D-Tab> is reserved by Mac OS X
1694         chars = special;
1695         special[0] = TAB;
1696         length = 1;
1698         if (mods & MOD_MASK_SHIFT) {
1699             mods &= ~MOD_MASK_SHIFT;
1700             special[0] = CSI;
1701             special[1] = K_SECOND(K_S_TAB);
1702             special[2] = K_THIRD(K_S_TAB);
1703             length = 3;
1704         } else if (mods & MOD_MASK_ALT) {
1705             int mtab = 0x80 | TAB;
1706 #ifdef FEAT_MBYTE
1707             if (enc_utf8) {
1708                 // Convert to utf-8
1709                 special[0] = (mtab >> 6) + 0xc0;
1710                 special[1] = mtab & 0xbf;
1711                 length = 2;
1712             } else
1713 #endif
1714             {
1715                 special[0] = mtab;
1716                 length = 1;
1717             }
1718             mods &= ~MOD_MASK_ALT;
1719         }
1720     } else if (length > 0) {
1721         unichar c = [key characterAtIndex:0];
1723         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1724         //        [key characterAtIndex:0], mods);
1726         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1727                 || (c == intr_char && intr_char != Ctrl_C))) {
1728             trash_input_buf();
1729             got_int = TRUE;
1730         }
1732         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1733         // cleared since they are already added to the key by the AppKit.
1734         // Unfortunately, the only way to deal with when to clear the modifiers
1735         // or not seems to be to have hard-wired rules like this.
1736         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1737                     || 0x9 == c || 0xd == c) ) {
1738             mods &= ~MOD_MASK_SHIFT;
1739             mods &= ~MOD_MASK_CTRL;
1740             //NSLog(@"clear shift ctrl");
1741         }
1743         // HACK!  All Option+key presses go via 'insert text' messages, except
1744         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1745         // not work to map to it.
1746         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1747             //NSLog(@"clear alt");
1748             mods &= ~MOD_MASK_ALT;
1749         }
1751 #ifdef FEAT_MBYTE
1752         if (input_conv.vc_type != CONV_NONE) {
1753             conv_str = string_convert(&input_conv, chars, &length);
1754             if (conv_str)
1755                 chars = conv_str;
1756         }
1757 #endif
1758     }
1760     if (chars && length > 0) {
1761         if (mods) {
1762             //NSLog(@"adding mods: %d", mods);
1763             modChars[0] = CSI;
1764             modChars[1] = KS_MODIFIER;
1765             modChars[2] = mods;
1766             add_to_input_buf(modChars, 3);
1767         }
1769         //NSLog(@"add to input buf: 0x%x", chars[0]);
1770         // TODO: Check for CSI bytes?
1771         add_to_input_buf(chars, length);
1772     }
1774 #ifdef FEAT_MBYTE
1775     if (conv_str)
1776         vim_free(conv_str);
1777 #endif
1780 - (void)queueMessage:(int)msgid data:(NSData *)data
1782     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1783     if (data)
1784         [outputQueue addObject:data];
1785     else
1786         [outputQueue addObject:[NSData data]];
1789 - (void)connectionDidDie:(NSNotification *)notification
1791     // If the main connection to MacVim is lost this means that MacVim was
1792     // either quit (by the user chosing Quit on the MacVim menu), or it has
1793     // crashed.  In either case our only option is to quit now.
1794     // TODO: Write backup file?
1796     //NSLog(@"A Vim process lost its connection to MacVim; quitting.");
1797     getout(0);
1800 - (void)blinkTimerFired:(NSTimer *)timer
1802     NSTimeInterval timeInterval = 0;
1804     [blinkTimer release];
1805     blinkTimer = nil;
1807     if (MMBlinkStateOn == blinkState) {
1808         gui_undraw_cursor();
1809         blinkState = MMBlinkStateOff;
1810         timeInterval = blinkOffInterval;
1811     } else if (MMBlinkStateOff == blinkState) {
1812         gui_update_cursor(TRUE, FALSE);
1813         blinkState = MMBlinkStateOn;
1814         timeInterval = blinkOnInterval;
1815     }
1817     if (timeInterval > 0) {
1818         blinkTimer = 
1819             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1820                                             selector:@selector(blinkTimerFired:)
1821                                             userInfo:nil repeats:NO] retain];
1822         [self flushQueue:YES];
1823     }
1826 - (void)focusChange:(BOOL)on
1828     gui_focus_change(on);
1831 - (void)handleToggleToolbar
1833     // If 'go' contains 'T', then remove it, else add it.
1835     char_u go[sizeof(GO_ALL)+2];
1836     char_u *p;
1837     int len;
1839     STRCPY(go, p_go);
1840     p = vim_strchr(go, GO_TOOLBAR);
1841     len = STRLEN(go);
1843     if (p != NULL) {
1844         char_u *end = go + len;
1845         while (p < end) {
1846             p[0] = p[1];
1847             ++p;
1848         }
1849     } else {
1850         go[len] = GO_TOOLBAR;
1851         go[len+1] = NUL;
1852     }
1854     set_option_value((char_u*)"guioptions", 0, go, 0);
1856     // Force screen redraw (does it have to be this complicated?).
1857     redraw_all_later(CLEAR);
1858     update_screen(NOT_VALID);
1859     setcursor();
1860     out_flush();
1861     gui_update_cursor(FALSE, FALSE);
1862     gui_mch_flush();
1865 - (void)handleScrollbarEvent:(NSData *)data
1867     if (!data) return;
1869     const void *bytes = [data bytes];
1870     long ident = *((long*)bytes);  bytes += sizeof(long);
1871     int hitPart = *((int*)bytes);  bytes += sizeof(int);
1872     float fval = *((float*)bytes);  bytes += sizeof(float);
1873     scrollbar_T *sb = gui_find_scrollbar(ident);
1875     if (sb) {
1876         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1877         long value = sb_info->value;
1878         long size = sb_info->size;
1879         long max = sb_info->max;
1880         BOOL isStillDragging = NO;
1881         BOOL updateKnob = YES;
1883         switch (hitPart) {
1884         case NSScrollerDecrementPage:
1885             value -= (size > 2 ? size - 2 : 1);
1886             break;
1887         case NSScrollerIncrementPage:
1888             value += (size > 2 ? size - 2 : 1);
1889             break;
1890         case NSScrollerDecrementLine:
1891             --value;
1892             break;
1893         case NSScrollerIncrementLine:
1894             ++value;
1895             break;
1896         case NSScrollerKnob:
1897             isStillDragging = YES;
1898             // fall through ...
1899         case NSScrollerKnobSlot:
1900             value = (long)(fval * (max - size + 1));
1901             // fall through ...
1902         default:
1903             updateKnob = NO;
1904             break;
1905         }
1907         //NSLog(@"value %d -> %d", sb_info->value, value);
1908         gui_drag_scrollbar(sb, value, isStillDragging);
1910         if (updateKnob) {
1911             // Dragging the knob or option+clicking automatically updates
1912             // the knob position (on the actual NSScroller), so we only
1913             // need to set the knob position in the other cases.
1914             if (sb->wp) {
1915                 // Update both the left&right vertical scrollbars.
1916                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1917                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1918                 [self setScrollbarThumbValue:value size:size max:max
1919                                   identifier:identLeft];
1920                 [self setScrollbarThumbValue:value size:size max:max
1921                                   identifier:identRight];
1922             } else {
1923                 // Update the horizontal scrollbar.
1924                 [self setScrollbarThumbValue:value size:size max:max
1925                                   identifier:ident];
1926             }
1927         }
1928     }
1931 - (void)handleSetFont:(NSData *)data
1933     if (!data) return;
1935     const void *bytes = [data bytes];
1936     float pointSize = *((float*)bytes);  bytes += sizeof(float);
1937     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1938     bytes += sizeof(unsigned);  // len not used
1940     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1941     [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1942     char_u *s = (char_u*)[name UTF8String];
1944 #ifdef FEAT_MBYTE
1945     s = CONVERT_FROM_UTF8(s);
1946 #endif
1948     set_option_value((char_u*)"guifont", 0, s, 0);
1950 #ifdef FEAT_MBYTE
1951     CONVERT_FROM_UTF8_FREE(s);
1952 #endif
1954     // Force screen redraw (does it have to be this complicated?).
1955     redraw_all_later(CLEAR);
1956     update_screen(NOT_VALID);
1957     setcursor();
1958     out_flush();
1959     gui_update_cursor(FALSE, FALSE);
1960     gui_mch_flush();
1963 - (void)handleDropFiles:(NSData *)data
1965     if (!data) return;
1967 #ifdef FEAT_DND
1968     const void *bytes = [data bytes];
1969     const void *end = [data bytes] + [data length];
1970     int n = *((int*)bytes);  bytes += sizeof(int);
1972     if (State & CMDLINE) {
1973         // HACK!  If Vim is in command line mode then the files names
1974         // should be added to the command line, instead of opening the
1975         // files in tabs.  This is taken care of by gui_handle_drop().
1976         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1977         if (fnames) {
1978             int i = 0;
1979             while (bytes < end && i < n) {
1980                 int len = *((int*)bytes);  bytes += sizeof(int);
1981                 char_u *s = (char_u*)bytes;
1982 #ifdef FEAT_MBYTE
1983                 s = CONVERT_FROM_UTF8(s);
1984 #endif
1985                 fnames[i++] = vim_strsave(s);
1986 #ifdef FEAT_MBYTE
1987                 CONVERT_FROM_UTF8_FREE(s);
1988 #endif
1989                 bytes += len;
1990             }
1992             // NOTE!  This function will free 'fnames'.
1993             // HACK!  It is assumed that the 'x' and 'y' arguments are
1994             // unused when in command line mode.
1995             gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
1996         }
1997     } else {
1998         // HACK!  I'm not sure how to get Vim to open a list of files in
1999         // tabs, so instead I create a ':tab drop' command with all the
2000         // files to open and execute it.
2001         NSMutableString *cmd = [NSMutableString stringWithString:@":tab drop"];
2003         int i;
2004         for (i = 0; i < n && bytes < end; ++i) {
2005             int len = *((int*)bytes);  bytes += sizeof(int);
2006             NSString *file = [NSString stringWithUTF8String:bytes];
2007             file = [file stringByEscapingSpecialFilenameCharacters];
2008             bytes += len;
2010             [cmd appendString:@" "];
2011             [cmd appendString:file];
2012         }
2014         // By going to the last tabpage we ensure that the new tabs will
2015         // appear last (if this call is left out, the taborder becomes
2016         // messy).
2017         goto_tabpage(9999);
2019         char_u *s = (char_u*)[cmd UTF8String];
2020 #ifdef FEAT_MBYTE
2021         s = CONVERT_FROM_UTF8(s);
2022 #endif
2023         do_cmdline_cmd(s);
2024 #ifdef FEAT_MBYTE
2025         CONVERT_FROM_UTF8_FREE(s);
2026 #endif
2028         // Force screen redraw (does it have to be this complicated?).
2029         // (This code was taken from the end of gui_handle_drop().)
2030         update_screen(NOT_VALID);
2031         setcursor();
2032         out_flush();
2033         gui_update_cursor(FALSE, FALSE);
2034         maketitle();
2035         gui_mch_flush();
2036     }
2037 #endif // FEAT_DND
2040 - (void)handleDropString:(NSData *)data
2042     if (!data) return;
2044 #ifdef FEAT_DND
2045     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2046     const void *bytes = [data bytes];
2047     int len = *((int*)bytes);  bytes += sizeof(int);
2048     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2050     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2051     NSRange range = { 0, [string length] };
2052     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2053                                          withString:@"\x0a" options:0
2054                                               range:range];
2055     if (0 == n) {
2056         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2057                                        options:0 range:range];
2058     }
2060     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2061     char_u *s = (char_u*)[string UTF8String];
2062 #ifdef FEAT_MBYTE
2063     if (input_conv.vc_type != CONV_NONE)
2064         s = string_convert(&input_conv, s, &len);
2065 #endif
2066     dnd_yank_drag_data(s, len);
2067 #ifdef FEAT_MBYTE
2068     if (input_conv.vc_type != CONV_NONE)
2069         vim_free(s);
2070 #endif
2071     add_to_input_buf(dropkey, sizeof(dropkey));
2072 #endif // FEAT_DND
2075 - (BOOL)checkForModifiedBuffers
2077     buf_T *buf;
2078     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2079         if (bufIsChanged(buf)) {
2080             return YES;
2081         }
2082     }
2084     return NO;
2087 - (void)addInput:(NSString *)input
2089     char_u *s = (char_u*)[input UTF8String];
2091 #ifdef FEAT_MBYTE
2092     s = CONVERT_FROM_UTF8(s);
2093 #endif
2095     server_to_input_buf(s);
2097 #ifdef FEAT_MBYTE
2098     CONVERT_FROM_UTF8_FREE(s);
2099 #endif
2102 @end // MMBackend (Private)
2107 @implementation MMBackend (ClientServer)
2109 - (NSString *)connectionNameFromServerName:(NSString *)name
2111     NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2113     return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2114         lowercaseString];
2117 - (NSConnection *)connectionForServerName:(NSString *)name
2119     // TODO: Try 'name%d' if 'name' fails.
2120     NSString *connName = [self connectionNameFromServerName:name];
2121     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2123     if (!svrConn) {
2124         svrConn = [NSConnection connectionWithRegisteredName:connName
2125                                                            host:nil];
2126         // Try alternate server...
2127         if (!svrConn && alternateServerName) {
2128             //NSLog(@"  trying to connect to alternate server: %@",
2129             //        alternateServerName);
2130             connName = [self connectionNameFromServerName:alternateServerName];
2131             svrConn = [NSConnection connectionWithRegisteredName:connName
2132                                                             host:nil];
2133         }
2135         // Try looking for alternate servers...
2136         if (!svrConn) {
2137             //NSLog(@"  looking for alternate servers...");
2138             NSString *alt = [self alternateServerNameForName:name];
2139             if (alt != alternateServerName) {
2140                 //NSLog(@"  found alternate server: %@", string);
2141                 [alternateServerName release];
2142                 alternateServerName = [alt copy];
2143             }
2144         }
2146         // Try alternate server again...
2147         if (!svrConn && alternateServerName) {
2148             //NSLog(@"  trying to connect to alternate server: %@",
2149             //        alternateServerName);
2150             connName = [self connectionNameFromServerName:alternateServerName];
2151             svrConn = [NSConnection connectionWithRegisteredName:connName
2152                                                             host:nil];
2153         }
2155         if (svrConn) {
2156             [connectionNameDict setObject:svrConn forKey:connName];
2158             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2159             [[NSNotificationCenter defaultCenter] addObserver:self
2160                     selector:@selector(serverConnectionDidDie:)
2161                         name:NSConnectionDidDieNotification object:svrConn];
2162         }
2163     }
2165     return svrConn;
2168 - (NSConnection *)connectionForServerPort:(int)port
2170     NSConnection *conn;
2171     NSEnumerator *e = [connectionNameDict objectEnumerator];
2173     while ((conn = [e nextObject])) {
2174         // HACK! Assume connection uses mach ports.
2175         if (port == [(NSMachPort*)[conn sendPort] machPort])
2176             return conn;
2177     }
2179     return nil;
2182 - (void)serverConnectionDidDie:(NSNotification *)notification
2184     //NSLog(@"%s%@", _cmd, notification);
2186     NSConnection *svrConn = [notification object];
2188     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2189     [[NSNotificationCenter defaultCenter]
2190             removeObserver:self
2191                       name:NSConnectionDidDieNotification
2192                     object:svrConn];
2194     [connectionNameDict removeObjectsForKeys:
2195         [connectionNameDict allKeysForObject:svrConn]];
2197     // HACK! Assume connection uses mach ports.
2198     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2199     NSNumber *key = [NSNumber numberWithInt:port];
2201     [clientProxyDict removeObjectForKey:key];
2202     [serverReplyDict removeObjectForKey:key];
2205 - (void)addClient:(NSDistantObject *)client
2207     NSConnection *conn = [client connectionForProxy];
2208     // HACK! Assume connection uses mach ports.
2209     int port = [(NSMachPort*)[conn sendPort] machPort];
2210     NSNumber *key = [NSNumber numberWithInt:port];
2212     if (![clientProxyDict objectForKey:key]) {
2213         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2214         [clientProxyDict setObject:client forKey:key];
2215     }
2217     // NOTE: 'clientWindow' is a global variable which is used by <client>
2218     clientWindow = port;
2221 - (NSString *)alternateServerNameForName:(NSString *)name
2223     if (!(name && [name length] > 0))
2224         return nil;
2226     // Only look for alternates if 'name' doesn't end in a digit.
2227     unichar lastChar = [name characterAtIndex:[name length]-1];
2228     if (lastChar >= '0' && lastChar <= '9')
2229         return nil;
2231     // Look for alternates among all current servers.
2232     NSArray *list = [self serverList];
2233     if (!(list && [list count] > 0))
2234         return nil;
2236     // Filter out servers starting with 'name' and ending with a number. The
2237     // (?i) pattern ensures that the match is case insensitive.
2238     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2239     NSPredicate *pred = [NSPredicate predicateWithFormat:
2240             @"SELF MATCHES %@", pat];
2241     list = [list filteredArrayUsingPredicate:pred];
2242     if ([list count] > 0) {
2243         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2244         return [list objectAtIndex:0];
2245     }
2247     return nil;
2250 @end // MMBackend (ClientServer)
2255 @implementation NSString (MMServerNameCompare)
2256 - (NSComparisonResult)serverNameCompare:(NSString *)string
2258     return [self compare:string
2259                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2261 @end
2266 static int eventModifierFlagsToVimModMask(int modifierFlags)
2268     int modMask = 0;
2270     if (modifierFlags & NSShiftKeyMask)
2271         modMask |= MOD_MASK_SHIFT;
2272     if (modifierFlags & NSControlKeyMask)
2273         modMask |= MOD_MASK_CTRL;
2274     if (modifierFlags & NSAlternateKeyMask)
2275         modMask |= MOD_MASK_ALT;
2276     if (modifierFlags & NSCommandKeyMask)
2277         modMask |= MOD_MASK_CMD;
2279     return modMask;
2282 static int vimModMaskToEventModifierFlags(int mods)
2284     int flags = 0;
2286     if (mods & MOD_MASK_SHIFT)
2287         flags |= NSShiftKeyMask;
2288     if (mods & MOD_MASK_CTRL)
2289         flags |= NSControlKeyMask;
2290     if (mods & MOD_MASK_ALT)
2291         flags |= NSAlternateKeyMask;
2292     if (mods & MOD_MASK_CMD)
2293         flags |= NSCommandKeyMask;
2295     return flags;
2298 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2300     int modMask = 0;
2302     if (modifierFlags & NSShiftKeyMask)
2303         modMask |= MOUSE_SHIFT;
2304     if (modifierFlags & NSControlKeyMask)
2305         modMask |= MOUSE_CTRL;
2306     if (modifierFlags & NSAlternateKeyMask)
2307         modMask |= MOUSE_ALT;
2309     return modMask;
2312 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2314     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2316     return (buttonNumber >= 0 && buttonNumber < 3)
2317             ? mouseButton[buttonNumber] : -1;
2320 static int specialKeyToNSKey(int key)
2322     if (!IS_SPECIAL(key))
2323         return key;
2325     static struct {
2326         int special;
2327         int nskey;
2328     } sp2ns[] = {
2329         { K_UP, NSUpArrowFunctionKey },
2330         { K_DOWN, NSDownArrowFunctionKey },
2331         { K_LEFT, NSLeftArrowFunctionKey },
2332         { K_RIGHT, NSRightArrowFunctionKey },
2333         { K_F1, NSF1FunctionKey },
2334         { K_F2, NSF2FunctionKey },
2335         { K_F3, NSF3FunctionKey },
2336         { K_F4, NSF4FunctionKey },
2337         { K_F5, NSF5FunctionKey },
2338         { K_F6, NSF6FunctionKey },
2339         { K_F7, NSF7FunctionKey },
2340         { K_F8, NSF8FunctionKey },
2341         { K_F9, NSF9FunctionKey },
2342         { K_F10, NSF10FunctionKey },
2343         { K_F11, NSF11FunctionKey },
2344         { K_F12, NSF12FunctionKey },
2345         { K_F13, NSF13FunctionKey },
2346         { K_F14, NSF14FunctionKey },
2347         { K_F15, NSF15FunctionKey },
2348         { K_F16, NSF16FunctionKey },
2349         { K_F17, NSF17FunctionKey },
2350         { K_F18, NSF18FunctionKey },
2351         { K_F19, NSF19FunctionKey },
2352         { K_F20, NSF20FunctionKey },
2353         { K_F21, NSF21FunctionKey },
2354         { K_F22, NSF22FunctionKey },
2355         { K_F23, NSF23FunctionKey },
2356         { K_F24, NSF24FunctionKey },
2357         { K_F25, NSF25FunctionKey },
2358         { K_F26, NSF26FunctionKey },
2359         { K_F27, NSF27FunctionKey },
2360         { K_F28, NSF28FunctionKey },
2361         { K_F29, NSF29FunctionKey },
2362         { K_F30, NSF30FunctionKey },
2363         { K_F31, NSF31FunctionKey },
2364         { K_F32, NSF32FunctionKey },
2365         { K_F33, NSF33FunctionKey },
2366         { K_F34, NSF34FunctionKey },
2367         { K_F35, NSF35FunctionKey },
2368         { K_DEL, NSBackspaceCharacter },
2369         { K_BS, NSDeleteCharacter },
2370         { K_HOME, NSHomeFunctionKey },
2371         { K_END, NSEndFunctionKey },
2372         { K_PAGEUP, NSPageUpFunctionKey },
2373         { K_PAGEDOWN, NSPageDownFunctionKey }
2374     };
2376     int i;
2377     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2378         if (sp2ns[i].special == key)
2379             return sp2ns[i].nskey;
2380     }
2382     return 0;