Add Vimball (vba) as supported filetype
[MacVim.git] / src / MacVim / MMBackend.m
blob8a327fa73a7414b280d26d636368fe98837c0fef
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 [MMAppController processInput:forIdentifier:].
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 // In gui_macvim.m
55 vimmenu_T *menu_for_descriptor(NSArray *desc);
57 static id evalExprCocoa(NSString * expr, NSString ** errstr);
60 enum {
61     MMBlinkStateNone = 0,
62     MMBlinkStateOn,
63     MMBlinkStateOff
66 static NSString *MMSymlinkWarningString =
67     @"\n\n\tMost likely this is because you have symlinked directly to\n"
68      "\tthe Vim binary, which Cocoa does not allow.  Please use an\n"
69      "\talias or the mvim shell script instead.  If you have not used\n"
70      "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
73 extern GuiFont gui_mch_retain_font(GuiFont font);
77 @interface NSString (MMServerNameCompare)
78 - (NSComparisonResult)serverNameCompare:(NSString *)string;
79 @end
84 @interface MMBackend (Private)
85 - (void)clearDrawData;
86 - (void)didChangeWholeLine;
87 - (void)waitForDialogReturn;
88 - (void)insertVimStateMessage;
89 - (void)processInputQueue;
90 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
91 + (NSDictionary *)specialKeys;
92 - (void)handleInsertText:(NSString *)text;
93 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
94 - (void)queueMessage:(int)msgid data:(NSData *)data;
95 - (void)connectionDidDie:(NSNotification *)notification;
96 - (void)blinkTimerFired:(NSTimer *)timer;
97 - (void)focusChange:(BOOL)on;
98 - (void)handleToggleToolbar;
99 - (void)handleScrollbarEvent:(NSData *)data;
100 - (void)handleSetFont:(NSData *)data;
101 - (void)handleDropFiles:(NSData *)data;
102 - (void)handleDropString:(NSData *)data;
103 - (void)startOdbEditWithArguments:(NSDictionary *)args;
104 - (void)handleXcodeMod:(NSData *)data;
105 - (void)handleOpenWithArguments:(NSDictionary *)args;
106 - (BOOL)checkForModifiedBuffers;
107 - (void)addInput:(NSString *)input;
108 - (BOOL)unusedEditor;
109 - (void)redrawScreen;
110 - (void)handleFindReplace:(NSDictionary *)args;
111 @end
115 @interface MMBackend (ClientServer)
116 - (NSString *)connectionNameFromServerName:(NSString *)name;
117 - (NSConnection *)connectionForServerName:(NSString *)name;
118 - (NSConnection *)connectionForServerPort:(int)port;
119 - (void)serverConnectionDidDie:(NSNotification *)notification;
120 - (void)addClient:(NSDistantObject *)client;
121 - (NSString *)alternateServerNameForName:(NSString *)name;
122 @end
126 @implementation MMBackend
128 + (MMBackend *)sharedInstance
130     static MMBackend *singleton = nil;
131     return singleton ? singleton : (singleton = [MMBackend new]);
134 - (id)init
136     self = [super init];
137     if (!self) return nil;
139     outputQueue = [[NSMutableArray alloc] init];
140     inputQueue = [[NSMutableArray alloc] init];
141     drawData = [[NSMutableData alloc] initWithCapacity:1024];
142     connectionNameDict = [[NSMutableDictionary alloc] init];
143     clientProxyDict = [[NSMutableDictionary alloc] init];
144     serverReplyDict = [[NSMutableDictionary alloc] init];
146     NSBundle *mainBundle = [NSBundle mainBundle];
147     NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
148     if (path)
149         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
151     path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
152     if (path)
153         sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
154             retain];
156     path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
157     if (path)
158         actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
160     if (!(colorDict && sysColorDict && actionDict)) {
161         ASLogNotice(@"Failed to load dictionaries.%@", MMSymlinkWarningString);
162     }
164     return self;
167 - (void)dealloc
169     ASLogDebug(@"");
171     [[NSNotificationCenter defaultCenter] removeObserver:self];
173     gui_mch_free_font(oldWideFont);  oldWideFont = NOFONT;
174     [blinkTimer release];  blinkTimer = nil;
175     [alternateServerName release];  alternateServerName = nil;
176     [serverReplyDict release];  serverReplyDict = nil;
177     [clientProxyDict release];  clientProxyDict = nil;
178     [connectionNameDict release];  connectionNameDict = nil;
179     [inputQueue release];  inputQueue = nil;
180     [outputQueue release];  outputQueue = nil;
181     [drawData release];  drawData = nil;
182     [connection release];  connection = nil;
183     [actionDict release];  actionDict = nil;
184     [sysColorDict release];  sysColorDict = nil;
185     [colorDict release];  colorDict = nil;
187     [super dealloc];
190 - (void)setBackgroundColor:(int)color
192     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
195 - (void)setForegroundColor:(int)color
197     foregroundColor = MM_COLOR(color);
200 - (void)setSpecialColor:(int)color
202     specialColor = MM_COLOR(color);
205 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
207     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
208     defaultForegroundColor = MM_COLOR(fg);
210     NSMutableData *data = [NSMutableData data];
212     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
213     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
215     [self queueMessage:SetDefaultColorsMsgID data:data];
218 - (NSConnection *)connection
220     if (!connection) {
221         // NOTE!  If the name of the connection changes here it must also be
222         // updated in MMAppController.m.
223         NSString *name = [NSString stringWithFormat:@"%@-connection",
224                [[NSBundle mainBundle] bundlePath]];
226         connection = [NSConnection connectionWithRegisteredName:name host:nil];
227         [connection retain];
228     }
230     // NOTE: 'connection' may be nil here.
231     return connection;
234 - (NSDictionary *)actionDict
236     return actionDict;
239 - (int)initialWindowLayout
241     return initialWindowLayout;
244 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
246     [self queueMessage:msgid data:[props dictionaryAsData]];
249 - (BOOL)checkin
251     if (![self connection]) {
252         if (waitForAck) {
253             // This is a preloaded process and as such should not cause the
254             // MacVim to be opened.  We probably got here as a result of the
255             // user quitting MacVim while the process was preloading, so exit
256             // this process too.
257             // (Don't use mch_exit() since it assumes the process has properly
258             // started.)
259             exit(0);
260         }
262         NSBundle *mainBundle = [NSBundle mainBundle];
263 #if 0
264         OSStatus status;
265         FSRef ref;
267         // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
268         // the API to pass Apple Event parameters is broken on 10.4).
269         NSString *path = [mainBundle bundlePath];
270         status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
271         if (noErr == status) {
272             // Pass parameter to the 'Open' Apple Event that tells MacVim not
273             // to open an untitled window.
274             NSAppleEventDescriptor *desc =
275                     [NSAppleEventDescriptor recordDescriptor];
276             [desc setParamDescriptor:
277                     [NSAppleEventDescriptor descriptorWithBoolean:NO]
278                           forKeyword:keyMMUntitledWindow];
280             LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
281                     kLSLaunchDefaults, NULL };
282             status = LSOpenFromRefSpec(&spec, NULL);
283         }
285         if (noErr != status) {
286         ASLogCrit(@"Failed to launch MacVim (path=%@).%@",
287                   path, MMSymlinkWarningString);
288             return NO;
289         }
290 #else
291         // Launch MacVim using NSTask.  For some reason the above code using
292         // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
293         // fails, the dock icon starts bouncing and never stops).  It seems
294         // like rebuilding the Launch Services database takes care of this
295         // problem, but the NSTask way seems more stable so stick with it.
296         //
297         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
298         // that the GUI won't be activated (or raised) so there is a hack in
299         // MMAppController which raises the app when a new window is opened.
300         NSMutableArray *args = [NSMutableArray arrayWithObjects:
301             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
302         NSString *exeName = [[mainBundle infoDictionary]
303                 objectForKey:@"CFBundleExecutable"];
304         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
305         if (!path) {
306             ASLogCrit(@"Could not find MacVim executable in bundle.%@",
307                       MMSymlinkWarningString);
308             return NO;
309         }
311         [NSTask launchedTaskWithLaunchPath:path arguments:args];
312 #endif
314         // HACK!  Poll the mach bootstrap server until it returns a valid
315         // connection to detect that MacVim has finished launching.  Also set a
316         // time-out date so that we don't get stuck doing this forever.
317         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
318         while (![self connection] &&
319                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
320             [[NSRunLoop currentRunLoop]
321                     runMode:NSDefaultRunLoopMode
322                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
324         // NOTE: [self connection] will set 'connection' as a side-effect.
325         if (!connection) {
326             ASLogCrit(@"Timed-out waiting for GUI to launch.");
327             return NO;
328         }
329     }
331     @try {
332         [[NSNotificationCenter defaultCenter] addObserver:self
333                 selector:@selector(connectionDidDie:)
334                     name:NSConnectionDidDieNotification object:connection];
336         appProxy = [[connection rootProxy] retain];
337         [appProxy setProtocolForProxy:@protocol(MMAppProtocol)];
339         // NOTE: We do not set any new timeout values for the connection to the
340         // frontend.  This means that if the frontend is "stuck" (e.g. in a
341         // modal loop) then any calls to the frontend will block indefinitely
342         // (the default timeouts are huge).
344         int pid = [[NSProcessInfo processInfo] processIdentifier];
346         identifier = [appProxy connectBackend:self pid:pid];
347         return YES;
348     }
349     @catch (NSException *ex) {
350         ASLogNotice(@"Connect backend failed: reason=%@", ex);
351     }
353     return NO;
356 - (BOOL)openGUIWindow
358     [self queueMessage:OpenWindowMsgID data:nil];
359     return YES;
362 - (void)clearAll
364     int type = ClearAllDrawType;
366     // Any draw commands in queue are effectively obsolete since this clearAll
367     // will negate any effect they have, therefore we may as well clear the
368     // draw queue.
369     [self clearDrawData];
371     [drawData appendBytes:&type length:sizeof(int)];
374 - (void)clearBlockFromRow:(int)row1 column:(int)col1
375                     toRow:(int)row2 column:(int)col2
377     int type = ClearBlockDrawType;
379     [drawData appendBytes:&type length:sizeof(int)];
381     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
382     [drawData appendBytes:&row1 length:sizeof(int)];
383     [drawData appendBytes:&col1 length:sizeof(int)];
384     [drawData appendBytes:&row2 length:sizeof(int)];
385     [drawData appendBytes:&col2 length:sizeof(int)];
388 - (void)deleteLinesFromRow:(int)row count:(int)count
389               scrollBottom:(int)bottom left:(int)left right:(int)right
391     int type = DeleteLinesDrawType;
393     [drawData appendBytes:&type length:sizeof(int)];
395     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
396     [drawData appendBytes:&row length:sizeof(int)];
397     [drawData appendBytes:&count length:sizeof(int)];
398     [drawData appendBytes:&bottom length:sizeof(int)];
399     [drawData appendBytes:&left length:sizeof(int)];
400     [drawData appendBytes:&right length:sizeof(int)];
402     if (left == 0 && right == gui.num_cols-1)
403         [self didChangeWholeLine];
406 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
407              cells:(int)cells flags:(int)flags
409     if (len <= 0 || cells <= 0) return;
411     int type = DrawStringDrawType;
413     [drawData appendBytes:&type length:sizeof(int)];
415     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
416     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
417     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
418     [drawData appendBytes:&row length:sizeof(int)];
419     [drawData appendBytes:&col length:sizeof(int)];
420     [drawData appendBytes:&cells length:sizeof(int)];
421     [drawData appendBytes:&flags length:sizeof(int)];
422     [drawData appendBytes:&len length:sizeof(int)];
423     [drawData appendBytes:s length:len];
426 - (void)insertLinesFromRow:(int)row count:(int)count
427               scrollBottom:(int)bottom left:(int)left right:(int)right
429     int type = InsertLinesDrawType;
431     [drawData appendBytes:&type length:sizeof(int)];
433     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
434     [drawData appendBytes:&row length:sizeof(int)];
435     [drawData appendBytes:&count length:sizeof(int)];
436     [drawData appendBytes:&bottom length:sizeof(int)];
437     [drawData appendBytes:&left length:sizeof(int)];
438     [drawData appendBytes:&right length:sizeof(int)];
440     if (left == 0 && right == gui.num_cols-1)
441         [self didChangeWholeLine];
444 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
445                fraction:(int)percent color:(int)color
447     int type = DrawCursorDrawType;
448     unsigned uc = MM_COLOR(color);
450     [drawData appendBytes:&type length:sizeof(int)];
452     [drawData appendBytes:&uc length:sizeof(unsigned)];
453     [drawData appendBytes:&row length:sizeof(int)];
454     [drawData appendBytes:&col length:sizeof(int)];
455     [drawData appendBytes:&shape length:sizeof(int)];
456     [drawData appendBytes:&percent length:sizeof(int)];
459 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
460                    numColumns:(int)nc invert:(int)invert
462     int type = DrawInvertedRectDrawType;
463     [drawData appendBytes:&type length:sizeof(int)];
465     [drawData appendBytes:&row length:sizeof(int)];
466     [drawData appendBytes:&col length:sizeof(int)];
467     [drawData appendBytes:&nr length:sizeof(int)];
468     [drawData appendBytes:&nc length:sizeof(int)];
469     [drawData appendBytes:&invert length:sizeof(int)];
472 - (void)update
474     // Keep running the run-loop until there is no more input to process.
475     while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
476             == kCFRunLoopRunHandledSource)
477         ;   // do nothing
480 - (void)flushQueue:(BOOL)force
482     // NOTE: This variable allows for better control over when the queue is
483     // flushed.  It can be set to YES at the beginning of a sequence of calls
484     // that may potentially add items to the queue, and then restored back to
485     // NO.
486     if (flushDisabled) return;
488     if ([drawData length] > 0) {
489         // HACK!  Detect changes to 'guifontwide'.
490         if (gui.wide_font != oldWideFont) {
491             gui_mch_free_font(oldWideFont);
492             oldWideFont = gui_mch_retain_font(gui.wide_font);
493             [self setFont:oldWideFont wide:YES];
494         }
496         int type = SetCursorPosDrawType;
497         [drawData appendBytes:&type length:sizeof(type)];
498         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
499         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
501         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
502         [self clearDrawData];
503     }
505     if ([outputQueue count] > 0) {
506         [self insertVimStateMessage];
508         @try {
509             ASLogDebug(@"Flushing queue: %@",
510                        debugStringForMessageQueue(outputQueue));
511             [appProxy processInput:outputQueue forIdentifier:identifier];
512         }
513         @catch (NSException *ex) {
514             ASLogNotice(@"processInput:forIdentifer failed: reason=%@", ex);
515             if (![connection isValid]) {
516                 ASLogNotice(@"Connection is invalid, exit now!");
517                 ASLogDebug(@"waitForAck=%d got_int=%d", waitForAck, got_int);
518                 mch_exit(-1);
519             }
520         }
522         [outputQueue removeAllObjects];
523     }
526 - (BOOL)waitForInput:(int)milliseconds
528     // Return NO if we timed out waiting for input, otherwise return YES.
529     BOOL inputReceived = NO;
531     // Only start the run loop if the input queue is empty, otherwise process
532     // the input first so that the input on queue isn't delayed.
533     if ([inputQueue count]) {
534         inputReceived = YES;
535     } else {
536         // Wait for the specified amount of time, unless 'milliseconds' is
537         // negative in which case we wait "forever" (1e6 seconds translates to
538         // approximately 11 days).
539         CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
541         while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
542                 == kCFRunLoopRunHandledSource) {
543             // In order to ensure that all input on the run-loop has been
544             // processed we set the timeout to 0 and keep processing until the
545             // run-loop times out.
546             dt = 0.0;
547             inputReceived = YES;
548         }
549     }
551     // The above calls may have placed messages on the input queue so process
552     // it now.  This call may enter a blocking loop.
553     if ([inputQueue count] > 0)
554         [self processInputQueue];
556     return inputReceived;
559 - (void)exit
561     // NOTE: This is called if mch_exit() is called.  Since we assume here that
562     // the process has started properly, be sure to use exit() instead of
563     // mch_exit() to prematurely terminate a process (or set 'isTerminating'
564     // first).
566     // Make sure no connectionDidDie: notification is received now that we are
567     // already exiting.
568     [[NSNotificationCenter defaultCenter] removeObserver:self];
570     // The 'isTerminating' flag indicates that the frontend is also exiting so
571     // there is no need to flush any more output since the frontend won't look
572     // at it anyway.
573     if (!isTerminating && [connection isValid]) {
574         @try {
575             // Flush the entire queue in case a VimLeave autocommand added
576             // something to the queue.
577             [self queueMessage:CloseWindowMsgID data:nil];
578             ASLogDebug(@"Flush output queue before exit: %@",
579                        debugStringForMessageQueue(outputQueue));
580             [appProxy processInput:outputQueue forIdentifier:identifier];
581         }
582         @catch (NSException *ex) {
583             ASLogNotice(@"CloseWindowMsgID send failed: reason=%@", ex);
584         }
586         // NOTE: If Cmd-w was pressed to close the window the menu is briefly
587         // highlighted and during this pause the frontend won't receive any DO
588         // messages.  If the Vim process exits before this highlighting has
589         // finished Cocoa will emit the following error message:
590         //   *** -[NSMachPort handlePortMessage:]: dropping incoming DO message
591         //   because the connection or ports are invalid
592         // To avoid this warning we delay here.  If the warning still appears
593         // this delay may need to be increased.
594         usleep(150000);
595     }
597 #ifdef MAC_CLIENTSERVER
598     // The default connection is used for the client/server code.
599     [[NSConnection defaultConnection] setRootObject:nil];
600     [[NSConnection defaultConnection] invalidate];
601 #endif
604 - (void)selectTab:(int)index
606     index -= 1;
607     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
608     [self queueMessage:SelectTabMsgID data:data];
611 - (void)updateTabBar
613     NSMutableData *data = [NSMutableData data];
615     int idx = tabpage_index(curtab) - 1;
616     [data appendBytes:&idx length:sizeof(int)];
618     tabpage_T *tp;
619     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
620         // Count the number of windows in the tabpage.
621         //win_T *wp = tp->tp_firstwin;
622         //int wincount;
623         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
624         //[data appendBytes:&wincount length:sizeof(int)];
626         int tabProp = MMTabInfoCount;
627         [data appendBytes:&tabProp length:sizeof(int)];
628         for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
629             // This function puts the label of the tab in the global 'NameBuff'.
630             get_tabline_label(tp, (tabProp == MMTabToolTip));
631             NSString *s = [NSString stringWithVimString:NameBuff];
632             int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
633             if (len < 0)
634                 len = 0;
636             [data appendBytes:&len length:sizeof(int)];
637             if (len > 0)
638                 [data appendBytes:[s UTF8String] length:len];
639         }
640     }
642     [self queueMessage:UpdateTabBarMsgID data:data];
645 - (BOOL)tabBarVisible
647     return tabBarVisible;
650 - (void)showTabBar:(BOOL)enable
652     tabBarVisible = enable;
654     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
655     [self queueMessage:msgid data:nil];
658 - (void)setRows:(int)rows columns:(int)cols
660     int dim[] = { rows, cols };
661     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
663     [self queueMessage:SetTextDimensionsMsgID data:data];
666 - (void)setWindowTitle:(char *)title
668     NSMutableData *data = [NSMutableData data];
669     int len = strlen(title);
670     if (len <= 0) return;
672     [data appendBytes:&len length:sizeof(int)];
673     [data appendBytes:title length:len];
675     [self queueMessage:SetWindowTitleMsgID data:data];
678 - (void)setDocumentFilename:(char *)filename
680     NSMutableData *data = [NSMutableData data];
681     int len = filename ? strlen(filename) : 0;
683     [data appendBytes:&len length:sizeof(int)];
684     if (len > 0)
685         [data appendBytes:filename length:len];
687     [self queueMessage:SetDocumentFilenameMsgID data:data];
690 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
692     char_u *s = NULL;
694     [self queueMessage:BrowseForFileMsgID properties:attr];
695     [self flushQueue:YES];
697     @try {
698         [self waitForDialogReturn];
700         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
701             s = [dialogReturn vimStringSave];
703         [dialogReturn release];  dialogReturn = nil;
704     }
705     @catch (NSException *ex) {
706         ASLogNotice(@"Exception: reason=%@", ex);
707     }
709     return (char *)s;
712 - (oneway void)setDialogReturn:(in bycopy id)obj
714     ASLogDebug(@"%@", obj);
716     // NOTE: This is called by
717     //   - [MMVimController panelDidEnd:::], and
718     //   - [MMVimController alertDidEnd:::],
719     // to indicate that a save/open panel or alert has finished.
721     // We want to distinguish between "no dialog return yet" and "dialog
722     // returned nothing".  The former can be tested with dialogReturn == nil,
723     // the latter with dialogReturn == [NSNull null].
724     if (!obj) obj = [NSNull null];
726     if (obj != dialogReturn) {
727         [dialogReturn release];
728         dialogReturn = [obj retain];
729     }
732 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
734     int retval = 0;
736     [self queueMessage:ShowDialogMsgID properties:attr];
737     [self flushQueue:YES];
739     @try {
740         [self waitForDialogReturn];
742         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
743                 && [dialogReturn count]) {
744             retval = [[dialogReturn objectAtIndex:0] intValue];
745             if (txtfield && [dialogReturn count] > 1) {
746                 NSString *retString = [dialogReturn objectAtIndex:1];
747                 char_u *ret = (char_u*)[retString UTF8String];
748 #ifdef FEAT_MBYTE
749                 ret = CONVERT_FROM_UTF8(ret);
750 #endif
751                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
752 #ifdef FEAT_MBYTE
753                 CONVERT_FROM_UTF8_FREE(ret);
754 #endif
755             }
756         }
758         [dialogReturn release]; dialogReturn = nil;
759     }
760     @catch (NSException *ex) {
761         ASLogNotice(@"Exception: reason=%@", ex);
762     }
764     return retval;
767 - (void)showToolbar:(int)enable flags:(int)flags
769     NSMutableData *data = [NSMutableData data];
771     [data appendBytes:&enable length:sizeof(int)];
772     [data appendBytes:&flags length:sizeof(int)];
774     [self queueMessage:ShowToolbarMsgID data:data];
777 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
779     NSMutableData *data = [NSMutableData data];
781     [data appendBytes:&ident length:sizeof(long)];
782     [data appendBytes:&type length:sizeof(int)];
784     [self queueMessage:CreateScrollbarMsgID data:data];
787 - (void)destroyScrollbarWithIdentifier:(long)ident
789     NSMutableData *data = [NSMutableData data];
790     [data appendBytes:&ident length:sizeof(long)];
792     [self queueMessage:DestroyScrollbarMsgID data:data];
795 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
797     NSMutableData *data = [NSMutableData data];
799     [data appendBytes:&ident length:sizeof(long)];
800     [data appendBytes:&visible length:sizeof(int)];
802     [self queueMessage:ShowScrollbarMsgID data:data];
805 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
807     NSMutableData *data = [NSMutableData data];
809     [data appendBytes:&ident length:sizeof(long)];
810     [data appendBytes:&pos length:sizeof(int)];
811     [data appendBytes:&len length:sizeof(int)];
813     [self queueMessage:SetScrollbarPositionMsgID data:data];
816 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
817                     identifier:(long)ident
819     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
820     float prop = (float)size/(max+1);
821     if (fval < 0) fval = 0;
822     else if (fval > 1.0f) fval = 1.0f;
823     if (prop < 0) prop = 0;
824     else if (prop > 1.0f) prop = 1.0f;
826     NSMutableData *data = [NSMutableData data];
828     [data appendBytes:&ident length:sizeof(long)];
829     [data appendBytes:&fval length:sizeof(float)];
830     [data appendBytes:&prop length:sizeof(float)];
832     [self queueMessage:SetScrollbarThumbMsgID data:data];
835 - (void)setFont:(GuiFont)font wide:(BOOL)wide
837     NSString *fontName = (NSString *)font;
838     float size = 0;
839     NSArray *components = [fontName componentsSeparatedByString:@":"];
840     if ([components count] == 2) {
841         size = [[components lastObject] floatValue];
842         fontName = [components objectAtIndex:0];
843     }
845     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
846     NSMutableData *data = [NSMutableData data];
847     [data appendBytes:&size length:sizeof(float)];
848     [data appendBytes:&len length:sizeof(int)];
850     if (len > 0)
851         [data appendBytes:[fontName UTF8String] length:len];
852     else if (!wide)
853         return;     // Only the wide font can be set to nothing
855     [self queueMessage:(wide ? SetWideFontMsgID : SetFontMsgID) data:data];
858 - (void)executeActionWithName:(NSString *)name
860     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
862     if (len > 0) {
863         NSMutableData *data = [NSMutableData data];
865         [data appendBytes:&len length:sizeof(int)];
866         [data appendBytes:[name UTF8String] length:len];
868         [self queueMessage:ExecuteActionMsgID data:data];
869     }
872 - (void)setMouseShape:(int)shape
874     NSMutableData *data = [NSMutableData data];
875     [data appendBytes:&shape length:sizeof(int)];
876     [self queueMessage:SetMouseShapeMsgID data:data];
879 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
881     // Vim specifies times in milliseconds, whereas Cocoa wants them in
882     // seconds.
883     blinkWaitInterval = .001f*wait;
884     blinkOnInterval = .001f*on;
885     blinkOffInterval = .001f*off;
888 - (void)startBlink
890     if (blinkTimer) {
891         [blinkTimer invalidate];
892         [blinkTimer release];
893         blinkTimer = nil;
894     }
896     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
897             && gui.in_focus) {
898         blinkState = MMBlinkStateOn;
899         blinkTimer =
900             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
901                                               target:self
902                                             selector:@selector(blinkTimerFired:)
903                                             userInfo:nil repeats:NO] retain];
904         gui_update_cursor(TRUE, FALSE);
905         [self flushQueue:YES];
906     }
909 - (void)stopBlink
911     if (MMBlinkStateOff == blinkState) {
912         gui_update_cursor(TRUE, FALSE);
913         [self flushQueue:YES];
914     }
916     blinkState = MMBlinkStateNone;
919 - (void)adjustLinespace:(int)linespace
921     NSMutableData *data = [NSMutableData data];
922     [data appendBytes:&linespace length:sizeof(int)];
923     [self queueMessage:AdjustLinespaceMsgID data:data];
926 - (void)activate
928     [self queueMessage:ActivateMsgID data:nil];
931 - (void)setPreEditRow:(int)row column:(int)col
933     NSMutableData *data = [NSMutableData data];
934     [data appendBytes:&row length:sizeof(int)];
935     [data appendBytes:&col length:sizeof(int)];
936     [self queueMessage:SetPreEditPositionMsgID data:data];
939 - (int)lookupColorWithKey:(NSString *)key
941     if (!(key && [key length] > 0))
942         return INVALCOLOR;
944     NSString *stripKey = [[[[key lowercaseString]
945         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
946             componentsSeparatedByString:@" "]
947                componentsJoinedByString:@""];
949     if (stripKey && [stripKey length] > 0) {
950         // First of all try to lookup key in the color dictionary; note that
951         // all keys in this dictionary are lowercase with no whitespace.
952         id obj = [colorDict objectForKey:stripKey];
953         if (obj) return [obj intValue];
955         // The key was not in the dictionary; is it perhaps of the form
956         // #rrggbb?
957         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
958             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
959             [scanner setScanLocation:1];
960             unsigned hex = 0;
961             if ([scanner scanHexInt:&hex]) {
962                 return (int)hex;
963             }
964         }
966         // As a last resort, check if it is one of the system defined colors.
967         // The keys in this dictionary are also lowercase with no whitespace.
968         obj = [sysColorDict objectForKey:stripKey];
969         if (obj) {
970             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
971             if (col) {
972                 float r, g, b, a;
973                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
974                 [col getRed:&r green:&g blue:&b alpha:&a];
975                 return (((int)(r*255+.5f) & 0xff) << 16)
976                      + (((int)(g*255+.5f) & 0xff) << 8)
977                      +  ((int)(b*255+.5f) & 0xff);
978             }
979         }
980     }
982     ASLogNotice(@"No color with key %@ found.", stripKey);
983     return INVALCOLOR;
986 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
988     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
989     id obj;
991     while ((obj = [e nextObject])) {
992         if ([value isEqual:obj])
993             return YES;
994     }
996     return NO;
999 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1001     NSMutableData *data = [NSMutableData data];
1002     [data appendBytes:&fuoptions length:sizeof(int)];
1003     bg = MM_COLOR(bg);
1004     [data appendBytes:&bg length:sizeof(int)];
1005     [self queueMessage:EnterFullscreenMsgID data:data];
1008 - (void)leaveFullscreen
1010     [self queueMessage:LeaveFullscreenMsgID data:nil];
1013 - (void)setFullscreenBackgroundColor:(int)color
1015     NSMutableData *data = [NSMutableData data];
1016     color = MM_COLOR(color);
1017     [data appendBytes:&color length:sizeof(int)];
1019     [self queueMessage:SetFullscreenColorMsgID data:data];
1022 - (void)setAntialias:(BOOL)antialias
1024     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1026     [self queueMessage:msgid data:nil];
1029 - (void)updateModifiedFlag
1031     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1032     // vice versa.
1033     int msgid = [self checkForModifiedBuffers]
1034             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1036     [self queueMessage:msgid data:nil];
1039 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1041     // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1042     // queue is processed since that only happens in waitForInput: (and Vim
1043     // regularly checks for Ctrl-C in between waiting for input).
1044     // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1045     // which waits on the run loop will fail to detect this message (e.g. in
1046     // waitForConnectionAcknowledgement).
1048     if (InsertTextMsgID == msgid && data != nil) {
1049         const void *bytes = [data bytes];
1050         bytes += sizeof(int);
1051         int len = *((int*)bytes);  bytes += sizeof(int);
1052         if (1 == len) {
1053             char_u *str = (char_u*)bytes;
1054             if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1055                     (str[0] == intr_char && intr_char != Ctrl_C)) {
1056                 got_int = TRUE;
1057                 [inputQueue removeAllObjects];
1058                 return;
1059             }
1060         }
1061     } else if (TerminateNowMsgID == msgid) {
1062         // Terminate immediately (the frontend is about to quit or this process
1063         // was aborted).  Don't preserve modified files since the user would
1064         // already have been presented with a dialog warning if there were any
1065         // modified files when we get here.
1066         isTerminating = YES;
1067         getout(0);
1068         return;
1069     }
1071     // Remove all previous instances of this message from the input queue, else
1072     // the input queue may fill up as a result of Vim not being able to keep up
1073     // with the speed at which new messages are received.
1074     // Keyboard input is never dropped, unless the input represents and
1075     // auto-repeated key.
1077     BOOL isKeyRepeat = NO;
1078     BOOL isKeyboardInput = NO;
1080     if (data && (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1081             CmdKeyMsgID == msgid)) {
1082         isKeyboardInput = YES;
1084         // The lowest bit of the first int is set if this key is a repeat.
1085         int flags = *((int*)[data bytes]);
1086         if (flags & 1)
1087             isKeyRepeat = YES;
1088     }
1090     // Keyboard input is not removed from the queue; repeats are ignored if
1091     // there already is keyboard input on the input queue.
1092     if (isKeyRepeat || !isKeyboardInput) {
1093         int i, count = [inputQueue count];
1094         for (i = 1; i < count; i+=2) {
1095             if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1096                 if (isKeyRepeat)
1097                     return;
1099                 [inputQueue removeObjectAtIndex:i];
1100                 [inputQueue removeObjectAtIndex:i-1];
1101                 break;
1102             }
1103         }
1104     }
1106     [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1107     [inputQueue addObject:(data ? (id)data : [NSNull null])];
1110 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1111                   errorString:(out bycopy NSString **)errstr
1113     return evalExprCocoa(expr, errstr);
1117 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1119     NSString *eval = nil;
1120     char_u *s = (char_u*)[expr UTF8String];
1122 #ifdef FEAT_MBYTE
1123     s = CONVERT_FROM_UTF8(s);
1124 #endif
1126     char_u *res = eval_client_expr_to_string(s);
1128 #ifdef FEAT_MBYTE
1129     CONVERT_FROM_UTF8_FREE(s);
1130 #endif
1132     if (res != NULL) {
1133         s = res;
1134 #ifdef FEAT_MBYTE
1135         s = CONVERT_TO_UTF8(s);
1136 #endif
1137         eval = [NSString stringWithUTF8String:(char*)s];
1138 #ifdef FEAT_MBYTE
1139         CONVERT_TO_UTF8_FREE(s);
1140 #endif
1141         vim_free(res);
1142     }
1144     return eval;
1147 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1149     // TODO: This method should share code with clip_mch_request_selection().
1151     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1152         // If there is no pasteboard, return YES to indicate that there is text
1153         // to copy.
1154         if (!pboard)
1155             return YES;
1157         clip_copy_selection();
1159         // Get the text to put on the pasteboard.
1160         long_u llen = 0; char_u *str = 0;
1161         int type = clip_convert_selection(&str, &llen, &clip_star);
1162         if (type < 0)
1163             return NO;
1164         
1165         // TODO: Avoid overflow.
1166         int len = (int)llen;
1167 #ifdef FEAT_MBYTE
1168         if (output_conv.vc_type != CONV_NONE) {
1169             char_u *conv_str = string_convert(&output_conv, str, &len);
1170             if (conv_str) {
1171                 vim_free(str);
1172                 str = conv_str;
1173             }
1174         }
1175 #endif
1177         NSString *string = [[NSString alloc]
1178             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1180         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1181         [pboard declareTypes:types owner:nil];
1182         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1183     
1184         [string release];
1185         vim_free(str);
1187         return ok;
1188     }
1190     return NO;
1193 - (oneway void)addReply:(in bycopy NSString *)reply
1194                  server:(in byref id <MMVimServerProtocol>)server
1196     ASLogDebug(@"reply=%@ server=%@", reply, (id)server);
1198     // Replies might come at any time and in any order so we keep them in an
1199     // array inside a dictionary with the send port used as key.
1201     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1202     // HACK! Assume connection uses mach ports.
1203     int port = [(NSMachPort*)[conn sendPort] machPort];
1204     NSNumber *key = [NSNumber numberWithInt:port];
1206     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1207     if (!replies) {
1208         replies = [NSMutableArray array];
1209         [serverReplyDict setObject:replies forKey:key];
1210     }
1212     [replies addObject:reply];
1215 - (void)addInput:(in bycopy NSString *)input
1216           client:(in byref id <MMVimClientProtocol>)client
1218     ASLogDebug(@"input=%@ client=%@", input, (id)client);
1220     // NOTE: We don't call addInput: here because it differs from
1221     // server_to_input_buf() in that it always sets the 'silent' flag and we
1222     // don't want the MacVim client/server code to behave differently from
1223     // other platforms.
1224     char_u *s = [input vimStringSave];
1225     server_to_input_buf(s);
1226     vim_free(s);
1228     [self addClient:(id)client];
1231 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1232                  client:(in byref id <MMVimClientProtocol>)client
1234     [self addClient:(id)client];
1235     return [self evaluateExpression:expr];
1238 - (void)registerServerWithName:(NSString *)name
1240     NSString *svrName = name;
1241     NSConnection *svrConn = [NSConnection defaultConnection];
1242     unsigned i;
1244     for (i = 0; i < MMServerMax; ++i) {
1245         NSString *connName = [self connectionNameFromServerName:svrName];
1247         if ([svrConn registerName:connName]) {
1248             ASLogInfo(@"Registered server with name: %@", svrName);
1250             // TODO: Set request/reply time-outs to something else?
1251             //
1252             // Don't wait for requests (time-out means that the message is
1253             // dropped).
1254             [svrConn setRequestTimeout:0];
1255             //[svrConn setReplyTimeout:MMReplyTimeout];
1256             [svrConn setRootObject:self];
1258             // NOTE: 'serverName' is a global variable
1259             serverName = [svrName vimStringSave];
1260 #ifdef FEAT_EVAL
1261             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1262 #endif
1263 #ifdef FEAT_TITLE
1264             need_maketitle = TRUE;
1265 #endif
1266             [self queueMessage:SetServerNameMsgID data:
1267                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1268             break;
1269         }
1271         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1272     }
1275 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1276                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1277               silent:(BOOL)silent
1279     // NOTE: If 'name' equals 'serverName' then the request is local (client
1280     // and server are the same).  This case is not handled separately, so a
1281     // connection will be set up anyway (this simplifies the code).
1283     NSConnection *conn = [self connectionForServerName:name];
1284     if (!conn) {
1285         if (!silent) {
1286             char_u *s = (char_u*)[name UTF8String];
1287 #ifdef FEAT_MBYTE
1288             s = CONVERT_FROM_UTF8(s);
1289 #endif
1290             EMSG2(_(e_noserver), s);
1291 #ifdef FEAT_MBYTE
1292             CONVERT_FROM_UTF8_FREE(s);
1293 #endif
1294         }
1295         return NO;
1296     }
1298     if (port) {
1299         // HACK! Assume connection uses mach ports.
1300         *port = [(NSMachPort*)[conn sendPort] machPort];
1301     }
1303     id proxy = [conn rootProxy];
1304     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1306     @try {
1307         if (expr) {
1308             NSString *eval = [proxy evaluateExpression:string client:self];
1309             if (reply) {
1310                 if (eval) {
1311                     *reply = [eval vimStringSave];
1312                 } else {
1313                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1314                 }
1315             }
1317             if (!eval)
1318                 return NO;
1319         } else {
1320             [proxy addInput:string client:self];
1321         }
1322     }
1323     @catch (NSException *ex) {
1324         ASLogNotice(@"Exception: reason=%@", ex);
1325         return NO;
1326     }
1328     return YES;
1331 - (NSArray *)serverList
1333     NSArray *list = nil;
1335     if ([self connection]) {
1336         id proxy = [connection rootProxy];
1337         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1339         @try {
1340             list = [proxy serverList];
1341         }
1342         @catch (NSException *ex) {
1343             ASLogNotice(@"serverList failed: reason=%@", ex);
1344         }
1345     } else {
1346         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1347     }
1349     return list;
1352 - (NSString *)peekForReplyOnPort:(int)port
1354     ASLogDebug(@"port=%d", port);
1356     NSNumber *key = [NSNumber numberWithInt:port];
1357     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1358     if (replies && [replies count]) {
1359         ASLogDebug(@"    %d replies, topmost is: %@", [replies count],
1360                    [replies objectAtIndex:0]);
1361         return [replies objectAtIndex:0];
1362     }
1364     ASLogDebug(@"    No replies");
1365     return nil;
1368 - (NSString *)waitForReplyOnPort:(int)port
1370     ASLogDebug(@"port=%d", port);
1371     
1372     NSConnection *conn = [self connectionForServerPort:port];
1373     if (!conn)
1374         return nil;
1376     NSNumber *key = [NSNumber numberWithInt:port];
1377     NSMutableArray *replies = nil;
1378     NSString *reply = nil;
1380     // Wait for reply as long as the connection to the server is valid (unless
1381     // user interrupts wait with Ctrl-C).
1382     while (!got_int && [conn isValid] &&
1383             !(replies = [serverReplyDict objectForKey:key])) {
1384         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1385                                  beforeDate:[NSDate distantFuture]];
1386     }
1388     if (replies) {
1389         if ([replies count] > 0) {
1390             reply = [[replies objectAtIndex:0] retain];
1391             ASLogDebug(@"    Got reply: %@", reply);
1392             [replies removeObjectAtIndex:0];
1393             [reply autorelease];
1394         }
1396         if ([replies count] == 0)
1397             [serverReplyDict removeObjectForKey:key];
1398     }
1400     return reply;
1403 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1405     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1406     if (client) {
1407         @try {
1408             ASLogDebug(@"reply=%@ port=%d", reply, port);
1409             [client addReply:reply server:self];
1410             return YES;
1411         }
1412         @catch (NSException *ex) {
1413             ASLogNotice(@"addReply:server: failed: reason=%@", ex);
1414         }
1415     } else {
1416         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1417     }
1419     return NO;
1422 - (BOOL)waitForAck
1424     return waitForAck;
1427 - (void)setWaitForAck:(BOOL)yn
1429     waitForAck = yn;
1432 - (void)waitForConnectionAcknowledgement
1434     if (!waitForAck) return;
1436     while (waitForAck && !got_int && [connection isValid]) {
1437         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1438                                  beforeDate:[NSDate distantFuture]];
1439         ASLogDebug(@"  waitForAck=%d got_int=%d isValid=%d",
1440                    waitForAck, got_int, [connection isValid]);
1441     }
1443     if (waitForAck) {
1444         ASLogDebug(@"Never received a connection acknowledgement");
1445         [[NSNotificationCenter defaultCenter] removeObserver:self];
1446         [appProxy release];  appProxy = nil;
1448         // NOTE: We intentionally do not call mch_exit() since this in turn
1449         // will lead to -[MMBackend exit] getting called which we want to
1450         // avoid.
1451         exit(0);
1452     }
1454     ASLogInfo(@"Connection acknowledgement received");
1455     [self processInputQueue];
1458 - (oneway void)acknowledgeConnection
1460     ASLogDebug(@"");
1461     waitForAck = NO;
1464 - (BOOL)imState
1466     return imState;
1469 - (void)setImState:(BOOL)activated
1471     imState = activated;
1474 static void netbeansReadCallback(CFSocketRef s,
1475                                  CFSocketCallBackType callbackType,
1476                                  CFDataRef address,
1477                                  const void *data,
1478                                  void *info)
1480     // NetBeans socket is readable.
1481     [[MMBackend sharedInstance] messageFromNetbeans];
1484 - (void)messageFromNetbeans
1486     [inputQueue addObject:[NSNumber numberWithInt:NetBeansMsgID]];
1487     [inputQueue addObject:[NSNull null]];
1490 - (void)setNetbeansSocket:(int)socket
1492     if (netbeansSocket) {
1493         CFRelease(netbeansSocket);
1494         netbeansSocket = NULL;
1495     }
1497     if (netbeansRunLoopSource) {
1498         CFRunLoopSourceInvalidate(netbeansRunLoopSource);
1499         netbeansRunLoopSource = NULL;
1500     }
1502     if (socket == -1)
1503         return;
1505     // Tell CFRunLoop that we are interested in NetBeans socket input.
1506     netbeansSocket = CFSocketCreateWithNative(kCFAllocatorDefault,
1507                                               socket,
1508                                               kCFSocketReadCallBack,
1509                                               &netbeansReadCallback,
1510                                               NULL);
1511     netbeansRunLoopSource = CFSocketCreateRunLoopSource(NULL,
1512                                                         netbeansSocket,
1513                                                         0);
1514     CFRunLoopAddSource(CFRunLoopGetCurrent(),
1515                        netbeansRunLoopSource,
1516                        kCFRunLoopCommonModes);
1519 @end // MMBackend
1523 @implementation MMBackend (Private)
1525 - (void)clearDrawData
1527     [drawData setLength:0];
1528     numWholeLineChanges = offsetForDrawDataPrune = 0;
1531 - (void)didChangeWholeLine
1533     // It may happen that draw queue is filled up with lots of changes that
1534     // affect a whole row.  If the number of such changes equals twice the
1535     // number of visible rows then we can prune some commands off the queue.
1536     //
1537     // NOTE: If we don't perform this pruning the draw queue may grow
1538     // indefinitely if Vim were to repeatedly send draw commands without ever
1539     // waiting for new input (that's when the draw queue is flushed).  The one
1540     // instance I know where this can happen is when a command is executed in
1541     // the shell (think ":grep" with thousands of matches).
1543     ++numWholeLineChanges;
1544     if (numWholeLineChanges == gui.num_rows) {
1545         // Remember the offset to prune up to.
1546         offsetForDrawDataPrune = [drawData length];
1547     } else if (numWholeLineChanges == 2*gui.num_rows) {
1548         // Delete all the unnecessary draw commands.
1549         NSMutableData *d = [[NSMutableData alloc]
1550                     initWithBytes:[drawData bytes] + offsetForDrawDataPrune
1551                            length:[drawData length] - offsetForDrawDataPrune];
1552         offsetForDrawDataPrune = [d length];
1553         numWholeLineChanges -= gui.num_rows;
1554         [drawData release];
1555         drawData = d;
1556     }
1559 - (void)waitForDialogReturn
1561     // Keep processing the run loop until a dialog returns.  To avoid getting
1562     // stuck in an endless loop (could happen if the setDialogReturn: message
1563     // was lost) we also do some paranoia checks.
1564     //
1565     // Note that in Cocoa the user can still resize windows and select menu
1566     // items while a sheet is being displayed, so we can't just wait for the
1567     // first message to arrive and assume that is the setDialogReturn: call.
1569     while (nil == dialogReturn && !got_int && [connection isValid])
1570         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1571                                  beforeDate:[NSDate distantFuture]];
1573     // Search for any resize messages on the input queue.  All other messages
1574     // on the input queue are dropped.  The reason why we single out resize
1575     // messages is because the user may have resized the window while a sheet
1576     // was open.
1577     int i, count = [inputQueue count];
1578     if (count > 0) {
1579         id textDimData = nil;
1580         if (count%2 == 0) {
1581             for (i = count-2; i >= 0; i -= 2) {
1582                 int msgid = [[inputQueue objectAtIndex:i] intValue];
1583                 if (SetTextDimensionsMsgID == msgid) {
1584                     textDimData = [[inputQueue objectAtIndex:i+1] retain];
1585                     break;
1586                 }
1587             }
1588         }
1590         [inputQueue removeAllObjects];
1592         if (textDimData) {
1593             [inputQueue addObject:
1594                     [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1595             [inputQueue addObject:textDimData];
1596             [textDimData release];
1597         }
1598     }
1601 - (void)insertVimStateMessage
1603     // NOTE: This is the place to add Vim state that needs to be accessed from
1604     // MacVim.  Do not add state that could potentially require lots of memory
1605     // since this message gets sent each time the output queue is forcibly
1606     // flushed (e.g. storing the currently selected text would be a bad idea).
1607     // We take this approach of "pushing" the state to MacVim to avoid having
1608     // to make synchronous calls from MacVim to Vim in order to get state.
1610     BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1611     int numTabs = tabpage_index(NULL) - 1;
1612     if (numTabs < 0)
1613         numTabs = 0;
1615     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1616         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1617         [NSNumber numberWithInt:p_mh], @"p_mh",
1618         [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1619         [NSNumber numberWithBool:mmta], @"p_mmta",
1620         [NSNumber numberWithInt:numTabs], @"numTabs",
1621         nil];
1623     // Put the state before all other messages.
1624     int msgid = SetVimStateMsgID;
1625     [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1626     [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1627                       atIndex:0];
1630 - (void)processInputQueue
1632     if ([inputQueue count] == 0) return;
1634     // NOTE: One of the input events may cause this method to be called
1635     // recursively, so copy the input queue to a local variable and clear the
1636     // queue before starting to process input events (otherwise we could get
1637     // stuck in an endless loop).
1638     NSArray *q = [inputQueue copy];
1639     unsigned i, count = [q count];
1641     [inputQueue removeAllObjects];
1643     for (i = 1; i < count; i+=2) {
1644         int msgid = [[q objectAtIndex:i-1] intValue];
1645         id data = [q objectAtIndex:i];
1646         if ([data isEqual:[NSNull null]])
1647             data = nil;
1649         ASLogDebug(@"(%d) %s", i, MessageStrings[msgid]);
1650         [self handleInputEvent:msgid data:data];
1651     }
1653     [q release];
1656 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1658     if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1659             CmdKeyMsgID == msgid) {
1660         if (!data) return;
1661         const void *bytes = [data bytes];
1662         int mods = *((int*)bytes);  bytes += sizeof(int);
1663         int len = *((int*)bytes);  bytes += sizeof(int);
1664         NSString *key = [[NSString alloc] initWithBytes:bytes
1665                                                  length:len
1666                                                encoding:NSUTF8StringEncoding];
1667         mods = eventModifierFlagsToVimModMask(mods);
1669         if (InsertTextMsgID == msgid)
1670             [self handleInsertText:key];
1671         else
1672             [self handleKeyDown:key modifiers:mods];
1674         [key release];
1675     } else if (ScrollWheelMsgID == msgid) {
1676         if (!data) return;
1677         const void *bytes = [data bytes];
1679         int row = *((int*)bytes);  bytes += sizeof(int);
1680         int col = *((int*)bytes);  bytes += sizeof(int);
1681         int flags = *((int*)bytes);  bytes += sizeof(int);
1682         float dy = *((float*)bytes);  bytes += sizeof(float);
1684         int button = MOUSE_5;
1685         if (dy > 0) button = MOUSE_4;
1687         flags = eventModifierFlagsToVimMouseModMask(flags);
1689         int numLines = (int)round(dy);
1690         if (numLines < 0) numLines = -numLines;
1691         if (numLines == 0) numLines = 1;
1693 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1694         gui.scroll_wheel_force = numLines;
1695 #endif
1697         gui_send_mouse_event(button, col, row, NO, flags);
1698     } else if (MouseDownMsgID == msgid) {
1699         if (!data) return;
1700         const void *bytes = [data bytes];
1702         int row = *((int*)bytes);  bytes += sizeof(int);
1703         int col = *((int*)bytes);  bytes += sizeof(int);
1704         int button = *((int*)bytes);  bytes += sizeof(int);
1705         int flags = *((int*)bytes);  bytes += sizeof(int);
1706         int count = *((int*)bytes);  bytes += sizeof(int);
1708         button = eventButtonNumberToVimMouseButton(button);
1709         if (button >= 0) {
1710             flags = eventModifierFlagsToVimMouseModMask(flags);
1711             gui_send_mouse_event(button, col, row, count>1, flags);
1712         }
1713     } else if (MouseUpMsgID == msgid) {
1714         if (!data) return;
1715         const void *bytes = [data bytes];
1717         int row = *((int*)bytes);  bytes += sizeof(int);
1718         int col = *((int*)bytes);  bytes += sizeof(int);
1719         int flags = *((int*)bytes);  bytes += sizeof(int);
1721         flags = eventModifierFlagsToVimMouseModMask(flags);
1723         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1724     } else if (MouseDraggedMsgID == msgid) {
1725         if (!data) return;
1726         const void *bytes = [data bytes];
1728         int row = *((int*)bytes);  bytes += sizeof(int);
1729         int col = *((int*)bytes);  bytes += sizeof(int);
1730         int flags = *((int*)bytes);  bytes += sizeof(int);
1732         flags = eventModifierFlagsToVimMouseModMask(flags);
1734         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1735     } else if (MouseMovedMsgID == msgid) {
1736         const void *bytes = [data bytes];
1737         int row = *((int*)bytes);  bytes += sizeof(int);
1738         int col = *((int*)bytes);  bytes += sizeof(int);
1740         gui_mouse_moved(col, row);
1741     } else if (AddInputMsgID == msgid) {
1742         NSString *string = [[NSString alloc] initWithData:data
1743                 encoding:NSUTF8StringEncoding];
1744         if (string) {
1745             [self addInput:string];
1746             [string release];
1747         }
1748     } else if (SelectTabMsgID == msgid) {
1749         if (!data) return;
1750         const void *bytes = [data bytes];
1751         int idx = *((int*)bytes) + 1;
1752         send_tabline_event(idx);
1753     } else if (CloseTabMsgID == msgid) {
1754         if (!data) return;
1755         const void *bytes = [data bytes];
1756         int idx = *((int*)bytes) + 1;
1757         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1758     } else if (AddNewTabMsgID == msgid) {
1759         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1760     } else if (DraggedTabMsgID == msgid) {
1761         if (!data) return;
1762         const void *bytes = [data bytes];
1763         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1764         // based.
1765         int idx = *((int*)bytes);
1767         tabpage_move(idx);
1768     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1769             || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1770         if (!data) return;
1771         const void *bytes = [data bytes];
1772         int rows = Rows;
1773         if (SetTextColumnsMsgID != msgid) {
1774             rows = *((int*)bytes);  bytes += sizeof(int);
1775         }
1776         int cols = Columns;
1777         if (SetTextRowsMsgID != msgid) {
1778             cols = *((int*)bytes);  bytes += sizeof(int);
1779         }
1781         NSData *d = data;
1782         if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1783             int dim[2] = { rows, cols };
1784             d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1785             msgid = SetTextDimensionsReplyMsgID;
1786         }
1788         if (SetTextDimensionsMsgID == msgid)
1789             msgid = SetTextDimensionsReplyMsgID;
1791         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1792         // gui_resize_shell(), so we have to manually set the rows and columns
1793         // here since MacVim doesn't change the rows and columns to avoid
1794         // inconsistent states between Vim and MacVim.  The message sent back
1795         // indicates that it is a reply to a message that originated in MacVim
1796         // since we need to be able to determine where a message originated.
1797         [self queueMessage:msgid data:d];
1799         gui_resize_shell(cols, rows);
1800     } else if (ExecuteMenuMsgID == msgid) {
1801         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1802         if (attrs) {
1803             NSArray *desc = [attrs objectForKey:@"descriptor"];
1804             vimmenu_T *menu = menu_for_descriptor(desc);
1805             if (menu)
1806                 gui_menu_cb(menu);
1807         }
1808     } else if (ToggleToolbarMsgID == msgid) {
1809         [self handleToggleToolbar];
1810     } else if (ScrollbarEventMsgID == msgid) {
1811         [self handleScrollbarEvent:data];
1812     } else if (SetFontMsgID == msgid) {
1813         [self handleSetFont:data];
1814     } else if (VimShouldCloseMsgID == msgid) {
1815         gui_shell_closed();
1816     } else if (DropFilesMsgID == msgid) {
1817         [self handleDropFiles:data];
1818     } else if (DropStringMsgID == msgid) {
1819         [self handleDropString:data];
1820     } else if (GotFocusMsgID == msgid) {
1821         if (!gui.in_focus)
1822             [self focusChange:YES];
1823     } else if (LostFocusMsgID == msgid) {
1824         if (gui.in_focus)
1825             [self focusChange:NO];
1826     } else if (SetMouseShapeMsgID == msgid) {
1827         const void *bytes = [data bytes];
1828         int shape = *((int*)bytes);  bytes += sizeof(int);
1829         update_mouseshape(shape);
1830     } else if (XcodeModMsgID == msgid) {
1831         [self handleXcodeMod:data];
1832     } else if (OpenWithArgumentsMsgID == msgid) {
1833         [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1834     } else if (FindReplaceMsgID == msgid) {
1835         [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1836     } else if (ActivatedImMsgID == msgid) {
1837         [self setImState:YES];
1838     } else if (DeactivatedImMsgID == msgid) {
1839         [self setImState:NO];
1840     } else if (NetBeansMsgID == msgid) {
1841 #ifdef FEAT_NETBEANS_INTG
1842         messageFromNetbeansMacVim();
1843 #endif
1844     } else {
1845         ASLogWarn(@"Unknown message received (msgid=%d)", msgid);
1846     }
1849 + (NSDictionary *)specialKeys
1851     static NSDictionary *specialKeys = nil;
1853     if (!specialKeys) {
1854         NSBundle *mainBundle = [NSBundle mainBundle];
1855         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1856                                               ofType:@"plist"];
1857         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1858     }
1860     return specialKeys;
1863 - (void)handleInsertText:(NSString *)text
1865     if (!text) return;
1867     char_u *str = (char_u*)[text UTF8String];
1868     int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1870 #ifdef FEAT_MBYTE
1871     char_u *conv_str = NULL;
1872     if (input_conv.vc_type != CONV_NONE) {
1873         conv_str = string_convert(&input_conv, str, &len);
1874         if (conv_str)
1875             str = conv_str;
1876     }
1877 #endif
1879     for (i = 0; i < len; ++i) {
1880         add_to_input_buf(str+i, 1);
1881         if (CSI == str[i]) {
1882             // NOTE: If the converted string contains the byte CSI, then it
1883             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1884             // won't work.
1885             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1886             add_to_input_buf(extra, 2);
1887         }
1888     }
1890 #ifdef FEAT_MBYTE
1891     if (conv_str)
1892         vim_free(conv_str);
1893 #endif
1896 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1898     // TODO: This code is a horrible mess -- clean up!
1899     char_u special[3];
1900     char_u modChars[3];
1901     char_u *chars = (char_u*)[key UTF8String];
1902 #ifdef FEAT_MBYTE
1903     char_u *conv_str = NULL;
1904 #endif
1905     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1907     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1908     // that new keys can easily be added.
1909     NSString *specialString = [[MMBackend specialKeys]
1910             objectForKey:key];
1911     if (specialString && [specialString length] > 1) {
1912         //ASLogDebug(@"special key: %@", specialString);
1913         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1914                 [specialString characterAtIndex:1]);
1916         ikey = simplify_key(ikey, &mods);
1917         if (ikey == CSI)
1918             ikey = K_CSI;
1920         special[0] = CSI;
1921         special[1] = K_SECOND(ikey);
1922         special[2] = K_THIRD(ikey);
1924         chars = special;
1925         length = 3;
1926     } else if (1 == length && TAB == chars[0]) {
1927         // Tab is a trouble child:
1928         // - <Tab> is added to the input buffer as is
1929         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1930         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1931         //   to be converted to utf-8
1932         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1933         // - <C-Tab> is reserved by Mac OS X
1934         // - <D-Tab> is reserved by Mac OS X
1935         chars = special;
1936         special[0] = TAB;
1937         length = 1;
1939         if (mods & MOD_MASK_SHIFT) {
1940             mods &= ~MOD_MASK_SHIFT;
1941             special[0] = CSI;
1942             special[1] = K_SECOND(K_S_TAB);
1943             special[2] = K_THIRD(K_S_TAB);
1944             length = 3;
1945         } else if (mods & MOD_MASK_ALT) {
1946             int mtab = 0x80 | TAB;
1947 #ifdef FEAT_MBYTE
1948             if (enc_utf8) {
1949                 // Convert to utf-8
1950                 special[0] = (mtab >> 6) + 0xc0;
1951                 special[1] = mtab & 0xbf;
1952                 length = 2;
1953             } else
1954 #endif
1955             {
1956                 special[0] = mtab;
1957                 length = 1;
1958             }
1959             mods &= ~MOD_MASK_ALT;
1960         }
1961     } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1962         // META key is treated separately.  This code was taken from gui_w48.c
1963         // and gui_gtk_x11.c.
1964         char_u string[7];
1965         int ch = simplify_key(chars[0], &mods);
1967         // Remove the SHIFT modifier for keys where it's already included,
1968         // e.g., '(' and '*'
1969         if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1970             mods &= ~MOD_MASK_SHIFT;
1972         // Interpret the ALT key as making the key META, include SHIFT, etc.
1973         ch = extract_modifiers(ch, &mods);
1974         if (ch == CSI)
1975             ch = K_CSI;
1977         int len = 0;
1978         if (mods) {
1979             string[len++] = CSI;
1980             string[len++] = KS_MODIFIER;
1981             string[len++] = mods;
1982         }
1984         if (IS_SPECIAL(ch)) {
1985             string[len++] = CSI;
1986             string[len++] = K_SECOND(ch);
1987             string[len++] = K_THIRD(ch);
1988         } else {
1989             string[len++] = ch;
1990 #ifdef FEAT_MBYTE
1991             // TODO: What if 'enc' is not "utf-8"?
1992             if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1993                 string[len++] = ch & 0xbf;
1994                 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1995                 if (string[len-1] == CSI) {
1996                     string[len++] = KS_EXTRA;
1997                     string[len++] = (int)KE_CSI;
1998                 }
1999             }
2000 #endif
2001         }
2003         add_to_input_buf(string, len);
2004         return;
2005     } else if (length > 0) {
2006         unichar c = [key characterAtIndex:0];
2007         //ASLogDebug(@"non-special: %@ (hex=%x, mods=%d)", key,
2008         //           [key characterAtIndex:0], mods);
2010         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
2011         // cleared since they are already added to the key by the AppKit.
2012         // Unfortunately, the only way to deal with when to clear the modifiers
2013         // or not seems to be to have hard-wired rules like this.
2014         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
2015                     || 0x9 == c || 0xd == c || ESC == c) ) {
2016             mods &= ~MOD_MASK_SHIFT;
2017             mods &= ~MOD_MASK_CTRL;
2018             //ASLogDebug(@"clear shift ctrl");
2019         }
2021 #ifdef FEAT_MBYTE
2022         if (input_conv.vc_type != CONV_NONE) {
2023             conv_str = string_convert(&input_conv, chars, &length);
2024             if (conv_str)
2025                 chars = conv_str;
2026         }
2027 #endif
2028     }
2030     if (chars && length > 0) {
2031         if (mods) {
2032             //ASLogDebug(@"adding mods: %d", mods);
2033             modChars[0] = CSI;
2034             modChars[1] = KS_MODIFIER;
2035             modChars[2] = mods;
2036             add_to_input_buf(modChars, 3);
2037         }
2039         //ASLogDebug(@"add to input buf: 0x%x", chars[0]);
2040         // TODO: Check for CSI bytes?
2041         add_to_input_buf(chars, length);
2042     }
2044 #ifdef FEAT_MBYTE
2045     if (conv_str)
2046         vim_free(conv_str);
2047 #endif
2050 - (void)queueMessage:(int)msgid data:(NSData *)data
2052     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
2053     if (data)
2054         [outputQueue addObject:data];
2055     else
2056         [outputQueue addObject:[NSData data]];
2059 - (void)connectionDidDie:(NSNotification *)notification
2061     // If the main connection to MacVim is lost this means that either MacVim
2062     // has crashed or this process did not receive its termination message
2063     // properly (e.g. if the TerminateNowMsgID was dropped).
2064     //
2065     // NOTE: This is not called if a Vim controller invalidates its connection.
2067     ASLogNotice(@"Main connection was lost before process had a chance "
2068                 "to terminate; preserving swap files.");
2069     getout_preserve_modified(1);
2072 - (void)blinkTimerFired:(NSTimer *)timer
2074     NSTimeInterval timeInterval = 0;
2076     [blinkTimer release];
2077     blinkTimer = nil;
2079     if (MMBlinkStateOn == blinkState) {
2080         gui_undraw_cursor();
2081         blinkState = MMBlinkStateOff;
2082         timeInterval = blinkOffInterval;
2083     } else if (MMBlinkStateOff == blinkState) {
2084         gui_update_cursor(TRUE, FALSE);
2085         blinkState = MMBlinkStateOn;
2086         timeInterval = blinkOnInterval;
2087     }
2089     if (timeInterval > 0) {
2090         blinkTimer = 
2091             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2092                                             selector:@selector(blinkTimerFired:)
2093                                             userInfo:nil repeats:NO] retain];
2094         [self flushQueue:YES];
2095     }
2098 - (void)focusChange:(BOOL)on
2100     gui_focus_change(on);
2103 - (void)handleToggleToolbar
2105     // If 'go' contains 'T', then remove it, else add it.
2107     char_u go[sizeof(GO_ALL)+2];
2108     char_u *p;
2109     int len;
2111     STRCPY(go, p_go);
2112     p = vim_strchr(go, GO_TOOLBAR);
2113     len = STRLEN(go);
2115     if (p != NULL) {
2116         char_u *end = go + len;
2117         while (p < end) {
2118             p[0] = p[1];
2119             ++p;
2120         }
2121     } else {
2122         go[len] = GO_TOOLBAR;
2123         go[len+1] = NUL;
2124     }
2126     set_option_value((char_u*)"guioptions", 0, go, 0);
2128     [self redrawScreen];
2131 - (void)handleScrollbarEvent:(NSData *)data
2133     if (!data) return;
2135     const void *bytes = [data bytes];
2136     long ident = *((long*)bytes);  bytes += sizeof(long);
2137     int hitPart = *((int*)bytes);  bytes += sizeof(int);
2138     float fval = *((float*)bytes);  bytes += sizeof(float);
2139     scrollbar_T *sb = gui_find_scrollbar(ident);
2141     if (sb) {
2142         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2143         long value = sb_info->value;
2144         long size = sb_info->size;
2145         long max = sb_info->max;
2146         BOOL isStillDragging = NO;
2147         BOOL updateKnob = YES;
2149         switch (hitPart) {
2150         case NSScrollerDecrementPage:
2151             value -= (size > 2 ? size - 2 : 1);
2152             break;
2153         case NSScrollerIncrementPage:
2154             value += (size > 2 ? size - 2 : 1);
2155             break;
2156         case NSScrollerDecrementLine:
2157             --value;
2158             break;
2159         case NSScrollerIncrementLine:
2160             ++value;
2161             break;
2162         case NSScrollerKnob:
2163             isStillDragging = YES;
2164             // fall through ...
2165         case NSScrollerKnobSlot:
2166             value = (long)(fval * (max - size + 1));
2167             // fall through ...
2168         default:
2169             updateKnob = NO;
2170             break;
2171         }
2173         gui_drag_scrollbar(sb, value, isStillDragging);
2175         if (updateKnob) {
2176             // Dragging the knob or option+clicking automatically updates
2177             // the knob position (on the actual NSScroller), so we only
2178             // need to set the knob position in the other cases.
2179             if (sb->wp) {
2180                 // Update both the left&right vertical scrollbars.
2181                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2182                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2183                 [self setScrollbarThumbValue:value size:size max:max
2184                                   identifier:identLeft];
2185                 [self setScrollbarThumbValue:value size:size max:max
2186                                   identifier:identRight];
2187             } else {
2188                 // Update the horizontal scrollbar.
2189                 [self setScrollbarThumbValue:value size:size max:max
2190                                   identifier:ident];
2191             }
2192         }
2193     }
2196 - (void)handleSetFont:(NSData *)data
2198     if (!data) return;
2200     const void *bytes = [data bytes];
2201     int pointSize = (int)*((float*)bytes);  bytes += sizeof(float);
2203     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2204     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2205     bytes += len;
2207     [name appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2208     char_u *s = (char_u*)[name UTF8String];
2210     unsigned wlen = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2211     char_u *ws = NULL;
2212     if (wlen > 0) {
2213         NSMutableString *wname = [NSMutableString stringWithUTF8String:bytes];
2214         bytes += wlen;
2216         [wname appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2217         ws = (char_u*)[wname UTF8String];
2218     }
2220 #ifdef FEAT_MBYTE
2221     s = CONVERT_FROM_UTF8(s);
2222     if (ws) {
2223         ws = CONVERT_FROM_UTF8(ws);
2224     }
2225 #endif
2227     set_option_value((char_u*)"guifont", 0, s, 0);
2229     if (ws && gui.wide_font != NOFONT) {
2230         // NOTE: This message is sent on Cmd-+/Cmd-- and as such should only
2231         // change the wide font if 'gfw' is non-empty (the frontend always has
2232         // some wide font set, even if 'gfw' is empty).
2233         set_option_value((char_u*)"guifontwide", 0, ws, 0);
2234     }
2236 #ifdef FEAT_MBYTE
2237     if (ws) {
2238         CONVERT_FROM_UTF8_FREE(ws);
2239     }
2240     CONVERT_FROM_UTF8_FREE(s);
2241 #endif
2243     [self redrawScreen];
2246 - (void)handleDropFiles:(NSData *)data
2248     // TODO: Get rid of this method; instead use Vim script directly.  At the
2249     // moment I know how to do this to open files in tabs, but I'm not sure how
2250     // to add the filenames to the command line when in command line mode.
2252     if (!data) return;
2254     NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2255     if (!args) return;
2257     id obj = [args objectForKey:@"forceOpen"];
2258     BOOL forceOpen = YES;
2259     if (obj)
2260         forceOpen = [obj boolValue];
2262     NSArray *filenames = [args objectForKey:@"filenames"];
2263     if (!(filenames && [filenames count] > 0)) return;
2265 #ifdef FEAT_DND
2266     if (!forceOpen && (State & CMDLINE)) {
2267         // HACK!  If Vim is in command line mode then the files names
2268         // should be added to the command line, instead of opening the
2269         // files in tabs (unless forceOpen is set).  This is taken care of by
2270         // gui_handle_drop().
2271         int n = [filenames count];
2272         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2273         if (fnames) {
2274             int i = 0;
2275             for (i = 0; i < n; ++i)
2276                 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2278             // NOTE!  This function will free 'fnames'.
2279             // HACK!  It is assumed that the 'x' and 'y' arguments are
2280             // unused when in command line mode.
2281             gui_handle_drop(0, 0, 0, fnames, n);
2282         }
2283     } else
2284 #endif // FEAT_DND
2285     {
2286         [self handleOpenWithArguments:args];
2287     }
2290 - (void)handleDropString:(NSData *)data
2292     if (!data) return;
2294 #ifdef FEAT_DND
2295     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2296     const void *bytes = [data bytes];
2297     int len = *((int*)bytes);  bytes += sizeof(int);
2298     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2300     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2301     NSRange range = { 0, [string length] };
2302     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2303                                          withString:@"\x0a" options:0
2304                                               range:range];
2305     if (0 == n) {
2306         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2307                                        options:0 range:range];
2308     }
2310     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2311     char_u *s = (char_u*)[string UTF8String];
2312 #ifdef FEAT_MBYTE
2313     if (input_conv.vc_type != CONV_NONE)
2314         s = string_convert(&input_conv, s, &len);
2315 #endif
2316     dnd_yank_drag_data(s, len);
2317 #ifdef FEAT_MBYTE
2318     if (input_conv.vc_type != CONV_NONE)
2319         vim_free(s);
2320 #endif
2321     add_to_input_buf(dropkey, sizeof(dropkey));
2322 #endif // FEAT_DND
2325 - (void)startOdbEditWithArguments:(NSDictionary *)args
2327 #ifdef FEAT_ODB_EDITOR
2328     id obj = [args objectForKey:@"remoteID"];
2329     if (!obj) return;
2331     OSType serverID = [obj unsignedIntValue];
2332     NSString *remotePath = [args objectForKey:@"remotePath"];
2334     NSAppleEventDescriptor *token = nil;
2335     NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2336     obj = [args objectForKey:@"remoteTokenDescType"];
2337     if (tokenData && obj) {
2338         DescType tokenType = [obj unsignedLongValue];
2339         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2340                                                                 data:tokenData];
2341     }
2343     NSArray *filenames = [args objectForKey:@"filenames"];
2344     unsigned i, numFiles = [filenames count];
2345     for (i = 0; i < numFiles; ++i) {
2346         NSString *filename = [filenames objectAtIndex:i];
2347         char_u *s = [filename vimStringSave];
2348         buf_T *buf = buflist_findname(s);
2349         vim_free(s);
2351         if (buf) {
2352             if (buf->b_odb_token) {
2353                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2354                 buf->b_odb_token = NULL;
2355             }
2357             if (buf->b_odb_fname) {
2358                 vim_free(buf->b_odb_fname);
2359                 buf->b_odb_fname = NULL;
2360             }
2362             buf->b_odb_server_id = serverID;
2364             if (token)
2365                 buf->b_odb_token = [token retain];
2366             if (remotePath)
2367                 buf->b_odb_fname = [remotePath vimStringSave];
2368         } else {
2369             ASLogWarn(@"Could not find buffer '%@' for ODB editing.", filename);
2370         }
2371     }
2372 #endif // FEAT_ODB_EDITOR
2375 - (void)handleXcodeMod:(NSData *)data
2377 #if 0
2378     const void *bytes = [data bytes];
2379     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2380     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2381     if (0 == len)
2382         return;
2384     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2385             descriptorWithDescriptorType:type
2386                                    bytes:bytes
2387                                   length:len];
2388 #endif
2391 - (void)handleOpenWithArguments:(NSDictionary *)args
2393     // ARGUMENT:                DESCRIPTION:
2394     // -------------------------------------------------------------
2395     // filenames                list of filenames
2396     // dontOpen                 don't open files specified in above argument
2397     // layout                   which layout to use to open files
2398     // selectionRange           range of lines to select
2399     // searchText               string to search for
2400     // cursorLine               line to position the cursor on
2401     // cursorColumn             column to position the cursor on
2402     //                          (only valid when "cursorLine" is set)
2403     // remoteID                 ODB parameter
2404     // remotePath               ODB parameter
2405     // remoteTokenDescType      ODB parameter
2406     // remoteTokenData          ODB parameter
2408     ASLogDebug(@"args=%@ (starting=%d)", args, starting);
2410     NSArray *filenames = [args objectForKey:@"filenames"];
2411     int i, numFiles = filenames ? [filenames count] : 0;
2412     BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2413     int layout = [[args objectForKey:@"layout"] intValue];
2415     // Change to directory of first file to open if this is an "unused" editor
2416     // (but do not do this if editing remotely).
2417     if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2418             && (starting || [self unusedEditor]) ) {
2419         char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2420         if (mch_isdir(s)) {
2421             mch_chdir((char*)s);
2422         } else {
2423             vim_chdirfile(s);
2424         }
2425         vim_free(s);
2426     }
2428     if (starting > 0) {
2429         // When Vim is starting we simply add the files to be opened to the
2430         // global arglist and Vim will take care of opening them for us.
2431         if (openFiles && numFiles > 0) {
2432             for (i = 0; i < numFiles; i++) {
2433                 NSString *fname = [filenames objectAtIndex:i];
2434                 char_u *p = NULL;
2436                 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2437                         || (p = [fname vimStringSave]) == NULL)
2438                     exit(2); // See comment in -[MMBackend exit]
2439                 else
2440                     alist_add(&global_alist, p, 2);
2441             }
2443             // Vim will take care of arranging the files added to the arglist
2444             // in windows or tabs; all we must do is to specify which layout to
2445             // use.
2446             initialWindowLayout = layout;
2447         }
2448     } else {
2449         // When Vim is already open we resort to some trickery to open the
2450         // files with the specified layout.
2451         //
2452         // TODO: Figure out a better way to handle this?
2453         if (openFiles && numFiles > 0) {
2454             BOOL oneWindowInTab = topframe ? YES
2455                                            : (topframe->fr_layout == FR_LEAF);
2456             BOOL bufChanged = NO;
2457             BOOL bufHasFilename = NO;
2458             if (curbuf) {
2459                 bufChanged = curbufIsChanged();
2460                 bufHasFilename = curbuf->b_ffname != NULL;
2461             }
2463             // Temporarily disable flushing since the following code may
2464             // potentially cause multiple redraws.
2465             flushDisabled = YES;
2467             BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2468             if (WIN_TABS == layout && !onlyOneTab) {
2469                 // By going to the last tabpage we ensure that the new tabs
2470                 // will appear last (if this call is left out, the taborder
2471                 // becomes messy).
2472                 goto_tabpage(9999);
2473             }
2475             // Make sure we're in normal mode first.
2476             [self addInput:@"<C-\\><C-N>"];
2478             if (numFiles > 1) {
2479                 // With "split layout" we open a new tab before opening
2480                 // multiple files if the current tab has more than one window
2481                 // or if there is exactly one window but whose buffer has a
2482                 // filename.  (The :drop command ensures modified buffers get
2483                 // their own window.)
2484                 if ((WIN_HOR == layout || WIN_VER == layout) &&
2485                         (!oneWindowInTab || bufHasFilename))
2486                     [self addInput:@":tabnew<CR>"];
2488                 // The files are opened by constructing a ":drop ..." command
2489                 // and executing it.
2490                 NSMutableString *cmd = (WIN_TABS == layout)
2491                         ? [NSMutableString stringWithString:@":tab drop"]
2492                         : [NSMutableString stringWithString:@":drop"];
2494                 for (i = 0; i < numFiles; ++i) {
2495                     NSString *file = [filenames objectAtIndex:i];
2496                     file = [file stringByEscapingSpecialFilenameCharacters];
2497                     [cmd appendString:@" "];
2498                     [cmd appendString:file];
2499                 }
2501                 // Temporarily clear 'suffixes' so that the files are opened in
2502                 // the same order as they appear in the "filenames" array.
2503                 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2505                 [self addInput:cmd];
2507                 // Split the view into multiple windows if requested.
2508                 if (WIN_HOR == layout)
2509                     [self addInput:@"|sall"];
2510                 else if (WIN_VER == layout)
2511                     [self addInput:@"|vert sall"];
2513                 // Restore the old value of 'suffixes'.
2514                 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2515             } else {
2516                 // When opening one file we try to reuse the current window,
2517                 // but not if its buffer is modified or has a filename.
2518                 // However, the 'arglist' layout always opens the file in the
2519                 // current window.
2520                 NSString *file = [[filenames lastObject]
2521                         stringByEscapingSpecialFilenameCharacters];
2522                 NSString *cmd;
2523                 if (WIN_HOR == layout) {
2524                     if (!(bufHasFilename || bufChanged))
2525                         cmd = [NSString stringWithFormat:@":e %@", file];
2526                     else
2527                         cmd = [NSString stringWithFormat:@":sp %@", file];
2528                 } else if (WIN_VER == layout) {
2529                     if (!(bufHasFilename || bufChanged))
2530                         cmd = [NSString stringWithFormat:@":e %@", file];
2531                     else
2532                         cmd = [NSString stringWithFormat:@":vsp %@", file];
2533                 } else if (WIN_TABS == layout) {
2534                     if (oneWindowInTab && !(bufHasFilename || bufChanged))
2535                         cmd = [NSString stringWithFormat:@":e %@", file];
2536                     else
2537                         cmd = [NSString stringWithFormat:@":tabe %@", file];
2538                 } else {
2539                     // (The :drop command will split if there is a modified
2540                     // buffer.)
2541                     cmd = [NSString stringWithFormat:@":drop %@", file];
2542                 }
2544                 [self addInput:cmd];
2545                 [self addInput:@"<CR>"];
2546             }
2548             // Force screen redraw (does it have to be this complicated?).
2549             // (This code was taken from the end of gui_handle_drop().)
2550             update_screen(NOT_VALID);
2551             setcursor();
2552             out_flush();
2553             gui_update_cursor(FALSE, FALSE);
2554             maketitle();
2556             flushDisabled = NO;
2557         }
2558     }
2560     if ([args objectForKey:@"remoteID"]) {
2561         // NOTE: We have to delay processing any ODB related arguments since
2562         // the file(s) may not be opened until the input buffer is processed.
2563         [self performSelector:@selector(startOdbEditWithArguments:)
2564                    withObject:args
2565                    afterDelay:0];
2566     }
2568     NSString *lineString = [args objectForKey:@"cursorLine"];
2569     if (lineString && [lineString intValue] > 0) {
2570         NSString *columnString = [args objectForKey:@"cursorColumn"];
2571         if (!(columnString && [columnString intValue] > 0))
2572             columnString = @"1";
2574         NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2575                 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2576         [self addInput:cmd];
2577     }
2579     NSString *rangeString = [args objectForKey:@"selectionRange"];
2580     if (rangeString) {
2581         // Build a command line string that will select the given range of
2582         // lines.  If range.length == 0, then position the cursor on the given
2583         // line but do not select.
2584         NSRange range = NSRangeFromString(rangeString);
2585         NSString *cmd;
2586         if (range.length > 0) {
2587             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2588                     NSMaxRange(range), range.location];
2589         } else {
2590             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2591                     range.location];
2592         }
2594         [self addInput:cmd];
2595     }
2597     NSString *searchText = [args objectForKey:@"searchText"];
2598     if (searchText) {
2599         [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2600                 searchText]];
2601     }
2604 - (BOOL)checkForModifiedBuffers
2606     buf_T *buf;
2607     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2608         if (bufIsChanged(buf)) {
2609             return YES;
2610         }
2611     }
2613     return NO;
2616 - (void)addInput:(NSString *)input
2618     // NOTE: This code is essentially identical to server_to_input_buf(),
2619     // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2620     char_u *string = [input vimStringSave];
2621     if (!string) return;
2623     /* Set 'cpoptions' the way we want it.
2624      *    B set - backslashes are *not* treated specially
2625      *    k set - keycodes are *not* reverse-engineered
2626      *    < unset - <Key> sequences *are* interpreted
2627      *  The last but one parameter of replace_termcodes() is TRUE so that the
2628      *  <lt> sequence is recognised - needed for a real backslash.
2629      */
2630     char_u *ptr = NULL;
2631     char_u *cpo_save = p_cpo;
2632     p_cpo = (char_u *)"Bk";
2633     char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2634     p_cpo = cpo_save;
2636     if (*ptr != NUL)    /* trailing CTRL-V results in nothing */
2637     {
2638         /*
2639          * Add the string to the input stream.
2640          * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2641          *
2642          * First clear typed characters from the typeahead buffer, there could
2643          * be half a mapping there.  Then append to the existing string, so
2644          * that multiple commands from a client are concatenated.
2645          */
2646         if (typebuf.tb_maplen < typebuf.tb_len)
2647             del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2648         (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2650         /* Let input_available() know we inserted text in the typeahead
2651          * buffer. */
2652         typebuf_was_filled = TRUE;
2653     }
2654     vim_free(ptr);
2655     vim_free(string);
2658 - (BOOL)unusedEditor
2660     BOOL oneWindowInTab = topframe ? YES
2661                                    : (topframe->fr_layout == FR_LEAF);
2662     BOOL bufChanged = NO;
2663     BOOL bufHasFilename = NO;
2664     if (curbuf) {
2665         bufChanged = curbufIsChanged();
2666         bufHasFilename = curbuf->b_ffname != NULL;
2667     }
2669     BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2671     return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2674 - (void)redrawScreen
2676     // Force screen redraw (does it have to be this complicated?).
2677     redraw_all_later(CLEAR);
2678     update_screen(NOT_VALID);
2679     setcursor();
2680     out_flush();
2681     gui_update_cursor(FALSE, FALSE);
2683     // HACK! The cursor is not put back at the command line by the above
2684     // "redraw commands".  The following test seems to do the trick though.
2685     if (State & CMDLINE)
2686         redrawcmdline();
2689 - (void)handleFindReplace:(NSDictionary *)args
2691     if (!args) return;
2693     NSString *findString = [args objectForKey:@"find"];
2694     if (!findString) return;
2696     char_u *find = [findString vimStringSave];
2697     char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2698     int flags = [[args objectForKey:@"flags"] intValue];
2700     // NOTE: The flag 0x100 is used to indicate a backward search.
2701     gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2703     vim_free(find);
2704     vim_free(replace);
2707 @end // MMBackend (Private)
2712 @implementation MMBackend (ClientServer)
2714 - (NSString *)connectionNameFromServerName:(NSString *)name
2716     NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2718     return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2719         lowercaseString];
2722 - (NSConnection *)connectionForServerName:(NSString *)name
2724     // TODO: Try 'name%d' if 'name' fails.
2725     NSString *connName = [self connectionNameFromServerName:name];
2726     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2728     if (!svrConn) {
2729         svrConn = [NSConnection connectionWithRegisteredName:connName
2730                                                            host:nil];
2731         // Try alternate server...
2732         if (!svrConn && alternateServerName) {
2733             ASLogInfo(@"  trying to connect to alternate server: %@",
2734                       alternateServerName);
2735             connName = [self connectionNameFromServerName:alternateServerName];
2736             svrConn = [NSConnection connectionWithRegisteredName:connName
2737                                                             host:nil];
2738         }
2740         // Try looking for alternate servers...
2741         if (!svrConn) {
2742             ASLogInfo(@"  looking for alternate servers...");
2743             NSString *alt = [self alternateServerNameForName:name];
2744             if (alt != alternateServerName) {
2745                 ASLogInfo(@"  found alternate server: %@", alt);
2746                 [alternateServerName release];
2747                 alternateServerName = [alt copy];
2748             }
2749         }
2751         // Try alternate server again...
2752         if (!svrConn && alternateServerName) {
2753             ASLogInfo(@"  trying to connect to alternate server: %@",
2754                       alternateServerName);
2755             connName = [self connectionNameFromServerName:alternateServerName];
2756             svrConn = [NSConnection connectionWithRegisteredName:connName
2757                                                             host:nil];
2758         }
2760         if (svrConn) {
2761             [connectionNameDict setObject:svrConn forKey:connName];
2763             ASLogDebug(@"Adding %@ as connection observer for %@",
2764                        self, svrConn);
2765             [[NSNotificationCenter defaultCenter] addObserver:self
2766                     selector:@selector(serverConnectionDidDie:)
2767                         name:NSConnectionDidDieNotification object:svrConn];
2768         }
2769     }
2771     return svrConn;
2774 - (NSConnection *)connectionForServerPort:(int)port
2776     NSConnection *conn;
2777     NSEnumerator *e = [connectionNameDict objectEnumerator];
2779     while ((conn = [e nextObject])) {
2780         // HACK! Assume connection uses mach ports.
2781         if (port == [(NSMachPort*)[conn sendPort] machPort])
2782             return conn;
2783     }
2785     return nil;
2788 - (void)serverConnectionDidDie:(NSNotification *)notification
2790     ASLogDebug(@"notification=%@", notification);
2792     NSConnection *svrConn = [notification object];
2794     ASLogDebug(@"Removing %@ as connection observer from %@", self, svrConn);
2795     [[NSNotificationCenter defaultCenter]
2796             removeObserver:self
2797                       name:NSConnectionDidDieNotification
2798                     object:svrConn];
2800     [connectionNameDict removeObjectsForKeys:
2801         [connectionNameDict allKeysForObject:svrConn]];
2803     // HACK! Assume connection uses mach ports.
2804     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2805     NSNumber *key = [NSNumber numberWithInt:port];
2807     [clientProxyDict removeObjectForKey:key];
2808     [serverReplyDict removeObjectForKey:key];
2811 - (void)addClient:(NSDistantObject *)client
2813     NSConnection *conn = [client connectionForProxy];
2814     // HACK! Assume connection uses mach ports.
2815     int port = [(NSMachPort*)[conn sendPort] machPort];
2816     NSNumber *key = [NSNumber numberWithInt:port];
2818     if (![clientProxyDict objectForKey:key]) {
2819         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2820         [clientProxyDict setObject:client forKey:key];
2821     }
2823     // NOTE: 'clientWindow' is a global variable which is used by <client>
2824     clientWindow = port;
2827 - (NSString *)alternateServerNameForName:(NSString *)name
2829     if (!(name && [name length] > 0))
2830         return nil;
2832     // Only look for alternates if 'name' doesn't end in a digit.
2833     unichar lastChar = [name characterAtIndex:[name length]-1];
2834     if (lastChar >= '0' && lastChar <= '9')
2835         return nil;
2837     // Look for alternates among all current servers.
2838     NSArray *list = [self serverList];
2839     if (!(list && [list count] > 0))
2840         return nil;
2842     // Filter out servers starting with 'name' and ending with a number. The
2843     // (?i) pattern ensures that the match is case insensitive.
2844     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2845     NSPredicate *pred = [NSPredicate predicateWithFormat:
2846             @"SELF MATCHES %@", pat];
2847     list = [list filteredArrayUsingPredicate:pred];
2848     if ([list count] > 0) {
2849         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2850         return [list objectAtIndex:0];
2851     }
2853     return nil;
2856 @end // MMBackend (ClientServer)
2861 @implementation NSString (MMServerNameCompare)
2862 - (NSComparisonResult)serverNameCompare:(NSString *)string
2864     return [self compare:string
2865                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2867 @end
2872 static int eventModifierFlagsToVimModMask(int modifierFlags)
2874     int modMask = 0;
2876     if (modifierFlags & NSShiftKeyMask)
2877         modMask |= MOD_MASK_SHIFT;
2878     if (modifierFlags & NSControlKeyMask)
2879         modMask |= MOD_MASK_CTRL;
2880     if (modifierFlags & NSAlternateKeyMask)
2881         modMask |= MOD_MASK_ALT;
2882     if (modifierFlags & NSCommandKeyMask)
2883         modMask |= MOD_MASK_CMD;
2885     return modMask;
2888 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2890     int modMask = 0;
2892     if (modifierFlags & NSShiftKeyMask)
2893         modMask |= MOUSE_SHIFT;
2894     if (modifierFlags & NSControlKeyMask)
2895         modMask |= MOUSE_CTRL;
2896     if (modifierFlags & NSAlternateKeyMask)
2897         modMask |= MOUSE_ALT;
2899     return modMask;
2902 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2904     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2906     return (buttonNumber >= 0 && buttonNumber < 3)
2907             ? mouseButton[buttonNumber] : -1;
2912 // This function is modeled after the VimToPython function found in if_python.c
2913 // NB This does a deep copy by value, it does not lookup references like the
2914 // VimToPython function does.  This is because I didn't want to deal with the
2915 // retain cycles that this would create, and we can cover 99% of the use cases
2916 // by ignoring it.  If we ever switch to using GC in MacVim then this
2917 // functionality can be implemented easily.
2918 static id vimToCocoa(typval_T * tv, int depth)
2920     id result = nil;
2921     id newObj = nil;
2924     // Avoid infinite recursion
2925     if (depth > 100) {
2926         return nil;
2927     }
2929     if (tv->v_type == VAR_STRING) {
2930         char_u * val = tv->vval.v_string;
2931         // val can be NULL if the string is empty
2932         if (!val) {
2933             result = [NSString string];
2934         } else {
2935 #ifdef FEAT_MBYTE
2936             val = CONVERT_TO_UTF8(val);
2937 #endif
2938             result = [NSString stringWithUTF8String:(char*)val];
2939 #ifdef FEAT_MBYTE
2940             CONVERT_TO_UTF8_FREE(val);
2941 #endif
2942         }
2943     } else if (tv->v_type == VAR_NUMBER) {
2944         // looks like sizeof(varnumber_T) is always <= sizeof(long)
2945         result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2946     } else if (tv->v_type == VAR_LIST) {
2947         list_T * list = tv->vval.v_list;
2948         listitem_T * curr;
2950         NSMutableArray * arr = result = [NSMutableArray array];
2952         if (list != NULL) {
2953             for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2954                 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2955                 [arr addObject:newObj];
2956             }
2957         }
2958     } else if (tv->v_type == VAR_DICT) {
2959         NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2961         if (tv->vval.v_dict != NULL) {
2962             hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2963             int todo = ht->ht_used;
2964             hashitem_T * hi;
2965             dictitem_T * di;
2967             for (hi = ht->ht_array; todo > 0; ++hi) {
2968                 if (!HASHITEM_EMPTY(hi)) {
2969                     --todo;
2971                     di = dict_lookup(hi);
2972                     newObj = vimToCocoa(&di->di_tv, depth + 1);
2974                     char_u * keyval = hi->hi_key;
2975 #ifdef FEAT_MBYTE
2976                     keyval = CONVERT_TO_UTF8(keyval);
2977 #endif
2978                     NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2979 #ifdef FEAT_MBYTE
2980                     CONVERT_TO_UTF8_FREE(keyval);
2981 #endif
2982                     [dict setObject:newObj forKey:key];
2983                 }
2984             }
2985         }
2986     } else { // only func refs should fall into this category?
2987         result = nil;
2988     }
2990     return result;
2994 // This function is modeled after eval_client_expr_to_string found in main.c
2995 // Returns nil if there was an error evaluating the expression, and writes a
2996 // message to errorStr.
2997 // TODO Get the error that occurred while evaluating the expression in vim
2998 // somehow.
2999 static id evalExprCocoa(NSString * expr, NSString ** errstr)
3002     char_u *s = (char_u*)[expr UTF8String];
3004 #ifdef FEAT_MBYTE
3005     s = CONVERT_FROM_UTF8(s);
3006 #endif
3008     int save_dbl = debug_break_level;
3009     int save_ro = redir_off;
3011     debug_break_level = -1;
3012     redir_off = 0;
3013     ++emsg_skip;
3015     typval_T * tvres = eval_expr(s, NULL);
3017     debug_break_level = save_dbl;
3018     redir_off = save_ro;
3019     --emsg_skip;
3021     setcursor();
3022     out_flush();
3024 #ifdef FEAT_MBYTE
3025     CONVERT_FROM_UTF8_FREE(s);
3026 #endif
3028 #ifdef FEAT_GUI
3029     if (gui.in_use)
3030         gui_update_cursor(FALSE, FALSE);
3031 #endif
3033     if (tvres == NULL) {
3034         free_tv(tvres);
3035         *errstr = @"Expression evaluation failed.";
3036     }
3038     id res = vimToCocoa(tvres, 1);
3040     free_tv(tvres);
3042     if (res == nil) {
3043         *errstr = @"Conversion to cocoa values failed.";
3044     }
3046     return res;
3051 @implementation NSString (VimStrings)
3053 + (id)stringWithVimString:(char_u *)s
3055     // This method ensures a non-nil string is returned.  If 's' cannot be
3056     // converted to a utf-8 string it is assumed to be latin-1.  If conversion
3057     // still fails an empty NSString is returned.
3058     NSString *string = nil;
3059     if (s) {
3060 #ifdef FEAT_MBYTE
3061         s = CONVERT_TO_UTF8(s);
3062 #endif
3063         string = [NSString stringWithUTF8String:(char*)s];
3064         if (!string) {
3065             // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
3066             // latin-1?
3067             string = [NSString stringWithCString:(char*)s
3068                                         encoding:NSISOLatin1StringEncoding];
3069         }
3070 #ifdef FEAT_MBYTE
3071         CONVERT_TO_UTF8_FREE(s);
3072 #endif
3073     }
3075     return string != nil ? string : [NSString string];
3078 - (char_u *)vimStringSave
3080     char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3082 #ifdef FEAT_MBYTE
3083     s = CONVERT_FROM_UTF8(s);
3084 #endif
3085     ret = vim_strsave(s);
3086 #ifdef FEAT_MBYTE
3087     CONVERT_FROM_UTF8_FREE(s);
3088 #endif
3090     return ret;
3093 @end // NSString (VimStrings)