Clean up process termination code
[MacVim.git] / src / MacVim / MMBackend.m
blobb7bc73d250f356940bfb4cde47e665bb19c1f985
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
11  * MMBackend
12  *
13  * MMBackend communicates with the frontend (MacVim).  It maintains a queue of
14  * output which is flushed to the frontend under controlled circumstances (so
15  * as to maintain a steady framerate).  Input from the frontend is also handled
16  * here.
17  *
18  * The frontend communicates with the backend via the MMBackendProtocol.  In
19  * particular, input is sent to the backend via processInput:data: and Vim
20  * state can be queried from the frontend with evaluateExpression:.
21  *
22  * It is very important to realize that all state is held by the backend, the
23  * frontend must either ask for state [MMBackend evaluateExpression:] or wait
24  * for the backend to update [MMVimController processCommandQueue:].
25  *
26  * The client/server functionality of Vim is handled by the backend.  It sets
27  * up a named NSConnection to which other Vim processes can connect.
28  */
30 #import "MMBackend.h"
34 // NOTE: Colors in MMBackend are stored as unsigned ints on the form 0xaarrggbb
35 // whereas colors in Vim are int without the alpha component.  Also note that
36 // 'transp' is assumed to be a value between 0 and 100.
37 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
38 #define MM_COLOR_WITH_TRANSP(col,transp) \
39     ((unsigned)( ((col)&0xffffff) \
40         | ((((unsigned)((((100-(transp))*255)/100)+.5f))&0xff)<<24) ))
42 // Values for window layout (must match values in main.c).
43 #define WIN_HOR     1       // "-o" horizontally split windows
44 #define WIN_VER     2       // "-O" vertically split windows
45 #define WIN_TABS    3       // "-p" windows on tab pages
47 static unsigned MMServerMax = 1000;
49 // TODO: Move to separate file.
50 static int eventModifierFlagsToVimModMask(int modifierFlags);
51 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
52 static int eventButtonNumberToVimMouseButton(int buttonNumber);
54 // In gui_macvim.m
55 vimmenu_T *menu_for_descriptor(NSArray *desc);
57 static id evalExprCocoa(NSString * expr, NSString ** errstr);
60 enum {
61     MMBlinkStateNone = 0,
62     MMBlinkStateOn,
63     MMBlinkStateOff
66 static NSString *MMSymlinkWarningString =
67     @"\n\n\tMost likely this is because you have symlinked directly to\n"
68      "\tthe Vim binary, which Cocoa does not allow.  Please use an\n"
69      "\talias or the mvim shell script instead.  If you have not used\n"
70      "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
73 extern GuiFont gui_mch_retain_font(GuiFont font);
77 @interface NSString (MMServerNameCompare)
78 - (NSComparisonResult)serverNameCompare:(NSString *)string;
79 @end
84 @interface MMBackend (Private)
85 - (void)waitForDialogReturn;
86 - (void)insertVimStateMessage;
87 - (void)processInputQueue;
88 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
89 + (NSDictionary *)specialKeys;
90 - (void)handleInsertText:(NSString *)text;
91 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
92 - (void)queueMessage:(int)msgid data:(NSData *)data;
93 - (void)connectionDidDie:(NSNotification *)notification;
94 - (void)blinkTimerFired:(NSTimer *)timer;
95 - (void)focusChange:(BOOL)on;
96 - (void)handleToggleToolbar;
97 - (void)handleScrollbarEvent:(NSData *)data;
98 - (void)handleSetFont:(NSData *)data;
99 - (void)handleDropFiles:(NSData *)data;
100 - (void)handleDropString:(NSData *)data;
101 - (void)startOdbEditWithArguments:(NSDictionary *)args;
102 - (void)handleXcodeMod:(NSData *)data;
103 - (void)handleOpenWithArguments:(NSDictionary *)args;
104 - (BOOL)checkForModifiedBuffers;
105 - (void)addInput:(NSString *)input;
106 - (BOOL)unusedEditor;
107 - (void)redrawScreen;
108 - (void)handleFindReplace:(NSDictionary *)args;
109 @end
113 @interface MMBackend (ClientServer)
114 - (NSString *)connectionNameFromServerName:(NSString *)name;
115 - (NSConnection *)connectionForServerName:(NSString *)name;
116 - (NSConnection *)connectionForServerPort:(int)port;
117 - (void)serverConnectionDidDie:(NSNotification *)notification;
118 - (void)addClient:(NSDistantObject *)client;
119 - (NSString *)alternateServerNameForName:(NSString *)name;
120 @end
124 @implementation MMBackend
126 + (MMBackend *)sharedInstance
128     static MMBackend *singleton = nil;
129     return singleton ? singleton : (singleton = [MMBackend new]);
132 - (id)init
134     self = [super init];
135     if (!self) return nil;
137     outputQueue = [[NSMutableArray alloc] init];
138     inputQueue = [[NSMutableArray alloc] init];
139     drawData = [[NSMutableData alloc] initWithCapacity:1024];
140     connectionNameDict = [[NSMutableDictionary alloc] init];
141     clientProxyDict = [[NSMutableDictionary alloc] init];
142     serverReplyDict = [[NSMutableDictionary alloc] init];
144     NSBundle *mainBundle = [NSBundle mainBundle];
145     NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
146     if (path)
147         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
149     path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
150     if (path)
151         sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
152             retain];
154     path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
155     if (path)
156         actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
158     if (!(colorDict && sysColorDict && actionDict))
159         NSLog(@"ERROR: Failed to load dictionaries.%@",
160                 MMSymlinkWarningString);
162     return self;
165 - (void)dealloc
167     //NSLog(@"%@ %s", [self className], _cmd);
168     [[NSNotificationCenter defaultCenter] removeObserver:self];
170     gui_mch_free_font(oldWideFont);  oldWideFont = NOFONT;
171     [blinkTimer release];  blinkTimer = nil;
172     [alternateServerName release];  alternateServerName = nil;
173     [serverReplyDict release];  serverReplyDict = nil;
174     [clientProxyDict release];  clientProxyDict = nil;
175     [connectionNameDict release];  connectionNameDict = nil;
176     [inputQueue release];  inputQueue = nil;
177     [outputQueue release];  outputQueue = nil;
178     [drawData release];  drawData = nil;
179     [frontendProxy release];  frontendProxy = nil;
180     [connection release];  connection = nil;
181     [actionDict release];  actionDict = nil;
182     [sysColorDict release];  sysColorDict = nil;
183     [colorDict release];  colorDict = nil;
185     [super dealloc];
188 - (void)setBackgroundColor:(int)color
190     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
193 - (void)setForegroundColor:(int)color
195     foregroundColor = MM_COLOR(color);
198 - (void)setSpecialColor:(int)color
200     specialColor = MM_COLOR(color);
203 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
205     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
206     defaultForegroundColor = MM_COLOR(fg);
208     NSMutableData *data = [NSMutableData data];
210     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
211     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
213     [self queueMessage:SetDefaultColorsMsgID data:data];
216 - (NSConnection *)connection
218     if (!connection) {
219         // NOTE!  If the name of the connection changes here it must also be
220         // updated in MMAppController.m.
221         NSString *name = [NSString stringWithFormat:@"%@-connection",
222                [[NSBundle mainBundle] bundlePath]];
224         connection = [NSConnection connectionWithRegisteredName:name host:nil];
225         [connection retain];
226     }
228     // NOTE: 'connection' may be nil here.
229     return connection;
232 - (NSDictionary *)actionDict
234     return actionDict;
237 - (int)initialWindowLayout
239     return initialWindowLayout;
242 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
244     [self queueMessage:msgid data:[props dictionaryAsData]];
247 - (BOOL)checkin
249     if (![self connection]) {
250         if (waitForAck) {
251             // This is a preloaded process and as such should not cause the
252             // MacVim to be opened.  We probably got here as a result of the
253             // user quitting MacVim while the process was preloading, so exit
254             // this process too.
255             // (Don't use mch_exit() since it assumes the process has properly
256             // started.)
257             exit(0);
258         }
260         NSBundle *mainBundle = [NSBundle mainBundle];
261 #if 0
262         OSStatus status;
263         FSRef ref;
265         // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
266         // the API to pass Apple Event parameters is broken on 10.4).
267         NSString *path = [mainBundle bundlePath];
268         status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
269         if (noErr == status) {
270             // Pass parameter to the 'Open' Apple Event that tells MacVim not
271             // to open an untitled window.
272             NSAppleEventDescriptor *desc =
273                     [NSAppleEventDescriptor recordDescriptor];
274             [desc setParamDescriptor:
275                     [NSAppleEventDescriptor descriptorWithBoolean:NO]
276                           forKeyword:keyMMUntitledWindow];
278             LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
279                     kLSLaunchDefaults, NULL };
280             status = LSOpenFromRefSpec(&spec, NULL);
281         }
283         if (noErr != status) {
284         NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
285                 path, MMSymlinkWarningString);
286             return NO;
287         }
288 #else
289         // Launch MacVim using NSTask.  For some reason the above code using
290         // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
291         // fails, the dock icon starts bouncing and never stops).  It seems
292         // like rebuilding the Launch Services database takes care of this
293         // problem, but the NSTask way seems more stable so stick with it.
294         //
295         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
296         // that the GUI won't be activated (or raised) so there is a hack in
297         // MMAppController which raises the app when a new window is opened.
298         NSMutableArray *args = [NSMutableArray arrayWithObjects:
299             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
300         NSString *exeName = [[mainBundle infoDictionary]
301                 objectForKey:@"CFBundleExecutable"];
302         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
303         if (!path) {
304             NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
305                     MMSymlinkWarningString);
306             return NO;
307         }
309         [NSTask launchedTaskWithLaunchPath:path arguments:args];
310 #endif
312         // HACK!  Poll the mach bootstrap server until it returns a valid
313         // connection to detect that MacVim has finished launching.  Also set a
314         // time-out date so that we don't get stuck doing this forever.
315         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
316         while (![self connection] &&
317                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
318             [[NSRunLoop currentRunLoop]
319                     runMode:NSDefaultRunLoopMode
320                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
322         // NOTE: [self connection] will set 'connection' as a side-effect.
323         if (!connection) {
324             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
325             return NO;
326         }
327     }
329     BOOL ok = NO;
330     @try {
331         [[NSNotificationCenter defaultCenter] addObserver:self
332                 selector:@selector(connectionDidDie:)
333                     name:NSConnectionDidDieNotification object:connection];
335         id proxy = [connection rootProxy];
336         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
338         int pid = [[NSProcessInfo processInfo] processIdentifier];
340         frontendProxy = [proxy connectBackend:self pid:pid];
341         if (frontendProxy) {
342             [frontendProxy retain];
343             [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
344             ok = YES;
345         }
346     }
347     @catch (NSException *e) {
348         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
349     }
351     return ok;
354 - (BOOL)openGUIWindow
356     [self queueMessage:OpenWindowMsgID data:nil];
357     return YES;
360 - (void)clearAll
362     int type = ClearAllDrawType;
364     // Any draw commands in queue are effectively obsolete since this clearAll
365     // will negate any effect they have, therefore we may as well clear the
366     // draw queue.
367     [drawData setLength:0];
369     [drawData appendBytes:&type length:sizeof(int)];
372 - (void)clearBlockFromRow:(int)row1 column:(int)col1
373                     toRow:(int)row2 column:(int)col2
375     int type = ClearBlockDrawType;
377     [drawData appendBytes:&type length:sizeof(int)];
379     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
380     [drawData appendBytes:&row1 length:sizeof(int)];
381     [drawData appendBytes:&col1 length:sizeof(int)];
382     [drawData appendBytes:&row2 length:sizeof(int)];
383     [drawData appendBytes:&col2 length:sizeof(int)];
386 - (void)deleteLinesFromRow:(int)row count:(int)count
387               scrollBottom:(int)bottom left:(int)left right:(int)right
389     int type = DeleteLinesDrawType;
391     [drawData appendBytes:&type length:sizeof(int)];
393     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
394     [drawData appendBytes:&row length:sizeof(int)];
395     [drawData appendBytes:&count length:sizeof(int)];
396     [drawData appendBytes:&bottom length:sizeof(int)];
397     [drawData appendBytes:&left length:sizeof(int)];
398     [drawData appendBytes:&right length:sizeof(int)];
401 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
402              cells:(int)cells flags:(int)flags
404     if (len <= 0 || cells <= 0) return;
406     int type = DrawStringDrawType;
408     [drawData appendBytes:&type length:sizeof(int)];
410     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
411     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
412     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
413     [drawData appendBytes:&row length:sizeof(int)];
414     [drawData appendBytes:&col length:sizeof(int)];
415     [drawData appendBytes:&cells length:sizeof(int)];
416     [drawData appendBytes:&flags length:sizeof(int)];
417     [drawData appendBytes:&len length:sizeof(int)];
418     [drawData appendBytes:s length:len];
421 - (void)insertLinesFromRow:(int)row count:(int)count
422               scrollBottom:(int)bottom left:(int)left right:(int)right
424     int type = InsertLinesDrawType;
426     [drawData appendBytes:&type length:sizeof(int)];
428     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
429     [drawData appendBytes:&row length:sizeof(int)];
430     [drawData appendBytes:&count length:sizeof(int)];
431     [drawData appendBytes:&bottom length:sizeof(int)];
432     [drawData appendBytes:&left length:sizeof(int)];
433     [drawData appendBytes:&right length:sizeof(int)];
436 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
437                fraction:(int)percent color:(int)color
439     int type = DrawCursorDrawType;
440     unsigned uc = MM_COLOR(color);
442     [drawData appendBytes:&type length:sizeof(int)];
444     [drawData appendBytes:&uc length:sizeof(unsigned)];
445     [drawData appendBytes:&row length:sizeof(int)];
446     [drawData appendBytes:&col length:sizeof(int)];
447     [drawData appendBytes:&shape length:sizeof(int)];
448     [drawData appendBytes:&percent length:sizeof(int)];
451 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
452                    numColumns:(int)nc invert:(int)invert
454     int type = DrawInvertedRectDrawType;
455     [drawData appendBytes:&type length:sizeof(int)];
457     [drawData appendBytes:&row length:sizeof(int)];
458     [drawData appendBytes:&col length:sizeof(int)];
459     [drawData appendBytes:&nr length:sizeof(int)];
460     [drawData appendBytes:&nc length:sizeof(int)];
461     [drawData appendBytes:&invert length:sizeof(int)];
464 - (void)update
466     // Keep running the run-loop until there is no more input to process.
467     while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
468             == kCFRunLoopRunHandledSource)
469         ;   // do nothing
472 - (void)flushQueue:(BOOL)force
474     // NOTE: This variable allows for better control over when the queue is
475     // flushed.  It can be set to YES at the beginning of a sequence of calls
476     // that may potentially add items to the queue, and then restored back to
477     // NO.
478     if (flushDisabled) return;
480     if ([drawData length] > 0) {
481         // HACK!  Detect changes to 'guifontwide'.
482         if (gui.wide_font != oldWideFont) {
483             gui_mch_free_font(oldWideFont);
484             oldWideFont = gui_mch_retain_font(gui.wide_font);
485             [self setFont:oldWideFont wide:YES];
486         }
488         int type = SetCursorPosDrawType;
489         [drawData appendBytes:&type length:sizeof(type)];
490         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
491         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
493         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
494         [drawData setLength:0];
495     }
497     if ([outputQueue count] > 0) {
498         [self insertVimStateMessage];
500         @try {
501             [frontendProxy processCommandQueue:outputQueue];
502         }
503         @catch (NSException *e) {
504             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
505             NSLog(@"outputQueue(len:%d)=%@", [outputQueue count]/2,
506                     outputQueue);
507             if (![connection isValid]) {
508                 NSLog(@"WARNING! Connection is invalid, exit now!");
509                 NSLog(@"waitForAck=%d got_int=%d", waitForAck, got_int);
510                 mch_exit(-1);
511             }
512         }
514         [outputQueue removeAllObjects];
515     }
518 - (BOOL)waitForInput:(int)milliseconds
520     // Return NO if we timed out waiting for input, otherwise return YES.
521     BOOL inputReceived = NO;
523     // Only start the run loop if the input queue is empty, otherwise process
524     // the input first so that the input on queue isn't delayed.
525     if ([inputQueue count]) {
526         inputReceived = YES;
527     } else {
528         // Wait for the specified amount of time, unless 'milliseconds' is
529         // negative in which case we wait "forever" (1e6 seconds translates to
530         // approximately 11 days).
531         CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
533         while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
534                 == kCFRunLoopRunHandledSource) {
535             // In order to ensure that all input on the run-loop has been
536             // processed we set the timeout to 0 and keep processing until the
537             // run-loop times out.
538             dt = 0.0;
539             inputReceived = YES;
540         }
541     }
543     // The above calls may have placed messages on the input queue so process
544     // it now.  This call may enter a blocking loop.
545     if ([inputQueue count] > 0)
546         [self processInputQueue];
548     return inputReceived;
551 - (void)exit
553     // NOTE: This is called if mch_exit() is called.  Since we assume here that
554     // the process has started properly, be sure to use exit() instead of
555     // mch_exit() to prematurely terminate a process (or set 'isTerminating'
556     // first).
558     // Make sure no connectionDidDie: notification is received now that we are
559     // already exiting.
560     [[NSNotificationCenter defaultCenter] removeObserver:self];
562     // The 'isTerminating' flag indicates that the frontend is also exiting so
563     // there is no need to flush any more output since the frontend won't look
564     // at it anyway.
565     if (!isTerminating && [connection isValid]) {
566         @try {
567             // Flush the entire queue in case a VimLeave autocommand added
568             // something to the queue.
569             [self queueMessage:CloseWindowMsgID data:nil];
570             [frontendProxy processCommandQueue:outputQueue];
571         }
572         @catch (NSException *e) {
573             NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
574         }
576         // NOTE: If Cmd-w was pressed to close the window the menu is briefly
577         // highlighted and during this pause the frontend won't receive any DO
578         // messages.  If the Vim process exits before this highlighting has
579         // finished Cocoa will emit the following error message:
580         //   *** -[NSMachPort handlePortMessage:]: dropping incoming DO message
581         //   because the connection or ports are invalid
582         // To avoid this warning we delay here.  If the warning still appears
583         // this delay may need to be increased.
584         usleep(150000);
585     }
587 #ifdef MAC_CLIENTSERVER
588     // The default connection is used for the client/server code.
589     [[NSConnection defaultConnection] setRootObject:nil];
590     [[NSConnection defaultConnection] invalidate];
591 #endif
594 - (void)selectTab:(int)index
596     //NSLog(@"%s%d", _cmd, index);
598     index -= 1;
599     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
600     [self queueMessage:SelectTabMsgID data:data];
603 - (void)updateTabBar
605     //NSLog(@"%s", _cmd);
607     NSMutableData *data = [NSMutableData data];
609     int idx = tabpage_index(curtab) - 1;
610     [data appendBytes:&idx length:sizeof(int)];
612     tabpage_T *tp;
613     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
614         // Count the number of windows in the tabpage.
615         //win_T *wp = tp->tp_firstwin;
616         //int wincount;
617         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
618         //[data appendBytes:&wincount length:sizeof(int)];
620         int tabProp = MMTabInfoCount;
621         [data appendBytes:&tabProp length:sizeof(int)];
622         for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
623             // This function puts the label of the tab in the global 'NameBuff'.
624             get_tabline_label(tp, (tabProp == MMTabToolTip));
625             NSString *s = [NSString stringWithVimString:NameBuff];
626             int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
627             if (len < 0)
628                 len = 0;
630             [data appendBytes:&len length:sizeof(int)];
631             if (len > 0)
632                 [data appendBytes:[s UTF8String] length:len];
633         }
634     }
636     [self queueMessage:UpdateTabBarMsgID data:data];
639 - (BOOL)tabBarVisible
641     return tabBarVisible;
644 - (void)showTabBar:(BOOL)enable
646     tabBarVisible = enable;
648     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
649     [self queueMessage:msgid data:nil];
652 - (void)setRows:(int)rows columns:(int)cols
654     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
656     int dim[] = { rows, cols };
657     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
659     [self queueMessage:SetTextDimensionsMsgID data:data];
662 - (void)setWindowTitle:(char *)title
664     NSMutableData *data = [NSMutableData data];
665     int len = strlen(title);
666     if (len <= 0) return;
668     [data appendBytes:&len length:sizeof(int)];
669     [data appendBytes:title length:len];
671     [self queueMessage:SetWindowTitleMsgID data:data];
674 - (void)setDocumentFilename:(char *)filename
676     NSMutableData *data = [NSMutableData data];
677     int len = filename ? strlen(filename) : 0;
679     [data appendBytes:&len length:sizeof(int)];
680     if (len > 0)
681         [data appendBytes:filename length:len];
683     [self queueMessage:SetDocumentFilenameMsgID data:data];
686 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
688     char_u *s = NULL;
690     @try {
691         [frontendProxy showSavePanelWithAttributes:attr];
693         [self waitForDialogReturn];
695         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
696             s = [dialogReturn vimStringSave];
698         [dialogReturn release];  dialogReturn = nil;
699     }
700     @catch (NSException *e) {
701         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
702     }
704     return (char *)s;
707 - (oneway void)setDialogReturn:(in bycopy id)obj
709     // NOTE: This is called by
710     //   - [MMVimController panelDidEnd:::], and
711     //   - [MMVimController alertDidEnd:::],
712     // to indicate that a save/open panel or alert has finished.
714     // We want to distinguish between "no dialog return yet" and "dialog
715     // returned nothing".  The former can be tested with dialogReturn == nil,
716     // the latter with dialogReturn == [NSNull null].
717     if (!obj) obj = [NSNull null];
719     if (obj != dialogReturn) {
720         [dialogReturn release];
721         dialogReturn = [obj retain];
722     }
725 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
727     int retval = 0;
729     @try {
730         [frontendProxy presentDialogWithAttributes:attr];
732         [self waitForDialogReturn];
734         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
735                 && [dialogReturn count]) {
736             retval = [[dialogReturn objectAtIndex:0] intValue];
737             if (txtfield && [dialogReturn count] > 1) {
738                 NSString *retString = [dialogReturn objectAtIndex:1];
739                 char_u *ret = (char_u*)[retString UTF8String];
740 #ifdef FEAT_MBYTE
741                 ret = CONVERT_FROM_UTF8(ret);
742 #endif
743                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
744 #ifdef FEAT_MBYTE
745                 CONVERT_FROM_UTF8_FREE(ret);
746 #endif
747             }
748         }
750         [dialogReturn release]; dialogReturn = nil;
751     }
752     @catch (NSException *e) {
753         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
754     }
756     return retval;
759 - (void)showToolbar:(int)enable flags:(int)flags
761     NSMutableData *data = [NSMutableData data];
763     [data appendBytes:&enable length:sizeof(int)];
764     [data appendBytes:&flags length:sizeof(int)];
766     [self queueMessage:ShowToolbarMsgID data:data];
769 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
771     NSMutableData *data = [NSMutableData data];
773     [data appendBytes:&ident length:sizeof(long)];
774     [data appendBytes:&type length:sizeof(int)];
776     [self queueMessage:CreateScrollbarMsgID data:data];
779 - (void)destroyScrollbarWithIdentifier:(long)ident
781     NSMutableData *data = [NSMutableData data];
782     [data appendBytes:&ident length:sizeof(long)];
784     [self queueMessage:DestroyScrollbarMsgID data:data];
787 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
789     NSMutableData *data = [NSMutableData data];
791     [data appendBytes:&ident length:sizeof(long)];
792     [data appendBytes:&visible length:sizeof(int)];
794     [self queueMessage:ShowScrollbarMsgID data:data];
797 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
799     NSMutableData *data = [NSMutableData data];
801     [data appendBytes:&ident length:sizeof(long)];
802     [data appendBytes:&pos length:sizeof(int)];
803     [data appendBytes:&len length:sizeof(int)];
805     [self queueMessage:SetScrollbarPositionMsgID data:data];
808 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
809                     identifier:(long)ident
811     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
812     float prop = (float)size/(max+1);
813     if (fval < 0) fval = 0;
814     else if (fval > 1.0f) fval = 1.0f;
815     if (prop < 0) prop = 0;
816     else if (prop > 1.0f) prop = 1.0f;
818     NSMutableData *data = [NSMutableData data];
820     [data appendBytes:&ident length:sizeof(long)];
821     [data appendBytes:&fval length:sizeof(float)];
822     [data appendBytes:&prop length:sizeof(float)];
824     [self queueMessage:SetScrollbarThumbMsgID data:data];
827 - (void)setFont:(GuiFont)font wide:(BOOL)wide
829     NSString *fontName = (NSString *)font;
830     float size = 0;
831     NSArray *components = [fontName componentsSeparatedByString:@":"];
832     if ([components count] == 2) {
833         size = [[components lastObject] floatValue];
834         fontName = [components objectAtIndex:0];
835     }
837     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
838     NSMutableData *data = [NSMutableData data];
839     [data appendBytes:&size length:sizeof(float)];
840     [data appendBytes:&len length:sizeof(int)];
842     if (len > 0)
843         [data appendBytes:[fontName UTF8String] length:len];
844     else if (!wide)
845         return;     // Only the wide font can be set to nothing
847     [self queueMessage:(wide ? SetWideFontMsgID : SetFontMsgID) data:data];
850 - (void)executeActionWithName:(NSString *)name
852     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
854     if (len > 0) {
855         NSMutableData *data = [NSMutableData data];
857         [data appendBytes:&len length:sizeof(int)];
858         [data appendBytes:[name UTF8String] length:len];
860         [self queueMessage:ExecuteActionMsgID data:data];
861     }
864 - (void)setMouseShape:(int)shape
866     NSMutableData *data = [NSMutableData data];
867     [data appendBytes:&shape length:sizeof(int)];
868     [self queueMessage:SetMouseShapeMsgID data:data];
871 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
873     // Vim specifies times in milliseconds, whereas Cocoa wants them in
874     // seconds.
875     blinkWaitInterval = .001f*wait;
876     blinkOnInterval = .001f*on;
877     blinkOffInterval = .001f*off;
880 - (void)startBlink
882     if (blinkTimer) {
883         [blinkTimer invalidate];
884         [blinkTimer release];
885         blinkTimer = nil;
886     }
888     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
889             && gui.in_focus) {
890         blinkState = MMBlinkStateOn;
891         blinkTimer =
892             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
893                                               target:self
894                                             selector:@selector(blinkTimerFired:)
895                                             userInfo:nil repeats:NO] retain];
896         gui_update_cursor(TRUE, FALSE);
897         [self flushQueue:YES];
898     }
901 - (void)stopBlink
903     if (MMBlinkStateOff == blinkState) {
904         gui_update_cursor(TRUE, FALSE);
905         [self flushQueue:YES];
906     }
908     blinkState = MMBlinkStateNone;
911 - (void)adjustLinespace:(int)linespace
913     NSMutableData *data = [NSMutableData data];
914     [data appendBytes:&linespace length:sizeof(int)];
915     [self queueMessage:AdjustLinespaceMsgID data:data];
918 - (void)activate
920     [self queueMessage:ActivateMsgID data:nil];
923 - (void)setPreEditRow:(int)row column:(int)col
925     NSMutableData *data = [NSMutableData data];
926     [data appendBytes:&row length:sizeof(int)];
927     [data appendBytes:&col length:sizeof(int)];
928     [self queueMessage:SetPreEditPositionMsgID data:data];
931 - (int)lookupColorWithKey:(NSString *)key
933     if (!(key && [key length] > 0))
934         return INVALCOLOR;
936     NSString *stripKey = [[[[key lowercaseString]
937         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
938             componentsSeparatedByString:@" "]
939                componentsJoinedByString:@""];
941     if (stripKey && [stripKey length] > 0) {
942         // First of all try to lookup key in the color dictionary; note that
943         // all keys in this dictionary are lowercase with no whitespace.
944         id obj = [colorDict objectForKey:stripKey];
945         if (obj) return [obj intValue];
947         // The key was not in the dictionary; is it perhaps of the form
948         // #rrggbb?
949         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
950             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
951             [scanner setScanLocation:1];
952             unsigned hex = 0;
953             if ([scanner scanHexInt:&hex]) {
954                 return (int)hex;
955             }
956         }
958         // As a last resort, check if it is one of the system defined colors.
959         // The keys in this dictionary are also lowercase with no whitespace.
960         obj = [sysColorDict objectForKey:stripKey];
961         if (obj) {
962             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
963             if (col) {
964                 float r, g, b, a;
965                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
966                 [col getRed:&r green:&g blue:&b alpha:&a];
967                 return (((int)(r*255+.5f) & 0xff) << 16)
968                      + (((int)(g*255+.5f) & 0xff) << 8)
969                      +  ((int)(b*255+.5f) & 0xff);
970             }
971         }
972     }
974     //NSLog(@"WARNING: No color with key %@ found.", stripKey);
975     return INVALCOLOR;
978 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
980     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
981     id obj;
983     while ((obj = [e nextObject])) {
984         if ([value isEqual:obj])
985             return YES;
986     }
988     return NO;
991 - (void)enterFullscreen:(int)fuoptions background:(int)bg
993     NSMutableData *data = [NSMutableData data];
994     [data appendBytes:&fuoptions length:sizeof(int)];
995     bg = MM_COLOR(bg);
996     [data appendBytes:&bg length:sizeof(int)];
997     [self queueMessage:EnterFullscreenMsgID data:data];
1000 - (void)leaveFullscreen
1002     [self queueMessage:LeaveFullscreenMsgID data:nil];
1005 - (void)setFullscreenBackgroundColor:(int)color
1007     NSMutableData *data = [NSMutableData data];
1008     color = MM_COLOR(color);
1009     [data appendBytes:&color length:sizeof(int)];
1011     [self queueMessage:SetFullscreenColorMsgID data:data];
1014 - (void)setAntialias:(BOOL)antialias
1016     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1018     [self queueMessage:msgid data:nil];
1021 - (void)updateModifiedFlag
1023     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1024     // vice versa.
1025     int msgid = [self checkForModifiedBuffers]
1026             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1028     [self queueMessage:msgid data:nil];
1031 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1033     // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1034     // queue is processed since that only happens in waitForInput: (and Vim
1035     // regularly checks for Ctrl-C in between waiting for input).
1036     // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1037     // which waits on the run loop will fail to detect this message (e.g. in
1038     // waitForConnectionAcknowledgement).
1040     BOOL shouldClearQueue = NO;
1041     if (InterruptMsgID == msgid) {
1042         shouldClearQueue = YES;
1043         got_int = TRUE;
1044     } else if (InsertTextMsgID == msgid && data != nil) {
1045         const void *bytes = [data bytes];
1046         bytes += sizeof(int);
1047         int len = *((int*)bytes);  bytes += sizeof(int);
1048         if (1 == len) {
1049             char_u *str = (char_u*)bytes;
1050             if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1051                     (str[0] == intr_char && intr_char != Ctrl_C)) {
1052                 shouldClearQueue = YES;
1053                 got_int = TRUE;
1054             }
1055         }
1056     } else if (TerminateNowMsgID == msgid) {
1057         // Terminate immediately (the frontend is about to quit or this process
1058         // was aborted).
1059         isTerminating = YES;
1060         mch_exit(0);
1061         return;
1062     }
1064     if (shouldClearQueue) {
1065         [inputQueue removeAllObjects];
1066         return;
1067     }
1069     // Remove all previous instances of this message from the input queue, else
1070     // the input queue may fill up as a result of Vim not being able to keep up
1071     // with the speed at which new messages are received.
1072     // Keyboard input is never dropped, unless the input represents and
1073     // auto-repeated key.
1075     BOOL isKeyRepeat = NO;
1076     BOOL isKeyboardInput = NO;
1078     if (data && (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1079             CmdKeyMsgID == msgid)) {
1080         isKeyboardInput = YES;
1082         // The lowest bit of the first int is set if this key is a repeat.
1083         int flags = *((int*)[data bytes]);
1084         if (flags & 1)
1085             isKeyRepeat = YES;
1086     }
1088     // Keyboard input is not removed from the queue; repeats are ignored if
1089     // there already is keyboard input on the input queue.
1090     if (isKeyRepeat || !isKeyboardInput) {
1091         int i, count = [inputQueue count];
1092         for (i = 1; i < count; i+=2) {
1093             if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1094                 if (isKeyRepeat)
1095                     return;
1097                 [inputQueue removeObjectAtIndex:i];
1098                 [inputQueue removeObjectAtIndex:i-1];
1099                 break;
1100             }
1101         }
1102     }
1104     [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1105     [inputQueue addObject:(data ? (id)data : [NSNull null])];
1108 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1110     // This is just a convenience method that allows the frontend to delay
1111     // sending messages.
1112     int i, count = [messages count];
1113     for (i = 1; i < count; i+=2)
1114         [self processInput:[[messages objectAtIndex:i-1] intValue]
1115                       data:[messages objectAtIndex:i]];
1118 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1119                   errorString:(out bycopy NSString **)errstr
1121     return evalExprCocoa(expr, errstr);
1125 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1127     NSString *eval = nil;
1128     char_u *s = (char_u*)[expr UTF8String];
1130 #ifdef FEAT_MBYTE
1131     s = CONVERT_FROM_UTF8(s);
1132 #endif
1134     char_u *res = eval_client_expr_to_string(s);
1136 #ifdef FEAT_MBYTE
1137     CONVERT_FROM_UTF8_FREE(s);
1138 #endif
1140     if (res != NULL) {
1141         s = res;
1142 #ifdef FEAT_MBYTE
1143         s = CONVERT_TO_UTF8(s);
1144 #endif
1145         eval = [NSString stringWithUTF8String:(char*)s];
1146 #ifdef FEAT_MBYTE
1147         CONVERT_TO_UTF8_FREE(s);
1148 #endif
1149         vim_free(res);
1150     }
1152     return eval;
1155 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1157     // TODO: This method should share code with clip_mch_request_selection().
1159     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1160         // If there is no pasteboard, return YES to indicate that there is text
1161         // to copy.
1162         if (!pboard)
1163             return YES;
1165         clip_copy_selection();
1167         // Get the text to put on the pasteboard.
1168         long_u llen = 0; char_u *str = 0;
1169         int type = clip_convert_selection(&str, &llen, &clip_star);
1170         if (type < 0)
1171             return NO;
1172         
1173         // TODO: Avoid overflow.
1174         int len = (int)llen;
1175 #ifdef FEAT_MBYTE
1176         if (output_conv.vc_type != CONV_NONE) {
1177             char_u *conv_str = string_convert(&output_conv, str, &len);
1178             if (conv_str) {
1179                 vim_free(str);
1180                 str = conv_str;
1181             }
1182         }
1183 #endif
1185         NSString *string = [[NSString alloc]
1186             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1188         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1189         [pboard declareTypes:types owner:nil];
1190         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1191     
1192         [string release];
1193         vim_free(str);
1195         return ok;
1196     }
1198     return NO;
1201 - (oneway void)addReply:(in bycopy NSString *)reply
1202                  server:(in byref id <MMVimServerProtocol>)server
1204     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1206     // Replies might come at any time and in any order so we keep them in an
1207     // array inside a dictionary with the send port used as key.
1209     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1210     // HACK! Assume connection uses mach ports.
1211     int port = [(NSMachPort*)[conn sendPort] machPort];
1212     NSNumber *key = [NSNumber numberWithInt:port];
1214     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1215     if (!replies) {
1216         replies = [NSMutableArray array];
1217         [serverReplyDict setObject:replies forKey:key];
1218     }
1220     [replies addObject:reply];
1223 - (void)addInput:(in bycopy NSString *)input
1224           client:(in byref id <MMVimClientProtocol>)client
1226     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1228     // NOTE: We don't call addInput: here because it differs from
1229     // server_to_input_buf() in that it always sets the 'silent' flag and we
1230     // don't want the MacVim client/server code to behave differently from
1231     // other platforms.
1232     char_u *s = [input vimStringSave];
1233     server_to_input_buf(s);
1234     vim_free(s);
1236     [self addClient:(id)client];
1239 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1240                  client:(in byref id <MMVimClientProtocol>)client
1242     [self addClient:(id)client];
1243     return [self evaluateExpression:expr];
1246 - (void)registerServerWithName:(NSString *)name
1248     NSString *svrName = name;
1249     NSConnection *svrConn = [NSConnection defaultConnection];
1250     unsigned i;
1252     for (i = 0; i < MMServerMax; ++i) {
1253         NSString *connName = [self connectionNameFromServerName:svrName];
1255         if ([svrConn registerName:connName]) {
1256             //NSLog(@"Registered server with name: %@", svrName);
1258             // TODO: Set request/reply time-outs to something else?
1259             //
1260             // Don't wait for requests (time-out means that the message is
1261             // dropped).
1262             [svrConn setRequestTimeout:0];
1263             //[svrConn setReplyTimeout:MMReplyTimeout];
1264             [svrConn setRootObject:self];
1266             // NOTE: 'serverName' is a global variable
1267             serverName = [svrName vimStringSave];
1268 #ifdef FEAT_EVAL
1269             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1270 #endif
1271 #ifdef FEAT_TITLE
1272             need_maketitle = TRUE;
1273 #endif
1274             [self queueMessage:SetServerNameMsgID data:
1275                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1276             break;
1277         }
1279         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1280     }
1283 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1284                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1285               silent:(BOOL)silent
1287     // NOTE: If 'name' equals 'serverName' then the request is local (client
1288     // and server are the same).  This case is not handled separately, so a
1289     // connection will be set up anyway (this simplifies the code).
1291     NSConnection *conn = [self connectionForServerName:name];
1292     if (!conn) {
1293         if (!silent) {
1294             char_u *s = (char_u*)[name UTF8String];
1295 #ifdef FEAT_MBYTE
1296             s = CONVERT_FROM_UTF8(s);
1297 #endif
1298             EMSG2(_(e_noserver), s);
1299 #ifdef FEAT_MBYTE
1300             CONVERT_FROM_UTF8_FREE(s);
1301 #endif
1302         }
1303         return NO;
1304     }
1306     if (port) {
1307         // HACK! Assume connection uses mach ports.
1308         *port = [(NSMachPort*)[conn sendPort] machPort];
1309     }
1311     id proxy = [conn rootProxy];
1312     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1314     @try {
1315         if (expr) {
1316             NSString *eval = [proxy evaluateExpression:string client:self];
1317             if (reply) {
1318                 if (eval) {
1319                     *reply = [eval vimStringSave];
1320                 } else {
1321                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1322                 }
1323             }
1325             if (!eval)
1326                 return NO;
1327         } else {
1328             [proxy addInput:string client:self];
1329         }
1330     }
1331     @catch (NSException *e) {
1332         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1333         return NO;
1334     }
1336     return YES;
1339 - (NSArray *)serverList
1341     NSArray *list = nil;
1343     if ([self connection]) {
1344         id proxy = [connection rootProxy];
1345         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1347         @try {
1348             list = [proxy serverList];
1349         }
1350         @catch (NSException *e) {
1351             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1352         }
1353     } else {
1354         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1355     }
1357     return list;
1360 - (NSString *)peekForReplyOnPort:(int)port
1362     //NSLog(@"%s%d", _cmd, port);
1364     NSNumber *key = [NSNumber numberWithInt:port];
1365     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1366     if (replies && [replies count]) {
1367         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1368         //        [replies objectAtIndex:0]);
1369         return [replies objectAtIndex:0];
1370     }
1372     //NSLog(@"    No replies");
1373     return nil;
1376 - (NSString *)waitForReplyOnPort:(int)port
1378     //NSLog(@"%s%d", _cmd, port);
1379     
1380     NSConnection *conn = [self connectionForServerPort:port];
1381     if (!conn)
1382         return nil;
1384     NSNumber *key = [NSNumber numberWithInt:port];
1385     NSMutableArray *replies = nil;
1386     NSString *reply = nil;
1388     // Wait for reply as long as the connection to the server is valid (unless
1389     // user interrupts wait with Ctrl-C).
1390     while (!got_int && [conn isValid] &&
1391             !(replies = [serverReplyDict objectForKey:key])) {
1392         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1393                                  beforeDate:[NSDate distantFuture]];
1394     }
1396     if (replies) {
1397         if ([replies count] > 0) {
1398             reply = [[replies objectAtIndex:0] retain];
1399             //NSLog(@"    Got reply: %@", reply);
1400             [replies removeObjectAtIndex:0];
1401             [reply autorelease];
1402         }
1404         if ([replies count] == 0)
1405             [serverReplyDict removeObjectForKey:key];
1406     }
1408     return reply;
1411 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1413     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1414     if (client) {
1415         @try {
1416             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1417             [client addReply:reply server:self];
1418             return YES;
1419         }
1420         @catch (NSException *e) {
1421             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1422         }
1423     } else {
1424         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1425     }
1427     return NO;
1430 - (BOOL)waitForAck
1432     return waitForAck;
1435 - (void)setWaitForAck:(BOOL)yn
1437     waitForAck = yn;
1440 - (void)waitForConnectionAcknowledgement
1442     if (!waitForAck) return;
1444     while (waitForAck && !got_int && [connection isValid]) {
1445         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1446                                  beforeDate:[NSDate distantFuture]];
1447         //NSLog(@"  waitForAck=%d got_int=%d isValid=%d",
1448         //        waitForAck, got_int, [connection isValid]);
1449     }
1451     if (waitForAck) {
1452         // Never received a connection acknowledgement, so die.
1453         [[NSNotificationCenter defaultCenter] removeObserver:self];
1454         [frontendProxy release];  frontendProxy = nil;
1456         // NOTE: We intentionally do not call mch_exit() since this in turn
1457         // will lead to -[MMBackend exit] getting called which we want to
1458         // avoid.
1459         exit(0);
1460     }
1462     [self processInputQueue];
1465 - (oneway void)acknowledgeConnection
1467     //NSLog(@"%s", _cmd);
1468     waitForAck = NO;
1471 @end // MMBackend
1475 @implementation MMBackend (Private)
1477 - (void)waitForDialogReturn
1479     // Keep processing the run loop until a dialog returns.  To avoid getting
1480     // stuck in an endless loop (could happen if the setDialogReturn: message
1481     // was lost) we also do some paranoia checks.
1482     //
1483     // Note that in Cocoa the user can still resize windows and select menu
1484     // items while a sheet is being displayed, so we can't just wait for the
1485     // first message to arrive and assume that is the setDialogReturn: call.
1487     while (nil == dialogReturn && !got_int && [connection isValid])
1488         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1489                                  beforeDate:[NSDate distantFuture]];
1491     // Search for any resize messages on the input queue.  All other messages
1492     // on the input queue are dropped.  The reason why we single out resize
1493     // messages is because the user may have resized the window while a sheet
1494     // was open.
1495     int i, count = [inputQueue count];
1496     if (count > 0) {
1497         id textDimData = nil;
1498         if (count%2 == 0) {
1499             for (i = count-2; i >= 0; i -= 2) {
1500                 int msgid = [[inputQueue objectAtIndex:i] intValue];
1501                 if (SetTextDimensionsMsgID == msgid) {
1502                     textDimData = [[inputQueue objectAtIndex:i+1] retain];
1503                     break;
1504                 }
1505             }
1506         }
1508         [inputQueue removeAllObjects];
1510         if (textDimData) {
1511             [inputQueue addObject:
1512                     [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1513             [inputQueue addObject:textDimData];
1514             [textDimData release];
1515         }
1516     }
1519 - (void)insertVimStateMessage
1521     // NOTE: This is the place to add Vim state that needs to be accessed from
1522     // MacVim.  Do not add state that could potentially require lots of memory
1523     // since this message gets sent each time the output queue is forcibly
1524     // flushed (e.g. storing the currently selected text would be a bad idea).
1525     // We take this approach of "pushing" the state to MacVim to avoid having
1526     // to make synchronous calls from MacVim to Vim in order to get state.
1528     BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1530     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1531         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1532         [NSNumber numberWithInt:p_mh], @"p_mh",
1533         [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1534         [NSNumber numberWithBool:mmta], @"p_mmta",
1535         nil];
1537     // Put the state before all other messages.
1538     int msgid = SetVimStateMsgID;
1539     [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1540     [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1541                       atIndex:0];
1544 - (void)processInputQueue
1546     if ([inputQueue count] == 0) return;
1548     // NOTE: One of the input events may cause this method to be called
1549     // recursively, so copy the input queue to a local variable and clear the
1550     // queue before starting to process input events (otherwise we could get
1551     // stuck in an endless loop).
1552     NSArray *q = [inputQueue copy];
1553     unsigned i, count = [q count];
1555     [inputQueue removeAllObjects];
1557     for (i = 1; i < count; i+=2) {
1558         int msgid = [[q objectAtIndex:i-1] intValue];
1559         id data = [q objectAtIndex:i];
1560         if ([data isEqual:[NSNull null]])
1561             data = nil;
1563         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1564         [self handleInputEvent:msgid data:data];
1565     }
1567     [q release];
1568     //NSLog(@"Clear input event queue");
1571 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1573     if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1574             CmdKeyMsgID == msgid) {
1575         if (!data) return;
1576         const void *bytes = [data bytes];
1577         int mods = *((int*)bytes);  bytes += sizeof(int);
1578         int len = *((int*)bytes);  bytes += sizeof(int);
1579         NSString *key = [[NSString alloc] initWithBytes:bytes
1580                                                  length:len
1581                                                encoding:NSUTF8StringEncoding];
1582         mods = eventModifierFlagsToVimModMask(mods);
1584         if (InsertTextMsgID == msgid)
1585             [self handleInsertText:key];
1586         else
1587             [self handleKeyDown:key modifiers:mods];
1589         [key release];
1590     } else if (ScrollWheelMsgID == msgid) {
1591         if (!data) return;
1592         const void *bytes = [data bytes];
1594         int row = *((int*)bytes);  bytes += sizeof(int);
1595         int col = *((int*)bytes);  bytes += sizeof(int);
1596         int flags = *((int*)bytes);  bytes += sizeof(int);
1597         float dy = *((float*)bytes);  bytes += sizeof(float);
1599         int button = MOUSE_5;
1600         if (dy > 0) button = MOUSE_4;
1602         flags = eventModifierFlagsToVimMouseModMask(flags);
1604         int numLines = (int)round(dy);
1605         if (numLines < 0) numLines = -numLines;
1606         if (numLines == 0) numLines = 1;
1608 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1609         gui.scroll_wheel_force = numLines;
1610 #endif
1612         gui_send_mouse_event(button, col, row, NO, flags);
1613     } else if (MouseDownMsgID == msgid) {
1614         if (!data) return;
1615         const void *bytes = [data bytes];
1617         int row = *((int*)bytes);  bytes += sizeof(int);
1618         int col = *((int*)bytes);  bytes += sizeof(int);
1619         int button = *((int*)bytes);  bytes += sizeof(int);
1620         int flags = *((int*)bytes);  bytes += sizeof(int);
1621         int count = *((int*)bytes);  bytes += sizeof(int);
1623         button = eventButtonNumberToVimMouseButton(button);
1624         if (button >= 0) {
1625             flags = eventModifierFlagsToVimMouseModMask(flags);
1626             gui_send_mouse_event(button, col, row, count>1, flags);
1627         }
1628     } else if (MouseUpMsgID == msgid) {
1629         if (!data) return;
1630         const void *bytes = [data bytes];
1632         int row = *((int*)bytes);  bytes += sizeof(int);
1633         int col = *((int*)bytes);  bytes += sizeof(int);
1634         int flags = *((int*)bytes);  bytes += sizeof(int);
1636         flags = eventModifierFlagsToVimMouseModMask(flags);
1638         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1639     } else if (MouseDraggedMsgID == msgid) {
1640         if (!data) return;
1641         const void *bytes = [data bytes];
1643         int row = *((int*)bytes);  bytes += sizeof(int);
1644         int col = *((int*)bytes);  bytes += sizeof(int);
1645         int flags = *((int*)bytes);  bytes += sizeof(int);
1647         flags = eventModifierFlagsToVimMouseModMask(flags);
1649         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1650     } else if (MouseMovedMsgID == msgid) {
1651         const void *bytes = [data bytes];
1652         int row = *((int*)bytes);  bytes += sizeof(int);
1653         int col = *((int*)bytes);  bytes += sizeof(int);
1655         gui_mouse_moved(col, row);
1656     } else if (AddInputMsgID == msgid) {
1657         NSString *string = [[NSString alloc] initWithData:data
1658                 encoding:NSUTF8StringEncoding];
1659         if (string) {
1660             [self addInput:string];
1661             [string release];
1662         }
1663     } else if (SelectTabMsgID == msgid) {
1664         if (!data) return;
1665         const void *bytes = [data bytes];
1666         int idx = *((int*)bytes) + 1;
1667         //NSLog(@"Selecting tab %d", idx);
1668         send_tabline_event(idx);
1669     } else if (CloseTabMsgID == msgid) {
1670         if (!data) return;
1671         const void *bytes = [data bytes];
1672         int idx = *((int*)bytes) + 1;
1673         //NSLog(@"Closing tab %d", idx);
1674         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1675     } else if (AddNewTabMsgID == msgid) {
1676         //NSLog(@"Adding new tab");
1677         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1678     } else if (DraggedTabMsgID == msgid) {
1679         if (!data) return;
1680         const void *bytes = [data bytes];
1681         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1682         // based.
1683         int idx = *((int*)bytes);
1685         tabpage_move(idx);
1686     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1687             || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1688         if (!data) return;
1689         const void *bytes = [data bytes];
1690         int rows = Rows;
1691         if (SetTextColumnsMsgID != msgid) {
1692             rows = *((int*)bytes);  bytes += sizeof(int);
1693         }
1694         int cols = Columns;
1695         if (SetTextRowsMsgID != msgid) {
1696             cols = *((int*)bytes);  bytes += sizeof(int);
1697         }
1699         NSData *d = data;
1700         if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1701             int dim[2] = { rows, cols };
1702             d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1703             msgid = SetTextDimensionsReplyMsgID;
1704         }
1706         if (SetTextDimensionsMsgID == msgid)
1707             msgid = SetTextDimensionsReplyMsgID;
1709         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1710         // gui_resize_shell(), so we have to manually set the rows and columns
1711         // here since MacVim doesn't change the rows and columns to avoid
1712         // inconsistent states between Vim and MacVim.  The message sent back
1713         // indicates that it is a reply to a message that originated in MacVim
1714         // since we need to be able to determine where a message originated.
1715         [self queueMessage:msgid data:d];
1717         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1718         gui_resize_shell(cols, rows);
1719     } else if (ExecuteMenuMsgID == msgid) {
1720         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1721         if (attrs) {
1722             NSArray *desc = [attrs objectForKey:@"descriptor"];
1723             vimmenu_T *menu = menu_for_descriptor(desc);
1724             if (menu)
1725                 gui_menu_cb(menu);
1726         }
1727     } else if (ToggleToolbarMsgID == msgid) {
1728         [self handleToggleToolbar];
1729     } else if (ScrollbarEventMsgID == msgid) {
1730         [self handleScrollbarEvent:data];
1731     } else if (SetFontMsgID == msgid) {
1732         [self handleSetFont:data];
1733     } else if (VimShouldCloseMsgID == msgid) {
1734         gui_shell_closed();
1735     } else if (DropFilesMsgID == msgid) {
1736         [self handleDropFiles:data];
1737     } else if (DropStringMsgID == msgid) {
1738         [self handleDropString:data];
1739     } else if (GotFocusMsgID == msgid) {
1740         if (!gui.in_focus)
1741             [self focusChange:YES];
1742     } else if (LostFocusMsgID == msgid) {
1743         if (gui.in_focus)
1744             [self focusChange:NO];
1745     } else if (SetMouseShapeMsgID == msgid) {
1746         const void *bytes = [data bytes];
1747         int shape = *((int*)bytes);  bytes += sizeof(int);
1748         update_mouseshape(shape);
1749     } else if (XcodeModMsgID == msgid) {
1750         [self handleXcodeMod:data];
1751     } else if (OpenWithArgumentsMsgID == msgid) {
1752         [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1753     } else if (FindReplaceMsgID == msgid) {
1754         [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1755     } else {
1756         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1757     }
1760 + (NSDictionary *)specialKeys
1762     static NSDictionary *specialKeys = nil;
1764     if (!specialKeys) {
1765         NSBundle *mainBundle = [NSBundle mainBundle];
1766         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1767                                               ofType:@"plist"];
1768         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1769     }
1771     return specialKeys;
1774 - (void)handleInsertText:(NSString *)text
1776     if (!text) return;
1778     char_u *str = (char_u*)[text UTF8String];
1779     int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1781 #ifdef FEAT_MBYTE
1782     char_u *conv_str = NULL;
1783     if (input_conv.vc_type != CONV_NONE) {
1784         conv_str = string_convert(&input_conv, str, &len);
1785         if (conv_str)
1786             str = conv_str;
1787     }
1788 #endif
1790     for (i = 0; i < len; ++i) {
1791         add_to_input_buf(str+i, 1);
1792         if (CSI == str[i]) {
1793             // NOTE: If the converted string contains the byte CSI, then it
1794             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1795             // won't work.
1796             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1797             add_to_input_buf(extra, 2);
1798         }
1799     }
1801 #ifdef FEAT_MBYTE
1802     if (conv_str)
1803         vim_free(conv_str);
1804 #endif
1807 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1809     // TODO: This code is a horrible mess -- clean up!
1810     char_u special[3];
1811     char_u modChars[3];
1812     char_u *chars = (char_u*)[key UTF8String];
1813 #ifdef FEAT_MBYTE
1814     char_u *conv_str = NULL;
1815 #endif
1816     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1818     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1819     // that new keys can easily be added.
1820     NSString *specialString = [[MMBackend specialKeys]
1821             objectForKey:key];
1822     if (specialString && [specialString length] > 1) {
1823         //NSLog(@"special key: %@", specialString);
1824         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1825                 [specialString characterAtIndex:1]);
1827         ikey = simplify_key(ikey, &mods);
1828         if (ikey == CSI)
1829             ikey = K_CSI;
1831         special[0] = CSI;
1832         special[1] = K_SECOND(ikey);
1833         special[2] = K_THIRD(ikey);
1835         chars = special;
1836         length = 3;
1837     } else if (1 == length && TAB == chars[0]) {
1838         // Tab is a trouble child:
1839         // - <Tab> is added to the input buffer as is
1840         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1841         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1842         //   to be converted to utf-8
1843         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1844         // - <C-Tab> is reserved by Mac OS X
1845         // - <D-Tab> is reserved by Mac OS X
1846         chars = special;
1847         special[0] = TAB;
1848         length = 1;
1850         if (mods & MOD_MASK_SHIFT) {
1851             mods &= ~MOD_MASK_SHIFT;
1852             special[0] = CSI;
1853             special[1] = K_SECOND(K_S_TAB);
1854             special[2] = K_THIRD(K_S_TAB);
1855             length = 3;
1856         } else if (mods & MOD_MASK_ALT) {
1857             int mtab = 0x80 | TAB;
1858 #ifdef FEAT_MBYTE
1859             if (enc_utf8) {
1860                 // Convert to utf-8
1861                 special[0] = (mtab >> 6) + 0xc0;
1862                 special[1] = mtab & 0xbf;
1863                 length = 2;
1864             } else
1865 #endif
1866             {
1867                 special[0] = mtab;
1868                 length = 1;
1869             }
1870             mods &= ~MOD_MASK_ALT;
1871         }
1872     } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1873         // META key is treated separately.  This code was taken from gui_w48.c
1874         // and gui_gtk_x11.c.
1875         char_u string[7];
1876         int ch = simplify_key(chars[0], &mods);
1878         // Remove the SHIFT modifier for keys where it's already included,
1879         // e.g., '(' and '*'
1880         if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1881             mods &= ~MOD_MASK_SHIFT;
1883         // Interpret the ALT key as making the key META, include SHIFT, etc.
1884         ch = extract_modifiers(ch, &mods);
1885         if (ch == CSI)
1886             ch = K_CSI;
1888         int len = 0;
1889         if (mods) {
1890             string[len++] = CSI;
1891             string[len++] = KS_MODIFIER;
1892             string[len++] = mods;
1893         }
1895         if (IS_SPECIAL(ch)) {
1896             string[len++] = CSI;
1897             string[len++] = K_SECOND(ch);
1898             string[len++] = K_THIRD(ch);
1899         } else {
1900             string[len++] = ch;
1901 #ifdef FEAT_MBYTE
1902             // TODO: What if 'enc' is not "utf-8"?
1903             if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1904                 string[len++] = ch & 0xbf;
1905                 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1906                 if (string[len-1] == CSI) {
1907                     string[len++] = KS_EXTRA;
1908                     string[len++] = (int)KE_CSI;
1909                 }
1910             }
1911 #endif
1912         }
1914         add_to_input_buf(string, len);
1915         return;
1916     } else if (length > 0) {
1917         unichar c = [key characterAtIndex:0];
1918         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1919         //        [key characterAtIndex:0], mods);
1921         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1922         // cleared since they are already added to the key by the AppKit.
1923         // Unfortunately, the only way to deal with when to clear the modifiers
1924         // or not seems to be to have hard-wired rules like this.
1925         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1926                     || 0x9 == c || 0xd == c || ESC == c) ) {
1927             mods &= ~MOD_MASK_SHIFT;
1928             mods &= ~MOD_MASK_CTRL;
1929             //NSLog(@"clear shift ctrl");
1930         }
1932 #ifdef FEAT_MBYTE
1933         if (input_conv.vc_type != CONV_NONE) {
1934             conv_str = string_convert(&input_conv, chars, &length);
1935             if (conv_str)
1936                 chars = conv_str;
1937         }
1938 #endif
1939     }
1941     if (chars && length > 0) {
1942         if (mods) {
1943             //NSLog(@"adding mods: %d", mods);
1944             modChars[0] = CSI;
1945             modChars[1] = KS_MODIFIER;
1946             modChars[2] = mods;
1947             add_to_input_buf(modChars, 3);
1948         }
1950         //NSLog(@"add to input buf: 0x%x", chars[0]);
1951         // TODO: Check for CSI bytes?
1952         add_to_input_buf(chars, length);
1953     }
1955 #ifdef FEAT_MBYTE
1956     if (conv_str)
1957         vim_free(conv_str);
1958 #endif
1961 - (void)queueMessage:(int)msgid data:(NSData *)data
1963     //if (msgid != EnableMenuItemMsgID)
1964     //    NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1966     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1967     if (data)
1968         [outputQueue addObject:data];
1969     else
1970         [outputQueue addObject:[NSData data]];
1973 - (void)connectionDidDie:(NSNotification *)notification
1975     // If the main connection to MacVim is lost this means that either MacVim
1976     // has crashed or this process did not receive its termination message
1977     // properly (e.g. if the TerminateNowMsgID was dropped).
1978     //
1979     // NOTE: This is not called if a Vim controller invalidates its connection.
1981     NSLog(@"WARNING[%s]: Main connection was lost before process had a chance "
1982             "to terminate; preserving swap files.", _cmd);
1983     getout_preserve_modified(1);
1986 - (void)blinkTimerFired:(NSTimer *)timer
1988     NSTimeInterval timeInterval = 0;
1990     [blinkTimer release];
1991     blinkTimer = nil;
1993     if (MMBlinkStateOn == blinkState) {
1994         gui_undraw_cursor();
1995         blinkState = MMBlinkStateOff;
1996         timeInterval = blinkOffInterval;
1997     } else if (MMBlinkStateOff == blinkState) {
1998         gui_update_cursor(TRUE, FALSE);
1999         blinkState = MMBlinkStateOn;
2000         timeInterval = blinkOnInterval;
2001     }
2003     if (timeInterval > 0) {
2004         blinkTimer = 
2005             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2006                                             selector:@selector(blinkTimerFired:)
2007                                             userInfo:nil repeats:NO] retain];
2008         [self flushQueue:YES];
2009     }
2012 - (void)focusChange:(BOOL)on
2014     gui_focus_change(on);
2017 - (void)handleToggleToolbar
2019     // If 'go' contains 'T', then remove it, else add it.
2021     char_u go[sizeof(GO_ALL)+2];
2022     char_u *p;
2023     int len;
2025     STRCPY(go, p_go);
2026     p = vim_strchr(go, GO_TOOLBAR);
2027     len = STRLEN(go);
2029     if (p != NULL) {
2030         char_u *end = go + len;
2031         while (p < end) {
2032             p[0] = p[1];
2033             ++p;
2034         }
2035     } else {
2036         go[len] = GO_TOOLBAR;
2037         go[len+1] = NUL;
2038     }
2040     set_option_value((char_u*)"guioptions", 0, go, 0);
2042     [self redrawScreen];
2045 - (void)handleScrollbarEvent:(NSData *)data
2047     if (!data) return;
2049     const void *bytes = [data bytes];
2050     long ident = *((long*)bytes);  bytes += sizeof(long);
2051     int hitPart = *((int*)bytes);  bytes += sizeof(int);
2052     float fval = *((float*)bytes);  bytes += sizeof(float);
2053     scrollbar_T *sb = gui_find_scrollbar(ident);
2055     if (sb) {
2056         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2057         long value = sb_info->value;
2058         long size = sb_info->size;
2059         long max = sb_info->max;
2060         BOOL isStillDragging = NO;
2061         BOOL updateKnob = YES;
2063         switch (hitPart) {
2064         case NSScrollerDecrementPage:
2065             value -= (size > 2 ? size - 2 : 1);
2066             break;
2067         case NSScrollerIncrementPage:
2068             value += (size > 2 ? size - 2 : 1);
2069             break;
2070         case NSScrollerDecrementLine:
2071             --value;
2072             break;
2073         case NSScrollerIncrementLine:
2074             ++value;
2075             break;
2076         case NSScrollerKnob:
2077             isStillDragging = YES;
2078             // fall through ...
2079         case NSScrollerKnobSlot:
2080             value = (long)(fval * (max - size + 1));
2081             // fall through ...
2082         default:
2083             updateKnob = NO;
2084             break;
2085         }
2087         //NSLog(@"value %d -> %d", sb_info->value, value);
2088         gui_drag_scrollbar(sb, value, isStillDragging);
2090         if (updateKnob) {
2091             // Dragging the knob or option+clicking automatically updates
2092             // the knob position (on the actual NSScroller), so we only
2093             // need to set the knob position in the other cases.
2094             if (sb->wp) {
2095                 // Update both the left&right vertical scrollbars.
2096                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2097                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2098                 [self setScrollbarThumbValue:value size:size max:max
2099                                   identifier:identLeft];
2100                 [self setScrollbarThumbValue:value size:size max:max
2101                                   identifier:identRight];
2102             } else {
2103                 // Update the horizontal scrollbar.
2104                 [self setScrollbarThumbValue:value size:size max:max
2105                                   identifier:ident];
2106             }
2107         }
2108     }
2111 - (void)handleSetFont:(NSData *)data
2113     if (!data) return;
2115     const void *bytes = [data bytes];
2116     float pointSize = *((float*)bytes);  bytes += sizeof(float);
2117     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2118     bytes += sizeof(unsigned);  // len not used
2120     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2121     [name appendString:[NSString stringWithFormat:@":h%d", (int)pointSize]];
2122     char_u *s = (char_u*)[name UTF8String];
2124 #ifdef FEAT_MBYTE
2125     s = CONVERT_FROM_UTF8(s);
2126 #endif
2128     set_option_value((char_u*)"guifont", 0, s, 0);
2130 #ifdef FEAT_MBYTE
2131     CONVERT_FROM_UTF8_FREE(s);
2132 #endif
2134     [self redrawScreen];
2137 - (void)handleDropFiles:(NSData *)data
2139     // TODO: Get rid of this method; instead use Vim script directly.  At the
2140     // moment I know how to do this to open files in tabs, but I'm not sure how
2141     // to add the filenames to the command line when in command line mode.
2143     if (!data) return;
2145     NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2146     if (!args) return;
2148     id obj = [args objectForKey:@"forceOpen"];
2149     BOOL forceOpen = YES;
2150     if (obj)
2151         forceOpen = [obj boolValue];
2153     NSArray *filenames = [args objectForKey:@"filenames"];
2154     if (!(filenames && [filenames count] > 0)) return;
2156 #ifdef FEAT_DND
2157     if (!forceOpen && (State & CMDLINE)) {
2158         // HACK!  If Vim is in command line mode then the files names
2159         // should be added to the command line, instead of opening the
2160         // files in tabs (unless forceOpen is set).  This is taken care of by
2161         // gui_handle_drop().
2162         int n = [filenames count];
2163         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2164         if (fnames) {
2165             int i = 0;
2166             for (i = 0; i < n; ++i)
2167                 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2169             // NOTE!  This function will free 'fnames'.
2170             // HACK!  It is assumed that the 'x' and 'y' arguments are
2171             // unused when in command line mode.
2172             gui_handle_drop(0, 0, 0, fnames, n);
2173         }
2174     } else
2175 #endif // FEAT_DND
2176     {
2177         [self handleOpenWithArguments:args];
2178     }
2181 - (void)handleDropString:(NSData *)data
2183     if (!data) return;
2185 #ifdef FEAT_DND
2186     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2187     const void *bytes = [data bytes];
2188     int len = *((int*)bytes);  bytes += sizeof(int);
2189     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2191     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2192     NSRange range = { 0, [string length] };
2193     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2194                                          withString:@"\x0a" options:0
2195                                               range:range];
2196     if (0 == n) {
2197         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2198                                        options:0 range:range];
2199     }
2201     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2202     char_u *s = (char_u*)[string UTF8String];
2203 #ifdef FEAT_MBYTE
2204     if (input_conv.vc_type != CONV_NONE)
2205         s = string_convert(&input_conv, s, &len);
2206 #endif
2207     dnd_yank_drag_data(s, len);
2208 #ifdef FEAT_MBYTE
2209     if (input_conv.vc_type != CONV_NONE)
2210         vim_free(s);
2211 #endif
2212     add_to_input_buf(dropkey, sizeof(dropkey));
2213 #endif // FEAT_DND
2216 - (void)startOdbEditWithArguments:(NSDictionary *)args
2218 #ifdef FEAT_ODB_EDITOR
2219     id obj = [args objectForKey:@"remoteID"];
2220     if (!obj) return;
2222     OSType serverID = [obj unsignedIntValue];
2223     NSString *remotePath = [args objectForKey:@"remotePath"];
2225     NSAppleEventDescriptor *token = nil;
2226     NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2227     obj = [args objectForKey:@"remoteTokenDescType"];
2228     if (tokenData && obj) {
2229         DescType tokenType = [obj unsignedLongValue];
2230         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2231                                                                 data:tokenData];
2232     }
2234     NSArray *filenames = [args objectForKey:@"filenames"];
2235     unsigned i, numFiles = [filenames count];
2236     for (i = 0; i < numFiles; ++i) {
2237         NSString *filename = [filenames objectAtIndex:i];
2238         char_u *s = [filename vimStringSave];
2239         buf_T *buf = buflist_findname(s);
2240         vim_free(s);
2242         if (buf) {
2243             if (buf->b_odb_token) {
2244                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2245                 buf->b_odb_token = NULL;
2246             }
2248             if (buf->b_odb_fname) {
2249                 vim_free(buf->b_odb_fname);
2250                 buf->b_odb_fname = NULL;
2251             }
2253             buf->b_odb_server_id = serverID;
2255             if (token)
2256                 buf->b_odb_token = [token retain];
2257             if (remotePath)
2258                 buf->b_odb_fname = [remotePath vimStringSave];
2259         } else {
2260             NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2261                     filename);
2262         }
2263     }
2264 #endif // FEAT_ODB_EDITOR
2267 - (void)handleXcodeMod:(NSData *)data
2269 #if 0
2270     const void *bytes = [data bytes];
2271     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2272     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2273     if (0 == len)
2274         return;
2276     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2277             descriptorWithDescriptorType:type
2278                                    bytes:bytes
2279                                   length:len];
2280 #endif
2283 - (void)handleOpenWithArguments:(NSDictionary *)args
2285     // ARGUMENT:                DESCRIPTION:
2286     // -------------------------------------------------------------
2287     // filenames                list of filenames
2288     // dontOpen                 don't open files specified in above argument
2289     // layout                   which layout to use to open files
2290     // selectionRange           range of lines to select
2291     // searchText               string to search for
2292     // cursorLine               line to position the cursor on
2293     // cursorColumn             column to position the cursor on
2294     //                          (only valid when "cursorLine" is set)
2295     // remoteID                 ODB parameter
2296     // remotePath               ODB parameter
2297     // remoteTokenDescType      ODB parameter
2298     // remoteTokenData          ODB parameter
2300     //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2302     NSArray *filenames = [args objectForKey:@"filenames"];
2303     int i, numFiles = filenames ? [filenames count] : 0;
2304     BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2305     int layout = [[args objectForKey:@"layout"] intValue];
2307     // Change to directory of first file to open if this is an "unused" editor
2308     // (but do not do this if editing remotely).
2309     if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2310             && (starting || [self unusedEditor]) ) {
2311         char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2312         vim_chdirfile(s);
2313         vim_free(s);
2314     }
2316     if (starting > 0) {
2317         // When Vim is starting we simply add the files to be opened to the
2318         // global arglist and Vim will take care of opening them for us.
2319         if (openFiles && numFiles > 0) {
2320             for (i = 0; i < numFiles; i++) {
2321                 NSString *fname = [filenames objectAtIndex:i];
2322                 char_u *p = NULL;
2324                 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2325                         || (p = [fname vimStringSave]) == NULL)
2326                     exit(2); // See comment in -[MMBackend exit]
2327                 else
2328                     alist_add(&global_alist, p, 2);
2329             }
2331             // Vim will take care of arranging the files added to the arglist
2332             // in windows or tabs; all we must do is to specify which layout to
2333             // use.
2334             initialWindowLayout = layout;
2335         }
2336     } else {
2337         // When Vim is already open we resort to some trickery to open the
2338         // files with the specified layout.
2339         //
2340         // TODO: Figure out a better way to handle this?
2341         if (openFiles && numFiles > 0) {
2342             BOOL oneWindowInTab = topframe ? YES
2343                                            : (topframe->fr_layout == FR_LEAF);
2344             BOOL bufChanged = NO;
2345             BOOL bufHasFilename = NO;
2346             if (curbuf) {
2347                 bufChanged = curbufIsChanged();
2348                 bufHasFilename = curbuf->b_ffname != NULL;
2349             }
2351             // Temporarily disable flushing since the following code may
2352             // potentially cause multiple redraws.
2353             flushDisabled = YES;
2355             BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2356             if (WIN_TABS == layout && !onlyOneTab) {
2357                 // By going to the last tabpage we ensure that the new tabs
2358                 // will appear last (if this call is left out, the taborder
2359                 // becomes messy).
2360                 goto_tabpage(9999);
2361             }
2363             // Make sure we're in normal mode first.
2364             [self addInput:@"<C-\\><C-N>"];
2366             if (numFiles > 1) {
2367                 // With "split layout" we open a new tab before opening
2368                 // multiple files if the current tab has more than one window
2369                 // or if there is exactly one window but whose buffer has a
2370                 // filename.  (The :drop command ensures modified buffers get
2371                 // their own window.)
2372                 if ((WIN_HOR == layout || WIN_VER == layout) &&
2373                         (!oneWindowInTab || bufHasFilename))
2374                     [self addInput:@":tabnew<CR>"];
2376                 // The files are opened by constructing a ":drop ..." command
2377                 // and executing it.
2378                 NSMutableString *cmd = (WIN_TABS == layout)
2379                         ? [NSMutableString stringWithString:@":tab drop"]
2380                         : [NSMutableString stringWithString:@":drop"];
2382                 for (i = 0; i < numFiles; ++i) {
2383                     NSString *file = [filenames objectAtIndex:i];
2384                     file = [file stringByEscapingSpecialFilenameCharacters];
2385                     [cmd appendString:@" "];
2386                     [cmd appendString:file];
2387                 }
2389                 // Temporarily clear 'suffixes' so that the files are opened in
2390                 // the same order as they appear in the "filenames" array.
2391                 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2393                 [self addInput:cmd];
2395                 // Split the view into multiple windows if requested.
2396                 if (WIN_HOR == layout)
2397                     [self addInput:@"|sall"];
2398                 else if (WIN_VER == layout)
2399                     [self addInput:@"|vert sall"];
2401                 // Restore the old value of 'suffixes'.
2402                 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2403             } else {
2404                 // When opening one file we try to reuse the current window,
2405                 // but not if its buffer is modified or has a filename.
2406                 // However, the 'arglist' layout always opens the file in the
2407                 // current window.
2408                 NSString *file = [[filenames lastObject]
2409                         stringByEscapingSpecialFilenameCharacters];
2410                 NSString *cmd;
2411                 if (WIN_HOR == layout) {
2412                     if (!(bufHasFilename || bufChanged))
2413                         cmd = [NSString stringWithFormat:@":e %@", file];
2414                     else
2415                         cmd = [NSString stringWithFormat:@":sp %@", file];
2416                 } else if (WIN_VER == layout) {
2417                     if (!(bufHasFilename || bufChanged))
2418                         cmd = [NSString stringWithFormat:@":e %@", file];
2419                     else
2420                         cmd = [NSString stringWithFormat:@":vsp %@", file];
2421                 } else if (WIN_TABS == layout) {
2422                     if (oneWindowInTab && !(bufHasFilename || bufChanged))
2423                         cmd = [NSString stringWithFormat:@":e %@", file];
2424                     else
2425                         cmd = [NSString stringWithFormat:@":tabe %@", file];
2426                 } else {
2427                     // (The :drop command will split if there is a modified
2428                     // buffer.)
2429                     cmd = [NSString stringWithFormat:@":drop %@", file];
2430                 }
2432                 [self addInput:cmd];
2433                 [self addInput:@"<CR>"];
2434             }
2436             // Force screen redraw (does it have to be this complicated?).
2437             // (This code was taken from the end of gui_handle_drop().)
2438             update_screen(NOT_VALID);
2439             setcursor();
2440             out_flush();
2441             gui_update_cursor(FALSE, FALSE);
2442             maketitle();
2444             flushDisabled = NO;
2445         }
2446     }
2448     if ([args objectForKey:@"remoteID"]) {
2449         // NOTE: We have to delay processing any ODB related arguments since
2450         // the file(s) may not be opened until the input buffer is processed.
2451         [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2452                                withObject:args
2453                             waitUntilDone:NO];
2454     }
2456     NSString *lineString = [args objectForKey:@"cursorLine"];
2457     if (lineString && [lineString intValue] > 0) {
2458         NSString *columnString = [args objectForKey:@"cursorColumn"];
2459         if (!(columnString && [columnString intValue] > 0))
2460             columnString = @"1";
2462         NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2463                 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2464         [self addInput:cmd];
2465     }
2467     NSString *rangeString = [args objectForKey:@"selectionRange"];
2468     if (rangeString) {
2469         // Build a command line string that will select the given range of
2470         // lines.  If range.length == 0, then position the cursor on the given
2471         // line but do not select.
2472         NSRange range = NSRangeFromString(rangeString);
2473         NSString *cmd;
2474         if (range.length > 0) {
2475             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2476                     NSMaxRange(range), range.location];
2477         } else {
2478             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2479                     range.location];
2480         }
2482         [self addInput:cmd];
2483     }
2485     NSString *searchText = [args objectForKey:@"searchText"];
2486     if (searchText) {
2487         // TODO: Searching is an exclusive motion, so if the pattern would
2488         // match on row 0 column 0 then this pattern will miss that match.
2489         [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@<CR>",
2490                 searchText]];
2491     }
2494 - (BOOL)checkForModifiedBuffers
2496     buf_T *buf;
2497     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2498         if (bufIsChanged(buf)) {
2499             return YES;
2500         }
2501     }
2503     return NO;
2506 - (void)addInput:(NSString *)input
2508     // NOTE: This code is essentially identical to server_to_input_buf(),
2509     // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2510     char_u *string = [input vimStringSave];
2511     if (!string) return;
2513     /* Set 'cpoptions' the way we want it.
2514      *    B set - backslashes are *not* treated specially
2515      *    k set - keycodes are *not* reverse-engineered
2516      *    < unset - <Key> sequences *are* interpreted
2517      *  The last but one parameter of replace_termcodes() is TRUE so that the
2518      *  <lt> sequence is recognised - needed for a real backslash.
2519      */
2520     char_u *ptr = NULL;
2521     char_u *cpo_save = p_cpo;
2522     p_cpo = (char_u *)"Bk";
2523     char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2524     p_cpo = cpo_save;
2526     if (*ptr != NUL)    /* trailing CTRL-V results in nothing */
2527     {
2528         /*
2529          * Add the string to the input stream.
2530          * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2531          *
2532          * First clear typed characters from the typeahead buffer, there could
2533          * be half a mapping there.  Then append to the existing string, so
2534          * that multiple commands from a client are concatenated.
2535          */
2536         if (typebuf.tb_maplen < typebuf.tb_len)
2537             del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2538         (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2540         /* Let input_available() know we inserted text in the typeahead
2541          * buffer. */
2542         typebuf_was_filled = TRUE;
2543     }
2544     vim_free(ptr);
2545     vim_free(string);
2548 - (BOOL)unusedEditor
2550     BOOL oneWindowInTab = topframe ? YES
2551                                    : (topframe->fr_layout == FR_LEAF);
2552     BOOL bufChanged = NO;
2553     BOOL bufHasFilename = NO;
2554     if (curbuf) {
2555         bufChanged = curbufIsChanged();
2556         bufHasFilename = curbuf->b_ffname != NULL;
2557     }
2559     BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2561     return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2564 - (void)redrawScreen
2566     // Force screen redraw (does it have to be this complicated?).
2567     redraw_all_later(CLEAR);
2568     update_screen(NOT_VALID);
2569     setcursor();
2570     out_flush();
2571     gui_update_cursor(FALSE, FALSE);
2573     // HACK! The cursor is not put back at the command line by the above
2574     // "redraw commands".  The following test seems to do the trick though.
2575     if (State & CMDLINE)
2576         redrawcmdline();
2579 - (void)handleFindReplace:(NSDictionary *)args
2581     if (!args) return;
2583     NSString *findString = [args objectForKey:@"find"];
2584     if (!findString) return;
2586     char_u *find = [findString vimStringSave];
2587     char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2588     int flags = [[args objectForKey:@"flags"] intValue];
2590     // NOTE: The flag 0x100 is used to indicate a backward search.
2591     gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2593     vim_free(find);
2594     vim_free(replace);
2597 @end // MMBackend (Private)
2602 @implementation MMBackend (ClientServer)
2604 - (NSString *)connectionNameFromServerName:(NSString *)name
2606     NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2608     return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2609         lowercaseString];
2612 - (NSConnection *)connectionForServerName:(NSString *)name
2614     // TODO: Try 'name%d' if 'name' fails.
2615     NSString *connName = [self connectionNameFromServerName:name];
2616     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2618     if (!svrConn) {
2619         svrConn = [NSConnection connectionWithRegisteredName:connName
2620                                                            host:nil];
2621         // Try alternate server...
2622         if (!svrConn && alternateServerName) {
2623             //NSLog(@"  trying to connect to alternate server: %@",
2624             //        alternateServerName);
2625             connName = [self connectionNameFromServerName:alternateServerName];
2626             svrConn = [NSConnection connectionWithRegisteredName:connName
2627                                                             host:nil];
2628         }
2630         // Try looking for alternate servers...
2631         if (!svrConn) {
2632             //NSLog(@"  looking for alternate servers...");
2633             NSString *alt = [self alternateServerNameForName:name];
2634             if (alt != alternateServerName) {
2635                 //NSLog(@"  found alternate server: %@", string);
2636                 [alternateServerName release];
2637                 alternateServerName = [alt copy];
2638             }
2639         }
2641         // Try alternate server again...
2642         if (!svrConn && alternateServerName) {
2643             //NSLog(@"  trying to connect to alternate server: %@",
2644             //        alternateServerName);
2645             connName = [self connectionNameFromServerName:alternateServerName];
2646             svrConn = [NSConnection connectionWithRegisteredName:connName
2647                                                             host:nil];
2648         }
2650         if (svrConn) {
2651             [connectionNameDict setObject:svrConn forKey:connName];
2653             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2654             [[NSNotificationCenter defaultCenter] addObserver:self
2655                     selector:@selector(serverConnectionDidDie:)
2656                         name:NSConnectionDidDieNotification object:svrConn];
2657         }
2658     }
2660     return svrConn;
2663 - (NSConnection *)connectionForServerPort:(int)port
2665     NSConnection *conn;
2666     NSEnumerator *e = [connectionNameDict objectEnumerator];
2668     while ((conn = [e nextObject])) {
2669         // HACK! Assume connection uses mach ports.
2670         if (port == [(NSMachPort*)[conn sendPort] machPort])
2671             return conn;
2672     }
2674     return nil;
2677 - (void)serverConnectionDidDie:(NSNotification *)notification
2679     //NSLog(@"%s%@", _cmd, notification);
2681     NSConnection *svrConn = [notification object];
2683     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2684     [[NSNotificationCenter defaultCenter]
2685             removeObserver:self
2686                       name:NSConnectionDidDieNotification
2687                     object:svrConn];
2689     [connectionNameDict removeObjectsForKeys:
2690         [connectionNameDict allKeysForObject:svrConn]];
2692     // HACK! Assume connection uses mach ports.
2693     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2694     NSNumber *key = [NSNumber numberWithInt:port];
2696     [clientProxyDict removeObjectForKey:key];
2697     [serverReplyDict removeObjectForKey:key];
2700 - (void)addClient:(NSDistantObject *)client
2702     NSConnection *conn = [client connectionForProxy];
2703     // HACK! Assume connection uses mach ports.
2704     int port = [(NSMachPort*)[conn sendPort] machPort];
2705     NSNumber *key = [NSNumber numberWithInt:port];
2707     if (![clientProxyDict objectForKey:key]) {
2708         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2709         [clientProxyDict setObject:client forKey:key];
2710     }
2712     // NOTE: 'clientWindow' is a global variable which is used by <client>
2713     clientWindow = port;
2716 - (NSString *)alternateServerNameForName:(NSString *)name
2718     if (!(name && [name length] > 0))
2719         return nil;
2721     // Only look for alternates if 'name' doesn't end in a digit.
2722     unichar lastChar = [name characterAtIndex:[name length]-1];
2723     if (lastChar >= '0' && lastChar <= '9')
2724         return nil;
2726     // Look for alternates among all current servers.
2727     NSArray *list = [self serverList];
2728     if (!(list && [list count] > 0))
2729         return nil;
2731     // Filter out servers starting with 'name' and ending with a number. The
2732     // (?i) pattern ensures that the match is case insensitive.
2733     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2734     NSPredicate *pred = [NSPredicate predicateWithFormat:
2735             @"SELF MATCHES %@", pat];
2736     list = [list filteredArrayUsingPredicate:pred];
2737     if ([list count] > 0) {
2738         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2739         return [list objectAtIndex:0];
2740     }
2742     return nil;
2745 @end // MMBackend (ClientServer)
2750 @implementation NSString (MMServerNameCompare)
2751 - (NSComparisonResult)serverNameCompare:(NSString *)string
2753     return [self compare:string
2754                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2756 @end
2761 static int eventModifierFlagsToVimModMask(int modifierFlags)
2763     int modMask = 0;
2765     if (modifierFlags & NSShiftKeyMask)
2766         modMask |= MOD_MASK_SHIFT;
2767     if (modifierFlags & NSControlKeyMask)
2768         modMask |= MOD_MASK_CTRL;
2769     if (modifierFlags & NSAlternateKeyMask)
2770         modMask |= MOD_MASK_ALT;
2771     if (modifierFlags & NSCommandKeyMask)
2772         modMask |= MOD_MASK_CMD;
2774     return modMask;
2777 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2779     int modMask = 0;
2781     if (modifierFlags & NSShiftKeyMask)
2782         modMask |= MOUSE_SHIFT;
2783     if (modifierFlags & NSControlKeyMask)
2784         modMask |= MOUSE_CTRL;
2785     if (modifierFlags & NSAlternateKeyMask)
2786         modMask |= MOUSE_ALT;
2788     return modMask;
2791 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2793     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2795     return (buttonNumber >= 0 && buttonNumber < 3)
2796             ? mouseButton[buttonNumber] : -1;
2801 // This function is modeled after the VimToPython function found in if_python.c
2802 // NB This does a deep copy by value, it does not lookup references like the
2803 // VimToPython function does.  This is because I didn't want to deal with the
2804 // retain cycles that this would create, and we can cover 99% of the use cases
2805 // by ignoring it.  If we ever switch to using GC in MacVim then this
2806 // functionality can be implemented easily.
2807 static id vimToCocoa(typval_T * tv, int depth)
2809     id result = nil;
2810     id newObj = nil;
2813     // Avoid infinite recursion
2814     if (depth > 100) {
2815         return nil;
2816     }
2818     if (tv->v_type == VAR_STRING) {
2819         char_u * val = tv->vval.v_string;
2820         // val can be NULL if the string is empty
2821         if (!val) {
2822             result = [NSString string];
2823         } else {
2824 #ifdef FEAT_MBYTE
2825             val = CONVERT_TO_UTF8(val);
2826 #endif
2827             result = [NSString stringWithUTF8String:(char*)val];
2828 #ifdef FEAT_MBYTE
2829             CONVERT_TO_UTF8_FREE(val);
2830 #endif
2831         }
2832     } else if (tv->v_type == VAR_NUMBER) {
2833         // looks like sizeof(varnumber_T) is always <= sizeof(long)
2834         result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2835     } else if (tv->v_type == VAR_LIST) {
2836         list_T * list = tv->vval.v_list;
2837         listitem_T * curr;
2839         NSMutableArray * arr = result = [NSMutableArray array];
2841         if (list != NULL) {
2842             for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2843                 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2844                 [arr addObject:newObj];
2845             }
2846         }
2847     } else if (tv->v_type == VAR_DICT) {
2848         NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2850         if (tv->vval.v_dict != NULL) {
2851             hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2852             int todo = ht->ht_used;
2853             hashitem_T * hi;
2854             dictitem_T * di;
2856             for (hi = ht->ht_array; todo > 0; ++hi) {
2857                 if (!HASHITEM_EMPTY(hi)) {
2858                     --todo;
2860                     di = dict_lookup(hi);
2861                     newObj = vimToCocoa(&di->di_tv, depth + 1);
2863                     char_u * keyval = hi->hi_key;
2864 #ifdef FEAT_MBYTE
2865                     keyval = CONVERT_TO_UTF8(keyval);
2866 #endif
2867                     NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2868 #ifdef FEAT_MBYTE
2869                     CONVERT_TO_UTF8_FREE(keyval);
2870 #endif
2871                     [dict setObject:newObj forKey:key];
2872                 }
2873             }
2874         }
2875     } else { // only func refs should fall into this category?
2876         result = nil;
2877     }
2879     return result;
2883 // This function is modeled after eval_client_expr_to_string found in main.c
2884 // Returns nil if there was an error evaluating the expression, and writes a
2885 // message to errorStr.
2886 // TODO Get the error that occurred while evaluating the expression in vim
2887 // somehow.
2888 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2891     char_u *s = (char_u*)[expr UTF8String];
2893 #ifdef FEAT_MBYTE
2894     s = CONVERT_FROM_UTF8(s);
2895 #endif
2897     int save_dbl = debug_break_level;
2898     int save_ro = redir_off;
2900     debug_break_level = -1;
2901     redir_off = 0;
2902     ++emsg_skip;
2904     typval_T * tvres = eval_expr(s, NULL);
2906     debug_break_level = save_dbl;
2907     redir_off = save_ro;
2908     --emsg_skip;
2910     setcursor();
2911     out_flush();
2913 #ifdef FEAT_MBYTE
2914     CONVERT_FROM_UTF8_FREE(s);
2915 #endif
2917 #ifdef FEAT_GUI
2918     if (gui.in_use)
2919         gui_update_cursor(FALSE, FALSE);
2920 #endif
2922     if (tvres == NULL) {
2923         free_tv(tvres);
2924         *errstr = @"Expression evaluation failed.";
2925     }
2927     id res = vimToCocoa(tvres, 1);
2929     free_tv(tvres);
2931     if (res == nil) {
2932         *errstr = @"Conversion to cocoa values failed.";
2933     }
2935     return res;
2940 @implementation NSString (VimStrings)
2942 + (id)stringWithVimString:(char_u *)s
2944     // This method ensures a non-nil string is returned.  If 's' cannot be
2945     // converted to a utf-8 string it is assumed to be latin-1.  If conversion
2946     // still fails an empty NSString is returned.
2947     NSString *string = nil;
2948     if (s) {
2949 #ifdef FEAT_MBYTE
2950         s = CONVERT_TO_UTF8(s);
2951 #endif
2952         string = [NSString stringWithUTF8String:(char*)s];
2953         if (!string) {
2954             // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2955             // latin-1?
2956             string = [NSString stringWithCString:(char*)s
2957                                         encoding:NSISOLatin1StringEncoding];
2958         }
2959 #ifdef FEAT_MBYTE
2960         CONVERT_TO_UTF8_FREE(s);
2961 #endif
2962     }
2964     return string != nil ? string : [NSString string];
2967 - (char_u *)vimStringSave
2969     char_u *s = (char_u*)[self UTF8String], *ret = NULL;
2971 #ifdef FEAT_MBYTE
2972     s = CONVERT_FROM_UTF8(s);
2973 #endif
2974     ret = vim_strsave(s);
2975 #ifdef FEAT_MBYTE
2976     CONVERT_FROM_UTF8_FREE(s);
2977 #endif
2979     return ret;
2982 @end // NSString (VimStrings)