Preserve swap files after crash
[MacVim.git] / src / MacVim / MMBackend.m
blob40832d5efef3e5d978764443d792799007fc8690
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  * MMBackend
12  *
13  * MMBackend communicates with the frontend (MacVim).  It maintains a queue of
14  * output which is flushed to the frontend under controlled circumstances (so
15  * as to maintain a steady framerate).  Input from the frontend is also handled
16  * here.
17  *
18  * The frontend communicates with the backend via the MMBackendProtocol.  In
19  * particular, input is sent to the backend via processInput:data: and Vim
20  * state can be queried from the frontend with evaluateExpression:.
21  *
22  * It is very important to realize that all state is held by the backend, the
23  * frontend must either ask for state [MMBackend evaluateExpression:] or wait
24  * for the backend to update [MMVimController processCommandQueue:].
25  *
26  * The client/server functionality of Vim is handled by the backend.  It sets
27  * up a named NSConnection to which other Vim processes can connect.
28  */
30 #import "MMBackend.h"
34 // NOTE: Colors in MMBackend are stored as unsigned ints on the form 0xaarrggbb
35 // whereas colors in Vim are int without the alpha component.  Also note that
36 // 'transp' is assumed to be a value between 0 and 100.
37 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
38 #define MM_COLOR_WITH_TRANSP(col,transp) \
39     ((unsigned)( ((col)&0xffffff) \
40         | ((((unsigned)((((100-(transp))*255)/100)+.5f))&0xff)<<24) ))
43 // This constant controls how often the command queue may be flushed.  If it is
44 // too small the app might feel unresponsive; if it is too large there might be
45 // long periods without the screen updating (e.g. when sourcing a large session
46 // file).  (The unit is seconds.)
47 static float MMFlushTimeoutInterval = 0.1f;
48 static int MMFlushQueueLenHint = 80*40;
50 static unsigned MMServerMax = 1000;
52 // TODO: Move to separate file.
53 static int eventModifierFlagsToVimModMask(int modifierFlags);
54 static int vimModMaskToEventModifierFlags(int mods);
55 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
56 static int eventButtonNumberToVimMouseButton(int buttonNumber);
57 static int specialKeyToNSKey(int key);
59 enum {
60     MMBlinkStateNone = 0,
61     MMBlinkStateOn,
62     MMBlinkStateOff
67 @interface NSString (MMServerNameCompare)
68 - (NSComparisonResult)serverNameCompare:(NSString *)string;
69 @end
73 @interface MMBackend (Private)
74 - (void)processInputQueue;
75 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
76 + (NSDictionary *)specialKeys;
77 - (void)handleInsertText:(NSData *)data;
78 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
79 - (void)queueMessage:(int)msgid data:(NSData *)data;
80 - (void)connectionDidDie:(NSNotification *)notification;
81 - (void)blinkTimerFired:(NSTimer *)timer;
82 - (void)focusChange:(BOOL)on;
83 - (void)handleToggleToolbar;
84 - (void)handleScrollbarEvent:(NSData *)data;
85 - (void)handleSetFont:(NSData *)data;
86 - (void)handleDropFiles:(NSData *)data;
87 - (void)handleDropString:(NSData *)data;
88 - (BOOL)checkForModifiedBuffers;
89 - (void)addInput:(NSString *)input;
90 @end
94 @interface MMBackend (ClientServer)
95 - (NSString *)connectionNameFromServerName:(NSString *)name;
96 - (NSConnection *)connectionForServerName:(NSString *)name;
97 - (NSConnection *)connectionForServerPort:(int)port;
98 - (void)serverConnectionDidDie:(NSNotification *)notification;
99 - (void)addClient:(NSDistantObject *)client;
100 - (NSString *)alternateServerNameForName:(NSString *)name;
101 @end
105 @implementation MMBackend
107 + (MMBackend *)sharedInstance
109     static MMBackend *singleton = nil;
110     return singleton ? singleton : (singleton = [MMBackend new]);
113 - (id)init
115     if ((self = [super init])) {
116         fontContainerRef = loadFonts();
118         outputQueue = [[NSMutableArray alloc] init];
119         inputQueue = [[NSMutableArray alloc] init];
120         drawData = [[NSMutableData alloc] initWithCapacity:1024];
121         connectionNameDict = [[NSMutableDictionary alloc] init];
122         clientProxyDict = [[NSMutableDictionary alloc] init];
123         serverReplyDict = [[NSMutableDictionary alloc] init];
125         NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
126                                                          ofType:@"plist"];
127         if (path) {
128             colorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
129                 retain];
130         } else {
131             NSLog(@"WARNING: Could not locate Colors.plist.");
132         }
134         path = [[NSBundle mainBundle] pathForResource:@"SystemColors"
135                                                ofType:@"plist"];
136         if (path) {
137             sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
138                 retain];
139         } else {
140             NSLog(@"WARNING: Could not locate SystemColors.plist.");
141         }
142     }
144     return self;
147 - (void)dealloc
149     //NSLog(@"%@ %s", [self className], _cmd);
150     [[NSNotificationCenter defaultCenter] removeObserver:self];
152     [oldWideFont release];  oldWideFont = nil;
153     [blinkTimer release];  blinkTimer = nil;
154     [alternateServerName release];  alternateServerName = nil;
155     [serverReplyDict release];  serverReplyDict = nil;
156     [clientProxyDict release];  clientProxyDict = nil;
157     [connectionNameDict release];  connectionNameDict = nil;
158     [inputQueue release];  inputQueue = nil;
159     [outputQueue release];  outputQueue = nil;
160     [drawData release];  drawData = nil;
161     [frontendProxy release];  frontendProxy = nil;
162     [connection release];  connection = nil;
163     [sysColorDict release];  sysColorDict = nil;
164     [colorDict release];  colorDict = nil;
166     [super dealloc];
169 - (void)setBackgroundColor:(int)color
171     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
174 - (void)setForegroundColor:(int)color
176     foregroundColor = MM_COLOR(color);
179 - (void)setSpecialColor:(int)color
181     specialColor = MM_COLOR(color);
184 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
186     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
187     defaultForegroundColor = MM_COLOR(fg);
189     NSMutableData *data = [NSMutableData data];
191     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
192     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
194     [self queueMessage:SetDefaultColorsMsgID data:data];
197 - (NSConnection *)connection
199     if (!connection) {
200         // NOTE!  If the name of the connection changes here it must also be
201         // updated in MMAppController.m.
202         NSString *name = [NSString stringWithFormat:@"%@-connection",
203                [[NSBundle mainBundle] bundleIdentifier]];
205         connection = [NSConnection connectionWithRegisteredName:name host:nil];
206         [connection retain];
207     }
209     // NOTE: 'connection' may be nil here.
210     return connection;
213 - (BOOL)checkin
215     if (![self connection]) {
216         NSBundle *mainBundle = [NSBundle mainBundle];
217 #if 0
218         NSString *path = [mainBundle bundlePath];
219         if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
220             NSLog(@"WARNING: Failed to launch GUI with path %@", path);
221             return NO;
222         }
223 #else
224         // HACK!  It would be preferable to launch the GUI using NSWorkspace,
225         // however I have not managed to figure out how to pass arguments using
226         // NSWorkspace.
227         //
228         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
229         // that the GUI won't be activated (or raised) so there is a hack in
230         // MMWindowController which always raises the app when a new window is
231         // opened.
232         NSMutableArray *args = [NSMutableArray arrayWithObjects:
233             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
234         NSString *exeName = [[mainBundle infoDictionary]
235                 objectForKey:@"CFBundleExecutable"];
236         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
237         if (!path) {
238             NSLog(@"ERROR: Could not find MacVim executable in bundle");
239             return NO;
240         }
242         [NSTask launchedTaskWithLaunchPath:path arguments:args];
243 #endif
245         // HACK!  The NSWorkspaceDidLaunchApplicationNotification does not work
246         // for tasks like this, so poll the mach bootstrap server until it
247         // returns a valid connection.  Also set a time-out date so that we
248         // don't get stuck doing this forever.
249         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
250         while (!connection &&
251                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
252         {
253             [[NSRunLoop currentRunLoop]
254                     runMode:NSDefaultRunLoopMode
255                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
257             // NOTE: This call will set 'connection' as a side-effect.
258             [self connection];
259         }
261         if (!connection) {
262             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
263             return NO;
264         }
265     }
267     id proxy = [connection rootProxy];
268     [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
270     [[NSNotificationCenter defaultCenter] addObserver:self
271             selector:@selector(connectionDidDie:)
272                 name:NSConnectionDidDieNotification object:connection];
274     int pid = [[NSProcessInfo processInfo] processIdentifier];
276     @try {
277         frontendProxy = [proxy connectBackend:self pid:pid];
278     }
279     @catch (NSException *e) {
280         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
281     }
283     if (frontendProxy) {
284         [frontendProxy retain];
285         [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
286     }
288     return connection && frontendProxy;
291 - (BOOL)openVimWindow
293     [self queueMessage:OpenVimWindowMsgID data:nil];
294     return YES;
297 - (void)clearAll
299     int type = ClearAllDrawType;
301     // Any draw commands in queue are effectively obsolete since this clearAll
302     // will negate any effect they have, therefore we may as well clear the
303     // draw queue.
304     [drawData setLength:0];
306     [drawData appendBytes:&type length:sizeof(int)];
309 - (void)clearBlockFromRow:(int)row1 column:(int)col1
310                     toRow:(int)row2 column:(int)col2
312     int type = ClearBlockDrawType;
314     [drawData appendBytes:&type length:sizeof(int)];
316     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
317     [drawData appendBytes:&row1 length:sizeof(int)];
318     [drawData appendBytes:&col1 length:sizeof(int)];
319     [drawData appendBytes:&row2 length:sizeof(int)];
320     [drawData appendBytes:&col2 length:sizeof(int)];
323 - (void)deleteLinesFromRow:(int)row count:(int)count
324               scrollBottom:(int)bottom left:(int)left right:(int)right
326     int type = DeleteLinesDrawType;
328     [drawData appendBytes:&type length:sizeof(int)];
330     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
331     [drawData appendBytes:&row length:sizeof(int)];
332     [drawData appendBytes:&count length:sizeof(int)];
333     [drawData appendBytes:&bottom length:sizeof(int)];
334     [drawData appendBytes:&left length:sizeof(int)];
335     [drawData appendBytes:&right length:sizeof(int)];
338 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
339              cells:(int)cells flags:(int)flags
341     if (len <= 0 || cells <= 0) return;
343     int type = DrawStringDrawType;
345     [drawData appendBytes:&type length:sizeof(int)];
347     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
348     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
349     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
350     [drawData appendBytes:&row length:sizeof(int)];
351     [drawData appendBytes:&col length:sizeof(int)];
352     [drawData appendBytes:&cells length:sizeof(int)];
353     [drawData appendBytes:&flags length:sizeof(int)];
354     [drawData appendBytes:&len length:sizeof(int)];
355     [drawData appendBytes:s length:len];
358 - (void)insertLinesFromRow:(int)row count:(int)count
359               scrollBottom:(int)bottom left:(int)left right:(int)right
361     int type = InsertLinesDrawType;
363     [drawData appendBytes:&type length:sizeof(int)];
365     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
366     [drawData appendBytes:&row length:sizeof(int)];
367     [drawData appendBytes:&count length:sizeof(int)];
368     [drawData appendBytes:&bottom length:sizeof(int)];
369     [drawData appendBytes:&left length:sizeof(int)];
370     [drawData appendBytes:&right length:sizeof(int)];
373 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
374                fraction:(int)percent color:(int)color
376     int type = DrawCursorDrawType;
377     unsigned uc = MM_COLOR(color);
379     [drawData appendBytes:&type length:sizeof(int)];
381     [drawData appendBytes:&uc length:sizeof(unsigned)];
382     [drawData appendBytes:&row length:sizeof(int)];
383     [drawData appendBytes:&col length:sizeof(int)];
384     [drawData appendBytes:&shape length:sizeof(int)];
385     [drawData appendBytes:&percent length:sizeof(int)];
388 - (void)update
390     // Tend to the run loop, returning immediately if there are no events
391     // waiting.
392     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
393                              beforeDate:[NSDate distantPast]];
395     // Keyboard and mouse input is handled directly, other input is queued and
396     // processed here.  This call may enter a blocking loop.
397     if ([inputQueue count] > 0)
398         [self processInputQueue];
401 - (void)flushQueue:(BOOL)force
403     // NOTE! This method gets called a lot; if we were to flush every time it
404     // got called MacVim would feel unresponsive.  So there is a time out which
405     // ensures that the queue isn't flushed too often.
406     if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
407             < MMFlushTimeoutInterval
408             && [drawData length] < MMFlushQueueLenHint)
409         return;
411     if ([drawData length] > 0) {
412         // HACK!  Detect changes to 'guifontwide'.
413         if (gui.wide_font != (GuiFont)oldWideFont) {
414             [oldWideFont release];
415             oldWideFont = [(NSFont*)gui.wide_font retain];
416             [self setWideFont:oldWideFont];
417         }
419         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
420         [drawData setLength:0];
421     }
423     if ([outputQueue count] > 0) {
424         @try {
425             [frontendProxy processCommandQueue:outputQueue];
426         }
427         @catch (NSException *e) {
428             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
429         }
431         [outputQueue removeAllObjects];
433         [lastFlushDate release];
434         lastFlushDate = [[NSDate date] retain];
435     }
438 - (BOOL)waitForInput:(int)milliseconds
440     //NSLog(@"|ENTER| %s%d",  _cmd, milliseconds);
441     NSDate *date = milliseconds > 0 ?
442             [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
443             [NSDate distantFuture];
445     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
447     // I know of no way to figure out if the run loop exited because input was
448     // found or because of a time out, so I need to manually indicate when
449     // input was received in processInput:data: and then reset it every time
450     // here.
451     BOOL yn = inputReceived;
452     inputReceived = NO;
454     if ([inputQueue count] > 0)
455         [self processInputQueue];
457     //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
458     return yn;
461 - (void)exit
463 #ifdef MAC_CLIENTSERVER
464     // The default connection is used for the client/server code.
465     [[NSConnection defaultConnection] setRootObject:nil];
466     [[NSConnection defaultConnection] invalidate];
467 #endif
469     // By invalidating the NSConnection the MMWindowController immediately
470     // finds out that the connection is down and as a result
471     // [MMWindowController connectionDidDie:] is invoked.
472     //NSLog(@"%@ %s", [self className], _cmd);
473     [[NSNotificationCenter defaultCenter] removeObserver:self];
474     [connection invalidate];
476     if (fontContainerRef) {
477         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
478         fontContainerRef = 0;
479     }
483 - (void)selectTab:(int)index
485     //NSLog(@"%s%d", _cmd, index);
487     index -= 1;
488     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
489     [self queueMessage:SelectTabMsgID data:data];
492 - (void)updateTabBar
494     //NSLog(@"%s", _cmd);
496     NSMutableData *data = [NSMutableData data];
498     int idx = tabpage_index(curtab) - 1;
499     [data appendBytes:&idx length:sizeof(int)];
501     tabpage_T *tp;
502     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
503         // This function puts the label of the tab in the global 'NameBuff'.
504         get_tabline_label(tp, FALSE);
505         char_u *s = NameBuff;
506         int len = STRLEN(s);
507         if (len <= 0) continue;
509 #ifdef FEAT_MBYTE
510         s = CONVERT_TO_UTF8(s);
511 #endif
513         // Count the number of windows in the tabpage.
514         //win_T *wp = tp->tp_firstwin;
515         //int wincount;
516         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
518         //[data appendBytes:&wincount length:sizeof(int)];
519         [data appendBytes:&len length:sizeof(int)];
520         [data appendBytes:s length:len];
522 #ifdef FEAT_MBYTE
523         CONVERT_TO_UTF8_FREE(s);
524 #endif
525     }
527     [self queueMessage:UpdateTabBarMsgID data:data];
530 - (BOOL)tabBarVisible
532     return tabBarVisible;
535 - (void)showTabBar:(BOOL)enable
537     tabBarVisible = enable;
539     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
540     [self queueMessage:msgid data:nil];
543 - (void)setRows:(int)rows columns:(int)cols
545     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
547     int dim[] = { rows, cols };
548     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
550     [self queueMessage:SetTextDimensionsMsgID data:data];
553 - (void)setWindowTitle:(char *)title
555     NSMutableData *data = [NSMutableData data];
556     int len = strlen(title);
557     if (len <= 0) return;
559     [data appendBytes:&len length:sizeof(int)];
560     [data appendBytes:title length:len];
562     [self queueMessage:SetWindowTitleMsgID data:data];
565 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
566                             saving:(int)saving
568     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
569     //        saving);
571     char_u *s = NULL;
572     NSString *ds = dir ? [NSString stringWithUTF8String:dir] : nil;
573     NSString *ts = title ? [NSString stringWithUTF8String:title] : nil;
574     @try {
575         [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
577         // Wait until a reply is sent from MMVimController.
578         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
579                                  beforeDate:[NSDate distantFuture]];
581         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
582             char_u *ret = (char_u*)[dialogReturn UTF8String];
583 #ifdef FEAT_MBYTE
584             ret = CONVERT_FROM_UTF8(ret);
585 #endif
586             s = vim_strsave(ret);
587 #ifdef FEAT_MBYTE
588             CONVERT_FROM_UTF8_FREE(ret);
589 #endif
590         }
592         [dialogReturn release];  dialogReturn = nil;
593     }
594     @catch (NSException *e) {
595         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
596     }
598     return (char *)s;
601 - (oneway void)setDialogReturn:(in bycopy id)obj
603     // NOTE: This is called by
604     //   - [MMVimController panelDidEnd:::], and
605     //   - [MMVimController alertDidEnd:::],
606     // to indicate that a save/open panel or alert has finished.
608     if (obj != dialogReturn) {
609         [dialogReturn release];
610         dialogReturn = [obj retain];
611     }
614 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
615                      buttons:(char *)btns textField:(char *)txtfield
617     int retval = 0;
618     NSString *message = nil, *text = nil, *textFieldString = nil;
619     NSArray *buttons = nil;
620     int style = NSInformationalAlertStyle;
622     if (VIM_WARNING == type) style = NSWarningAlertStyle;
623     else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
625     if (btns) {
626         NSString *btnString = [NSString stringWithUTF8String:btns];
627         buttons = [btnString componentsSeparatedByString:@"\n"];
628     }
629     if (title)
630         message = [NSString stringWithUTF8String:title];
631     if (msg) {
632         text = [NSString stringWithUTF8String:msg];
633         if (!message) {
634             // HACK! If there is a '\n\n' or '\n' sequence in the message, then
635             // make the part up to there into the title.  We only do this
636             // because Vim has lots of dialogs without a title and they look
637             // ugly that way.
638             // TODO: Fix the actual dialog texts.
639             NSRange eolRange = [text rangeOfString:@"\n\n"];
640             if (NSNotFound == eolRange.location)
641                 eolRange = [text rangeOfString:@"\n"];
642             if (NSNotFound != eolRange.location) {
643                 message = [text substringToIndex:eolRange.location];
644                 text = [text substringFromIndex:NSMaxRange(eolRange)];
645             }
646         }
647     }
648     if (txtfield)
649         textFieldString = [NSString stringWithUTF8String:txtfield];
651     @try {
652         [frontendProxy presentDialogWithStyle:style message:message
653                               informativeText:text buttonTitles:buttons
654                               textFieldString:textFieldString];
656         // Wait until a reply is sent from MMVimController.
657         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
658                                  beforeDate:[NSDate distantFuture]];
660         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
661                 && [dialogReturn count]) {
662             retval = [[dialogReturn objectAtIndex:0] intValue];
663             if (txtfield && [dialogReturn count] > 1) {
664                 NSString *retString = [dialogReturn objectAtIndex:1];
665                 char_u *ret = (char_u*)[retString UTF8String];
666 #ifdef FEAT_MBYTE
667                 ret = CONVERT_FROM_UTF8(ret);
668 #endif
669                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
670 #ifdef FEAT_MBYTE
671                 CONVERT_FROM_UTF8_FREE(ret);
672 #endif
673             }
674         }
676         [dialogReturn release]; dialogReturn = nil;
677     }
678     @catch (NSException *e) {
679         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
680     }
682     return retval;
685 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
686                atIndex:(int)index
688     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
689     //        name, index);
691     int namelen = name ? strlen(name) : 0;
692     NSMutableData *data = [NSMutableData data];
694     [data appendBytes:&tag length:sizeof(int)];
695     [data appendBytes:&parentTag length:sizeof(int)];
696     [data appendBytes:&namelen length:sizeof(int)];
697     if (namelen > 0) [data appendBytes:name length:namelen];
698     [data appendBytes:&index length:sizeof(int)];
700     [self queueMessage:AddMenuMsgID data:data];
703 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
704                        tip:(char *)tip icon:(char *)icon
705              keyEquivalent:(int)key modifiers:(int)mods
706                     action:(NSString *)action atIndex:(int)index
708     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
709     //        parentTag, name, tip, index);
711     int namelen = name ? strlen(name) : 0;
712     int tiplen = tip ? strlen(tip) : 0;
713     int iconlen = icon ? strlen(icon) : 0;
714     int eventFlags = vimModMaskToEventModifierFlags(mods);
715     int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
716     NSMutableData *data = [NSMutableData data];
718     key = specialKeyToNSKey(key);
720     [data appendBytes:&tag length:sizeof(int)];
721     [data appendBytes:&parentTag length:sizeof(int)];
722     [data appendBytes:&namelen length:sizeof(int)];
723     if (namelen > 0) [data appendBytes:name length:namelen];
724     [data appendBytes:&tiplen length:sizeof(int)];
725     if (tiplen > 0) [data appendBytes:tip length:tiplen];
726     [data appendBytes:&iconlen length:sizeof(int)];
727     if (iconlen > 0) [data appendBytes:icon length:iconlen];
728     [data appendBytes:&actionlen length:sizeof(int)];
729     if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
730     [data appendBytes:&index length:sizeof(int)];
731     [data appendBytes:&key length:sizeof(int)];
732     [data appendBytes:&eventFlags length:sizeof(int)];
734     [self queueMessage:AddMenuItemMsgID data:data];
737 - (void)removeMenuItemWithTag:(int)tag
739     NSMutableData *data = [NSMutableData data];
740     [data appendBytes:&tag length:sizeof(int)];
742     [self queueMessage:RemoveMenuItemMsgID data:data];
745 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
747     NSMutableData *data = [NSMutableData data];
749     [data appendBytes:&tag length:sizeof(int)];
750     [data appendBytes:&enabled length:sizeof(int)];
752     [self queueMessage:EnableMenuItemMsgID data:data];
755 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
757     int len = strlen(name);
758     int row = -1, col = -1;
760     if (len <= 0) return;
762     if (!mouse && curwin) {
763         row = curwin->w_wrow;
764         col = curwin->w_wcol;
765     }
767     NSMutableData *data = [NSMutableData data];
769     [data appendBytes:&row length:sizeof(int)];
770     [data appendBytes:&col length:sizeof(int)];
771     [data appendBytes:&len length:sizeof(int)];
772     [data appendBytes:name length:len];
774     [self queueMessage:ShowPopupMenuMsgID data:data];
777 - (void)showToolbar:(int)enable flags:(int)flags
779     NSMutableData *data = [NSMutableData data];
781     [data appendBytes:&enable length:sizeof(int)];
782     [data appendBytes:&flags length:sizeof(int)];
784     [self queueMessage:ShowToolbarMsgID data:data];
787 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
789     NSMutableData *data = [NSMutableData data];
791     [data appendBytes:&ident length:sizeof(long)];
792     [data appendBytes:&type length:sizeof(int)];
794     [self queueMessage:CreateScrollbarMsgID data:data];
797 - (void)destroyScrollbarWithIdentifier:(long)ident
799     NSMutableData *data = [NSMutableData data];
800     [data appendBytes:&ident length:sizeof(long)];
802     [self queueMessage:DestroyScrollbarMsgID data:data];
805 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
807     NSMutableData *data = [NSMutableData data];
809     [data appendBytes:&ident length:sizeof(long)];
810     [data appendBytes:&visible length:sizeof(int)];
812     [self queueMessage:ShowScrollbarMsgID data:data];
815 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
817     NSMutableData *data = [NSMutableData data];
819     [data appendBytes:&ident length:sizeof(long)];
820     [data appendBytes:&pos length:sizeof(int)];
821     [data appendBytes:&len length:sizeof(int)];
823     [self queueMessage:SetScrollbarPositionMsgID data:data];
826 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
827                     identifier:(long)ident
829     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
830     float prop = (float)size/(max+1);
831     if (fval < 0) fval = 0;
832     else if (fval > 1.0f) fval = 1.0f;
833     if (prop < 0) prop = 0;
834     else if (prop > 1.0f) prop = 1.0f;
836     NSMutableData *data = [NSMutableData data];
838     [data appendBytes:&ident length:sizeof(long)];
839     [data appendBytes:&fval length:sizeof(float)];
840     [data appendBytes:&prop length:sizeof(float)];
842     [self queueMessage:SetScrollbarThumbMsgID data:data];
845 - (void)setFont:(NSFont *)font
847     NSString *fontName = [font displayName];
848     float size = [font pointSize];
849     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
850     if (len > 0) {
851         NSMutableData *data = [NSMutableData data];
853         [data appendBytes:&size length:sizeof(float)];
854         [data appendBytes:&len length:sizeof(int)];
855         [data appendBytes:[fontName UTF8String] length:len];
857         [self queueMessage:SetFontMsgID data:data];
858     }
861 - (void)setWideFont:(NSFont *)font
863     NSString *fontName = [font displayName];
864     float size = [font pointSize];
865     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
866     NSMutableData *data = [NSMutableData data];
868     [data appendBytes:&size length:sizeof(float)];
869     [data appendBytes:&len length:sizeof(int)];
870     if (len > 0)
871         [data appendBytes:[fontName UTF8String] length:len];
873     [self queueMessage:SetWideFontMsgID data:data];
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 - (void)setPreEditRow:(int)row column:(int)col
951     NSMutableData *data = [NSMutableData data];
952     [data appendBytes:&row length:sizeof(int)];
953     [data appendBytes:&col length:sizeof(int)];
954     [self queueMessage:SetPreEditPositionMsgID data:data];
957 - (int)lookupColorWithKey:(NSString *)key
959     if (!(key && [key length] > 0))
960         return INVALCOLOR;
962     NSString *stripKey = [[[[key lowercaseString]
963         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
964             componentsSeparatedByString:@" "]
965                componentsJoinedByString:@""];
967     if (stripKey && [stripKey length] > 0) {
968         // First of all try to lookup key in the color dictionary; note that
969         // all keys in this dictionary are lowercase with no whitespace.
970         id obj = [colorDict objectForKey:stripKey];
971         if (obj) return [obj intValue];
973         // The key was not in the dictionary; is it perhaps of the form
974         // #rrggbb?
975         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
976             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
977             [scanner setScanLocation:1];
978             unsigned hex = 0;
979             if ([scanner scanHexInt:&hex]) {
980                 return (int)hex;
981             }
982         }
984         // As a last resort, check if it is one of the system defined colors.
985         // The keys in this dictionary are also lowercase with no whitespace.
986         obj = [sysColorDict objectForKey:stripKey];
987         if (obj) {
988             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
989             if (col) {
990                 float r, g, b, a;
991                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
992                 [col getRed:&r green:&g blue:&b alpha:&a];
993                 return (((int)(r*255+.5f) & 0xff) << 16)
994                      + (((int)(g*255+.5f) & 0xff) << 8)
995                      +  ((int)(b*255+.5f) & 0xff);
996             }
997         }
998     }
1000     NSLog(@"WARNING: No color with key %@ found.", stripKey);
1001     return INVALCOLOR;
1004 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
1006     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1007     id obj;
1009     while ((obj = [e nextObject])) {
1010         if ([value isEqual:obj])
1011             return YES;
1012     }
1014     return NO;
1017 - (void)enterFullscreen
1019     [self queueMessage:EnterFullscreenMsgID data:nil];
1022 - (void)leaveFullscreen
1024     [self queueMessage:LeaveFullscreenMsgID data:nil];
1027 - (void)updateModifiedFlag
1029     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1030     // vice versa.
1031     int msgid = [self checkForModifiedBuffers]
1032             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1034     [self queueMessage:msgid data:nil];
1037 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1039     // NOTE: This method might get called whenever the run loop is tended to.
1040     // Normal keyboard and mouse input is added to input buffers, so there is
1041     // no risk in handling these events directly (they return immediately, and
1042     // do not call any other Vim functions).  However, other events such
1043     // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
1044     // events which would cause this method to be called recursively.  This
1045     // in turn leads to various difficulties that we do not want to have to
1046     // deal with.  To avoid recursive calls here we add all events except
1047     // keyboard and mouse events to an input queue which is processed whenever
1048     // gui_mch_update() is called (see processInputQueue).
1050     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1052     // Don't flush too soon after receiving input or update speed will suffer.
1053     [lastFlushDate release];
1054     lastFlushDate = [[NSDate date] retain];
1056     // Handle keyboard and mouse input now.  All other events are queued.
1057     if (InsertTextMsgID == msgid) {
1058         [self handleInsertText:data];
1059     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1060         if (!data) return;
1061         const void *bytes = [data bytes];
1062         int mods = *((int*)bytes);  bytes += sizeof(int);
1063         int len = *((int*)bytes);  bytes += sizeof(int);
1064         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1065                                               encoding:NSUTF8StringEncoding];
1066         mods = eventModifierFlagsToVimModMask(mods);
1068         [self handleKeyDown:key modifiers:mods];
1070         [key release];
1071     } else if (ScrollWheelMsgID == msgid) {
1072         if (!data) return;
1073         const void *bytes = [data bytes];
1075         int row = *((int*)bytes);  bytes += sizeof(int);
1076         int col = *((int*)bytes);  bytes += sizeof(int);
1077         int flags = *((int*)bytes);  bytes += sizeof(int);
1078         float dy = *((float*)bytes);  bytes += sizeof(float);
1080         int button = MOUSE_5;
1081         if (dy > 0) button = MOUSE_4;
1083         flags = eventModifierFlagsToVimMouseModMask(flags);
1085         gui_send_mouse_event(button, col, row, NO, flags);
1086     } else if (MouseDownMsgID == msgid) {
1087         if (!data) return;
1088         const void *bytes = [data bytes];
1090         int row = *((int*)bytes);  bytes += sizeof(int);
1091         int col = *((int*)bytes);  bytes += sizeof(int);
1092         int button = *((int*)bytes);  bytes += sizeof(int);
1093         int flags = *((int*)bytes);  bytes += sizeof(int);
1094         int count = *((int*)bytes);  bytes += sizeof(int);
1096         button = eventButtonNumberToVimMouseButton(button);
1097         if (button >= 0) {
1098             flags = eventModifierFlagsToVimMouseModMask(flags);
1099             gui_send_mouse_event(button, col, row, count>1, flags);
1100         }
1101     } else if (MouseUpMsgID == msgid) {
1102         if (!data) return;
1103         const void *bytes = [data bytes];
1105         int row = *((int*)bytes);  bytes += sizeof(int);
1106         int col = *((int*)bytes);  bytes += sizeof(int);
1107         int flags = *((int*)bytes);  bytes += sizeof(int);
1109         flags = eventModifierFlagsToVimMouseModMask(flags);
1111         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1112     } else if (MouseDraggedMsgID == msgid) {
1113         if (!data) return;
1114         const void *bytes = [data bytes];
1116         int row = *((int*)bytes);  bytes += sizeof(int);
1117         int col = *((int*)bytes);  bytes += sizeof(int);
1118         int flags = *((int*)bytes);  bytes += sizeof(int);
1120         flags = eventModifierFlagsToVimMouseModMask(flags);
1122         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1123     } else if (MouseMovedMsgID == msgid) {
1124         const void *bytes = [data bytes];
1125         int row = *((int*)bytes);  bytes += sizeof(int);
1126         int col = *((int*)bytes);  bytes += sizeof(int);
1128         gui_mouse_moved(col, row);
1129     } else if (AddInputMsgID == msgid) {
1130         NSString *string = [[NSString alloc] initWithData:data
1131                 encoding:NSUTF8StringEncoding];
1132         if (string) {
1133             [self addInput:string];
1134             [string release];
1135         }
1136     } else if (TerminateNowMsgID == msgid) {
1137         isTerminating = YES;
1138     } else {
1139         // Not keyboard or mouse event, queue it and handle later.
1140         //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
1141         [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1142         [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
1143     }
1145     // See waitForInput: for an explanation of this flag.
1146     inputReceived = YES;
1149 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1151     // TODO: Get rid of this method?
1152     //NSLog(@"%s%@", _cmd, messages);
1154     unsigned i, count = [messages count];
1155     for (i = 0; i < count; i += 2) {
1156         int msgid = [[messages objectAtIndex:i] intValue];
1157         id data = [messages objectAtIndex:i+1];
1158         if ([data isEqual:[NSNull null]])
1159             data = nil;
1161         [self processInput:msgid data:data];
1162     }
1165 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1167     NSString *eval = nil;
1168     char_u *s = (char_u*)[expr UTF8String];
1170 #ifdef FEAT_MBYTE
1171     s = CONVERT_FROM_UTF8(s);
1172 #endif
1174     char_u *res = eval_client_expr_to_string(s);
1176 #ifdef FEAT_MBYTE
1177     CONVERT_FROM_UTF8_FREE(s);
1178 #endif
1180     if (res != NULL) {
1181         s = res;
1182 #ifdef FEAT_MBYTE
1183         s = CONVERT_TO_UTF8(s);
1184 #endif
1185         eval = [NSString stringWithUTF8String:(char*)s];
1186 #ifdef FEAT_MBYTE
1187         CONVERT_TO_UTF8_FREE(s);
1188 #endif
1189         vim_free(res);
1190     }
1192     return eval;
1195 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1197     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1198         // If there is no pasteboard, return YES to indicate that there is text
1199         // to copy.
1200         if (!pboard)
1201             return YES;
1203         clip_copy_selection();
1205         // Get the text to put on the pasteboard.
1206         long_u llen = 0; char_u *str = 0;
1207         int type = clip_convert_selection(&str, &llen, &clip_star);
1208         if (type < 0)
1209             return NO;
1210         
1211         // TODO: Avoid overflow.
1212         int len = (int)llen;
1213 #ifdef FEAT_MBYTE
1214         if (output_conv.vc_type != CONV_NONE) {
1215             char_u *conv_str = string_convert(&output_conv, str, &len);
1216             if (conv_str) {
1217                 vim_free(str);
1218                 str = conv_str;
1219             }
1220         }
1221 #endif
1223         NSString *string = [[NSString alloc]
1224             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1226         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1227         [pboard declareTypes:types owner:nil];
1228         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1229     
1230         [string release];
1231         vim_free(str);
1233         return ok;
1234     }
1236     return NO;
1239 - (oneway void)addReply:(in bycopy NSString *)reply
1240                  server:(in byref id <MMVimServerProtocol>)server
1242     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1244     // Replies might come at any time and in any order so we keep them in an
1245     // array inside a dictionary with the send port used as key.
1247     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1248     // HACK! Assume connection uses mach ports.
1249     int port = [(NSMachPort*)[conn sendPort] machPort];
1250     NSNumber *key = [NSNumber numberWithInt:port];
1252     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1253     if (!replies) {
1254         replies = [NSMutableArray array];
1255         [serverReplyDict setObject:replies forKey:key];
1256     }
1258     [replies addObject:reply];
1261 - (void)addInput:(in bycopy NSString *)input
1262                  client:(in byref id <MMVimClientProtocol>)client
1264     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1266     [self addInput:input];
1267     [self addClient:(id)client];
1269     inputReceived = YES;
1272 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1273                  client:(in byref id <MMVimClientProtocol>)client
1275     [self addClient:(id)client];
1276     return [self evaluateExpression:expr];
1279 - (void)registerServerWithName:(NSString *)name
1281     NSString *svrName = name;
1282     NSConnection *svrConn = [NSConnection defaultConnection];
1283     unsigned i;
1285     for (i = 0; i < MMServerMax; ++i) {
1286         NSString *connName = [self connectionNameFromServerName:svrName];
1288         if ([svrConn registerName:connName]) {
1289             //NSLog(@"Registered server with name: %@", svrName);
1291             // TODO: Set request/reply time-outs to something else?
1292             //
1293             // Don't wait for requests (time-out means that the message is
1294             // dropped).
1295             [svrConn setRequestTimeout:0];
1296             //[svrConn setReplyTimeout:MMReplyTimeout];
1297             [svrConn setRootObject:self];
1299             char_u *s = (char_u*)[svrName UTF8String];
1300 #ifdef FEAT_MBYTE
1301             s = CONVERT_FROM_UTF8(s);
1302 #endif
1303             // NOTE: 'serverName' is a global variable
1304             serverName = vim_strsave(s);
1305 #ifdef FEAT_MBYTE
1306             CONVERT_FROM_UTF8_FREE(s);
1307 #endif
1308 #ifdef FEAT_EVAL
1309             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1310 #endif
1311 #ifdef FEAT_TITLE
1312             need_maketitle = TRUE;
1313 #endif
1314             [self queueMessage:SetServerNameMsgID data:
1315                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1316             break;
1317         }
1319         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1320     }
1323 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1324                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1325               silent:(BOOL)silent
1327     // NOTE: If 'name' equals 'serverName' then the request is local (client
1328     // and server are the same).  This case is not handled separately, so a
1329     // connection will be set up anyway (this simplifies the code).
1331     NSConnection *conn = [self connectionForServerName:name];
1332     if (!conn) {
1333         if (!silent) {
1334             char_u *s = (char_u*)[name UTF8String];
1335 #ifdef FEAT_MBYTE
1336             s = CONVERT_FROM_UTF8(s);
1337 #endif
1338             EMSG2(_(e_noserver), s);
1339 #ifdef FEAT_MBYTE
1340             CONVERT_FROM_UTF8_FREE(s);
1341 #endif
1342         }
1343         return NO;
1344     }
1346     if (port) {
1347         // HACK! Assume connection uses mach ports.
1348         *port = [(NSMachPort*)[conn sendPort] machPort];
1349     }
1351     id proxy = [conn rootProxy];
1352     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1354     @try {
1355         if (expr) {
1356             NSString *eval = [proxy evaluateExpression:string client:self];
1357             if (reply) {
1358                 if (eval) {
1359                     char_u *r = (char_u*)[eval UTF8String];
1360 #ifdef FEAT_MBYTE
1361                     r = CONVERT_FROM_UTF8(r);
1362 #endif
1363                     *reply = vim_strsave(r);
1364 #ifdef FEAT_MBYTE
1365                     CONVERT_FROM_UTF8_FREE(r);
1366 #endif
1367                 } else {
1368                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1369                 }
1370             }
1372             if (!eval)
1373                 return NO;
1374         } else {
1375             [proxy addInput:string client:self];
1376         }
1377     }
1378     @catch (NSException *e) {
1379         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1380         return NO;
1381     }
1383     return YES;
1386 - (NSArray *)serverList
1388     NSArray *list = nil;
1390     if ([self connection]) {
1391         id proxy = [connection rootProxy];
1392         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1394         @try {
1395             list = [proxy serverList];
1396         }
1397         @catch (NSException *e) {
1398             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1399         }
1400     } else {
1401         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1402     }
1404     return list;
1407 - (NSString *)peekForReplyOnPort:(int)port
1409     //NSLog(@"%s%d", _cmd, port);
1411     NSNumber *key = [NSNumber numberWithInt:port];
1412     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1413     if (replies && [replies count]) {
1414         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1415         //        [replies objectAtIndex:0]);
1416         return [replies objectAtIndex:0];
1417     }
1419     //NSLog(@"    No replies");
1420     return nil;
1423 - (NSString *)waitForReplyOnPort:(int)port
1425     //NSLog(@"%s%d", _cmd, port);
1426     
1427     NSConnection *conn = [self connectionForServerPort:port];
1428     if (!conn)
1429         return nil;
1431     NSNumber *key = [NSNumber numberWithInt:port];
1432     NSMutableArray *replies = nil;
1433     NSString *reply = nil;
1435     // Wait for reply as long as the connection to the server is valid (unless
1436     // user interrupts wait with Ctrl-C).
1437     while (!got_int && [conn isValid] &&
1438             !(replies = [serverReplyDict objectForKey:key])) {
1439         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1440                                  beforeDate:[NSDate distantFuture]];
1441     }
1443     if (replies) {
1444         if ([replies count] > 0) {
1445             reply = [[replies objectAtIndex:0] retain];
1446             //NSLog(@"    Got reply: %@", reply);
1447             [replies removeObjectAtIndex:0];
1448             [reply autorelease];
1449         }
1451         if ([replies count] == 0)
1452             [serverReplyDict removeObjectForKey:key];
1453     }
1455     return reply;
1458 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1460     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1461     if (client) {
1462         @try {
1463             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1464             [client addReply:reply server:self];
1465             return YES;
1466         }
1467         @catch (NSException *e) {
1468             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1469         }
1470     } else {
1471         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1472     }
1474     return NO;
1477 @end // MMBackend
1481 @implementation MMBackend (Private)
1483 - (void)processInputQueue
1485     // NOTE: One of the input events may cause this method to be called
1486     // recursively, so copy the input queue to a local variable and clear it
1487     // before starting to process input events (otherwise we could get stuck in
1488     // an endless loop).
1489     NSArray *q = [inputQueue copy];
1490     unsigned i, count = [q count];
1492     [inputQueue removeAllObjects];
1494     for (i = 0; i < count-1; i += 2) {
1495         int msgid = [[q objectAtIndex:i] intValue];
1496         id data = [q objectAtIndex:i+1];
1497         if ([data isEqual:[NSNull null]])
1498             data = nil;
1500         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1501         [self handleInputEvent:msgid data:data];
1502     }
1504     [q release];
1505     //NSLog(@"Clear input event queue");
1508 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1510     // NOTE: Be careful with what you do in this method.  Ideally, a message
1511     // should be handled by adding something to the input buffer and returning
1512     // immediately.  If you call a Vim function then it should not enter a loop
1513     // waiting for key presses or in any other way block the process.  The
1514     // reason for this being that only one message can be processed at a time,
1515     // so if another message is received while processing, then the new message
1516     // is dropped.  See also the comment in processInput:data:.
1518     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1520     if (SelectTabMsgID == msgid) {
1521         if (!data) return;
1522         const void *bytes = [data bytes];
1523         int idx = *((int*)bytes) + 1;
1524         //NSLog(@"Selecting tab %d", idx);
1525         send_tabline_event(idx);
1526     } else if (CloseTabMsgID == msgid) {
1527         if (!data) return;
1528         const void *bytes = [data bytes];
1529         int idx = *((int*)bytes) + 1;
1530         //NSLog(@"Closing tab %d", idx);
1531         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1532     } else if (AddNewTabMsgID == msgid) {
1533         //NSLog(@"Adding new tab");
1534         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1535     } else if (DraggedTabMsgID == msgid) {
1536         if (!data) return;
1537         const void *bytes = [data bytes];
1538         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1539         // based.
1540         int idx = *((int*)bytes);
1542         tabpage_move(idx);
1543     } else if (SetTextDimensionsMsgID == msgid) {
1544         if (!data) return;
1545         const void *bytes = [data bytes];
1546         int rows = *((int*)bytes);  bytes += sizeof(int);
1547         int cols = *((int*)bytes);  bytes += sizeof(int);
1549         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1550         // gui_resize_shell(), so we have to manually set the rows and columns
1551         // here.  (MacVim doesn't change the rows and columns to avoid
1552         // inconsistent states between Vim and MacVim.)
1553         [self setRows:rows columns:cols];
1555         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1556         gui_resize_shell(cols, rows);
1557     } else if (ExecuteMenuMsgID == msgid) {
1558         if (!data) return;
1559         const void *bytes = [data bytes];
1560         int tag = *((int*)bytes);  bytes += sizeof(int);
1562         vimmenu_T *menu = (vimmenu_T*)tag;
1563         // TODO!  Make sure 'menu' is a valid menu pointer!
1564         if (menu) {
1565             gui_menu_cb(menu);
1566         }
1567     } else if (ToggleToolbarMsgID == msgid) {
1568         [self handleToggleToolbar];
1569     } else if (ScrollbarEventMsgID == msgid) {
1570         [self handleScrollbarEvent:data];
1571     } else if (SetFontMsgID == msgid) {
1572         [self handleSetFont:data];
1573     } else if (VimShouldCloseMsgID == msgid) {
1574         gui_shell_closed();
1575     } else if (DropFilesMsgID == msgid) {
1576         [self handleDropFiles:data];
1577     } else if (DropStringMsgID == msgid) {
1578         [self handleDropString:data];
1579     } else if (GotFocusMsgID == msgid) {
1580         if (!gui.in_focus)
1581             [self focusChange:YES];
1582     } else if (LostFocusMsgID == msgid) {
1583         if (gui.in_focus)
1584             [self focusChange:NO];
1585     } else if (SetMouseShapeMsgID == msgid) {
1586         const void *bytes = [data bytes];
1587         int shape = *((int*)bytes);  bytes += sizeof(int);
1588         update_mouseshape(shape);
1589     } else {
1590         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1591     }
1594 + (NSDictionary *)specialKeys
1596     static NSDictionary *specialKeys = nil;
1598     if (!specialKeys) {
1599         NSBundle *mainBundle = [NSBundle mainBundle];
1600         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1601                                               ofType:@"plist"];
1602         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1603     }
1605     return specialKeys;
1608 - (void)handleInsertText:(NSData *)data
1610     if (!data) return;
1612     NSString *key = [[NSString alloc] initWithData:data
1613                                           encoding:NSUTF8StringEncoding];
1614     char_u *str = (char_u*)[key UTF8String];
1615     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1617 #ifdef FEAT_MBYTE
1618     char_u *conv_str = NULL;
1619     if (input_conv.vc_type != CONV_NONE) {
1620         conv_str = string_convert(&input_conv, str, &len);
1621         if (conv_str)
1622             str = conv_str;
1623     }
1624 #endif
1626     for (i = 0; i < len; ++i) {
1627         add_to_input_buf(str+i, 1);
1628         if (CSI == str[i]) {
1629             // NOTE: If the converted string contains the byte CSI, then it
1630             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1631             // won't work.
1632             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1633             add_to_input_buf(extra, 2);
1634         }
1635     }
1637 #ifdef FEAT_MBYTE
1638     if (conv_str)
1639         vim_free(conv_str);
1640 #endif
1641     [key release];
1644 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1646     char_u special[3];
1647     char_u modChars[3];
1648     char_u *chars = (char_u*)[key UTF8String];
1649 #ifdef FEAT_MBYTE
1650     char_u *conv_str = NULL;
1651 #endif
1652     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1654     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1655     // that new keys can easily be added.
1656     NSString *specialString = [[MMBackend specialKeys]
1657             objectForKey:key];
1658     if (specialString && [specialString length] > 1) {
1659         //NSLog(@"special key: %@", specialString);
1660         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1661                 [specialString characterAtIndex:1]);
1663         ikey = simplify_key(ikey, &mods);
1664         if (ikey == CSI)
1665             ikey = K_CSI;
1667         special[0] = CSI;
1668         special[1] = K_SECOND(ikey);
1669         special[2] = K_THIRD(ikey);
1671         chars = special;
1672         length = 3;
1673     } else if (1 == length && TAB == chars[0]) {
1674         // Tab is a trouble child:
1675         // - <Tab> is added to the input buffer as is
1676         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1677         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1678         //   to be converted to utf-8
1679         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1680         // - <C-Tab> is reserved by Mac OS X
1681         // - <D-Tab> is reserved by Mac OS X
1682         chars = special;
1683         special[0] = TAB;
1684         length = 1;
1686         if (mods & MOD_MASK_SHIFT) {
1687             mods &= ~MOD_MASK_SHIFT;
1688             special[0] = CSI;
1689             special[1] = K_SECOND(K_S_TAB);
1690             special[2] = K_THIRD(K_S_TAB);
1691             length = 3;
1692         } else if (mods & MOD_MASK_ALT) {
1693             int mtab = 0x80 | TAB;
1694 #ifdef FEAT_MBYTE
1695             if (enc_utf8) {
1696                 // Convert to utf-8
1697                 special[0] = (mtab >> 6) + 0xc0;
1698                 special[1] = mtab & 0xbf;
1699                 length = 2;
1700             } else
1701 #endif
1702             {
1703                 special[0] = mtab;
1704                 length = 1;
1705             }
1706             mods &= ~MOD_MASK_ALT;
1707         }
1708     } else if (length > 0) {
1709         unichar c = [key characterAtIndex:0];
1711         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1712         //        [key characterAtIndex:0], mods);
1714         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1715                 || (c == intr_char && intr_char != Ctrl_C))) {
1716             trash_input_buf();
1717             got_int = TRUE;
1718         }
1720         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1721         // cleared since they are already added to the key by the AppKit.
1722         // Unfortunately, the only way to deal with when to clear the modifiers
1723         // or not seems to be to have hard-wired rules like this.
1724         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1725                     || 0x9 == c || 0xd == c) ) {
1726             mods &= ~MOD_MASK_SHIFT;
1727             mods &= ~MOD_MASK_CTRL;
1728             //NSLog(@"clear shift ctrl");
1729         }
1731         // HACK!  All Option+key presses go via 'insert text' messages, except
1732         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1733         // not work to map to it.
1734         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1735             //NSLog(@"clear alt");
1736             mods &= ~MOD_MASK_ALT;
1737         }
1739 #ifdef FEAT_MBYTE
1740         if (input_conv.vc_type != CONV_NONE) {
1741             conv_str = string_convert(&input_conv, chars, &length);
1742             if (conv_str)
1743                 chars = conv_str;
1744         }
1745 #endif
1746     }
1748     if (chars && length > 0) {
1749         if (mods) {
1750             //NSLog(@"adding mods: %d", mods);
1751             modChars[0] = CSI;
1752             modChars[1] = KS_MODIFIER;
1753             modChars[2] = mods;
1754             add_to_input_buf(modChars, 3);
1755         }
1757         //NSLog(@"add to input buf: 0x%x", chars[0]);
1758         // TODO: Check for CSI bytes?
1759         add_to_input_buf(chars, length);
1760     }
1762 #ifdef FEAT_MBYTE
1763     if (conv_str)
1764         vim_free(conv_str);
1765 #endif
1768 - (void)queueMessage:(int)msgid data:(NSData *)data
1770     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1771     if (data)
1772         [outputQueue addObject:data];
1773     else
1774         [outputQueue addObject:[NSData data]];
1777 - (void)connectionDidDie:(NSNotification *)notification
1779     // If the main connection to MacVim is lost this means that MacVim was
1780     // either quit (by the user chosing Quit on the MacVim menu), or it has
1781     // crashed.  In the former case the flag 'isTerminating' is set and we then
1782     // quit cleanly; in the latter case we make sure the swap files are left
1783     // for recovery.
1785     NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1786     if (isTerminating)
1787         getout(0);
1788     else
1789         getout_preserve_modified(1);
1792 - (void)blinkTimerFired:(NSTimer *)timer
1794     NSTimeInterval timeInterval = 0;
1796     [blinkTimer release];
1797     blinkTimer = nil;
1799     if (MMBlinkStateOn == blinkState) {
1800         gui_undraw_cursor();
1801         blinkState = MMBlinkStateOff;
1802         timeInterval = blinkOffInterval;
1803     } else if (MMBlinkStateOff == blinkState) {
1804         gui_update_cursor(TRUE, FALSE);
1805         blinkState = MMBlinkStateOn;
1806         timeInterval = blinkOnInterval;
1807     }
1809     if (timeInterval > 0) {
1810         blinkTimer = 
1811             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1812                                             selector:@selector(blinkTimerFired:)
1813                                             userInfo:nil repeats:NO] retain];
1814         [self flushQueue:YES];
1815     }
1818 - (void)focusChange:(BOOL)on
1820     gui_focus_change(on);
1823 - (void)handleToggleToolbar
1825     // If 'go' contains 'T', then remove it, else add it.
1827     char_u go[sizeof(GO_ALL)+2];
1828     char_u *p;
1829     int len;
1831     STRCPY(go, p_go);
1832     p = vim_strchr(go, GO_TOOLBAR);
1833     len = STRLEN(go);
1835     if (p != NULL) {
1836         char_u *end = go + len;
1837         while (p < end) {
1838             p[0] = p[1];
1839             ++p;
1840         }
1841     } else {
1842         go[len] = GO_TOOLBAR;
1843         go[len+1] = NUL;
1844     }
1846     set_option_value((char_u*)"guioptions", 0, go, 0);
1848     // Force screen redraw (does it have to be this complicated?).
1849     redraw_all_later(CLEAR);
1850     update_screen(NOT_VALID);
1851     setcursor();
1852     out_flush();
1853     gui_update_cursor(FALSE, FALSE);
1854     gui_mch_flush();
1857 - (void)handleScrollbarEvent:(NSData *)data
1859     if (!data) return;
1861     const void *bytes = [data bytes];
1862     long ident = *((long*)bytes);  bytes += sizeof(long);
1863     int hitPart = *((int*)bytes);  bytes += sizeof(int);
1864     float fval = *((float*)bytes);  bytes += sizeof(float);
1865     scrollbar_T *sb = gui_find_scrollbar(ident);
1867     if (sb) {
1868         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1869         long value = sb_info->value;
1870         long size = sb_info->size;
1871         long max = sb_info->max;
1872         BOOL isStillDragging = NO;
1873         BOOL updateKnob = YES;
1875         switch (hitPart) {
1876         case NSScrollerDecrementPage:
1877             value -= (size > 2 ? size - 2 : 1);
1878             break;
1879         case NSScrollerIncrementPage:
1880             value += (size > 2 ? size - 2 : 1);
1881             break;
1882         case NSScrollerDecrementLine:
1883             --value;
1884             break;
1885         case NSScrollerIncrementLine:
1886             ++value;
1887             break;
1888         case NSScrollerKnob:
1889             isStillDragging = YES;
1890             // fall through ...
1891         case NSScrollerKnobSlot:
1892             value = (long)(fval * (max - size + 1));
1893             // fall through ...
1894         default:
1895             updateKnob = NO;
1896             break;
1897         }
1899         //NSLog(@"value %d -> %d", sb_info->value, value);
1900         gui_drag_scrollbar(sb, value, isStillDragging);
1902         if (updateKnob) {
1903             // Dragging the knob or option+clicking automatically updates
1904             // the knob position (on the actual NSScroller), so we only
1905             // need to set the knob position in the other cases.
1906             if (sb->wp) {
1907                 // Update both the left&right vertical scrollbars.
1908                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1909                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1910                 [self setScrollbarThumbValue:value size:size max:max
1911                                   identifier:identLeft];
1912                 [self setScrollbarThumbValue:value size:size max:max
1913                                   identifier:identRight];
1914             } else {
1915                 // Update the horizontal scrollbar.
1916                 [self setScrollbarThumbValue:value size:size max:max
1917                                   identifier:ident];
1918             }
1919         }
1920     }
1923 - (void)handleSetFont:(NSData *)data
1925     if (!data) return;
1927     const void *bytes = [data bytes];
1928     float pointSize = *((float*)bytes);  bytes += sizeof(float);
1929     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1930     bytes += sizeof(unsigned);  // len not used
1932     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1933     [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1934     char_u *s = (char_u*)[name UTF8String];
1936 #ifdef FEAT_MBYTE
1937     s = CONVERT_FROM_UTF8(s);
1938 #endif
1940     set_option_value((char_u*)"guifont", 0, s, 0);
1942 #ifdef FEAT_MBYTE
1943     CONVERT_FROM_UTF8_FREE(s);
1944 #endif
1946     // Force screen redraw (does it have to be this complicated?).
1947     redraw_all_later(CLEAR);
1948     update_screen(NOT_VALID);
1949     setcursor();
1950     out_flush();
1951     gui_update_cursor(FALSE, FALSE);
1952     gui_mch_flush();
1955 - (void)handleDropFiles:(NSData *)data
1957     if (!data) return;
1959 #ifdef FEAT_DND
1960     const void *bytes = [data bytes];
1961     const void *end = [data bytes] + [data length];
1962     int n = *((int*)bytes);  bytes += sizeof(int);
1964     if (State & CMDLINE) {
1965         // HACK!  If Vim is in command line mode then the files names
1966         // should be added to the command line, instead of opening the
1967         // files in tabs.  This is taken care of by gui_handle_drop().
1968         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1969         if (fnames) {
1970             int i = 0;
1971             while (bytes < end && i < n) {
1972                 int len = *((int*)bytes);  bytes += sizeof(int);
1973                 char_u *s = (char_u*)bytes;
1974 #ifdef FEAT_MBYTE
1975                 s = CONVERT_FROM_UTF8(s);
1976 #endif
1977                 fnames[i++] = vim_strsave(s);
1978 #ifdef FEAT_MBYTE
1979                 CONVERT_FROM_UTF8_FREE(s);
1980 #endif
1981                 bytes += len;
1982             }
1984             // NOTE!  This function will free 'fnames'.
1985             // HACK!  It is assumed that the 'x' and 'y' arguments are
1986             // unused when in command line mode.
1987             gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
1988         }
1989     } else {
1990         // HACK!  I'm not sure how to get Vim to open a list of files in
1991         // tabs, so instead I create a ':tab drop' command with all the
1992         // files to open and execute it.
1993         NSMutableString *cmd = [NSMutableString stringWithString:@":tab drop"];
1995         int i;
1996         for (i = 0; i < n && bytes < end; ++i) {
1997             int len = *((int*)bytes);  bytes += sizeof(int);
1998             NSString *file = [NSString stringWithUTF8String:bytes];
1999             file = [file stringByEscapingSpecialFilenameCharacters];
2000             bytes += len;
2002             [cmd appendString:@" "];
2003             [cmd appendString:file];
2004         }
2006         // By going to the last tabpage we ensure that the new tabs will
2007         // appear last (if this call is left out, the taborder becomes
2008         // messy).
2009         goto_tabpage(9999);
2011         char_u *s = (char_u*)[cmd UTF8String];
2012 #ifdef FEAT_MBYTE
2013         s = CONVERT_FROM_UTF8(s);
2014 #endif
2015         do_cmdline_cmd(s);
2016 #ifdef FEAT_MBYTE
2017         CONVERT_FROM_UTF8_FREE(s);
2018 #endif
2020         // Force screen redraw (does it have to be this complicated?).
2021         // (This code was taken from the end of gui_handle_drop().)
2022         update_screen(NOT_VALID);
2023         setcursor();
2024         out_flush();
2025         gui_update_cursor(FALSE, FALSE);
2026         maketitle();
2027         gui_mch_flush();
2028     }
2029 #endif // FEAT_DND
2032 - (void)handleDropString:(NSData *)data
2034     if (!data) return;
2036 #ifdef FEAT_DND
2037     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2038     const void *bytes = [data bytes];
2039     int len = *((int*)bytes);  bytes += sizeof(int);
2040     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2042     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2043     NSRange range = { 0, [string length] };
2044     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2045                                          withString:@"\x0a" options:0
2046                                               range:range];
2047     if (0 == n) {
2048         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2049                                        options:0 range:range];
2050     }
2052     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2053     char_u *s = (char_u*)[string UTF8String];
2054 #ifdef FEAT_MBYTE
2055     if (input_conv.vc_type != CONV_NONE)
2056         s = string_convert(&input_conv, s, &len);
2057 #endif
2058     dnd_yank_drag_data(s, len);
2059 #ifdef FEAT_MBYTE
2060     if (input_conv.vc_type != CONV_NONE)
2061         vim_free(s);
2062 #endif
2063     add_to_input_buf(dropkey, sizeof(dropkey));
2064 #endif // FEAT_DND
2067 - (BOOL)checkForModifiedBuffers
2069     buf_T *buf;
2070     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2071         if (bufIsChanged(buf)) {
2072             return YES;
2073         }
2074     }
2076     return NO;
2079 - (void)addInput:(NSString *)input
2081     char_u *s = (char_u*)[input UTF8String];
2083 #ifdef FEAT_MBYTE
2084     s = CONVERT_FROM_UTF8(s);
2085 #endif
2087     server_to_input_buf(s);
2089 #ifdef FEAT_MBYTE
2090     CONVERT_FROM_UTF8_FREE(s);
2091 #endif
2094 @end // MMBackend (Private)
2099 @implementation MMBackend (ClientServer)
2101 - (NSString *)connectionNameFromServerName:(NSString *)name
2103     NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2105     return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2106         lowercaseString];
2109 - (NSConnection *)connectionForServerName:(NSString *)name
2111     // TODO: Try 'name%d' if 'name' fails.
2112     NSString *connName = [self connectionNameFromServerName:name];
2113     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2115     if (!svrConn) {
2116         svrConn = [NSConnection connectionWithRegisteredName:connName
2117                                                            host:nil];
2118         // Try alternate server...
2119         if (!svrConn && alternateServerName) {
2120             //NSLog(@"  trying to connect to alternate server: %@",
2121             //        alternateServerName);
2122             connName = [self connectionNameFromServerName:alternateServerName];
2123             svrConn = [NSConnection connectionWithRegisteredName:connName
2124                                                             host:nil];
2125         }
2127         // Try looking for alternate servers...
2128         if (!svrConn) {
2129             //NSLog(@"  looking for alternate servers...");
2130             NSString *alt = [self alternateServerNameForName:name];
2131             if (alt != alternateServerName) {
2132                 //NSLog(@"  found alternate server: %@", string);
2133                 [alternateServerName release];
2134                 alternateServerName = [alt copy];
2135             }
2136         }
2138         // Try alternate server again...
2139         if (!svrConn && alternateServerName) {
2140             //NSLog(@"  trying to connect to alternate server: %@",
2141             //        alternateServerName);
2142             connName = [self connectionNameFromServerName:alternateServerName];
2143             svrConn = [NSConnection connectionWithRegisteredName:connName
2144                                                             host:nil];
2145         }
2147         if (svrConn) {
2148             [connectionNameDict setObject:svrConn forKey:connName];
2150             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2151             [[NSNotificationCenter defaultCenter] addObserver:self
2152                     selector:@selector(serverConnectionDidDie:)
2153                         name:NSConnectionDidDieNotification object:svrConn];
2154         }
2155     }
2157     return svrConn;
2160 - (NSConnection *)connectionForServerPort:(int)port
2162     NSConnection *conn;
2163     NSEnumerator *e = [connectionNameDict objectEnumerator];
2165     while ((conn = [e nextObject])) {
2166         // HACK! Assume connection uses mach ports.
2167         if (port == [(NSMachPort*)[conn sendPort] machPort])
2168             return conn;
2169     }
2171     return nil;
2174 - (void)serverConnectionDidDie:(NSNotification *)notification
2176     //NSLog(@"%s%@", _cmd, notification);
2178     NSConnection *svrConn = [notification object];
2180     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2181     [[NSNotificationCenter defaultCenter]
2182             removeObserver:self
2183                       name:NSConnectionDidDieNotification
2184                     object:svrConn];
2186     [connectionNameDict removeObjectsForKeys:
2187         [connectionNameDict allKeysForObject:svrConn]];
2189     // HACK! Assume connection uses mach ports.
2190     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2191     NSNumber *key = [NSNumber numberWithInt:port];
2193     [clientProxyDict removeObjectForKey:key];
2194     [serverReplyDict removeObjectForKey:key];
2197 - (void)addClient:(NSDistantObject *)client
2199     NSConnection *conn = [client connectionForProxy];
2200     // HACK! Assume connection uses mach ports.
2201     int port = [(NSMachPort*)[conn sendPort] machPort];
2202     NSNumber *key = [NSNumber numberWithInt:port];
2204     if (![clientProxyDict objectForKey:key]) {
2205         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2206         [clientProxyDict setObject:client forKey:key];
2207     }
2209     // NOTE: 'clientWindow' is a global variable which is used by <client>
2210     clientWindow = port;
2213 - (NSString *)alternateServerNameForName:(NSString *)name
2215     if (!(name && [name length] > 0))
2216         return nil;
2218     // Only look for alternates if 'name' doesn't end in a digit.
2219     unichar lastChar = [name characterAtIndex:[name length]-1];
2220     if (lastChar >= '0' && lastChar <= '9')
2221         return nil;
2223     // Look for alternates among all current servers.
2224     NSArray *list = [self serverList];
2225     if (!(list && [list count] > 0))
2226         return nil;
2228     // Filter out servers starting with 'name' and ending with a number. The
2229     // (?i) pattern ensures that the match is case insensitive.
2230     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2231     NSPredicate *pred = [NSPredicate predicateWithFormat:
2232             @"SELF MATCHES %@", pat];
2233     list = [list filteredArrayUsingPredicate:pred];
2234     if ([list count] > 0) {
2235         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2236         return [list objectAtIndex:0];
2237     }
2239     return nil;
2242 @end // MMBackend (ClientServer)
2247 @implementation NSString (MMServerNameCompare)
2248 - (NSComparisonResult)serverNameCompare:(NSString *)string
2250     return [self compare:string
2251                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2253 @end
2258 static int eventModifierFlagsToVimModMask(int modifierFlags)
2260     int modMask = 0;
2262     if (modifierFlags & NSShiftKeyMask)
2263         modMask |= MOD_MASK_SHIFT;
2264     if (modifierFlags & NSControlKeyMask)
2265         modMask |= MOD_MASK_CTRL;
2266     if (modifierFlags & NSAlternateKeyMask)
2267         modMask |= MOD_MASK_ALT;
2268     if (modifierFlags & NSCommandKeyMask)
2269         modMask |= MOD_MASK_CMD;
2271     return modMask;
2274 static int vimModMaskToEventModifierFlags(int mods)
2276     int flags = 0;
2278     if (mods & MOD_MASK_SHIFT)
2279         flags |= NSShiftKeyMask;
2280     if (mods & MOD_MASK_CTRL)
2281         flags |= NSControlKeyMask;
2282     if (mods & MOD_MASK_ALT)
2283         flags |= NSAlternateKeyMask;
2284     if (mods & MOD_MASK_CMD)
2285         flags |= NSCommandKeyMask;
2287     return flags;
2290 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2292     int modMask = 0;
2294     if (modifierFlags & NSShiftKeyMask)
2295         modMask |= MOUSE_SHIFT;
2296     if (modifierFlags & NSControlKeyMask)
2297         modMask |= MOUSE_CTRL;
2298     if (modifierFlags & NSAlternateKeyMask)
2299         modMask |= MOUSE_ALT;
2301     return modMask;
2304 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2306     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2308     return (buttonNumber >= 0 && buttonNumber < 3)
2309             ? mouseButton[buttonNumber] : -1;
2312 static int specialKeyToNSKey(int key)
2314     if (!IS_SPECIAL(key))
2315         return key;
2317     static struct {
2318         int special;
2319         int nskey;
2320     } sp2ns[] = {
2321         { K_UP, NSUpArrowFunctionKey },
2322         { K_DOWN, NSDownArrowFunctionKey },
2323         { K_LEFT, NSLeftArrowFunctionKey },
2324         { K_RIGHT, NSRightArrowFunctionKey },
2325         { K_F1, NSF1FunctionKey },
2326         { K_F2, NSF2FunctionKey },
2327         { K_F3, NSF3FunctionKey },
2328         { K_F4, NSF4FunctionKey },
2329         { K_F5, NSF5FunctionKey },
2330         { K_F6, NSF6FunctionKey },
2331         { K_F7, NSF7FunctionKey },
2332         { K_F8, NSF8FunctionKey },
2333         { K_F9, NSF9FunctionKey },
2334         { K_F10, NSF10FunctionKey },
2335         { K_F11, NSF11FunctionKey },
2336         { K_F12, NSF12FunctionKey },
2337         { K_F13, NSF13FunctionKey },
2338         { K_F14, NSF14FunctionKey },
2339         { K_F15, NSF15FunctionKey },
2340         { K_F16, NSF16FunctionKey },
2341         { K_F17, NSF17FunctionKey },
2342         { K_F18, NSF18FunctionKey },
2343         { K_F19, NSF19FunctionKey },
2344         { K_F20, NSF20FunctionKey },
2345         { K_F21, NSF21FunctionKey },
2346         { K_F22, NSF22FunctionKey },
2347         { K_F23, NSF23FunctionKey },
2348         { K_F24, NSF24FunctionKey },
2349         { K_F25, NSF25FunctionKey },
2350         { K_F26, NSF26FunctionKey },
2351         { K_F27, NSF27FunctionKey },
2352         { K_F28, NSF28FunctionKey },
2353         { K_F29, NSF29FunctionKey },
2354         { K_F30, NSF30FunctionKey },
2355         { K_F31, NSF31FunctionKey },
2356         { K_F32, NSF32FunctionKey },
2357         { K_F33, NSF33FunctionKey },
2358         { K_F34, NSF34FunctionKey },
2359         { K_F35, NSF35FunctionKey },
2360         { K_DEL, NSBackspaceCharacter },
2361         { K_BS, NSDeleteCharacter },
2362         { K_HOME, NSHomeFunctionKey },
2363         { K_END, NSEndFunctionKey },
2364         { K_PAGEUP, NSPageUpFunctionKey },
2365         { K_PAGEDOWN, NSPageDownFunctionKey }
2366     };
2368     int i;
2369     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2370         if (sp2ns[i].special == key)
2371             return sp2ns[i].nskey;
2372     }
2374     return 0;