Set correct protocol for frontend proxy
[MacVim.git] / src / MacVim / MMBackend.m
blob5806e09b6aceab996a4010d53b104d8148f6b603
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
11  * MMBackend
12  *
13  * MMBackend communicates with the frontend (MacVim).  It maintains a queue of
14  * output which is flushed to the frontend under controlled circumstances (so
15  * as to maintain a steady framerate).  Input from the frontend is also handled
16  * here.
17  *
18  * The frontend communicates with the backend via the MMBackendProtocol.  In
19  * particular, input is sent to the backend via processInput:data: and Vim
20  * state can be queried from the frontend with evaluateExpression:.
21  *
22  * It is very important to realize that all state is held by the backend, the
23  * frontend must either ask for state [MMBackend evaluateExpression:] or wait
24  * for the backend to update [MMVimController processCommandQueue:].
25  *
26  * The client/server functionality of Vim is handled by the backend.  It sets
27  * up a named NSConnection to which other Vim processes can connect.
28  */
30 #import "MMBackend.h"
34 // NOTE: Colors in MMBackend are stored as unsigned ints on the form 0xaarrggbb
35 // whereas colors in Vim are int without the alpha component.  Also note that
36 // 'transp' is assumed to be a value between 0 and 100.
37 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
38 #define MM_COLOR_WITH_TRANSP(col,transp) \
39     ((unsigned)( ((col)&0xffffff) \
40         | ((((unsigned)((((100-(transp))*255)/100)+.5f))&0xff)<<24) ))
42 // Values for window layout (must match values in main.c).
43 #define WIN_HOR     1       // "-o" horizontally split windows
44 #define WIN_VER     2       // "-O" vertically split windows
45 #define WIN_TABS    3       // "-p" windows on tab pages
47 static unsigned MMServerMax = 1000;
49 // TODO: Move to separate file.
50 static int eventModifierFlagsToVimModMask(int modifierFlags);
51 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
52 static int eventButtonNumberToVimMouseButton(int buttonNumber);
54 // 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         NSLog(@"ERROR: Failed to load dictionaries.%@",
162                 MMSymlinkWarningString);
164     return self;
167 - (void)dealloc
169     //NSLog(@"%@ %s", [self className], _cmd);
170     [[NSNotificationCenter defaultCenter] removeObserver:self];
172     gui_mch_free_font(oldWideFont);  oldWideFont = NOFONT;
173     [blinkTimer release];  blinkTimer = nil;
174     [alternateServerName release];  alternateServerName = nil;
175     [serverReplyDict release];  serverReplyDict = nil;
176     [clientProxyDict release];  clientProxyDict = nil;
177     [connectionNameDict release];  connectionNameDict = nil;
178     [inputQueue release];  inputQueue = nil;
179     [outputQueue release];  outputQueue = nil;
180     [drawData release];  drawData = nil;
181     [frontendProxy release];  frontendProxy = 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         NSLog(@"ERROR: 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             NSLog(@"ERROR: 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             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
327             return NO;
328         }
329     }
331     BOOL ok = NO;
332     @try {
333         [[NSNotificationCenter defaultCenter] addObserver:self
334                 selector:@selector(connectionDidDie:)
335                     name:NSConnectionDidDieNotification object:connection];
337         id proxy = [connection rootProxy];
338         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
340         int pid = [[NSProcessInfo processInfo] processIdentifier];
342         frontendProxy = [proxy connectBackend:self pid:pid];
343         if (frontendProxy) {
344             [frontendProxy retain];
345             [frontendProxy setProtocolForProxy:@protocol(MMFrontendProtocol)];
346             ok = YES;
347         }
348     }
349     @catch (NSException *e) {
350         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
351     }
353     return ok;
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             [frontendProxy processCommandQueue:outputQueue];
510         }
511         @catch (NSException *e) {
512             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
513             NSLog(@"outputQueue(len:%d)=%@", [outputQueue count]/2,
514                     outputQueue);
515             if (![connection isValid]) {
516                 NSLog(@"WARNING! Connection is invalid, exit now!");
517                 NSLog(@"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             [frontendProxy processCommandQueue:outputQueue];
579         }
580         @catch (NSException *e) {
581             NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
582         }
584         // NOTE: If Cmd-w was pressed to close the window the menu is briefly
585         // highlighted and during this pause the frontend won't receive any DO
586         // messages.  If the Vim process exits before this highlighting has
587         // finished Cocoa will emit the following error message:
588         //   *** -[NSMachPort handlePortMessage:]: dropping incoming DO message
589         //   because the connection or ports are invalid
590         // To avoid this warning we delay here.  If the warning still appears
591         // this delay may need to be increased.
592         usleep(150000);
593     }
595 #ifdef MAC_CLIENTSERVER
596     // The default connection is used for the client/server code.
597     [[NSConnection defaultConnection] setRootObject:nil];
598     [[NSConnection defaultConnection] invalidate];
599 #endif
602 - (void)selectTab:(int)index
604     //NSLog(@"%s%d", _cmd, index);
606     index -= 1;
607     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
608     [self queueMessage:SelectTabMsgID data:data];
611 - (void)updateTabBar
613     //NSLog(@"%s", _cmd);
615     NSMutableData *data = [NSMutableData data];
617     int idx = tabpage_index(curtab) - 1;
618     [data appendBytes:&idx length:sizeof(int)];
620     tabpage_T *tp;
621     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
622         // Count the number of windows in the tabpage.
623         //win_T *wp = tp->tp_firstwin;
624         //int wincount;
625         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
626         //[data appendBytes:&wincount length:sizeof(int)];
628         int tabProp = MMTabInfoCount;
629         [data appendBytes:&tabProp length:sizeof(int)];
630         for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
631             // This function puts the label of the tab in the global 'NameBuff'.
632             get_tabline_label(tp, (tabProp == MMTabToolTip));
633             NSString *s = [NSString stringWithVimString:NameBuff];
634             int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
635             if (len < 0)
636                 len = 0;
638             [data appendBytes:&len length:sizeof(int)];
639             if (len > 0)
640                 [data appendBytes:[s UTF8String] length:len];
641         }
642     }
644     [self queueMessage:UpdateTabBarMsgID data:data];
647 - (BOOL)tabBarVisible
649     return tabBarVisible;
652 - (void)showTabBar:(BOOL)enable
654     tabBarVisible = enable;
656     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
657     [self queueMessage:msgid data:nil];
660 - (void)setRows:(int)rows columns:(int)cols
662     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
664     int dim[] = { rows, cols };
665     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
667     [self queueMessage:SetTextDimensionsMsgID data:data];
670 - (void)setWindowTitle:(char *)title
672     NSMutableData *data = [NSMutableData data];
673     int len = strlen(title);
674     if (len <= 0) return;
676     [data appendBytes:&len length:sizeof(int)];
677     [data appendBytes:title length:len];
679     [self queueMessage:SetWindowTitleMsgID data:data];
682 - (void)setDocumentFilename:(char *)filename
684     NSMutableData *data = [NSMutableData data];
685     int len = filename ? strlen(filename) : 0;
687     [data appendBytes:&len length:sizeof(int)];
688     if (len > 0)
689         [data appendBytes:filename length:len];
691     [self queueMessage:SetDocumentFilenameMsgID data:data];
694 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
696     char_u *s = NULL;
698     @try {
699         [frontendProxy showSavePanelWithAttributes:attr];
701         [self waitForDialogReturn];
703         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
704             s = [dialogReturn vimStringSave];
706         [dialogReturn release];  dialogReturn = nil;
707     }
708     @catch (NSException *e) {
709         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
710     }
712     return (char *)s;
715 - (oneway void)setDialogReturn:(in bycopy id)obj
717     // NOTE: This is called by
718     //   - [MMVimController panelDidEnd:::], and
719     //   - [MMVimController alertDidEnd:::],
720     // to indicate that a save/open panel or alert has finished.
722     // We want to distinguish between "no dialog return yet" and "dialog
723     // returned nothing".  The former can be tested with dialogReturn == nil,
724     // the latter with dialogReturn == [NSNull null].
725     if (!obj) obj = [NSNull null];
727     if (obj != dialogReturn) {
728         [dialogReturn release];
729         dialogReturn = [obj retain];
730     }
733 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
735     int retval = 0;
737     @try {
738         [frontendProxy presentDialogWithAttributes:attr];
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 *e) {
761         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
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     //NSLog(@"WARNING: 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 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1112     // This is just a convenience method that allows the frontend to delay
1113     // sending messages.
1114     int i, count = [messages count];
1115     for (i = 1; i < count; i+=2)
1116         [self processInput:[[messages objectAtIndex:i-1] intValue]
1117                       data:[messages objectAtIndex:i]];
1120 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1121                   errorString:(out bycopy NSString **)errstr
1123     return evalExprCocoa(expr, errstr);
1127 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1129     NSString *eval = nil;
1130     char_u *s = (char_u*)[expr UTF8String];
1132 #ifdef FEAT_MBYTE
1133     s = CONVERT_FROM_UTF8(s);
1134 #endif
1136     char_u *res = eval_client_expr_to_string(s);
1138 #ifdef FEAT_MBYTE
1139     CONVERT_FROM_UTF8_FREE(s);
1140 #endif
1142     if (res != NULL) {
1143         s = res;
1144 #ifdef FEAT_MBYTE
1145         s = CONVERT_TO_UTF8(s);
1146 #endif
1147         eval = [NSString stringWithUTF8String:(char*)s];
1148 #ifdef FEAT_MBYTE
1149         CONVERT_TO_UTF8_FREE(s);
1150 #endif
1151         vim_free(res);
1152     }
1154     return eval;
1157 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1159     // TODO: This method should share code with clip_mch_request_selection().
1161     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1162         // If there is no pasteboard, return YES to indicate that there is text
1163         // to copy.
1164         if (!pboard)
1165             return YES;
1167         clip_copy_selection();
1169         // Get the text to put on the pasteboard.
1170         long_u llen = 0; char_u *str = 0;
1171         int type = clip_convert_selection(&str, &llen, &clip_star);
1172         if (type < 0)
1173             return NO;
1174         
1175         // TODO: Avoid overflow.
1176         int len = (int)llen;
1177 #ifdef FEAT_MBYTE
1178         if (output_conv.vc_type != CONV_NONE) {
1179             char_u *conv_str = string_convert(&output_conv, str, &len);
1180             if (conv_str) {
1181                 vim_free(str);
1182                 str = conv_str;
1183             }
1184         }
1185 #endif
1187         NSString *string = [[NSString alloc]
1188             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1190         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1191         [pboard declareTypes:types owner:nil];
1192         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1193     
1194         [string release];
1195         vim_free(str);
1197         return ok;
1198     }
1200     return NO;
1203 - (oneway void)addReply:(in bycopy NSString *)reply
1204                  server:(in byref id <MMVimServerProtocol>)server
1206     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1208     // Replies might come at any time and in any order so we keep them in an
1209     // array inside a dictionary with the send port used as key.
1211     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1212     // HACK! Assume connection uses mach ports.
1213     int port = [(NSMachPort*)[conn sendPort] machPort];
1214     NSNumber *key = [NSNumber numberWithInt:port];
1216     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1217     if (!replies) {
1218         replies = [NSMutableArray array];
1219         [serverReplyDict setObject:replies forKey:key];
1220     }
1222     [replies addObject:reply];
1225 - (void)addInput:(in bycopy NSString *)input
1226           client:(in byref id <MMVimClientProtocol>)client
1228     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1230     // NOTE: We don't call addInput: here because it differs from
1231     // server_to_input_buf() in that it always sets the 'silent' flag and we
1232     // don't want the MacVim client/server code to behave differently from
1233     // other platforms.
1234     char_u *s = [input vimStringSave];
1235     server_to_input_buf(s);
1236     vim_free(s);
1238     [self addClient:(id)client];
1241 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1242                  client:(in byref id <MMVimClientProtocol>)client
1244     [self addClient:(id)client];
1245     return [self evaluateExpression:expr];
1248 - (void)registerServerWithName:(NSString *)name
1250     NSString *svrName = name;
1251     NSConnection *svrConn = [NSConnection defaultConnection];
1252     unsigned i;
1254     for (i = 0; i < MMServerMax; ++i) {
1255         NSString *connName = [self connectionNameFromServerName:svrName];
1257         if ([svrConn registerName:connName]) {
1258             //NSLog(@"Registered server with name: %@", svrName);
1260             // TODO: Set request/reply time-outs to something else?
1261             //
1262             // Don't wait for requests (time-out means that the message is
1263             // dropped).
1264             [svrConn setRequestTimeout:0];
1265             //[svrConn setReplyTimeout:MMReplyTimeout];
1266             [svrConn setRootObject:self];
1268             // NOTE: 'serverName' is a global variable
1269             serverName = [svrName vimStringSave];
1270 #ifdef FEAT_EVAL
1271             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1272 #endif
1273 #ifdef FEAT_TITLE
1274             need_maketitle = TRUE;
1275 #endif
1276             [self queueMessage:SetServerNameMsgID data:
1277                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1278             break;
1279         }
1281         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1282     }
1285 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1286                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1287               silent:(BOOL)silent
1289     // NOTE: If 'name' equals 'serverName' then the request is local (client
1290     // and server are the same).  This case is not handled separately, so a
1291     // connection will be set up anyway (this simplifies the code).
1293     NSConnection *conn = [self connectionForServerName:name];
1294     if (!conn) {
1295         if (!silent) {
1296             char_u *s = (char_u*)[name UTF8String];
1297 #ifdef FEAT_MBYTE
1298             s = CONVERT_FROM_UTF8(s);
1299 #endif
1300             EMSG2(_(e_noserver), s);
1301 #ifdef FEAT_MBYTE
1302             CONVERT_FROM_UTF8_FREE(s);
1303 #endif
1304         }
1305         return NO;
1306     }
1308     if (port) {
1309         // HACK! Assume connection uses mach ports.
1310         *port = [(NSMachPort*)[conn sendPort] machPort];
1311     }
1313     id proxy = [conn rootProxy];
1314     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1316     @try {
1317         if (expr) {
1318             NSString *eval = [proxy evaluateExpression:string client:self];
1319             if (reply) {
1320                 if (eval) {
1321                     *reply = [eval vimStringSave];
1322                 } else {
1323                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1324                 }
1325             }
1327             if (!eval)
1328                 return NO;
1329         } else {
1330             [proxy addInput:string client:self];
1331         }
1332     }
1333     @catch (NSException *e) {
1334         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1335         return NO;
1336     }
1338     return YES;
1341 - (NSArray *)serverList
1343     NSArray *list = nil;
1345     if ([self connection]) {
1346         id proxy = [connection rootProxy];
1347         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1349         @try {
1350             list = [proxy serverList];
1351         }
1352         @catch (NSException *e) {
1353             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1354         }
1355     } else {
1356         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1357     }
1359     return list;
1362 - (NSString *)peekForReplyOnPort:(int)port
1364     //NSLog(@"%s%d", _cmd, port);
1366     NSNumber *key = [NSNumber numberWithInt:port];
1367     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1368     if (replies && [replies count]) {
1369         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1370         //        [replies objectAtIndex:0]);
1371         return [replies objectAtIndex:0];
1372     }
1374     //NSLog(@"    No replies");
1375     return nil;
1378 - (NSString *)waitForReplyOnPort:(int)port
1380     //NSLog(@"%s%d", _cmd, port);
1381     
1382     NSConnection *conn = [self connectionForServerPort:port];
1383     if (!conn)
1384         return nil;
1386     NSNumber *key = [NSNumber numberWithInt:port];
1387     NSMutableArray *replies = nil;
1388     NSString *reply = nil;
1390     // Wait for reply as long as the connection to the server is valid (unless
1391     // user interrupts wait with Ctrl-C).
1392     while (!got_int && [conn isValid] &&
1393             !(replies = [serverReplyDict objectForKey:key])) {
1394         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1395                                  beforeDate:[NSDate distantFuture]];
1396     }
1398     if (replies) {
1399         if ([replies count] > 0) {
1400             reply = [[replies objectAtIndex:0] retain];
1401             //NSLog(@"    Got reply: %@", reply);
1402             [replies removeObjectAtIndex:0];
1403             [reply autorelease];
1404         }
1406         if ([replies count] == 0)
1407             [serverReplyDict removeObjectForKey:key];
1408     }
1410     return reply;
1413 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1415     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1416     if (client) {
1417         @try {
1418             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1419             [client addReply:reply server:self];
1420             return YES;
1421         }
1422         @catch (NSException *e) {
1423             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1424         }
1425     } else {
1426         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1427     }
1429     return NO;
1432 - (BOOL)waitForAck
1434     return waitForAck;
1437 - (void)setWaitForAck:(BOOL)yn
1439     waitForAck = yn;
1442 - (void)waitForConnectionAcknowledgement
1444     if (!waitForAck) return;
1446     while (waitForAck && !got_int && [connection isValid]) {
1447         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1448                                  beforeDate:[NSDate distantFuture]];
1449         //NSLog(@"  waitForAck=%d got_int=%d isValid=%d",
1450         //        waitForAck, got_int, [connection isValid]);
1451     }
1453     if (waitForAck) {
1454         // Never received a connection acknowledgement, so die.
1455         [[NSNotificationCenter defaultCenter] removeObserver:self];
1456         [frontendProxy release];  frontendProxy = nil;
1458         // NOTE: We intentionally do not call mch_exit() since this in turn
1459         // will lead to -[MMBackend exit] getting called which we want to
1460         // avoid.
1461         exit(0);
1462     }
1464     [self processInputQueue];
1467 - (oneway void)acknowledgeConnection
1469     //NSLog(@"%s", _cmd);
1470     waitForAck = NO;
1473 @end // MMBackend
1477 @implementation MMBackend (Private)
1479 - (void)clearDrawData
1481     [drawData setLength:0];
1482     numWholeLineChanges = offsetForDrawDataPrune = 0;
1485 - (void)didChangeWholeLine
1487     // It may happen that draw queue is filled up with lots of changes that
1488     // affect a whole row.  If the number of such changes equals twice the
1489     // number of visible rows then we can prune some commands off the queue.
1490     //
1491     // NOTE: If we don't perform this pruning the draw queue may grow
1492     // indefinitely if Vim were to repeatedly send draw commands without ever
1493     // waiting for new input (that's when the draw queue is flushed).  The one
1494     // instance I know where this can happen is when a command is executed in
1495     // the shell (think ":grep" with thousands of matches).
1497     ++numWholeLineChanges;
1498     if (numWholeLineChanges == gui.num_rows) {
1499         // Remember the offset to prune up to.
1500         offsetForDrawDataPrune = [drawData length];
1501     } else if (numWholeLineChanges == 2*gui.num_rows) {
1502         // Delete all the unnecessary draw commands.
1503         NSMutableData *d = [[NSMutableData alloc]
1504                     initWithBytes:[drawData bytes] + offsetForDrawDataPrune
1505                            length:[drawData length] - offsetForDrawDataPrune];
1506         offsetForDrawDataPrune = [d length];
1507         numWholeLineChanges -= gui.num_rows;
1508         [drawData release];
1509         drawData = d;
1510     }
1513 - (void)waitForDialogReturn
1515     // Keep processing the run loop until a dialog returns.  To avoid getting
1516     // stuck in an endless loop (could happen if the setDialogReturn: message
1517     // was lost) we also do some paranoia checks.
1518     //
1519     // Note that in Cocoa the user can still resize windows and select menu
1520     // items while a sheet is being displayed, so we can't just wait for the
1521     // first message to arrive and assume that is the setDialogReturn: call.
1523     while (nil == dialogReturn && !got_int && [connection isValid])
1524         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1525                                  beforeDate:[NSDate distantFuture]];
1527     // Search for any resize messages on the input queue.  All other messages
1528     // on the input queue are dropped.  The reason why we single out resize
1529     // messages is because the user may have resized the window while a sheet
1530     // was open.
1531     int i, count = [inputQueue count];
1532     if (count > 0) {
1533         id textDimData = nil;
1534         if (count%2 == 0) {
1535             for (i = count-2; i >= 0; i -= 2) {
1536                 int msgid = [[inputQueue objectAtIndex:i] intValue];
1537                 if (SetTextDimensionsMsgID == msgid) {
1538                     textDimData = [[inputQueue objectAtIndex:i+1] retain];
1539                     break;
1540                 }
1541             }
1542         }
1544         [inputQueue removeAllObjects];
1546         if (textDimData) {
1547             [inputQueue addObject:
1548                     [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1549             [inputQueue addObject:textDimData];
1550             [textDimData release];
1551         }
1552     }
1555 - (void)insertVimStateMessage
1557     // NOTE: This is the place to add Vim state that needs to be accessed from
1558     // MacVim.  Do not add state that could potentially require lots of memory
1559     // since this message gets sent each time the output queue is forcibly
1560     // flushed (e.g. storing the currently selected text would be a bad idea).
1561     // We take this approach of "pushing" the state to MacVim to avoid having
1562     // to make synchronous calls from MacVim to Vim in order to get state.
1564     BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1565     int numTabs = tabpage_index(NULL) - 1;
1566     if (numTabs < 0)
1567         numTabs = 0;
1569     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1570         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1571         [NSNumber numberWithInt:p_mh], @"p_mh",
1572         [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1573         [NSNumber numberWithBool:mmta], @"p_mmta",
1574         [NSNumber numberWithInt:numTabs], @"numTabs",
1575         nil];
1577     // Put the state before all other messages.
1578     int msgid = SetVimStateMsgID;
1579     [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1580     [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1581                       atIndex:0];
1584 - (void)processInputQueue
1586     if ([inputQueue count] == 0) return;
1588     // NOTE: One of the input events may cause this method to be called
1589     // recursively, so copy the input queue to a local variable and clear the
1590     // queue before starting to process input events (otherwise we could get
1591     // stuck in an endless loop).
1592     NSArray *q = [inputQueue copy];
1593     unsigned i, count = [q count];
1595     [inputQueue removeAllObjects];
1597     for (i = 1; i < count; i+=2) {
1598         int msgid = [[q objectAtIndex:i-1] intValue];
1599         id data = [q objectAtIndex:i];
1600         if ([data isEqual:[NSNull null]])
1601             data = nil;
1603         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1604         [self handleInputEvent:msgid data:data];
1605     }
1607     [q release];
1608     //NSLog(@"Clear input event queue");
1611 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1613     if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1614             CmdKeyMsgID == msgid) {
1615         if (!data) return;
1616         const void *bytes = [data bytes];
1617         int mods = *((int*)bytes);  bytes += sizeof(int);
1618         int len = *((int*)bytes);  bytes += sizeof(int);
1619         NSString *key = [[NSString alloc] initWithBytes:bytes
1620                                                  length:len
1621                                                encoding:NSUTF8StringEncoding];
1622         mods = eventModifierFlagsToVimModMask(mods);
1624         if (InsertTextMsgID == msgid)
1625             [self handleInsertText:key];
1626         else
1627             [self handleKeyDown:key modifiers:mods];
1629         [key release];
1630     } else if (ScrollWheelMsgID == msgid) {
1631         if (!data) return;
1632         const void *bytes = [data bytes];
1634         int row = *((int*)bytes);  bytes += sizeof(int);
1635         int col = *((int*)bytes);  bytes += sizeof(int);
1636         int flags = *((int*)bytes);  bytes += sizeof(int);
1637         float dy = *((float*)bytes);  bytes += sizeof(float);
1639         int button = MOUSE_5;
1640         if (dy > 0) button = MOUSE_4;
1642         flags = eventModifierFlagsToVimMouseModMask(flags);
1644         int numLines = (int)round(dy);
1645         if (numLines < 0) numLines = -numLines;
1646         if (numLines == 0) numLines = 1;
1648 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1649         gui.scroll_wheel_force = numLines;
1650 #endif
1652         gui_send_mouse_event(button, col, row, NO, flags);
1653     } else if (MouseDownMsgID == msgid) {
1654         if (!data) return;
1655         const void *bytes = [data bytes];
1657         int row = *((int*)bytes);  bytes += sizeof(int);
1658         int col = *((int*)bytes);  bytes += sizeof(int);
1659         int button = *((int*)bytes);  bytes += sizeof(int);
1660         int flags = *((int*)bytes);  bytes += sizeof(int);
1661         int count = *((int*)bytes);  bytes += sizeof(int);
1663         button = eventButtonNumberToVimMouseButton(button);
1664         if (button >= 0) {
1665             flags = eventModifierFlagsToVimMouseModMask(flags);
1666             gui_send_mouse_event(button, col, row, count>1, flags);
1667         }
1668     } else if (MouseUpMsgID == msgid) {
1669         if (!data) return;
1670         const void *bytes = [data bytes];
1672         int row = *((int*)bytes);  bytes += sizeof(int);
1673         int col = *((int*)bytes);  bytes += sizeof(int);
1674         int flags = *((int*)bytes);  bytes += sizeof(int);
1676         flags = eventModifierFlagsToVimMouseModMask(flags);
1678         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1679     } else if (MouseDraggedMsgID == msgid) {
1680         if (!data) return;
1681         const void *bytes = [data bytes];
1683         int row = *((int*)bytes);  bytes += sizeof(int);
1684         int col = *((int*)bytes);  bytes += sizeof(int);
1685         int flags = *((int*)bytes);  bytes += sizeof(int);
1687         flags = eventModifierFlagsToVimMouseModMask(flags);
1689         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1690     } else if (MouseMovedMsgID == msgid) {
1691         const void *bytes = [data bytes];
1692         int row = *((int*)bytes);  bytes += sizeof(int);
1693         int col = *((int*)bytes);  bytes += sizeof(int);
1695         gui_mouse_moved(col, row);
1696     } else if (AddInputMsgID == msgid) {
1697         NSString *string = [[NSString alloc] initWithData:data
1698                 encoding:NSUTF8StringEncoding];
1699         if (string) {
1700             [self addInput:string];
1701             [string release];
1702         }
1703     } else if (SelectTabMsgID == msgid) {
1704         if (!data) return;
1705         const void *bytes = [data bytes];
1706         int idx = *((int*)bytes) + 1;
1707         //NSLog(@"Selecting tab %d", idx);
1708         send_tabline_event(idx);
1709     } else if (CloseTabMsgID == msgid) {
1710         if (!data) return;
1711         const void *bytes = [data bytes];
1712         int idx = *((int*)bytes) + 1;
1713         //NSLog(@"Closing tab %d", idx);
1714         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1715     } else if (AddNewTabMsgID == msgid) {
1716         //NSLog(@"Adding new tab");
1717         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1718     } else if (DraggedTabMsgID == msgid) {
1719         if (!data) return;
1720         const void *bytes = [data bytes];
1721         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1722         // based.
1723         int idx = *((int*)bytes);
1725         tabpage_move(idx);
1726     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1727             || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1728         if (!data) return;
1729         const void *bytes = [data bytes];
1730         int rows = Rows;
1731         if (SetTextColumnsMsgID != msgid) {
1732             rows = *((int*)bytes);  bytes += sizeof(int);
1733         }
1734         int cols = Columns;
1735         if (SetTextRowsMsgID != msgid) {
1736             cols = *((int*)bytes);  bytes += sizeof(int);
1737         }
1739         NSData *d = data;
1740         if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1741             int dim[2] = { rows, cols };
1742             d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1743             msgid = SetTextDimensionsReplyMsgID;
1744         }
1746         if (SetTextDimensionsMsgID == msgid)
1747             msgid = SetTextDimensionsReplyMsgID;
1749         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1750         // gui_resize_shell(), so we have to manually set the rows and columns
1751         // here since MacVim doesn't change the rows and columns to avoid
1752         // inconsistent states between Vim and MacVim.  The message sent back
1753         // indicates that it is a reply to a message that originated in MacVim
1754         // since we need to be able to determine where a message originated.
1755         [self queueMessage:msgid data:d];
1757         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1758         gui_resize_shell(cols, rows);
1759     } else if (ExecuteMenuMsgID == msgid) {
1760         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1761         if (attrs) {
1762             NSArray *desc = [attrs objectForKey:@"descriptor"];
1763             vimmenu_T *menu = menu_for_descriptor(desc);
1764             if (menu)
1765                 gui_menu_cb(menu);
1766         }
1767     } else if (ToggleToolbarMsgID == msgid) {
1768         [self handleToggleToolbar];
1769     } else if (ScrollbarEventMsgID == msgid) {
1770         [self handleScrollbarEvent:data];
1771     } else if (SetFontMsgID == msgid) {
1772         [self handleSetFont:data];
1773     } else if (VimShouldCloseMsgID == msgid) {
1774         gui_shell_closed();
1775     } else if (DropFilesMsgID == msgid) {
1776         [self handleDropFiles:data];
1777     } else if (DropStringMsgID == msgid) {
1778         [self handleDropString:data];
1779     } else if (GotFocusMsgID == msgid) {
1780         if (!gui.in_focus)
1781             [self focusChange:YES];
1782     } else if (LostFocusMsgID == msgid) {
1783         if (gui.in_focus)
1784             [self focusChange:NO];
1785     } else if (SetMouseShapeMsgID == msgid) {
1786         const void *bytes = [data bytes];
1787         int shape = *((int*)bytes);  bytes += sizeof(int);
1788         update_mouseshape(shape);
1789     } else if (XcodeModMsgID == msgid) {
1790         [self handleXcodeMod:data];
1791     } else if (OpenWithArgumentsMsgID == msgid) {
1792         [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1793     } else if (FindReplaceMsgID == msgid) {
1794         [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1795     } else {
1796         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1797     }
1800 + (NSDictionary *)specialKeys
1802     static NSDictionary *specialKeys = nil;
1804     if (!specialKeys) {
1805         NSBundle *mainBundle = [NSBundle mainBundle];
1806         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1807                                               ofType:@"plist"];
1808         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1809     }
1811     return specialKeys;
1814 - (void)handleInsertText:(NSString *)text
1816     if (!text) return;
1818     char_u *str = (char_u*)[text UTF8String];
1819     int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1821 #ifdef FEAT_MBYTE
1822     char_u *conv_str = NULL;
1823     if (input_conv.vc_type != CONV_NONE) {
1824         conv_str = string_convert(&input_conv, str, &len);
1825         if (conv_str)
1826             str = conv_str;
1827     }
1828 #endif
1830     for (i = 0; i < len; ++i) {
1831         add_to_input_buf(str+i, 1);
1832         if (CSI == str[i]) {
1833             // NOTE: If the converted string contains the byte CSI, then it
1834             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1835             // won't work.
1836             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1837             add_to_input_buf(extra, 2);
1838         }
1839     }
1841 #ifdef FEAT_MBYTE
1842     if (conv_str)
1843         vim_free(conv_str);
1844 #endif
1847 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1849     // TODO: This code is a horrible mess -- clean up!
1850     char_u special[3];
1851     char_u modChars[3];
1852     char_u *chars = (char_u*)[key UTF8String];
1853 #ifdef FEAT_MBYTE
1854     char_u *conv_str = NULL;
1855 #endif
1856     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1858     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1859     // that new keys can easily be added.
1860     NSString *specialString = [[MMBackend specialKeys]
1861             objectForKey:key];
1862     if (specialString && [specialString length] > 1) {
1863         //NSLog(@"special key: %@", specialString);
1864         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1865                 [specialString characterAtIndex:1]);
1867         ikey = simplify_key(ikey, &mods);
1868         if (ikey == CSI)
1869             ikey = K_CSI;
1871         special[0] = CSI;
1872         special[1] = K_SECOND(ikey);
1873         special[2] = K_THIRD(ikey);
1875         chars = special;
1876         length = 3;
1877     } else if (1 == length && TAB == chars[0]) {
1878         // Tab is a trouble child:
1879         // - <Tab> is added to the input buffer as is
1880         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1881         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1882         //   to be converted to utf-8
1883         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1884         // - <C-Tab> is reserved by Mac OS X
1885         // - <D-Tab> is reserved by Mac OS X
1886         chars = special;
1887         special[0] = TAB;
1888         length = 1;
1890         if (mods & MOD_MASK_SHIFT) {
1891             mods &= ~MOD_MASK_SHIFT;
1892             special[0] = CSI;
1893             special[1] = K_SECOND(K_S_TAB);
1894             special[2] = K_THIRD(K_S_TAB);
1895             length = 3;
1896         } else if (mods & MOD_MASK_ALT) {
1897             int mtab = 0x80 | TAB;
1898 #ifdef FEAT_MBYTE
1899             if (enc_utf8) {
1900                 // Convert to utf-8
1901                 special[0] = (mtab >> 6) + 0xc0;
1902                 special[1] = mtab & 0xbf;
1903                 length = 2;
1904             } else
1905 #endif
1906             {
1907                 special[0] = mtab;
1908                 length = 1;
1909             }
1910             mods &= ~MOD_MASK_ALT;
1911         }
1912     } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1913         // META key is treated separately.  This code was taken from gui_w48.c
1914         // and gui_gtk_x11.c.
1915         char_u string[7];
1916         int ch = simplify_key(chars[0], &mods);
1918         // Remove the SHIFT modifier for keys where it's already included,
1919         // e.g., '(' and '*'
1920         if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1921             mods &= ~MOD_MASK_SHIFT;
1923         // Interpret the ALT key as making the key META, include SHIFT, etc.
1924         ch = extract_modifiers(ch, &mods);
1925         if (ch == CSI)
1926             ch = K_CSI;
1928         int len = 0;
1929         if (mods) {
1930             string[len++] = CSI;
1931             string[len++] = KS_MODIFIER;
1932             string[len++] = mods;
1933         }
1935         if (IS_SPECIAL(ch)) {
1936             string[len++] = CSI;
1937             string[len++] = K_SECOND(ch);
1938             string[len++] = K_THIRD(ch);
1939         } else {
1940             string[len++] = ch;
1941 #ifdef FEAT_MBYTE
1942             // TODO: What if 'enc' is not "utf-8"?
1943             if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1944                 string[len++] = ch & 0xbf;
1945                 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1946                 if (string[len-1] == CSI) {
1947                     string[len++] = KS_EXTRA;
1948                     string[len++] = (int)KE_CSI;
1949                 }
1950             }
1951 #endif
1952         }
1954         add_to_input_buf(string, len);
1955         return;
1956     } else if (length > 0) {
1957         unichar c = [key characterAtIndex:0];
1958         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1959         //        [key characterAtIndex:0], mods);
1961         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1962         // cleared since they are already added to the key by the AppKit.
1963         // Unfortunately, the only way to deal with when to clear the modifiers
1964         // or not seems to be to have hard-wired rules like this.
1965         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1966                     || 0x9 == c || 0xd == c || ESC == c) ) {
1967             mods &= ~MOD_MASK_SHIFT;
1968             mods &= ~MOD_MASK_CTRL;
1969             //NSLog(@"clear shift ctrl");
1970         }
1972 #ifdef FEAT_MBYTE
1973         if (input_conv.vc_type != CONV_NONE) {
1974             conv_str = string_convert(&input_conv, chars, &length);
1975             if (conv_str)
1976                 chars = conv_str;
1977         }
1978 #endif
1979     }
1981     if (chars && length > 0) {
1982         if (mods) {
1983             //NSLog(@"adding mods: %d", mods);
1984             modChars[0] = CSI;
1985             modChars[1] = KS_MODIFIER;
1986             modChars[2] = mods;
1987             add_to_input_buf(modChars, 3);
1988         }
1990         //NSLog(@"add to input buf: 0x%x", chars[0]);
1991         // TODO: Check for CSI bytes?
1992         add_to_input_buf(chars, length);
1993     }
1995 #ifdef FEAT_MBYTE
1996     if (conv_str)
1997         vim_free(conv_str);
1998 #endif
2001 - (void)queueMessage:(int)msgid data:(NSData *)data
2003     //if (msgid != EnableMenuItemMsgID)
2004     //    NSLog(@"queueMessage:%s", MessageStrings[msgid]);
2006     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
2007     if (data)
2008         [outputQueue addObject:data];
2009     else
2010         [outputQueue addObject:[NSData data]];
2013 - (void)connectionDidDie:(NSNotification *)notification
2015     // If the main connection to MacVim is lost this means that either MacVim
2016     // has crashed or this process did not receive its termination message
2017     // properly (e.g. if the TerminateNowMsgID was dropped).
2018     //
2019     // NOTE: This is not called if a Vim controller invalidates its connection.
2021     NSLog(@"WARNING[%s]: Main connection was lost before process had a chance "
2022             "to terminate; preserving swap files.", _cmd);
2023     getout_preserve_modified(1);
2026 - (void)blinkTimerFired:(NSTimer *)timer
2028     NSTimeInterval timeInterval = 0;
2030     [blinkTimer release];
2031     blinkTimer = nil;
2033     if (MMBlinkStateOn == blinkState) {
2034         gui_undraw_cursor();
2035         blinkState = MMBlinkStateOff;
2036         timeInterval = blinkOffInterval;
2037     } else if (MMBlinkStateOff == blinkState) {
2038         gui_update_cursor(TRUE, FALSE);
2039         blinkState = MMBlinkStateOn;
2040         timeInterval = blinkOnInterval;
2041     }
2043     if (timeInterval > 0) {
2044         blinkTimer = 
2045             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2046                                             selector:@selector(blinkTimerFired:)
2047                                             userInfo:nil repeats:NO] retain];
2048         [self flushQueue:YES];
2049     }
2052 - (void)focusChange:(BOOL)on
2054     gui_focus_change(on);
2057 - (void)handleToggleToolbar
2059     // If 'go' contains 'T', then remove it, else add it.
2061     char_u go[sizeof(GO_ALL)+2];
2062     char_u *p;
2063     int len;
2065     STRCPY(go, p_go);
2066     p = vim_strchr(go, GO_TOOLBAR);
2067     len = STRLEN(go);
2069     if (p != NULL) {
2070         char_u *end = go + len;
2071         while (p < end) {
2072             p[0] = p[1];
2073             ++p;
2074         }
2075     } else {
2076         go[len] = GO_TOOLBAR;
2077         go[len+1] = NUL;
2078     }
2080     set_option_value((char_u*)"guioptions", 0, go, 0);
2082     [self redrawScreen];
2085 - (void)handleScrollbarEvent:(NSData *)data
2087     if (!data) return;
2089     const void *bytes = [data bytes];
2090     long ident = *((long*)bytes);  bytes += sizeof(long);
2091     int hitPart = *((int*)bytes);  bytes += sizeof(int);
2092     float fval = *((float*)bytes);  bytes += sizeof(float);
2093     scrollbar_T *sb = gui_find_scrollbar(ident);
2095     if (sb) {
2096         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2097         long value = sb_info->value;
2098         long size = sb_info->size;
2099         long max = sb_info->max;
2100         BOOL isStillDragging = NO;
2101         BOOL updateKnob = YES;
2103         switch (hitPart) {
2104         case NSScrollerDecrementPage:
2105             value -= (size > 2 ? size - 2 : 1);
2106             break;
2107         case NSScrollerIncrementPage:
2108             value += (size > 2 ? size - 2 : 1);
2109             break;
2110         case NSScrollerDecrementLine:
2111             --value;
2112             break;
2113         case NSScrollerIncrementLine:
2114             ++value;
2115             break;
2116         case NSScrollerKnob:
2117             isStillDragging = YES;
2118             // fall through ...
2119         case NSScrollerKnobSlot:
2120             value = (long)(fval * (max - size + 1));
2121             // fall through ...
2122         default:
2123             updateKnob = NO;
2124             break;
2125         }
2127         //NSLog(@"value %d -> %d", sb_info->value, value);
2128         gui_drag_scrollbar(sb, value, isStillDragging);
2130         if (updateKnob) {
2131             // Dragging the knob or option+clicking automatically updates
2132             // the knob position (on the actual NSScroller), so we only
2133             // need to set the knob position in the other cases.
2134             if (sb->wp) {
2135                 // Update both the left&right vertical scrollbars.
2136                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2137                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2138                 [self setScrollbarThumbValue:value size:size max:max
2139                                   identifier:identLeft];
2140                 [self setScrollbarThumbValue:value size:size max:max
2141                                   identifier:identRight];
2142             } else {
2143                 // Update the horizontal scrollbar.
2144                 [self setScrollbarThumbValue:value size:size max:max
2145                                   identifier:ident];
2146             }
2147         }
2148     }
2151 - (void)handleSetFont:(NSData *)data
2153     if (!data) return;
2155     const void *bytes = [data bytes];
2156     float pointSize = *((float*)bytes);  bytes += sizeof(float);
2157     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2158     bytes += sizeof(unsigned);  // len not used
2160     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2161     [name appendString:[NSString stringWithFormat:@":h%d", (int)pointSize]];
2162     char_u *s = (char_u*)[name UTF8String];
2164 #ifdef FEAT_MBYTE
2165     s = CONVERT_FROM_UTF8(s);
2166 #endif
2168     set_option_value((char_u*)"guifont", 0, s, 0);
2170 #ifdef FEAT_MBYTE
2171     CONVERT_FROM_UTF8_FREE(s);
2172 #endif
2174     [self redrawScreen];
2177 - (void)handleDropFiles:(NSData *)data
2179     // TODO: Get rid of this method; instead use Vim script directly.  At the
2180     // moment I know how to do this to open files in tabs, but I'm not sure how
2181     // to add the filenames to the command line when in command line mode.
2183     if (!data) return;
2185     NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2186     if (!args) return;
2188     id obj = [args objectForKey:@"forceOpen"];
2189     BOOL forceOpen = YES;
2190     if (obj)
2191         forceOpen = [obj boolValue];
2193     NSArray *filenames = [args objectForKey:@"filenames"];
2194     if (!(filenames && [filenames count] > 0)) return;
2196 #ifdef FEAT_DND
2197     if (!forceOpen && (State & CMDLINE)) {
2198         // HACK!  If Vim is in command line mode then the files names
2199         // should be added to the command line, instead of opening the
2200         // files in tabs (unless forceOpen is set).  This is taken care of by
2201         // gui_handle_drop().
2202         int n = [filenames count];
2203         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2204         if (fnames) {
2205             int i = 0;
2206             for (i = 0; i < n; ++i)
2207                 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2209             // NOTE!  This function will free 'fnames'.
2210             // HACK!  It is assumed that the 'x' and 'y' arguments are
2211             // unused when in command line mode.
2212             gui_handle_drop(0, 0, 0, fnames, n);
2213         }
2214     } else
2215 #endif // FEAT_DND
2216     {
2217         [self handleOpenWithArguments:args];
2218     }
2221 - (void)handleDropString:(NSData *)data
2223     if (!data) return;
2225 #ifdef FEAT_DND
2226     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2227     const void *bytes = [data bytes];
2228     int len = *((int*)bytes);  bytes += sizeof(int);
2229     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2231     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2232     NSRange range = { 0, [string length] };
2233     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2234                                          withString:@"\x0a" options:0
2235                                               range:range];
2236     if (0 == n) {
2237         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2238                                        options:0 range:range];
2239     }
2241     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2242     char_u *s = (char_u*)[string UTF8String];
2243 #ifdef FEAT_MBYTE
2244     if (input_conv.vc_type != CONV_NONE)
2245         s = string_convert(&input_conv, s, &len);
2246 #endif
2247     dnd_yank_drag_data(s, len);
2248 #ifdef FEAT_MBYTE
2249     if (input_conv.vc_type != CONV_NONE)
2250         vim_free(s);
2251 #endif
2252     add_to_input_buf(dropkey, sizeof(dropkey));
2253 #endif // FEAT_DND
2256 - (void)startOdbEditWithArguments:(NSDictionary *)args
2258 #ifdef FEAT_ODB_EDITOR
2259     id obj = [args objectForKey:@"remoteID"];
2260     if (!obj) return;
2262     OSType serverID = [obj unsignedIntValue];
2263     NSString *remotePath = [args objectForKey:@"remotePath"];
2265     NSAppleEventDescriptor *token = nil;
2266     NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2267     obj = [args objectForKey:@"remoteTokenDescType"];
2268     if (tokenData && obj) {
2269         DescType tokenType = [obj unsignedLongValue];
2270         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2271                                                                 data:tokenData];
2272     }
2274     NSArray *filenames = [args objectForKey:@"filenames"];
2275     unsigned i, numFiles = [filenames count];
2276     for (i = 0; i < numFiles; ++i) {
2277         NSString *filename = [filenames objectAtIndex:i];
2278         char_u *s = [filename vimStringSave];
2279         buf_T *buf = buflist_findname(s);
2280         vim_free(s);
2282         if (buf) {
2283             if (buf->b_odb_token) {
2284                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2285                 buf->b_odb_token = NULL;
2286             }
2288             if (buf->b_odb_fname) {
2289                 vim_free(buf->b_odb_fname);
2290                 buf->b_odb_fname = NULL;
2291             }
2293             buf->b_odb_server_id = serverID;
2295             if (token)
2296                 buf->b_odb_token = [token retain];
2297             if (remotePath)
2298                 buf->b_odb_fname = [remotePath vimStringSave];
2299         } else {
2300             NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2301                     filename);
2302         }
2303     }
2304 #endif // FEAT_ODB_EDITOR
2307 - (void)handleXcodeMod:(NSData *)data
2309 #if 0
2310     const void *bytes = [data bytes];
2311     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2312     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2313     if (0 == len)
2314         return;
2316     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2317             descriptorWithDescriptorType:type
2318                                    bytes:bytes
2319                                   length:len];
2320 #endif
2323 - (void)handleOpenWithArguments:(NSDictionary *)args
2325     // ARGUMENT:                DESCRIPTION:
2326     // -------------------------------------------------------------
2327     // filenames                list of filenames
2328     // dontOpen                 don't open files specified in above argument
2329     // layout                   which layout to use to open files
2330     // selectionRange           range of lines to select
2331     // searchText               string to search for
2332     // cursorLine               line to position the cursor on
2333     // cursorColumn             column to position the cursor on
2334     //                          (only valid when "cursorLine" is set)
2335     // remoteID                 ODB parameter
2336     // remotePath               ODB parameter
2337     // remoteTokenDescType      ODB parameter
2338     // remoteTokenData          ODB parameter
2340     //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2342     NSArray *filenames = [args objectForKey:@"filenames"];
2343     int i, numFiles = filenames ? [filenames count] : 0;
2344     BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2345     int layout = [[args objectForKey:@"layout"] intValue];
2347     // Change to directory of first file to open if this is an "unused" editor
2348     // (but do not do this if editing remotely).
2349     if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2350             && (starting || [self unusedEditor]) ) {
2351         char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2352         vim_chdirfile(s);
2353         vim_free(s);
2354     }
2356     if (starting > 0) {
2357         // When Vim is starting we simply add the files to be opened to the
2358         // global arglist and Vim will take care of opening them for us.
2359         if (openFiles && numFiles > 0) {
2360             for (i = 0; i < numFiles; i++) {
2361                 NSString *fname = [filenames objectAtIndex:i];
2362                 char_u *p = NULL;
2364                 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2365                         || (p = [fname vimStringSave]) == NULL)
2366                     exit(2); // See comment in -[MMBackend exit]
2367                 else
2368                     alist_add(&global_alist, p, 2);
2369             }
2371             // Vim will take care of arranging the files added to the arglist
2372             // in windows or tabs; all we must do is to specify which layout to
2373             // use.
2374             initialWindowLayout = layout;
2375         }
2376     } else {
2377         // When Vim is already open we resort to some trickery to open the
2378         // files with the specified layout.
2379         //
2380         // TODO: Figure out a better way to handle this?
2381         if (openFiles && numFiles > 0) {
2382             BOOL oneWindowInTab = topframe ? YES
2383                                            : (topframe->fr_layout == FR_LEAF);
2384             BOOL bufChanged = NO;
2385             BOOL bufHasFilename = NO;
2386             if (curbuf) {
2387                 bufChanged = curbufIsChanged();
2388                 bufHasFilename = curbuf->b_ffname != NULL;
2389             }
2391             // Temporarily disable flushing since the following code may
2392             // potentially cause multiple redraws.
2393             flushDisabled = YES;
2395             BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2396             if (WIN_TABS == layout && !onlyOneTab) {
2397                 // By going to the last tabpage we ensure that the new tabs
2398                 // will appear last (if this call is left out, the taborder
2399                 // becomes messy).
2400                 goto_tabpage(9999);
2401             }
2403             // Make sure we're in normal mode first.
2404             [self addInput:@"<C-\\><C-N>"];
2406             if (numFiles > 1) {
2407                 // With "split layout" we open a new tab before opening
2408                 // multiple files if the current tab has more than one window
2409                 // or if there is exactly one window but whose buffer has a
2410                 // filename.  (The :drop command ensures modified buffers get
2411                 // their own window.)
2412                 if ((WIN_HOR == layout || WIN_VER == layout) &&
2413                         (!oneWindowInTab || bufHasFilename))
2414                     [self addInput:@":tabnew<CR>"];
2416                 // The files are opened by constructing a ":drop ..." command
2417                 // and executing it.
2418                 NSMutableString *cmd = (WIN_TABS == layout)
2419                         ? [NSMutableString stringWithString:@":tab drop"]
2420                         : [NSMutableString stringWithString:@":drop"];
2422                 for (i = 0; i < numFiles; ++i) {
2423                     NSString *file = [filenames objectAtIndex:i];
2424                     file = [file stringByEscapingSpecialFilenameCharacters];
2425                     [cmd appendString:@" "];
2426                     [cmd appendString:file];
2427                 }
2429                 // Temporarily clear 'suffixes' so that the files are opened in
2430                 // the same order as they appear in the "filenames" array.
2431                 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2433                 [self addInput:cmd];
2435                 // Split the view into multiple windows if requested.
2436                 if (WIN_HOR == layout)
2437                     [self addInput:@"|sall"];
2438                 else if (WIN_VER == layout)
2439                     [self addInput:@"|vert sall"];
2441                 // Restore the old value of 'suffixes'.
2442                 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2443             } else {
2444                 // When opening one file we try to reuse the current window,
2445                 // but not if its buffer is modified or has a filename.
2446                 // However, the 'arglist' layout always opens the file in the
2447                 // current window.
2448                 NSString *file = [[filenames lastObject]
2449                         stringByEscapingSpecialFilenameCharacters];
2450                 NSString *cmd;
2451                 if (WIN_HOR == layout) {
2452                     if (!(bufHasFilename || bufChanged))
2453                         cmd = [NSString stringWithFormat:@":e %@", file];
2454                     else
2455                         cmd = [NSString stringWithFormat:@":sp %@", file];
2456                 } else if (WIN_VER == layout) {
2457                     if (!(bufHasFilename || bufChanged))
2458                         cmd = [NSString stringWithFormat:@":e %@", file];
2459                     else
2460                         cmd = [NSString stringWithFormat:@":vsp %@", file];
2461                 } else if (WIN_TABS == layout) {
2462                     if (oneWindowInTab && !(bufHasFilename || bufChanged))
2463                         cmd = [NSString stringWithFormat:@":e %@", file];
2464                     else
2465                         cmd = [NSString stringWithFormat:@":tabe %@", file];
2466                 } else {
2467                     // (The :drop command will split if there is a modified
2468                     // buffer.)
2469                     cmd = [NSString stringWithFormat:@":drop %@", file];
2470                 }
2472                 [self addInput:cmd];
2473                 [self addInput:@"<CR>"];
2474             }
2476             // Force screen redraw (does it have to be this complicated?).
2477             // (This code was taken from the end of gui_handle_drop().)
2478             update_screen(NOT_VALID);
2479             setcursor();
2480             out_flush();
2481             gui_update_cursor(FALSE, FALSE);
2482             maketitle();
2484             flushDisabled = NO;
2485         }
2486     }
2488     if ([args objectForKey:@"remoteID"]) {
2489         // NOTE: We have to delay processing any ODB related arguments since
2490         // the file(s) may not be opened until the input buffer is processed.
2491         [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2492                                withObject:args
2493                             waitUntilDone:NO];
2494     }
2496     NSString *lineString = [args objectForKey:@"cursorLine"];
2497     if (lineString && [lineString intValue] > 0) {
2498         NSString *columnString = [args objectForKey:@"cursorColumn"];
2499         if (!(columnString && [columnString intValue] > 0))
2500             columnString = @"1";
2502         NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2503                 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2504         [self addInput:cmd];
2505     }
2507     NSString *rangeString = [args objectForKey:@"selectionRange"];
2508     if (rangeString) {
2509         // Build a command line string that will select the given range of
2510         // lines.  If range.length == 0, then position the cursor on the given
2511         // line but do not select.
2512         NSRange range = NSRangeFromString(rangeString);
2513         NSString *cmd;
2514         if (range.length > 0) {
2515             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2516                     NSMaxRange(range), range.location];
2517         } else {
2518             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2519                     range.location];
2520         }
2522         [self addInput:cmd];
2523     }
2525     NSString *searchText = [args objectForKey:@"searchText"];
2526     if (searchText) {
2527         [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2528                 searchText]];
2529     }
2532 - (BOOL)checkForModifiedBuffers
2534     buf_T *buf;
2535     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2536         if (bufIsChanged(buf)) {
2537             return YES;
2538         }
2539     }
2541     return NO;
2544 - (void)addInput:(NSString *)input
2546     // NOTE: This code is essentially identical to server_to_input_buf(),
2547     // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2548     char_u *string = [input vimStringSave];
2549     if (!string) return;
2551     /* Set 'cpoptions' the way we want it.
2552      *    B set - backslashes are *not* treated specially
2553      *    k set - keycodes are *not* reverse-engineered
2554      *    < unset - <Key> sequences *are* interpreted
2555      *  The last but one parameter of replace_termcodes() is TRUE so that the
2556      *  <lt> sequence is recognised - needed for a real backslash.
2557      */
2558     char_u *ptr = NULL;
2559     char_u *cpo_save = p_cpo;
2560     p_cpo = (char_u *)"Bk";
2561     char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2562     p_cpo = cpo_save;
2564     if (*ptr != NUL)    /* trailing CTRL-V results in nothing */
2565     {
2566         /*
2567          * Add the string to the input stream.
2568          * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2569          *
2570          * First clear typed characters from the typeahead buffer, there could
2571          * be half a mapping there.  Then append to the existing string, so
2572          * that multiple commands from a client are concatenated.
2573          */
2574         if (typebuf.tb_maplen < typebuf.tb_len)
2575             del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2576         (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2578         /* Let input_available() know we inserted text in the typeahead
2579          * buffer. */
2580         typebuf_was_filled = TRUE;
2581     }
2582     vim_free(ptr);
2583     vim_free(string);
2586 - (BOOL)unusedEditor
2588     BOOL oneWindowInTab = topframe ? YES
2589                                    : (topframe->fr_layout == FR_LEAF);
2590     BOOL bufChanged = NO;
2591     BOOL bufHasFilename = NO;
2592     if (curbuf) {
2593         bufChanged = curbufIsChanged();
2594         bufHasFilename = curbuf->b_ffname != NULL;
2595     }
2597     BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2599     return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2602 - (void)redrawScreen
2604     // Force screen redraw (does it have to be this complicated?).
2605     redraw_all_later(CLEAR);
2606     update_screen(NOT_VALID);
2607     setcursor();
2608     out_flush();
2609     gui_update_cursor(FALSE, FALSE);
2611     // HACK! The cursor is not put back at the command line by the above
2612     // "redraw commands".  The following test seems to do the trick though.
2613     if (State & CMDLINE)
2614         redrawcmdline();
2617 - (void)handleFindReplace:(NSDictionary *)args
2619     if (!args) return;
2621     NSString *findString = [args objectForKey:@"find"];
2622     if (!findString) return;
2624     char_u *find = [findString vimStringSave];
2625     char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2626     int flags = [[args objectForKey:@"flags"] intValue];
2628     // NOTE: The flag 0x100 is used to indicate a backward search.
2629     gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2631     vim_free(find);
2632     vim_free(replace);
2635 @end // MMBackend (Private)
2640 @implementation MMBackend (ClientServer)
2642 - (NSString *)connectionNameFromServerName:(NSString *)name
2644     NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2646     return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2647         lowercaseString];
2650 - (NSConnection *)connectionForServerName:(NSString *)name
2652     // TODO: Try 'name%d' if 'name' fails.
2653     NSString *connName = [self connectionNameFromServerName:name];
2654     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2656     if (!svrConn) {
2657         svrConn = [NSConnection connectionWithRegisteredName:connName
2658                                                            host:nil];
2659         // Try alternate server...
2660         if (!svrConn && alternateServerName) {
2661             //NSLog(@"  trying to connect to alternate server: %@",
2662             //        alternateServerName);
2663             connName = [self connectionNameFromServerName:alternateServerName];
2664             svrConn = [NSConnection connectionWithRegisteredName:connName
2665                                                             host:nil];
2666         }
2668         // Try looking for alternate servers...
2669         if (!svrConn) {
2670             //NSLog(@"  looking for alternate servers...");
2671             NSString *alt = [self alternateServerNameForName:name];
2672             if (alt != alternateServerName) {
2673                 //NSLog(@"  found alternate server: %@", string);
2674                 [alternateServerName release];
2675                 alternateServerName = [alt copy];
2676             }
2677         }
2679         // Try alternate server again...
2680         if (!svrConn && alternateServerName) {
2681             //NSLog(@"  trying to connect to alternate server: %@",
2682             //        alternateServerName);
2683             connName = [self connectionNameFromServerName:alternateServerName];
2684             svrConn = [NSConnection connectionWithRegisteredName:connName
2685                                                             host:nil];
2686         }
2688         if (svrConn) {
2689             [connectionNameDict setObject:svrConn forKey:connName];
2691             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2692             [[NSNotificationCenter defaultCenter] addObserver:self
2693                     selector:@selector(serverConnectionDidDie:)
2694                         name:NSConnectionDidDieNotification object:svrConn];
2695         }
2696     }
2698     return svrConn;
2701 - (NSConnection *)connectionForServerPort:(int)port
2703     NSConnection *conn;
2704     NSEnumerator *e = [connectionNameDict objectEnumerator];
2706     while ((conn = [e nextObject])) {
2707         // HACK! Assume connection uses mach ports.
2708         if (port == [(NSMachPort*)[conn sendPort] machPort])
2709             return conn;
2710     }
2712     return nil;
2715 - (void)serverConnectionDidDie:(NSNotification *)notification
2717     //NSLog(@"%s%@", _cmd, notification);
2719     NSConnection *svrConn = [notification object];
2721     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2722     [[NSNotificationCenter defaultCenter]
2723             removeObserver:self
2724                       name:NSConnectionDidDieNotification
2725                     object:svrConn];
2727     [connectionNameDict removeObjectsForKeys:
2728         [connectionNameDict allKeysForObject:svrConn]];
2730     // HACK! Assume connection uses mach ports.
2731     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2732     NSNumber *key = [NSNumber numberWithInt:port];
2734     [clientProxyDict removeObjectForKey:key];
2735     [serverReplyDict removeObjectForKey:key];
2738 - (void)addClient:(NSDistantObject *)client
2740     NSConnection *conn = [client connectionForProxy];
2741     // HACK! Assume connection uses mach ports.
2742     int port = [(NSMachPort*)[conn sendPort] machPort];
2743     NSNumber *key = [NSNumber numberWithInt:port];
2745     if (![clientProxyDict objectForKey:key]) {
2746         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2747         [clientProxyDict setObject:client forKey:key];
2748     }
2750     // NOTE: 'clientWindow' is a global variable which is used by <client>
2751     clientWindow = port;
2754 - (NSString *)alternateServerNameForName:(NSString *)name
2756     if (!(name && [name length] > 0))
2757         return nil;
2759     // Only look for alternates if 'name' doesn't end in a digit.
2760     unichar lastChar = [name characterAtIndex:[name length]-1];
2761     if (lastChar >= '0' && lastChar <= '9')
2762         return nil;
2764     // Look for alternates among all current servers.
2765     NSArray *list = [self serverList];
2766     if (!(list && [list count] > 0))
2767         return nil;
2769     // Filter out servers starting with 'name' and ending with a number. The
2770     // (?i) pattern ensures that the match is case insensitive.
2771     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2772     NSPredicate *pred = [NSPredicate predicateWithFormat:
2773             @"SELF MATCHES %@", pat];
2774     list = [list filteredArrayUsingPredicate:pred];
2775     if ([list count] > 0) {
2776         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2777         return [list objectAtIndex:0];
2778     }
2780     return nil;
2783 @end // MMBackend (ClientServer)
2788 @implementation NSString (MMServerNameCompare)
2789 - (NSComparisonResult)serverNameCompare:(NSString *)string
2791     return [self compare:string
2792                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2794 @end
2799 static int eventModifierFlagsToVimModMask(int modifierFlags)
2801     int modMask = 0;
2803     if (modifierFlags & NSShiftKeyMask)
2804         modMask |= MOD_MASK_SHIFT;
2805     if (modifierFlags & NSControlKeyMask)
2806         modMask |= MOD_MASK_CTRL;
2807     if (modifierFlags & NSAlternateKeyMask)
2808         modMask |= MOD_MASK_ALT;
2809     if (modifierFlags & NSCommandKeyMask)
2810         modMask |= MOD_MASK_CMD;
2812     return modMask;
2815 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2817     int modMask = 0;
2819     if (modifierFlags & NSShiftKeyMask)
2820         modMask |= MOUSE_SHIFT;
2821     if (modifierFlags & NSControlKeyMask)
2822         modMask |= MOUSE_CTRL;
2823     if (modifierFlags & NSAlternateKeyMask)
2824         modMask |= MOUSE_ALT;
2826     return modMask;
2829 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2831     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2833     return (buttonNumber >= 0 && buttonNumber < 3)
2834             ? mouseButton[buttonNumber] : -1;
2839 // This function is modeled after the VimToPython function found in if_python.c
2840 // NB This does a deep copy by value, it does not lookup references like the
2841 // VimToPython function does.  This is because I didn't want to deal with the
2842 // retain cycles that this would create, and we can cover 99% of the use cases
2843 // by ignoring it.  If we ever switch to using GC in MacVim then this
2844 // functionality can be implemented easily.
2845 static id vimToCocoa(typval_T * tv, int depth)
2847     id result = nil;
2848     id newObj = nil;
2851     // Avoid infinite recursion
2852     if (depth > 100) {
2853         return nil;
2854     }
2856     if (tv->v_type == VAR_STRING) {
2857         char_u * val = tv->vval.v_string;
2858         // val can be NULL if the string is empty
2859         if (!val) {
2860             result = [NSString string];
2861         } else {
2862 #ifdef FEAT_MBYTE
2863             val = CONVERT_TO_UTF8(val);
2864 #endif
2865             result = [NSString stringWithUTF8String:(char*)val];
2866 #ifdef FEAT_MBYTE
2867             CONVERT_TO_UTF8_FREE(val);
2868 #endif
2869         }
2870     } else if (tv->v_type == VAR_NUMBER) {
2871         // looks like sizeof(varnumber_T) is always <= sizeof(long)
2872         result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2873     } else if (tv->v_type == VAR_LIST) {
2874         list_T * list = tv->vval.v_list;
2875         listitem_T * curr;
2877         NSMutableArray * arr = result = [NSMutableArray array];
2879         if (list != NULL) {
2880             for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2881                 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2882                 [arr addObject:newObj];
2883             }
2884         }
2885     } else if (tv->v_type == VAR_DICT) {
2886         NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2888         if (tv->vval.v_dict != NULL) {
2889             hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2890             int todo = ht->ht_used;
2891             hashitem_T * hi;
2892             dictitem_T * di;
2894             for (hi = ht->ht_array; todo > 0; ++hi) {
2895                 if (!HASHITEM_EMPTY(hi)) {
2896                     --todo;
2898                     di = dict_lookup(hi);
2899                     newObj = vimToCocoa(&di->di_tv, depth + 1);
2901                     char_u * keyval = hi->hi_key;
2902 #ifdef FEAT_MBYTE
2903                     keyval = CONVERT_TO_UTF8(keyval);
2904 #endif
2905                     NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2906 #ifdef FEAT_MBYTE
2907                     CONVERT_TO_UTF8_FREE(keyval);
2908 #endif
2909                     [dict setObject:newObj forKey:key];
2910                 }
2911             }
2912         }
2913     } else { // only func refs should fall into this category?
2914         result = nil;
2915     }
2917     return result;
2921 // This function is modeled after eval_client_expr_to_string found in main.c
2922 // Returns nil if there was an error evaluating the expression, and writes a
2923 // message to errorStr.
2924 // TODO Get the error that occurred while evaluating the expression in vim
2925 // somehow.
2926 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2929     char_u *s = (char_u*)[expr UTF8String];
2931 #ifdef FEAT_MBYTE
2932     s = CONVERT_FROM_UTF8(s);
2933 #endif
2935     int save_dbl = debug_break_level;
2936     int save_ro = redir_off;
2938     debug_break_level = -1;
2939     redir_off = 0;
2940     ++emsg_skip;
2942     typval_T * tvres = eval_expr(s, NULL);
2944     debug_break_level = save_dbl;
2945     redir_off = save_ro;
2946     --emsg_skip;
2948     setcursor();
2949     out_flush();
2951 #ifdef FEAT_MBYTE
2952     CONVERT_FROM_UTF8_FREE(s);
2953 #endif
2955 #ifdef FEAT_GUI
2956     if (gui.in_use)
2957         gui_update_cursor(FALSE, FALSE);
2958 #endif
2960     if (tvres == NULL) {
2961         free_tv(tvres);
2962         *errstr = @"Expression evaluation failed.";
2963     }
2965     id res = vimToCocoa(tvres, 1);
2967     free_tv(tvres);
2969     if (res == nil) {
2970         *errstr = @"Conversion to cocoa values failed.";
2971     }
2973     return res;
2978 @implementation NSString (VimStrings)
2980 + (id)stringWithVimString:(char_u *)s
2982     // This method ensures a non-nil string is returned.  If 's' cannot be
2983     // converted to a utf-8 string it is assumed to be latin-1.  If conversion
2984     // still fails an empty NSString is returned.
2985     NSString *string = nil;
2986     if (s) {
2987 #ifdef FEAT_MBYTE
2988         s = CONVERT_TO_UTF8(s);
2989 #endif
2990         string = [NSString stringWithUTF8String:(char*)s];
2991         if (!string) {
2992             // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2993             // latin-1?
2994             string = [NSString stringWithCString:(char*)s
2995                                         encoding:NSISOLatin1StringEncoding];
2996         }
2997 #ifdef FEAT_MBYTE
2998         CONVERT_TO_UTF8_FREE(s);
2999 #endif
3000     }
3002     return string != nil ? string : [NSString string];
3005 - (char_u *)vimStringSave
3007     char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3009 #ifdef FEAT_MBYTE
3010     s = CONVERT_FROM_UTF8(s);
3011 #endif
3012     ret = vim_strsave(s);
3013 #ifdef FEAT_MBYTE
3014     CONVERT_FROM_UTF8_FREE(s);
3015 #endif
3017     return ret;
3020 @end // NSString (VimStrings)