Handle TerminateNow input message immediately
[MacVim.git] / src / MacVim / MMBackend.m
blobbc2ef3b9d7ea76ade90cc631f877ccd8cc854a87
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) ))
42 // Values for window layout (must match values in main.c).
43 #define WIN_HOR     1       // "-o" horizontally split windows
44 #define WIN_VER     2       // "-O" vertically split windows
45 #define WIN_TABS    3       // "-p" windows on tab pages
47 static unsigned MMServerMax = 1000;
49 // TODO: Move to separate file.
50 static int eventModifierFlagsToVimModMask(int modifierFlags);
51 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
52 static int eventButtonNumberToVimMouseButton(int buttonNumber);
54 // Before exiting process, sleep for this many microseconds.  This is to allow
55 // any distributed object messages in transit to be received by MacVim before
56 // the process dies (otherwise an error message is logged by Cocoa).  Note that
57 // this delay is only necessary if an NSConnection to MacVim has been
58 // established.
59 static useconds_t MMExitProcessDelay = 300000;
61 // In gui_macvim.m
62 vimmenu_T *menu_for_descriptor(NSArray *desc);
64 static id evalExprCocoa(NSString * expr, NSString ** errstr);
67 enum {
68     MMBlinkStateNone = 0,
69     MMBlinkStateOn,
70     MMBlinkStateOff
73 static NSString *MMSymlinkWarningString =
74     @"\n\n\tMost likely this is because you have symlinked directly to\n"
75      "\tthe Vim binary, which Cocoa does not allow.  Please use an\n"
76      "\talias or the mvim shell script instead.  If you have not used\n"
77      "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
81 @interface NSString (MMServerNameCompare)
82 - (NSComparisonResult)serverNameCompare:(NSString *)string;
83 @end
88 @interface MMBackend (Private)
89 - (void)waitForDialogReturn;
90 - (void)insertVimStateMessage;
91 - (void)processInputQueue;
92 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
93 + (NSDictionary *)specialKeys;
94 - (void)handleInsertText:(NSString *)text;
95 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
96 - (void)queueMessage:(int)msgid data:(NSData *)data;
97 - (void)connectionDidDie:(NSNotification *)notification;
98 - (void)blinkTimerFired:(NSTimer *)timer;
99 - (void)focusChange:(BOOL)on;
100 - (void)handleToggleToolbar;
101 - (void)handleScrollbarEvent:(NSData *)data;
102 - (void)handleSetFont:(NSData *)data;
103 - (void)handleDropFiles:(NSData *)data;
104 - (void)handleDropString:(NSData *)data;
105 - (void)startOdbEditWithArguments:(NSDictionary *)args;
106 - (void)handleXcodeMod:(NSData *)data;
107 - (void)handleOpenWithArguments:(NSDictionary *)args;
108 - (BOOL)checkForModifiedBuffers;
109 - (void)addInput:(NSString *)input;
110 - (BOOL)unusedEditor;
111 - (void)redrawScreen;
112 - (void)handleFindReplace:(NSDictionary *)args;
113 @end
117 @interface MMBackend (ClientServer)
118 - (NSString *)connectionNameFromServerName:(NSString *)name;
119 - (NSConnection *)connectionForServerName:(NSString *)name;
120 - (NSConnection *)connectionForServerPort:(int)port;
121 - (void)serverConnectionDidDie:(NSNotification *)notification;
122 - (void)addClient:(NSDistantObject *)client;
123 - (NSString *)alternateServerNameForName:(NSString *)name;
124 @end
128 @implementation MMBackend
130 + (MMBackend *)sharedInstance
132     static MMBackend *singleton = nil;
133     return singleton ? singleton : (singleton = [MMBackend new]);
136 - (id)init
138     self = [super init];
139     if (!self) return nil;
141     fontContainerRef = loadFonts();
143     outputQueue = [[NSMutableArray alloc] init];
144     inputQueue = [[NSMutableArray alloc] init];
145     drawData = [[NSMutableData alloc] initWithCapacity:1024];
146     connectionNameDict = [[NSMutableDictionary alloc] init];
147     clientProxyDict = [[NSMutableDictionary alloc] init];
148     serverReplyDict = [[NSMutableDictionary alloc] init];
150     NSBundle *mainBundle = [NSBundle mainBundle];
151     NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
152     if (path)
153         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
155     path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
156     if (path)
157         sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
158             retain];
160     path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
161     if (path)
162         actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
164     if (!(colorDict && sysColorDict && actionDict))
165         NSLog(@"ERROR: Failed to load dictionaries.%@",
166                 MMSymlinkWarningString);
168     return self;
171 - (void)dealloc
173     //NSLog(@"%@ %s", [self className], _cmd);
174     [[NSNotificationCenter defaultCenter] removeObserver:self];
176     [oldWideFont release];  oldWideFont = nil;
177     [blinkTimer release];  blinkTimer = nil;
178     [alternateServerName release];  alternateServerName = nil;
179     [serverReplyDict release];  serverReplyDict = nil;
180     [clientProxyDict release];  clientProxyDict = nil;
181     [connectionNameDict release];  connectionNameDict = nil;
182     [inputQueue release];  inputQueue = nil;
183     [outputQueue release];  outputQueue = nil;
184     [drawData release];  drawData = nil;
185     [frontendProxy release];  frontendProxy = nil;
186     [connection release];  connection = nil;
187     [actionDict release];  actionDict = nil;
188     [sysColorDict release];  sysColorDict = nil;
189     [colorDict release];  colorDict = nil;
191     [super dealloc];
194 - (void)setBackgroundColor:(int)color
196     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
199 - (void)setForegroundColor:(int)color
201     foregroundColor = MM_COLOR(color);
204 - (void)setSpecialColor:(int)color
206     specialColor = MM_COLOR(color);
209 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
211     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
212     defaultForegroundColor = MM_COLOR(fg);
214     NSMutableData *data = [NSMutableData data];
216     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
217     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
219     [self queueMessage:SetDefaultColorsMsgID data:data];
222 - (NSConnection *)connection
224     if (!connection) {
225         // NOTE!  If the name of the connection changes here it must also be
226         // updated in MMAppController.m.
227         NSString *name = [NSString stringWithFormat:@"%@-connection",
228                [[NSBundle mainBundle] bundlePath]];
230         connection = [NSConnection connectionWithRegisteredName:name host:nil];
231         [connection retain];
232     }
234     // NOTE: 'connection' may be nil here.
235     return connection;
238 - (NSDictionary *)actionDict
240     return actionDict;
243 - (int)initialWindowLayout
245     return initialWindowLayout;
248 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
250     [self queueMessage:msgid data:[props dictionaryAsData]];
253 - (BOOL)checkin
255     if (![self connection]) {
256         if (waitForAck) {
257             // This is a preloaded process and as such should not cause the
258             // MacVim to be opened.  We probably got here as a result of the
259             // user quitting MacVim while the process was preloading, so exit
260             // this process too.
261             // (Don't use mch_exit() since it assumes the process has properly
262             // started.)
263             exit(0);
264         }
266         NSBundle *mainBundle = [NSBundle mainBundle];
267 #if 0
268         OSStatus status;
269         FSRef ref;
271         // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
272         // the API to pass Apple Event parameters is broken on 10.4).
273         NSString *path = [mainBundle bundlePath];
274         status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
275         if (noErr == status) {
276             // Pass parameter to the 'Open' Apple Event that tells MacVim not
277             // to open an untitled window.
278             NSAppleEventDescriptor *desc =
279                     [NSAppleEventDescriptor recordDescriptor];
280             [desc setParamDescriptor:
281                     [NSAppleEventDescriptor descriptorWithBoolean:NO]
282                           forKeyword:keyMMUntitledWindow];
284             LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
285                     kLSLaunchDefaults, NULL };
286             status = LSOpenFromRefSpec(&spec, NULL);
287         }
289         if (noErr != status) {
290         NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
291                 path, MMSymlinkWarningString);
292             return NO;
293         }
294 #else
295         // Launch MacVim using NSTask.  For some reason the above code using
296         // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
297         // fails, the dock icon starts bouncing and never stops).  It seems
298         // like rebuilding the Launch Services database takes care of this
299         // problem, but the NSTask way seems more stable so stick with it.
300         //
301         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
302         // that the GUI won't be activated (or raised) so there is a hack in
303         // MMAppController which raises the app when a new window is opened.
304         NSMutableArray *args = [NSMutableArray arrayWithObjects:
305             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
306         NSString *exeName = [[mainBundle infoDictionary]
307                 objectForKey:@"CFBundleExecutable"];
308         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
309         if (!path) {
310             NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
311                     MMSymlinkWarningString);
312             return NO;
313         }
315         [NSTask launchedTaskWithLaunchPath:path arguments:args];
316 #endif
318         // HACK!  Poll the mach bootstrap server until it returns a valid
319         // connection to detect that MacVim has finished launching.  Also set a
320         // time-out date so that we don't get stuck doing this forever.
321         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
322         while (![self connection] &&
323                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
324             [[NSRunLoop currentRunLoop]
325                     runMode:NSDefaultRunLoopMode
326                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
328         // NOTE: [self connection] will set 'connection' as a side-effect.
329         if (!connection) {
330             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
331             return NO;
332         }
333     }
335     BOOL ok = NO;
336     @try {
337         [[NSNotificationCenter defaultCenter] addObserver:self
338                 selector:@selector(connectionDidDie:)
339                     name:NSConnectionDidDieNotification object:connection];
341         id proxy = [connection rootProxy];
342         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
344         int pid = [[NSProcessInfo processInfo] processIdentifier];
346         frontendProxy = [proxy connectBackend:self pid:pid];
347         if (frontendProxy) {
348             [frontendProxy retain];
349             [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
350             ok = YES;
351         }
352     }
353     @catch (NSException *e) {
354         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
355     }
357     return ok;
360 - (BOOL)openGUIWindow
362     [self queueMessage:OpenWindowMsgID data:nil];
363     return YES;
366 - (void)clearAll
368     int type = ClearAllDrawType;
370     // Any draw commands in queue are effectively obsolete since this clearAll
371     // will negate any effect they have, therefore we may as well clear the
372     // draw queue.
373     [drawData setLength:0];
375     [drawData appendBytes:&type length:sizeof(int)];
378 - (void)clearBlockFromRow:(int)row1 column:(int)col1
379                     toRow:(int)row2 column:(int)col2
381     int type = ClearBlockDrawType;
383     [drawData appendBytes:&type length:sizeof(int)];
385     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
386     [drawData appendBytes:&row1 length:sizeof(int)];
387     [drawData appendBytes:&col1 length:sizeof(int)];
388     [drawData appendBytes:&row2 length:sizeof(int)];
389     [drawData appendBytes:&col2 length:sizeof(int)];
392 - (void)deleteLinesFromRow:(int)row count:(int)count
393               scrollBottom:(int)bottom left:(int)left right:(int)right
395     int type = DeleteLinesDrawType;
397     [drawData appendBytes:&type length:sizeof(int)];
399     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
400     [drawData appendBytes:&row length:sizeof(int)];
401     [drawData appendBytes:&count length:sizeof(int)];
402     [drawData appendBytes:&bottom length:sizeof(int)];
403     [drawData appendBytes:&left length:sizeof(int)];
404     [drawData appendBytes:&right length:sizeof(int)];
407 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
408              cells:(int)cells flags:(int)flags
410     if (len <= 0 || cells <= 0) return;
412     int type = DrawStringDrawType;
414     [drawData appendBytes:&type length:sizeof(int)];
416     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
417     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
418     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
419     [drawData appendBytes:&row length:sizeof(int)];
420     [drawData appendBytes:&col length:sizeof(int)];
421     [drawData appendBytes:&cells length:sizeof(int)];
422     [drawData appendBytes:&flags length:sizeof(int)];
423     [drawData appendBytes:&len length:sizeof(int)];
424     [drawData appendBytes:s length:len];
427 - (void)insertLinesFromRow:(int)row count:(int)count
428               scrollBottom:(int)bottom left:(int)left right:(int)right
430     int type = InsertLinesDrawType;
432     [drawData appendBytes:&type length:sizeof(int)];
434     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
435     [drawData appendBytes:&row length:sizeof(int)];
436     [drawData appendBytes:&count length:sizeof(int)];
437     [drawData appendBytes:&bottom length:sizeof(int)];
438     [drawData appendBytes:&left length:sizeof(int)];
439     [drawData appendBytes:&right length:sizeof(int)];
442 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
443                fraction:(int)percent color:(int)color
445     int type = DrawCursorDrawType;
446     unsigned uc = MM_COLOR(color);
448     [drawData appendBytes:&type length:sizeof(int)];
450     [drawData appendBytes:&uc length:sizeof(unsigned)];
451     [drawData appendBytes:&row length:sizeof(int)];
452     [drawData appendBytes:&col length:sizeof(int)];
453     [drawData appendBytes:&shape length:sizeof(int)];
454     [drawData appendBytes:&percent length:sizeof(int)];
457 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
458                    numColumns:(int)nc invert:(int)invert
460     int type = DrawInvertedRectDrawType;
461     [drawData appendBytes:&type length:sizeof(int)];
463     [drawData appendBytes:&row length:sizeof(int)];
464     [drawData appendBytes:&col length:sizeof(int)];
465     [drawData appendBytes:&nr length:sizeof(int)];
466     [drawData appendBytes:&nc length:sizeof(int)];
467     [drawData appendBytes:&invert length:sizeof(int)];
470 - (void)update
472     // Keep running the run-loop until there is no more input to process.
473     while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
474             == kCFRunLoopRunHandledSource)
475         ;
478 - (void)flushQueue:(BOOL)force
480     // NOTE: This variable allows for better control over when the queue is
481     // flushed.  It can be set to YES at the beginning of a sequence of calls
482     // that may potentially add items to the queue, and then restored back to
483     // NO.
484     if (flushDisabled) return;
486     if ([drawData length] > 0) {
487         // HACK!  Detect changes to 'guifontwide'.
488         if (gui.wide_font != (GuiFont)oldWideFont) {
489             [oldWideFont release];
490             oldWideFont = [(NSFont*)gui.wide_font retain];
491             [self setWideFont:oldWideFont];
492         }
494         int type = SetCursorPosDrawType;
495         [drawData appendBytes:&type length:sizeof(type)];
496         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
497         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
499         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
500         [drawData setLength:0];
501     }
503     if ([outputQueue count] > 0) {
504         [self insertVimStateMessage];
506         @try {
507             [frontendProxy processCommandQueue:outputQueue];
508         }
509         @catch (NSException *e) {
510             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
511             NSLog(@"outputQueue(len:%d)=%@", [outputQueue count]/2,
512                     outputQueue);
513             if (![connection isValid]) {
514                 NSLog(@"WARNING! Connection is invalid, exit now!");
515                 NSLog(@"waitForAck=%d got_int=%d isTerminating=%d",
516                         waitForAck, got_int, isTerminating);
517                 mch_exit(-1);
518             }
519         }
521         [outputQueue removeAllObjects];
522     }
525 - (BOOL)waitForInput:(int)milliseconds
527     // Return NO if we timed out waiting for input, otherwise return YES.
528     BOOL inputReceived = NO;
530     // Only start the run loop if the input queue is empty, otherwise process
531     // the input first so that the input on queue isn't delayed.
532     if ([inputQueue count]) {
533         inputReceived = YES;
534     } else {
535         // Wait for the specified amount of time, unless 'milliseconds' is
536         // negative in which case we wait "forever" (1e6 seconds translates to
537         // approximately 11 days).
538         CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
540         while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
541                 == kCFRunLoopRunHandledSource) {
542             // In order to ensure that all input on the run-loop has been
543             // processed we set the timeout to 0 and keep processing until the
544             // run-loop times out.
545             dt = 0.0;
546             inputReceived = YES;
547         }
548     }
550     // The above calls may have placed messages on the input queue so process
551     // it now.  This call may enter a blocking loop.
552     if ([inputQueue count] > 0)
553         [self processInputQueue];
555     return inputReceived;
558 - (void)exit
560     // NOTE: This is called if mch_exit() is called.  Since we assume here that
561     // the process has started properly, be sure to use exit() instead of
562     // mch_exit() to prematurely terminate a process.
564     // To notify MacVim that this Vim process is exiting we could simply
565     // invalidate the connection and it would automatically receive a
566     // connectionDidDie: notification.  However, this notification seems to
567     // take up to 300 ms to arrive which is quite a noticeable delay.  Instead
568     // we immediately send a message to MacVim asking it to close the window
569     // belonging to this process, and then we invalidate the connection (in
570     // case the message got lost).
572     // Make sure no connectionDidDie: notification is received now that we are
573     // already exiting.
574     [[NSNotificationCenter defaultCenter] removeObserver:self];
576     if ([connection isValid]) {
577         @try {
578             // Flush the entire queue in case a VimLeave autocommand added
579             // something to the queue.
580             [self queueMessage:CloseWindowMsgID data:nil];
581             [frontendProxy processCommandQueue:outputQueue];
582         }
583         @catch (NSException *e) {
584             NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
585         }
587         [connection invalidate];
588     }
590 #ifdef MAC_CLIENTSERVER
591     // The default connection is used for the client/server code.
592     [[NSConnection defaultConnection] setRootObject:nil];
593     [[NSConnection defaultConnection] invalidate];
594 #endif
596     if (fontContainerRef) {
597         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
598         fontContainerRef = 0;
599     }
601     usleep(MMExitProcessDelay);
604 - (void)selectTab:(int)index
606     //NSLog(@"%s%d", _cmd, index);
608     index -= 1;
609     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
610     [self queueMessage:SelectTabMsgID data:data];
613 - (void)updateTabBar
615     //NSLog(@"%s", _cmd);
617     NSMutableData *data = [NSMutableData data];
619     int idx = tabpage_index(curtab) - 1;
620     [data appendBytes:&idx length:sizeof(int)];
622     tabpage_T *tp;
623     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
624         // This function puts the label of the tab in the global 'NameBuff'.
625         get_tabline_label(tp, FALSE);
626         char_u *s = NameBuff;
627         int len = STRLEN(s);
628         if (len <= 0) continue;
630 #ifdef FEAT_MBYTE
631         s = CONVERT_TO_UTF8(s);
632 #endif
634         // Count the number of windows in the tabpage.
635         //win_T *wp = tp->tp_firstwin;
636         //int wincount;
637         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
639         //[data appendBytes:&wincount length:sizeof(int)];
640         [data appendBytes:&len length:sizeof(int)];
641         [data appendBytes:s length:len];
643 #ifdef FEAT_MBYTE
644         CONVERT_TO_UTF8_FREE(s);
645 #endif
646     }
648     [self queueMessage:UpdateTabBarMsgID data:data];
651 - (BOOL)tabBarVisible
653     return tabBarVisible;
656 - (void)showTabBar:(BOOL)enable
658     tabBarVisible = enable;
660     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
661     [self queueMessage:msgid data:nil];
664 - (void)setRows:(int)rows columns:(int)cols
666     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
668     int dim[] = { rows, cols };
669     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
671     [self queueMessage:SetTextDimensionsMsgID data:data];
674 - (void)setWindowTitle:(char *)title
676     NSMutableData *data = [NSMutableData data];
677     int len = strlen(title);
678     if (len <= 0) return;
680     [data appendBytes:&len length:sizeof(int)];
681     [data appendBytes:title length:len];
683     [self queueMessage:SetWindowTitleMsgID data:data];
686 - (void)setDocumentFilename:(char *)filename
688     NSMutableData *data = [NSMutableData data];
689     int len = filename ? strlen(filename) : 0;
691     [data appendBytes:&len length:sizeof(int)];
692     if (len > 0)
693         [data appendBytes:filename length:len];
695     [self queueMessage:SetDocumentFilenameMsgID data:data];
698 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
700     char_u *s = NULL;
702     @try {
703         [frontendProxy showSavePanelWithAttributes:attr];
705         [self waitForDialogReturn];
707         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
708             s = [dialogReturn vimStringSave];
710         [dialogReturn release];  dialogReturn = nil;
711     }
712     @catch (NSException *e) {
713         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
714     }
716     return (char *)s;
719 - (oneway void)setDialogReturn:(in bycopy id)obj
721     // NOTE: This is called by
722     //   - [MMVimController panelDidEnd:::], and
723     //   - [MMVimController alertDidEnd:::],
724     // to indicate that a save/open panel or alert has finished.
726     // We want to distinguish between "no dialog return yet" and "dialog
727     // returned nothing".  The former can be tested with dialogReturn == nil,
728     // the latter with dialogReturn == [NSNull null].
729     if (!obj) obj = [NSNull null];
731     if (obj != dialogReturn) {
732         [dialogReturn release];
733         dialogReturn = [obj retain];
734     }
737 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
739     int retval = 0;
741     @try {
742         [frontendProxy presentDialogWithAttributes:attr];
744         [self waitForDialogReturn];
746         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
747                 && [dialogReturn count]) {
748             retval = [[dialogReturn objectAtIndex:0] intValue];
749             if (txtfield && [dialogReturn count] > 1) {
750                 NSString *retString = [dialogReturn objectAtIndex:1];
751                 char_u *ret = (char_u*)[retString UTF8String];
752 #ifdef FEAT_MBYTE
753                 ret = CONVERT_FROM_UTF8(ret);
754 #endif
755                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
756 #ifdef FEAT_MBYTE
757                 CONVERT_FROM_UTF8_FREE(ret);
758 #endif
759             }
760         }
762         [dialogReturn release]; dialogReturn = nil;
763     }
764     @catch (NSException *e) {
765         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
766     }
768     return retval;
771 - (void)showToolbar:(int)enable flags:(int)flags
773     NSMutableData *data = [NSMutableData data];
775     [data appendBytes:&enable length:sizeof(int)];
776     [data appendBytes:&flags length:sizeof(int)];
778     [self queueMessage:ShowToolbarMsgID data:data];
781 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
783     NSMutableData *data = [NSMutableData data];
785     [data appendBytes:&ident length:sizeof(long)];
786     [data appendBytes:&type length:sizeof(int)];
788     [self queueMessage:CreateScrollbarMsgID data:data];
791 - (void)destroyScrollbarWithIdentifier:(long)ident
793     NSMutableData *data = [NSMutableData data];
794     [data appendBytes:&ident length:sizeof(long)];
796     [self queueMessage:DestroyScrollbarMsgID data:data];
799 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
801     NSMutableData *data = [NSMutableData data];
803     [data appendBytes:&ident length:sizeof(long)];
804     [data appendBytes:&visible length:sizeof(int)];
806     [self queueMessage:ShowScrollbarMsgID data:data];
809 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
811     NSMutableData *data = [NSMutableData data];
813     [data appendBytes:&ident length:sizeof(long)];
814     [data appendBytes:&pos length:sizeof(int)];
815     [data appendBytes:&len length:sizeof(int)];
817     [self queueMessage:SetScrollbarPositionMsgID data:data];
820 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
821                     identifier:(long)ident
823     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
824     float prop = (float)size/(max+1);
825     if (fval < 0) fval = 0;
826     else if (fval > 1.0f) fval = 1.0f;
827     if (prop < 0) prop = 0;
828     else if (prop > 1.0f) prop = 1.0f;
830     NSMutableData *data = [NSMutableData data];
832     [data appendBytes:&ident length:sizeof(long)];
833     [data appendBytes:&fval length:sizeof(float)];
834     [data appendBytes:&prop length:sizeof(float)];
836     [self queueMessage:SetScrollbarThumbMsgID data:data];
839 - (void)setFont:(NSFont *)font
841     NSString *fontName = [font displayName];
842     float size = [font pointSize];
843     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
844     if (len > 0) {
845         NSMutableData *data = [NSMutableData data];
847         [data appendBytes:&size length:sizeof(float)];
848         [data appendBytes:&len length:sizeof(int)];
849         [data appendBytes:[fontName UTF8String] length:len];
851         [self queueMessage:SetFontMsgID data:data];
852     }
855 - (void)setWideFont:(NSFont *)font
857     NSString *fontName = [font displayName];
858     float size = [font pointSize];
859     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
860     NSMutableData *data = [NSMutableData data];
862     [data appendBytes:&size length:sizeof(float)];
863     [data appendBytes:&len length:sizeof(int)];
864     if (len > 0)
865         [data appendBytes:[fontName UTF8String] length:len];
867     [self queueMessage:SetWideFontMsgID data:data];
870 - (void)executeActionWithName:(NSString *)name
872     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
874     if (len > 0) {
875         NSMutableData *data = [NSMutableData data];
877         [data appendBytes:&len length:sizeof(int)];
878         [data appendBytes:[name UTF8String] length:len];
880         [self queueMessage:ExecuteActionMsgID data:data];
881     }
884 - (void)setMouseShape:(int)shape
886     NSMutableData *data = [NSMutableData data];
887     [data appendBytes:&shape length:sizeof(int)];
888     [self queueMessage:SetMouseShapeMsgID data:data];
891 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
893     // Vim specifies times in milliseconds, whereas Cocoa wants them in
894     // seconds.
895     blinkWaitInterval = .001f*wait;
896     blinkOnInterval = .001f*on;
897     blinkOffInterval = .001f*off;
900 - (void)startBlink
902     if (blinkTimer) {
903         [blinkTimer invalidate];
904         [blinkTimer release];
905         blinkTimer = nil;
906     }
908     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
909             && gui.in_focus) {
910         blinkState = MMBlinkStateOn;
911         blinkTimer =
912             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
913                                               target:self
914                                             selector:@selector(blinkTimerFired:)
915                                             userInfo:nil repeats:NO] retain];
916         gui_update_cursor(TRUE, FALSE);
917         [self flushQueue:YES];
918     }
921 - (void)stopBlink
923     if (MMBlinkStateOff == blinkState) {
924         gui_update_cursor(TRUE, FALSE);
925         [self flushQueue:YES];
926     }
928     blinkState = MMBlinkStateNone;
931 - (void)adjustLinespace:(int)linespace
933     NSMutableData *data = [NSMutableData data];
934     [data appendBytes:&linespace length:sizeof(int)];
935     [self queueMessage:AdjustLinespaceMsgID data:data];
938 - (void)activate
940     [self queueMessage:ActivateMsgID data:nil];
943 - (void)setPreEditRow:(int)row column:(int)col
945     NSMutableData *data = [NSMutableData data];
946     [data appendBytes:&row length:sizeof(int)];
947     [data appendBytes:&col length:sizeof(int)];
948     [self queueMessage:SetPreEditPositionMsgID data:data];
951 - (int)lookupColorWithKey:(NSString *)key
953     if (!(key && [key length] > 0))
954         return INVALCOLOR;
956     NSString *stripKey = [[[[key lowercaseString]
957         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
958             componentsSeparatedByString:@" "]
959                componentsJoinedByString:@""];
961     if (stripKey && [stripKey length] > 0) {
962         // First of all try to lookup key in the color dictionary; note that
963         // all keys in this dictionary are lowercase with no whitespace.
964         id obj = [colorDict objectForKey:stripKey];
965         if (obj) return [obj intValue];
967         // The key was not in the dictionary; is it perhaps of the form
968         // #rrggbb?
969         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
970             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
971             [scanner setScanLocation:1];
972             unsigned hex = 0;
973             if ([scanner scanHexInt:&hex]) {
974                 return (int)hex;
975             }
976         }
978         // As a last resort, check if it is one of the system defined colors.
979         // The keys in this dictionary are also lowercase with no whitespace.
980         obj = [sysColorDict objectForKey:stripKey];
981         if (obj) {
982             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
983             if (col) {
984                 float r, g, b, a;
985                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
986                 [col getRed:&r green:&g blue:&b alpha:&a];
987                 return (((int)(r*255+.5f) & 0xff) << 16)
988                      + (((int)(g*255+.5f) & 0xff) << 8)
989                      +  ((int)(b*255+.5f) & 0xff);
990             }
991         }
992     }
994     //NSLog(@"WARNING: No color with key %@ found.", stripKey);
995     return INVALCOLOR;
998 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
1000     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1001     id obj;
1003     while ((obj = [e nextObject])) {
1004         if ([value isEqual:obj])
1005             return YES;
1006     }
1008     return NO;
1011 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1013     NSMutableData *data = [NSMutableData data];
1014     [data appendBytes:&fuoptions length:sizeof(int)];
1015     bg = MM_COLOR(bg);
1016     [data appendBytes:&bg length:sizeof(int)];
1017     [self queueMessage:EnterFullscreenMsgID data:data];
1020 - (void)leaveFullscreen
1022     [self queueMessage:LeaveFullscreenMsgID data:nil];
1025 - (void)setFullscreenBackgroundColor:(int)color
1027     NSMutableData *data = [NSMutableData data];
1028     color = MM_COLOR(color);
1029     [data appendBytes:&color length:sizeof(int)];
1031     [self queueMessage:SetFullscreenColorMsgID data:data];
1034 - (void)setAntialias:(BOOL)antialias
1036     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1038     [self queueMessage:msgid 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     // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1054     // queue is processed since that only happens in waitForInput: (and Vim
1055     // regularly checks for Ctrl-C in between waiting for input).
1056     // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1057     // which waits on the run loop will fail to detect this message (e.g. in
1058     // waitForConnectionAcknowledgement).
1060     BOOL shouldClearQueue = NO;
1061     if (InterruptMsgID == msgid) {
1062         shouldClearQueue = YES;
1063         got_int = TRUE;
1064     } else if (InsertTextMsgID == msgid && data != nil) {
1065         const void *bytes = [data bytes];
1066         bytes += sizeof(int);
1067         int len = *((int*)bytes);  bytes += sizeof(int);
1068         if (1 == len) {
1069             char_u *str = (char_u*)bytes;
1070             if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1071                     (str[0] == intr_char && intr_char != Ctrl_C)) {
1072                 shouldClearQueue = YES;
1073                 got_int = TRUE;
1074             }
1075         }
1076     } else if (TerminateNowMsgID == msgid) {
1077         shouldClearQueue = YES;
1078         isTerminating = YES;
1079     }
1081     if (shouldClearQueue) {
1082         [inputQueue removeAllObjects];
1083         return;
1084     }
1086     // Remove all previous instances of this message from the input queue, else
1087     // the input queue may fill up as a result of Vim not being able to keep up
1088     // with the speed at which new messages are received.
1089     // Keyboard input is never dropped, unless the input represents and
1090     // auto-repeated key.
1092     BOOL isKeyRepeat = NO;
1093     BOOL isKeyboardInput = NO;
1095     if (data && (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1096             CmdKeyMsgID == msgid)) {
1097         isKeyboardInput = YES;
1099         // The lowest bit of the first int is set if this key is a repeat.
1100         int flags = *((int*)[data bytes]);
1101         if (flags & 1)
1102             isKeyRepeat = YES;
1103     }
1105     // Keyboard input is not removed from the queue; repeats are ignored if
1106     // there already is keyboard input on the input queue.
1107     if (isKeyRepeat || !isKeyboardInput) {
1108         int i, count = [inputQueue count];
1109         for (i = 1; i < count; i+=2) {
1110             if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1111                 if (isKeyRepeat)
1112                     return;
1114                 [inputQueue removeObjectAtIndex:i];
1115                 [inputQueue removeObjectAtIndex:i-1];
1116                 break;
1117             }
1118         }
1119     }
1121     [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1122     [inputQueue addObject:(data ? (id)data : [NSNull null])];
1125 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1127     // This is just a convenience method that allows the frontend to delay
1128     // sending messages.
1129     int i, count = [messages count];
1130     for (i = 1; i < count; i+=2)
1131         [self processInput:[[messages objectAtIndex:i-1] intValue]
1132                       data:[messages objectAtIndex:i]];
1135 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1136                   errorString:(out bycopy NSString **)errstr
1138     return evalExprCocoa(expr, errstr);
1142 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1144     NSString *eval = nil;
1145     char_u *s = (char_u*)[expr UTF8String];
1147 #ifdef FEAT_MBYTE
1148     s = CONVERT_FROM_UTF8(s);
1149 #endif
1151     char_u *res = eval_client_expr_to_string(s);
1153 #ifdef FEAT_MBYTE
1154     CONVERT_FROM_UTF8_FREE(s);
1155 #endif
1157     if (res != NULL) {
1158         s = res;
1159 #ifdef FEAT_MBYTE
1160         s = CONVERT_TO_UTF8(s);
1161 #endif
1162         eval = [NSString stringWithUTF8String:(char*)s];
1163 #ifdef FEAT_MBYTE
1164         CONVERT_TO_UTF8_FREE(s);
1165 #endif
1166         vim_free(res);
1167     }
1169     return eval;
1172 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1174     // TODO: This method should share code with clip_mch_request_selection().
1176     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1177         // If there is no pasteboard, return YES to indicate that there is text
1178         // to copy.
1179         if (!pboard)
1180             return YES;
1182         clip_copy_selection();
1184         // Get the text to put on the pasteboard.
1185         long_u llen = 0; char_u *str = 0;
1186         int type = clip_convert_selection(&str, &llen, &clip_star);
1187         if (type < 0)
1188             return NO;
1189         
1190         // TODO: Avoid overflow.
1191         int len = (int)llen;
1192 #ifdef FEAT_MBYTE
1193         if (output_conv.vc_type != CONV_NONE) {
1194             char_u *conv_str = string_convert(&output_conv, str, &len);
1195             if (conv_str) {
1196                 vim_free(str);
1197                 str = conv_str;
1198             }
1199         }
1200 #endif
1202         NSString *string = [[NSString alloc]
1203             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1205         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1206         [pboard declareTypes:types owner:nil];
1207         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1208     
1209         [string release];
1210         vim_free(str);
1212         return ok;
1213     }
1215     return NO;
1218 - (oneway void)addReply:(in bycopy NSString *)reply
1219                  server:(in byref id <MMVimServerProtocol>)server
1221     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1223     // Replies might come at any time and in any order so we keep them in an
1224     // array inside a dictionary with the send port used as key.
1226     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1227     // HACK! Assume connection uses mach ports.
1228     int port = [(NSMachPort*)[conn sendPort] machPort];
1229     NSNumber *key = [NSNumber numberWithInt:port];
1231     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1232     if (!replies) {
1233         replies = [NSMutableArray array];
1234         [serverReplyDict setObject:replies forKey:key];
1235     }
1237     [replies addObject:reply];
1240 - (void)addInput:(in bycopy NSString *)input
1241                  client:(in byref id <MMVimClientProtocol>)client
1243     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1245     [self addInput:input];
1246     [self addClient:(id)client];
1249 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1250                  client:(in byref id <MMVimClientProtocol>)client
1252     [self addClient:(id)client];
1253     return [self evaluateExpression:expr];
1256 - (void)registerServerWithName:(NSString *)name
1258     NSString *svrName = name;
1259     NSConnection *svrConn = [NSConnection defaultConnection];
1260     unsigned i;
1262     for (i = 0; i < MMServerMax; ++i) {
1263         NSString *connName = [self connectionNameFromServerName:svrName];
1265         if ([svrConn registerName:connName]) {
1266             //NSLog(@"Registered server with name: %@", svrName);
1268             // TODO: Set request/reply time-outs to something else?
1269             //
1270             // Don't wait for requests (time-out means that the message is
1271             // dropped).
1272             [svrConn setRequestTimeout:0];
1273             //[svrConn setReplyTimeout:MMReplyTimeout];
1274             [svrConn setRootObject:self];
1276             // NOTE: 'serverName' is a global variable
1277             serverName = [svrName vimStringSave];
1278 #ifdef FEAT_EVAL
1279             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1280 #endif
1281 #ifdef FEAT_TITLE
1282             need_maketitle = TRUE;
1283 #endif
1284             [self queueMessage:SetServerNameMsgID data:
1285                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1286             break;
1287         }
1289         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1290     }
1293 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1294                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1295               silent:(BOOL)silent
1297     // NOTE: If 'name' equals 'serverName' then the request is local (client
1298     // and server are the same).  This case is not handled separately, so a
1299     // connection will be set up anyway (this simplifies the code).
1301     NSConnection *conn = [self connectionForServerName:name];
1302     if (!conn) {
1303         if (!silent) {
1304             char_u *s = (char_u*)[name UTF8String];
1305 #ifdef FEAT_MBYTE
1306             s = CONVERT_FROM_UTF8(s);
1307 #endif
1308             EMSG2(_(e_noserver), s);
1309 #ifdef FEAT_MBYTE
1310             CONVERT_FROM_UTF8_FREE(s);
1311 #endif
1312         }
1313         return NO;
1314     }
1316     if (port) {
1317         // HACK! Assume connection uses mach ports.
1318         *port = [(NSMachPort*)[conn sendPort] machPort];
1319     }
1321     id proxy = [conn rootProxy];
1322     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1324     @try {
1325         if (expr) {
1326             NSString *eval = [proxy evaluateExpression:string client:self];
1327             if (reply) {
1328                 if (eval) {
1329                     *reply = [eval vimStringSave];
1330                 } else {
1331                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1332                 }
1333             }
1335             if (!eval)
1336                 return NO;
1337         } else {
1338             [proxy addInput:string client:self];
1339         }
1340     }
1341     @catch (NSException *e) {
1342         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1343         return NO;
1344     }
1346     return YES;
1349 - (NSArray *)serverList
1351     NSArray *list = nil;
1353     if ([self connection]) {
1354         id proxy = [connection rootProxy];
1355         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1357         @try {
1358             list = [proxy serverList];
1359         }
1360         @catch (NSException *e) {
1361             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1362         }
1363     } else {
1364         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1365     }
1367     return list;
1370 - (NSString *)peekForReplyOnPort:(int)port
1372     //NSLog(@"%s%d", _cmd, port);
1374     NSNumber *key = [NSNumber numberWithInt:port];
1375     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1376     if (replies && [replies count]) {
1377         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1378         //        [replies objectAtIndex:0]);
1379         return [replies objectAtIndex:0];
1380     }
1382     //NSLog(@"    No replies");
1383     return nil;
1386 - (NSString *)waitForReplyOnPort:(int)port
1388     //NSLog(@"%s%d", _cmd, port);
1389     
1390     NSConnection *conn = [self connectionForServerPort:port];
1391     if (!conn)
1392         return nil;
1394     NSNumber *key = [NSNumber numberWithInt:port];
1395     NSMutableArray *replies = nil;
1396     NSString *reply = nil;
1398     // Wait for reply as long as the connection to the server is valid (unless
1399     // user interrupts wait with Ctrl-C).
1400     while (!got_int && [conn isValid] &&
1401             !(replies = [serverReplyDict objectForKey:key])) {
1402         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1403                                  beforeDate:[NSDate distantFuture]];
1404     }
1406     if (replies) {
1407         if ([replies count] > 0) {
1408             reply = [[replies objectAtIndex:0] retain];
1409             //NSLog(@"    Got reply: %@", reply);
1410             [replies removeObjectAtIndex:0];
1411             [reply autorelease];
1412         }
1414         if ([replies count] == 0)
1415             [serverReplyDict removeObjectForKey:key];
1416     }
1418     return reply;
1421 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1423     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1424     if (client) {
1425         @try {
1426             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1427             [client addReply:reply server:self];
1428             return YES;
1429         }
1430         @catch (NSException *e) {
1431             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1432         }
1433     } else {
1434         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1435     }
1437     return NO;
1440 - (BOOL)waitForAck
1442     return waitForAck;
1445 - (void)setWaitForAck:(BOOL)yn
1447     waitForAck = yn;
1450 - (void)waitForConnectionAcknowledgement
1452     if (!waitForAck) return;
1454     while (waitForAck && !got_int && [connection isValid] && !isTerminating) {
1455         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1456                                  beforeDate:[NSDate distantFuture]];
1457         //NSLog(@"  waitForAck=%d got_int=%d isTerminating=%d isValid=%d",
1458         //        waitForAck, got_int, isTerminating, [connection isValid]);
1459     }
1461     if (waitForAck) {
1462         // Never received a connection acknowledgement, so die.
1463         [[NSNotificationCenter defaultCenter] removeObserver:self];
1464         [frontendProxy release];  frontendProxy = nil;
1466         // NOTE: We intentionally do not call mch_exit() since this in turn
1467         // will lead to -[MMBackend exit] getting called which we want to
1468         // avoid.
1469         usleep(MMExitProcessDelay);
1470         exit(0);
1471     }
1473     [self processInputQueue];
1476 - (oneway void)acknowledgeConnection
1478     //NSLog(@"%s", _cmd);
1479     waitForAck = NO;
1482 @end // MMBackend
1486 @implementation MMBackend (Private)
1488 - (void)waitForDialogReturn
1490     // Keep processing the run loop until a dialog returns.  To avoid getting
1491     // stuck in an endless loop (could happen if the setDialogReturn: message
1492     // was lost) we also do some paranoia checks.
1493     //
1494     // Note that in Cocoa the user can still resize windows and select menu
1495     // items while a sheet is being displayed, so we can't just wait for the
1496     // first message to arrive and assume that is the setDialogReturn: call.
1498     while (nil == dialogReturn && !got_int && [connection isValid]
1499             && !isTerminating)
1500         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1501                                  beforeDate:[NSDate distantFuture]];
1503     // Search for any resize messages on the input queue.  All other messages
1504     // on the input queue are dropped.  The reason why we single out resize
1505     // messages is because the user may have resized the window while a sheet
1506     // was open.
1507     int i, count = [inputQueue count];
1508     if (count > 0) {
1509         id textDimData = nil;
1510         if (count%2 == 0) {
1511             for (i = count-2; i >= 0; i -= 2) {
1512                 int msgid = [[inputQueue objectAtIndex:i] intValue];
1513                 if (SetTextDimensionsMsgID == msgid) {
1514                     textDimData = [[inputQueue objectAtIndex:i+1] retain];
1515                     break;
1516                 }
1517             }
1518         }
1520         [inputQueue removeAllObjects];
1522         if (textDimData) {
1523             [inputQueue addObject:
1524                     [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1525             [inputQueue addObject:textDimData];
1526             [textDimData release];
1527         }
1528     }
1531 - (void)insertVimStateMessage
1533     // NOTE: This is the place to add Vim state that needs to be accessed from
1534     // MacVim.  Do not add state that could potentially require lots of memory
1535     // since this message gets sent each time the output queue is forcibly
1536     // flushed (e.g. storing the currently selected text would be a bad idea).
1537     // We take this approach of "pushing" the state to MacVim to avoid having
1538     // to make synchronous calls from MacVim to Vim in order to get state.
1540     BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1542     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1543         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1544         [NSNumber numberWithInt:p_mh], @"p_mh",
1545         [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1546         [NSNumber numberWithBool:mmta], @"p_mmta",
1547         nil];
1549     // Put the state before all other messages.
1550     int msgid = SetVimStateMsgID;
1551     [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1552     [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1553                       atIndex:0];
1556 - (void)processInputQueue
1558     if ([inputQueue count] == 0) return;
1560     // NOTE: One of the input events may cause this method to be called
1561     // recursively, so copy the input queue to a local variable and clear the
1562     // queue before starting to process input events (otherwise we could get
1563     // stuck in an endless loop).
1564     NSArray *q = [inputQueue copy];
1565     unsigned i, count = [q count];
1567     [inputQueue removeAllObjects];
1569     for (i = 1; i < count; i+=2) {
1570         int msgid = [[q objectAtIndex:i-1] intValue];
1571         id data = [q objectAtIndex:i];
1572         if ([data isEqual:[NSNull null]])
1573             data = nil;
1575         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1576         [self handleInputEvent:msgid data:data];
1577     }
1579     [q release];
1580     //NSLog(@"Clear input event queue");
1583 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1585     if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1586             CmdKeyMsgID == msgid) {
1587         if (!data) return;
1588         const void *bytes = [data bytes];
1589         int mods = *((int*)bytes);  bytes += sizeof(int);
1590         int len = *((int*)bytes);  bytes += sizeof(int);
1591         NSString *key = [[NSString alloc] initWithBytes:bytes
1592                                                  length:len
1593                                                encoding:NSUTF8StringEncoding];
1594         mods = eventModifierFlagsToVimModMask(mods);
1596         if (InsertTextMsgID == msgid)
1597             [self handleInsertText:key];
1598         else
1599             [self handleKeyDown:key modifiers:mods];
1601         [key release];
1602     } else if (ScrollWheelMsgID == msgid) {
1603         if (!data) return;
1604         const void *bytes = [data bytes];
1606         int row = *((int*)bytes);  bytes += sizeof(int);
1607         int col = *((int*)bytes);  bytes += sizeof(int);
1608         int flags = *((int*)bytes);  bytes += sizeof(int);
1609         float dy = *((float*)bytes);  bytes += sizeof(float);
1611         int button = MOUSE_5;
1612         if (dy > 0) button = MOUSE_4;
1614         flags = eventModifierFlagsToVimMouseModMask(flags);
1616         int numLines = (int)round(dy);
1617         if (numLines < 0) numLines = -numLines;
1618         if (numLines == 0) numLines = 1;
1620 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1621         gui.scroll_wheel_force = numLines;
1622 #endif
1624         gui_send_mouse_event(button, col, row, NO, flags);
1625     } else if (MouseDownMsgID == msgid) {
1626         if (!data) return;
1627         const void *bytes = [data bytes];
1629         int row = *((int*)bytes);  bytes += sizeof(int);
1630         int col = *((int*)bytes);  bytes += sizeof(int);
1631         int button = *((int*)bytes);  bytes += sizeof(int);
1632         int flags = *((int*)bytes);  bytes += sizeof(int);
1633         int count = *((int*)bytes);  bytes += sizeof(int);
1635         button = eventButtonNumberToVimMouseButton(button);
1636         if (button >= 0) {
1637             flags = eventModifierFlagsToVimMouseModMask(flags);
1638             gui_send_mouse_event(button, col, row, count>1, flags);
1639         }
1640     } else if (MouseUpMsgID == msgid) {
1641         if (!data) return;
1642         const void *bytes = [data bytes];
1644         int row = *((int*)bytes);  bytes += sizeof(int);
1645         int col = *((int*)bytes);  bytes += sizeof(int);
1646         int flags = *((int*)bytes);  bytes += sizeof(int);
1648         flags = eventModifierFlagsToVimMouseModMask(flags);
1650         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1651     } else if (MouseDraggedMsgID == msgid) {
1652         if (!data) return;
1653         const void *bytes = [data bytes];
1655         int row = *((int*)bytes);  bytes += sizeof(int);
1656         int col = *((int*)bytes);  bytes += sizeof(int);
1657         int flags = *((int*)bytes);  bytes += sizeof(int);
1659         flags = eventModifierFlagsToVimMouseModMask(flags);
1661         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1662     } else if (MouseMovedMsgID == msgid) {
1663         const void *bytes = [data bytes];
1664         int row = *((int*)bytes);  bytes += sizeof(int);
1665         int col = *((int*)bytes);  bytes += sizeof(int);
1667         gui_mouse_moved(col, row);
1668     } else if (AddInputMsgID == msgid) {
1669         NSString *string = [[NSString alloc] initWithData:data
1670                 encoding:NSUTF8StringEncoding];
1671         if (string) {
1672             [self addInput:string];
1673             [string release];
1674         }
1675     } else if (SelectTabMsgID == msgid) {
1676         if (!data) return;
1677         const void *bytes = [data bytes];
1678         int idx = *((int*)bytes) + 1;
1679         //NSLog(@"Selecting tab %d", idx);
1680         send_tabline_event(idx);
1681     } else if (CloseTabMsgID == msgid) {
1682         if (!data) return;
1683         const void *bytes = [data bytes];
1684         int idx = *((int*)bytes) + 1;
1685         //NSLog(@"Closing tab %d", idx);
1686         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1687     } else if (AddNewTabMsgID == msgid) {
1688         //NSLog(@"Adding new tab");
1689         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1690     } else if (DraggedTabMsgID == msgid) {
1691         if (!data) return;
1692         const void *bytes = [data bytes];
1693         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1694         // based.
1695         int idx = *((int*)bytes);
1697         tabpage_move(idx);
1698     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1699             || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1700         if (!data) return;
1701         const void *bytes = [data bytes];
1702         int rows = Rows;
1703         if (SetTextColumnsMsgID != msgid) {
1704             rows = *((int*)bytes);  bytes += sizeof(int);
1705         }
1706         int cols = Columns;
1707         if (SetTextRowsMsgID != msgid) {
1708             cols = *((int*)bytes);  bytes += sizeof(int);
1709         }
1711         NSData *d = data;
1712         if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1713             int dim[2] = { rows, cols };
1714             d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1715             msgid = SetTextDimensionsReplyMsgID;
1716         }
1718         if (SetTextDimensionsMsgID == msgid)
1719             msgid = SetTextDimensionsReplyMsgID;
1721         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1722         // gui_resize_shell(), so we have to manually set the rows and columns
1723         // here since MacVim doesn't change the rows and columns to avoid
1724         // inconsistent states between Vim and MacVim.  The message sent back
1725         // indicates that it is a reply to a message that originated in MacVim
1726         // since we need to be able to determine where a message originated.
1727         [self queueMessage:msgid data:d];
1729         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1730         gui_resize_shell(cols, rows);
1731     } else if (ExecuteMenuMsgID == msgid) {
1732         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1733         if (attrs) {
1734             NSArray *desc = [attrs objectForKey:@"descriptor"];
1735             vimmenu_T *menu = menu_for_descriptor(desc);
1736             if (menu)
1737                 gui_menu_cb(menu);
1738         }
1739     } else if (ToggleToolbarMsgID == msgid) {
1740         [self handleToggleToolbar];
1741     } else if (ScrollbarEventMsgID == msgid) {
1742         [self handleScrollbarEvent:data];
1743     } else if (SetFontMsgID == msgid) {
1744         [self handleSetFont:data];
1745     } else if (VimShouldCloseMsgID == msgid) {
1746         gui_shell_closed();
1747     } else if (DropFilesMsgID == msgid) {
1748         [self handleDropFiles:data];
1749     } else if (DropStringMsgID == msgid) {
1750         [self handleDropString:data];
1751     } else if (GotFocusMsgID == msgid) {
1752         if (!gui.in_focus)
1753             [self focusChange:YES];
1754     } else if (LostFocusMsgID == msgid) {
1755         if (gui.in_focus)
1756             [self focusChange:NO];
1757     } else if (SetMouseShapeMsgID == msgid) {
1758         const void *bytes = [data bytes];
1759         int shape = *((int*)bytes);  bytes += sizeof(int);
1760         update_mouseshape(shape);
1761     } else if (XcodeModMsgID == msgid) {
1762         [self handleXcodeMod:data];
1763     } else if (OpenWithArgumentsMsgID == msgid) {
1764         [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1765     } else if (FindReplaceMsgID == msgid) {
1766         [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1767     } else {
1768         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1769     }
1772 + (NSDictionary *)specialKeys
1774     static NSDictionary *specialKeys = nil;
1776     if (!specialKeys) {
1777         NSBundle *mainBundle = [NSBundle mainBundle];
1778         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1779                                               ofType:@"plist"];
1780         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1781     }
1783     return specialKeys;
1786 - (void)handleInsertText:(NSString *)text
1788     if (!text) return;
1790     char_u *str = (char_u*)[text UTF8String];
1791     int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1793 #ifdef FEAT_MBYTE
1794     char_u *conv_str = NULL;
1795     if (input_conv.vc_type != CONV_NONE) {
1796         conv_str = string_convert(&input_conv, str, &len);
1797         if (conv_str)
1798             str = conv_str;
1799     }
1800 #endif
1802     for (i = 0; i < len; ++i) {
1803         add_to_input_buf(str+i, 1);
1804         if (CSI == str[i]) {
1805             // NOTE: If the converted string contains the byte CSI, then it
1806             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1807             // won't work.
1808             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1809             add_to_input_buf(extra, 2);
1810         }
1811     }
1813 #ifdef FEAT_MBYTE
1814     if (conv_str)
1815         vim_free(conv_str);
1816 #endif
1819 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1821     // TODO: This code is a horrible mess -- clean up!
1822     char_u special[3];
1823     char_u modChars[3];
1824     char_u *chars = (char_u*)[key UTF8String];
1825 #ifdef FEAT_MBYTE
1826     char_u *conv_str = NULL;
1827 #endif
1828     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1830     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1831     // that new keys can easily be added.
1832     NSString *specialString = [[MMBackend specialKeys]
1833             objectForKey:key];
1834     if (specialString && [specialString length] > 1) {
1835         //NSLog(@"special key: %@", specialString);
1836         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1837                 [specialString characterAtIndex:1]);
1839         ikey = simplify_key(ikey, &mods);
1840         if (ikey == CSI)
1841             ikey = K_CSI;
1843         special[0] = CSI;
1844         special[1] = K_SECOND(ikey);
1845         special[2] = K_THIRD(ikey);
1847         chars = special;
1848         length = 3;
1849     } else if (1 == length && TAB == chars[0]) {
1850         // Tab is a trouble child:
1851         // - <Tab> is added to the input buffer as is
1852         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1853         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1854         //   to be converted to utf-8
1855         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1856         // - <C-Tab> is reserved by Mac OS X
1857         // - <D-Tab> is reserved by Mac OS X
1858         chars = special;
1859         special[0] = TAB;
1860         length = 1;
1862         if (mods & MOD_MASK_SHIFT) {
1863             mods &= ~MOD_MASK_SHIFT;
1864             special[0] = CSI;
1865             special[1] = K_SECOND(K_S_TAB);
1866             special[2] = K_THIRD(K_S_TAB);
1867             length = 3;
1868         } else if (mods & MOD_MASK_ALT) {
1869             int mtab = 0x80 | TAB;
1870 #ifdef FEAT_MBYTE
1871             if (enc_utf8) {
1872                 // Convert to utf-8
1873                 special[0] = (mtab >> 6) + 0xc0;
1874                 special[1] = mtab & 0xbf;
1875                 length = 2;
1876             } else
1877 #endif
1878             {
1879                 special[0] = mtab;
1880                 length = 1;
1881             }
1882             mods &= ~MOD_MASK_ALT;
1883         }
1884     } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1885         // META key is treated separately.  This code was taken from gui_w48.c
1886         // and gui_gtk_x11.c.
1887         char_u string[7];
1888         int ch = simplify_key(chars[0], &mods);
1890         // Remove the SHIFT modifier for keys where it's already included,
1891         // e.g., '(' and '*'
1892         if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1893             mods &= ~MOD_MASK_SHIFT;
1895         // Interpret the ALT key as making the key META, include SHIFT, etc.
1896         ch = extract_modifiers(ch, &mods);
1897         if (ch == CSI)
1898             ch = K_CSI;
1900         int len = 0;
1901         if (mods) {
1902             string[len++] = CSI;
1903             string[len++] = KS_MODIFIER;
1904             string[len++] = mods;
1905         }
1907         if (IS_SPECIAL(ch)) {
1908             string[len++] = CSI;
1909             string[len++] = K_SECOND(ch);
1910             string[len++] = K_THIRD(ch);
1911         } else {
1912             string[len++] = ch;
1913 #ifdef FEAT_MBYTE
1914             // TODO: What if 'enc' is not "utf-8"?
1915             if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1916                 string[len++] = ch & 0xbf;
1917                 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1918                 if (string[len-1] == CSI) {
1919                     string[len++] = KS_EXTRA;
1920                     string[len++] = (int)KE_CSI;
1921                 }
1922             }
1923 #endif
1924         }
1926         add_to_input_buf(string, len);
1927         return;
1928     } else if (length > 0) {
1929         unichar c = [key characterAtIndex:0];
1930         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1931         //        [key characterAtIndex:0], mods);
1933         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1934         // cleared since they are already added to the key by the AppKit.
1935         // Unfortunately, the only way to deal with when to clear the modifiers
1936         // or not seems to be to have hard-wired rules like this.
1937         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1938                     || 0x9 == c || 0xd == c || ESC == c) ) {
1939             mods &= ~MOD_MASK_SHIFT;
1940             mods &= ~MOD_MASK_CTRL;
1941             //NSLog(@"clear shift ctrl");
1942         }
1944 #ifdef FEAT_MBYTE
1945         if (input_conv.vc_type != CONV_NONE) {
1946             conv_str = string_convert(&input_conv, chars, &length);
1947             if (conv_str)
1948                 chars = conv_str;
1949         }
1950 #endif
1951     }
1953     if (chars && length > 0) {
1954         if (mods) {
1955             //NSLog(@"adding mods: %d", mods);
1956             modChars[0] = CSI;
1957             modChars[1] = KS_MODIFIER;
1958             modChars[2] = mods;
1959             add_to_input_buf(modChars, 3);
1960         }
1962         //NSLog(@"add to input buf: 0x%x", chars[0]);
1963         // TODO: Check for CSI bytes?
1964         add_to_input_buf(chars, length);
1965     }
1967 #ifdef FEAT_MBYTE
1968     if (conv_str)
1969         vim_free(conv_str);
1970 #endif
1973 - (void)queueMessage:(int)msgid data:(NSData *)data
1975     //if (msgid != EnableMenuItemMsgID)
1976     //    NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1978     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1979     if (data)
1980         [outputQueue addObject:data];
1981     else
1982         [outputQueue addObject:[NSData data]];
1985 - (void)connectionDidDie:(NSNotification *)notification
1987     // If the main connection to MacVim is lost this means that MacVim was
1988     // either quit (by the user chosing Quit on the MacVim menu), or it has
1989     // crashed.  In the former case the flag 'isTerminating' is set and we then
1990     // quit cleanly; in the latter case we make sure the swap files are left
1991     // for recovery.
1992     //
1993     // NOTE: This is not called if a Vim controller invalidates its connection.
1995     //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1996     if (isTerminating)
1997         getout(0);
1998     else
1999         getout_preserve_modified(1);
2002 - (void)blinkTimerFired:(NSTimer *)timer
2004     NSTimeInterval timeInterval = 0;
2006     [blinkTimer release];
2007     blinkTimer = nil;
2009     if (MMBlinkStateOn == blinkState) {
2010         gui_undraw_cursor();
2011         blinkState = MMBlinkStateOff;
2012         timeInterval = blinkOffInterval;
2013     } else if (MMBlinkStateOff == blinkState) {
2014         gui_update_cursor(TRUE, FALSE);
2015         blinkState = MMBlinkStateOn;
2016         timeInterval = blinkOnInterval;
2017     }
2019     if (timeInterval > 0) {
2020         blinkTimer = 
2021             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2022                                             selector:@selector(blinkTimerFired:)
2023                                             userInfo:nil repeats:NO] retain];
2024         [self flushQueue:YES];
2025     }
2028 - (void)focusChange:(BOOL)on
2030     gui_focus_change(on);
2033 - (void)handleToggleToolbar
2035     // If 'go' contains 'T', then remove it, else add it.
2037     char_u go[sizeof(GO_ALL)+2];
2038     char_u *p;
2039     int len;
2041     STRCPY(go, p_go);
2042     p = vim_strchr(go, GO_TOOLBAR);
2043     len = STRLEN(go);
2045     if (p != NULL) {
2046         char_u *end = go + len;
2047         while (p < end) {
2048             p[0] = p[1];
2049             ++p;
2050         }
2051     } else {
2052         go[len] = GO_TOOLBAR;
2053         go[len+1] = NUL;
2054     }
2056     set_option_value((char_u*)"guioptions", 0, go, 0);
2058     [self redrawScreen];
2061 - (void)handleScrollbarEvent:(NSData *)data
2063     if (!data) return;
2065     const void *bytes = [data bytes];
2066     long ident = *((long*)bytes);  bytes += sizeof(long);
2067     int hitPart = *((int*)bytes);  bytes += sizeof(int);
2068     float fval = *((float*)bytes);  bytes += sizeof(float);
2069     scrollbar_T *sb = gui_find_scrollbar(ident);
2071     if (sb) {
2072         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2073         long value = sb_info->value;
2074         long size = sb_info->size;
2075         long max = sb_info->max;
2076         BOOL isStillDragging = NO;
2077         BOOL updateKnob = YES;
2079         switch (hitPart) {
2080         case NSScrollerDecrementPage:
2081             value -= (size > 2 ? size - 2 : 1);
2082             break;
2083         case NSScrollerIncrementPage:
2084             value += (size > 2 ? size - 2 : 1);
2085             break;
2086         case NSScrollerDecrementLine:
2087             --value;
2088             break;
2089         case NSScrollerIncrementLine:
2090             ++value;
2091             break;
2092         case NSScrollerKnob:
2093             isStillDragging = YES;
2094             // fall through ...
2095         case NSScrollerKnobSlot:
2096             value = (long)(fval * (max - size + 1));
2097             // fall through ...
2098         default:
2099             updateKnob = NO;
2100             break;
2101         }
2103         //NSLog(@"value %d -> %d", sb_info->value, value);
2104         gui_drag_scrollbar(sb, value, isStillDragging);
2106         if (updateKnob) {
2107             // Dragging the knob or option+clicking automatically updates
2108             // the knob position (on the actual NSScroller), so we only
2109             // need to set the knob position in the other cases.
2110             if (sb->wp) {
2111                 // Update both the left&right vertical scrollbars.
2112                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2113                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2114                 [self setScrollbarThumbValue:value size:size max:max
2115                                   identifier:identLeft];
2116                 [self setScrollbarThumbValue:value size:size max:max
2117                                   identifier:identRight];
2118             } else {
2119                 // Update the horizontal scrollbar.
2120                 [self setScrollbarThumbValue:value size:size max:max
2121                                   identifier:ident];
2122             }
2123         }
2124     }
2127 - (void)handleSetFont:(NSData *)data
2129     if (!data) return;
2131     const void *bytes = [data bytes];
2132     float pointSize = *((float*)bytes);  bytes += sizeof(float);
2133     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2134     bytes += sizeof(unsigned);  // len not used
2136     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2137     [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
2138     char_u *s = (char_u*)[name UTF8String];
2140 #ifdef FEAT_MBYTE
2141     s = CONVERT_FROM_UTF8(s);
2142 #endif
2144     set_option_value((char_u*)"guifont", 0, s, 0);
2146 #ifdef FEAT_MBYTE
2147     CONVERT_FROM_UTF8_FREE(s);
2148 #endif
2150     [self redrawScreen];
2153 - (void)handleDropFiles:(NSData *)data
2155     // TODO: Get rid of this method; instead use Vim script directly.  At the
2156     // moment I know how to do this to open files in tabs, but I'm not sure how
2157     // to add the filenames to the command line when in command line mode.
2159     if (!data) return;
2161     NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2162     if (!args) return;
2164     id obj = [args objectForKey:@"forceOpen"];
2165     BOOL forceOpen = YES;
2166     if (obj)
2167         forceOpen = [obj boolValue];
2169     NSArray *filenames = [args objectForKey:@"filenames"];
2170     if (!(filenames && [filenames count] > 0)) return;
2172 #ifdef FEAT_DND
2173     if (!forceOpen && (State & CMDLINE)) {
2174         // HACK!  If Vim is in command line mode then the files names
2175         // should be added to the command line, instead of opening the
2176         // files in tabs (unless forceOpen is set).  This is taken care of by
2177         // gui_handle_drop().
2178         int n = [filenames count];
2179         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2180         if (fnames) {
2181             int i = 0;
2182             for (i = 0; i < n; ++i)
2183                 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2185             // NOTE!  This function will free 'fnames'.
2186             // HACK!  It is assumed that the 'x' and 'y' arguments are
2187             // unused when in command line mode.
2188             gui_handle_drop(0, 0, 0, fnames, n);
2189         }
2190     } else
2191 #endif // FEAT_DND
2192     {
2193         [self handleOpenWithArguments:args];
2194     }
2197 - (void)handleDropString:(NSData *)data
2199     if (!data) return;
2201 #ifdef FEAT_DND
2202     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2203     const void *bytes = [data bytes];
2204     int len = *((int*)bytes);  bytes += sizeof(int);
2205     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2207     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2208     NSRange range = { 0, [string length] };
2209     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2210                                          withString:@"\x0a" options:0
2211                                               range:range];
2212     if (0 == n) {
2213         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2214                                        options:0 range:range];
2215     }
2217     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2218     char_u *s = (char_u*)[string UTF8String];
2219 #ifdef FEAT_MBYTE
2220     if (input_conv.vc_type != CONV_NONE)
2221         s = string_convert(&input_conv, s, &len);
2222 #endif
2223     dnd_yank_drag_data(s, len);
2224 #ifdef FEAT_MBYTE
2225     if (input_conv.vc_type != CONV_NONE)
2226         vim_free(s);
2227 #endif
2228     add_to_input_buf(dropkey, sizeof(dropkey));
2229 #endif // FEAT_DND
2232 - (void)startOdbEditWithArguments:(NSDictionary *)args
2234 #ifdef FEAT_ODB_EDITOR
2235     id obj = [args objectForKey:@"remoteID"];
2236     if (!obj) return;
2238     OSType serverID = [obj unsignedIntValue];
2239     NSString *remotePath = [args objectForKey:@"remotePath"];
2241     NSAppleEventDescriptor *token = nil;
2242     NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2243     obj = [args objectForKey:@"remoteTokenDescType"];
2244     if (tokenData && obj) {
2245         DescType tokenType = [obj unsignedLongValue];
2246         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2247                                                                 data:tokenData];
2248     }
2250     NSArray *filenames = [args objectForKey:@"filenames"];
2251     unsigned i, numFiles = [filenames count];
2252     for (i = 0; i < numFiles; ++i) {
2253         NSString *filename = [filenames objectAtIndex:i];
2254         char_u *s = [filename vimStringSave];
2255         buf_T *buf = buflist_findname(s);
2256         vim_free(s);
2258         if (buf) {
2259             if (buf->b_odb_token) {
2260                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2261                 buf->b_odb_token = NULL;
2262             }
2264             if (buf->b_odb_fname) {
2265                 vim_free(buf->b_odb_fname);
2266                 buf->b_odb_fname = NULL;
2267             }
2269             buf->b_odb_server_id = serverID;
2271             if (token)
2272                 buf->b_odb_token = [token retain];
2273             if (remotePath)
2274                 buf->b_odb_fname = [remotePath vimStringSave];
2275         } else {
2276             NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2277                     filename);
2278         }
2279     }
2280 #endif // FEAT_ODB_EDITOR
2283 - (void)handleXcodeMod:(NSData *)data
2285 #if 0
2286     const void *bytes = [data bytes];
2287     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2288     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2289     if (0 == len)
2290         return;
2292     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2293             descriptorWithDescriptorType:type
2294                                    bytes:bytes
2295                                   length:len];
2296 #endif
2299 - (void)handleOpenWithArguments:(NSDictionary *)args
2301     // ARGUMENT:                DESCRIPTION:
2302     // -------------------------------------------------------------
2303     // filenames                list of filenames
2304     // dontOpen                 don't open files specified in above argument
2305     // layout                   which layout to use to open files
2306     // selectionRange           range of lines to select
2307     // searchText               string to search for
2308     // cursorLine               line to position the cursor on
2309     // cursorColumn             column to position the cursor on
2310     //                          (only valid when "cursorLine" is set)
2311     // remoteID                 ODB parameter
2312     // remotePath               ODB parameter
2313     // remoteTokenDescType      ODB parameter
2314     // remoteTokenData          ODB parameter
2316     //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2318     NSArray *filenames = [args objectForKey:@"filenames"];
2319     int i, numFiles = filenames ? [filenames count] : 0;
2320     BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2321     int layout = [[args objectForKey:@"layout"] intValue];
2323     // Change to directory of first file to open if this is an "unused" editor
2324     // (but do not do this if editing remotely).
2325     if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2326             && (starting || [self unusedEditor]) ) {
2327         char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2328         vim_chdirfile(s);
2329         vim_free(s);
2330     }
2332     if (starting > 0) {
2333         // When Vim is starting we simply add the files to be opened to the
2334         // global arglist and Vim will take care of opening them for us.
2335         if (openFiles && numFiles > 0) {
2336             for (i = 0; i < numFiles; i++) {
2337                 NSString *fname = [filenames objectAtIndex:i];
2338                 char_u *p = NULL;
2340                 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2341                         || (p = [fname vimStringSave]) == NULL)
2342                     exit(2); // See comment in -[MMBackend exit]
2343                 else
2344                     alist_add(&global_alist, p, 2);
2345             }
2347             // Vim will take care of arranging the files added to the arglist
2348             // in windows or tabs; all we must do is to specify which layout to
2349             // use.
2350             initialWindowLayout = layout;
2351         }
2352     } else {
2353         // When Vim is already open we resort to some trickery to open the
2354         // files with the specified layout.
2355         //
2356         // TODO: Figure out a better way to handle this?
2357         if (openFiles && numFiles > 0) {
2358             BOOL oneWindowInTab = topframe ? YES
2359                                            : (topframe->fr_layout == FR_LEAF);
2360             BOOL bufChanged = NO;
2361             BOOL bufHasFilename = NO;
2362             if (curbuf) {
2363                 bufChanged = curbufIsChanged();
2364                 bufHasFilename = curbuf->b_ffname != NULL;
2365             }
2367             // Temporarily disable flushing since the following code may
2368             // potentially cause multiple redraws.
2369             flushDisabled = YES;
2371             BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2372             if (WIN_TABS == layout && !onlyOneTab) {
2373                 // By going to the last tabpage we ensure that the new tabs
2374                 // will appear last (if this call is left out, the taborder
2375                 // becomes messy).
2376                 goto_tabpage(9999);
2377             }
2379             // Make sure we're in normal mode first.
2380             [self addInput:@"<C-\\><C-N>"];
2382             if (numFiles > 1) {
2383                 // With "split layout" we open a new tab before opening
2384                 // multiple files if the current tab has more than one window
2385                 // or if there is exactly one window but whose buffer has a
2386                 // filename.  (The :drop command ensures modified buffers get
2387                 // their own window.)
2388                 if ((WIN_HOR == layout || WIN_VER == layout) &&
2389                         (!oneWindowInTab || bufHasFilename))
2390                     [self addInput:@":tabnew<CR>"];
2392                 // The files are opened by constructing a ":drop ..." command
2393                 // and executing it.
2394                 NSMutableString *cmd = (WIN_TABS == layout)
2395                         ? [NSMutableString stringWithString:@":tab drop"]
2396                         : [NSMutableString stringWithString:@":drop"];
2398                 for (i = 0; i < numFiles; ++i) {
2399                     NSString *file = [filenames objectAtIndex:i];
2400                     file = [file stringByEscapingSpecialFilenameCharacters];
2401                     [cmd appendString:@" "];
2402                     [cmd appendString:file];
2403                 }
2405                 // Temporarily clear 'suffixes' so that the files are opened in
2406                 // the same order as they appear in the "filenames" array.
2407                 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2409                 [self addInput:cmd];
2411                 // Split the view into multiple windows if requested.
2412                 if (WIN_HOR == layout)
2413                     [self addInput:@"|sall"];
2414                 else if (WIN_VER == layout)
2415                     [self addInput:@"|vert sall"];
2417                 // Restore the old value of 'suffixes'.
2418                 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu"];
2420                 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2421                 [self addInput:@"|redr|f<CR>"];
2422             } else {
2423                 // When opening one file we try to reuse the current window,
2424                 // but not if its buffer is modified or has a filename.
2425                 // However, the 'arglist' layout always opens the file in the
2426                 // current window.
2427                 NSString *file = [[filenames lastObject]
2428                         stringByEscapingSpecialFilenameCharacters];
2429                 NSString *cmd;
2430                 if (WIN_HOR == layout) {
2431                     if (!(bufHasFilename || bufChanged))
2432                         cmd = [NSString stringWithFormat:@":e %@", file];
2433                     else
2434                         cmd = [NSString stringWithFormat:@":sp %@", file];
2435                 } else if (WIN_VER == layout) {
2436                     if (!(bufHasFilename || bufChanged))
2437                         cmd = [NSString stringWithFormat:@":e %@", file];
2438                     else
2439                         cmd = [NSString stringWithFormat:@":vsp %@", file];
2440                 } else if (WIN_TABS == layout) {
2441                     if (oneWindowInTab && !(bufHasFilename || bufChanged))
2442                         cmd = [NSString stringWithFormat:@":e %@", file];
2443                     else
2444                         cmd = [NSString stringWithFormat:@":tabe %@", file];
2445                 } else {
2446                     // (The :drop command will split if there is a modified
2447                     // buffer.)
2448                     cmd = [NSString stringWithFormat:@":drop %@", file];
2449                 }
2451                 [self addInput:cmd];
2453                 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2454                 [self addInput:@"|redr|f<CR>"];
2455             }
2457             // Force screen redraw (does it have to be this complicated?).
2458             // (This code was taken from the end of gui_handle_drop().)
2459             update_screen(NOT_VALID);
2460             setcursor();
2461             out_flush();
2462             gui_update_cursor(FALSE, FALSE);
2463             maketitle();
2465             flushDisabled = NO;
2466         }
2467     }
2469     if ([args objectForKey:@"remoteID"]) {
2470         // NOTE: We have to delay processing any ODB related arguments since
2471         // the file(s) may not be opened until the input buffer is processed.
2472         [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2473                                withObject:args
2474                             waitUntilDone:NO];
2475     }
2477     NSString *lineString = [args objectForKey:@"cursorLine"];
2478     if (lineString && [lineString intValue] > 0) {
2479         NSString *columnString = [args objectForKey:@"cursorColumn"];
2480         if (!(columnString && [columnString intValue] > 0))
2481             columnString = @"1";
2483         NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2484                 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2485         [self addInput:cmd];
2486     }
2488     NSString *rangeString = [args objectForKey:@"selectionRange"];
2489     if (rangeString) {
2490         // Build a command line string that will select the given range of
2491         // lines.  If range.length == 0, then position the cursor on the given
2492         // line but do not select.
2493         NSRange range = NSRangeFromString(rangeString);
2494         NSString *cmd;
2495         if (range.length > 0) {
2496             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2497                     NSMaxRange(range), range.location];
2498         } else {
2499             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2500                     range.location];
2501         }
2503         [self addInput:cmd];
2504     }
2506     NSString *searchText = [args objectForKey:@"searchText"];
2507     if (searchText) {
2508         // TODO: Searching is an exclusive motion, so if the pattern would
2509         // match on row 0 column 0 then this pattern will miss that match.
2510         [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@<CR>",
2511                 searchText]];
2512     }
2515 - (BOOL)checkForModifiedBuffers
2517     buf_T *buf;
2518     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2519         if (bufIsChanged(buf)) {
2520             return YES;
2521         }
2522     }
2524     return NO;
2527 - (void)addInput:(NSString *)input
2529     char_u *s = (char_u*)[input UTF8String];
2531 #ifdef FEAT_MBYTE
2532     s = CONVERT_FROM_UTF8(s);
2533 #endif
2535     server_to_input_buf(s);
2537 #ifdef FEAT_MBYTE
2538     CONVERT_FROM_UTF8_FREE(s);
2539 #endif
2542 - (BOOL)unusedEditor
2544     BOOL oneWindowInTab = topframe ? YES
2545                                    : (topframe->fr_layout == FR_LEAF);
2546     BOOL bufChanged = NO;
2547     BOOL bufHasFilename = NO;
2548     if (curbuf) {
2549         bufChanged = curbufIsChanged();
2550         bufHasFilename = curbuf->b_ffname != NULL;
2551     }
2553     BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2555     return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2558 - (void)redrawScreen
2560     // Force screen redraw (does it have to be this complicated?).
2561     redraw_all_later(CLEAR);
2562     update_screen(NOT_VALID);
2563     setcursor();
2564     out_flush();
2565     gui_update_cursor(FALSE, FALSE);
2567     // HACK! The cursor is not put back at the command line by the above
2568     // "redraw commands".  The following test seems to do the trick though.
2569     if (State & CMDLINE)
2570         redrawcmdline();
2573 - (void)handleFindReplace:(NSDictionary *)args
2575     if (!args) return;
2577     NSString *findString = [args objectForKey:@"find"];
2578     if (!findString) return;
2580     char_u *find = [findString vimStringSave];
2581     char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2582     int flags = [[args objectForKey:@"flags"] intValue];
2584     // NOTE: The flag 0x100 is used to indicate a backward search.
2585     gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2587     vim_free(find);
2588     vim_free(replace);
2591 @end // MMBackend (Private)
2596 @implementation MMBackend (ClientServer)
2598 - (NSString *)connectionNameFromServerName:(NSString *)name
2600     NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2602     return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2603         lowercaseString];
2606 - (NSConnection *)connectionForServerName:(NSString *)name
2608     // TODO: Try 'name%d' if 'name' fails.
2609     NSString *connName = [self connectionNameFromServerName:name];
2610     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2612     if (!svrConn) {
2613         svrConn = [NSConnection connectionWithRegisteredName:connName
2614                                                            host:nil];
2615         // Try alternate server...
2616         if (!svrConn && alternateServerName) {
2617             //NSLog(@"  trying to connect to alternate server: %@",
2618             //        alternateServerName);
2619             connName = [self connectionNameFromServerName:alternateServerName];
2620             svrConn = [NSConnection connectionWithRegisteredName:connName
2621                                                             host:nil];
2622         }
2624         // Try looking for alternate servers...
2625         if (!svrConn) {
2626             //NSLog(@"  looking for alternate servers...");
2627             NSString *alt = [self alternateServerNameForName:name];
2628             if (alt != alternateServerName) {
2629                 //NSLog(@"  found alternate server: %@", string);
2630                 [alternateServerName release];
2631                 alternateServerName = [alt copy];
2632             }
2633         }
2635         // Try alternate server again...
2636         if (!svrConn && alternateServerName) {
2637             //NSLog(@"  trying to connect to alternate server: %@",
2638             //        alternateServerName);
2639             connName = [self connectionNameFromServerName:alternateServerName];
2640             svrConn = [NSConnection connectionWithRegisteredName:connName
2641                                                             host:nil];
2642         }
2644         if (svrConn) {
2645             [connectionNameDict setObject:svrConn forKey:connName];
2647             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2648             [[NSNotificationCenter defaultCenter] addObserver:self
2649                     selector:@selector(serverConnectionDidDie:)
2650                         name:NSConnectionDidDieNotification object:svrConn];
2651         }
2652     }
2654     return svrConn;
2657 - (NSConnection *)connectionForServerPort:(int)port
2659     NSConnection *conn;
2660     NSEnumerator *e = [connectionNameDict objectEnumerator];
2662     while ((conn = [e nextObject])) {
2663         // HACK! Assume connection uses mach ports.
2664         if (port == [(NSMachPort*)[conn sendPort] machPort])
2665             return conn;
2666     }
2668     return nil;
2671 - (void)serverConnectionDidDie:(NSNotification *)notification
2673     //NSLog(@"%s%@", _cmd, notification);
2675     NSConnection *svrConn = [notification object];
2677     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2678     [[NSNotificationCenter defaultCenter]
2679             removeObserver:self
2680                       name:NSConnectionDidDieNotification
2681                     object:svrConn];
2683     [connectionNameDict removeObjectsForKeys:
2684         [connectionNameDict allKeysForObject:svrConn]];
2686     // HACK! Assume connection uses mach ports.
2687     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2688     NSNumber *key = [NSNumber numberWithInt:port];
2690     [clientProxyDict removeObjectForKey:key];
2691     [serverReplyDict removeObjectForKey:key];
2694 - (void)addClient:(NSDistantObject *)client
2696     NSConnection *conn = [client connectionForProxy];
2697     // HACK! Assume connection uses mach ports.
2698     int port = [(NSMachPort*)[conn sendPort] machPort];
2699     NSNumber *key = [NSNumber numberWithInt:port];
2701     if (![clientProxyDict objectForKey:key]) {
2702         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2703         [clientProxyDict setObject:client forKey:key];
2704     }
2706     // NOTE: 'clientWindow' is a global variable which is used by <client>
2707     clientWindow = port;
2710 - (NSString *)alternateServerNameForName:(NSString *)name
2712     if (!(name && [name length] > 0))
2713         return nil;
2715     // Only look for alternates if 'name' doesn't end in a digit.
2716     unichar lastChar = [name characterAtIndex:[name length]-1];
2717     if (lastChar >= '0' && lastChar <= '9')
2718         return nil;
2720     // Look for alternates among all current servers.
2721     NSArray *list = [self serverList];
2722     if (!(list && [list count] > 0))
2723         return nil;
2725     // Filter out servers starting with 'name' and ending with a number. The
2726     // (?i) pattern ensures that the match is case insensitive.
2727     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2728     NSPredicate *pred = [NSPredicate predicateWithFormat:
2729             @"SELF MATCHES %@", pat];
2730     list = [list filteredArrayUsingPredicate:pred];
2731     if ([list count] > 0) {
2732         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2733         return [list objectAtIndex:0];
2734     }
2736     return nil;
2739 @end // MMBackend (ClientServer)
2744 @implementation NSString (MMServerNameCompare)
2745 - (NSComparisonResult)serverNameCompare:(NSString *)string
2747     return [self compare:string
2748                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2750 @end
2755 static int eventModifierFlagsToVimModMask(int modifierFlags)
2757     int modMask = 0;
2759     if (modifierFlags & NSShiftKeyMask)
2760         modMask |= MOD_MASK_SHIFT;
2761     if (modifierFlags & NSControlKeyMask)
2762         modMask |= MOD_MASK_CTRL;
2763     if (modifierFlags & NSAlternateKeyMask)
2764         modMask |= MOD_MASK_ALT;
2765     if (modifierFlags & NSCommandKeyMask)
2766         modMask |= MOD_MASK_CMD;
2768     return modMask;
2771 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2773     int modMask = 0;
2775     if (modifierFlags & NSShiftKeyMask)
2776         modMask |= MOUSE_SHIFT;
2777     if (modifierFlags & NSControlKeyMask)
2778         modMask |= MOUSE_CTRL;
2779     if (modifierFlags & NSAlternateKeyMask)
2780         modMask |= MOUSE_ALT;
2782     return modMask;
2785 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2787     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2789     return (buttonNumber >= 0 && buttonNumber < 3)
2790             ? mouseButton[buttonNumber] : -1;
2795 // This function is modeled after the VimToPython function found in if_python.c
2796 // NB This does a deep copy by value, it does not lookup references like the
2797 // VimToPython function does.  This is because I didn't want to deal with the
2798 // retain cycles that this would create, and we can cover 99% of the use cases
2799 // by ignoring it.  If we ever switch to using GC in MacVim then this
2800 // functionality can be implemented easily.
2801 static id vimToCocoa(typval_T * tv, int depth)
2803     id result = nil;
2804     id newObj = nil;
2807     // Avoid infinite recursion
2808     if (depth > 100) {
2809         return nil;
2810     }
2812     if (tv->v_type == VAR_STRING) {
2813         char_u * val = tv->vval.v_string;
2814         // val can be NULL if the string is empty
2815         if (!val) {
2816             result = [NSString string];
2817         } else {
2818 #ifdef FEAT_MBYTE
2819             val = CONVERT_TO_UTF8(val);
2820 #endif
2821             result = [NSString stringWithUTF8String:(char*)val];
2822 #ifdef FEAT_MBYTE
2823             CONVERT_TO_UTF8_FREE(val);
2824 #endif
2825         }
2826     } else if (tv->v_type == VAR_NUMBER) {
2827         // looks like sizeof(varnumber_T) is always <= sizeof(long)
2828         result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2829     } else if (tv->v_type == VAR_LIST) {
2830         list_T * list = tv->vval.v_list;
2831         listitem_T * curr;
2833         NSMutableArray * arr = result = [NSMutableArray array];
2835         if (list != NULL) {
2836             for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2837                 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2838                 [arr addObject:newObj];
2839             }
2840         }
2841     } else if (tv->v_type == VAR_DICT) {
2842         NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2844         if (tv->vval.v_dict != NULL) {
2845             hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2846             int todo = ht->ht_used;
2847             hashitem_T * hi;
2848             dictitem_T * di;
2850             for (hi = ht->ht_array; todo > 0; ++hi) {
2851                 if (!HASHITEM_EMPTY(hi)) {
2852                     --todo;
2854                     di = dict_lookup(hi);
2855                     newObj = vimToCocoa(&di->di_tv, depth + 1);
2857                     char_u * keyval = hi->hi_key;
2858 #ifdef FEAT_MBYTE
2859                     keyval = CONVERT_TO_UTF8(keyval);
2860 #endif
2861                     NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2862 #ifdef FEAT_MBYTE
2863                     CONVERT_TO_UTF8_FREE(keyval);
2864 #endif
2865                     [dict setObject:newObj forKey:key];
2866                 }
2867             }
2868         }
2869     } else { // only func refs should fall into this category?
2870         result = nil;
2871     }
2873     return result;
2877 // This function is modeled after eval_client_expr_to_string found in main.c
2878 // Returns nil if there was an error evaluating the expression, and writes a
2879 // message to errorStr.
2880 // TODO Get the error that occurred while evaluating the expression in vim
2881 // somehow.
2882 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2885     char_u *s = (char_u*)[expr UTF8String];
2887 #ifdef FEAT_MBYTE
2888     s = CONVERT_FROM_UTF8(s);
2889 #endif
2891     int save_dbl = debug_break_level;
2892     int save_ro = redir_off;
2894     debug_break_level = -1;
2895     redir_off = 0;
2896     ++emsg_skip;
2898     typval_T * tvres = eval_expr(s, NULL);
2900     debug_break_level = save_dbl;
2901     redir_off = save_ro;
2902     --emsg_skip;
2904     setcursor();
2905     out_flush();
2907 #ifdef FEAT_MBYTE
2908     CONVERT_FROM_UTF8_FREE(s);
2909 #endif
2911 #ifdef FEAT_GUI
2912     if (gui.in_use)
2913         gui_update_cursor(FALSE, FALSE);
2914 #endif
2916     if (tvres == NULL) {
2917         free_tv(tvres);
2918         *errstr = @"Expression evaluation failed.";
2919     }
2921     id res = vimToCocoa(tvres, 1);
2923     free_tv(tvres);
2925     if (res == nil) {
2926         *errstr = @"Conversion to cocoa values failed.";
2927     }
2929     return res;
2934 @implementation NSString (VimStrings)
2936 + (id)stringWithVimString:(char_u *)s
2938     // This method ensures a non-nil string is returned.  If 's' cannot be
2939     // converted to a utf-8 string it is assumed to be latin-1.  If conversion
2940     // still fails an empty NSString is returned.
2941     NSString *string = nil;
2942     if (s) {
2943 #ifdef FEAT_MBYTE
2944         s = CONVERT_TO_UTF8(s);
2945 #endif
2946         string = [NSString stringWithUTF8String:(char*)s];
2947         if (!string) {
2948             // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2949             // latin-1?
2950             string = [NSString stringWithCString:(char*)s
2951                                         encoding:NSISOLatin1StringEncoding];
2952         }
2953 #ifdef FEAT_MBYTE
2954         CONVERT_TO_UTF8_FREE(s);
2955 #endif
2956     }
2958     return string != nil ? string : [NSString string];
2961 - (char_u *)vimStringSave
2963     char_u *s = (char_u*)[self UTF8String], *ret = NULL;
2965 #ifdef FEAT_MBYTE
2966     s = CONVERT_FROM_UTF8(s);
2967 #endif
2968     ret = vim_strsave(s);
2969 #ifdef FEAT_MBYTE
2970     CONVERT_FROM_UTF8_FREE(s);
2971 #endif
2973     return ret;
2976 @end // NSString (VimStrings)