Vim talks only to app controller
[MacVim.git] / src / MacVim / MMBackend.m
blob8bafaf5ae0628f5a31b23fa02d2b2deb50a88c86
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     [connection release];  connection = nil;
182     [actionDict release];  actionDict = nil;
183     [sysColorDict release];  sysColorDict = nil;
184     [colorDict release];  colorDict = nil;
186     [super dealloc];
189 - (void)setBackgroundColor:(int)color
191     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
194 - (void)setForegroundColor:(int)color
196     foregroundColor = MM_COLOR(color);
199 - (void)setSpecialColor:(int)color
201     specialColor = MM_COLOR(color);
204 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
206     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
207     defaultForegroundColor = MM_COLOR(fg);
209     NSMutableData *data = [NSMutableData data];
211     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
212     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
214     [self queueMessage:SetDefaultColorsMsgID data:data];
217 - (NSConnection *)connection
219     if (!connection) {
220         // NOTE!  If the name of the connection changes here it must also be
221         // updated in MMAppController.m.
222         NSString *name = [NSString stringWithFormat:@"%@-connection",
223                [[NSBundle mainBundle] bundlePath]];
225         connection = [NSConnection connectionWithRegisteredName:name host:nil];
226         [connection retain];
227     }
229     // NOTE: 'connection' may be nil here.
230     return connection;
233 - (NSDictionary *)actionDict
235     return actionDict;
238 - (int)initialWindowLayout
240     return initialWindowLayout;
243 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
245     [self queueMessage:msgid data:[props dictionaryAsData]];
248 - (BOOL)checkin
250     if (![self connection]) {
251         if (waitForAck) {
252             // This is a preloaded process and as such should not cause the
253             // MacVim to be opened.  We probably got here as a result of the
254             // user quitting MacVim while the process was preloading, so exit
255             // this process too.
256             // (Don't use mch_exit() since it assumes the process has properly
257             // started.)
258             exit(0);
259         }
261         NSBundle *mainBundle = [NSBundle mainBundle];
262 #if 0
263         OSStatus status;
264         FSRef ref;
266         // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
267         // the API to pass Apple Event parameters is broken on 10.4).
268         NSString *path = [mainBundle bundlePath];
269         status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
270         if (noErr == status) {
271             // Pass parameter to the 'Open' Apple Event that tells MacVim not
272             // to open an untitled window.
273             NSAppleEventDescriptor *desc =
274                     [NSAppleEventDescriptor recordDescriptor];
275             [desc setParamDescriptor:
276                     [NSAppleEventDescriptor descriptorWithBoolean:NO]
277                           forKeyword:keyMMUntitledWindow];
279             LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
280                     kLSLaunchDefaults, NULL };
281             status = LSOpenFromRefSpec(&spec, NULL);
282         }
284         if (noErr != status) {
285         NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
286                 path, MMSymlinkWarningString);
287             return NO;
288         }
289 #else
290         // Launch MacVim using NSTask.  For some reason the above code using
291         // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
292         // fails, the dock icon starts bouncing and never stops).  It seems
293         // like rebuilding the Launch Services database takes care of this
294         // problem, but the NSTask way seems more stable so stick with it.
295         //
296         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
297         // that the GUI won't be activated (or raised) so there is a hack in
298         // MMAppController which raises the app when a new window is opened.
299         NSMutableArray *args = [NSMutableArray arrayWithObjects:
300             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
301         NSString *exeName = [[mainBundle infoDictionary]
302                 objectForKey:@"CFBundleExecutable"];
303         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
304         if (!path) {
305             NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
306                     MMSymlinkWarningString);
307             return NO;
308         }
310         [NSTask launchedTaskWithLaunchPath:path arguments:args];
311 #endif
313         // HACK!  Poll the mach bootstrap server until it returns a valid
314         // connection to detect that MacVim has finished launching.  Also set a
315         // time-out date so that we don't get stuck doing this forever.
316         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
317         while (![self connection] &&
318                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
319             [[NSRunLoop currentRunLoop]
320                     runMode:NSDefaultRunLoopMode
321                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
323         // NOTE: [self connection] will set 'connection' as a side-effect.
324         if (!connection) {
325             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
326             return NO;
327         }
328     }
330     BOOL ok = NO;
331     @try {
332         [[NSNotificationCenter defaultCenter] addObserver:self
333                 selector:@selector(connectionDidDie:)
334                     name:NSConnectionDidDieNotification object:connection];
336         appProxy = [[connection rootProxy] retain];
337         [appProxy setProtocolForProxy:@protocol(MMAppProtocol)];
339         int pid = [[NSProcessInfo processInfo] processIdentifier];
341         identifier = [appProxy connectBackend:self pid:pid];
342         if (identifier != 0)
343             ok = YES;
344     }
345     @catch (NSException *e) {
346         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
347     }
349     return ok;
352 - (BOOL)openGUIWindow
354     [self queueMessage:OpenWindowMsgID data:nil];
355     return YES;
358 - (void)clearAll
360     int type = ClearAllDrawType;
362     // Any draw commands in queue are effectively obsolete since this clearAll
363     // will negate any effect they have, therefore we may as well clear the
364     // draw queue.
365     [self clearDrawData];
367     [drawData appendBytes:&type length:sizeof(int)];
370 - (void)clearBlockFromRow:(int)row1 column:(int)col1
371                     toRow:(int)row2 column:(int)col2
373     int type = ClearBlockDrawType;
375     [drawData appendBytes:&type length:sizeof(int)];
377     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
378     [drawData appendBytes:&row1 length:sizeof(int)];
379     [drawData appendBytes:&col1 length:sizeof(int)];
380     [drawData appendBytes:&row2 length:sizeof(int)];
381     [drawData appendBytes:&col2 length:sizeof(int)];
384 - (void)deleteLinesFromRow:(int)row count:(int)count
385               scrollBottom:(int)bottom left:(int)left right:(int)right
387     int type = DeleteLinesDrawType;
389     [drawData appendBytes:&type length:sizeof(int)];
391     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
392     [drawData appendBytes:&row length:sizeof(int)];
393     [drawData appendBytes:&count length:sizeof(int)];
394     [drawData appendBytes:&bottom length:sizeof(int)];
395     [drawData appendBytes:&left length:sizeof(int)];
396     [drawData appendBytes:&right length:sizeof(int)];
398     if (left == 0 && right == gui.num_cols-1)
399         [self didChangeWholeLine];
402 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
403              cells:(int)cells flags:(int)flags
405     if (len <= 0 || cells <= 0) return;
407     int type = DrawStringDrawType;
409     [drawData appendBytes:&type length:sizeof(int)];
411     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
412     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
413     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
414     [drawData appendBytes:&row length:sizeof(int)];
415     [drawData appendBytes:&col length:sizeof(int)];
416     [drawData appendBytes:&cells length:sizeof(int)];
417     [drawData appendBytes:&flags length:sizeof(int)];
418     [drawData appendBytes:&len length:sizeof(int)];
419     [drawData appendBytes:s length:len];
422 - (void)insertLinesFromRow:(int)row count:(int)count
423               scrollBottom:(int)bottom left:(int)left right:(int)right
425     int type = InsertLinesDrawType;
427     [drawData appendBytes:&type length:sizeof(int)];
429     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
430     [drawData appendBytes:&row length:sizeof(int)];
431     [drawData appendBytes:&count length:sizeof(int)];
432     [drawData appendBytes:&bottom length:sizeof(int)];
433     [drawData appendBytes:&left length:sizeof(int)];
434     [drawData appendBytes:&right length:sizeof(int)];
436     if (left == 0 && right == gui.num_cols-1)
437         [self didChangeWholeLine];
440 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
441                fraction:(int)percent color:(int)color
443     int type = DrawCursorDrawType;
444     unsigned uc = MM_COLOR(color);
446     [drawData appendBytes:&type length:sizeof(int)];
448     [drawData appendBytes:&uc length:sizeof(unsigned)];
449     [drawData appendBytes:&row length:sizeof(int)];
450     [drawData appendBytes:&col length:sizeof(int)];
451     [drawData appendBytes:&shape length:sizeof(int)];
452     [drawData appendBytes:&percent length:sizeof(int)];
455 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
456                    numColumns:(int)nc invert:(int)invert
458     int type = DrawInvertedRectDrawType;
459     [drawData appendBytes:&type length:sizeof(int)];
461     [drawData appendBytes:&row length:sizeof(int)];
462     [drawData appendBytes:&col length:sizeof(int)];
463     [drawData appendBytes:&nr length:sizeof(int)];
464     [drawData appendBytes:&nc length:sizeof(int)];
465     [drawData appendBytes:&invert length:sizeof(int)];
468 - (void)update
470     // Keep running the run-loop until there is no more input to process.
471     while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
472             == kCFRunLoopRunHandledSource)
473         ;   // do nothing
476 - (void)flushQueue:(BOOL)force
478     // NOTE: This variable allows for better control over when the queue is
479     // flushed.  It can be set to YES at the beginning of a sequence of calls
480     // that may potentially add items to the queue, and then restored back to
481     // NO.
482     if (flushDisabled) return;
484     if ([drawData length] > 0) {
485         // HACK!  Detect changes to 'guifontwide'.
486         if (gui.wide_font != oldWideFont) {
487             gui_mch_free_font(oldWideFont);
488             oldWideFont = gui_mch_retain_font(gui.wide_font);
489             [self setFont:oldWideFont wide:YES];
490         }
492         int type = SetCursorPosDrawType;
493         [drawData appendBytes:&type length:sizeof(type)];
494         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
495         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
497         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
498         [self clearDrawData];
499     }
501     if ([outputQueue count] > 0) {
502         [self insertVimStateMessage];
504         @try {
505             [appProxy processInput:outputQueue forIdentifier:identifier];
506         }
507         @catch (NSException *e) {
508             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
509             NSLog(@"outputQueue(len:%d)=%@", [outputQueue count]/2,
510                     outputQueue);
511             if (![connection isValid]) {
512                 NSLog(@"WARNING! Connection is invalid, exit now!");
513                 NSLog(@"waitForAck=%d got_int=%d", waitForAck, got_int);
514                 mch_exit(-1);
515             }
516         }
518         [outputQueue removeAllObjects];
519     }
522 - (BOOL)waitForInput:(int)milliseconds
524     // Return NO if we timed out waiting for input, otherwise return YES.
525     BOOL inputReceived = NO;
527     // Only start the run loop if the input queue is empty, otherwise process
528     // the input first so that the input on queue isn't delayed.
529     if ([inputQueue count]) {
530         inputReceived = YES;
531     } else {
532         // Wait for the specified amount of time, unless 'milliseconds' is
533         // negative in which case we wait "forever" (1e6 seconds translates to
534         // approximately 11 days).
535         CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
537         while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
538                 == kCFRunLoopRunHandledSource) {
539             // In order to ensure that all input on the run-loop has been
540             // processed we set the timeout to 0 and keep processing until the
541             // run-loop times out.
542             dt = 0.0;
543             inputReceived = YES;
544         }
545     }
547     // The above calls may have placed messages on the input queue so process
548     // it now.  This call may enter a blocking loop.
549     if ([inputQueue count] > 0)
550         [self processInputQueue];
552     return inputReceived;
555 - (void)exit
557     // NOTE: This is called if mch_exit() is called.  Since we assume here that
558     // the process has started properly, be sure to use exit() instead of
559     // mch_exit() to prematurely terminate a process (or set 'isTerminating'
560     // first).
562     // Make sure no connectionDidDie: notification is received now that we are
563     // already exiting.
564     [[NSNotificationCenter defaultCenter] removeObserver:self];
566     // The 'isTerminating' flag indicates that the frontend is also exiting so
567     // there is no need to flush any more output since the frontend won't look
568     // at it anyway.
569     if (!isTerminating && [connection isValid]) {
570         @try {
571             // Flush the entire queue in case a VimLeave autocommand added
572             // something to the queue.
573             [self queueMessage:CloseWindowMsgID data:nil];
574             [appProxy processInput:outputQueue forIdentifier:identifier];
575         }
576         @catch (NSException *e) {
577             NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
578         }
580         // NOTE: If Cmd-w was pressed to close the window the menu is briefly
581         // highlighted and during this pause the frontend won't receive any DO
582         // messages.  If the Vim process exits before this highlighting has
583         // finished Cocoa will emit the following error message:
584         //   *** -[NSMachPort handlePortMessage:]: dropping incoming DO message
585         //   because the connection or ports are invalid
586         // To avoid this warning we delay here.  If the warning still appears
587         // this delay may need to be increased.
588         usleep(150000);
589     }
591 #ifdef MAC_CLIENTSERVER
592     // The default connection is used for the client/server code.
593     [[NSConnection defaultConnection] setRootObject:nil];
594     [[NSConnection defaultConnection] invalidate];
595 #endif
598 - (void)selectTab:(int)index
600     //NSLog(@"%s%d", _cmd, index);
602     index -= 1;
603     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
604     [self queueMessage:SelectTabMsgID data:data];
607 - (void)updateTabBar
609     //NSLog(@"%s", _cmd);
611     NSMutableData *data = [NSMutableData data];
613     int idx = tabpage_index(curtab) - 1;
614     [data appendBytes:&idx length:sizeof(int)];
616     tabpage_T *tp;
617     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
618         // Count the number of windows in the tabpage.
619         //win_T *wp = tp->tp_firstwin;
620         //int wincount;
621         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
622         //[data appendBytes:&wincount length:sizeof(int)];
624         int tabProp = MMTabInfoCount;
625         [data appendBytes:&tabProp length:sizeof(int)];
626         for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
627             // This function puts the label of the tab in the global 'NameBuff'.
628             get_tabline_label(tp, (tabProp == MMTabToolTip));
629             NSString *s = [NSString stringWithVimString:NameBuff];
630             int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
631             if (len < 0)
632                 len = 0;
634             [data appendBytes:&len length:sizeof(int)];
635             if (len > 0)
636                 [data appendBytes:[s UTF8String] length:len];
637         }
638     }
640     [self queueMessage:UpdateTabBarMsgID data:data];
643 - (BOOL)tabBarVisible
645     return tabBarVisible;
648 - (void)showTabBar:(BOOL)enable
650     tabBarVisible = enable;
652     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
653     [self queueMessage:msgid data:nil];
656 - (void)setRows:(int)rows columns:(int)cols
658     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
660     int dim[] = { rows, cols };
661     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
663     [self queueMessage:SetTextDimensionsMsgID data:data];
666 - (void)setWindowTitle:(char *)title
668     NSMutableData *data = [NSMutableData data];
669     int len = strlen(title);
670     if (len <= 0) return;
672     [data appendBytes:&len length:sizeof(int)];
673     [data appendBytes:title length:len];
675     [self queueMessage:SetWindowTitleMsgID data:data];
678 - (void)setDocumentFilename:(char *)filename
680     NSMutableData *data = [NSMutableData data];
681     int len = filename ? strlen(filename) : 0;
683     [data appendBytes:&len length:sizeof(int)];
684     if (len > 0)
685         [data appendBytes:filename length:len];
687     [self queueMessage:SetDocumentFilenameMsgID data:data];
690 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
692     char_u *s = NULL;
694 #if 0
695     @try {
696         [frontendProxy showSavePanelWithAttributes:attr];
698         [self waitForDialogReturn];
700         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
701             s = [dialogReturn vimStringSave];
703         [dialogReturn release];  dialogReturn = nil;
704     }
705     @catch (NSException *e) {
706         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
707     }
708 #endif
709     return (char *)s;
712 - (oneway void)setDialogReturn:(in bycopy id)obj
714     // NOTE: This is called by
715     //   - [MMVimController panelDidEnd:::], and
716     //   - [MMVimController alertDidEnd:::],
717     // to indicate that a save/open panel or alert has finished.
719     // We want to distinguish between "no dialog return yet" and "dialog
720     // returned nothing".  The former can be tested with dialogReturn == nil,
721     // the latter with dialogReturn == [NSNull null].
722     if (!obj) obj = [NSNull null];
724     if (obj != dialogReturn) {
725         [dialogReturn release];
726         dialogReturn = [obj retain];
727     }
730 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
732     int retval = 0;
734 #if 0
735     @try {
736         [frontendProxy presentDialogWithAttributes:attr];
738         [self waitForDialogReturn];
740         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
741                 && [dialogReturn count]) {
742             retval = [[dialogReturn objectAtIndex:0] intValue];
743             if (txtfield && [dialogReturn count] > 1) {
744                 NSString *retString = [dialogReturn objectAtIndex:1];
745                 char_u *ret = (char_u*)[retString UTF8String];
746 #ifdef FEAT_MBYTE
747                 ret = CONVERT_FROM_UTF8(ret);
748 #endif
749                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
750 #ifdef FEAT_MBYTE
751                 CONVERT_FROM_UTF8_FREE(ret);
752 #endif
753             }
754         }
756         [dialogReturn release]; dialogReturn = nil;
757     }
758     @catch (NSException *e) {
759         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
760     }
761 #endif
763     return retval;
766 - (void)showToolbar:(int)enable flags:(int)flags
768     NSMutableData *data = [NSMutableData data];
770     [data appendBytes:&enable length:sizeof(int)];
771     [data appendBytes:&flags length:sizeof(int)];
773     [self queueMessage:ShowToolbarMsgID data:data];
776 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
778     NSMutableData *data = [NSMutableData data];
780     [data appendBytes:&ident length:sizeof(long)];
781     [data appendBytes:&type length:sizeof(int)];
783     [self queueMessage:CreateScrollbarMsgID data:data];
786 - (void)destroyScrollbarWithIdentifier:(long)ident
788     NSMutableData *data = [NSMutableData data];
789     [data appendBytes:&ident length:sizeof(long)];
791     [self queueMessage:DestroyScrollbarMsgID data:data];
794 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
796     NSMutableData *data = [NSMutableData data];
798     [data appendBytes:&ident length:sizeof(long)];
799     [data appendBytes:&visible length:sizeof(int)];
801     [self queueMessage:ShowScrollbarMsgID data:data];
804 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
806     NSMutableData *data = [NSMutableData data];
808     [data appendBytes:&ident length:sizeof(long)];
809     [data appendBytes:&pos length:sizeof(int)];
810     [data appendBytes:&len length:sizeof(int)];
812     [self queueMessage:SetScrollbarPositionMsgID data:data];
815 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
816                     identifier:(long)ident
818     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
819     float prop = (float)size/(max+1);
820     if (fval < 0) fval = 0;
821     else if (fval > 1.0f) fval = 1.0f;
822     if (prop < 0) prop = 0;
823     else if (prop > 1.0f) prop = 1.0f;
825     NSMutableData *data = [NSMutableData data];
827     [data appendBytes:&ident length:sizeof(long)];
828     [data appendBytes:&fval length:sizeof(float)];
829     [data appendBytes:&prop length:sizeof(float)];
831     [self queueMessage:SetScrollbarThumbMsgID data:data];
834 - (void)setFont:(GuiFont)font wide:(BOOL)wide
836     NSString *fontName = (NSString *)font;
837     float size = 0;
838     NSArray *components = [fontName componentsSeparatedByString:@":"];
839     if ([components count] == 2) {
840         size = [[components lastObject] floatValue];
841         fontName = [components objectAtIndex:0];
842     }
844     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
845     NSMutableData *data = [NSMutableData data];
846     [data appendBytes:&size length:sizeof(float)];
847     [data appendBytes:&len length:sizeof(int)];
849     if (len > 0)
850         [data appendBytes:[fontName UTF8String] length:len];
851     else if (!wide)
852         return;     // Only the wide font can be set to nothing
854     [self queueMessage:(wide ? SetWideFontMsgID : SetFontMsgID) data:data];
857 - (void)executeActionWithName:(NSString *)name
859     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
861     if (len > 0) {
862         NSMutableData *data = [NSMutableData data];
864         [data appendBytes:&len length:sizeof(int)];
865         [data appendBytes:[name UTF8String] length:len];
867         [self queueMessage:ExecuteActionMsgID data:data];
868     }
871 - (void)setMouseShape:(int)shape
873     NSMutableData *data = [NSMutableData data];
874     [data appendBytes:&shape length:sizeof(int)];
875     [self queueMessage:SetMouseShapeMsgID data:data];
878 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
880     // Vim specifies times in milliseconds, whereas Cocoa wants them in
881     // seconds.
882     blinkWaitInterval = .001f*wait;
883     blinkOnInterval = .001f*on;
884     blinkOffInterval = .001f*off;
887 - (void)startBlink
889     if (blinkTimer) {
890         [blinkTimer invalidate];
891         [blinkTimer release];
892         blinkTimer = nil;
893     }
895     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
896             && gui.in_focus) {
897         blinkState = MMBlinkStateOn;
898         blinkTimer =
899             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
900                                               target:self
901                                             selector:@selector(blinkTimerFired:)
902                                             userInfo:nil repeats:NO] retain];
903         gui_update_cursor(TRUE, FALSE);
904         [self flushQueue:YES];
905     }
908 - (void)stopBlink
910     if (MMBlinkStateOff == blinkState) {
911         gui_update_cursor(TRUE, FALSE);
912         [self flushQueue:YES];
913     }
915     blinkState = MMBlinkStateNone;
918 - (void)adjustLinespace:(int)linespace
920     NSMutableData *data = [NSMutableData data];
921     [data appendBytes:&linespace length:sizeof(int)];
922     [self queueMessage:AdjustLinespaceMsgID data:data];
925 - (void)activate
927     [self queueMessage:ActivateMsgID data:nil];
930 - (void)setPreEditRow:(int)row column:(int)col
932     NSMutableData *data = [NSMutableData data];
933     [data appendBytes:&row length:sizeof(int)];
934     [data appendBytes:&col length:sizeof(int)];
935     [self queueMessage:SetPreEditPositionMsgID data:data];
938 - (int)lookupColorWithKey:(NSString *)key
940     if (!(key && [key length] > 0))
941         return INVALCOLOR;
943     NSString *stripKey = [[[[key lowercaseString]
944         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
945             componentsSeparatedByString:@" "]
946                componentsJoinedByString:@""];
948     if (stripKey && [stripKey length] > 0) {
949         // First of all try to lookup key in the color dictionary; note that
950         // all keys in this dictionary are lowercase with no whitespace.
951         id obj = [colorDict objectForKey:stripKey];
952         if (obj) return [obj intValue];
954         // The key was not in the dictionary; is it perhaps of the form
955         // #rrggbb?
956         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
957             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
958             [scanner setScanLocation:1];
959             unsigned hex = 0;
960             if ([scanner scanHexInt:&hex]) {
961                 return (int)hex;
962             }
963         }
965         // As a last resort, check if it is one of the system defined colors.
966         // The keys in this dictionary are also lowercase with no whitespace.
967         obj = [sysColorDict objectForKey:stripKey];
968         if (obj) {
969             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
970             if (col) {
971                 float r, g, b, a;
972                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
973                 [col getRed:&r green:&g blue:&b alpha:&a];
974                 return (((int)(r*255+.5f) & 0xff) << 16)
975                      + (((int)(g*255+.5f) & 0xff) << 8)
976                      +  ((int)(b*255+.5f) & 0xff);
977             }
978         }
979     }
981     //NSLog(@"WARNING: No color with key %@ found.", stripKey);
982     return INVALCOLOR;
985 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
987     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
988     id obj;
990     while ((obj = [e nextObject])) {
991         if ([value isEqual:obj])
992             return YES;
993     }
995     return NO;
998 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1000     NSMutableData *data = [NSMutableData data];
1001     [data appendBytes:&fuoptions length:sizeof(int)];
1002     bg = MM_COLOR(bg);
1003     [data appendBytes:&bg length:sizeof(int)];
1004     [self queueMessage:EnterFullscreenMsgID data:data];
1007 - (void)leaveFullscreen
1009     [self queueMessage:LeaveFullscreenMsgID data:nil];
1012 - (void)setFullscreenBackgroundColor:(int)color
1014     NSMutableData *data = [NSMutableData data];
1015     color = MM_COLOR(color);
1016     [data appendBytes:&color length:sizeof(int)];
1018     [self queueMessage:SetFullscreenColorMsgID data:data];
1021 - (void)setAntialias:(BOOL)antialias
1023     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1025     [self queueMessage:msgid data:nil];
1028 - (void)updateModifiedFlag
1030     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1031     // vice versa.
1032     int msgid = [self checkForModifiedBuffers]
1033             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1035     [self queueMessage:msgid data:nil];
1038 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1040     // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1041     // queue is processed since that only happens in waitForInput: (and Vim
1042     // regularly checks for Ctrl-C in between waiting for input).
1043     // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1044     // which waits on the run loop will fail to detect this message (e.g. in
1045     // waitForConnectionAcknowledgement).
1047     if (InsertTextMsgID == msgid && data != nil) {
1048         const void *bytes = [data bytes];
1049         bytes += sizeof(int);
1050         int len = *((int*)bytes);  bytes += sizeof(int);
1051         if (1 == len) {
1052             char_u *str = (char_u*)bytes;
1053             if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1054                     (str[0] == intr_char && intr_char != Ctrl_C)) {
1055                 got_int = TRUE;
1056                 [inputQueue removeAllObjects];
1057                 return;
1058             }
1059         }
1060     } else if (TerminateNowMsgID == msgid) {
1061         // Terminate immediately (the frontend is about to quit or this process
1062         // was aborted).  Don't preserve modified files since the user would
1063         // already have been presented with a dialog warning if there were any
1064         // modified files when we get here.
1065         isTerminating = YES;
1066         getout(0);
1067         return;
1068     }
1070     // Remove all previous instances of this message from the input queue, else
1071     // the input queue may fill up as a result of Vim not being able to keep up
1072     // with the speed at which new messages are received.
1073     // Keyboard input is never dropped, unless the input represents and
1074     // auto-repeated key.
1076     BOOL isKeyRepeat = NO;
1077     BOOL isKeyboardInput = NO;
1079     if (data && (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1080             CmdKeyMsgID == msgid)) {
1081         isKeyboardInput = YES;
1083         // The lowest bit of the first int is set if this key is a repeat.
1084         int flags = *((int*)[data bytes]);
1085         if (flags & 1)
1086             isKeyRepeat = YES;
1087     }
1089     // Keyboard input is not removed from the queue; repeats are ignored if
1090     // there already is keyboard input on the input queue.
1091     if (isKeyRepeat || !isKeyboardInput) {
1092         int i, count = [inputQueue count];
1093         for (i = 1; i < count; i+=2) {
1094             if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1095                 if (isKeyRepeat)
1096                     return;
1098                 [inputQueue removeObjectAtIndex:i];
1099                 [inputQueue removeObjectAtIndex:i-1];
1100                 break;
1101             }
1102         }
1103     }
1105     [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1106     [inputQueue addObject:(data ? (id)data : [NSNull null])];
1109 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1111     // This is just a convenience method that allows the frontend to delay
1112     // sending messages.
1113     int i, count = [messages count];
1114     for (i = 1; i < count; i+=2)
1115         [self processInput:[[messages objectAtIndex:i-1] intValue]
1116                       data:[messages objectAtIndex:i]];
1119 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1120                   errorString:(out bycopy NSString **)errstr
1122     return evalExprCocoa(expr, errstr);
1126 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1128     NSString *eval = nil;
1129     char_u *s = (char_u*)[expr UTF8String];
1131 #ifdef FEAT_MBYTE
1132     s = CONVERT_FROM_UTF8(s);
1133 #endif
1135     char_u *res = eval_client_expr_to_string(s);
1137 #ifdef FEAT_MBYTE
1138     CONVERT_FROM_UTF8_FREE(s);
1139 #endif
1141     if (res != NULL) {
1142         s = res;
1143 #ifdef FEAT_MBYTE
1144         s = CONVERT_TO_UTF8(s);
1145 #endif
1146         eval = [NSString stringWithUTF8String:(char*)s];
1147 #ifdef FEAT_MBYTE
1148         CONVERT_TO_UTF8_FREE(s);
1149 #endif
1150         vim_free(res);
1151     }
1153     return eval;
1156 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1158     // TODO: This method should share code with clip_mch_request_selection().
1160     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1161         // If there is no pasteboard, return YES to indicate that there is text
1162         // to copy.
1163         if (!pboard)
1164             return YES;
1166         clip_copy_selection();
1168         // Get the text to put on the pasteboard.
1169         long_u llen = 0; char_u *str = 0;
1170         int type = clip_convert_selection(&str, &llen, &clip_star);
1171         if (type < 0)
1172             return NO;
1173         
1174         // TODO: Avoid overflow.
1175         int len = (int)llen;
1176 #ifdef FEAT_MBYTE
1177         if (output_conv.vc_type != CONV_NONE) {
1178             char_u *conv_str = string_convert(&output_conv, str, &len);
1179             if (conv_str) {
1180                 vim_free(str);
1181                 str = conv_str;
1182             }
1183         }
1184 #endif
1186         NSString *string = [[NSString alloc]
1187             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1189         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1190         [pboard declareTypes:types owner:nil];
1191         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1192     
1193         [string release];
1194         vim_free(str);
1196         return ok;
1197     }
1199     return NO;
1202 - (oneway void)addReply:(in bycopy NSString *)reply
1203                  server:(in byref id <MMVimServerProtocol>)server
1205     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1207     // Replies might come at any time and in any order so we keep them in an
1208     // array inside a dictionary with the send port used as key.
1210     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1211     // HACK! Assume connection uses mach ports.
1212     int port = [(NSMachPort*)[conn sendPort] machPort];
1213     NSNumber *key = [NSNumber numberWithInt:port];
1215     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1216     if (!replies) {
1217         replies = [NSMutableArray array];
1218         [serverReplyDict setObject:replies forKey:key];
1219     }
1221     [replies addObject:reply];
1224 - (void)addInput:(in bycopy NSString *)input
1225           client:(in byref id <MMVimClientProtocol>)client
1227     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1229     // NOTE: We don't call addInput: here because it differs from
1230     // server_to_input_buf() in that it always sets the 'silent' flag and we
1231     // don't want the MacVim client/server code to behave differently from
1232     // other platforms.
1233     char_u *s = [input vimStringSave];
1234     server_to_input_buf(s);
1235     vim_free(s);
1237     [self addClient:(id)client];
1240 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1241                  client:(in byref id <MMVimClientProtocol>)client
1243     [self addClient:(id)client];
1244     return [self evaluateExpression:expr];
1247 - (void)registerServerWithName:(NSString *)name
1249     NSString *svrName = name;
1250     NSConnection *svrConn = [NSConnection defaultConnection];
1251     unsigned i;
1253     for (i = 0; i < MMServerMax; ++i) {
1254         NSString *connName = [self connectionNameFromServerName:svrName];
1256         if ([svrConn registerName:connName]) {
1257             //NSLog(@"Registered server with name: %@", svrName);
1259             // TODO: Set request/reply time-outs to something else?
1260             //
1261             // Don't wait for requests (time-out means that the message is
1262             // dropped).
1263             [svrConn setRequestTimeout:0];
1264             //[svrConn setReplyTimeout:MMReplyTimeout];
1265             [svrConn setRootObject:self];
1267             // NOTE: 'serverName' is a global variable
1268             serverName = [svrName vimStringSave];
1269 #ifdef FEAT_EVAL
1270             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1271 #endif
1272 #ifdef FEAT_TITLE
1273             need_maketitle = TRUE;
1274 #endif
1275             [self queueMessage:SetServerNameMsgID data:
1276                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1277             break;
1278         }
1280         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1281     }
1284 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1285                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1286               silent:(BOOL)silent
1288     // NOTE: If 'name' equals 'serverName' then the request is local (client
1289     // and server are the same).  This case is not handled separately, so a
1290     // connection will be set up anyway (this simplifies the code).
1292     NSConnection *conn = [self connectionForServerName:name];
1293     if (!conn) {
1294         if (!silent) {
1295             char_u *s = (char_u*)[name UTF8String];
1296 #ifdef FEAT_MBYTE
1297             s = CONVERT_FROM_UTF8(s);
1298 #endif
1299             EMSG2(_(e_noserver), s);
1300 #ifdef FEAT_MBYTE
1301             CONVERT_FROM_UTF8_FREE(s);
1302 #endif
1303         }
1304         return NO;
1305     }
1307     if (port) {
1308         // HACK! Assume connection uses mach ports.
1309         *port = [(NSMachPort*)[conn sendPort] machPort];
1310     }
1312     id proxy = [conn rootProxy];
1313     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1315     @try {
1316         if (expr) {
1317             NSString *eval = [proxy evaluateExpression:string client:self];
1318             if (reply) {
1319                 if (eval) {
1320                     *reply = [eval vimStringSave];
1321                 } else {
1322                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1323                 }
1324             }
1326             if (!eval)
1327                 return NO;
1328         } else {
1329             [proxy addInput:string client:self];
1330         }
1331     }
1332     @catch (NSException *e) {
1333         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1334         return NO;
1335     }
1337     return YES;
1340 - (NSArray *)serverList
1342     NSArray *list = nil;
1344     if ([self connection]) {
1345         id proxy = [connection rootProxy];
1346         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1348         @try {
1349             list = [proxy serverList];
1350         }
1351         @catch (NSException *e) {
1352             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1353         }
1354     } else {
1355         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1356     }
1358     return list;
1361 - (NSString *)peekForReplyOnPort:(int)port
1363     //NSLog(@"%s%d", _cmd, port);
1365     NSNumber *key = [NSNumber numberWithInt:port];
1366     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1367     if (replies && [replies count]) {
1368         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1369         //        [replies objectAtIndex:0]);
1370         return [replies objectAtIndex:0];
1371     }
1373     //NSLog(@"    No replies");
1374     return nil;
1377 - (NSString *)waitForReplyOnPort:(int)port
1379     //NSLog(@"%s%d", _cmd, port);
1380     
1381     NSConnection *conn = [self connectionForServerPort:port];
1382     if (!conn)
1383         return nil;
1385     NSNumber *key = [NSNumber numberWithInt:port];
1386     NSMutableArray *replies = nil;
1387     NSString *reply = nil;
1389     // Wait for reply as long as the connection to the server is valid (unless
1390     // user interrupts wait with Ctrl-C).
1391     while (!got_int && [conn isValid] &&
1392             !(replies = [serverReplyDict objectForKey:key])) {
1393         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1394                                  beforeDate:[NSDate distantFuture]];
1395     }
1397     if (replies) {
1398         if ([replies count] > 0) {
1399             reply = [[replies objectAtIndex:0] retain];
1400             //NSLog(@"    Got reply: %@", reply);
1401             [replies removeObjectAtIndex:0];
1402             [reply autorelease];
1403         }
1405         if ([replies count] == 0)
1406             [serverReplyDict removeObjectForKey:key];
1407     }
1409     return reply;
1412 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1414     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1415     if (client) {
1416         @try {
1417             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1418             [client addReply:reply server:self];
1419             return YES;
1420         }
1421         @catch (NSException *e) {
1422             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1423         }
1424     } else {
1425         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1426     }
1428     return NO;
1431 - (BOOL)waitForAck
1433     return waitForAck;
1436 - (void)setWaitForAck:(BOOL)yn
1438     waitForAck = yn;
1441 - (void)waitForConnectionAcknowledgement
1443     if (!waitForAck) return;
1445     while (waitForAck && !got_int && [connection isValid]) {
1446         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1447                                  beforeDate:[NSDate distantFuture]];
1448         //NSLog(@"  waitForAck=%d got_int=%d isValid=%d",
1449         //        waitForAck, got_int, [connection isValid]);
1450     }
1452     if (waitForAck) {
1453         // Never received a connection acknowledgement, so die.
1454         [[NSNotificationCenter defaultCenter] removeObserver:self];
1455         [appProxy release];  appProxy = nil;
1457         // NOTE: We intentionally do not call mch_exit() since this in turn
1458         // will lead to -[MMBackend exit] getting called which we want to
1459         // avoid.
1460         exit(0);
1461     }
1463     [self processInputQueue];
1466 - (oneway void)acknowledgeConnection
1468     //NSLog(@"%s", _cmd);
1469     waitForAck = NO;
1472 @end // MMBackend
1476 @implementation MMBackend (Private)
1478 - (void)clearDrawData
1480     [drawData setLength:0];
1481     numWholeLineChanges = offsetForDrawDataPrune = 0;
1484 - (void)didChangeWholeLine
1486     // It may happen that draw queue is filled up with lots of changes that
1487     // affect a whole row.  If the number of such changes equals twice the
1488     // number of visible rows then we can prune some commands off the queue.
1489     //
1490     // NOTE: If we don't perform this pruning the draw queue may grow
1491     // indefinitely if Vim were to repeatedly send draw commands without ever
1492     // waiting for new input (that's when the draw queue is flushed).  The one
1493     // instance I know where this can happen is when a command is executed in
1494     // the shell (think ":grep" with thousands of matches).
1496     ++numWholeLineChanges;
1497     if (numWholeLineChanges == gui.num_rows) {
1498         // Remember the offset to prune up to.
1499         offsetForDrawDataPrune = [drawData length];
1500     } else if (numWholeLineChanges == 2*gui.num_rows) {
1501         // Delete all the unnecessary draw commands.
1502         NSMutableData *d = [[NSMutableData alloc]
1503                     initWithBytes:[drawData bytes] + offsetForDrawDataPrune
1504                            length:[drawData length] - offsetForDrawDataPrune];
1505         offsetForDrawDataPrune = [d length];
1506         numWholeLineChanges -= gui.num_rows;
1507         [drawData release];
1508         drawData = d;
1509     }
1512 - (void)waitForDialogReturn
1514     // Keep processing the run loop until a dialog returns.  To avoid getting
1515     // stuck in an endless loop (could happen if the setDialogReturn: message
1516     // was lost) we also do some paranoia checks.
1517     //
1518     // Note that in Cocoa the user can still resize windows and select menu
1519     // items while a sheet is being displayed, so we can't just wait for the
1520     // first message to arrive and assume that is the setDialogReturn: call.
1522     while (nil == dialogReturn && !got_int && [connection isValid])
1523         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1524                                  beforeDate:[NSDate distantFuture]];
1526     // Search for any resize messages on the input queue.  All other messages
1527     // on the input queue are dropped.  The reason why we single out resize
1528     // messages is because the user may have resized the window while a sheet
1529     // was open.
1530     int i, count = [inputQueue count];
1531     if (count > 0) {
1532         id textDimData = nil;
1533         if (count%2 == 0) {
1534             for (i = count-2; i >= 0; i -= 2) {
1535                 int msgid = [[inputQueue objectAtIndex:i] intValue];
1536                 if (SetTextDimensionsMsgID == msgid) {
1537                     textDimData = [[inputQueue objectAtIndex:i+1] retain];
1538                     break;
1539                 }
1540             }
1541         }
1543         [inputQueue removeAllObjects];
1545         if (textDimData) {
1546             [inputQueue addObject:
1547                     [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1548             [inputQueue addObject:textDimData];
1549             [textDimData release];
1550         }
1551     }
1554 - (void)insertVimStateMessage
1556     // NOTE: This is the place to add Vim state that needs to be accessed from
1557     // MacVim.  Do not add state that could potentially require lots of memory
1558     // since this message gets sent each time the output queue is forcibly
1559     // flushed (e.g. storing the currently selected text would be a bad idea).
1560     // We take this approach of "pushing" the state to MacVim to avoid having
1561     // to make synchronous calls from MacVim to Vim in order to get state.
1563     BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1564     int numTabs = tabpage_index(NULL) - 1;
1565     if (numTabs < 0)
1566         numTabs = 0;
1568     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1569         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1570         [NSNumber numberWithInt:p_mh], @"p_mh",
1571         [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1572         [NSNumber numberWithBool:mmta], @"p_mmta",
1573         [NSNumber numberWithInt:numTabs], @"numTabs",
1574         nil];
1576     // Put the state before all other messages.
1577     int msgid = SetVimStateMsgID;
1578     [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1579     [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1580                       atIndex:0];
1583 - (void)processInputQueue
1585     if ([inputQueue count] == 0) return;
1587     // NOTE: One of the input events may cause this method to be called
1588     // recursively, so copy the input queue to a local variable and clear the
1589     // queue before starting to process input events (otherwise we could get
1590     // stuck in an endless loop).
1591     NSArray *q = [inputQueue copy];
1592     unsigned i, count = [q count];
1594     [inputQueue removeAllObjects];
1596     for (i = 1; i < count; i+=2) {
1597         int msgid = [[q objectAtIndex:i-1] intValue];
1598         id data = [q objectAtIndex:i];
1599         if ([data isEqual:[NSNull null]])
1600             data = nil;
1602         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1603         [self handleInputEvent:msgid data:data];
1604     }
1606     [q release];
1607     //NSLog(@"Clear input event queue");
1610 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1612     if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1613             CmdKeyMsgID == msgid) {
1614         if (!data) return;
1615         const void *bytes = [data bytes];
1616         int mods = *((int*)bytes);  bytes += sizeof(int);
1617         int len = *((int*)bytes);  bytes += sizeof(int);
1618         NSString *key = [[NSString alloc] initWithBytes:bytes
1619                                                  length:len
1620                                                encoding:NSUTF8StringEncoding];
1621         mods = eventModifierFlagsToVimModMask(mods);
1623         if (InsertTextMsgID == msgid)
1624             [self handleInsertText:key];
1625         else
1626             [self handleKeyDown:key modifiers:mods];
1628         [key release];
1629     } else if (ScrollWheelMsgID == msgid) {
1630         if (!data) return;
1631         const void *bytes = [data bytes];
1633         int row = *((int*)bytes);  bytes += sizeof(int);
1634         int col = *((int*)bytes);  bytes += sizeof(int);
1635         int flags = *((int*)bytes);  bytes += sizeof(int);
1636         float dy = *((float*)bytes);  bytes += sizeof(float);
1638         int button = MOUSE_5;
1639         if (dy > 0) button = MOUSE_4;
1641         flags = eventModifierFlagsToVimMouseModMask(flags);
1643         int numLines = (int)round(dy);
1644         if (numLines < 0) numLines = -numLines;
1645         if (numLines == 0) numLines = 1;
1647 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1648         gui.scroll_wheel_force = numLines;
1649 #endif
1651         gui_send_mouse_event(button, col, row, NO, flags);
1652     } else if (MouseDownMsgID == msgid) {
1653         if (!data) return;
1654         const void *bytes = [data bytes];
1656         int row = *((int*)bytes);  bytes += sizeof(int);
1657         int col = *((int*)bytes);  bytes += sizeof(int);
1658         int button = *((int*)bytes);  bytes += sizeof(int);
1659         int flags = *((int*)bytes);  bytes += sizeof(int);
1660         int count = *((int*)bytes);  bytes += sizeof(int);
1662         button = eventButtonNumberToVimMouseButton(button);
1663         if (button >= 0) {
1664             flags = eventModifierFlagsToVimMouseModMask(flags);
1665             gui_send_mouse_event(button, col, row, count>1, flags);
1666         }
1667     } else if (MouseUpMsgID == msgid) {
1668         if (!data) return;
1669         const void *bytes = [data bytes];
1671         int row = *((int*)bytes);  bytes += sizeof(int);
1672         int col = *((int*)bytes);  bytes += sizeof(int);
1673         int flags = *((int*)bytes);  bytes += sizeof(int);
1675         flags = eventModifierFlagsToVimMouseModMask(flags);
1677         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1678     } else if (MouseDraggedMsgID == msgid) {
1679         if (!data) return;
1680         const void *bytes = [data bytes];
1682         int row = *((int*)bytes);  bytes += sizeof(int);
1683         int col = *((int*)bytes);  bytes += sizeof(int);
1684         int flags = *((int*)bytes);  bytes += sizeof(int);
1686         flags = eventModifierFlagsToVimMouseModMask(flags);
1688         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1689     } else if (MouseMovedMsgID == msgid) {
1690         const void *bytes = [data bytes];
1691         int row = *((int*)bytes);  bytes += sizeof(int);
1692         int col = *((int*)bytes);  bytes += sizeof(int);
1694         gui_mouse_moved(col, row);
1695     } else if (AddInputMsgID == msgid) {
1696         NSString *string = [[NSString alloc] initWithData:data
1697                 encoding:NSUTF8StringEncoding];
1698         if (string) {
1699             [self addInput:string];
1700             [string release];
1701         }
1702     } else if (SelectTabMsgID == msgid) {
1703         if (!data) return;
1704         const void *bytes = [data bytes];
1705         int idx = *((int*)bytes) + 1;
1706         //NSLog(@"Selecting tab %d", idx);
1707         send_tabline_event(idx);
1708     } else if (CloseTabMsgID == msgid) {
1709         if (!data) return;
1710         const void *bytes = [data bytes];
1711         int idx = *((int*)bytes) + 1;
1712         //NSLog(@"Closing tab %d", idx);
1713         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1714     } else if (AddNewTabMsgID == msgid) {
1715         //NSLog(@"Adding new tab");
1716         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1717     } else if (DraggedTabMsgID == msgid) {
1718         if (!data) return;
1719         const void *bytes = [data bytes];
1720         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1721         // based.
1722         int idx = *((int*)bytes);
1724         tabpage_move(idx);
1725     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1726             || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1727         if (!data) return;
1728         const void *bytes = [data bytes];
1729         int rows = Rows;
1730         if (SetTextColumnsMsgID != msgid) {
1731             rows = *((int*)bytes);  bytes += sizeof(int);
1732         }
1733         int cols = Columns;
1734         if (SetTextRowsMsgID != msgid) {
1735             cols = *((int*)bytes);  bytes += sizeof(int);
1736         }
1738         NSData *d = data;
1739         if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1740             int dim[2] = { rows, cols };
1741             d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1742             msgid = SetTextDimensionsReplyMsgID;
1743         }
1745         if (SetTextDimensionsMsgID == msgid)
1746             msgid = SetTextDimensionsReplyMsgID;
1748         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1749         // gui_resize_shell(), so we have to manually set the rows and columns
1750         // here since MacVim doesn't change the rows and columns to avoid
1751         // inconsistent states between Vim and MacVim.  The message sent back
1752         // indicates that it is a reply to a message that originated in MacVim
1753         // since we need to be able to determine where a message originated.
1754         [self queueMessage:msgid data:d];
1756         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1757         gui_resize_shell(cols, rows);
1758     } else if (ExecuteMenuMsgID == msgid) {
1759         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1760         if (attrs) {
1761             NSArray *desc = [attrs objectForKey:@"descriptor"];
1762             vimmenu_T *menu = menu_for_descriptor(desc);
1763             if (menu)
1764                 gui_menu_cb(menu);
1765         }
1766     } else if (ToggleToolbarMsgID == msgid) {
1767         [self handleToggleToolbar];
1768     } else if (ScrollbarEventMsgID == msgid) {
1769         [self handleScrollbarEvent:data];
1770     } else if (SetFontMsgID == msgid) {
1771         [self handleSetFont:data];
1772     } else if (VimShouldCloseMsgID == msgid) {
1773         gui_shell_closed();
1774     } else if (DropFilesMsgID == msgid) {
1775         [self handleDropFiles:data];
1776     } else if (DropStringMsgID == msgid) {
1777         [self handleDropString:data];
1778     } else if (GotFocusMsgID == msgid) {
1779         if (!gui.in_focus)
1780             [self focusChange:YES];
1781     } else if (LostFocusMsgID == msgid) {
1782         if (gui.in_focus)
1783             [self focusChange:NO];
1784     } else if (SetMouseShapeMsgID == msgid) {
1785         const void *bytes = [data bytes];
1786         int shape = *((int*)bytes);  bytes += sizeof(int);
1787         update_mouseshape(shape);
1788     } else if (XcodeModMsgID == msgid) {
1789         [self handleXcodeMod:data];
1790     } else if (OpenWithArgumentsMsgID == msgid) {
1791         [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1792     } else if (FindReplaceMsgID == msgid) {
1793         [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1794     } else {
1795         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1796     }
1799 + (NSDictionary *)specialKeys
1801     static NSDictionary *specialKeys = nil;
1803     if (!specialKeys) {
1804         NSBundle *mainBundle = [NSBundle mainBundle];
1805         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1806                                               ofType:@"plist"];
1807         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1808     }
1810     return specialKeys;
1813 - (void)handleInsertText:(NSString *)text
1815     if (!text) return;
1817     char_u *str = (char_u*)[text UTF8String];
1818     int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1820 #ifdef FEAT_MBYTE
1821     char_u *conv_str = NULL;
1822     if (input_conv.vc_type != CONV_NONE) {
1823         conv_str = string_convert(&input_conv, str, &len);
1824         if (conv_str)
1825             str = conv_str;
1826     }
1827 #endif
1829     for (i = 0; i < len; ++i) {
1830         add_to_input_buf(str+i, 1);
1831         if (CSI == str[i]) {
1832             // NOTE: If the converted string contains the byte CSI, then it
1833             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1834             // won't work.
1835             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1836             add_to_input_buf(extra, 2);
1837         }
1838     }
1840 #ifdef FEAT_MBYTE
1841     if (conv_str)
1842         vim_free(conv_str);
1843 #endif
1846 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1848     // TODO: This code is a horrible mess -- clean up!
1849     char_u special[3];
1850     char_u modChars[3];
1851     char_u *chars = (char_u*)[key UTF8String];
1852 #ifdef FEAT_MBYTE
1853     char_u *conv_str = NULL;
1854 #endif
1855     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1857     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1858     // that new keys can easily be added.
1859     NSString *specialString = [[MMBackend specialKeys]
1860             objectForKey:key];
1861     if (specialString && [specialString length] > 1) {
1862         //NSLog(@"special key: %@", specialString);
1863         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1864                 [specialString characterAtIndex:1]);
1866         ikey = simplify_key(ikey, &mods);
1867         if (ikey == CSI)
1868             ikey = K_CSI;
1870         special[0] = CSI;
1871         special[1] = K_SECOND(ikey);
1872         special[2] = K_THIRD(ikey);
1874         chars = special;
1875         length = 3;
1876     } else if (1 == length && TAB == chars[0]) {
1877         // Tab is a trouble child:
1878         // - <Tab> is added to the input buffer as is
1879         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1880         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1881         //   to be converted to utf-8
1882         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1883         // - <C-Tab> is reserved by Mac OS X
1884         // - <D-Tab> is reserved by Mac OS X
1885         chars = special;
1886         special[0] = TAB;
1887         length = 1;
1889         if (mods & MOD_MASK_SHIFT) {
1890             mods &= ~MOD_MASK_SHIFT;
1891             special[0] = CSI;
1892             special[1] = K_SECOND(K_S_TAB);
1893             special[2] = K_THIRD(K_S_TAB);
1894             length = 3;
1895         } else if (mods & MOD_MASK_ALT) {
1896             int mtab = 0x80 | TAB;
1897 #ifdef FEAT_MBYTE
1898             if (enc_utf8) {
1899                 // Convert to utf-8
1900                 special[0] = (mtab >> 6) + 0xc0;
1901                 special[1] = mtab & 0xbf;
1902                 length = 2;
1903             } else
1904 #endif
1905             {
1906                 special[0] = mtab;
1907                 length = 1;
1908             }
1909             mods &= ~MOD_MASK_ALT;
1910         }
1911     } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1912         // META key is treated separately.  This code was taken from gui_w48.c
1913         // and gui_gtk_x11.c.
1914         char_u string[7];
1915         int ch = simplify_key(chars[0], &mods);
1917         // Remove the SHIFT modifier for keys where it's already included,
1918         // e.g., '(' and '*'
1919         if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1920             mods &= ~MOD_MASK_SHIFT;
1922         // Interpret the ALT key as making the key META, include SHIFT, etc.
1923         ch = extract_modifiers(ch, &mods);
1924         if (ch == CSI)
1925             ch = K_CSI;
1927         int len = 0;
1928         if (mods) {
1929             string[len++] = CSI;
1930             string[len++] = KS_MODIFIER;
1931             string[len++] = mods;
1932         }
1934         if (IS_SPECIAL(ch)) {
1935             string[len++] = CSI;
1936             string[len++] = K_SECOND(ch);
1937             string[len++] = K_THIRD(ch);
1938         } else {
1939             string[len++] = ch;
1940 #ifdef FEAT_MBYTE
1941             // TODO: What if 'enc' is not "utf-8"?
1942             if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1943                 string[len++] = ch & 0xbf;
1944                 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1945                 if (string[len-1] == CSI) {
1946                     string[len++] = KS_EXTRA;
1947                     string[len++] = (int)KE_CSI;
1948                 }
1949             }
1950 #endif
1951         }
1953         add_to_input_buf(string, len);
1954         return;
1955     } else if (length > 0) {
1956         unichar c = [key characterAtIndex:0];
1957         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1958         //        [key characterAtIndex:0], mods);
1960         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1961         // cleared since they are already added to the key by the AppKit.
1962         // Unfortunately, the only way to deal with when to clear the modifiers
1963         // or not seems to be to have hard-wired rules like this.
1964         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1965                     || 0x9 == c || 0xd == c || ESC == c) ) {
1966             mods &= ~MOD_MASK_SHIFT;
1967             mods &= ~MOD_MASK_CTRL;
1968             //NSLog(@"clear shift ctrl");
1969         }
1971 #ifdef FEAT_MBYTE
1972         if (input_conv.vc_type != CONV_NONE) {
1973             conv_str = string_convert(&input_conv, chars, &length);
1974             if (conv_str)
1975                 chars = conv_str;
1976         }
1977 #endif
1978     }
1980     if (chars && length > 0) {
1981         if (mods) {
1982             //NSLog(@"adding mods: %d", mods);
1983             modChars[0] = CSI;
1984             modChars[1] = KS_MODIFIER;
1985             modChars[2] = mods;
1986             add_to_input_buf(modChars, 3);
1987         }
1989         //NSLog(@"add to input buf: 0x%x", chars[0]);
1990         // TODO: Check for CSI bytes?
1991         add_to_input_buf(chars, length);
1992     }
1994 #ifdef FEAT_MBYTE
1995     if (conv_str)
1996         vim_free(conv_str);
1997 #endif
2000 - (void)queueMessage:(int)msgid data:(NSData *)data
2002     //if (msgid != EnableMenuItemMsgID)
2003     //    NSLog(@"queueMessage:%s", MessageStrings[msgid]);
2005     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
2006     if (data)
2007         [outputQueue addObject:data];
2008     else
2009         [outputQueue addObject:[NSData data]];
2012 - (void)connectionDidDie:(NSNotification *)notification
2014     // If the main connection to MacVim is lost this means that either MacVim
2015     // has crashed or this process did not receive its termination message
2016     // properly (e.g. if the TerminateNowMsgID was dropped).
2017     //
2018     // NOTE: This is not called if a Vim controller invalidates its connection.
2020     NSLog(@"WARNING[%s]: Main connection was lost before process had a chance "
2021             "to terminate; preserving swap files.", _cmd);
2022     getout_preserve_modified(1);
2025 - (void)blinkTimerFired:(NSTimer *)timer
2027     NSTimeInterval timeInterval = 0;
2029     [blinkTimer release];
2030     blinkTimer = nil;
2032     if (MMBlinkStateOn == blinkState) {
2033         gui_undraw_cursor();
2034         blinkState = MMBlinkStateOff;
2035         timeInterval = blinkOffInterval;
2036     } else if (MMBlinkStateOff == blinkState) {
2037         gui_update_cursor(TRUE, FALSE);
2038         blinkState = MMBlinkStateOn;
2039         timeInterval = blinkOnInterval;
2040     }
2042     if (timeInterval > 0) {
2043         blinkTimer = 
2044             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2045                                             selector:@selector(blinkTimerFired:)
2046                                             userInfo:nil repeats:NO] retain];
2047         [self flushQueue:YES];
2048     }
2051 - (void)focusChange:(BOOL)on
2053     gui_focus_change(on);
2056 - (void)handleToggleToolbar
2058     // If 'go' contains 'T', then remove it, else add it.
2060     char_u go[sizeof(GO_ALL)+2];
2061     char_u *p;
2062     int len;
2064     STRCPY(go, p_go);
2065     p = vim_strchr(go, GO_TOOLBAR);
2066     len = STRLEN(go);
2068     if (p != NULL) {
2069         char_u *end = go + len;
2070         while (p < end) {
2071             p[0] = p[1];
2072             ++p;
2073         }
2074     } else {
2075         go[len] = GO_TOOLBAR;
2076         go[len+1] = NUL;
2077     }
2079     set_option_value((char_u*)"guioptions", 0, go, 0);
2081     [self redrawScreen];
2084 - (void)handleScrollbarEvent:(NSData *)data
2086     if (!data) return;
2088     const void *bytes = [data bytes];
2089     long ident = *((long*)bytes);  bytes += sizeof(long);
2090     int hitPart = *((int*)bytes);  bytes += sizeof(int);
2091     float fval = *((float*)bytes);  bytes += sizeof(float);
2092     scrollbar_T *sb = gui_find_scrollbar(ident);
2094     if (sb) {
2095         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2096         long value = sb_info->value;
2097         long size = sb_info->size;
2098         long max = sb_info->max;
2099         BOOL isStillDragging = NO;
2100         BOOL updateKnob = YES;
2102         switch (hitPart) {
2103         case NSScrollerDecrementPage:
2104             value -= (size > 2 ? size - 2 : 1);
2105             break;
2106         case NSScrollerIncrementPage:
2107             value += (size > 2 ? size - 2 : 1);
2108             break;
2109         case NSScrollerDecrementLine:
2110             --value;
2111             break;
2112         case NSScrollerIncrementLine:
2113             ++value;
2114             break;
2115         case NSScrollerKnob:
2116             isStillDragging = YES;
2117             // fall through ...
2118         case NSScrollerKnobSlot:
2119             value = (long)(fval * (max - size + 1));
2120             // fall through ...
2121         default:
2122             updateKnob = NO;
2123             break;
2124         }
2126         //NSLog(@"value %d -> %d", sb_info->value, value);
2127         gui_drag_scrollbar(sb, value, isStillDragging);
2129         if (updateKnob) {
2130             // Dragging the knob or option+clicking automatically updates
2131             // the knob position (on the actual NSScroller), so we only
2132             // need to set the knob position in the other cases.
2133             if (sb->wp) {
2134                 // Update both the left&right vertical scrollbars.
2135                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2136                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2137                 [self setScrollbarThumbValue:value size:size max:max
2138                                   identifier:identLeft];
2139                 [self setScrollbarThumbValue:value size:size max:max
2140                                   identifier:identRight];
2141             } else {
2142                 // Update the horizontal scrollbar.
2143                 [self setScrollbarThumbValue:value size:size max:max
2144                                   identifier:ident];
2145             }
2146         }
2147     }
2150 - (void)handleSetFont:(NSData *)data
2152     if (!data) return;
2154     const void *bytes = [data bytes];
2155     float pointSize = *((float*)bytes);  bytes += sizeof(float);
2156     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2157     bytes += sizeof(unsigned);  // len not used
2159     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2160     [name appendString:[NSString stringWithFormat:@":h%d", (int)pointSize]];
2161     char_u *s = (char_u*)[name UTF8String];
2163 #ifdef FEAT_MBYTE
2164     s = CONVERT_FROM_UTF8(s);
2165 #endif
2167     set_option_value((char_u*)"guifont", 0, s, 0);
2169 #ifdef FEAT_MBYTE
2170     CONVERT_FROM_UTF8_FREE(s);
2171 #endif
2173     [self redrawScreen];
2176 - (void)handleDropFiles:(NSData *)data
2178     // TODO: Get rid of this method; instead use Vim script directly.  At the
2179     // moment I know how to do this to open files in tabs, but I'm not sure how
2180     // to add the filenames to the command line when in command line mode.
2182     if (!data) return;
2184     NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2185     if (!args) return;
2187     id obj = [args objectForKey:@"forceOpen"];
2188     BOOL forceOpen = YES;
2189     if (obj)
2190         forceOpen = [obj boolValue];
2192     NSArray *filenames = [args objectForKey:@"filenames"];
2193     if (!(filenames && [filenames count] > 0)) return;
2195 #ifdef FEAT_DND
2196     if (!forceOpen && (State & CMDLINE)) {
2197         // HACK!  If Vim is in command line mode then the files names
2198         // should be added to the command line, instead of opening the
2199         // files in tabs (unless forceOpen is set).  This is taken care of by
2200         // gui_handle_drop().
2201         int n = [filenames count];
2202         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2203         if (fnames) {
2204             int i = 0;
2205             for (i = 0; i < n; ++i)
2206                 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2208             // NOTE!  This function will free 'fnames'.
2209             // HACK!  It is assumed that the 'x' and 'y' arguments are
2210             // unused when in command line mode.
2211             gui_handle_drop(0, 0, 0, fnames, n);
2212         }
2213     } else
2214 #endif // FEAT_DND
2215     {
2216         [self handleOpenWithArguments:args];
2217     }
2220 - (void)handleDropString:(NSData *)data
2222     if (!data) return;
2224 #ifdef FEAT_DND
2225     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2226     const void *bytes = [data bytes];
2227     int len = *((int*)bytes);  bytes += sizeof(int);
2228     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2230     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2231     NSRange range = { 0, [string length] };
2232     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2233                                          withString:@"\x0a" options:0
2234                                               range:range];
2235     if (0 == n) {
2236         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2237                                        options:0 range:range];
2238     }
2240     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2241     char_u *s = (char_u*)[string UTF8String];
2242 #ifdef FEAT_MBYTE
2243     if (input_conv.vc_type != CONV_NONE)
2244         s = string_convert(&input_conv, s, &len);
2245 #endif
2246     dnd_yank_drag_data(s, len);
2247 #ifdef FEAT_MBYTE
2248     if (input_conv.vc_type != CONV_NONE)
2249         vim_free(s);
2250 #endif
2251     add_to_input_buf(dropkey, sizeof(dropkey));
2252 #endif // FEAT_DND
2255 - (void)startOdbEditWithArguments:(NSDictionary *)args
2257 #ifdef FEAT_ODB_EDITOR
2258     id obj = [args objectForKey:@"remoteID"];
2259     if (!obj) return;
2261     OSType serverID = [obj unsignedIntValue];
2262     NSString *remotePath = [args objectForKey:@"remotePath"];
2264     NSAppleEventDescriptor *token = nil;
2265     NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2266     obj = [args objectForKey:@"remoteTokenDescType"];
2267     if (tokenData && obj) {
2268         DescType tokenType = [obj unsignedLongValue];
2269         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2270                                                                 data:tokenData];
2271     }
2273     NSArray *filenames = [args objectForKey:@"filenames"];
2274     unsigned i, numFiles = [filenames count];
2275     for (i = 0; i < numFiles; ++i) {
2276         NSString *filename = [filenames objectAtIndex:i];
2277         char_u *s = [filename vimStringSave];
2278         buf_T *buf = buflist_findname(s);
2279         vim_free(s);
2281         if (buf) {
2282             if (buf->b_odb_token) {
2283                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2284                 buf->b_odb_token = NULL;
2285             }
2287             if (buf->b_odb_fname) {
2288                 vim_free(buf->b_odb_fname);
2289                 buf->b_odb_fname = NULL;
2290             }
2292             buf->b_odb_server_id = serverID;
2294             if (token)
2295                 buf->b_odb_token = [token retain];
2296             if (remotePath)
2297                 buf->b_odb_fname = [remotePath vimStringSave];
2298         } else {
2299             NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2300                     filename);
2301         }
2302     }
2303 #endif // FEAT_ODB_EDITOR
2306 - (void)handleXcodeMod:(NSData *)data
2308 #if 0
2309     const void *bytes = [data bytes];
2310     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2311     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2312     if (0 == len)
2313         return;
2315     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2316             descriptorWithDescriptorType:type
2317                                    bytes:bytes
2318                                   length:len];
2319 #endif
2322 - (void)handleOpenWithArguments:(NSDictionary *)args
2324     // ARGUMENT:                DESCRIPTION:
2325     // -------------------------------------------------------------
2326     // filenames                list of filenames
2327     // dontOpen                 don't open files specified in above argument
2328     // layout                   which layout to use to open files
2329     // selectionRange           range of lines to select
2330     // searchText               string to search for
2331     // cursorLine               line to position the cursor on
2332     // cursorColumn             column to position the cursor on
2333     //                          (only valid when "cursorLine" is set)
2334     // remoteID                 ODB parameter
2335     // remotePath               ODB parameter
2336     // remoteTokenDescType      ODB parameter
2337     // remoteTokenData          ODB parameter
2339     //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2341     NSArray *filenames = [args objectForKey:@"filenames"];
2342     int i, numFiles = filenames ? [filenames count] : 0;
2343     BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2344     int layout = [[args objectForKey:@"layout"] intValue];
2346     // Change to directory of first file to open if this is an "unused" editor
2347     // (but do not do this if editing remotely).
2348     if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2349             && (starting || [self unusedEditor]) ) {
2350         char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2351         vim_chdirfile(s);
2352         vim_free(s);
2353     }
2355     if (starting > 0) {
2356         // When Vim is starting we simply add the files to be opened to the
2357         // global arglist and Vim will take care of opening them for us.
2358         if (openFiles && numFiles > 0) {
2359             for (i = 0; i < numFiles; i++) {
2360                 NSString *fname = [filenames objectAtIndex:i];
2361                 char_u *p = NULL;
2363                 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2364                         || (p = [fname vimStringSave]) == NULL)
2365                     exit(2); // See comment in -[MMBackend exit]
2366                 else
2367                     alist_add(&global_alist, p, 2);
2368             }
2370             // Vim will take care of arranging the files added to the arglist
2371             // in windows or tabs; all we must do is to specify which layout to
2372             // use.
2373             initialWindowLayout = layout;
2374         }
2375     } else {
2376         // When Vim is already open we resort to some trickery to open the
2377         // files with the specified layout.
2378         //
2379         // TODO: Figure out a better way to handle this?
2380         if (openFiles && numFiles > 0) {
2381             BOOL oneWindowInTab = topframe ? YES
2382                                            : (topframe->fr_layout == FR_LEAF);
2383             BOOL bufChanged = NO;
2384             BOOL bufHasFilename = NO;
2385             if (curbuf) {
2386                 bufChanged = curbufIsChanged();
2387                 bufHasFilename = curbuf->b_ffname != NULL;
2388             }
2390             // Temporarily disable flushing since the following code may
2391             // potentially cause multiple redraws.
2392             flushDisabled = YES;
2394             BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2395             if (WIN_TABS == layout && !onlyOneTab) {
2396                 // By going to the last tabpage we ensure that the new tabs
2397                 // will appear last (if this call is left out, the taborder
2398                 // becomes messy).
2399                 goto_tabpage(9999);
2400             }
2402             // Make sure we're in normal mode first.
2403             [self addInput:@"<C-\\><C-N>"];
2405             if (numFiles > 1) {
2406                 // With "split layout" we open a new tab before opening
2407                 // multiple files if the current tab has more than one window
2408                 // or if there is exactly one window but whose buffer has a
2409                 // filename.  (The :drop command ensures modified buffers get
2410                 // their own window.)
2411                 if ((WIN_HOR == layout || WIN_VER == layout) &&
2412                         (!oneWindowInTab || bufHasFilename))
2413                     [self addInput:@":tabnew<CR>"];
2415                 // The files are opened by constructing a ":drop ..." command
2416                 // and executing it.
2417                 NSMutableString *cmd = (WIN_TABS == layout)
2418                         ? [NSMutableString stringWithString:@":tab drop"]
2419                         : [NSMutableString stringWithString:@":drop"];
2421                 for (i = 0; i < numFiles; ++i) {
2422                     NSString *file = [filenames objectAtIndex:i];
2423                     file = [file stringByEscapingSpecialFilenameCharacters];
2424                     [cmd appendString:@" "];
2425                     [cmd appendString:file];
2426                 }
2428                 // Temporarily clear 'suffixes' so that the files are opened in
2429                 // the same order as they appear in the "filenames" array.
2430                 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2432                 [self addInput:cmd];
2434                 // Split the view into multiple windows if requested.
2435                 if (WIN_HOR == layout)
2436                     [self addInput:@"|sall"];
2437                 else if (WIN_VER == layout)
2438                     [self addInput:@"|vert sall"];
2440                 // Restore the old value of 'suffixes'.
2441                 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2442             } else {
2443                 // When opening one file we try to reuse the current window,
2444                 // but not if its buffer is modified or has a filename.
2445                 // However, the 'arglist' layout always opens the file in the
2446                 // current window.
2447                 NSString *file = [[filenames lastObject]
2448                         stringByEscapingSpecialFilenameCharacters];
2449                 NSString *cmd;
2450                 if (WIN_HOR == layout) {
2451                     if (!(bufHasFilename || bufChanged))
2452                         cmd = [NSString stringWithFormat:@":e %@", file];
2453                     else
2454                         cmd = [NSString stringWithFormat:@":sp %@", file];
2455                 } else if (WIN_VER == layout) {
2456                     if (!(bufHasFilename || bufChanged))
2457                         cmd = [NSString stringWithFormat:@":e %@", file];
2458                     else
2459                         cmd = [NSString stringWithFormat:@":vsp %@", file];
2460                 } else if (WIN_TABS == layout) {
2461                     if (oneWindowInTab && !(bufHasFilename || bufChanged))
2462                         cmd = [NSString stringWithFormat:@":e %@", file];
2463                     else
2464                         cmd = [NSString stringWithFormat:@":tabe %@", file];
2465                 } else {
2466                     // (The :drop command will split if there is a modified
2467                     // buffer.)
2468                     cmd = [NSString stringWithFormat:@":drop %@", file];
2469                 }
2471                 [self addInput:cmd];
2472                 [self addInput:@"<CR>"];
2473             }
2475             // Force screen redraw (does it have to be this complicated?).
2476             // (This code was taken from the end of gui_handle_drop().)
2477             update_screen(NOT_VALID);
2478             setcursor();
2479             out_flush();
2480             gui_update_cursor(FALSE, FALSE);
2481             maketitle();
2483             flushDisabled = NO;
2484         }
2485     }
2487     if ([args objectForKey:@"remoteID"]) {
2488         // NOTE: We have to delay processing any ODB related arguments since
2489         // the file(s) may not be opened until the input buffer is processed.
2490         [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2491                                withObject:args
2492                             waitUntilDone:NO];
2493     }
2495     NSString *lineString = [args objectForKey:@"cursorLine"];
2496     if (lineString && [lineString intValue] > 0) {
2497         NSString *columnString = [args objectForKey:@"cursorColumn"];
2498         if (!(columnString && [columnString intValue] > 0))
2499             columnString = @"1";
2501         NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2502                 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2503         [self addInput:cmd];
2504     }
2506     NSString *rangeString = [args objectForKey:@"selectionRange"];
2507     if (rangeString) {
2508         // Build a command line string that will select the given range of
2509         // lines.  If range.length == 0, then position the cursor on the given
2510         // line but do not select.
2511         NSRange range = NSRangeFromString(rangeString);
2512         NSString *cmd;
2513         if (range.length > 0) {
2514             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2515                     NSMaxRange(range), range.location];
2516         } else {
2517             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2518                     range.location];
2519         }
2521         [self addInput:cmd];
2522     }
2524     NSString *searchText = [args objectForKey:@"searchText"];
2525     if (searchText) {
2526         [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2527                 searchText]];
2528     }
2531 - (BOOL)checkForModifiedBuffers
2533     buf_T *buf;
2534     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2535         if (bufIsChanged(buf)) {
2536             return YES;
2537         }
2538     }
2540     return NO;
2543 - (void)addInput:(NSString *)input
2545     // NOTE: This code is essentially identical to server_to_input_buf(),
2546     // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2547     char_u *string = [input vimStringSave];
2548     if (!string) return;
2550     /* Set 'cpoptions' the way we want it.
2551      *    B set - backslashes are *not* treated specially
2552      *    k set - keycodes are *not* reverse-engineered
2553      *    < unset - <Key> sequences *are* interpreted
2554      *  The last but one parameter of replace_termcodes() is TRUE so that the
2555      *  <lt> sequence is recognised - needed for a real backslash.
2556      */
2557     char_u *ptr = NULL;
2558     char_u *cpo_save = p_cpo;
2559     p_cpo = (char_u *)"Bk";
2560     char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2561     p_cpo = cpo_save;
2563     if (*ptr != NUL)    /* trailing CTRL-V results in nothing */
2564     {
2565         /*
2566          * Add the string to the input stream.
2567          * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2568          *
2569          * First clear typed characters from the typeahead buffer, there could
2570          * be half a mapping there.  Then append to the existing string, so
2571          * that multiple commands from a client are concatenated.
2572          */
2573         if (typebuf.tb_maplen < typebuf.tb_len)
2574             del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2575         (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2577         /* Let input_available() know we inserted text in the typeahead
2578          * buffer. */
2579         typebuf_was_filled = TRUE;
2580     }
2581     vim_free(ptr);
2582     vim_free(string);
2585 - (BOOL)unusedEditor
2587     BOOL oneWindowInTab = topframe ? YES
2588                                    : (topframe->fr_layout == FR_LEAF);
2589     BOOL bufChanged = NO;
2590     BOOL bufHasFilename = NO;
2591     if (curbuf) {
2592         bufChanged = curbufIsChanged();
2593         bufHasFilename = curbuf->b_ffname != NULL;
2594     }
2596     BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2598     return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2601 - (void)redrawScreen
2603     // Force screen redraw (does it have to be this complicated?).
2604     redraw_all_later(CLEAR);
2605     update_screen(NOT_VALID);
2606     setcursor();
2607     out_flush();
2608     gui_update_cursor(FALSE, FALSE);
2610     // HACK! The cursor is not put back at the command line by the above
2611     // "redraw commands".  The following test seems to do the trick though.
2612     if (State & CMDLINE)
2613         redrawcmdline();
2616 - (void)handleFindReplace:(NSDictionary *)args
2618     if (!args) return;
2620     NSString *findString = [args objectForKey:@"find"];
2621     if (!findString) return;
2623     char_u *find = [findString vimStringSave];
2624     char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2625     int flags = [[args objectForKey:@"flags"] intValue];
2627     // NOTE: The flag 0x100 is used to indicate a backward search.
2628     gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2630     vim_free(find);
2631     vim_free(replace);
2634 @end // MMBackend (Private)
2639 @implementation MMBackend (ClientServer)
2641 - (NSString *)connectionNameFromServerName:(NSString *)name
2643     NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2645     return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2646         lowercaseString];
2649 - (NSConnection *)connectionForServerName:(NSString *)name
2651     // TODO: Try 'name%d' if 'name' fails.
2652     NSString *connName = [self connectionNameFromServerName:name];
2653     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2655     if (!svrConn) {
2656         svrConn = [NSConnection connectionWithRegisteredName:connName
2657                                                            host:nil];
2658         // Try alternate server...
2659         if (!svrConn && alternateServerName) {
2660             //NSLog(@"  trying to connect to alternate server: %@",
2661             //        alternateServerName);
2662             connName = [self connectionNameFromServerName:alternateServerName];
2663             svrConn = [NSConnection connectionWithRegisteredName:connName
2664                                                             host:nil];
2665         }
2667         // Try looking for alternate servers...
2668         if (!svrConn) {
2669             //NSLog(@"  looking for alternate servers...");
2670             NSString *alt = [self alternateServerNameForName:name];
2671             if (alt != alternateServerName) {
2672                 //NSLog(@"  found alternate server: %@", string);
2673                 [alternateServerName release];
2674                 alternateServerName = [alt copy];
2675             }
2676         }
2678         // Try alternate server again...
2679         if (!svrConn && alternateServerName) {
2680             //NSLog(@"  trying to connect to alternate server: %@",
2681             //        alternateServerName);
2682             connName = [self connectionNameFromServerName:alternateServerName];
2683             svrConn = [NSConnection connectionWithRegisteredName:connName
2684                                                             host:nil];
2685         }
2687         if (svrConn) {
2688             [connectionNameDict setObject:svrConn forKey:connName];
2690             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2691             [[NSNotificationCenter defaultCenter] addObserver:self
2692                     selector:@selector(serverConnectionDidDie:)
2693                         name:NSConnectionDidDieNotification object:svrConn];
2694         }
2695     }
2697     return svrConn;
2700 - (NSConnection *)connectionForServerPort:(int)port
2702     NSConnection *conn;
2703     NSEnumerator *e = [connectionNameDict objectEnumerator];
2705     while ((conn = [e nextObject])) {
2706         // HACK! Assume connection uses mach ports.
2707         if (port == [(NSMachPort*)[conn sendPort] machPort])
2708             return conn;
2709     }
2711     return nil;
2714 - (void)serverConnectionDidDie:(NSNotification *)notification
2716     //NSLog(@"%s%@", _cmd, notification);
2718     NSConnection *svrConn = [notification object];
2720     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2721     [[NSNotificationCenter defaultCenter]
2722             removeObserver:self
2723                       name:NSConnectionDidDieNotification
2724                     object:svrConn];
2726     [connectionNameDict removeObjectsForKeys:
2727         [connectionNameDict allKeysForObject:svrConn]];
2729     // HACK! Assume connection uses mach ports.
2730     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2731     NSNumber *key = [NSNumber numberWithInt:port];
2733     [clientProxyDict removeObjectForKey:key];
2734     [serverReplyDict removeObjectForKey:key];
2737 - (void)addClient:(NSDistantObject *)client
2739     NSConnection *conn = [client connectionForProxy];
2740     // HACK! Assume connection uses mach ports.
2741     int port = [(NSMachPort*)[conn sendPort] machPort];
2742     NSNumber *key = [NSNumber numberWithInt:port];
2744     if (![clientProxyDict objectForKey:key]) {
2745         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2746         [clientProxyDict setObject:client forKey:key];
2747     }
2749     // NOTE: 'clientWindow' is a global variable which is used by <client>
2750     clientWindow = port;
2753 - (NSString *)alternateServerNameForName:(NSString *)name
2755     if (!(name && [name length] > 0))
2756         return nil;
2758     // Only look for alternates if 'name' doesn't end in a digit.
2759     unichar lastChar = [name characterAtIndex:[name length]-1];
2760     if (lastChar >= '0' && lastChar <= '9')
2761         return nil;
2763     // Look for alternates among all current servers.
2764     NSArray *list = [self serverList];
2765     if (!(list && [list count] > 0))
2766         return nil;
2768     // Filter out servers starting with 'name' and ending with a number. The
2769     // (?i) pattern ensures that the match is case insensitive.
2770     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2771     NSPredicate *pred = [NSPredicate predicateWithFormat:
2772             @"SELF MATCHES %@", pat];
2773     list = [list filteredArrayUsingPredicate:pred];
2774     if ([list count] > 0) {
2775         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2776         return [list objectAtIndex:0];
2777     }
2779     return nil;
2782 @end // MMBackend (ClientServer)
2787 @implementation NSString (MMServerNameCompare)
2788 - (NSComparisonResult)serverNameCompare:(NSString *)string
2790     return [self compare:string
2791                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2793 @end
2798 static int eventModifierFlagsToVimModMask(int modifierFlags)
2800     int modMask = 0;
2802     if (modifierFlags & NSShiftKeyMask)
2803         modMask |= MOD_MASK_SHIFT;
2804     if (modifierFlags & NSControlKeyMask)
2805         modMask |= MOD_MASK_CTRL;
2806     if (modifierFlags & NSAlternateKeyMask)
2807         modMask |= MOD_MASK_ALT;
2808     if (modifierFlags & NSCommandKeyMask)
2809         modMask |= MOD_MASK_CMD;
2811     return modMask;
2814 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2816     int modMask = 0;
2818     if (modifierFlags & NSShiftKeyMask)
2819         modMask |= MOUSE_SHIFT;
2820     if (modifierFlags & NSControlKeyMask)
2821         modMask |= MOUSE_CTRL;
2822     if (modifierFlags & NSAlternateKeyMask)
2823         modMask |= MOUSE_ALT;
2825     return modMask;
2828 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2830     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2832     return (buttonNumber >= 0 && buttonNumber < 3)
2833             ? mouseButton[buttonNumber] : -1;
2838 // This function is modeled after the VimToPython function found in if_python.c
2839 // NB This does a deep copy by value, it does not lookup references like the
2840 // VimToPython function does.  This is because I didn't want to deal with the
2841 // retain cycles that this would create, and we can cover 99% of the use cases
2842 // by ignoring it.  If we ever switch to using GC in MacVim then this
2843 // functionality can be implemented easily.
2844 static id vimToCocoa(typval_T * tv, int depth)
2846     id result = nil;
2847     id newObj = nil;
2850     // Avoid infinite recursion
2851     if (depth > 100) {
2852         return nil;
2853     }
2855     if (tv->v_type == VAR_STRING) {
2856         char_u * val = tv->vval.v_string;
2857         // val can be NULL if the string is empty
2858         if (!val) {
2859             result = [NSString string];
2860         } else {
2861 #ifdef FEAT_MBYTE
2862             val = CONVERT_TO_UTF8(val);
2863 #endif
2864             result = [NSString stringWithUTF8String:(char*)val];
2865 #ifdef FEAT_MBYTE
2866             CONVERT_TO_UTF8_FREE(val);
2867 #endif
2868         }
2869     } else if (tv->v_type == VAR_NUMBER) {
2870         // looks like sizeof(varnumber_T) is always <= sizeof(long)
2871         result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2872     } else if (tv->v_type == VAR_LIST) {
2873         list_T * list = tv->vval.v_list;
2874         listitem_T * curr;
2876         NSMutableArray * arr = result = [NSMutableArray array];
2878         if (list != NULL) {
2879             for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2880                 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2881                 [arr addObject:newObj];
2882             }
2883         }
2884     } else if (tv->v_type == VAR_DICT) {
2885         NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2887         if (tv->vval.v_dict != NULL) {
2888             hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2889             int todo = ht->ht_used;
2890             hashitem_T * hi;
2891             dictitem_T * di;
2893             for (hi = ht->ht_array; todo > 0; ++hi) {
2894                 if (!HASHITEM_EMPTY(hi)) {
2895                     --todo;
2897                     di = dict_lookup(hi);
2898                     newObj = vimToCocoa(&di->di_tv, depth + 1);
2900                     char_u * keyval = hi->hi_key;
2901 #ifdef FEAT_MBYTE
2902                     keyval = CONVERT_TO_UTF8(keyval);
2903 #endif
2904                     NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2905 #ifdef FEAT_MBYTE
2906                     CONVERT_TO_UTF8_FREE(keyval);
2907 #endif
2908                     [dict setObject:newObj forKey:key];
2909                 }
2910             }
2911         }
2912     } else { // only func refs should fall into this category?
2913         result = nil;
2914     }
2916     return result;
2920 // This function is modeled after eval_client_expr_to_string found in main.c
2921 // Returns nil if there was an error evaluating the expression, and writes a
2922 // message to errorStr.
2923 // TODO Get the error that occurred while evaluating the expression in vim
2924 // somehow.
2925 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2928     char_u *s = (char_u*)[expr UTF8String];
2930 #ifdef FEAT_MBYTE
2931     s = CONVERT_FROM_UTF8(s);
2932 #endif
2934     int save_dbl = debug_break_level;
2935     int save_ro = redir_off;
2937     debug_break_level = -1;
2938     redir_off = 0;
2939     ++emsg_skip;
2941     typval_T * tvres = eval_expr(s, NULL);
2943     debug_break_level = save_dbl;
2944     redir_off = save_ro;
2945     --emsg_skip;
2947     setcursor();
2948     out_flush();
2950 #ifdef FEAT_MBYTE
2951     CONVERT_FROM_UTF8_FREE(s);
2952 #endif
2954 #ifdef FEAT_GUI
2955     if (gui.in_use)
2956         gui_update_cursor(FALSE, FALSE);
2957 #endif
2959     if (tvres == NULL) {
2960         free_tv(tvres);
2961         *errstr = @"Expression evaluation failed.";
2962     }
2964     id res = vimToCocoa(tvres, 1);
2966     free_tv(tvres);
2968     if (res == nil) {
2969         *errstr = @"Conversion to cocoa values failed.";
2970     }
2972     return res;
2977 @implementation NSString (VimStrings)
2979 + (id)stringWithVimString:(char_u *)s
2981     // This method ensures a non-nil string is returned.  If 's' cannot be
2982     // converted to a utf-8 string it is assumed to be latin-1.  If conversion
2983     // still fails an empty NSString is returned.
2984     NSString *string = nil;
2985     if (s) {
2986 #ifdef FEAT_MBYTE
2987         s = CONVERT_TO_UTF8(s);
2988 #endif
2989         string = [NSString stringWithUTF8String:(char*)s];
2990         if (!string) {
2991             // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2992             // latin-1?
2993             string = [NSString stringWithCString:(char*)s
2994                                         encoding:NSISOLatin1StringEncoding];
2995         }
2996 #ifdef FEAT_MBYTE
2997         CONVERT_TO_UTF8_FREE(s);
2998 #endif
2999     }
3001     return string != nil ? string : [NSString string];
3004 - (char_u *)vimStringSave
3006     char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3008 #ifdef FEAT_MBYTE
3009     s = CONVERT_FROM_UTF8(s);
3010 #endif
3011     ret = vim_strsave(s);
3012 #ifdef FEAT_MBYTE
3013     CONVERT_FROM_UTF8_FREE(s);
3014 #endif
3016     return ret;
3019 @end // NSString (VimStrings)