Deprecate "redr|f" in addInput: calls
[MacVim.git] / src / MacVim / MMBackend.m
blob97d4ddcfbe8aaae55d295ac3eba8b005106bed91
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
11  * MMBackend
12  *
13  * MMBackend communicates with the frontend (MacVim).  It maintains a queue of
14  * output which is flushed to the frontend under controlled circumstances (so
15  * as to maintain a steady framerate).  Input from the frontend is also handled
16  * here.
17  *
18  * The frontend communicates with the backend via the MMBackendProtocol.  In
19  * particular, input is sent to the backend via processInput:data: and Vim
20  * state can be queried from the frontend with evaluateExpression:.
21  *
22  * It is very important to realize that all state is held by the backend, the
23  * frontend must either ask for state [MMBackend evaluateExpression:] or wait
24  * for the backend to update [MMVimController processCommandQueue:].
25  *
26  * The client/server functionality of Vim is handled by the backend.  It sets
27  * up a named NSConnection to which other Vim processes can connect.
28  */
30 #import "MMBackend.h"
34 // NOTE: Colors in MMBackend are stored as unsigned ints on the form 0xaarrggbb
35 // whereas colors in Vim are int without the alpha component.  Also note that
36 // 'transp' is assumed to be a value between 0 and 100.
37 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
38 #define MM_COLOR_WITH_TRANSP(col,transp) \
39     ((unsigned)( ((col)&0xffffff) \
40         | ((((unsigned)((((100-(transp))*255)/100)+.5f))&0xff)<<24) ))
42 // Values for window layout (must match values in main.c).
43 #define WIN_HOR     1       // "-o" horizontally split windows
44 #define WIN_VER     2       // "-O" vertically split windows
45 #define WIN_TABS    3       // "-p" windows on tab pages
47 static unsigned MMServerMax = 1000;
49 // TODO: Move to separate file.
50 static int eventModifierFlagsToVimModMask(int modifierFlags);
51 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
52 static int eventButtonNumberToVimMouseButton(int buttonNumber);
54 // Before exiting process, sleep for this many microseconds.  This is to allow
55 // any distributed object messages in transit to be received by MacVim before
56 // the process dies (otherwise an error message is logged by Cocoa).  Note that
57 // this delay is only necessary if an NSConnection to MacVim has been
58 // established.
59 static useconds_t MMExitProcessDelay = 300000;
61 // In gui_macvim.m
62 vimmenu_T *menu_for_descriptor(NSArray *desc);
64 static id evalExprCocoa(NSString * expr, NSString ** errstr);
67 enum {
68     MMBlinkStateNone = 0,
69     MMBlinkStateOn,
70     MMBlinkStateOff
73 static NSString *MMSymlinkWarningString =
74     @"\n\n\tMost likely this is because you have symlinked directly to\n"
75      "\tthe Vim binary, which Cocoa does not allow.  Please use an\n"
76      "\talias or the mvim shell script instead.  If you have not used\n"
77      "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
81 @interface NSString (MMServerNameCompare)
82 - (NSComparisonResult)serverNameCompare:(NSString *)string;
83 @end
88 @interface MMBackend (Private)
89 - (void)waitForDialogReturn;
90 - (void)insertVimStateMessage;
91 - (void)processInputQueue;
92 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
93 + (NSDictionary *)specialKeys;
94 - (void)handleInsertText:(NSString *)text;
95 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
96 - (void)queueMessage:(int)msgid data:(NSData *)data;
97 - (void)connectionDidDie:(NSNotification *)notification;
98 - (void)blinkTimerFired:(NSTimer *)timer;
99 - (void)focusChange:(BOOL)on;
100 - (void)handleToggleToolbar;
101 - (void)handleScrollbarEvent:(NSData *)data;
102 - (void)handleSetFont:(NSData *)data;
103 - (void)handleDropFiles:(NSData *)data;
104 - (void)handleDropString:(NSData *)data;
105 - (void)startOdbEditWithArguments:(NSDictionary *)args;
106 - (void)handleXcodeMod:(NSData *)data;
107 - (void)handleOpenWithArguments:(NSDictionary *)args;
108 - (BOOL)checkForModifiedBuffers;
109 - (void)addInput:(NSString *)input;
110 - (BOOL)unusedEditor;
111 - (void)redrawScreen;
112 - (void)handleFindReplace:(NSDictionary *)args;
113 @end
117 @interface MMBackend (ClientServer)
118 - (NSString *)connectionNameFromServerName:(NSString *)name;
119 - (NSConnection *)connectionForServerName:(NSString *)name;
120 - (NSConnection *)connectionForServerPort:(int)port;
121 - (void)serverConnectionDidDie:(NSNotification *)notification;
122 - (void)addClient:(NSDistantObject *)client;
123 - (NSString *)alternateServerNameForName:(NSString *)name;
124 @end
128 @implementation MMBackend
130 + (MMBackend *)sharedInstance
132     static MMBackend *singleton = nil;
133     return singleton ? singleton : (singleton = [MMBackend new]);
136 - (id)init
138     self = [super init];
139     if (!self) return nil;
141     fontContainerRef = loadFonts();
143     outputQueue = [[NSMutableArray alloc] init];
144     inputQueue = [[NSMutableArray alloc] init];
145     drawData = [[NSMutableData alloc] initWithCapacity:1024];
146     connectionNameDict = [[NSMutableDictionary alloc] init];
147     clientProxyDict = [[NSMutableDictionary alloc] init];
148     serverReplyDict = [[NSMutableDictionary alloc] init];
150     NSBundle *mainBundle = [NSBundle mainBundle];
151     NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
152     if (path)
153         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
155     path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
156     if (path)
157         sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
158             retain];
160     path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
161     if (path)
162         actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
164     if (!(colorDict && sysColorDict && actionDict))
165         NSLog(@"ERROR: Failed to load dictionaries.%@",
166                 MMSymlinkWarningString);
168     return self;
171 - (void)dealloc
173     //NSLog(@"%@ %s", [self className], _cmd);
174     [[NSNotificationCenter defaultCenter] removeObserver:self];
176     [oldWideFont release];  oldWideFont = nil;
177     [blinkTimer release];  blinkTimer = nil;
178     [alternateServerName release];  alternateServerName = nil;
179     [serverReplyDict release];  serverReplyDict = nil;
180     [clientProxyDict release];  clientProxyDict = nil;
181     [connectionNameDict release];  connectionNameDict = nil;
182     [inputQueue release];  inputQueue = nil;
183     [outputQueue release];  outputQueue = nil;
184     [drawData release];  drawData = nil;
185     [frontendProxy release];  frontendProxy = nil;
186     [connection release];  connection = nil;
187     [actionDict release];  actionDict = nil;
188     [sysColorDict release];  sysColorDict = nil;
189     [colorDict release];  colorDict = nil;
191     [super dealloc];
194 - (void)setBackgroundColor:(int)color
196     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
199 - (void)setForegroundColor:(int)color
201     foregroundColor = MM_COLOR(color);
204 - (void)setSpecialColor:(int)color
206     specialColor = MM_COLOR(color);
209 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
211     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
212     defaultForegroundColor = MM_COLOR(fg);
214     NSMutableData *data = [NSMutableData data];
216     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
217     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
219     [self queueMessage:SetDefaultColorsMsgID data:data];
222 - (NSConnection *)connection
224     if (!connection) {
225         // NOTE!  If the name of the connection changes here it must also be
226         // updated in MMAppController.m.
227         NSString *name = [NSString stringWithFormat:@"%@-connection",
228                [[NSBundle mainBundle] bundlePath]];
230         connection = [NSConnection connectionWithRegisteredName:name host:nil];
231         [connection retain];
232     }
234     // NOTE: 'connection' may be nil here.
235     return connection;
238 - (NSDictionary *)actionDict
240     return actionDict;
243 - (int)initialWindowLayout
245     return initialWindowLayout;
248 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
250     [self queueMessage:msgid data:[props dictionaryAsData]];
253 - (BOOL)checkin
255     if (![self connection]) {
256         if (waitForAck) {
257             // This is a preloaded process and as such should not cause the
258             // MacVim to be opened.  We probably got here as a result of the
259             // user quitting MacVim while the process was preloading, so exit
260             // this process too.
261             // (Don't use mch_exit() since it assumes the process has properly
262             // started.)
263             exit(0);
264         }
266         NSBundle *mainBundle = [NSBundle mainBundle];
267 #if 0
268         OSStatus status;
269         FSRef ref;
271         // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
272         // the API to pass Apple Event parameters is broken on 10.4).
273         NSString *path = [mainBundle bundlePath];
274         status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
275         if (noErr == status) {
276             // Pass parameter to the 'Open' Apple Event that tells MacVim not
277             // to open an untitled window.
278             NSAppleEventDescriptor *desc =
279                     [NSAppleEventDescriptor recordDescriptor];
280             [desc setParamDescriptor:
281                     [NSAppleEventDescriptor descriptorWithBoolean:NO]
282                           forKeyword:keyMMUntitledWindow];
284             LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
285                     kLSLaunchDefaults, NULL };
286             status = LSOpenFromRefSpec(&spec, NULL);
287         }
289         if (noErr != status) {
290         NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
291                 path, MMSymlinkWarningString);
292             return NO;
293         }
294 #else
295         // Launch MacVim using NSTask.  For some reason the above code using
296         // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
297         // fails, the dock icon starts bouncing and never stops).  It seems
298         // like rebuilding the Launch Services database takes care of this
299         // problem, but the NSTask way seems more stable so stick with it.
300         //
301         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
302         // that the GUI won't be activated (or raised) so there is a hack in
303         // MMAppController which raises the app when a new window is opened.
304         NSMutableArray *args = [NSMutableArray arrayWithObjects:
305             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
306         NSString *exeName = [[mainBundle infoDictionary]
307                 objectForKey:@"CFBundleExecutable"];
308         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
309         if (!path) {
310             NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
311                     MMSymlinkWarningString);
312             return NO;
313         }
315         [NSTask launchedTaskWithLaunchPath:path arguments:args];
316 #endif
318         // HACK!  Poll the mach bootstrap server until it returns a valid
319         // connection to detect that MacVim has finished launching.  Also set a
320         // time-out date so that we don't get stuck doing this forever.
321         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
322         while (![self connection] &&
323                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
324             [[NSRunLoop currentRunLoop]
325                     runMode:NSDefaultRunLoopMode
326                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
328         // NOTE: [self connection] will set 'connection' as a side-effect.
329         if (!connection) {
330             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
331             return NO;
332         }
333     }
335     BOOL ok = NO;
336     @try {
337         [[NSNotificationCenter defaultCenter] addObserver:self
338                 selector:@selector(connectionDidDie:)
339                     name:NSConnectionDidDieNotification object:connection];
341         id proxy = [connection rootProxy];
342         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
344         int pid = [[NSProcessInfo processInfo] processIdentifier];
346         frontendProxy = [proxy connectBackend:self pid:pid];
347         if (frontendProxy) {
348             [frontendProxy retain];
349             [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
350             ok = YES;
351         }
352     }
353     @catch (NSException *e) {
354         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
355     }
357     return ok;
360 - (BOOL)openGUIWindow
362     [self queueMessage:OpenWindowMsgID data:nil];
363     return YES;
366 - (void)clearAll
368     int type = ClearAllDrawType;
370     // Any draw commands in queue are effectively obsolete since this clearAll
371     // will negate any effect they have, therefore we may as well clear the
372     // draw queue.
373     [drawData setLength:0];
375     [drawData appendBytes:&type length:sizeof(int)];
378 - (void)clearBlockFromRow:(int)row1 column:(int)col1
379                     toRow:(int)row2 column:(int)col2
381     int type = ClearBlockDrawType;
383     [drawData appendBytes:&type length:sizeof(int)];
385     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
386     [drawData appendBytes:&row1 length:sizeof(int)];
387     [drawData appendBytes:&col1 length:sizeof(int)];
388     [drawData appendBytes:&row2 length:sizeof(int)];
389     [drawData appendBytes:&col2 length:sizeof(int)];
392 - (void)deleteLinesFromRow:(int)row count:(int)count
393               scrollBottom:(int)bottom left:(int)left right:(int)right
395     int type = DeleteLinesDrawType;
397     [drawData appendBytes:&type length:sizeof(int)];
399     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
400     [drawData appendBytes:&row length:sizeof(int)];
401     [drawData appendBytes:&count length:sizeof(int)];
402     [drawData appendBytes:&bottom length:sizeof(int)];
403     [drawData appendBytes:&left length:sizeof(int)];
404     [drawData appendBytes:&right length:sizeof(int)];
407 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
408              cells:(int)cells flags:(int)flags
410     if (len <= 0 || cells <= 0) return;
412     int type = DrawStringDrawType;
414     [drawData appendBytes:&type length:sizeof(int)];
416     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
417     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
418     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
419     [drawData appendBytes:&row length:sizeof(int)];
420     [drawData appendBytes:&col length:sizeof(int)];
421     [drawData appendBytes:&cells length:sizeof(int)];
422     [drawData appendBytes:&flags length:sizeof(int)];
423     [drawData appendBytes:&len length:sizeof(int)];
424     [drawData appendBytes:s length:len];
427 - (void)insertLinesFromRow:(int)row count:(int)count
428               scrollBottom:(int)bottom left:(int)left right:(int)right
430     int type = InsertLinesDrawType;
432     [drawData appendBytes:&type length:sizeof(int)];
434     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
435     [drawData appendBytes:&row length:sizeof(int)];
436     [drawData appendBytes:&count length:sizeof(int)];
437     [drawData appendBytes:&bottom length:sizeof(int)];
438     [drawData appendBytes:&left length:sizeof(int)];
439     [drawData appendBytes:&right length:sizeof(int)];
442 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
443                fraction:(int)percent color:(int)color
445     int type = DrawCursorDrawType;
446     unsigned uc = MM_COLOR(color);
448     [drawData appendBytes:&type length:sizeof(int)];
450     [drawData appendBytes:&uc length:sizeof(unsigned)];
451     [drawData appendBytes:&row length:sizeof(int)];
452     [drawData appendBytes:&col length:sizeof(int)];
453     [drawData appendBytes:&shape length:sizeof(int)];
454     [drawData appendBytes:&percent length:sizeof(int)];
457 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
458                    numColumns:(int)nc invert:(int)invert
460     int type = DrawInvertedRectDrawType;
461     [drawData appendBytes:&type length:sizeof(int)];
463     [drawData appendBytes:&row length:sizeof(int)];
464     [drawData appendBytes:&col length:sizeof(int)];
465     [drawData appendBytes:&nr length:sizeof(int)];
466     [drawData appendBytes:&nc length:sizeof(int)];
467     [drawData appendBytes:&invert length:sizeof(int)];
470 - (void)update
472     // Keep running the run-loop until there is no more input to process.
473     while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
474             == kCFRunLoopRunHandledSource)
475         ;
478 - (void)flushQueue:(BOOL)force
480     // NOTE: This variable allows for better control over when the queue is
481     // flushed.  It can be set to YES at the beginning of a sequence of calls
482     // that may potentially add items to the queue, and then restored back to
483     // NO.
484     if (flushDisabled) return;
486     if ([drawData length] > 0) {
487         // HACK!  Detect changes to 'guifontwide'.
488         if (gui.wide_font != (GuiFont)oldWideFont) {
489             [oldWideFont release];
490             oldWideFont = [(NSFont*)gui.wide_font retain];
491             [self setWideFont:oldWideFont];
492         }
494         int type = SetCursorPosDrawType;
495         [drawData appendBytes:&type length:sizeof(type)];
496         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
497         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
499         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
500         [drawData setLength:0];
501     }
503     if ([outputQueue count] > 0) {
504         [self insertVimStateMessage];
506         @try {
507             [frontendProxy processCommandQueue:outputQueue];
508         }
509         @catch (NSException *e) {
510             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
511             NSLog(@"outputQueue(len:%d)=%@", [outputQueue count]/2,
512                     outputQueue);
513             if (![connection isValid]) {
514                 NSLog(@"WARNING! Connection is invalid, exit now!");
515                 NSLog(@"waitForAck=%d got_int=%d isTerminating=%d",
516                         waitForAck, got_int, isTerminating);
517                 mch_exit(-1);
518             }
519         }
521         [outputQueue removeAllObjects];
522     }
525 - (BOOL)waitForInput:(int)milliseconds
527     // Return NO if we timed out waiting for input, otherwise return YES.
528     BOOL inputReceived = NO;
530     // Only start the run loop if the input queue is empty, otherwise process
531     // the input first so that the input on queue isn't delayed.
532     if ([inputQueue count]) {
533         inputReceived = YES;
534     } else {
535         // Wait for the specified amount of time, unless 'milliseconds' is
536         // negative in which case we wait "forever" (1e6 seconds translates to
537         // approximately 11 days).
538         CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
540         while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
541                 == kCFRunLoopRunHandledSource) {
542             // In order to ensure that all input on the run-loop has been
543             // processed we set the timeout to 0 and keep processing until the
544             // run-loop times out.
545             dt = 0.0;
546             inputReceived = YES;
547         }
548     }
550     // The above calls may have placed messages on the input queue so process
551     // it now.  This call may enter a blocking loop.
552     if ([inputQueue count] > 0)
553         [self processInputQueue];
555     return inputReceived;
558 - (void)exit
560     // NOTE: This is called if mch_exit() is called.  Since we assume here that
561     // the process has started properly, be sure to use exit() instead of
562     // mch_exit() to prematurely terminate a process.
564     // To notify MacVim that this Vim process is exiting we could simply
565     // invalidate the connection and it would automatically receive a
566     // connectionDidDie: notification.  However, this notification seems to
567     // take up to 300 ms to arrive which is quite a noticeable delay.  Instead
568     // we immediately send a message to MacVim asking it to close the window
569     // belonging to this process, and then we invalidate the connection (in
570     // case the message got lost).
572     // Make sure no connectionDidDie: notification is received now that we are
573     // already exiting.
574     [[NSNotificationCenter defaultCenter] removeObserver:self];
576     if ([connection isValid]) {
577         @try {
578             // Flush the entire queue in case a VimLeave autocommand added
579             // something to the queue.
580             [self queueMessage:CloseWindowMsgID data:nil];
581             [frontendProxy processCommandQueue:outputQueue];
582         }
583         @catch (NSException *e) {
584             NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
585         }
587         [connection invalidate];
588     }
590 #ifdef MAC_CLIENTSERVER
591     // The default connection is used for the client/server code.
592     [[NSConnection defaultConnection] setRootObject:nil];
593     [[NSConnection defaultConnection] invalidate];
594 #endif
596     if (fontContainerRef) {
597         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
598         fontContainerRef = 0;
599     }
601     usleep(MMExitProcessDelay);
604 - (void)selectTab:(int)index
606     //NSLog(@"%s%d", _cmd, index);
608     index -= 1;
609     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
610     [self queueMessage:SelectTabMsgID data:data];
613 - (void)updateTabBar
615     //NSLog(@"%s", _cmd);
617     NSMutableData *data = [NSMutableData data];
619     int idx = tabpage_index(curtab) - 1;
620     [data appendBytes:&idx length:sizeof(int)];
622     tabpage_T *tp;
623     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
624         // Count the number of windows in the tabpage.
625         //win_T *wp = tp->tp_firstwin;
626         //int wincount;
627         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
628         //[data appendBytes:&wincount length:sizeof(int)];
630         int tabProp = MMTabInfoCount;
631         [data appendBytes:&tabProp length:sizeof(int)];
632         for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
633             // This function puts the label of the tab in the global 'NameBuff'.
634             get_tabline_label(tp, (tabProp == MMTabToolTip));
635             NSString *s = [NSString stringWithVimString:NameBuff];
636             int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
637             if (len < 0)
638                 len = 0;
640             [data appendBytes:&len length:sizeof(int)];
641             if (len > 0)
642                 [data appendBytes:[s UTF8String] length:len];
643         }
644     }
646     [self queueMessage:UpdateTabBarMsgID data:data];
649 - (BOOL)tabBarVisible
651     return tabBarVisible;
654 - (void)showTabBar:(BOOL)enable
656     tabBarVisible = enable;
658     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
659     [self queueMessage:msgid data:nil];
662 - (void)setRows:(int)rows columns:(int)cols
664     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
666     int dim[] = { rows, cols };
667     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
669     [self queueMessage:SetTextDimensionsMsgID data:data];
672 - (void)setWindowTitle:(char *)title
674     NSMutableData *data = [NSMutableData data];
675     int len = strlen(title);
676     if (len <= 0) return;
678     [data appendBytes:&len length:sizeof(int)];
679     [data appendBytes:title length:len];
681     [self queueMessage:SetWindowTitleMsgID data:data];
684 - (void)setDocumentFilename:(char *)filename
686     NSMutableData *data = [NSMutableData data];
687     int len = filename ? strlen(filename) : 0;
689     [data appendBytes:&len length:sizeof(int)];
690     if (len > 0)
691         [data appendBytes:filename length:len];
693     [self queueMessage:SetDocumentFilenameMsgID data:data];
696 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
698     char_u *s = NULL;
700     @try {
701         [frontendProxy showSavePanelWithAttributes:attr];
703         [self waitForDialogReturn];
705         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
706             s = [dialogReturn vimStringSave];
708         [dialogReturn release];  dialogReturn = nil;
709     }
710     @catch (NSException *e) {
711         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
712     }
714     return (char *)s;
717 - (oneway void)setDialogReturn:(in bycopy id)obj
719     // NOTE: This is called by
720     //   - [MMVimController panelDidEnd:::], and
721     //   - [MMVimController alertDidEnd:::],
722     // to indicate that a save/open panel or alert has finished.
724     // We want to distinguish between "no dialog return yet" and "dialog
725     // returned nothing".  The former can be tested with dialogReturn == nil,
726     // the latter with dialogReturn == [NSNull null].
727     if (!obj) obj = [NSNull null];
729     if (obj != dialogReturn) {
730         [dialogReturn release];
731         dialogReturn = [obj retain];
732     }
735 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
737     int retval = 0;
739     @try {
740         [frontendProxy presentDialogWithAttributes:attr];
742         [self waitForDialogReturn];
744         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
745                 && [dialogReturn count]) {
746             retval = [[dialogReturn objectAtIndex:0] intValue];
747             if (txtfield && [dialogReturn count] > 1) {
748                 NSString *retString = [dialogReturn objectAtIndex:1];
749                 char_u *ret = (char_u*)[retString UTF8String];
750 #ifdef FEAT_MBYTE
751                 ret = CONVERT_FROM_UTF8(ret);
752 #endif
753                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
754 #ifdef FEAT_MBYTE
755                 CONVERT_FROM_UTF8_FREE(ret);
756 #endif
757             }
758         }
760         [dialogReturn release]; dialogReturn = nil;
761     }
762     @catch (NSException *e) {
763         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
764     }
766     return retval;
769 - (void)showToolbar:(int)enable flags:(int)flags
771     NSMutableData *data = [NSMutableData data];
773     [data appendBytes:&enable length:sizeof(int)];
774     [data appendBytes:&flags length:sizeof(int)];
776     [self queueMessage:ShowToolbarMsgID data:data];
779 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
781     NSMutableData *data = [NSMutableData data];
783     [data appendBytes:&ident length:sizeof(long)];
784     [data appendBytes:&type length:sizeof(int)];
786     [self queueMessage:CreateScrollbarMsgID data:data];
789 - (void)destroyScrollbarWithIdentifier:(long)ident
791     NSMutableData *data = [NSMutableData data];
792     [data appendBytes:&ident length:sizeof(long)];
794     [self queueMessage:DestroyScrollbarMsgID data:data];
797 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
799     NSMutableData *data = [NSMutableData data];
801     [data appendBytes:&ident length:sizeof(long)];
802     [data appendBytes:&visible length:sizeof(int)];
804     [self queueMessage:ShowScrollbarMsgID data:data];
807 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
809     NSMutableData *data = [NSMutableData data];
811     [data appendBytes:&ident length:sizeof(long)];
812     [data appendBytes:&pos length:sizeof(int)];
813     [data appendBytes:&len length:sizeof(int)];
815     [self queueMessage:SetScrollbarPositionMsgID data:data];
818 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
819                     identifier:(long)ident
821     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
822     float prop = (float)size/(max+1);
823     if (fval < 0) fval = 0;
824     else if (fval > 1.0f) fval = 1.0f;
825     if (prop < 0) prop = 0;
826     else if (prop > 1.0f) prop = 1.0f;
828     NSMutableData *data = [NSMutableData data];
830     [data appendBytes:&ident length:sizeof(long)];
831     [data appendBytes:&fval length:sizeof(float)];
832     [data appendBytes:&prop length:sizeof(float)];
834     [self queueMessage:SetScrollbarThumbMsgID data:data];
837 - (void)setFont:(NSFont *)font
839     NSString *fontName = [font displayName];
840     float size = [font pointSize];
841     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
842     if (len > 0) {
843         NSMutableData *data = [NSMutableData data];
845         [data appendBytes:&size length:sizeof(float)];
846         [data appendBytes:&len length:sizeof(int)];
847         [data appendBytes:[fontName UTF8String] length:len];
849         [self queueMessage:SetFontMsgID data:data];
850     }
853 - (void)setWideFont:(NSFont *)font
855     NSString *fontName = [font displayName];
856     float size = [font pointSize];
857     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
858     NSMutableData *data = [NSMutableData data];
860     [data appendBytes:&size length:sizeof(float)];
861     [data appendBytes:&len length:sizeof(int)];
862     if (len > 0)
863         [data appendBytes:[fontName UTF8String] length:len];
865     [self queueMessage:SetWideFontMsgID data:data];
868 - (void)executeActionWithName:(NSString *)name
870     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
872     if (len > 0) {
873         NSMutableData *data = [NSMutableData data];
875         [data appendBytes:&len length:sizeof(int)];
876         [data appendBytes:[name UTF8String] length:len];
878         [self queueMessage:ExecuteActionMsgID data:data];
879     }
882 - (void)setMouseShape:(int)shape
884     NSMutableData *data = [NSMutableData data];
885     [data appendBytes:&shape length:sizeof(int)];
886     [self queueMessage:SetMouseShapeMsgID data:data];
889 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
891     // Vim specifies times in milliseconds, whereas Cocoa wants them in
892     // seconds.
893     blinkWaitInterval = .001f*wait;
894     blinkOnInterval = .001f*on;
895     blinkOffInterval = .001f*off;
898 - (void)startBlink
900     if (blinkTimer) {
901         [blinkTimer invalidate];
902         [blinkTimer release];
903         blinkTimer = nil;
904     }
906     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
907             && gui.in_focus) {
908         blinkState = MMBlinkStateOn;
909         blinkTimer =
910             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
911                                               target:self
912                                             selector:@selector(blinkTimerFired:)
913                                             userInfo:nil repeats:NO] retain];
914         gui_update_cursor(TRUE, FALSE);
915         [self flushQueue:YES];
916     }
919 - (void)stopBlink
921     if (MMBlinkStateOff == blinkState) {
922         gui_update_cursor(TRUE, FALSE);
923         [self flushQueue:YES];
924     }
926     blinkState = MMBlinkStateNone;
929 - (void)adjustLinespace:(int)linespace
931     NSMutableData *data = [NSMutableData data];
932     [data appendBytes:&linespace length:sizeof(int)];
933     [self queueMessage:AdjustLinespaceMsgID data:data];
936 - (void)activate
938     [self queueMessage:ActivateMsgID data:nil];
941 - (void)setPreEditRow:(int)row column:(int)col
943     NSMutableData *data = [NSMutableData data];
944     [data appendBytes:&row length:sizeof(int)];
945     [data appendBytes:&col length:sizeof(int)];
946     [self queueMessage:SetPreEditPositionMsgID data:data];
949 - (int)lookupColorWithKey:(NSString *)key
951     if (!(key && [key length] > 0))
952         return INVALCOLOR;
954     NSString *stripKey = [[[[key lowercaseString]
955         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
956             componentsSeparatedByString:@" "]
957                componentsJoinedByString:@""];
959     if (stripKey && [stripKey length] > 0) {
960         // First of all try to lookup key in the color dictionary; note that
961         // all keys in this dictionary are lowercase with no whitespace.
962         id obj = [colorDict objectForKey:stripKey];
963         if (obj) return [obj intValue];
965         // The key was not in the dictionary; is it perhaps of the form
966         // #rrggbb?
967         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
968             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
969             [scanner setScanLocation:1];
970             unsigned hex = 0;
971             if ([scanner scanHexInt:&hex]) {
972                 return (int)hex;
973             }
974         }
976         // As a last resort, check if it is one of the system defined colors.
977         // The keys in this dictionary are also lowercase with no whitespace.
978         obj = [sysColorDict objectForKey:stripKey];
979         if (obj) {
980             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
981             if (col) {
982                 float r, g, b, a;
983                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
984                 [col getRed:&r green:&g blue:&b alpha:&a];
985                 return (((int)(r*255+.5f) & 0xff) << 16)
986                      + (((int)(g*255+.5f) & 0xff) << 8)
987                      +  ((int)(b*255+.5f) & 0xff);
988             }
989         }
990     }
992     //NSLog(@"WARNING: No color with key %@ found.", stripKey);
993     return INVALCOLOR;
996 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
998     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
999     id obj;
1001     while ((obj = [e nextObject])) {
1002         if ([value isEqual:obj])
1003             return YES;
1004     }
1006     return NO;
1009 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1011     NSMutableData *data = [NSMutableData data];
1012     [data appendBytes:&fuoptions length:sizeof(int)];
1013     bg = MM_COLOR(bg);
1014     [data appendBytes:&bg length:sizeof(int)];
1015     [self queueMessage:EnterFullscreenMsgID data:data];
1018 - (void)leaveFullscreen
1020     [self queueMessage:LeaveFullscreenMsgID data:nil];
1023 - (void)setFullscreenBackgroundColor:(int)color
1025     NSMutableData *data = [NSMutableData data];
1026     color = MM_COLOR(color);
1027     [data appendBytes:&color length:sizeof(int)];
1029     [self queueMessage:SetFullscreenColorMsgID data:data];
1032 - (void)setAntialias:(BOOL)antialias
1034     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1036     [self queueMessage:msgid data:nil];
1039 - (void)updateModifiedFlag
1041     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1042     // vice versa.
1043     int msgid = [self checkForModifiedBuffers]
1044             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1046     [self queueMessage:msgid data:nil];
1049 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1051     // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1052     // queue is processed since that only happens in waitForInput: (and Vim
1053     // regularly checks for Ctrl-C in between waiting for input).
1054     // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1055     // which waits on the run loop will fail to detect this message (e.g. in
1056     // waitForConnectionAcknowledgement).
1058     BOOL shouldClearQueue = NO;
1059     if (InterruptMsgID == msgid) {
1060         shouldClearQueue = YES;
1061         got_int = TRUE;
1062     } else if (InsertTextMsgID == msgid && data != nil) {
1063         const void *bytes = [data bytes];
1064         bytes += sizeof(int);
1065         int len = *((int*)bytes);  bytes += sizeof(int);
1066         if (1 == len) {
1067             char_u *str = (char_u*)bytes;
1068             if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1069                     (str[0] == intr_char && intr_char != Ctrl_C)) {
1070                 shouldClearQueue = YES;
1071                 got_int = TRUE;
1072             }
1073         }
1074     } else if (TerminateNowMsgID == msgid) {
1075         shouldClearQueue = YES;
1076         isTerminating = YES;
1077     }
1079     if (shouldClearQueue) {
1080         [inputQueue removeAllObjects];
1081         return;
1082     }
1084     // Remove all previous instances of this message from the input queue, else
1085     // the input queue may fill up as a result of Vim not being able to keep up
1086     // with the speed at which new messages are received.
1087     // Keyboard input is never dropped, unless the input represents and
1088     // auto-repeated key.
1090     BOOL isKeyRepeat = NO;
1091     BOOL isKeyboardInput = NO;
1093     if (data && (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1094             CmdKeyMsgID == msgid)) {
1095         isKeyboardInput = YES;
1097         // The lowest bit of the first int is set if this key is a repeat.
1098         int flags = *((int*)[data bytes]);
1099         if (flags & 1)
1100             isKeyRepeat = YES;
1101     }
1103     // Keyboard input is not removed from the queue; repeats are ignored if
1104     // there already is keyboard input on the input queue.
1105     if (isKeyRepeat || !isKeyboardInput) {
1106         int i, count = [inputQueue count];
1107         for (i = 1; i < count; i+=2) {
1108             if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1109                 if (isKeyRepeat)
1110                     return;
1112                 [inputQueue removeObjectAtIndex:i];
1113                 [inputQueue removeObjectAtIndex:i-1];
1114                 break;
1115             }
1116         }
1117     }
1119     [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1120     [inputQueue addObject:(data ? (id)data : [NSNull null])];
1123 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1125     // This is just a convenience method that allows the frontend to delay
1126     // sending messages.
1127     int i, count = [messages count];
1128     for (i = 1; i < count; i+=2)
1129         [self processInput:[[messages objectAtIndex:i-1] intValue]
1130                       data:[messages objectAtIndex:i]];
1133 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1134                   errorString:(out bycopy NSString **)errstr
1136     return evalExprCocoa(expr, errstr);
1140 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1142     NSString *eval = nil;
1143     char_u *s = (char_u*)[expr UTF8String];
1145 #ifdef FEAT_MBYTE
1146     s = CONVERT_FROM_UTF8(s);
1147 #endif
1149     char_u *res = eval_client_expr_to_string(s);
1151 #ifdef FEAT_MBYTE
1152     CONVERT_FROM_UTF8_FREE(s);
1153 #endif
1155     if (res != NULL) {
1156         s = res;
1157 #ifdef FEAT_MBYTE
1158         s = CONVERT_TO_UTF8(s);
1159 #endif
1160         eval = [NSString stringWithUTF8String:(char*)s];
1161 #ifdef FEAT_MBYTE
1162         CONVERT_TO_UTF8_FREE(s);
1163 #endif
1164         vim_free(res);
1165     }
1167     return eval;
1170 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1172     // TODO: This method should share code with clip_mch_request_selection().
1174     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1175         // If there is no pasteboard, return YES to indicate that there is text
1176         // to copy.
1177         if (!pboard)
1178             return YES;
1180         clip_copy_selection();
1182         // Get the text to put on the pasteboard.
1183         long_u llen = 0; char_u *str = 0;
1184         int type = clip_convert_selection(&str, &llen, &clip_star);
1185         if (type < 0)
1186             return NO;
1187         
1188         // TODO: Avoid overflow.
1189         int len = (int)llen;
1190 #ifdef FEAT_MBYTE
1191         if (output_conv.vc_type != CONV_NONE) {
1192             char_u *conv_str = string_convert(&output_conv, str, &len);
1193             if (conv_str) {
1194                 vim_free(str);
1195                 str = conv_str;
1196             }
1197         }
1198 #endif
1200         NSString *string = [[NSString alloc]
1201             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1203         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1204         [pboard declareTypes:types owner:nil];
1205         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1206     
1207         [string release];
1208         vim_free(str);
1210         return ok;
1211     }
1213     return NO;
1216 - (oneway void)addReply:(in bycopy NSString *)reply
1217                  server:(in byref id <MMVimServerProtocol>)server
1219     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1221     // Replies might come at any time and in any order so we keep them in an
1222     // array inside a dictionary with the send port used as key.
1224     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1225     // HACK! Assume connection uses mach ports.
1226     int port = [(NSMachPort*)[conn sendPort] machPort];
1227     NSNumber *key = [NSNumber numberWithInt:port];
1229     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1230     if (!replies) {
1231         replies = [NSMutableArray array];
1232         [serverReplyDict setObject:replies forKey:key];
1233     }
1235     [replies addObject:reply];
1238 - (void)addInput:(in bycopy NSString *)input
1239           client:(in byref id <MMVimClientProtocol>)client
1241     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1243     // NOTE: We don't call addInput: here because it differs from
1244     // server_to_input_buf() in that it always sets the 'silent' flag and we
1245     // don't want the MacVim client/server code to behave differently from
1246     // other platforms.
1247     char_u *s = [input vimStringSave];
1248     server_to_input_buf(s);
1249     vim_free(s);
1251     [self addClient:(id)client];
1254 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1255                  client:(in byref id <MMVimClientProtocol>)client
1257     [self addClient:(id)client];
1258     return [self evaluateExpression:expr];
1261 - (void)registerServerWithName:(NSString *)name
1263     NSString *svrName = name;
1264     NSConnection *svrConn = [NSConnection defaultConnection];
1265     unsigned i;
1267     for (i = 0; i < MMServerMax; ++i) {
1268         NSString *connName = [self connectionNameFromServerName:svrName];
1270         if ([svrConn registerName:connName]) {
1271             //NSLog(@"Registered server with name: %@", svrName);
1273             // TODO: Set request/reply time-outs to something else?
1274             //
1275             // Don't wait for requests (time-out means that the message is
1276             // dropped).
1277             [svrConn setRequestTimeout:0];
1278             //[svrConn setReplyTimeout:MMReplyTimeout];
1279             [svrConn setRootObject:self];
1281             // NOTE: 'serverName' is a global variable
1282             serverName = [svrName vimStringSave];
1283 #ifdef FEAT_EVAL
1284             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1285 #endif
1286 #ifdef FEAT_TITLE
1287             need_maketitle = TRUE;
1288 #endif
1289             [self queueMessage:SetServerNameMsgID data:
1290                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1291             break;
1292         }
1294         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1295     }
1298 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1299                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1300               silent:(BOOL)silent
1302     // NOTE: If 'name' equals 'serverName' then the request is local (client
1303     // and server are the same).  This case is not handled separately, so a
1304     // connection will be set up anyway (this simplifies the code).
1306     NSConnection *conn = [self connectionForServerName:name];
1307     if (!conn) {
1308         if (!silent) {
1309             char_u *s = (char_u*)[name UTF8String];
1310 #ifdef FEAT_MBYTE
1311             s = CONVERT_FROM_UTF8(s);
1312 #endif
1313             EMSG2(_(e_noserver), s);
1314 #ifdef FEAT_MBYTE
1315             CONVERT_FROM_UTF8_FREE(s);
1316 #endif
1317         }
1318         return NO;
1319     }
1321     if (port) {
1322         // HACK! Assume connection uses mach ports.
1323         *port = [(NSMachPort*)[conn sendPort] machPort];
1324     }
1326     id proxy = [conn rootProxy];
1327     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1329     @try {
1330         if (expr) {
1331             NSString *eval = [proxy evaluateExpression:string client:self];
1332             if (reply) {
1333                 if (eval) {
1334                     *reply = [eval vimStringSave];
1335                 } else {
1336                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1337                 }
1338             }
1340             if (!eval)
1341                 return NO;
1342         } else {
1343             [proxy addInput:string client:self];
1344         }
1345     }
1346     @catch (NSException *e) {
1347         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1348         return NO;
1349     }
1351     return YES;
1354 - (NSArray *)serverList
1356     NSArray *list = nil;
1358     if ([self connection]) {
1359         id proxy = [connection rootProxy];
1360         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1362         @try {
1363             list = [proxy serverList];
1364         }
1365         @catch (NSException *e) {
1366             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1367         }
1368     } else {
1369         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1370     }
1372     return list;
1375 - (NSString *)peekForReplyOnPort:(int)port
1377     //NSLog(@"%s%d", _cmd, port);
1379     NSNumber *key = [NSNumber numberWithInt:port];
1380     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1381     if (replies && [replies count]) {
1382         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1383         //        [replies objectAtIndex:0]);
1384         return [replies objectAtIndex:0];
1385     }
1387     //NSLog(@"    No replies");
1388     return nil;
1391 - (NSString *)waitForReplyOnPort:(int)port
1393     //NSLog(@"%s%d", _cmd, port);
1394     
1395     NSConnection *conn = [self connectionForServerPort:port];
1396     if (!conn)
1397         return nil;
1399     NSNumber *key = [NSNumber numberWithInt:port];
1400     NSMutableArray *replies = nil;
1401     NSString *reply = nil;
1403     // Wait for reply as long as the connection to the server is valid (unless
1404     // user interrupts wait with Ctrl-C).
1405     while (!got_int && [conn isValid] &&
1406             !(replies = [serverReplyDict objectForKey:key])) {
1407         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1408                                  beforeDate:[NSDate distantFuture]];
1409     }
1411     if (replies) {
1412         if ([replies count] > 0) {
1413             reply = [[replies objectAtIndex:0] retain];
1414             //NSLog(@"    Got reply: %@", reply);
1415             [replies removeObjectAtIndex:0];
1416             [reply autorelease];
1417         }
1419         if ([replies count] == 0)
1420             [serverReplyDict removeObjectForKey:key];
1421     }
1423     return reply;
1426 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1428     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1429     if (client) {
1430         @try {
1431             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1432             [client addReply:reply server:self];
1433             return YES;
1434         }
1435         @catch (NSException *e) {
1436             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1437         }
1438     } else {
1439         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1440     }
1442     return NO;
1445 - (BOOL)waitForAck
1447     return waitForAck;
1450 - (void)setWaitForAck:(BOOL)yn
1452     waitForAck = yn;
1455 - (void)waitForConnectionAcknowledgement
1457     if (!waitForAck) return;
1459     while (waitForAck && !got_int && [connection isValid] && !isTerminating) {
1460         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1461                                  beforeDate:[NSDate distantFuture]];
1462         //NSLog(@"  waitForAck=%d got_int=%d isTerminating=%d isValid=%d",
1463         //        waitForAck, got_int, isTerminating, [connection isValid]);
1464     }
1466     if (waitForAck) {
1467         // Never received a connection acknowledgement, so die.
1468         [[NSNotificationCenter defaultCenter] removeObserver:self];
1469         [frontendProxy release];  frontendProxy = nil;
1471         // NOTE: We intentionally do not call mch_exit() since this in turn
1472         // will lead to -[MMBackend exit] getting called which we want to
1473         // avoid.
1474         usleep(MMExitProcessDelay);
1475         exit(0);
1476     }
1478     [self processInputQueue];
1481 - (oneway void)acknowledgeConnection
1483     //NSLog(@"%s", _cmd);
1484     waitForAck = NO;
1487 @end // MMBackend
1491 @implementation MMBackend (Private)
1493 - (void)waitForDialogReturn
1495     // Keep processing the run loop until a dialog returns.  To avoid getting
1496     // stuck in an endless loop (could happen if the setDialogReturn: message
1497     // was lost) we also do some paranoia checks.
1498     //
1499     // Note that in Cocoa the user can still resize windows and select menu
1500     // items while a sheet is being displayed, so we can't just wait for the
1501     // first message to arrive and assume that is the setDialogReturn: call.
1503     while (nil == dialogReturn && !got_int && [connection isValid]
1504             && !isTerminating)
1505         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1506                                  beforeDate:[NSDate distantFuture]];
1508     // Search for any resize messages on the input queue.  All other messages
1509     // on the input queue are dropped.  The reason why we single out resize
1510     // messages is because the user may have resized the window while a sheet
1511     // was open.
1512     int i, count = [inputQueue count];
1513     if (count > 0) {
1514         id textDimData = nil;
1515         if (count%2 == 0) {
1516             for (i = count-2; i >= 0; i -= 2) {
1517                 int msgid = [[inputQueue objectAtIndex:i] intValue];
1518                 if (SetTextDimensionsMsgID == msgid) {
1519                     textDimData = [[inputQueue objectAtIndex:i+1] retain];
1520                     break;
1521                 }
1522             }
1523         }
1525         [inputQueue removeAllObjects];
1527         if (textDimData) {
1528             [inputQueue addObject:
1529                     [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1530             [inputQueue addObject:textDimData];
1531             [textDimData release];
1532         }
1533     }
1536 - (void)insertVimStateMessage
1538     // NOTE: This is the place to add Vim state that needs to be accessed from
1539     // MacVim.  Do not add state that could potentially require lots of memory
1540     // since this message gets sent each time the output queue is forcibly
1541     // flushed (e.g. storing the currently selected text would be a bad idea).
1542     // We take this approach of "pushing" the state to MacVim to avoid having
1543     // to make synchronous calls from MacVim to Vim in order to get state.
1545     BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1547     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1548         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1549         [NSNumber numberWithInt:p_mh], @"p_mh",
1550         [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1551         [NSNumber numberWithBool:mmta], @"p_mmta",
1552         nil];
1554     // Put the state before all other messages.
1555     int msgid = SetVimStateMsgID;
1556     [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1557     [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1558                       atIndex:0];
1561 - (void)processInputQueue
1563     if ([inputQueue count] == 0) return;
1565     // NOTE: One of the input events may cause this method to be called
1566     // recursively, so copy the input queue to a local variable and clear the
1567     // queue before starting to process input events (otherwise we could get
1568     // stuck in an endless loop).
1569     NSArray *q = [inputQueue copy];
1570     unsigned i, count = [q count];
1572     [inputQueue removeAllObjects];
1574     for (i = 1; i < count; i+=2) {
1575         int msgid = [[q objectAtIndex:i-1] intValue];
1576         id data = [q objectAtIndex:i];
1577         if ([data isEqual:[NSNull null]])
1578             data = nil;
1580         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1581         [self handleInputEvent:msgid data:data];
1582     }
1584     [q release];
1585     //NSLog(@"Clear input event queue");
1588 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1590     if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1591             CmdKeyMsgID == msgid) {
1592         if (!data) return;
1593         const void *bytes = [data bytes];
1594         int mods = *((int*)bytes);  bytes += sizeof(int);
1595         int len = *((int*)bytes);  bytes += sizeof(int);
1596         NSString *key = [[NSString alloc] initWithBytes:bytes
1597                                                  length:len
1598                                                encoding:NSUTF8StringEncoding];
1599         mods = eventModifierFlagsToVimModMask(mods);
1601         if (InsertTextMsgID == msgid)
1602             [self handleInsertText:key];
1603         else
1604             [self handleKeyDown:key modifiers:mods];
1606         [key release];
1607     } else if (ScrollWheelMsgID == msgid) {
1608         if (!data) return;
1609         const void *bytes = [data bytes];
1611         int row = *((int*)bytes);  bytes += sizeof(int);
1612         int col = *((int*)bytes);  bytes += sizeof(int);
1613         int flags = *((int*)bytes);  bytes += sizeof(int);
1614         float dy = *((float*)bytes);  bytes += sizeof(float);
1616         int button = MOUSE_5;
1617         if (dy > 0) button = MOUSE_4;
1619         flags = eventModifierFlagsToVimMouseModMask(flags);
1621         int numLines = (int)round(dy);
1622         if (numLines < 0) numLines = -numLines;
1623         if (numLines == 0) numLines = 1;
1625 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1626         gui.scroll_wheel_force = numLines;
1627 #endif
1629         gui_send_mouse_event(button, col, row, NO, flags);
1630     } else if (MouseDownMsgID == msgid) {
1631         if (!data) return;
1632         const void *bytes = [data bytes];
1634         int row = *((int*)bytes);  bytes += sizeof(int);
1635         int col = *((int*)bytes);  bytes += sizeof(int);
1636         int button = *((int*)bytes);  bytes += sizeof(int);
1637         int flags = *((int*)bytes);  bytes += sizeof(int);
1638         int count = *((int*)bytes);  bytes += sizeof(int);
1640         button = eventButtonNumberToVimMouseButton(button);
1641         if (button >= 0) {
1642             flags = eventModifierFlagsToVimMouseModMask(flags);
1643             gui_send_mouse_event(button, col, row, count>1, flags);
1644         }
1645     } else if (MouseUpMsgID == msgid) {
1646         if (!data) return;
1647         const void *bytes = [data bytes];
1649         int row = *((int*)bytes);  bytes += sizeof(int);
1650         int col = *((int*)bytes);  bytes += sizeof(int);
1651         int flags = *((int*)bytes);  bytes += sizeof(int);
1653         flags = eventModifierFlagsToVimMouseModMask(flags);
1655         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1656     } else if (MouseDraggedMsgID == msgid) {
1657         if (!data) return;
1658         const void *bytes = [data bytes];
1660         int row = *((int*)bytes);  bytes += sizeof(int);
1661         int col = *((int*)bytes);  bytes += sizeof(int);
1662         int flags = *((int*)bytes);  bytes += sizeof(int);
1664         flags = eventModifierFlagsToVimMouseModMask(flags);
1666         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1667     } else if (MouseMovedMsgID == msgid) {
1668         const void *bytes = [data bytes];
1669         int row = *((int*)bytes);  bytes += sizeof(int);
1670         int col = *((int*)bytes);  bytes += sizeof(int);
1672         gui_mouse_moved(col, row);
1673     } else if (AddInputMsgID == msgid) {
1674         NSString *string = [[NSString alloc] initWithData:data
1675                 encoding:NSUTF8StringEncoding];
1676         if (string) {
1677             [self addInput:string];
1678             [string release];
1679         }
1680     } else if (SelectTabMsgID == msgid) {
1681         if (!data) return;
1682         const void *bytes = [data bytes];
1683         int idx = *((int*)bytes) + 1;
1684         //NSLog(@"Selecting tab %d", idx);
1685         send_tabline_event(idx);
1686     } else if (CloseTabMsgID == msgid) {
1687         if (!data) return;
1688         const void *bytes = [data bytes];
1689         int idx = *((int*)bytes) + 1;
1690         //NSLog(@"Closing tab %d", idx);
1691         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1692     } else if (AddNewTabMsgID == msgid) {
1693         //NSLog(@"Adding new tab");
1694         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1695     } else if (DraggedTabMsgID == msgid) {
1696         if (!data) return;
1697         const void *bytes = [data bytes];
1698         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1699         // based.
1700         int idx = *((int*)bytes);
1702         tabpage_move(idx);
1703     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1704             || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1705         if (!data) return;
1706         const void *bytes = [data bytes];
1707         int rows = Rows;
1708         if (SetTextColumnsMsgID != msgid) {
1709             rows = *((int*)bytes);  bytes += sizeof(int);
1710         }
1711         int cols = Columns;
1712         if (SetTextRowsMsgID != msgid) {
1713             cols = *((int*)bytes);  bytes += sizeof(int);
1714         }
1716         NSData *d = data;
1717         if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1718             int dim[2] = { rows, cols };
1719             d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1720             msgid = SetTextDimensionsReplyMsgID;
1721         }
1723         if (SetTextDimensionsMsgID == msgid)
1724             msgid = SetTextDimensionsReplyMsgID;
1726         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1727         // gui_resize_shell(), so we have to manually set the rows and columns
1728         // here since MacVim doesn't change the rows and columns to avoid
1729         // inconsistent states between Vim and MacVim.  The message sent back
1730         // indicates that it is a reply to a message that originated in MacVim
1731         // since we need to be able to determine where a message originated.
1732         [self queueMessage:msgid data:d];
1734         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1735         gui_resize_shell(cols, rows);
1736     } else if (ExecuteMenuMsgID == msgid) {
1737         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1738         if (attrs) {
1739             NSArray *desc = [attrs objectForKey:@"descriptor"];
1740             vimmenu_T *menu = menu_for_descriptor(desc);
1741             if (menu)
1742                 gui_menu_cb(menu);
1743         }
1744     } else if (ToggleToolbarMsgID == msgid) {
1745         [self handleToggleToolbar];
1746     } else if (ScrollbarEventMsgID == msgid) {
1747         [self handleScrollbarEvent:data];
1748     } else if (SetFontMsgID == msgid) {
1749         [self handleSetFont:data];
1750     } else if (VimShouldCloseMsgID == msgid) {
1751         gui_shell_closed();
1752     } else if (DropFilesMsgID == msgid) {
1753         [self handleDropFiles:data];
1754     } else if (DropStringMsgID == msgid) {
1755         [self handleDropString:data];
1756     } else if (GotFocusMsgID == msgid) {
1757         if (!gui.in_focus)
1758             [self focusChange:YES];
1759     } else if (LostFocusMsgID == msgid) {
1760         if (gui.in_focus)
1761             [self focusChange:NO];
1762     } else if (SetMouseShapeMsgID == msgid) {
1763         const void *bytes = [data bytes];
1764         int shape = *((int*)bytes);  bytes += sizeof(int);
1765         update_mouseshape(shape);
1766     } else if (XcodeModMsgID == msgid) {
1767         [self handleXcodeMod:data];
1768     } else if (OpenWithArgumentsMsgID == msgid) {
1769         [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1770     } else if (FindReplaceMsgID == msgid) {
1771         [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1772     } else {
1773         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1774     }
1777 + (NSDictionary *)specialKeys
1779     static NSDictionary *specialKeys = nil;
1781     if (!specialKeys) {
1782         NSBundle *mainBundle = [NSBundle mainBundle];
1783         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1784                                               ofType:@"plist"];
1785         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1786     }
1788     return specialKeys;
1791 - (void)handleInsertText:(NSString *)text
1793     if (!text) return;
1795     char_u *str = (char_u*)[text UTF8String];
1796     int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1798 #ifdef FEAT_MBYTE
1799     char_u *conv_str = NULL;
1800     if (input_conv.vc_type != CONV_NONE) {
1801         conv_str = string_convert(&input_conv, str, &len);
1802         if (conv_str)
1803             str = conv_str;
1804     }
1805 #endif
1807     for (i = 0; i < len; ++i) {
1808         add_to_input_buf(str+i, 1);
1809         if (CSI == str[i]) {
1810             // NOTE: If the converted string contains the byte CSI, then it
1811             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1812             // won't work.
1813             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1814             add_to_input_buf(extra, 2);
1815         }
1816     }
1818 #ifdef FEAT_MBYTE
1819     if (conv_str)
1820         vim_free(conv_str);
1821 #endif
1824 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1826     // TODO: This code is a horrible mess -- clean up!
1827     char_u special[3];
1828     char_u modChars[3];
1829     char_u *chars = (char_u*)[key UTF8String];
1830 #ifdef FEAT_MBYTE
1831     char_u *conv_str = NULL;
1832 #endif
1833     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1835     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1836     // that new keys can easily be added.
1837     NSString *specialString = [[MMBackend specialKeys]
1838             objectForKey:key];
1839     if (specialString && [specialString length] > 1) {
1840         //NSLog(@"special key: %@", specialString);
1841         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1842                 [specialString characterAtIndex:1]);
1844         ikey = simplify_key(ikey, &mods);
1845         if (ikey == CSI)
1846             ikey = K_CSI;
1848         special[0] = CSI;
1849         special[1] = K_SECOND(ikey);
1850         special[2] = K_THIRD(ikey);
1852         chars = special;
1853         length = 3;
1854     } else if (1 == length && TAB == chars[0]) {
1855         // Tab is a trouble child:
1856         // - <Tab> is added to the input buffer as is
1857         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1858         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1859         //   to be converted to utf-8
1860         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1861         // - <C-Tab> is reserved by Mac OS X
1862         // - <D-Tab> is reserved by Mac OS X
1863         chars = special;
1864         special[0] = TAB;
1865         length = 1;
1867         if (mods & MOD_MASK_SHIFT) {
1868             mods &= ~MOD_MASK_SHIFT;
1869             special[0] = CSI;
1870             special[1] = K_SECOND(K_S_TAB);
1871             special[2] = K_THIRD(K_S_TAB);
1872             length = 3;
1873         } else if (mods & MOD_MASK_ALT) {
1874             int mtab = 0x80 | TAB;
1875 #ifdef FEAT_MBYTE
1876             if (enc_utf8) {
1877                 // Convert to utf-8
1878                 special[0] = (mtab >> 6) + 0xc0;
1879                 special[1] = mtab & 0xbf;
1880                 length = 2;
1881             } else
1882 #endif
1883             {
1884                 special[0] = mtab;
1885                 length = 1;
1886             }
1887             mods &= ~MOD_MASK_ALT;
1888         }
1889     } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1890         // META key is treated separately.  This code was taken from gui_w48.c
1891         // and gui_gtk_x11.c.
1892         char_u string[7];
1893         int ch = simplify_key(chars[0], &mods);
1895         // Remove the SHIFT modifier for keys where it's already included,
1896         // e.g., '(' and '*'
1897         if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1898             mods &= ~MOD_MASK_SHIFT;
1900         // Interpret the ALT key as making the key META, include SHIFT, etc.
1901         ch = extract_modifiers(ch, &mods);
1902         if (ch == CSI)
1903             ch = K_CSI;
1905         int len = 0;
1906         if (mods) {
1907             string[len++] = CSI;
1908             string[len++] = KS_MODIFIER;
1909             string[len++] = mods;
1910         }
1912         if (IS_SPECIAL(ch)) {
1913             string[len++] = CSI;
1914             string[len++] = K_SECOND(ch);
1915             string[len++] = K_THIRD(ch);
1916         } else {
1917             string[len++] = ch;
1918 #ifdef FEAT_MBYTE
1919             // TODO: What if 'enc' is not "utf-8"?
1920             if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1921                 string[len++] = ch & 0xbf;
1922                 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1923                 if (string[len-1] == CSI) {
1924                     string[len++] = KS_EXTRA;
1925                     string[len++] = (int)KE_CSI;
1926                 }
1927             }
1928 #endif
1929         }
1931         add_to_input_buf(string, len);
1932         return;
1933     } else if (length > 0) {
1934         unichar c = [key characterAtIndex:0];
1935         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1936         //        [key characterAtIndex:0], mods);
1938         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1939         // cleared since they are already added to the key by the AppKit.
1940         // Unfortunately, the only way to deal with when to clear the modifiers
1941         // or not seems to be to have hard-wired rules like this.
1942         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1943                     || 0x9 == c || 0xd == c || ESC == c) ) {
1944             mods &= ~MOD_MASK_SHIFT;
1945             mods &= ~MOD_MASK_CTRL;
1946             //NSLog(@"clear shift ctrl");
1947         }
1949 #ifdef FEAT_MBYTE
1950         if (input_conv.vc_type != CONV_NONE) {
1951             conv_str = string_convert(&input_conv, chars, &length);
1952             if (conv_str)
1953                 chars = conv_str;
1954         }
1955 #endif
1956     }
1958     if (chars && length > 0) {
1959         if (mods) {
1960             //NSLog(@"adding mods: %d", mods);
1961             modChars[0] = CSI;
1962             modChars[1] = KS_MODIFIER;
1963             modChars[2] = mods;
1964             add_to_input_buf(modChars, 3);
1965         }
1967         //NSLog(@"add to input buf: 0x%x", chars[0]);
1968         // TODO: Check for CSI bytes?
1969         add_to_input_buf(chars, length);
1970     }
1972 #ifdef FEAT_MBYTE
1973     if (conv_str)
1974         vim_free(conv_str);
1975 #endif
1978 - (void)queueMessage:(int)msgid data:(NSData *)data
1980     //if (msgid != EnableMenuItemMsgID)
1981     //    NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1983     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1984     if (data)
1985         [outputQueue addObject:data];
1986     else
1987         [outputQueue addObject:[NSData data]];
1990 - (void)connectionDidDie:(NSNotification *)notification
1992     // If the main connection to MacVim is lost this means that MacVim was
1993     // either quit (by the user chosing Quit on the MacVim menu), or it has
1994     // crashed.  In the former case the flag 'isTerminating' is set and we then
1995     // quit cleanly; in the latter case we make sure the swap files are left
1996     // for recovery.
1997     //
1998     // NOTE: This is not called if a Vim controller invalidates its connection.
2000     //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
2001     if (isTerminating)
2002         getout(0);
2003     else
2004         getout_preserve_modified(1);
2007 - (void)blinkTimerFired:(NSTimer *)timer
2009     NSTimeInterval timeInterval = 0;
2011     [blinkTimer release];
2012     blinkTimer = nil;
2014     if (MMBlinkStateOn == blinkState) {
2015         gui_undraw_cursor();
2016         blinkState = MMBlinkStateOff;
2017         timeInterval = blinkOffInterval;
2018     } else if (MMBlinkStateOff == blinkState) {
2019         gui_update_cursor(TRUE, FALSE);
2020         blinkState = MMBlinkStateOn;
2021         timeInterval = blinkOnInterval;
2022     }
2024     if (timeInterval > 0) {
2025         blinkTimer = 
2026             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2027                                             selector:@selector(blinkTimerFired:)
2028                                             userInfo:nil repeats:NO] retain];
2029         [self flushQueue:YES];
2030     }
2033 - (void)focusChange:(BOOL)on
2035     gui_focus_change(on);
2038 - (void)handleToggleToolbar
2040     // If 'go' contains 'T', then remove it, else add it.
2042     char_u go[sizeof(GO_ALL)+2];
2043     char_u *p;
2044     int len;
2046     STRCPY(go, p_go);
2047     p = vim_strchr(go, GO_TOOLBAR);
2048     len = STRLEN(go);
2050     if (p != NULL) {
2051         char_u *end = go + len;
2052         while (p < end) {
2053             p[0] = p[1];
2054             ++p;
2055         }
2056     } else {
2057         go[len] = GO_TOOLBAR;
2058         go[len+1] = NUL;
2059     }
2061     set_option_value((char_u*)"guioptions", 0, go, 0);
2063     [self redrawScreen];
2066 - (void)handleScrollbarEvent:(NSData *)data
2068     if (!data) return;
2070     const void *bytes = [data bytes];
2071     long ident = *((long*)bytes);  bytes += sizeof(long);
2072     int hitPart = *((int*)bytes);  bytes += sizeof(int);
2073     float fval = *((float*)bytes);  bytes += sizeof(float);
2074     scrollbar_T *sb = gui_find_scrollbar(ident);
2076     if (sb) {
2077         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2078         long value = sb_info->value;
2079         long size = sb_info->size;
2080         long max = sb_info->max;
2081         BOOL isStillDragging = NO;
2082         BOOL updateKnob = YES;
2084         switch (hitPart) {
2085         case NSScrollerDecrementPage:
2086             value -= (size > 2 ? size - 2 : 1);
2087             break;
2088         case NSScrollerIncrementPage:
2089             value += (size > 2 ? size - 2 : 1);
2090             break;
2091         case NSScrollerDecrementLine:
2092             --value;
2093             break;
2094         case NSScrollerIncrementLine:
2095             ++value;
2096             break;
2097         case NSScrollerKnob:
2098             isStillDragging = YES;
2099             // fall through ...
2100         case NSScrollerKnobSlot:
2101             value = (long)(fval * (max - size + 1));
2102             // fall through ...
2103         default:
2104             updateKnob = NO;
2105             break;
2106         }
2108         //NSLog(@"value %d -> %d", sb_info->value, value);
2109         gui_drag_scrollbar(sb, value, isStillDragging);
2111         if (updateKnob) {
2112             // Dragging the knob or option+clicking automatically updates
2113             // the knob position (on the actual NSScroller), so we only
2114             // need to set the knob position in the other cases.
2115             if (sb->wp) {
2116                 // Update both the left&right vertical scrollbars.
2117                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2118                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2119                 [self setScrollbarThumbValue:value size:size max:max
2120                                   identifier:identLeft];
2121                 [self setScrollbarThumbValue:value size:size max:max
2122                                   identifier:identRight];
2123             } else {
2124                 // Update the horizontal scrollbar.
2125                 [self setScrollbarThumbValue:value size:size max:max
2126                                   identifier:ident];
2127             }
2128         }
2129     }
2132 - (void)handleSetFont:(NSData *)data
2134     if (!data) return;
2136     const void *bytes = [data bytes];
2137     float pointSize = *((float*)bytes);  bytes += sizeof(float);
2138     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2139     bytes += sizeof(unsigned);  // len not used
2141     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2142     [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
2143     char_u *s = (char_u*)[name UTF8String];
2145 #ifdef FEAT_MBYTE
2146     s = CONVERT_FROM_UTF8(s);
2147 #endif
2149     set_option_value((char_u*)"guifont", 0, s, 0);
2151 #ifdef FEAT_MBYTE
2152     CONVERT_FROM_UTF8_FREE(s);
2153 #endif
2155     [self redrawScreen];
2158 - (void)handleDropFiles:(NSData *)data
2160     // TODO: Get rid of this method; instead use Vim script directly.  At the
2161     // moment I know how to do this to open files in tabs, but I'm not sure how
2162     // to add the filenames to the command line when in command line mode.
2164     if (!data) return;
2166     NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2167     if (!args) return;
2169     id obj = [args objectForKey:@"forceOpen"];
2170     BOOL forceOpen = YES;
2171     if (obj)
2172         forceOpen = [obj boolValue];
2174     NSArray *filenames = [args objectForKey:@"filenames"];
2175     if (!(filenames && [filenames count] > 0)) return;
2177 #ifdef FEAT_DND
2178     if (!forceOpen && (State & CMDLINE)) {
2179         // HACK!  If Vim is in command line mode then the files names
2180         // should be added to the command line, instead of opening the
2181         // files in tabs (unless forceOpen is set).  This is taken care of by
2182         // gui_handle_drop().
2183         int n = [filenames count];
2184         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2185         if (fnames) {
2186             int i = 0;
2187             for (i = 0; i < n; ++i)
2188                 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2190             // NOTE!  This function will free 'fnames'.
2191             // HACK!  It is assumed that the 'x' and 'y' arguments are
2192             // unused when in command line mode.
2193             gui_handle_drop(0, 0, 0, fnames, n);
2194         }
2195     } else
2196 #endif // FEAT_DND
2197     {
2198         [self handleOpenWithArguments:args];
2199     }
2202 - (void)handleDropString:(NSData *)data
2204     if (!data) return;
2206 #ifdef FEAT_DND
2207     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2208     const void *bytes = [data bytes];
2209     int len = *((int*)bytes);  bytes += sizeof(int);
2210     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2212     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2213     NSRange range = { 0, [string length] };
2214     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2215                                          withString:@"\x0a" options:0
2216                                               range:range];
2217     if (0 == n) {
2218         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2219                                        options:0 range:range];
2220     }
2222     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2223     char_u *s = (char_u*)[string UTF8String];
2224 #ifdef FEAT_MBYTE
2225     if (input_conv.vc_type != CONV_NONE)
2226         s = string_convert(&input_conv, s, &len);
2227 #endif
2228     dnd_yank_drag_data(s, len);
2229 #ifdef FEAT_MBYTE
2230     if (input_conv.vc_type != CONV_NONE)
2231         vim_free(s);
2232 #endif
2233     add_to_input_buf(dropkey, sizeof(dropkey));
2234 #endif // FEAT_DND
2237 - (void)startOdbEditWithArguments:(NSDictionary *)args
2239 #ifdef FEAT_ODB_EDITOR
2240     id obj = [args objectForKey:@"remoteID"];
2241     if (!obj) return;
2243     OSType serverID = [obj unsignedIntValue];
2244     NSString *remotePath = [args objectForKey:@"remotePath"];
2246     NSAppleEventDescriptor *token = nil;
2247     NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2248     obj = [args objectForKey:@"remoteTokenDescType"];
2249     if (tokenData && obj) {
2250         DescType tokenType = [obj unsignedLongValue];
2251         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2252                                                                 data:tokenData];
2253     }
2255     NSArray *filenames = [args objectForKey:@"filenames"];
2256     unsigned i, numFiles = [filenames count];
2257     for (i = 0; i < numFiles; ++i) {
2258         NSString *filename = [filenames objectAtIndex:i];
2259         char_u *s = [filename vimStringSave];
2260         buf_T *buf = buflist_findname(s);
2261         vim_free(s);
2263         if (buf) {
2264             if (buf->b_odb_token) {
2265                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2266                 buf->b_odb_token = NULL;
2267             }
2269             if (buf->b_odb_fname) {
2270                 vim_free(buf->b_odb_fname);
2271                 buf->b_odb_fname = NULL;
2272             }
2274             buf->b_odb_server_id = serverID;
2276             if (token)
2277                 buf->b_odb_token = [token retain];
2278             if (remotePath)
2279                 buf->b_odb_fname = [remotePath vimStringSave];
2280         } else {
2281             NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2282                     filename);
2283         }
2284     }
2285 #endif // FEAT_ODB_EDITOR
2288 - (void)handleXcodeMod:(NSData *)data
2290 #if 0
2291     const void *bytes = [data bytes];
2292     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2293     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2294     if (0 == len)
2295         return;
2297     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2298             descriptorWithDescriptorType:type
2299                                    bytes:bytes
2300                                   length:len];
2301 #endif
2304 - (void)handleOpenWithArguments:(NSDictionary *)args
2306     // ARGUMENT:                DESCRIPTION:
2307     // -------------------------------------------------------------
2308     // filenames                list of filenames
2309     // dontOpen                 don't open files specified in above argument
2310     // layout                   which layout to use to open files
2311     // selectionRange           range of lines to select
2312     // searchText               string to search for
2313     // cursorLine               line to position the cursor on
2314     // cursorColumn             column to position the cursor on
2315     //                          (only valid when "cursorLine" is set)
2316     // remoteID                 ODB parameter
2317     // remotePath               ODB parameter
2318     // remoteTokenDescType      ODB parameter
2319     // remoteTokenData          ODB parameter
2321     //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2323     NSArray *filenames = [args objectForKey:@"filenames"];
2324     int i, numFiles = filenames ? [filenames count] : 0;
2325     BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2326     int layout = [[args objectForKey:@"layout"] intValue];
2328     // Change to directory of first file to open if this is an "unused" editor
2329     // (but do not do this if editing remotely).
2330     if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2331             && (starting || [self unusedEditor]) ) {
2332         char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2333         vim_chdirfile(s);
2334         vim_free(s);
2335     }
2337     if (starting > 0) {
2338         // When Vim is starting we simply add the files to be opened to the
2339         // global arglist and Vim will take care of opening them for us.
2340         if (openFiles && numFiles > 0) {
2341             for (i = 0; i < numFiles; i++) {
2342                 NSString *fname = [filenames objectAtIndex:i];
2343                 char_u *p = NULL;
2345                 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2346                         || (p = [fname vimStringSave]) == NULL)
2347                     exit(2); // See comment in -[MMBackend exit]
2348                 else
2349                     alist_add(&global_alist, p, 2);
2350             }
2352             // Vim will take care of arranging the files added to the arglist
2353             // in windows or tabs; all we must do is to specify which layout to
2354             // use.
2355             initialWindowLayout = layout;
2356         }
2357     } else {
2358         // When Vim is already open we resort to some trickery to open the
2359         // files with the specified layout.
2360         //
2361         // TODO: Figure out a better way to handle this?
2362         if (openFiles && numFiles > 0) {
2363             BOOL oneWindowInTab = topframe ? YES
2364                                            : (topframe->fr_layout == FR_LEAF);
2365             BOOL bufChanged = NO;
2366             BOOL bufHasFilename = NO;
2367             if (curbuf) {
2368                 bufChanged = curbufIsChanged();
2369                 bufHasFilename = curbuf->b_ffname != NULL;
2370             }
2372             // Temporarily disable flushing since the following code may
2373             // potentially cause multiple redraws.
2374             flushDisabled = YES;
2376             BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2377             if (WIN_TABS == layout && !onlyOneTab) {
2378                 // By going to the last tabpage we ensure that the new tabs
2379                 // will appear last (if this call is left out, the taborder
2380                 // becomes messy).
2381                 goto_tabpage(9999);
2382             }
2384             // Make sure we're in normal mode first.
2385             [self addInput:@"<C-\\><C-N>"];
2387             if (numFiles > 1) {
2388                 // With "split layout" we open a new tab before opening
2389                 // multiple files if the current tab has more than one window
2390                 // or if there is exactly one window but whose buffer has a
2391                 // filename.  (The :drop command ensures modified buffers get
2392                 // their own window.)
2393                 if ((WIN_HOR == layout || WIN_VER == layout) &&
2394                         (!oneWindowInTab || bufHasFilename))
2395                     [self addInput:@":tabnew<CR>"];
2397                 // The files are opened by constructing a ":drop ..." command
2398                 // and executing it.
2399                 NSMutableString *cmd = (WIN_TABS == layout)
2400                         ? [NSMutableString stringWithString:@":tab drop"]
2401                         : [NSMutableString stringWithString:@":drop"];
2403                 for (i = 0; i < numFiles; ++i) {
2404                     NSString *file = [filenames objectAtIndex:i];
2405                     file = [file stringByEscapingSpecialFilenameCharacters];
2406                     [cmd appendString:@" "];
2407                     [cmd appendString:file];
2408                 }
2410                 // Temporarily clear 'suffixes' so that the files are opened in
2411                 // the same order as they appear in the "filenames" array.
2412                 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2414                 [self addInput:cmd];
2416                 // Split the view into multiple windows if requested.
2417                 if (WIN_HOR == layout)
2418                     [self addInput:@"|sall"];
2419                 else if (WIN_VER == layout)
2420                     [self addInput:@"|vert sall"];
2422                 // Restore the old value of 'suffixes'.
2423                 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2424             } else {
2425                 // When opening one file we try to reuse the current window,
2426                 // but not if its buffer is modified or has a filename.
2427                 // However, the 'arglist' layout always opens the file in the
2428                 // current window.
2429                 NSString *file = [[filenames lastObject]
2430                         stringByEscapingSpecialFilenameCharacters];
2431                 NSString *cmd;
2432                 if (WIN_HOR == layout) {
2433                     if (!(bufHasFilename || bufChanged))
2434                         cmd = [NSString stringWithFormat:@":e %@", file];
2435                     else
2436                         cmd = [NSString stringWithFormat:@":sp %@", file];
2437                 } else if (WIN_VER == layout) {
2438                     if (!(bufHasFilename || bufChanged))
2439                         cmd = [NSString stringWithFormat:@":e %@", file];
2440                     else
2441                         cmd = [NSString stringWithFormat:@":vsp %@", file];
2442                 } else if (WIN_TABS == layout) {
2443                     if (oneWindowInTab && !(bufHasFilename || bufChanged))
2444                         cmd = [NSString stringWithFormat:@":e %@", file];
2445                     else
2446                         cmd = [NSString stringWithFormat:@":tabe %@", file];
2447                 } else {
2448                     // (The :drop command will split if there is a modified
2449                     // buffer.)
2450                     cmd = [NSString stringWithFormat:@":drop %@", file];
2451                 }
2453                 [self addInput:cmd];
2454                 [self addInput:@"<CR>"];
2455             }
2457             // Force screen redraw (does it have to be this complicated?).
2458             // (This code was taken from the end of gui_handle_drop().)
2459             update_screen(NOT_VALID);
2460             setcursor();
2461             out_flush();
2462             gui_update_cursor(FALSE, FALSE);
2463             maketitle();
2465             flushDisabled = NO;
2466         }
2467     }
2469     if ([args objectForKey:@"remoteID"]) {
2470         // NOTE: We have to delay processing any ODB related arguments since
2471         // the file(s) may not be opened until the input buffer is processed.
2472         [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2473                                withObject:args
2474                             waitUntilDone:NO];
2475     }
2477     NSString *lineString = [args objectForKey:@"cursorLine"];
2478     if (lineString && [lineString intValue] > 0) {
2479         NSString *columnString = [args objectForKey:@"cursorColumn"];
2480         if (!(columnString && [columnString intValue] > 0))
2481             columnString = @"1";
2483         NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2484                 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2485         [self addInput:cmd];
2486     }
2488     NSString *rangeString = [args objectForKey:@"selectionRange"];
2489     if (rangeString) {
2490         // Build a command line string that will select the given range of
2491         // lines.  If range.length == 0, then position the cursor on the given
2492         // line but do not select.
2493         NSRange range = NSRangeFromString(rangeString);
2494         NSString *cmd;
2495         if (range.length > 0) {
2496             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2497                     NSMaxRange(range), range.location];
2498         } else {
2499             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2500                     range.location];
2501         }
2503         [self addInput:cmd];
2504     }
2506     NSString *searchText = [args objectForKey:@"searchText"];
2507     if (searchText) {
2508         // TODO: Searching is an exclusive motion, so if the pattern would
2509         // match on row 0 column 0 then this pattern will miss that match.
2510         [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@<CR>",
2511                 searchText]];
2512     }
2515 - (BOOL)checkForModifiedBuffers
2517     buf_T *buf;
2518     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2519         if (bufIsChanged(buf)) {
2520             return YES;
2521         }
2522     }
2524     return NO;
2527 - (void)addInput:(NSString *)input
2529     // NOTE: This code is essentially identical to server_to_input_buf(),
2530     // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2531     char_u      *str = [input vimStringSave];
2532     char_u      *ptr = NULL;
2533     char_u      *cpo_save = p_cpo;
2535     if (!str) return;
2537     /* Set 'cpoptions' the way we want it.
2538      *    B set - backslashes are *not* treated specially
2539      *    k set - keycodes are *not* reverse-engineered
2540      *    < unset - <Key> sequences *are* interpreted
2541      *  The last but one parameter of replace_termcodes() is TRUE so that the
2542      *  <lt> sequence is recognised - needed for a real backslash.
2543      */
2544     p_cpo = (char_u *)"Bk";
2545     str = replace_termcodes((char_u *)str, &ptr, FALSE, TRUE, FALSE);
2546     p_cpo = cpo_save;
2548     if (*ptr != NUL)    /* trailing CTRL-V results in nothing */
2549     {
2550         /*
2551          * Add the string to the input stream.
2552          * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2553          *
2554          * First clear typed characters from the typeahead buffer, there could
2555          * be half a mapping there.  Then append to the existing string, so
2556          * that multiple commands from a client are concatenated.
2557          */
2558         if (typebuf.tb_maplen < typebuf.tb_len)
2559             del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2560         (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2562         /* Let input_available() know we inserted text in the typeahead
2563          * buffer. */
2564         typebuf_was_filled = TRUE;
2565     }
2566     vim_free((char_u *)ptr);
2567     vim_free(str);
2570 - (BOOL)unusedEditor
2572     BOOL oneWindowInTab = topframe ? YES
2573                                    : (topframe->fr_layout == FR_LEAF);
2574     BOOL bufChanged = NO;
2575     BOOL bufHasFilename = NO;
2576     if (curbuf) {
2577         bufChanged = curbufIsChanged();
2578         bufHasFilename = curbuf->b_ffname != NULL;
2579     }
2581     BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2583     return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2586 - (void)redrawScreen
2588     // Force screen redraw (does it have to be this complicated?).
2589     redraw_all_later(CLEAR);
2590     update_screen(NOT_VALID);
2591     setcursor();
2592     out_flush();
2593     gui_update_cursor(FALSE, FALSE);
2595     // HACK! The cursor is not put back at the command line by the above
2596     // "redraw commands".  The following test seems to do the trick though.
2597     if (State & CMDLINE)
2598         redrawcmdline();
2601 - (void)handleFindReplace:(NSDictionary *)args
2603     if (!args) return;
2605     NSString *findString = [args objectForKey:@"find"];
2606     if (!findString) return;
2608     char_u *find = [findString vimStringSave];
2609     char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2610     int flags = [[args objectForKey:@"flags"] intValue];
2612     // NOTE: The flag 0x100 is used to indicate a backward search.
2613     gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2615     vim_free(find);
2616     vim_free(replace);
2619 @end // MMBackend (Private)
2624 @implementation MMBackend (ClientServer)
2626 - (NSString *)connectionNameFromServerName:(NSString *)name
2628     NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2630     return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2631         lowercaseString];
2634 - (NSConnection *)connectionForServerName:(NSString *)name
2636     // TODO: Try 'name%d' if 'name' fails.
2637     NSString *connName = [self connectionNameFromServerName:name];
2638     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2640     if (!svrConn) {
2641         svrConn = [NSConnection connectionWithRegisteredName:connName
2642                                                            host:nil];
2643         // Try alternate server...
2644         if (!svrConn && alternateServerName) {
2645             //NSLog(@"  trying to connect to alternate server: %@",
2646             //        alternateServerName);
2647             connName = [self connectionNameFromServerName:alternateServerName];
2648             svrConn = [NSConnection connectionWithRegisteredName:connName
2649                                                             host:nil];
2650         }
2652         // Try looking for alternate servers...
2653         if (!svrConn) {
2654             //NSLog(@"  looking for alternate servers...");
2655             NSString *alt = [self alternateServerNameForName:name];
2656             if (alt != alternateServerName) {
2657                 //NSLog(@"  found alternate server: %@", string);
2658                 [alternateServerName release];
2659                 alternateServerName = [alt copy];
2660             }
2661         }
2663         // Try alternate server again...
2664         if (!svrConn && alternateServerName) {
2665             //NSLog(@"  trying to connect to alternate server: %@",
2666             //        alternateServerName);
2667             connName = [self connectionNameFromServerName:alternateServerName];
2668             svrConn = [NSConnection connectionWithRegisteredName:connName
2669                                                             host:nil];
2670         }
2672         if (svrConn) {
2673             [connectionNameDict setObject:svrConn forKey:connName];
2675             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2676             [[NSNotificationCenter defaultCenter] addObserver:self
2677                     selector:@selector(serverConnectionDidDie:)
2678                         name:NSConnectionDidDieNotification object:svrConn];
2679         }
2680     }
2682     return svrConn;
2685 - (NSConnection *)connectionForServerPort:(int)port
2687     NSConnection *conn;
2688     NSEnumerator *e = [connectionNameDict objectEnumerator];
2690     while ((conn = [e nextObject])) {
2691         // HACK! Assume connection uses mach ports.
2692         if (port == [(NSMachPort*)[conn sendPort] machPort])
2693             return conn;
2694     }
2696     return nil;
2699 - (void)serverConnectionDidDie:(NSNotification *)notification
2701     //NSLog(@"%s%@", _cmd, notification);
2703     NSConnection *svrConn = [notification object];
2705     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2706     [[NSNotificationCenter defaultCenter]
2707             removeObserver:self
2708                       name:NSConnectionDidDieNotification
2709                     object:svrConn];
2711     [connectionNameDict removeObjectsForKeys:
2712         [connectionNameDict allKeysForObject:svrConn]];
2714     // HACK! Assume connection uses mach ports.
2715     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2716     NSNumber *key = [NSNumber numberWithInt:port];
2718     [clientProxyDict removeObjectForKey:key];
2719     [serverReplyDict removeObjectForKey:key];
2722 - (void)addClient:(NSDistantObject *)client
2724     NSConnection *conn = [client connectionForProxy];
2725     // HACK! Assume connection uses mach ports.
2726     int port = [(NSMachPort*)[conn sendPort] machPort];
2727     NSNumber *key = [NSNumber numberWithInt:port];
2729     if (![clientProxyDict objectForKey:key]) {
2730         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2731         [clientProxyDict setObject:client forKey:key];
2732     }
2734     // NOTE: 'clientWindow' is a global variable which is used by <client>
2735     clientWindow = port;
2738 - (NSString *)alternateServerNameForName:(NSString *)name
2740     if (!(name && [name length] > 0))
2741         return nil;
2743     // Only look for alternates if 'name' doesn't end in a digit.
2744     unichar lastChar = [name characterAtIndex:[name length]-1];
2745     if (lastChar >= '0' && lastChar <= '9')
2746         return nil;
2748     // Look for alternates among all current servers.
2749     NSArray *list = [self serverList];
2750     if (!(list && [list count] > 0))
2751         return nil;
2753     // Filter out servers starting with 'name' and ending with a number. The
2754     // (?i) pattern ensures that the match is case insensitive.
2755     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2756     NSPredicate *pred = [NSPredicate predicateWithFormat:
2757             @"SELF MATCHES %@", pat];
2758     list = [list filteredArrayUsingPredicate:pred];
2759     if ([list count] > 0) {
2760         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2761         return [list objectAtIndex:0];
2762     }
2764     return nil;
2767 @end // MMBackend (ClientServer)
2772 @implementation NSString (MMServerNameCompare)
2773 - (NSComparisonResult)serverNameCompare:(NSString *)string
2775     return [self compare:string
2776                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2778 @end
2783 static int eventModifierFlagsToVimModMask(int modifierFlags)
2785     int modMask = 0;
2787     if (modifierFlags & NSShiftKeyMask)
2788         modMask |= MOD_MASK_SHIFT;
2789     if (modifierFlags & NSControlKeyMask)
2790         modMask |= MOD_MASK_CTRL;
2791     if (modifierFlags & NSAlternateKeyMask)
2792         modMask |= MOD_MASK_ALT;
2793     if (modifierFlags & NSCommandKeyMask)
2794         modMask |= MOD_MASK_CMD;
2796     return modMask;
2799 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2801     int modMask = 0;
2803     if (modifierFlags & NSShiftKeyMask)
2804         modMask |= MOUSE_SHIFT;
2805     if (modifierFlags & NSControlKeyMask)
2806         modMask |= MOUSE_CTRL;
2807     if (modifierFlags & NSAlternateKeyMask)
2808         modMask |= MOUSE_ALT;
2810     return modMask;
2813 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2815     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2817     return (buttonNumber >= 0 && buttonNumber < 3)
2818             ? mouseButton[buttonNumber] : -1;
2823 // This function is modeled after the VimToPython function found in if_python.c
2824 // NB This does a deep copy by value, it does not lookup references like the
2825 // VimToPython function does.  This is because I didn't want to deal with the
2826 // retain cycles that this would create, and we can cover 99% of the use cases
2827 // by ignoring it.  If we ever switch to using GC in MacVim then this
2828 // functionality can be implemented easily.
2829 static id vimToCocoa(typval_T * tv, int depth)
2831     id result = nil;
2832     id newObj = nil;
2835     // Avoid infinite recursion
2836     if (depth > 100) {
2837         return nil;
2838     }
2840     if (tv->v_type == VAR_STRING) {
2841         char_u * val = tv->vval.v_string;
2842         // val can be NULL if the string is empty
2843         if (!val) {
2844             result = [NSString string];
2845         } else {
2846 #ifdef FEAT_MBYTE
2847             val = CONVERT_TO_UTF8(val);
2848 #endif
2849             result = [NSString stringWithUTF8String:(char*)val];
2850 #ifdef FEAT_MBYTE
2851             CONVERT_TO_UTF8_FREE(val);
2852 #endif
2853         }
2854     } else if (tv->v_type == VAR_NUMBER) {
2855         // looks like sizeof(varnumber_T) is always <= sizeof(long)
2856         result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2857     } else if (tv->v_type == VAR_LIST) {
2858         list_T * list = tv->vval.v_list;
2859         listitem_T * curr;
2861         NSMutableArray * arr = result = [NSMutableArray array];
2863         if (list != NULL) {
2864             for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2865                 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2866                 [arr addObject:newObj];
2867             }
2868         }
2869     } else if (tv->v_type == VAR_DICT) {
2870         NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2872         if (tv->vval.v_dict != NULL) {
2873             hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2874             int todo = ht->ht_used;
2875             hashitem_T * hi;
2876             dictitem_T * di;
2878             for (hi = ht->ht_array; todo > 0; ++hi) {
2879                 if (!HASHITEM_EMPTY(hi)) {
2880                     --todo;
2882                     di = dict_lookup(hi);
2883                     newObj = vimToCocoa(&di->di_tv, depth + 1);
2885                     char_u * keyval = hi->hi_key;
2886 #ifdef FEAT_MBYTE
2887                     keyval = CONVERT_TO_UTF8(keyval);
2888 #endif
2889                     NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2890 #ifdef FEAT_MBYTE
2891                     CONVERT_TO_UTF8_FREE(keyval);
2892 #endif
2893                     [dict setObject:newObj forKey:key];
2894                 }
2895             }
2896         }
2897     } else { // only func refs should fall into this category?
2898         result = nil;
2899     }
2901     return result;
2905 // This function is modeled after eval_client_expr_to_string found in main.c
2906 // Returns nil if there was an error evaluating the expression, and writes a
2907 // message to errorStr.
2908 // TODO Get the error that occurred while evaluating the expression in vim
2909 // somehow.
2910 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2913     char_u *s = (char_u*)[expr UTF8String];
2915 #ifdef FEAT_MBYTE
2916     s = CONVERT_FROM_UTF8(s);
2917 #endif
2919     int save_dbl = debug_break_level;
2920     int save_ro = redir_off;
2922     debug_break_level = -1;
2923     redir_off = 0;
2924     ++emsg_skip;
2926     typval_T * tvres = eval_expr(s, NULL);
2928     debug_break_level = save_dbl;
2929     redir_off = save_ro;
2930     --emsg_skip;
2932     setcursor();
2933     out_flush();
2935 #ifdef FEAT_MBYTE
2936     CONVERT_FROM_UTF8_FREE(s);
2937 #endif
2939 #ifdef FEAT_GUI
2940     if (gui.in_use)
2941         gui_update_cursor(FALSE, FALSE);
2942 #endif
2944     if (tvres == NULL) {
2945         free_tv(tvres);
2946         *errstr = @"Expression evaluation failed.";
2947     }
2949     id res = vimToCocoa(tvres, 1);
2951     free_tv(tvres);
2953     if (res == nil) {
2954         *errstr = @"Conversion to cocoa values failed.";
2955     }
2957     return res;
2962 @implementation NSString (VimStrings)
2964 + (id)stringWithVimString:(char_u *)s
2966     // This method ensures a non-nil string is returned.  If 's' cannot be
2967     // converted to a utf-8 string it is assumed to be latin-1.  If conversion
2968     // still fails an empty NSString is returned.
2969     NSString *string = nil;
2970     if (s) {
2971 #ifdef FEAT_MBYTE
2972         s = CONVERT_TO_UTF8(s);
2973 #endif
2974         string = [NSString stringWithUTF8String:(char*)s];
2975         if (!string) {
2976             // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2977             // latin-1?
2978             string = [NSString stringWithCString:(char*)s
2979                                         encoding:NSISOLatin1StringEncoding];
2980         }
2981 #ifdef FEAT_MBYTE
2982         CONVERT_TO_UTF8_FREE(s);
2983 #endif
2984     }
2986     return string != nil ? string : [NSString string];
2989 - (char_u *)vimStringSave
2991     char_u *s = (char_u*)[self UTF8String], *ret = NULL;
2993 #ifdef FEAT_MBYTE
2994     s = CONVERT_FROM_UTF8(s);
2995 #endif
2996     ret = vim_strsave(s);
2997 #ifdef FEAT_MBYTE
2998     CONVERT_FROM_UTF8_FREE(s);
2999 #endif
3001     return ret;
3004 @end // NSString (VimStrings)