Deprecate inProcessCommandQueue related code
[MacVim.git] / src / MacVim / MMBackend.m
blob998096176e31b79ec7b042c9c5d60fd8c68cdca8
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
11  * MMBackend
12  *
13  * MMBackend communicates with the frontend (MacVim).  It maintains a queue of
14  * output which is flushed to the frontend under controlled circumstances (so
15  * as to maintain a steady framerate).  Input from the frontend is also handled
16  * here.
17  *
18  * The frontend communicates with the backend via the MMBackendProtocol.  In
19  * particular, input is sent to the backend via processInput:data: and Vim
20  * state can be queried from the frontend with evaluateExpression:.
21  *
22  * It is very important to realize that all state is held by the backend, the
23  * frontend must either ask for state [MMBackend evaluateExpression:] or wait
24  * for the backend to update [MMAppController processInput:forIdentifier:].
25  *
26  * The client/server functionality of Vim is handled by the backend.  It sets
27  * up a named NSConnection to which other Vim processes can connect.
28  */
30 #import "MMBackend.h"
34 // NOTE: Colors in MMBackend are stored as unsigned ints on the form 0xaarrggbb
35 // whereas colors in Vim are int without the alpha component.  Also note that
36 // 'transp' is assumed to be a value between 0 and 100.
37 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
38 #define MM_COLOR_WITH_TRANSP(col,transp) \
39     ((unsigned)( ((col)&0xffffff) \
40         | ((((unsigned)((((100-(transp))*255)/100)+.5f))&0xff)<<24) ))
42 // Values for window layout (must match values in main.c).
43 #define WIN_HOR     1       // "-o" horizontally split windows
44 #define WIN_VER     2       // "-O" vertically split windows
45 #define WIN_TABS    3       // "-p" windows on tab pages
47 static unsigned MMServerMax = 1000;
49 // TODO: Move to separate file.
50 static int eventModifierFlagsToVimModMask(int modifierFlags);
51 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
52 static int eventButtonNumberToVimMouseButton(int buttonNumber);
54 // In gui_macvim.m
55 vimmenu_T *menu_for_descriptor(NSArray *desc);
57 static id evalExprCocoa(NSString * expr, NSString ** errstr);
60 enum {
61     MMBlinkStateNone = 0,
62     MMBlinkStateOn,
63     MMBlinkStateOff
66 static NSString *MMSymlinkWarningString =
67     @"\n\n\tMost likely this is because you have symlinked directly to\n"
68      "\tthe Vim binary, which Cocoa does not allow.  Please use an\n"
69      "\talias or the mvim shell script instead.  If you have not used\n"
70      "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
73 extern GuiFont gui_mch_retain_font(GuiFont font);
77 @interface NSString (MMServerNameCompare)
78 - (NSComparisonResult)serverNameCompare:(NSString *)string;
79 @end
84 @interface MMBackend (Private)
85 - (void)clearDrawData;
86 - (void)didChangeWholeLine;
87 - (void)waitForDialogReturn;
88 - (void)insertVimStateMessage;
89 - (void)processInputQueue;
90 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
91 + (NSDictionary *)specialKeys;
92 - (void)handleInsertText:(NSString *)text;
93 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
94 - (void)queueMessage:(int)msgid data:(NSData *)data;
95 - (void)connectionDidDie:(NSNotification *)notification;
96 - (void)blinkTimerFired:(NSTimer *)timer;
97 - (void)focusChange:(BOOL)on;
98 - (void)handleToggleToolbar;
99 - (void)handleScrollbarEvent:(NSData *)data;
100 - (void)handleSetFont:(NSData *)data;
101 - (void)handleDropFiles:(NSData *)data;
102 - (void)handleDropString:(NSData *)data;
103 - (void)startOdbEditWithArguments:(NSDictionary *)args;
104 - (void)handleXcodeMod:(NSData *)data;
105 - (void)handleOpenWithArguments:(NSDictionary *)args;
106 - (BOOL)checkForModifiedBuffers;
107 - (void)addInput:(NSString *)input;
108 - (BOOL)unusedEditor;
109 - (void)redrawScreen;
110 - (void)handleFindReplace:(NSDictionary *)args;
111 @end
115 @interface MMBackend (ClientServer)
116 - (NSString *)connectionNameFromServerName:(NSString *)name;
117 - (NSConnection *)connectionForServerName:(NSString *)name;
118 - (NSConnection *)connectionForServerPort:(int)port;
119 - (void)serverConnectionDidDie:(NSNotification *)notification;
120 - (void)addClient:(NSDistantObject *)client;
121 - (NSString *)alternateServerNameForName:(NSString *)name;
122 @end
126 @implementation MMBackend
128 + (MMBackend *)sharedInstance
130     static MMBackend *singleton = nil;
131     return singleton ? singleton : (singleton = [MMBackend new]);
134 - (id)init
136     self = [super init];
137     if (!self) return nil;
139     outputQueue = [[NSMutableArray alloc] init];
140     inputQueue = [[NSMutableArray alloc] init];
141     drawData = [[NSMutableData alloc] initWithCapacity:1024];
142     connectionNameDict = [[NSMutableDictionary alloc] init];
143     clientProxyDict = [[NSMutableDictionary alloc] init];
144     serverReplyDict = [[NSMutableDictionary alloc] init];
146     NSBundle *mainBundle = [NSBundle mainBundle];
147     NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
148     if (path)
149         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
151     path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
152     if (path)
153         sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
154             retain];
156     path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
157     if (path)
158         actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
160     if (!(colorDict && sysColorDict && actionDict))
161         NSLog(@"ERROR: Failed to load dictionaries.%@",
162                 MMSymlinkWarningString);
164     return self;
167 - (void)dealloc
169     //NSLog(@"%@ %s", [self className], _cmd);
170     [[NSNotificationCenter defaultCenter] removeObserver:self];
172     gui_mch_free_font(oldWideFont);  oldWideFont = NOFONT;
173     [blinkTimer release];  blinkTimer = nil;
174     [alternateServerName release];  alternateServerName = nil;
175     [serverReplyDict release];  serverReplyDict = nil;
176     [clientProxyDict release];  clientProxyDict = nil;
177     [connectionNameDict release];  connectionNameDict = nil;
178     [inputQueue release];  inputQueue = nil;
179     [outputQueue release];  outputQueue = nil;
180     [drawData release];  drawData = nil;
181     [connection release];  connection = nil;
182     [actionDict release];  actionDict = nil;
183     [sysColorDict release];  sysColorDict = nil;
184     [colorDict release];  colorDict = nil;
186     [super dealloc];
189 - (void)setBackgroundColor:(int)color
191     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
194 - (void)setForegroundColor:(int)color
196     foregroundColor = MM_COLOR(color);
199 - (void)setSpecialColor:(int)color
201     specialColor = MM_COLOR(color);
204 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
206     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
207     defaultForegroundColor = MM_COLOR(fg);
209     NSMutableData *data = [NSMutableData data];
211     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
212     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
214     [self queueMessage:SetDefaultColorsMsgID data:data];
217 - (NSConnection *)connection
219     if (!connection) {
220         // NOTE!  If the name of the connection changes here it must also be
221         // updated in MMAppController.m.
222         NSString *name = [NSString stringWithFormat:@"%@-connection",
223                [[NSBundle mainBundle] bundlePath]];
225         connection = [NSConnection connectionWithRegisteredName:name host:nil];
226         [connection retain];
227     }
229     // NOTE: 'connection' may be nil here.
230     return connection;
233 - (NSDictionary *)actionDict
235     return actionDict;
238 - (int)initialWindowLayout
240     return initialWindowLayout;
243 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
245     [self queueMessage:msgid data:[props dictionaryAsData]];
248 - (BOOL)checkin
250     if (![self connection]) {
251         if (waitForAck) {
252             // This is a preloaded process and as such should not cause the
253             // MacVim to be opened.  We probably got here as a result of the
254             // user quitting MacVim while the process was preloading, so exit
255             // this process too.
256             // (Don't use mch_exit() since it assumes the process has properly
257             // started.)
258             exit(0);
259         }
261         NSBundle *mainBundle = [NSBundle mainBundle];
262 #if 0
263         OSStatus status;
264         FSRef ref;
266         // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
267         // the API to pass Apple Event parameters is broken on 10.4).
268         NSString *path = [mainBundle bundlePath];
269         status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
270         if (noErr == status) {
271             // Pass parameter to the 'Open' Apple Event that tells MacVim not
272             // to open an untitled window.
273             NSAppleEventDescriptor *desc =
274                     [NSAppleEventDescriptor recordDescriptor];
275             [desc setParamDescriptor:
276                     [NSAppleEventDescriptor descriptorWithBoolean:NO]
277                           forKeyword:keyMMUntitledWindow];
279             LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
280                     kLSLaunchDefaults, NULL };
281             status = LSOpenFromRefSpec(&spec, NULL);
282         }
284         if (noErr != status) {
285         NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
286                 path, MMSymlinkWarningString);
287             return NO;
288         }
289 #else
290         // Launch MacVim using NSTask.  For some reason the above code using
291         // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
292         // fails, the dock icon starts bouncing and never stops).  It seems
293         // like rebuilding the Launch Services database takes care of this
294         // problem, but the NSTask way seems more stable so stick with it.
295         //
296         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
297         // that the GUI won't be activated (or raised) so there is a hack in
298         // MMAppController which raises the app when a new window is opened.
299         NSMutableArray *args = [NSMutableArray arrayWithObjects:
300             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
301         NSString *exeName = [[mainBundle infoDictionary]
302                 objectForKey:@"CFBundleExecutable"];
303         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
304         if (!path) {
305             NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
306                     MMSymlinkWarningString);
307             return NO;
308         }
310         [NSTask launchedTaskWithLaunchPath:path arguments:args];
311 #endif
313         // HACK!  Poll the mach bootstrap server until it returns a valid
314         // connection to detect that MacVim has finished launching.  Also set a
315         // time-out date so that we don't get stuck doing this forever.
316         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
317         while (![self connection] &&
318                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
319             [[NSRunLoop currentRunLoop]
320                     runMode:NSDefaultRunLoopMode
321                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
323         // NOTE: [self connection] will set 'connection' as a side-effect.
324         if (!connection) {
325             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
326             return NO;
327         }
328     }
330     BOOL ok = NO;
331     @try {
332         [[NSNotificationCenter defaultCenter] addObserver:self
333                 selector:@selector(connectionDidDie:)
334                     name:NSConnectionDidDieNotification object:connection];
336         appProxy = [[connection rootProxy] retain];
337         [appProxy setProtocolForProxy:@protocol(MMAppProtocol)];
339         int pid = [[NSProcessInfo processInfo] processIdentifier];
341         identifier = [appProxy connectBackend:self pid:pid];
342         if (identifier != 0)
343             ok = YES;
344     }
345     @catch (NSException *e) {
346         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
347     }
349     return ok;
352 - (BOOL)openGUIWindow
354     [self queueMessage:OpenWindowMsgID data:nil];
355     return YES;
358 - (void)clearAll
360     int type = ClearAllDrawType;
362     // Any draw commands in queue are effectively obsolete since this clearAll
363     // will negate any effect they have, therefore we may as well clear the
364     // draw queue.
365     [self clearDrawData];
367     [drawData appendBytes:&type length:sizeof(int)];
370 - (void)clearBlockFromRow:(int)row1 column:(int)col1
371                     toRow:(int)row2 column:(int)col2
373     int type = ClearBlockDrawType;
375     [drawData appendBytes:&type length:sizeof(int)];
377     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
378     [drawData appendBytes:&row1 length:sizeof(int)];
379     [drawData appendBytes:&col1 length:sizeof(int)];
380     [drawData appendBytes:&row2 length:sizeof(int)];
381     [drawData appendBytes:&col2 length:sizeof(int)];
384 - (void)deleteLinesFromRow:(int)row count:(int)count
385               scrollBottom:(int)bottom left:(int)left right:(int)right
387     int type = DeleteLinesDrawType;
389     [drawData appendBytes:&type length:sizeof(int)];
391     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
392     [drawData appendBytes:&row length:sizeof(int)];
393     [drawData appendBytes:&count length:sizeof(int)];
394     [drawData appendBytes:&bottom length:sizeof(int)];
395     [drawData appendBytes:&left length:sizeof(int)];
396     [drawData appendBytes:&right length:sizeof(int)];
398     if (left == 0 && right == gui.num_cols-1)
399         [self didChangeWholeLine];
402 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
403              cells:(int)cells flags:(int)flags
405     if (len <= 0 || cells <= 0) return;
407     int type = DrawStringDrawType;
409     [drawData appendBytes:&type length:sizeof(int)];
411     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
412     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
413     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
414     [drawData appendBytes:&row length:sizeof(int)];
415     [drawData appendBytes:&col length:sizeof(int)];
416     [drawData appendBytes:&cells length:sizeof(int)];
417     [drawData appendBytes:&flags length:sizeof(int)];
418     [drawData appendBytes:&len length:sizeof(int)];
419     [drawData appendBytes:s length:len];
422 - (void)insertLinesFromRow:(int)row count:(int)count
423               scrollBottom:(int)bottom left:(int)left right:(int)right
425     int type = InsertLinesDrawType;
427     [drawData appendBytes:&type length:sizeof(int)];
429     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
430     [drawData appendBytes:&row length:sizeof(int)];
431     [drawData appendBytes:&count length:sizeof(int)];
432     [drawData appendBytes:&bottom length:sizeof(int)];
433     [drawData appendBytes:&left length:sizeof(int)];
434     [drawData appendBytes:&right length:sizeof(int)];
436     if (left == 0 && right == gui.num_cols-1)
437         [self didChangeWholeLine];
440 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
441                fraction:(int)percent color:(int)color
443     int type = DrawCursorDrawType;
444     unsigned uc = MM_COLOR(color);
446     [drawData appendBytes:&type length:sizeof(int)];
448     [drawData appendBytes:&uc length:sizeof(unsigned)];
449     [drawData appendBytes:&row length:sizeof(int)];
450     [drawData appendBytes:&col length:sizeof(int)];
451     [drawData appendBytes:&shape length:sizeof(int)];
452     [drawData appendBytes:&percent length:sizeof(int)];
455 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
456                    numColumns:(int)nc invert:(int)invert
458     int type = DrawInvertedRectDrawType;
459     [drawData appendBytes:&type length:sizeof(int)];
461     [drawData appendBytes:&row length:sizeof(int)];
462     [drawData appendBytes:&col length:sizeof(int)];
463     [drawData appendBytes:&nr length:sizeof(int)];
464     [drawData appendBytes:&nc length:sizeof(int)];
465     [drawData appendBytes:&invert length:sizeof(int)];
468 - (void)update
470     // Keep running the run-loop until there is no more input to process.
471     while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
472             == kCFRunLoopRunHandledSource)
473         ;   // do nothing
476 - (void)flushQueue:(BOOL)force
478     // NOTE: This variable allows for better control over when the queue is
479     // flushed.  It can be set to YES at the beginning of a sequence of calls
480     // that may potentially add items to the queue, and then restored back to
481     // NO.
482     if (flushDisabled) return;
484     if ([drawData length] > 0) {
485         // HACK!  Detect changes to 'guifontwide'.
486         if (gui.wide_font != oldWideFont) {
487             gui_mch_free_font(oldWideFont);
488             oldWideFont = gui_mch_retain_font(gui.wide_font);
489             [self setFont:oldWideFont wide:YES];
490         }
492         int type = SetCursorPosDrawType;
493         [drawData appendBytes:&type length:sizeof(type)];
494         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
495         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
497         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
498         [self clearDrawData];
499     }
501     if ([outputQueue count] > 0) {
502         [self insertVimStateMessage];
504         @try {
505             //NSLog(@"[%s] Flushing (count=%d)", _cmd, [outputQueue count]);
506             [appProxy processInput:outputQueue forIdentifier:identifier];
507         }
508         @catch (NSException *e) {
509             NSLog(@"[%s] Exception caught: \"%@\"", _cmd, e);
510             NSLog(@"outputQueue(len:%d)=%@", [outputQueue count]/2,
511                     outputQueue);
512             if (![connection isValid]) {
513                 NSLog(@"WARNING! Connection is invalid, exit now!");
514                 NSLog(@"waitForAck=%d got_int=%d", waitForAck, got_int);
515                 mch_exit(-1);
516             }
517         }
519         [outputQueue removeAllObjects];
520     }
523 - (BOOL)waitForInput:(int)milliseconds
525     // Return NO if we timed out waiting for input, otherwise return YES.
526     BOOL inputReceived = NO;
528     // Only start the run loop if the input queue is empty, otherwise process
529     // the input first so that the input on queue isn't delayed.
530     if ([inputQueue count]) {
531         inputReceived = YES;
532     } else {
533         // Wait for the specified amount of time, unless 'milliseconds' is
534         // negative in which case we wait "forever" (1e6 seconds translates to
535         // approximately 11 days).
536         CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
538         while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
539                 == kCFRunLoopRunHandledSource) {
540             // In order to ensure that all input on the run-loop has been
541             // processed we set the timeout to 0 and keep processing until the
542             // run-loop times out.
543             dt = 0.0;
544             inputReceived = YES;
545         }
546     }
548     // The above calls may have placed messages on the input queue so process
549     // it now.  This call may enter a blocking loop.
550     if ([inputQueue count] > 0)
551         [self processInputQueue];
553     return inputReceived;
556 - (void)exit
558     // NOTE: This is called if mch_exit() is called.  Since we assume here that
559     // the process has started properly, be sure to use exit() instead of
560     // mch_exit() to prematurely terminate a process (or set 'isTerminating'
561     // first).
563     // Make sure no connectionDidDie: notification is received now that we are
564     // already exiting.
565     [[NSNotificationCenter defaultCenter] removeObserver:self];
567     // The 'isTerminating' flag indicates that the frontend is also exiting so
568     // there is no need to flush any more output since the frontend won't look
569     // at it anyway.
570     if (!isTerminating && [connection isValid]) {
571         @try {
572             // Flush the entire queue in case a VimLeave autocommand added
573             // something to the queue.
574             [self queueMessage:CloseWindowMsgID data:nil];
575             [appProxy processInput:outputQueue forIdentifier:identifier];
576         }
577         @catch (NSException *e) {
578             NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
579         }
581         // NOTE: If Cmd-w was pressed to close the window the menu is briefly
582         // highlighted and during this pause the frontend won't receive any DO
583         // messages.  If the Vim process exits before this highlighting has
584         // finished Cocoa will emit the following error message:
585         //   *** -[NSMachPort handlePortMessage:]: dropping incoming DO message
586         //   because the connection or ports are invalid
587         // To avoid this warning we delay here.  If the warning still appears
588         // this delay may need to be increased.
589         usleep(150000);
590     }
592 #ifdef MAC_CLIENTSERVER
593     // The default connection is used for the client/server code.
594     [[NSConnection defaultConnection] setRootObject:nil];
595     [[NSConnection defaultConnection] invalidate];
596 #endif
599 - (void)selectTab:(int)index
601     //NSLog(@"%s%d", _cmd, index);
603     index -= 1;
604     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
605     [self queueMessage:SelectTabMsgID data:data];
608 - (void)updateTabBar
610     //NSLog(@"%s", _cmd);
612     NSMutableData *data = [NSMutableData data];
614     int idx = tabpage_index(curtab) - 1;
615     [data appendBytes:&idx length:sizeof(int)];
617     tabpage_T *tp;
618     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
619         // Count the number of windows in the tabpage.
620         //win_T *wp = tp->tp_firstwin;
621         //int wincount;
622         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
623         //[data appendBytes:&wincount length:sizeof(int)];
625         int tabProp = MMTabInfoCount;
626         [data appendBytes:&tabProp length:sizeof(int)];
627         for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
628             // This function puts the label of the tab in the global 'NameBuff'.
629             get_tabline_label(tp, (tabProp == MMTabToolTip));
630             NSString *s = [NSString stringWithVimString:NameBuff];
631             int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
632             if (len < 0)
633                 len = 0;
635             [data appendBytes:&len length:sizeof(int)];
636             if (len > 0)
637                 [data appendBytes:[s UTF8String] length:len];
638         }
639     }
641     [self queueMessage:UpdateTabBarMsgID data:data];
644 - (BOOL)tabBarVisible
646     return tabBarVisible;
649 - (void)showTabBar:(BOOL)enable
651     tabBarVisible = enable;
653     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
654     [self queueMessage:msgid data:nil];
657 - (void)setRows:(int)rows columns:(int)cols
659     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
661     int dim[] = { rows, cols };
662     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
664     [self queueMessage:SetTextDimensionsMsgID data:data];
667 - (void)setWindowTitle:(char *)title
669     NSMutableData *data = [NSMutableData data];
670     int len = strlen(title);
671     if (len <= 0) return;
673     [data appendBytes:&len length:sizeof(int)];
674     [data appendBytes:title length:len];
676     [self queueMessage:SetWindowTitleMsgID data:data];
679 - (void)setDocumentFilename:(char *)filename
681     NSMutableData *data = [NSMutableData data];
682     int len = filename ? strlen(filename) : 0;
684     [data appendBytes:&len length:sizeof(int)];
685     if (len > 0)
686         [data appendBytes:filename length:len];
688     [self queueMessage:SetDocumentFilenameMsgID data:data];
691 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
693     char_u *s = NULL;
695     [self queueMessage:BrowseForFileMsgID properties:attr];
696     [self flushQueue:YES];
698     @try {
699         [self waitForDialogReturn];
701         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
702             s = [dialogReturn vimStringSave];
704         [dialogReturn release];  dialogReturn = nil;
705     }
706     @catch (NSException *e) {
707         NSLog(@"[%s] Exception caught: \"%@\"", _cmd, e);
708     }
710     return (char *)s;
713 - (oneway void)setDialogReturn:(in bycopy id)obj
715     // NOTE: This is called by
716     //   - [MMVimController panelDidEnd:::], and
717     //   - [MMVimController alertDidEnd:::],
718     // to indicate that a save/open panel or alert has finished.
720     // We want to distinguish between "no dialog return yet" and "dialog
721     // returned nothing".  The former can be tested with dialogReturn == nil,
722     // the latter with dialogReturn == [NSNull null].
723     if (!obj) obj = [NSNull null];
725     if (obj != dialogReturn) {
726         [dialogReturn release];
727         dialogReturn = [obj retain];
728     }
731 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
733     int retval = 0;
735     [self queueMessage:ShowDialogMsgID properties:attr];
736     [self flushQueue:YES];
738     @try {
739         [self waitForDialogReturn];
741         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
742                 && [dialogReturn count]) {
743             retval = [[dialogReturn objectAtIndex:0] intValue];
744             if (txtfield && [dialogReturn count] > 1) {
745                 NSString *retString = [dialogReturn objectAtIndex:1];
746                 char_u *ret = (char_u*)[retString UTF8String];
747 #ifdef FEAT_MBYTE
748                 ret = CONVERT_FROM_UTF8(ret);
749 #endif
750                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
751 #ifdef FEAT_MBYTE
752                 CONVERT_FROM_UTF8_FREE(ret);
753 #endif
754             }
755         }
757         [dialogReturn release]; dialogReturn = nil;
758     }
759     @catch (NSException *e) {
760         NSLog(@"[%s] Exception caught: \"%@\"", _cmd, e);
761     }
763     return retval;
766 - (void)showToolbar:(int)enable flags:(int)flags
768     NSMutableData *data = [NSMutableData data];
770     [data appendBytes:&enable length:sizeof(int)];
771     [data appendBytes:&flags length:sizeof(int)];
773     [self queueMessage:ShowToolbarMsgID data:data];
776 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
778     NSMutableData *data = [NSMutableData data];
780     [data appendBytes:&ident length:sizeof(long)];
781     [data appendBytes:&type length:sizeof(int)];
783     [self queueMessage:CreateScrollbarMsgID data:data];
786 - (void)destroyScrollbarWithIdentifier:(long)ident
788     NSMutableData *data = [NSMutableData data];
789     [data appendBytes:&ident length:sizeof(long)];
791     [self queueMessage:DestroyScrollbarMsgID data:data];
794 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
796     NSMutableData *data = [NSMutableData data];
798     [data appendBytes:&ident length:sizeof(long)];
799     [data appendBytes:&visible length:sizeof(int)];
801     [self queueMessage:ShowScrollbarMsgID data:data];
804 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
806     NSMutableData *data = [NSMutableData data];
808     [data appendBytes:&ident length:sizeof(long)];
809     [data appendBytes:&pos length:sizeof(int)];
810     [data appendBytes:&len length:sizeof(int)];
812     [self queueMessage:SetScrollbarPositionMsgID data:data];
815 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
816                     identifier:(long)ident
818     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
819     float prop = (float)size/(max+1);
820     if (fval < 0) fval = 0;
821     else if (fval > 1.0f) fval = 1.0f;
822     if (prop < 0) prop = 0;
823     else if (prop > 1.0f) prop = 1.0f;
825     NSMutableData *data = [NSMutableData data];
827     [data appendBytes:&ident length:sizeof(long)];
828     [data appendBytes:&fval length:sizeof(float)];
829     [data appendBytes:&prop length:sizeof(float)];
831     [self queueMessage:SetScrollbarThumbMsgID data:data];
834 - (void)setFont:(GuiFont)font wide:(BOOL)wide
836     NSString *fontName = (NSString *)font;
837     float size = 0;
838     NSArray *components = [fontName componentsSeparatedByString:@":"];
839     if ([components count] == 2) {
840         size = [[components lastObject] floatValue];
841         fontName = [components objectAtIndex:0];
842     }
844     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
845     NSMutableData *data = [NSMutableData data];
846     [data appendBytes:&size length:sizeof(float)];
847     [data appendBytes:&len length:sizeof(int)];
849     if (len > 0)
850         [data appendBytes:[fontName UTF8String] length:len];
851     else if (!wide)
852         return;     // Only the wide font can be set to nothing
854     [self queueMessage:(wide ? SetWideFontMsgID : SetFontMsgID) data:data];
857 - (void)executeActionWithName:(NSString *)name
859     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
861     if (len > 0) {
862         NSMutableData *data = [NSMutableData data];
864         [data appendBytes:&len length:sizeof(int)];
865         [data appendBytes:[name UTF8String] length:len];
867         [self queueMessage:ExecuteActionMsgID data:data];
868     }
871 - (void)setMouseShape:(int)shape
873     NSMutableData *data = [NSMutableData data];
874     [data appendBytes:&shape length:sizeof(int)];
875     [self queueMessage:SetMouseShapeMsgID data:data];
878 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
880     // Vim specifies times in milliseconds, whereas Cocoa wants them in
881     // seconds.
882     blinkWaitInterval = .001f*wait;
883     blinkOnInterval = .001f*on;
884     blinkOffInterval = .001f*off;
887 - (void)startBlink
889     if (blinkTimer) {
890         [blinkTimer invalidate];
891         [blinkTimer release];
892         blinkTimer = nil;
893     }
895     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
896             && gui.in_focus) {
897         blinkState = MMBlinkStateOn;
898         blinkTimer =
899             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
900                                               target:self
901                                             selector:@selector(blinkTimerFired:)
902                                             userInfo:nil repeats:NO] retain];
903         gui_update_cursor(TRUE, FALSE);
904         [self flushQueue:YES];
905     }
908 - (void)stopBlink
910     if (MMBlinkStateOff == blinkState) {
911         gui_update_cursor(TRUE, FALSE);
912         [self flushQueue:YES];
913     }
915     blinkState = MMBlinkStateNone;
918 - (void)adjustLinespace:(int)linespace
920     NSMutableData *data = [NSMutableData data];
921     [data appendBytes:&linespace length:sizeof(int)];
922     [self queueMessage:AdjustLinespaceMsgID data:data];
925 - (void)activate
927     [self queueMessage:ActivateMsgID data:nil];
930 - (void)setPreEditRow:(int)row column:(int)col
932     NSMutableData *data = [NSMutableData data];
933     [data appendBytes:&row length:sizeof(int)];
934     [data appendBytes:&col length:sizeof(int)];
935     [self queueMessage:SetPreEditPositionMsgID data:data];
938 - (int)lookupColorWithKey:(NSString *)key
940     if (!(key && [key length] > 0))
941         return INVALCOLOR;
943     NSString *stripKey = [[[[key lowercaseString]
944         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
945             componentsSeparatedByString:@" "]
946                componentsJoinedByString:@""];
948     if (stripKey && [stripKey length] > 0) {
949         // First of all try to lookup key in the color dictionary; note that
950         // all keys in this dictionary are lowercase with no whitespace.
951         id obj = [colorDict objectForKey:stripKey];
952         if (obj) return [obj intValue];
954         // The key was not in the dictionary; is it perhaps of the form
955         // #rrggbb?
956         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
957             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
958             [scanner setScanLocation:1];
959             unsigned hex = 0;
960             if ([scanner scanHexInt:&hex]) {
961                 return (int)hex;
962             }
963         }
965         // As a last resort, check if it is one of the system defined colors.
966         // The keys in this dictionary are also lowercase with no whitespace.
967         obj = [sysColorDict objectForKey:stripKey];
968         if (obj) {
969             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
970             if (col) {
971                 float r, g, b, a;
972                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
973                 [col getRed:&r green:&g blue:&b alpha:&a];
974                 return (((int)(r*255+.5f) & 0xff) << 16)
975                      + (((int)(g*255+.5f) & 0xff) << 8)
976                      +  ((int)(b*255+.5f) & 0xff);
977             }
978         }
979     }
981     //NSLog(@"WARNING: No color with key %@ found.", stripKey);
982     return INVALCOLOR;
985 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
987     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
988     id obj;
990     while ((obj = [e nextObject])) {
991         if ([value isEqual:obj])
992             return YES;
993     }
995     return NO;
998 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1000     NSMutableData *data = [NSMutableData data];
1001     [data appendBytes:&fuoptions length:sizeof(int)];
1002     bg = MM_COLOR(bg);
1003     [data appendBytes:&bg length:sizeof(int)];
1004     [self queueMessage:EnterFullscreenMsgID data:data];
1007 - (void)leaveFullscreen
1009     [self queueMessage:LeaveFullscreenMsgID data:nil];
1012 - (void)setFullscreenBackgroundColor:(int)color
1014     NSMutableData *data = [NSMutableData data];
1015     color = MM_COLOR(color);
1016     [data appendBytes:&color length:sizeof(int)];
1018     [self queueMessage:SetFullscreenColorMsgID data:data];
1021 - (void)setAntialias:(BOOL)antialias
1023     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1025     [self queueMessage:msgid data:nil];
1028 - (void)updateModifiedFlag
1030     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1031     // vice versa.
1032     int msgid = [self checkForModifiedBuffers]
1033             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1035     [self queueMessage:msgid data:nil];
1038 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1040     // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1041     // queue is processed since that only happens in waitForInput: (and Vim
1042     // regularly checks for Ctrl-C in between waiting for input).
1043     // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1044     // which waits on the run loop will fail to detect this message (e.g. in
1045     // waitForConnectionAcknowledgement).
1047     if (InsertTextMsgID == msgid && data != nil) {
1048         const void *bytes = [data bytes];
1049         bytes += sizeof(int);
1050         int len = *((int*)bytes);  bytes += sizeof(int);
1051         if (1 == len) {
1052             char_u *str = (char_u*)bytes;
1053             if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1054                     (str[0] == intr_char && intr_char != Ctrl_C)) {
1055                 got_int = TRUE;
1056                 [inputQueue removeAllObjects];
1057                 return;
1058             }
1059         }
1060     } else if (TerminateNowMsgID == msgid) {
1061         // Terminate immediately (the frontend is about to quit or this process
1062         // was aborted).  Don't preserve modified files since the user would
1063         // already have been presented with a dialog warning if there were any
1064         // modified files when we get here.
1065         isTerminating = YES;
1066         getout(0);
1067         return;
1068     }
1070     // Remove all previous instances of this message from the input queue, else
1071     // the input queue may fill up as a result of Vim not being able to keep up
1072     // with the speed at which new messages are received.
1073     // Keyboard input is never dropped, unless the input represents and
1074     // auto-repeated key.
1076     BOOL isKeyRepeat = NO;
1077     BOOL isKeyboardInput = NO;
1079     if (data && (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1080             CmdKeyMsgID == msgid)) {
1081         isKeyboardInput = YES;
1083         // The lowest bit of the first int is set if this key is a repeat.
1084         int flags = *((int*)[data bytes]);
1085         if (flags & 1)
1086             isKeyRepeat = YES;
1087     }
1089     // Keyboard input is not removed from the queue; repeats are ignored if
1090     // there already is keyboard input on the input queue.
1091     if (isKeyRepeat || !isKeyboardInput) {
1092         int i, count = [inputQueue count];
1093         for (i = 1; i < count; i+=2) {
1094             if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1095                 if (isKeyRepeat)
1096                     return;
1098                 [inputQueue removeObjectAtIndex:i];
1099                 [inputQueue removeObjectAtIndex:i-1];
1100                 break;
1101             }
1102         }
1103     }
1105     [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1106     [inputQueue addObject:(data ? (id)data : [NSNull null])];
1109 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1110                   errorString:(out bycopy NSString **)errstr
1112     return evalExprCocoa(expr, errstr);
1116 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1118     NSString *eval = nil;
1119     char_u *s = (char_u*)[expr UTF8String];
1121 #ifdef FEAT_MBYTE
1122     s = CONVERT_FROM_UTF8(s);
1123 #endif
1125     char_u *res = eval_client_expr_to_string(s);
1127 #ifdef FEAT_MBYTE
1128     CONVERT_FROM_UTF8_FREE(s);
1129 #endif
1131     if (res != NULL) {
1132         s = res;
1133 #ifdef FEAT_MBYTE
1134         s = CONVERT_TO_UTF8(s);
1135 #endif
1136         eval = [NSString stringWithUTF8String:(char*)s];
1137 #ifdef FEAT_MBYTE
1138         CONVERT_TO_UTF8_FREE(s);
1139 #endif
1140         vim_free(res);
1141     }
1143     return eval;
1146 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1148     // TODO: This method should share code with clip_mch_request_selection().
1150     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1151         // If there is no pasteboard, return YES to indicate that there is text
1152         // to copy.
1153         if (!pboard)
1154             return YES;
1156         clip_copy_selection();
1158         // Get the text to put on the pasteboard.
1159         long_u llen = 0; char_u *str = 0;
1160         int type = clip_convert_selection(&str, &llen, &clip_star);
1161         if (type < 0)
1162             return NO;
1163         
1164         // TODO: Avoid overflow.
1165         int len = (int)llen;
1166 #ifdef FEAT_MBYTE
1167         if (output_conv.vc_type != CONV_NONE) {
1168             char_u *conv_str = string_convert(&output_conv, str, &len);
1169             if (conv_str) {
1170                 vim_free(str);
1171                 str = conv_str;
1172             }
1173         }
1174 #endif
1176         NSString *string = [[NSString alloc]
1177             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1179         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1180         [pboard declareTypes:types owner:nil];
1181         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1182     
1183         [string release];
1184         vim_free(str);
1186         return ok;
1187     }
1189     return NO;
1192 - (oneway void)addReply:(in bycopy NSString *)reply
1193                  server:(in byref id <MMVimServerProtocol>)server
1195     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1197     // Replies might come at any time and in any order so we keep them in an
1198     // array inside a dictionary with the send port used as key.
1200     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1201     // HACK! Assume connection uses mach ports.
1202     int port = [(NSMachPort*)[conn sendPort] machPort];
1203     NSNumber *key = [NSNumber numberWithInt:port];
1205     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1206     if (!replies) {
1207         replies = [NSMutableArray array];
1208         [serverReplyDict setObject:replies forKey:key];
1209     }
1211     [replies addObject:reply];
1214 - (void)addInput:(in bycopy NSString *)input
1215           client:(in byref id <MMVimClientProtocol>)client
1217     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1219     // NOTE: We don't call addInput: here because it differs from
1220     // server_to_input_buf() in that it always sets the 'silent' flag and we
1221     // don't want the MacVim client/server code to behave differently from
1222     // other platforms.
1223     char_u *s = [input vimStringSave];
1224     server_to_input_buf(s);
1225     vim_free(s);
1227     [self addClient:(id)client];
1230 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1231                  client:(in byref id <MMVimClientProtocol>)client
1233     [self addClient:(id)client];
1234     return [self evaluateExpression:expr];
1237 - (void)registerServerWithName:(NSString *)name
1239     NSString *svrName = name;
1240     NSConnection *svrConn = [NSConnection defaultConnection];
1241     unsigned i;
1243     for (i = 0; i < MMServerMax; ++i) {
1244         NSString *connName = [self connectionNameFromServerName:svrName];
1246         if ([svrConn registerName:connName]) {
1247             //NSLog(@"Registered server with name: %@", svrName);
1249             // TODO: Set request/reply time-outs to something else?
1250             //
1251             // Don't wait for requests (time-out means that the message is
1252             // dropped).
1253             [svrConn setRequestTimeout:0];
1254             //[svrConn setReplyTimeout:MMReplyTimeout];
1255             [svrConn setRootObject:self];
1257             // NOTE: 'serverName' is a global variable
1258             serverName = [svrName vimStringSave];
1259 #ifdef FEAT_EVAL
1260             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1261 #endif
1262 #ifdef FEAT_TITLE
1263             need_maketitle = TRUE;
1264 #endif
1265             [self queueMessage:SetServerNameMsgID data:
1266                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1267             break;
1268         }
1270         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1271     }
1274 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1275                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1276               silent:(BOOL)silent
1278     // NOTE: If 'name' equals 'serverName' then the request is local (client
1279     // and server are the same).  This case is not handled separately, so a
1280     // connection will be set up anyway (this simplifies the code).
1282     NSConnection *conn = [self connectionForServerName:name];
1283     if (!conn) {
1284         if (!silent) {
1285             char_u *s = (char_u*)[name UTF8String];
1286 #ifdef FEAT_MBYTE
1287             s = CONVERT_FROM_UTF8(s);
1288 #endif
1289             EMSG2(_(e_noserver), s);
1290 #ifdef FEAT_MBYTE
1291             CONVERT_FROM_UTF8_FREE(s);
1292 #endif
1293         }
1294         return NO;
1295     }
1297     if (port) {
1298         // HACK! Assume connection uses mach ports.
1299         *port = [(NSMachPort*)[conn sendPort] machPort];
1300     }
1302     id proxy = [conn rootProxy];
1303     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1305     @try {
1306         if (expr) {
1307             NSString *eval = [proxy evaluateExpression:string client:self];
1308             if (reply) {
1309                 if (eval) {
1310                     *reply = [eval vimStringSave];
1311                 } else {
1312                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1313                 }
1314             }
1316             if (!eval)
1317                 return NO;
1318         } else {
1319             [proxy addInput:string client:self];
1320         }
1321     }
1322     @catch (NSException *e) {
1323         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1324         return NO;
1325     }
1327     return YES;
1330 - (NSArray *)serverList
1332     NSArray *list = nil;
1334     if ([self connection]) {
1335         id proxy = [connection rootProxy];
1336         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1338         @try {
1339             list = [proxy serverList];
1340         }
1341         @catch (NSException *e) {
1342             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1343         }
1344     } else {
1345         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1346     }
1348     return list;
1351 - (NSString *)peekForReplyOnPort:(int)port
1353     //NSLog(@"%s%d", _cmd, port);
1355     NSNumber *key = [NSNumber numberWithInt:port];
1356     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1357     if (replies && [replies count]) {
1358         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1359         //        [replies objectAtIndex:0]);
1360         return [replies objectAtIndex:0];
1361     }
1363     //NSLog(@"    No replies");
1364     return nil;
1367 - (NSString *)waitForReplyOnPort:(int)port
1369     //NSLog(@"%s%d", _cmd, port);
1370     
1371     NSConnection *conn = [self connectionForServerPort:port];
1372     if (!conn)
1373         return nil;
1375     NSNumber *key = [NSNumber numberWithInt:port];
1376     NSMutableArray *replies = nil;
1377     NSString *reply = nil;
1379     // Wait for reply as long as the connection to the server is valid (unless
1380     // user interrupts wait with Ctrl-C).
1381     while (!got_int && [conn isValid] &&
1382             !(replies = [serverReplyDict objectForKey:key])) {
1383         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1384                                  beforeDate:[NSDate distantFuture]];
1385     }
1387     if (replies) {
1388         if ([replies count] > 0) {
1389             reply = [[replies objectAtIndex:0] retain];
1390             //NSLog(@"    Got reply: %@", reply);
1391             [replies removeObjectAtIndex:0];
1392             [reply autorelease];
1393         }
1395         if ([replies count] == 0)
1396             [serverReplyDict removeObjectForKey:key];
1397     }
1399     return reply;
1402 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1404     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1405     if (client) {
1406         @try {
1407             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1408             [client addReply:reply server:self];
1409             return YES;
1410         }
1411         @catch (NSException *e) {
1412             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1413         }
1414     } else {
1415         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1416     }
1418     return NO;
1421 - (BOOL)waitForAck
1423     return waitForAck;
1426 - (void)setWaitForAck:(BOOL)yn
1428     waitForAck = yn;
1431 - (void)waitForConnectionAcknowledgement
1433     if (!waitForAck) return;
1435     while (waitForAck && !got_int && [connection isValid]) {
1436         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1437                                  beforeDate:[NSDate distantFuture]];
1438         //NSLog(@"  waitForAck=%d got_int=%d isValid=%d",
1439         //        waitForAck, got_int, [connection isValid]);
1440     }
1442     if (waitForAck) {
1443         // Never received a connection acknowledgement, so die.
1444         [[NSNotificationCenter defaultCenter] removeObserver:self];
1445         [appProxy release];  appProxy = nil;
1447         // NOTE: We intentionally do not call mch_exit() since this in turn
1448         // will lead to -[MMBackend exit] getting called which we want to
1449         // avoid.
1450         exit(0);
1451     }
1453     [self processInputQueue];
1456 - (oneway void)acknowledgeConnection
1458     //NSLog(@"%s", _cmd);
1459     waitForAck = NO;
1462 @end // MMBackend
1466 @implementation MMBackend (Private)
1468 - (void)clearDrawData
1470     [drawData setLength:0];
1471     numWholeLineChanges = offsetForDrawDataPrune = 0;
1474 - (void)didChangeWholeLine
1476     // It may happen that draw queue is filled up with lots of changes that
1477     // affect a whole row.  If the number of such changes equals twice the
1478     // number of visible rows then we can prune some commands off the queue.
1479     //
1480     // NOTE: If we don't perform this pruning the draw queue may grow
1481     // indefinitely if Vim were to repeatedly send draw commands without ever
1482     // waiting for new input (that's when the draw queue is flushed).  The one
1483     // instance I know where this can happen is when a command is executed in
1484     // the shell (think ":grep" with thousands of matches).
1486     ++numWholeLineChanges;
1487     if (numWholeLineChanges == gui.num_rows) {
1488         // Remember the offset to prune up to.
1489         offsetForDrawDataPrune = [drawData length];
1490     } else if (numWholeLineChanges == 2*gui.num_rows) {
1491         // Delete all the unnecessary draw commands.
1492         NSMutableData *d = [[NSMutableData alloc]
1493                     initWithBytes:[drawData bytes] + offsetForDrawDataPrune
1494                            length:[drawData length] - offsetForDrawDataPrune];
1495         offsetForDrawDataPrune = [d length];
1496         numWholeLineChanges -= gui.num_rows;
1497         [drawData release];
1498         drawData = d;
1499     }
1502 - (void)waitForDialogReturn
1504     // Keep processing the run loop until a dialog returns.  To avoid getting
1505     // stuck in an endless loop (could happen if the setDialogReturn: message
1506     // was lost) we also do some paranoia checks.
1507     //
1508     // Note that in Cocoa the user can still resize windows and select menu
1509     // items while a sheet is being displayed, so we can't just wait for the
1510     // first message to arrive and assume that is the setDialogReturn: call.
1512     while (nil == dialogReturn && !got_int && [connection isValid])
1513         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1514                                  beforeDate:[NSDate distantFuture]];
1516     // Search for any resize messages on the input queue.  All other messages
1517     // on the input queue are dropped.  The reason why we single out resize
1518     // messages is because the user may have resized the window while a sheet
1519     // was open.
1520     int i, count = [inputQueue count];
1521     if (count > 0) {
1522         id textDimData = nil;
1523         if (count%2 == 0) {
1524             for (i = count-2; i >= 0; i -= 2) {
1525                 int msgid = [[inputQueue objectAtIndex:i] intValue];
1526                 if (SetTextDimensionsMsgID == msgid) {
1527                     textDimData = [[inputQueue objectAtIndex:i+1] retain];
1528                     break;
1529                 }
1530             }
1531         }
1533         [inputQueue removeAllObjects];
1535         if (textDimData) {
1536             [inputQueue addObject:
1537                     [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1538             [inputQueue addObject:textDimData];
1539             [textDimData release];
1540         }
1541     }
1544 - (void)insertVimStateMessage
1546     // NOTE: This is the place to add Vim state that needs to be accessed from
1547     // MacVim.  Do not add state that could potentially require lots of memory
1548     // since this message gets sent each time the output queue is forcibly
1549     // flushed (e.g. storing the currently selected text would be a bad idea).
1550     // We take this approach of "pushing" the state to MacVim to avoid having
1551     // to make synchronous calls from MacVim to Vim in order to get state.
1553     BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1554     int numTabs = tabpage_index(NULL) - 1;
1555     if (numTabs < 0)
1556         numTabs = 0;
1558     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1559         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1560         [NSNumber numberWithInt:p_mh], @"p_mh",
1561         [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1562         [NSNumber numberWithBool:mmta], @"p_mmta",
1563         [NSNumber numberWithInt:numTabs], @"numTabs",
1564         nil];
1566     // Put the state before all other messages.
1567     int msgid = SetVimStateMsgID;
1568     [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1569     [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1570                       atIndex:0];
1573 - (void)processInputQueue
1575     if ([inputQueue count] == 0) return;
1577     // NOTE: One of the input events may cause this method to be called
1578     // recursively, so copy the input queue to a local variable and clear the
1579     // queue before starting to process input events (otherwise we could get
1580     // stuck in an endless loop).
1581     NSArray *q = [inputQueue copy];
1582     unsigned i, count = [q count];
1584     [inputQueue removeAllObjects];
1586     for (i = 1; i < count; i+=2) {
1587         int msgid = [[q objectAtIndex:i-1] intValue];
1588         id data = [q objectAtIndex:i];
1589         if ([data isEqual:[NSNull null]])
1590             data = nil;
1592         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1593         [self handleInputEvent:msgid data:data];
1594     }
1596     [q release];
1597     //NSLog(@"Clear input event queue");
1600 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1602     if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1603             CmdKeyMsgID == msgid) {
1604         if (!data) return;
1605         const void *bytes = [data bytes];
1606         int mods = *((int*)bytes);  bytes += sizeof(int);
1607         int len = *((int*)bytes);  bytes += sizeof(int);
1608         NSString *key = [[NSString alloc] initWithBytes:bytes
1609                                                  length:len
1610                                                encoding:NSUTF8StringEncoding];
1611         mods = eventModifierFlagsToVimModMask(mods);
1613         if (InsertTextMsgID == msgid)
1614             [self handleInsertText:key];
1615         else
1616             [self handleKeyDown:key modifiers:mods];
1618         [key release];
1619     } else if (ScrollWheelMsgID == msgid) {
1620         if (!data) return;
1621         const void *bytes = [data bytes];
1623         int row = *((int*)bytes);  bytes += sizeof(int);
1624         int col = *((int*)bytes);  bytes += sizeof(int);
1625         int flags = *((int*)bytes);  bytes += sizeof(int);
1626         float dy = *((float*)bytes);  bytes += sizeof(float);
1628         int button = MOUSE_5;
1629         if (dy > 0) button = MOUSE_4;
1631         flags = eventModifierFlagsToVimMouseModMask(flags);
1633         int numLines = (int)round(dy);
1634         if (numLines < 0) numLines = -numLines;
1635         if (numLines == 0) numLines = 1;
1637 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1638         gui.scroll_wheel_force = numLines;
1639 #endif
1641         gui_send_mouse_event(button, col, row, NO, flags);
1642     } else if (MouseDownMsgID == msgid) {
1643         if (!data) return;
1644         const void *bytes = [data bytes];
1646         int row = *((int*)bytes);  bytes += sizeof(int);
1647         int col = *((int*)bytes);  bytes += sizeof(int);
1648         int button = *((int*)bytes);  bytes += sizeof(int);
1649         int flags = *((int*)bytes);  bytes += sizeof(int);
1650         int count = *((int*)bytes);  bytes += sizeof(int);
1652         button = eventButtonNumberToVimMouseButton(button);
1653         if (button >= 0) {
1654             flags = eventModifierFlagsToVimMouseModMask(flags);
1655             gui_send_mouse_event(button, col, row, count>1, flags);
1656         }
1657     } else if (MouseUpMsgID == msgid) {
1658         if (!data) return;
1659         const void *bytes = [data bytes];
1661         int row = *((int*)bytes);  bytes += sizeof(int);
1662         int col = *((int*)bytes);  bytes += sizeof(int);
1663         int flags = *((int*)bytes);  bytes += sizeof(int);
1665         flags = eventModifierFlagsToVimMouseModMask(flags);
1667         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1668     } else if (MouseDraggedMsgID == msgid) {
1669         if (!data) return;
1670         const void *bytes = [data bytes];
1672         int row = *((int*)bytes);  bytes += sizeof(int);
1673         int col = *((int*)bytes);  bytes += sizeof(int);
1674         int flags = *((int*)bytes);  bytes += sizeof(int);
1676         flags = eventModifierFlagsToVimMouseModMask(flags);
1678         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1679     } else if (MouseMovedMsgID == msgid) {
1680         const void *bytes = [data bytes];
1681         int row = *((int*)bytes);  bytes += sizeof(int);
1682         int col = *((int*)bytes);  bytes += sizeof(int);
1684         gui_mouse_moved(col, row);
1685     } else if (AddInputMsgID == msgid) {
1686         NSString *string = [[NSString alloc] initWithData:data
1687                 encoding:NSUTF8StringEncoding];
1688         if (string) {
1689             [self addInput:string];
1690             [string release];
1691         }
1692     } else if (SelectTabMsgID == msgid) {
1693         if (!data) return;
1694         const void *bytes = [data bytes];
1695         int idx = *((int*)bytes) + 1;
1696         //NSLog(@"Selecting tab %d", idx);
1697         send_tabline_event(idx);
1698     } else if (CloseTabMsgID == msgid) {
1699         if (!data) return;
1700         const void *bytes = [data bytes];
1701         int idx = *((int*)bytes) + 1;
1702         //NSLog(@"Closing tab %d", idx);
1703         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1704     } else if (AddNewTabMsgID == msgid) {
1705         //NSLog(@"Adding new tab");
1706         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1707     } else if (DraggedTabMsgID == msgid) {
1708         if (!data) return;
1709         const void *bytes = [data bytes];
1710         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1711         // based.
1712         int idx = *((int*)bytes);
1714         tabpage_move(idx);
1715     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1716             || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1717         if (!data) return;
1718         const void *bytes = [data bytes];
1719         int rows = Rows;
1720         if (SetTextColumnsMsgID != msgid) {
1721             rows = *((int*)bytes);  bytes += sizeof(int);
1722         }
1723         int cols = Columns;
1724         if (SetTextRowsMsgID != msgid) {
1725             cols = *((int*)bytes);  bytes += sizeof(int);
1726         }
1728         NSData *d = data;
1729         if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1730             int dim[2] = { rows, cols };
1731             d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1732             msgid = SetTextDimensionsReplyMsgID;
1733         }
1735         if (SetTextDimensionsMsgID == msgid)
1736             msgid = SetTextDimensionsReplyMsgID;
1738         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1739         // gui_resize_shell(), so we have to manually set the rows and columns
1740         // here since MacVim doesn't change the rows and columns to avoid
1741         // inconsistent states between Vim and MacVim.  The message sent back
1742         // indicates that it is a reply to a message that originated in MacVim
1743         // since we need to be able to determine where a message originated.
1744         [self queueMessage:msgid data:d];
1746         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1747         gui_resize_shell(cols, rows);
1748     } else if (ExecuteMenuMsgID == msgid) {
1749         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1750         if (attrs) {
1751             NSArray *desc = [attrs objectForKey:@"descriptor"];
1752             vimmenu_T *menu = menu_for_descriptor(desc);
1753             if (menu)
1754                 gui_menu_cb(menu);
1755         }
1756     } else if (ToggleToolbarMsgID == msgid) {
1757         [self handleToggleToolbar];
1758     } else if (ScrollbarEventMsgID == msgid) {
1759         [self handleScrollbarEvent:data];
1760     } else if (SetFontMsgID == msgid) {
1761         [self handleSetFont:data];
1762     } else if (VimShouldCloseMsgID == msgid) {
1763         gui_shell_closed();
1764     } else if (DropFilesMsgID == msgid) {
1765         [self handleDropFiles:data];
1766     } else if (DropStringMsgID == msgid) {
1767         [self handleDropString:data];
1768     } else if (GotFocusMsgID == msgid) {
1769         if (!gui.in_focus)
1770             [self focusChange:YES];
1771     } else if (LostFocusMsgID == msgid) {
1772         if (gui.in_focus)
1773             [self focusChange:NO];
1774     } else if (SetMouseShapeMsgID == msgid) {
1775         const void *bytes = [data bytes];
1776         int shape = *((int*)bytes);  bytes += sizeof(int);
1777         update_mouseshape(shape);
1778     } else if (XcodeModMsgID == msgid) {
1779         [self handleXcodeMod:data];
1780     } else if (OpenWithArgumentsMsgID == msgid) {
1781         [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1782     } else if (FindReplaceMsgID == msgid) {
1783         [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1784     } else {
1785         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1786     }
1789 + (NSDictionary *)specialKeys
1791     static NSDictionary *specialKeys = nil;
1793     if (!specialKeys) {
1794         NSBundle *mainBundle = [NSBundle mainBundle];
1795         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1796                                               ofType:@"plist"];
1797         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1798     }
1800     return specialKeys;
1803 - (void)handleInsertText:(NSString *)text
1805     if (!text) return;
1807     char_u *str = (char_u*)[text UTF8String];
1808     int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1810 #ifdef FEAT_MBYTE
1811     char_u *conv_str = NULL;
1812     if (input_conv.vc_type != CONV_NONE) {
1813         conv_str = string_convert(&input_conv, str, &len);
1814         if (conv_str)
1815             str = conv_str;
1816     }
1817 #endif
1819     for (i = 0; i < len; ++i) {
1820         add_to_input_buf(str+i, 1);
1821         if (CSI == str[i]) {
1822             // NOTE: If the converted string contains the byte CSI, then it
1823             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1824             // won't work.
1825             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1826             add_to_input_buf(extra, 2);
1827         }
1828     }
1830 #ifdef FEAT_MBYTE
1831     if (conv_str)
1832         vim_free(conv_str);
1833 #endif
1836 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1838     // TODO: This code is a horrible mess -- clean up!
1839     char_u special[3];
1840     char_u modChars[3];
1841     char_u *chars = (char_u*)[key UTF8String];
1842 #ifdef FEAT_MBYTE
1843     char_u *conv_str = NULL;
1844 #endif
1845     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1847     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1848     // that new keys can easily be added.
1849     NSString *specialString = [[MMBackend specialKeys]
1850             objectForKey:key];
1851     if (specialString && [specialString length] > 1) {
1852         //NSLog(@"special key: %@", specialString);
1853         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1854                 [specialString characterAtIndex:1]);
1856         ikey = simplify_key(ikey, &mods);
1857         if (ikey == CSI)
1858             ikey = K_CSI;
1860         special[0] = CSI;
1861         special[1] = K_SECOND(ikey);
1862         special[2] = K_THIRD(ikey);
1864         chars = special;
1865         length = 3;
1866     } else if (1 == length && TAB == chars[0]) {
1867         // Tab is a trouble child:
1868         // - <Tab> is added to the input buffer as is
1869         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1870         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1871         //   to be converted to utf-8
1872         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1873         // - <C-Tab> is reserved by Mac OS X
1874         // - <D-Tab> is reserved by Mac OS X
1875         chars = special;
1876         special[0] = TAB;
1877         length = 1;
1879         if (mods & MOD_MASK_SHIFT) {
1880             mods &= ~MOD_MASK_SHIFT;
1881             special[0] = CSI;
1882             special[1] = K_SECOND(K_S_TAB);
1883             special[2] = K_THIRD(K_S_TAB);
1884             length = 3;
1885         } else if (mods & MOD_MASK_ALT) {
1886             int mtab = 0x80 | TAB;
1887 #ifdef FEAT_MBYTE
1888             if (enc_utf8) {
1889                 // Convert to utf-8
1890                 special[0] = (mtab >> 6) + 0xc0;
1891                 special[1] = mtab & 0xbf;
1892                 length = 2;
1893             } else
1894 #endif
1895             {
1896                 special[0] = mtab;
1897                 length = 1;
1898             }
1899             mods &= ~MOD_MASK_ALT;
1900         }
1901     } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1902         // META key is treated separately.  This code was taken from gui_w48.c
1903         // and gui_gtk_x11.c.
1904         char_u string[7];
1905         int ch = simplify_key(chars[0], &mods);
1907         // Remove the SHIFT modifier for keys where it's already included,
1908         // e.g., '(' and '*'
1909         if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1910             mods &= ~MOD_MASK_SHIFT;
1912         // Interpret the ALT key as making the key META, include SHIFT, etc.
1913         ch = extract_modifiers(ch, &mods);
1914         if (ch == CSI)
1915             ch = K_CSI;
1917         int len = 0;
1918         if (mods) {
1919             string[len++] = CSI;
1920             string[len++] = KS_MODIFIER;
1921             string[len++] = mods;
1922         }
1924         if (IS_SPECIAL(ch)) {
1925             string[len++] = CSI;
1926             string[len++] = K_SECOND(ch);
1927             string[len++] = K_THIRD(ch);
1928         } else {
1929             string[len++] = ch;
1930 #ifdef FEAT_MBYTE
1931             // TODO: What if 'enc' is not "utf-8"?
1932             if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1933                 string[len++] = ch & 0xbf;
1934                 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1935                 if (string[len-1] == CSI) {
1936                     string[len++] = KS_EXTRA;
1937                     string[len++] = (int)KE_CSI;
1938                 }
1939             }
1940 #endif
1941         }
1943         add_to_input_buf(string, len);
1944         return;
1945     } else if (length > 0) {
1946         unichar c = [key characterAtIndex:0];
1947         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1948         //        [key characterAtIndex:0], mods);
1950         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1951         // cleared since they are already added to the key by the AppKit.
1952         // Unfortunately, the only way to deal with when to clear the modifiers
1953         // or not seems to be to have hard-wired rules like this.
1954         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1955                     || 0x9 == c || 0xd == c || ESC == c) ) {
1956             mods &= ~MOD_MASK_SHIFT;
1957             mods &= ~MOD_MASK_CTRL;
1958             //NSLog(@"clear shift ctrl");
1959         }
1961 #ifdef FEAT_MBYTE
1962         if (input_conv.vc_type != CONV_NONE) {
1963             conv_str = string_convert(&input_conv, chars, &length);
1964             if (conv_str)
1965                 chars = conv_str;
1966         }
1967 #endif
1968     }
1970     if (chars && length > 0) {
1971         if (mods) {
1972             //NSLog(@"adding mods: %d", mods);
1973             modChars[0] = CSI;
1974             modChars[1] = KS_MODIFIER;
1975             modChars[2] = mods;
1976             add_to_input_buf(modChars, 3);
1977         }
1979         //NSLog(@"add to input buf: 0x%x", chars[0]);
1980         // TODO: Check for CSI bytes?
1981         add_to_input_buf(chars, length);
1982     }
1984 #ifdef FEAT_MBYTE
1985     if (conv_str)
1986         vim_free(conv_str);
1987 #endif
1990 - (void)queueMessage:(int)msgid data:(NSData *)data
1992     //if (msgid != EnableMenuItemMsgID)
1993     //    NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1995     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1996     if (data)
1997         [outputQueue addObject:data];
1998     else
1999         [outputQueue addObject:[NSData data]];
2002 - (void)connectionDidDie:(NSNotification *)notification
2004     // If the main connection to MacVim is lost this means that either MacVim
2005     // has crashed or this process did not receive its termination message
2006     // properly (e.g. if the TerminateNowMsgID was dropped).
2007     //
2008     // NOTE: This is not called if a Vim controller invalidates its connection.
2010     NSLog(@"WARNING[%s]: Main connection was lost before process had a chance "
2011             "to terminate; preserving swap files.", _cmd);
2012     getout_preserve_modified(1);
2015 - (void)blinkTimerFired:(NSTimer *)timer
2017     NSTimeInterval timeInterval = 0;
2019     [blinkTimer release];
2020     blinkTimer = nil;
2022     if (MMBlinkStateOn == blinkState) {
2023         gui_undraw_cursor();
2024         blinkState = MMBlinkStateOff;
2025         timeInterval = blinkOffInterval;
2026     } else if (MMBlinkStateOff == blinkState) {
2027         gui_update_cursor(TRUE, FALSE);
2028         blinkState = MMBlinkStateOn;
2029         timeInterval = blinkOnInterval;
2030     }
2032     if (timeInterval > 0) {
2033         blinkTimer = 
2034             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2035                                             selector:@selector(blinkTimerFired:)
2036                                             userInfo:nil repeats:NO] retain];
2037         [self flushQueue:YES];
2038     }
2041 - (void)focusChange:(BOOL)on
2043     gui_focus_change(on);
2046 - (void)handleToggleToolbar
2048     // If 'go' contains 'T', then remove it, else add it.
2050     char_u go[sizeof(GO_ALL)+2];
2051     char_u *p;
2052     int len;
2054     STRCPY(go, p_go);
2055     p = vim_strchr(go, GO_TOOLBAR);
2056     len = STRLEN(go);
2058     if (p != NULL) {
2059         char_u *end = go + len;
2060         while (p < end) {
2061             p[0] = p[1];
2062             ++p;
2063         }
2064     } else {
2065         go[len] = GO_TOOLBAR;
2066         go[len+1] = NUL;
2067     }
2069     set_option_value((char_u*)"guioptions", 0, go, 0);
2071     [self redrawScreen];
2074 - (void)handleScrollbarEvent:(NSData *)data
2076     if (!data) return;
2078     const void *bytes = [data bytes];
2079     long ident = *((long*)bytes);  bytes += sizeof(long);
2080     int hitPart = *((int*)bytes);  bytes += sizeof(int);
2081     float fval = *((float*)bytes);  bytes += sizeof(float);
2082     scrollbar_T *sb = gui_find_scrollbar(ident);
2084     if (sb) {
2085         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2086         long value = sb_info->value;
2087         long size = sb_info->size;
2088         long max = sb_info->max;
2089         BOOL isStillDragging = NO;
2090         BOOL updateKnob = YES;
2092         switch (hitPart) {
2093         case NSScrollerDecrementPage:
2094             value -= (size > 2 ? size - 2 : 1);
2095             break;
2096         case NSScrollerIncrementPage:
2097             value += (size > 2 ? size - 2 : 1);
2098             break;
2099         case NSScrollerDecrementLine:
2100             --value;
2101             break;
2102         case NSScrollerIncrementLine:
2103             ++value;
2104             break;
2105         case NSScrollerKnob:
2106             isStillDragging = YES;
2107             // fall through ...
2108         case NSScrollerKnobSlot:
2109             value = (long)(fval * (max - size + 1));
2110             // fall through ...
2111         default:
2112             updateKnob = NO;
2113             break;
2114         }
2116         //NSLog(@"value %d -> %d", sb_info->value, value);
2117         gui_drag_scrollbar(sb, value, isStillDragging);
2119         if (updateKnob) {
2120             // Dragging the knob or option+clicking automatically updates
2121             // the knob position (on the actual NSScroller), so we only
2122             // need to set the knob position in the other cases.
2123             if (sb->wp) {
2124                 // Update both the left&right vertical scrollbars.
2125                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2126                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2127                 [self setScrollbarThumbValue:value size:size max:max
2128                                   identifier:identLeft];
2129                 [self setScrollbarThumbValue:value size:size max:max
2130                                   identifier:identRight];
2131             } else {
2132                 // Update the horizontal scrollbar.
2133                 [self setScrollbarThumbValue:value size:size max:max
2134                                   identifier:ident];
2135             }
2136         }
2137     }
2140 - (void)handleSetFont:(NSData *)data
2142     if (!data) return;
2144     const void *bytes = [data bytes];
2145     float pointSize = *((float*)bytes);  bytes += sizeof(float);
2146     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2147     bytes += sizeof(unsigned);  // len not used
2149     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2150     [name appendString:[NSString stringWithFormat:@":h%d", (int)pointSize]];
2151     char_u *s = (char_u*)[name UTF8String];
2153 #ifdef FEAT_MBYTE
2154     s = CONVERT_FROM_UTF8(s);
2155 #endif
2157     set_option_value((char_u*)"guifont", 0, s, 0);
2159 #ifdef FEAT_MBYTE
2160     CONVERT_FROM_UTF8_FREE(s);
2161 #endif
2163     [self redrawScreen];
2166 - (void)handleDropFiles:(NSData *)data
2168     // TODO: Get rid of this method; instead use Vim script directly.  At the
2169     // moment I know how to do this to open files in tabs, but I'm not sure how
2170     // to add the filenames to the command line when in command line mode.
2172     if (!data) return;
2174     NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2175     if (!args) return;
2177     id obj = [args objectForKey:@"forceOpen"];
2178     BOOL forceOpen = YES;
2179     if (obj)
2180         forceOpen = [obj boolValue];
2182     NSArray *filenames = [args objectForKey:@"filenames"];
2183     if (!(filenames && [filenames count] > 0)) return;
2185 #ifdef FEAT_DND
2186     if (!forceOpen && (State & CMDLINE)) {
2187         // HACK!  If Vim is in command line mode then the files names
2188         // should be added to the command line, instead of opening the
2189         // files in tabs (unless forceOpen is set).  This is taken care of by
2190         // gui_handle_drop().
2191         int n = [filenames count];
2192         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2193         if (fnames) {
2194             int i = 0;
2195             for (i = 0; i < n; ++i)
2196                 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2198             // NOTE!  This function will free 'fnames'.
2199             // HACK!  It is assumed that the 'x' and 'y' arguments are
2200             // unused when in command line mode.
2201             gui_handle_drop(0, 0, 0, fnames, n);
2202         }
2203     } else
2204 #endif // FEAT_DND
2205     {
2206         [self handleOpenWithArguments:args];
2207     }
2210 - (void)handleDropString:(NSData *)data
2212     if (!data) return;
2214 #ifdef FEAT_DND
2215     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2216     const void *bytes = [data bytes];
2217     int len = *((int*)bytes);  bytes += sizeof(int);
2218     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2220     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2221     NSRange range = { 0, [string length] };
2222     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2223                                          withString:@"\x0a" options:0
2224                                               range:range];
2225     if (0 == n) {
2226         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2227                                        options:0 range:range];
2228     }
2230     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2231     char_u *s = (char_u*)[string UTF8String];
2232 #ifdef FEAT_MBYTE
2233     if (input_conv.vc_type != CONV_NONE)
2234         s = string_convert(&input_conv, s, &len);
2235 #endif
2236     dnd_yank_drag_data(s, len);
2237 #ifdef FEAT_MBYTE
2238     if (input_conv.vc_type != CONV_NONE)
2239         vim_free(s);
2240 #endif
2241     add_to_input_buf(dropkey, sizeof(dropkey));
2242 #endif // FEAT_DND
2245 - (void)startOdbEditWithArguments:(NSDictionary *)args
2247 #ifdef FEAT_ODB_EDITOR
2248     id obj = [args objectForKey:@"remoteID"];
2249     if (!obj) return;
2251     OSType serverID = [obj unsignedIntValue];
2252     NSString *remotePath = [args objectForKey:@"remotePath"];
2254     NSAppleEventDescriptor *token = nil;
2255     NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2256     obj = [args objectForKey:@"remoteTokenDescType"];
2257     if (tokenData && obj) {
2258         DescType tokenType = [obj unsignedLongValue];
2259         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2260                                                                 data:tokenData];
2261     }
2263     NSArray *filenames = [args objectForKey:@"filenames"];
2264     unsigned i, numFiles = [filenames count];
2265     for (i = 0; i < numFiles; ++i) {
2266         NSString *filename = [filenames objectAtIndex:i];
2267         char_u *s = [filename vimStringSave];
2268         buf_T *buf = buflist_findname(s);
2269         vim_free(s);
2271         if (buf) {
2272             if (buf->b_odb_token) {
2273                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2274                 buf->b_odb_token = NULL;
2275             }
2277             if (buf->b_odb_fname) {
2278                 vim_free(buf->b_odb_fname);
2279                 buf->b_odb_fname = NULL;
2280             }
2282             buf->b_odb_server_id = serverID;
2284             if (token)
2285                 buf->b_odb_token = [token retain];
2286             if (remotePath)
2287                 buf->b_odb_fname = [remotePath vimStringSave];
2288         } else {
2289             NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2290                     filename);
2291         }
2292     }
2293 #endif // FEAT_ODB_EDITOR
2296 - (void)handleXcodeMod:(NSData *)data
2298 #if 0
2299     const void *bytes = [data bytes];
2300     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2301     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2302     if (0 == len)
2303         return;
2305     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2306             descriptorWithDescriptorType:type
2307                                    bytes:bytes
2308                                   length:len];
2309 #endif
2312 - (void)handleOpenWithArguments:(NSDictionary *)args
2314     // ARGUMENT:                DESCRIPTION:
2315     // -------------------------------------------------------------
2316     // filenames                list of filenames
2317     // dontOpen                 don't open files specified in above argument
2318     // layout                   which layout to use to open files
2319     // selectionRange           range of lines to select
2320     // searchText               string to search for
2321     // cursorLine               line to position the cursor on
2322     // cursorColumn             column to position the cursor on
2323     //                          (only valid when "cursorLine" is set)
2324     // remoteID                 ODB parameter
2325     // remotePath               ODB parameter
2326     // remoteTokenDescType      ODB parameter
2327     // remoteTokenData          ODB parameter
2329     //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2331     NSArray *filenames = [args objectForKey:@"filenames"];
2332     int i, numFiles = filenames ? [filenames count] : 0;
2333     BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2334     int layout = [[args objectForKey:@"layout"] intValue];
2336     // Change to directory of first file to open if this is an "unused" editor
2337     // (but do not do this if editing remotely).
2338     if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2339             && (starting || [self unusedEditor]) ) {
2340         char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2341         vim_chdirfile(s);
2342         vim_free(s);
2343     }
2345     if (starting > 0) {
2346         // When Vim is starting we simply add the files to be opened to the
2347         // global arglist and Vim will take care of opening them for us.
2348         if (openFiles && numFiles > 0) {
2349             for (i = 0; i < numFiles; i++) {
2350                 NSString *fname = [filenames objectAtIndex:i];
2351                 char_u *p = NULL;
2353                 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2354                         || (p = [fname vimStringSave]) == NULL)
2355                     exit(2); // See comment in -[MMBackend exit]
2356                 else
2357                     alist_add(&global_alist, p, 2);
2358             }
2360             // Vim will take care of arranging the files added to the arglist
2361             // in windows or tabs; all we must do is to specify which layout to
2362             // use.
2363             initialWindowLayout = layout;
2364         }
2365     } else {
2366         // When Vim is already open we resort to some trickery to open the
2367         // files with the specified layout.
2368         //
2369         // TODO: Figure out a better way to handle this?
2370         if (openFiles && numFiles > 0) {
2371             BOOL oneWindowInTab = topframe ? YES
2372                                            : (topframe->fr_layout == FR_LEAF);
2373             BOOL bufChanged = NO;
2374             BOOL bufHasFilename = NO;
2375             if (curbuf) {
2376                 bufChanged = curbufIsChanged();
2377                 bufHasFilename = curbuf->b_ffname != NULL;
2378             }
2380             // Temporarily disable flushing since the following code may
2381             // potentially cause multiple redraws.
2382             flushDisabled = YES;
2384             BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2385             if (WIN_TABS == layout && !onlyOneTab) {
2386                 // By going to the last tabpage we ensure that the new tabs
2387                 // will appear last (if this call is left out, the taborder
2388                 // becomes messy).
2389                 goto_tabpage(9999);
2390             }
2392             // Make sure we're in normal mode first.
2393             [self addInput:@"<C-\\><C-N>"];
2395             if (numFiles > 1) {
2396                 // With "split layout" we open a new tab before opening
2397                 // multiple files if the current tab has more than one window
2398                 // or if there is exactly one window but whose buffer has a
2399                 // filename.  (The :drop command ensures modified buffers get
2400                 // their own window.)
2401                 if ((WIN_HOR == layout || WIN_VER == layout) &&
2402                         (!oneWindowInTab || bufHasFilename))
2403                     [self addInput:@":tabnew<CR>"];
2405                 // The files are opened by constructing a ":drop ..." command
2406                 // and executing it.
2407                 NSMutableString *cmd = (WIN_TABS == layout)
2408                         ? [NSMutableString stringWithString:@":tab drop"]
2409                         : [NSMutableString stringWithString:@":drop"];
2411                 for (i = 0; i < numFiles; ++i) {
2412                     NSString *file = [filenames objectAtIndex:i];
2413                     file = [file stringByEscapingSpecialFilenameCharacters];
2414                     [cmd appendString:@" "];
2415                     [cmd appendString:file];
2416                 }
2418                 // Temporarily clear 'suffixes' so that the files are opened in
2419                 // the same order as they appear in the "filenames" array.
2420                 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2422                 [self addInput:cmd];
2424                 // Split the view into multiple windows if requested.
2425                 if (WIN_HOR == layout)
2426                     [self addInput:@"|sall"];
2427                 else if (WIN_VER == layout)
2428                     [self addInput:@"|vert sall"];
2430                 // Restore the old value of 'suffixes'.
2431                 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2432             } else {
2433                 // When opening one file we try to reuse the current window,
2434                 // but not if its buffer is modified or has a filename.
2435                 // However, the 'arglist' layout always opens the file in the
2436                 // current window.
2437                 NSString *file = [[filenames lastObject]
2438                         stringByEscapingSpecialFilenameCharacters];
2439                 NSString *cmd;
2440                 if (WIN_HOR == layout) {
2441                     if (!(bufHasFilename || bufChanged))
2442                         cmd = [NSString stringWithFormat:@":e %@", file];
2443                     else
2444                         cmd = [NSString stringWithFormat:@":sp %@", file];
2445                 } else if (WIN_VER == layout) {
2446                     if (!(bufHasFilename || bufChanged))
2447                         cmd = [NSString stringWithFormat:@":e %@", file];
2448                     else
2449                         cmd = [NSString stringWithFormat:@":vsp %@", file];
2450                 } else if (WIN_TABS == layout) {
2451                     if (oneWindowInTab && !(bufHasFilename || bufChanged))
2452                         cmd = [NSString stringWithFormat:@":e %@", file];
2453                     else
2454                         cmd = [NSString stringWithFormat:@":tabe %@", file];
2455                 } else {
2456                     // (The :drop command will split if there is a modified
2457                     // buffer.)
2458                     cmd = [NSString stringWithFormat:@":drop %@", file];
2459                 }
2461                 [self addInput:cmd];
2462                 [self addInput:@"<CR>"];
2463             }
2465             // Force screen redraw (does it have to be this complicated?).
2466             // (This code was taken from the end of gui_handle_drop().)
2467             update_screen(NOT_VALID);
2468             setcursor();
2469             out_flush();
2470             gui_update_cursor(FALSE, FALSE);
2471             maketitle();
2473             flushDisabled = NO;
2474         }
2475     }
2477     if ([args objectForKey:@"remoteID"]) {
2478         // NOTE: We have to delay processing any ODB related arguments since
2479         // the file(s) may not be opened until the input buffer is processed.
2480         [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2481                                withObject:args
2482                             waitUntilDone:NO];
2483     }
2485     NSString *lineString = [args objectForKey:@"cursorLine"];
2486     if (lineString && [lineString intValue] > 0) {
2487         NSString *columnString = [args objectForKey:@"cursorColumn"];
2488         if (!(columnString && [columnString intValue] > 0))
2489             columnString = @"1";
2491         NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2492                 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2493         [self addInput:cmd];
2494     }
2496     NSString *rangeString = [args objectForKey:@"selectionRange"];
2497     if (rangeString) {
2498         // Build a command line string that will select the given range of
2499         // lines.  If range.length == 0, then position the cursor on the given
2500         // line but do not select.
2501         NSRange range = NSRangeFromString(rangeString);
2502         NSString *cmd;
2503         if (range.length > 0) {
2504             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2505                     NSMaxRange(range), range.location];
2506         } else {
2507             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2508                     range.location];
2509         }
2511         [self addInput:cmd];
2512     }
2514     NSString *searchText = [args objectForKey:@"searchText"];
2515     if (searchText) {
2516         [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2517                 searchText]];
2518     }
2521 - (BOOL)checkForModifiedBuffers
2523     buf_T *buf;
2524     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2525         if (bufIsChanged(buf)) {
2526             return YES;
2527         }
2528     }
2530     return NO;
2533 - (void)addInput:(NSString *)input
2535     // NOTE: This code is essentially identical to server_to_input_buf(),
2536     // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2537     char_u *string = [input vimStringSave];
2538     if (!string) return;
2540     /* Set 'cpoptions' the way we want it.
2541      *    B set - backslashes are *not* treated specially
2542      *    k set - keycodes are *not* reverse-engineered
2543      *    < unset - <Key> sequences *are* interpreted
2544      *  The last but one parameter of replace_termcodes() is TRUE so that the
2545      *  <lt> sequence is recognised - needed for a real backslash.
2546      */
2547     char_u *ptr = NULL;
2548     char_u *cpo_save = p_cpo;
2549     p_cpo = (char_u *)"Bk";
2550     char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2551     p_cpo = cpo_save;
2553     if (*ptr != NUL)    /* trailing CTRL-V results in nothing */
2554     {
2555         /*
2556          * Add the string to the input stream.
2557          * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2558          *
2559          * First clear typed characters from the typeahead buffer, there could
2560          * be half a mapping there.  Then append to the existing string, so
2561          * that multiple commands from a client are concatenated.
2562          */
2563         if (typebuf.tb_maplen < typebuf.tb_len)
2564             del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2565         (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2567         /* Let input_available() know we inserted text in the typeahead
2568          * buffer. */
2569         typebuf_was_filled = TRUE;
2570     }
2571     vim_free(ptr);
2572     vim_free(string);
2575 - (BOOL)unusedEditor
2577     BOOL oneWindowInTab = topframe ? YES
2578                                    : (topframe->fr_layout == FR_LEAF);
2579     BOOL bufChanged = NO;
2580     BOOL bufHasFilename = NO;
2581     if (curbuf) {
2582         bufChanged = curbufIsChanged();
2583         bufHasFilename = curbuf->b_ffname != NULL;
2584     }
2586     BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2588     return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2591 - (void)redrawScreen
2593     // Force screen redraw (does it have to be this complicated?).
2594     redraw_all_later(CLEAR);
2595     update_screen(NOT_VALID);
2596     setcursor();
2597     out_flush();
2598     gui_update_cursor(FALSE, FALSE);
2600     // HACK! The cursor is not put back at the command line by the above
2601     // "redraw commands".  The following test seems to do the trick though.
2602     if (State & CMDLINE)
2603         redrawcmdline();
2606 - (void)handleFindReplace:(NSDictionary *)args
2608     if (!args) return;
2610     NSString *findString = [args objectForKey:@"find"];
2611     if (!findString) return;
2613     char_u *find = [findString vimStringSave];
2614     char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2615     int flags = [[args objectForKey:@"flags"] intValue];
2617     // NOTE: The flag 0x100 is used to indicate a backward search.
2618     gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2620     vim_free(find);
2621     vim_free(replace);
2624 @end // MMBackend (Private)
2629 @implementation MMBackend (ClientServer)
2631 - (NSString *)connectionNameFromServerName:(NSString *)name
2633     NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2635     return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2636         lowercaseString];
2639 - (NSConnection *)connectionForServerName:(NSString *)name
2641     // TODO: Try 'name%d' if 'name' fails.
2642     NSString *connName = [self connectionNameFromServerName:name];
2643     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2645     if (!svrConn) {
2646         svrConn = [NSConnection connectionWithRegisteredName:connName
2647                                                            host:nil];
2648         // Try alternate server...
2649         if (!svrConn && alternateServerName) {
2650             //NSLog(@"  trying to connect to alternate server: %@",
2651             //        alternateServerName);
2652             connName = [self connectionNameFromServerName:alternateServerName];
2653             svrConn = [NSConnection connectionWithRegisteredName:connName
2654                                                             host:nil];
2655         }
2657         // Try looking for alternate servers...
2658         if (!svrConn) {
2659             //NSLog(@"  looking for alternate servers...");
2660             NSString *alt = [self alternateServerNameForName:name];
2661             if (alt != alternateServerName) {
2662                 //NSLog(@"  found alternate server: %@", string);
2663                 [alternateServerName release];
2664                 alternateServerName = [alt copy];
2665             }
2666         }
2668         // Try alternate server again...
2669         if (!svrConn && alternateServerName) {
2670             //NSLog(@"  trying to connect to alternate server: %@",
2671             //        alternateServerName);
2672             connName = [self connectionNameFromServerName:alternateServerName];
2673             svrConn = [NSConnection connectionWithRegisteredName:connName
2674                                                             host:nil];
2675         }
2677         if (svrConn) {
2678             [connectionNameDict setObject:svrConn forKey:connName];
2680             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2681             [[NSNotificationCenter defaultCenter] addObserver:self
2682                     selector:@selector(serverConnectionDidDie:)
2683                         name:NSConnectionDidDieNotification object:svrConn];
2684         }
2685     }
2687     return svrConn;
2690 - (NSConnection *)connectionForServerPort:(int)port
2692     NSConnection *conn;
2693     NSEnumerator *e = [connectionNameDict objectEnumerator];
2695     while ((conn = [e nextObject])) {
2696         // HACK! Assume connection uses mach ports.
2697         if (port == [(NSMachPort*)[conn sendPort] machPort])
2698             return conn;
2699     }
2701     return nil;
2704 - (void)serverConnectionDidDie:(NSNotification *)notification
2706     //NSLog(@"%s%@", _cmd, notification);
2708     NSConnection *svrConn = [notification object];
2710     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2711     [[NSNotificationCenter defaultCenter]
2712             removeObserver:self
2713                       name:NSConnectionDidDieNotification
2714                     object:svrConn];
2716     [connectionNameDict removeObjectsForKeys:
2717         [connectionNameDict allKeysForObject:svrConn]];
2719     // HACK! Assume connection uses mach ports.
2720     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2721     NSNumber *key = [NSNumber numberWithInt:port];
2723     [clientProxyDict removeObjectForKey:key];
2724     [serverReplyDict removeObjectForKey:key];
2727 - (void)addClient:(NSDistantObject *)client
2729     NSConnection *conn = [client connectionForProxy];
2730     // HACK! Assume connection uses mach ports.
2731     int port = [(NSMachPort*)[conn sendPort] machPort];
2732     NSNumber *key = [NSNumber numberWithInt:port];
2734     if (![clientProxyDict objectForKey:key]) {
2735         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2736         [clientProxyDict setObject:client forKey:key];
2737     }
2739     // NOTE: 'clientWindow' is a global variable which is used by <client>
2740     clientWindow = port;
2743 - (NSString *)alternateServerNameForName:(NSString *)name
2745     if (!(name && [name length] > 0))
2746         return nil;
2748     // Only look for alternates if 'name' doesn't end in a digit.
2749     unichar lastChar = [name characterAtIndex:[name length]-1];
2750     if (lastChar >= '0' && lastChar <= '9')
2751         return nil;
2753     // Look for alternates among all current servers.
2754     NSArray *list = [self serverList];
2755     if (!(list && [list count] > 0))
2756         return nil;
2758     // Filter out servers starting with 'name' and ending with a number. The
2759     // (?i) pattern ensures that the match is case insensitive.
2760     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2761     NSPredicate *pred = [NSPredicate predicateWithFormat:
2762             @"SELF MATCHES %@", pat];
2763     list = [list filteredArrayUsingPredicate:pred];
2764     if ([list count] > 0) {
2765         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2766         return [list objectAtIndex:0];
2767     }
2769     return nil;
2772 @end // MMBackend (ClientServer)
2777 @implementation NSString (MMServerNameCompare)
2778 - (NSComparisonResult)serverNameCompare:(NSString *)string
2780     return [self compare:string
2781                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2783 @end
2788 static int eventModifierFlagsToVimModMask(int modifierFlags)
2790     int modMask = 0;
2792     if (modifierFlags & NSShiftKeyMask)
2793         modMask |= MOD_MASK_SHIFT;
2794     if (modifierFlags & NSControlKeyMask)
2795         modMask |= MOD_MASK_CTRL;
2796     if (modifierFlags & NSAlternateKeyMask)
2797         modMask |= MOD_MASK_ALT;
2798     if (modifierFlags & NSCommandKeyMask)
2799         modMask |= MOD_MASK_CMD;
2801     return modMask;
2804 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2806     int modMask = 0;
2808     if (modifierFlags & NSShiftKeyMask)
2809         modMask |= MOUSE_SHIFT;
2810     if (modifierFlags & NSControlKeyMask)
2811         modMask |= MOUSE_CTRL;
2812     if (modifierFlags & NSAlternateKeyMask)
2813         modMask |= MOUSE_ALT;
2815     return modMask;
2818 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2820     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2822     return (buttonNumber >= 0 && buttonNumber < 3)
2823             ? mouseButton[buttonNumber] : -1;
2828 // This function is modeled after the VimToPython function found in if_python.c
2829 // NB This does a deep copy by value, it does not lookup references like the
2830 // VimToPython function does.  This is because I didn't want to deal with the
2831 // retain cycles that this would create, and we can cover 99% of the use cases
2832 // by ignoring it.  If we ever switch to using GC in MacVim then this
2833 // functionality can be implemented easily.
2834 static id vimToCocoa(typval_T * tv, int depth)
2836     id result = nil;
2837     id newObj = nil;
2840     // Avoid infinite recursion
2841     if (depth > 100) {
2842         return nil;
2843     }
2845     if (tv->v_type == VAR_STRING) {
2846         char_u * val = tv->vval.v_string;
2847         // val can be NULL if the string is empty
2848         if (!val) {
2849             result = [NSString string];
2850         } else {
2851 #ifdef FEAT_MBYTE
2852             val = CONVERT_TO_UTF8(val);
2853 #endif
2854             result = [NSString stringWithUTF8String:(char*)val];
2855 #ifdef FEAT_MBYTE
2856             CONVERT_TO_UTF8_FREE(val);
2857 #endif
2858         }
2859     } else if (tv->v_type == VAR_NUMBER) {
2860         // looks like sizeof(varnumber_T) is always <= sizeof(long)
2861         result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2862     } else if (tv->v_type == VAR_LIST) {
2863         list_T * list = tv->vval.v_list;
2864         listitem_T * curr;
2866         NSMutableArray * arr = result = [NSMutableArray array];
2868         if (list != NULL) {
2869             for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2870                 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2871                 [arr addObject:newObj];
2872             }
2873         }
2874     } else if (tv->v_type == VAR_DICT) {
2875         NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2877         if (tv->vval.v_dict != NULL) {
2878             hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2879             int todo = ht->ht_used;
2880             hashitem_T * hi;
2881             dictitem_T * di;
2883             for (hi = ht->ht_array; todo > 0; ++hi) {
2884                 if (!HASHITEM_EMPTY(hi)) {
2885                     --todo;
2887                     di = dict_lookup(hi);
2888                     newObj = vimToCocoa(&di->di_tv, depth + 1);
2890                     char_u * keyval = hi->hi_key;
2891 #ifdef FEAT_MBYTE
2892                     keyval = CONVERT_TO_UTF8(keyval);
2893 #endif
2894                     NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2895 #ifdef FEAT_MBYTE
2896                     CONVERT_TO_UTF8_FREE(keyval);
2897 #endif
2898                     [dict setObject:newObj forKey:key];
2899                 }
2900             }
2901         }
2902     } else { // only func refs should fall into this category?
2903         result = nil;
2904     }
2906     return result;
2910 // This function is modeled after eval_client_expr_to_string found in main.c
2911 // Returns nil if there was an error evaluating the expression, and writes a
2912 // message to errorStr.
2913 // TODO Get the error that occurred while evaluating the expression in vim
2914 // somehow.
2915 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2918     char_u *s = (char_u*)[expr UTF8String];
2920 #ifdef FEAT_MBYTE
2921     s = CONVERT_FROM_UTF8(s);
2922 #endif
2924     int save_dbl = debug_break_level;
2925     int save_ro = redir_off;
2927     debug_break_level = -1;
2928     redir_off = 0;
2929     ++emsg_skip;
2931     typval_T * tvres = eval_expr(s, NULL);
2933     debug_break_level = save_dbl;
2934     redir_off = save_ro;
2935     --emsg_skip;
2937     setcursor();
2938     out_flush();
2940 #ifdef FEAT_MBYTE
2941     CONVERT_FROM_UTF8_FREE(s);
2942 #endif
2944 #ifdef FEAT_GUI
2945     if (gui.in_use)
2946         gui_update_cursor(FALSE, FALSE);
2947 #endif
2949     if (tvres == NULL) {
2950         free_tv(tvres);
2951         *errstr = @"Expression evaluation failed.";
2952     }
2954     id res = vimToCocoa(tvres, 1);
2956     free_tv(tvres);
2958     if (res == nil) {
2959         *errstr = @"Conversion to cocoa values failed.";
2960     }
2962     return res;
2967 @implementation NSString (VimStrings)
2969 + (id)stringWithVimString:(char_u *)s
2971     // This method ensures a non-nil string is returned.  If 's' cannot be
2972     // converted to a utf-8 string it is assumed to be latin-1.  If conversion
2973     // still fails an empty NSString is returned.
2974     NSString *string = nil;
2975     if (s) {
2976 #ifdef FEAT_MBYTE
2977         s = CONVERT_TO_UTF8(s);
2978 #endif
2979         string = [NSString stringWithUTF8String:(char*)s];
2980         if (!string) {
2981             // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2982             // latin-1?
2983             string = [NSString stringWithCString:(char*)s
2984                                         encoding:NSISOLatin1StringEncoding];
2985         }
2986 #ifdef FEAT_MBYTE
2987         CONVERT_TO_UTF8_FREE(s);
2988 #endif
2989     }
2991     return string != nil ? string : [NSString string];
2994 - (char_u *)vimStringSave
2996     char_u *s = (char_u*)[self UTF8String], *ret = NULL;
2998 #ifdef FEAT_MBYTE
2999     s = CONVERT_FROM_UTF8(s);
3000 #endif
3001     ret = vim_strsave(s);
3002 #ifdef FEAT_MBYTE
3003     CONVERT_FROM_UTF8_FREE(s);
3004 #endif
3006     return ret;
3009 @end // NSString (VimStrings)