Cleanup
[MacVim.git] / src / MacVim / MMBackend.m
blobc32eca65ceea8025b75724aaad5d66251846507b
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             [appProxy processInput:outputQueue forIdentifier:identifier];
506         }
507         @catch (NSException *e) {
508             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
509             NSLog(@"outputQueue(len:%d)=%@", [outputQueue count]/2,
510                     outputQueue);
511             if (![connection isValid]) {
512                 NSLog(@"WARNING! Connection is invalid, exit now!");
513                 NSLog(@"waitForAck=%d got_int=%d", waitForAck, got_int);
514                 mch_exit(-1);
515             }
516         }
518         [outputQueue removeAllObjects];
519     }
522 - (BOOL)waitForInput:(int)milliseconds
524     // Return NO if we timed out waiting for input, otherwise return YES.
525     BOOL inputReceived = NO;
527     // Only start the run loop if the input queue is empty, otherwise process
528     // the input first so that the input on queue isn't delayed.
529     if ([inputQueue count]) {
530         inputReceived = YES;
531     } else {
532         // Wait for the specified amount of time, unless 'milliseconds' is
533         // negative in which case we wait "forever" (1e6 seconds translates to
534         // approximately 11 days).
535         CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
537         while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
538                 == kCFRunLoopRunHandledSource) {
539             // In order to ensure that all input on the run-loop has been
540             // processed we set the timeout to 0 and keep processing until the
541             // run-loop times out.
542             dt = 0.0;
543             inputReceived = YES;
544         }
545     }
547     // The above calls may have placed messages on the input queue so process
548     // it now.  This call may enter a blocking loop.
549     if ([inputQueue count] > 0)
550         [self processInputQueue];
552     return inputReceived;
555 - (void)exit
557     // NOTE: This is called if mch_exit() is called.  Since we assume here that
558     // the process has started properly, be sure to use exit() instead of
559     // mch_exit() to prematurely terminate a process (or set 'isTerminating'
560     // first).
562     // Make sure no connectionDidDie: notification is received now that we are
563     // already exiting.
564     [[NSNotificationCenter defaultCenter] removeObserver:self];
566     // The 'isTerminating' flag indicates that the frontend is also exiting so
567     // there is no need to flush any more output since the frontend won't look
568     // at it anyway.
569     if (!isTerminating && [connection isValid]) {
570         @try {
571             // Flush the entire queue in case a VimLeave autocommand added
572             // something to the queue.
573             [self queueMessage:CloseWindowMsgID data:nil];
574             [appProxy processInput:outputQueue forIdentifier:identifier];
575         }
576         @catch (NSException *e) {
577             NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
578         }
580         // NOTE: If Cmd-w was pressed to close the window the menu is briefly
581         // highlighted and during this pause the frontend won't receive any DO
582         // messages.  If the Vim process exits before this highlighting has
583         // finished Cocoa will emit the following error message:
584         //   *** -[NSMachPort handlePortMessage:]: dropping incoming DO message
585         //   because the connection or ports are invalid
586         // To avoid this warning we delay here.  If the warning still appears
587         // this delay may need to be increased.
588         usleep(150000);
589     }
591 #ifdef MAC_CLIENTSERVER
592     // The default connection is used for the client/server code.
593     [[NSConnection defaultConnection] setRootObject:nil];
594     [[NSConnection defaultConnection] invalidate];
595 #endif
598 - (void)selectTab:(int)index
600     //NSLog(@"%s%d", _cmd, index);
602     index -= 1;
603     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
604     [self queueMessage:SelectTabMsgID data:data];
607 - (void)updateTabBar
609     //NSLog(@"%s", _cmd);
611     NSMutableData *data = [NSMutableData data];
613     int idx = tabpage_index(curtab) - 1;
614     [data appendBytes:&idx length:sizeof(int)];
616     tabpage_T *tp;
617     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
618         // Count the number of windows in the tabpage.
619         //win_T *wp = tp->tp_firstwin;
620         //int wincount;
621         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
622         //[data appendBytes:&wincount length:sizeof(int)];
624         int tabProp = MMTabInfoCount;
625         [data appendBytes:&tabProp length:sizeof(int)];
626         for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
627             // This function puts the label of the tab in the global 'NameBuff'.
628             get_tabline_label(tp, (tabProp == MMTabToolTip));
629             NSString *s = [NSString stringWithVimString:NameBuff];
630             int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
631             if (len < 0)
632                 len = 0;
634             [data appendBytes:&len length:sizeof(int)];
635             if (len > 0)
636                 [data appendBytes:[s UTF8String] length:len];
637         }
638     }
640     [self queueMessage:UpdateTabBarMsgID data:data];
643 - (BOOL)tabBarVisible
645     return tabBarVisible;
648 - (void)showTabBar:(BOOL)enable
650     tabBarVisible = enable;
652     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
653     [self queueMessage:msgid data:nil];
656 - (void)setRows:(int)rows columns:(int)cols
658     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
660     int dim[] = { rows, cols };
661     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
663     [self queueMessage:SetTextDimensionsMsgID data:data];
666 - (void)setWindowTitle:(char *)title
668     NSMutableData *data = [NSMutableData data];
669     int len = strlen(title);
670     if (len <= 0) return;
672     [data appendBytes:&len length:sizeof(int)];
673     [data appendBytes:title length:len];
675     [self queueMessage:SetWindowTitleMsgID data:data];
678 - (void)setDocumentFilename:(char *)filename
680     NSMutableData *data = [NSMutableData data];
681     int len = filename ? strlen(filename) : 0;
683     [data appendBytes:&len length:sizeof(int)];
684     if (len > 0)
685         [data appendBytes:filename length:len];
687     [self queueMessage:SetDocumentFilenameMsgID data:data];
690 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
692     char_u *s = NULL;
694     [self queueMessage:BrowseForFileMsgID properties:attr];
695     [self flushQueue:YES];
697     @try {
698         [self waitForDialogReturn];
700         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
701             s = [dialogReturn vimStringSave];
703         [dialogReturn release];  dialogReturn = nil;
704     }
705     @catch (NSException *e) {
706         NSLog(@"[%s] Exception caught: \"%@\"", _cmd, e);
707     }
709     return (char *)s;
712 - (oneway void)setDialogReturn:(in bycopy id)obj
714     // NOTE: This is called by
715     //   - [MMVimController panelDidEnd:::], and
716     //   - [MMVimController alertDidEnd:::],
717     // to indicate that a save/open panel or alert has finished.
719     // We want to distinguish between "no dialog return yet" and "dialog
720     // returned nothing".  The former can be tested with dialogReturn == nil,
721     // the latter with dialogReturn == [NSNull null].
722     if (!obj) obj = [NSNull null];
724     if (obj != dialogReturn) {
725         [dialogReturn release];
726         dialogReturn = [obj retain];
727     }
730 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
732     int retval = 0;
734     [self queueMessage:ShowDialogMsgID properties:attr];
735     [self flushQueue:YES];
737     @try {
738         [self waitForDialogReturn];
740         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
741                 && [dialogReturn count]) {
742             retval = [[dialogReturn objectAtIndex:0] intValue];
743             if (txtfield && [dialogReturn count] > 1) {
744                 NSString *retString = [dialogReturn objectAtIndex:1];
745                 char_u *ret = (char_u*)[retString UTF8String];
746 #ifdef FEAT_MBYTE
747                 ret = CONVERT_FROM_UTF8(ret);
748 #endif
749                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
750 #ifdef FEAT_MBYTE
751                 CONVERT_FROM_UTF8_FREE(ret);
752 #endif
753             }
754         }
756         [dialogReturn release]; dialogReturn = nil;
757     }
758     @catch (NSException *e) {
759         NSLog(@"[%s] Exception caught: \"%@\"", _cmd, e);
760     }
762     return retval;
765 - (void)showToolbar:(int)enable flags:(int)flags
767     NSMutableData *data = [NSMutableData data];
769     [data appendBytes:&enable length:sizeof(int)];
770     [data appendBytes:&flags length:sizeof(int)];
772     [self queueMessage:ShowToolbarMsgID data:data];
775 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
777     NSMutableData *data = [NSMutableData data];
779     [data appendBytes:&ident length:sizeof(long)];
780     [data appendBytes:&type length:sizeof(int)];
782     [self queueMessage:CreateScrollbarMsgID data:data];
785 - (void)destroyScrollbarWithIdentifier:(long)ident
787     NSMutableData *data = [NSMutableData data];
788     [data appendBytes:&ident length:sizeof(long)];
790     [self queueMessage:DestroyScrollbarMsgID data:data];
793 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
795     NSMutableData *data = [NSMutableData data];
797     [data appendBytes:&ident length:sizeof(long)];
798     [data appendBytes:&visible length:sizeof(int)];
800     [self queueMessage:ShowScrollbarMsgID data:data];
803 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
805     NSMutableData *data = [NSMutableData data];
807     [data appendBytes:&ident length:sizeof(long)];
808     [data appendBytes:&pos length:sizeof(int)];
809     [data appendBytes:&len length:sizeof(int)];
811     [self queueMessage:SetScrollbarPositionMsgID data:data];
814 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
815                     identifier:(long)ident
817     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
818     float prop = (float)size/(max+1);
819     if (fval < 0) fval = 0;
820     else if (fval > 1.0f) fval = 1.0f;
821     if (prop < 0) prop = 0;
822     else if (prop > 1.0f) prop = 1.0f;
824     NSMutableData *data = [NSMutableData data];
826     [data appendBytes:&ident length:sizeof(long)];
827     [data appendBytes:&fval length:sizeof(float)];
828     [data appendBytes:&prop length:sizeof(float)];
830     [self queueMessage:SetScrollbarThumbMsgID data:data];
833 - (void)setFont:(GuiFont)font wide:(BOOL)wide
835     NSString *fontName = (NSString *)font;
836     float size = 0;
837     NSArray *components = [fontName componentsSeparatedByString:@":"];
838     if ([components count] == 2) {
839         size = [[components lastObject] floatValue];
840         fontName = [components objectAtIndex:0];
841     }
843     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
844     NSMutableData *data = [NSMutableData data];
845     [data appendBytes:&size length:sizeof(float)];
846     [data appendBytes:&len length:sizeof(int)];
848     if (len > 0)
849         [data appendBytes:[fontName UTF8String] length:len];
850     else if (!wide)
851         return;     // Only the wide font can be set to nothing
853     [self queueMessage:(wide ? SetWideFontMsgID : SetFontMsgID) data:data];
856 - (void)executeActionWithName:(NSString *)name
858     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
860     if (len > 0) {
861         NSMutableData *data = [NSMutableData data];
863         [data appendBytes:&len length:sizeof(int)];
864         [data appendBytes:[name UTF8String] length:len];
866         [self queueMessage:ExecuteActionMsgID data:data];
867     }
870 - (void)setMouseShape:(int)shape
872     NSMutableData *data = [NSMutableData data];
873     [data appendBytes:&shape length:sizeof(int)];
874     [self queueMessage:SetMouseShapeMsgID data:data];
877 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
879     // Vim specifies times in milliseconds, whereas Cocoa wants them in
880     // seconds.
881     blinkWaitInterval = .001f*wait;
882     blinkOnInterval = .001f*on;
883     blinkOffInterval = .001f*off;
886 - (void)startBlink
888     if (blinkTimer) {
889         [blinkTimer invalidate];
890         [blinkTimer release];
891         blinkTimer = nil;
892     }
894     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
895             && gui.in_focus) {
896         blinkState = MMBlinkStateOn;
897         blinkTimer =
898             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
899                                               target:self
900                                             selector:@selector(blinkTimerFired:)
901                                             userInfo:nil repeats:NO] retain];
902         gui_update_cursor(TRUE, FALSE);
903         [self flushQueue:YES];
904     }
907 - (void)stopBlink
909     if (MMBlinkStateOff == blinkState) {
910         gui_update_cursor(TRUE, FALSE);
911         [self flushQueue:YES];
912     }
914     blinkState = MMBlinkStateNone;
917 - (void)adjustLinespace:(int)linespace
919     NSMutableData *data = [NSMutableData data];
920     [data appendBytes:&linespace length:sizeof(int)];
921     [self queueMessage:AdjustLinespaceMsgID data:data];
924 - (void)activate
926     [self queueMessage:ActivateMsgID data:nil];
929 - (void)setPreEditRow:(int)row column:(int)col
931     NSMutableData *data = [NSMutableData data];
932     [data appendBytes:&row length:sizeof(int)];
933     [data appendBytes:&col length:sizeof(int)];
934     [self queueMessage:SetPreEditPositionMsgID data:data];
937 - (int)lookupColorWithKey:(NSString *)key
939     if (!(key && [key length] > 0))
940         return INVALCOLOR;
942     NSString *stripKey = [[[[key lowercaseString]
943         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
944             componentsSeparatedByString:@" "]
945                componentsJoinedByString:@""];
947     if (stripKey && [stripKey length] > 0) {
948         // First of all try to lookup key in the color dictionary; note that
949         // all keys in this dictionary are lowercase with no whitespace.
950         id obj = [colorDict objectForKey:stripKey];
951         if (obj) return [obj intValue];
953         // The key was not in the dictionary; is it perhaps of the form
954         // #rrggbb?
955         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
956             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
957             [scanner setScanLocation:1];
958             unsigned hex = 0;
959             if ([scanner scanHexInt:&hex]) {
960                 return (int)hex;
961             }
962         }
964         // As a last resort, check if it is one of the system defined colors.
965         // The keys in this dictionary are also lowercase with no whitespace.
966         obj = [sysColorDict objectForKey:stripKey];
967         if (obj) {
968             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
969             if (col) {
970                 float r, g, b, a;
971                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
972                 [col getRed:&r green:&g blue:&b alpha:&a];
973                 return (((int)(r*255+.5f) & 0xff) << 16)
974                      + (((int)(g*255+.5f) & 0xff) << 8)
975                      +  ((int)(b*255+.5f) & 0xff);
976             }
977         }
978     }
980     //NSLog(@"WARNING: No color with key %@ found.", stripKey);
981     return INVALCOLOR;
984 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
986     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
987     id obj;
989     while ((obj = [e nextObject])) {
990         if ([value isEqual:obj])
991             return YES;
992     }
994     return NO;
997 - (void)enterFullscreen:(int)fuoptions background:(int)bg
999     NSMutableData *data = [NSMutableData data];
1000     [data appendBytes:&fuoptions length:sizeof(int)];
1001     bg = MM_COLOR(bg);
1002     [data appendBytes:&bg length:sizeof(int)];
1003     [self queueMessage:EnterFullscreenMsgID data:data];
1006 - (void)leaveFullscreen
1008     [self queueMessage:LeaveFullscreenMsgID data:nil];
1011 - (void)setFullscreenBackgroundColor:(int)color
1013     NSMutableData *data = [NSMutableData data];
1014     color = MM_COLOR(color);
1015     [data appendBytes:&color length:sizeof(int)];
1017     [self queueMessage:SetFullscreenColorMsgID data:data];
1020 - (void)setAntialias:(BOOL)antialias
1022     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1024     [self queueMessage:msgid data:nil];
1027 - (void)updateModifiedFlag
1029     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1030     // vice versa.
1031     int msgid = [self checkForModifiedBuffers]
1032             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1034     [self queueMessage:msgid data:nil];
1037 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1039     // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1040     // queue is processed since that only happens in waitForInput: (and Vim
1041     // regularly checks for Ctrl-C in between waiting for input).
1042     // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1043     // which waits on the run loop will fail to detect this message (e.g. in
1044     // waitForConnectionAcknowledgement).
1046     if (InsertTextMsgID == msgid && data != nil) {
1047         const void *bytes = [data bytes];
1048         bytes += sizeof(int);
1049         int len = *((int*)bytes);  bytes += sizeof(int);
1050         if (1 == len) {
1051             char_u *str = (char_u*)bytes;
1052             if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1053                     (str[0] == intr_char && intr_char != Ctrl_C)) {
1054                 got_int = TRUE;
1055                 [inputQueue removeAllObjects];
1056                 return;
1057             }
1058         }
1059     } else if (TerminateNowMsgID == msgid) {
1060         // Terminate immediately (the frontend is about to quit or this process
1061         // was aborted).  Don't preserve modified files since the user would
1062         // already have been presented with a dialog warning if there were any
1063         // modified files when we get here.
1064         isTerminating = YES;
1065         getout(0);
1066         return;
1067     }
1069     // Remove all previous instances of this message from the input queue, else
1070     // the input queue may fill up as a result of Vim not being able to keep up
1071     // with the speed at which new messages are received.
1072     // Keyboard input is never dropped, unless the input represents and
1073     // auto-repeated key.
1075     BOOL isKeyRepeat = NO;
1076     BOOL isKeyboardInput = NO;
1078     if (data && (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1079             CmdKeyMsgID == msgid)) {
1080         isKeyboardInput = YES;
1082         // The lowest bit of the first int is set if this key is a repeat.
1083         int flags = *((int*)[data bytes]);
1084         if (flags & 1)
1085             isKeyRepeat = YES;
1086     }
1088     // Keyboard input is not removed from the queue; repeats are ignored if
1089     // there already is keyboard input on the input queue.
1090     if (isKeyRepeat || !isKeyboardInput) {
1091         int i, count = [inputQueue count];
1092         for (i = 1; i < count; i+=2) {
1093             if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1094                 if (isKeyRepeat)
1095                     return;
1097                 [inputQueue removeObjectAtIndex:i];
1098                 [inputQueue removeObjectAtIndex:i-1];
1099                 break;
1100             }
1101         }
1102     }
1104     [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1105     [inputQueue addObject:(data ? (id)data : [NSNull null])];
1108 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1110     // This is just a convenience method that allows the frontend to delay
1111     // sending messages.
1112     int i, count = [messages count];
1113     for (i = 1; i < count; i+=2)
1114         [self processInput:[[messages objectAtIndex:i-1] intValue]
1115                       data:[messages objectAtIndex:i]];
1118 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1119                   errorString:(out bycopy NSString **)errstr
1121     return evalExprCocoa(expr, errstr);
1125 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1127     NSString *eval = nil;
1128     char_u *s = (char_u*)[expr UTF8String];
1130 #ifdef FEAT_MBYTE
1131     s = CONVERT_FROM_UTF8(s);
1132 #endif
1134     char_u *res = eval_client_expr_to_string(s);
1136 #ifdef FEAT_MBYTE
1137     CONVERT_FROM_UTF8_FREE(s);
1138 #endif
1140     if (res != NULL) {
1141         s = res;
1142 #ifdef FEAT_MBYTE
1143         s = CONVERT_TO_UTF8(s);
1144 #endif
1145         eval = [NSString stringWithUTF8String:(char*)s];
1146 #ifdef FEAT_MBYTE
1147         CONVERT_TO_UTF8_FREE(s);
1148 #endif
1149         vim_free(res);
1150     }
1152     return eval;
1155 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1157     // TODO: This method should share code with clip_mch_request_selection().
1159     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1160         // If there is no pasteboard, return YES to indicate that there is text
1161         // to copy.
1162         if (!pboard)
1163             return YES;
1165         clip_copy_selection();
1167         // Get the text to put on the pasteboard.
1168         long_u llen = 0; char_u *str = 0;
1169         int type = clip_convert_selection(&str, &llen, &clip_star);
1170         if (type < 0)
1171             return NO;
1172         
1173         // TODO: Avoid overflow.
1174         int len = (int)llen;
1175 #ifdef FEAT_MBYTE
1176         if (output_conv.vc_type != CONV_NONE) {
1177             char_u *conv_str = string_convert(&output_conv, str, &len);
1178             if (conv_str) {
1179                 vim_free(str);
1180                 str = conv_str;
1181             }
1182         }
1183 #endif
1185         NSString *string = [[NSString alloc]
1186             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1188         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1189         [pboard declareTypes:types owner:nil];
1190         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1191     
1192         [string release];
1193         vim_free(str);
1195         return ok;
1196     }
1198     return NO;
1201 - (oneway void)addReply:(in bycopy NSString *)reply
1202                  server:(in byref id <MMVimServerProtocol>)server
1204     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1206     // Replies might come at any time and in any order so we keep them in an
1207     // array inside a dictionary with the send port used as key.
1209     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1210     // HACK! Assume connection uses mach ports.
1211     int port = [(NSMachPort*)[conn sendPort] machPort];
1212     NSNumber *key = [NSNumber numberWithInt:port];
1214     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1215     if (!replies) {
1216         replies = [NSMutableArray array];
1217         [serverReplyDict setObject:replies forKey:key];
1218     }
1220     [replies addObject:reply];
1223 - (void)addInput:(in bycopy NSString *)input
1224           client:(in byref id <MMVimClientProtocol>)client
1226     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1228     // NOTE: We don't call addInput: here because it differs from
1229     // server_to_input_buf() in that it always sets the 'silent' flag and we
1230     // don't want the MacVim client/server code to behave differently from
1231     // other platforms.
1232     char_u *s = [input vimStringSave];
1233     server_to_input_buf(s);
1234     vim_free(s);
1236     [self addClient:(id)client];
1239 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1240                  client:(in byref id <MMVimClientProtocol>)client
1242     [self addClient:(id)client];
1243     return [self evaluateExpression:expr];
1246 - (void)registerServerWithName:(NSString *)name
1248     NSString *svrName = name;
1249     NSConnection *svrConn = [NSConnection defaultConnection];
1250     unsigned i;
1252     for (i = 0; i < MMServerMax; ++i) {
1253         NSString *connName = [self connectionNameFromServerName:svrName];
1255         if ([svrConn registerName:connName]) {
1256             //NSLog(@"Registered server with name: %@", svrName);
1258             // TODO: Set request/reply time-outs to something else?
1259             //
1260             // Don't wait for requests (time-out means that the message is
1261             // dropped).
1262             [svrConn setRequestTimeout:0];
1263             //[svrConn setReplyTimeout:MMReplyTimeout];
1264             [svrConn setRootObject:self];
1266             // NOTE: 'serverName' is a global variable
1267             serverName = [svrName vimStringSave];
1268 #ifdef FEAT_EVAL
1269             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1270 #endif
1271 #ifdef FEAT_TITLE
1272             need_maketitle = TRUE;
1273 #endif
1274             [self queueMessage:SetServerNameMsgID data:
1275                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1276             break;
1277         }
1279         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1280     }
1283 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1284                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1285               silent:(BOOL)silent
1287     // NOTE: If 'name' equals 'serverName' then the request is local (client
1288     // and server are the same).  This case is not handled separately, so a
1289     // connection will be set up anyway (this simplifies the code).
1291     NSConnection *conn = [self connectionForServerName:name];
1292     if (!conn) {
1293         if (!silent) {
1294             char_u *s = (char_u*)[name UTF8String];
1295 #ifdef FEAT_MBYTE
1296             s = CONVERT_FROM_UTF8(s);
1297 #endif
1298             EMSG2(_(e_noserver), s);
1299 #ifdef FEAT_MBYTE
1300             CONVERT_FROM_UTF8_FREE(s);
1301 #endif
1302         }
1303         return NO;
1304     }
1306     if (port) {
1307         // HACK! Assume connection uses mach ports.
1308         *port = [(NSMachPort*)[conn sendPort] machPort];
1309     }
1311     id proxy = [conn rootProxy];
1312     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1314     @try {
1315         if (expr) {
1316             NSString *eval = [proxy evaluateExpression:string client:self];
1317             if (reply) {
1318                 if (eval) {
1319                     *reply = [eval vimStringSave];
1320                 } else {
1321                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1322                 }
1323             }
1325             if (!eval)
1326                 return NO;
1327         } else {
1328             [proxy addInput:string client:self];
1329         }
1330     }
1331     @catch (NSException *e) {
1332         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1333         return NO;
1334     }
1336     return YES;
1339 - (NSArray *)serverList
1341     NSArray *list = nil;
1343     if ([self connection]) {
1344         id proxy = [connection rootProxy];
1345         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1347         @try {
1348             list = [proxy serverList];
1349         }
1350         @catch (NSException *e) {
1351             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1352         }
1353     } else {
1354         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1355     }
1357     return list;
1360 - (NSString *)peekForReplyOnPort:(int)port
1362     //NSLog(@"%s%d", _cmd, port);
1364     NSNumber *key = [NSNumber numberWithInt:port];
1365     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1366     if (replies && [replies count]) {
1367         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1368         //        [replies objectAtIndex:0]);
1369         return [replies objectAtIndex:0];
1370     }
1372     //NSLog(@"    No replies");
1373     return nil;
1376 - (NSString *)waitForReplyOnPort:(int)port
1378     //NSLog(@"%s%d", _cmd, port);
1379     
1380     NSConnection *conn = [self connectionForServerPort:port];
1381     if (!conn)
1382         return nil;
1384     NSNumber *key = [NSNumber numberWithInt:port];
1385     NSMutableArray *replies = nil;
1386     NSString *reply = nil;
1388     // Wait for reply as long as the connection to the server is valid (unless
1389     // user interrupts wait with Ctrl-C).
1390     while (!got_int && [conn isValid] &&
1391             !(replies = [serverReplyDict objectForKey:key])) {
1392         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1393                                  beforeDate:[NSDate distantFuture]];
1394     }
1396     if (replies) {
1397         if ([replies count] > 0) {
1398             reply = [[replies objectAtIndex:0] retain];
1399             //NSLog(@"    Got reply: %@", reply);
1400             [replies removeObjectAtIndex:0];
1401             [reply autorelease];
1402         }
1404         if ([replies count] == 0)
1405             [serverReplyDict removeObjectForKey:key];
1406     }
1408     return reply;
1411 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1413     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1414     if (client) {
1415         @try {
1416             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1417             [client addReply:reply server:self];
1418             return YES;
1419         }
1420         @catch (NSException *e) {
1421             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1422         }
1423     } else {
1424         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1425     }
1427     return NO;
1430 - (BOOL)waitForAck
1432     return waitForAck;
1435 - (void)setWaitForAck:(BOOL)yn
1437     waitForAck = yn;
1440 - (void)waitForConnectionAcknowledgement
1442     if (!waitForAck) return;
1444     while (waitForAck && !got_int && [connection isValid]) {
1445         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1446                                  beforeDate:[NSDate distantFuture]];
1447         //NSLog(@"  waitForAck=%d got_int=%d isValid=%d",
1448         //        waitForAck, got_int, [connection isValid]);
1449     }
1451     if (waitForAck) {
1452         // Never received a connection acknowledgement, so die.
1453         [[NSNotificationCenter defaultCenter] removeObserver:self];
1454         [appProxy release];  appProxy = nil;
1456         // NOTE: We intentionally do not call mch_exit() since this in turn
1457         // will lead to -[MMBackend exit] getting called which we want to
1458         // avoid.
1459         exit(0);
1460     }
1462     [self processInputQueue];
1465 - (oneway void)acknowledgeConnection
1467     //NSLog(@"%s", _cmd);
1468     waitForAck = NO;
1471 @end // MMBackend
1475 @implementation MMBackend (Private)
1477 - (void)clearDrawData
1479     [drawData setLength:0];
1480     numWholeLineChanges = offsetForDrawDataPrune = 0;
1483 - (void)didChangeWholeLine
1485     // It may happen that draw queue is filled up with lots of changes that
1486     // affect a whole row.  If the number of such changes equals twice the
1487     // number of visible rows then we can prune some commands off the queue.
1488     //
1489     // NOTE: If we don't perform this pruning the draw queue may grow
1490     // indefinitely if Vim were to repeatedly send draw commands without ever
1491     // waiting for new input (that's when the draw queue is flushed).  The one
1492     // instance I know where this can happen is when a command is executed in
1493     // the shell (think ":grep" with thousands of matches).
1495     ++numWholeLineChanges;
1496     if (numWholeLineChanges == gui.num_rows) {
1497         // Remember the offset to prune up to.
1498         offsetForDrawDataPrune = [drawData length];
1499     } else if (numWholeLineChanges == 2*gui.num_rows) {
1500         // Delete all the unnecessary draw commands.
1501         NSMutableData *d = [[NSMutableData alloc]
1502                     initWithBytes:[drawData bytes] + offsetForDrawDataPrune
1503                            length:[drawData length] - offsetForDrawDataPrune];
1504         offsetForDrawDataPrune = [d length];
1505         numWholeLineChanges -= gui.num_rows;
1506         [drawData release];
1507         drawData = d;
1508     }
1511 - (void)waitForDialogReturn
1513     // Keep processing the run loop until a dialog returns.  To avoid getting
1514     // stuck in an endless loop (could happen if the setDialogReturn: message
1515     // was lost) we also do some paranoia checks.
1516     //
1517     // Note that in Cocoa the user can still resize windows and select menu
1518     // items while a sheet is being displayed, so we can't just wait for the
1519     // first message to arrive and assume that is the setDialogReturn: call.
1521     while (nil == dialogReturn && !got_int && [connection isValid])
1522         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1523                                  beforeDate:[NSDate distantFuture]];
1525     // Search for any resize messages on the input queue.  All other messages
1526     // on the input queue are dropped.  The reason why we single out resize
1527     // messages is because the user may have resized the window while a sheet
1528     // was open.
1529     int i, count = [inputQueue count];
1530     if (count > 0) {
1531         id textDimData = nil;
1532         if (count%2 == 0) {
1533             for (i = count-2; i >= 0; i -= 2) {
1534                 int msgid = [[inputQueue objectAtIndex:i] intValue];
1535                 if (SetTextDimensionsMsgID == msgid) {
1536                     textDimData = [[inputQueue objectAtIndex:i+1] retain];
1537                     break;
1538                 }
1539             }
1540         }
1542         [inputQueue removeAllObjects];
1544         if (textDimData) {
1545             [inputQueue addObject:
1546                     [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1547             [inputQueue addObject:textDimData];
1548             [textDimData release];
1549         }
1550     }
1553 - (void)insertVimStateMessage
1555     // NOTE: This is the place to add Vim state that needs to be accessed from
1556     // MacVim.  Do not add state that could potentially require lots of memory
1557     // since this message gets sent each time the output queue is forcibly
1558     // flushed (e.g. storing the currently selected text would be a bad idea).
1559     // We take this approach of "pushing" the state to MacVim to avoid having
1560     // to make synchronous calls from MacVim to Vim in order to get state.
1562     BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1563     int numTabs = tabpage_index(NULL) - 1;
1564     if (numTabs < 0)
1565         numTabs = 0;
1567     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1568         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1569         [NSNumber numberWithInt:p_mh], @"p_mh",
1570         [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1571         [NSNumber numberWithBool:mmta], @"p_mmta",
1572         [NSNumber numberWithInt:numTabs], @"numTabs",
1573         nil];
1575     // Put the state before all other messages.
1576     int msgid = SetVimStateMsgID;
1577     [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1578     [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1579                       atIndex:0];
1582 - (void)processInputQueue
1584     if ([inputQueue count] == 0) return;
1586     // NOTE: One of the input events may cause this method to be called
1587     // recursively, so copy the input queue to a local variable and clear the
1588     // queue before starting to process input events (otherwise we could get
1589     // stuck in an endless loop).
1590     NSArray *q = [inputQueue copy];
1591     unsigned i, count = [q count];
1593     [inputQueue removeAllObjects];
1595     for (i = 1; i < count; i+=2) {
1596         int msgid = [[q objectAtIndex:i-1] intValue];
1597         id data = [q objectAtIndex:i];
1598         if ([data isEqual:[NSNull null]])
1599             data = nil;
1601         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1602         [self handleInputEvent:msgid data:data];
1603     }
1605     [q release];
1606     //NSLog(@"Clear input event queue");
1609 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1611     if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1612             CmdKeyMsgID == msgid) {
1613         if (!data) return;
1614         const void *bytes = [data bytes];
1615         int mods = *((int*)bytes);  bytes += sizeof(int);
1616         int len = *((int*)bytes);  bytes += sizeof(int);
1617         NSString *key = [[NSString alloc] initWithBytes:bytes
1618                                                  length:len
1619                                                encoding:NSUTF8StringEncoding];
1620         mods = eventModifierFlagsToVimModMask(mods);
1622         if (InsertTextMsgID == msgid)
1623             [self handleInsertText:key];
1624         else
1625             [self handleKeyDown:key modifiers:mods];
1627         [key release];
1628     } else if (ScrollWheelMsgID == msgid) {
1629         if (!data) return;
1630         const void *bytes = [data bytes];
1632         int row = *((int*)bytes);  bytes += sizeof(int);
1633         int col = *((int*)bytes);  bytes += sizeof(int);
1634         int flags = *((int*)bytes);  bytes += sizeof(int);
1635         float dy = *((float*)bytes);  bytes += sizeof(float);
1637         int button = MOUSE_5;
1638         if (dy > 0) button = MOUSE_4;
1640         flags = eventModifierFlagsToVimMouseModMask(flags);
1642         int numLines = (int)round(dy);
1643         if (numLines < 0) numLines = -numLines;
1644         if (numLines == 0) numLines = 1;
1646 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1647         gui.scroll_wheel_force = numLines;
1648 #endif
1650         gui_send_mouse_event(button, col, row, NO, flags);
1651     } else if (MouseDownMsgID == msgid) {
1652         if (!data) return;
1653         const void *bytes = [data bytes];
1655         int row = *((int*)bytes);  bytes += sizeof(int);
1656         int col = *((int*)bytes);  bytes += sizeof(int);
1657         int button = *((int*)bytes);  bytes += sizeof(int);
1658         int flags = *((int*)bytes);  bytes += sizeof(int);
1659         int count = *((int*)bytes);  bytes += sizeof(int);
1661         button = eventButtonNumberToVimMouseButton(button);
1662         if (button >= 0) {
1663             flags = eventModifierFlagsToVimMouseModMask(flags);
1664             gui_send_mouse_event(button, col, row, count>1, flags);
1665         }
1666     } else if (MouseUpMsgID == msgid) {
1667         if (!data) return;
1668         const void *bytes = [data bytes];
1670         int row = *((int*)bytes);  bytes += sizeof(int);
1671         int col = *((int*)bytes);  bytes += sizeof(int);
1672         int flags = *((int*)bytes);  bytes += sizeof(int);
1674         flags = eventModifierFlagsToVimMouseModMask(flags);
1676         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1677     } else if (MouseDraggedMsgID == msgid) {
1678         if (!data) return;
1679         const void *bytes = [data bytes];
1681         int row = *((int*)bytes);  bytes += sizeof(int);
1682         int col = *((int*)bytes);  bytes += sizeof(int);
1683         int flags = *((int*)bytes);  bytes += sizeof(int);
1685         flags = eventModifierFlagsToVimMouseModMask(flags);
1687         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1688     } else if (MouseMovedMsgID == msgid) {
1689         const void *bytes = [data bytes];
1690         int row = *((int*)bytes);  bytes += sizeof(int);
1691         int col = *((int*)bytes);  bytes += sizeof(int);
1693         gui_mouse_moved(col, row);
1694     } else if (AddInputMsgID == msgid) {
1695         NSString *string = [[NSString alloc] initWithData:data
1696                 encoding:NSUTF8StringEncoding];
1697         if (string) {
1698             [self addInput:string];
1699             [string release];
1700         }
1701     } else if (SelectTabMsgID == msgid) {
1702         if (!data) return;
1703         const void *bytes = [data bytes];
1704         int idx = *((int*)bytes) + 1;
1705         //NSLog(@"Selecting tab %d", idx);
1706         send_tabline_event(idx);
1707     } else if (CloseTabMsgID == msgid) {
1708         if (!data) return;
1709         const void *bytes = [data bytes];
1710         int idx = *((int*)bytes) + 1;
1711         //NSLog(@"Closing tab %d", idx);
1712         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1713     } else if (AddNewTabMsgID == msgid) {
1714         //NSLog(@"Adding new tab");
1715         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1716     } else if (DraggedTabMsgID == msgid) {
1717         if (!data) return;
1718         const void *bytes = [data bytes];
1719         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1720         // based.
1721         int idx = *((int*)bytes);
1723         tabpage_move(idx);
1724     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1725             || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1726         if (!data) return;
1727         const void *bytes = [data bytes];
1728         int rows = Rows;
1729         if (SetTextColumnsMsgID != msgid) {
1730             rows = *((int*)bytes);  bytes += sizeof(int);
1731         }
1732         int cols = Columns;
1733         if (SetTextRowsMsgID != msgid) {
1734             cols = *((int*)bytes);  bytes += sizeof(int);
1735         }
1737         NSData *d = data;
1738         if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1739             int dim[2] = { rows, cols };
1740             d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1741             msgid = SetTextDimensionsReplyMsgID;
1742         }
1744         if (SetTextDimensionsMsgID == msgid)
1745             msgid = SetTextDimensionsReplyMsgID;
1747         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1748         // gui_resize_shell(), so we have to manually set the rows and columns
1749         // here since MacVim doesn't change the rows and columns to avoid
1750         // inconsistent states between Vim and MacVim.  The message sent back
1751         // indicates that it is a reply to a message that originated in MacVim
1752         // since we need to be able to determine where a message originated.
1753         [self queueMessage:msgid data:d];
1755         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1756         gui_resize_shell(cols, rows);
1757     } else if (ExecuteMenuMsgID == msgid) {
1758         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1759         if (attrs) {
1760             NSArray *desc = [attrs objectForKey:@"descriptor"];
1761             vimmenu_T *menu = menu_for_descriptor(desc);
1762             if (menu)
1763                 gui_menu_cb(menu);
1764         }
1765     } else if (ToggleToolbarMsgID == msgid) {
1766         [self handleToggleToolbar];
1767     } else if (ScrollbarEventMsgID == msgid) {
1768         [self handleScrollbarEvent:data];
1769     } else if (SetFontMsgID == msgid) {
1770         [self handleSetFont:data];
1771     } else if (VimShouldCloseMsgID == msgid) {
1772         gui_shell_closed();
1773     } else if (DropFilesMsgID == msgid) {
1774         [self handleDropFiles:data];
1775     } else if (DropStringMsgID == msgid) {
1776         [self handleDropString:data];
1777     } else if (GotFocusMsgID == msgid) {
1778         if (!gui.in_focus)
1779             [self focusChange:YES];
1780     } else if (LostFocusMsgID == msgid) {
1781         if (gui.in_focus)
1782             [self focusChange:NO];
1783     } else if (SetMouseShapeMsgID == msgid) {
1784         const void *bytes = [data bytes];
1785         int shape = *((int*)bytes);  bytes += sizeof(int);
1786         update_mouseshape(shape);
1787     } else if (XcodeModMsgID == msgid) {
1788         [self handleXcodeMod:data];
1789     } else if (OpenWithArgumentsMsgID == msgid) {
1790         [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1791     } else if (FindReplaceMsgID == msgid) {
1792         [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1793     } else {
1794         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1795     }
1798 + (NSDictionary *)specialKeys
1800     static NSDictionary *specialKeys = nil;
1802     if (!specialKeys) {
1803         NSBundle *mainBundle = [NSBundle mainBundle];
1804         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1805                                               ofType:@"plist"];
1806         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1807     }
1809     return specialKeys;
1812 - (void)handleInsertText:(NSString *)text
1814     if (!text) return;
1816     char_u *str = (char_u*)[text UTF8String];
1817     int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1819 #ifdef FEAT_MBYTE
1820     char_u *conv_str = NULL;
1821     if (input_conv.vc_type != CONV_NONE) {
1822         conv_str = string_convert(&input_conv, str, &len);
1823         if (conv_str)
1824             str = conv_str;
1825     }
1826 #endif
1828     for (i = 0; i < len; ++i) {
1829         add_to_input_buf(str+i, 1);
1830         if (CSI == str[i]) {
1831             // NOTE: If the converted string contains the byte CSI, then it
1832             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1833             // won't work.
1834             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1835             add_to_input_buf(extra, 2);
1836         }
1837     }
1839 #ifdef FEAT_MBYTE
1840     if (conv_str)
1841         vim_free(conv_str);
1842 #endif
1845 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1847     // TODO: This code is a horrible mess -- clean up!
1848     char_u special[3];
1849     char_u modChars[3];
1850     char_u *chars = (char_u*)[key UTF8String];
1851 #ifdef FEAT_MBYTE
1852     char_u *conv_str = NULL;
1853 #endif
1854     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1856     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1857     // that new keys can easily be added.
1858     NSString *specialString = [[MMBackend specialKeys]
1859             objectForKey:key];
1860     if (specialString && [specialString length] > 1) {
1861         //NSLog(@"special key: %@", specialString);
1862         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1863                 [specialString characterAtIndex:1]);
1865         ikey = simplify_key(ikey, &mods);
1866         if (ikey == CSI)
1867             ikey = K_CSI;
1869         special[0] = CSI;
1870         special[1] = K_SECOND(ikey);
1871         special[2] = K_THIRD(ikey);
1873         chars = special;
1874         length = 3;
1875     } else if (1 == length && TAB == chars[0]) {
1876         // Tab is a trouble child:
1877         // - <Tab> is added to the input buffer as is
1878         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1879         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1880         //   to be converted to utf-8
1881         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1882         // - <C-Tab> is reserved by Mac OS X
1883         // - <D-Tab> is reserved by Mac OS X
1884         chars = special;
1885         special[0] = TAB;
1886         length = 1;
1888         if (mods & MOD_MASK_SHIFT) {
1889             mods &= ~MOD_MASK_SHIFT;
1890             special[0] = CSI;
1891             special[1] = K_SECOND(K_S_TAB);
1892             special[2] = K_THIRD(K_S_TAB);
1893             length = 3;
1894         } else if (mods & MOD_MASK_ALT) {
1895             int mtab = 0x80 | TAB;
1896 #ifdef FEAT_MBYTE
1897             if (enc_utf8) {
1898                 // Convert to utf-8
1899                 special[0] = (mtab >> 6) + 0xc0;
1900                 special[1] = mtab & 0xbf;
1901                 length = 2;
1902             } else
1903 #endif
1904             {
1905                 special[0] = mtab;
1906                 length = 1;
1907             }
1908             mods &= ~MOD_MASK_ALT;
1909         }
1910     } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1911         // META key is treated separately.  This code was taken from gui_w48.c
1912         // and gui_gtk_x11.c.
1913         char_u string[7];
1914         int ch = simplify_key(chars[0], &mods);
1916         // Remove the SHIFT modifier for keys where it's already included,
1917         // e.g., '(' and '*'
1918         if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1919             mods &= ~MOD_MASK_SHIFT;
1921         // Interpret the ALT key as making the key META, include SHIFT, etc.
1922         ch = extract_modifiers(ch, &mods);
1923         if (ch == CSI)
1924             ch = K_CSI;
1926         int len = 0;
1927         if (mods) {
1928             string[len++] = CSI;
1929             string[len++] = KS_MODIFIER;
1930             string[len++] = mods;
1931         }
1933         if (IS_SPECIAL(ch)) {
1934             string[len++] = CSI;
1935             string[len++] = K_SECOND(ch);
1936             string[len++] = K_THIRD(ch);
1937         } else {
1938             string[len++] = ch;
1939 #ifdef FEAT_MBYTE
1940             // TODO: What if 'enc' is not "utf-8"?
1941             if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1942                 string[len++] = ch & 0xbf;
1943                 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1944                 if (string[len-1] == CSI) {
1945                     string[len++] = KS_EXTRA;
1946                     string[len++] = (int)KE_CSI;
1947                 }
1948             }
1949 #endif
1950         }
1952         add_to_input_buf(string, len);
1953         return;
1954     } else if (length > 0) {
1955         unichar c = [key characterAtIndex:0];
1956         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1957         //        [key characterAtIndex:0], mods);
1959         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1960         // cleared since they are already added to the key by the AppKit.
1961         // Unfortunately, the only way to deal with when to clear the modifiers
1962         // or not seems to be to have hard-wired rules like this.
1963         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1964                     || 0x9 == c || 0xd == c || ESC == c) ) {
1965             mods &= ~MOD_MASK_SHIFT;
1966             mods &= ~MOD_MASK_CTRL;
1967             //NSLog(@"clear shift ctrl");
1968         }
1970 #ifdef FEAT_MBYTE
1971         if (input_conv.vc_type != CONV_NONE) {
1972             conv_str = string_convert(&input_conv, chars, &length);
1973             if (conv_str)
1974                 chars = conv_str;
1975         }
1976 #endif
1977     }
1979     if (chars && length > 0) {
1980         if (mods) {
1981             //NSLog(@"adding mods: %d", mods);
1982             modChars[0] = CSI;
1983             modChars[1] = KS_MODIFIER;
1984             modChars[2] = mods;
1985             add_to_input_buf(modChars, 3);
1986         }
1988         //NSLog(@"add to input buf: 0x%x", chars[0]);
1989         // TODO: Check for CSI bytes?
1990         add_to_input_buf(chars, length);
1991     }
1993 #ifdef FEAT_MBYTE
1994     if (conv_str)
1995         vim_free(conv_str);
1996 #endif
1999 - (void)queueMessage:(int)msgid data:(NSData *)data
2001     //if (msgid != EnableMenuItemMsgID)
2002     //    NSLog(@"queueMessage:%s", MessageStrings[msgid]);
2004     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
2005     if (data)
2006         [outputQueue addObject:data];
2007     else
2008         [outputQueue addObject:[NSData data]];
2011 - (void)connectionDidDie:(NSNotification *)notification
2013     // If the main connection to MacVim is lost this means that either MacVim
2014     // has crashed or this process did not receive its termination message
2015     // properly (e.g. if the TerminateNowMsgID was dropped).
2016     //
2017     // NOTE: This is not called if a Vim controller invalidates its connection.
2019     NSLog(@"WARNING[%s]: Main connection was lost before process had a chance "
2020             "to terminate; preserving swap files.", _cmd);
2021     getout_preserve_modified(1);
2024 - (void)blinkTimerFired:(NSTimer *)timer
2026     NSTimeInterval timeInterval = 0;
2028     [blinkTimer release];
2029     blinkTimer = nil;
2031     if (MMBlinkStateOn == blinkState) {
2032         gui_undraw_cursor();
2033         blinkState = MMBlinkStateOff;
2034         timeInterval = blinkOffInterval;
2035     } else if (MMBlinkStateOff == blinkState) {
2036         gui_update_cursor(TRUE, FALSE);
2037         blinkState = MMBlinkStateOn;
2038         timeInterval = blinkOnInterval;
2039     }
2041     if (timeInterval > 0) {
2042         blinkTimer = 
2043             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2044                                             selector:@selector(blinkTimerFired:)
2045                                             userInfo:nil repeats:NO] retain];
2046         [self flushQueue:YES];
2047     }
2050 - (void)focusChange:(BOOL)on
2052     gui_focus_change(on);
2055 - (void)handleToggleToolbar
2057     // If 'go' contains 'T', then remove it, else add it.
2059     char_u go[sizeof(GO_ALL)+2];
2060     char_u *p;
2061     int len;
2063     STRCPY(go, p_go);
2064     p = vim_strchr(go, GO_TOOLBAR);
2065     len = STRLEN(go);
2067     if (p != NULL) {
2068         char_u *end = go + len;
2069         while (p < end) {
2070             p[0] = p[1];
2071             ++p;
2072         }
2073     } else {
2074         go[len] = GO_TOOLBAR;
2075         go[len+1] = NUL;
2076     }
2078     set_option_value((char_u*)"guioptions", 0, go, 0);
2080     [self redrawScreen];
2083 - (void)handleScrollbarEvent:(NSData *)data
2085     if (!data) return;
2087     const void *bytes = [data bytes];
2088     long ident = *((long*)bytes);  bytes += sizeof(long);
2089     int hitPart = *((int*)bytes);  bytes += sizeof(int);
2090     float fval = *((float*)bytes);  bytes += sizeof(float);
2091     scrollbar_T *sb = gui_find_scrollbar(ident);
2093     if (sb) {
2094         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2095         long value = sb_info->value;
2096         long size = sb_info->size;
2097         long max = sb_info->max;
2098         BOOL isStillDragging = NO;
2099         BOOL updateKnob = YES;
2101         switch (hitPart) {
2102         case NSScrollerDecrementPage:
2103             value -= (size > 2 ? size - 2 : 1);
2104             break;
2105         case NSScrollerIncrementPage:
2106             value += (size > 2 ? size - 2 : 1);
2107             break;
2108         case NSScrollerDecrementLine:
2109             --value;
2110             break;
2111         case NSScrollerIncrementLine:
2112             ++value;
2113             break;
2114         case NSScrollerKnob:
2115             isStillDragging = YES;
2116             // fall through ...
2117         case NSScrollerKnobSlot:
2118             value = (long)(fval * (max - size + 1));
2119             // fall through ...
2120         default:
2121             updateKnob = NO;
2122             break;
2123         }
2125         //NSLog(@"value %d -> %d", sb_info->value, value);
2126         gui_drag_scrollbar(sb, value, isStillDragging);
2128         if (updateKnob) {
2129             // Dragging the knob or option+clicking automatically updates
2130             // the knob position (on the actual NSScroller), so we only
2131             // need to set the knob position in the other cases.
2132             if (sb->wp) {
2133                 // Update both the left&right vertical scrollbars.
2134                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2135                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2136                 [self setScrollbarThumbValue:value size:size max:max
2137                                   identifier:identLeft];
2138                 [self setScrollbarThumbValue:value size:size max:max
2139                                   identifier:identRight];
2140             } else {
2141                 // Update the horizontal scrollbar.
2142                 [self setScrollbarThumbValue:value size:size max:max
2143                                   identifier:ident];
2144             }
2145         }
2146     }
2149 - (void)handleSetFont:(NSData *)data
2151     if (!data) return;
2153     const void *bytes = [data bytes];
2154     float pointSize = *((float*)bytes);  bytes += sizeof(float);
2155     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2156     bytes += sizeof(unsigned);  // len not used
2158     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2159     [name appendString:[NSString stringWithFormat:@":h%d", (int)pointSize]];
2160     char_u *s = (char_u*)[name UTF8String];
2162 #ifdef FEAT_MBYTE
2163     s = CONVERT_FROM_UTF8(s);
2164 #endif
2166     set_option_value((char_u*)"guifont", 0, s, 0);
2168 #ifdef FEAT_MBYTE
2169     CONVERT_FROM_UTF8_FREE(s);
2170 #endif
2172     [self redrawScreen];
2175 - (void)handleDropFiles:(NSData *)data
2177     // TODO: Get rid of this method; instead use Vim script directly.  At the
2178     // moment I know how to do this to open files in tabs, but I'm not sure how
2179     // to add the filenames to the command line when in command line mode.
2181     if (!data) return;
2183     NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2184     if (!args) return;
2186     id obj = [args objectForKey:@"forceOpen"];
2187     BOOL forceOpen = YES;
2188     if (obj)
2189         forceOpen = [obj boolValue];
2191     NSArray *filenames = [args objectForKey:@"filenames"];
2192     if (!(filenames && [filenames count] > 0)) return;
2194 #ifdef FEAT_DND
2195     if (!forceOpen && (State & CMDLINE)) {
2196         // HACK!  If Vim is in command line mode then the files names
2197         // should be added to the command line, instead of opening the
2198         // files in tabs (unless forceOpen is set).  This is taken care of by
2199         // gui_handle_drop().
2200         int n = [filenames count];
2201         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2202         if (fnames) {
2203             int i = 0;
2204             for (i = 0; i < n; ++i)
2205                 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2207             // NOTE!  This function will free 'fnames'.
2208             // HACK!  It is assumed that the 'x' and 'y' arguments are
2209             // unused when in command line mode.
2210             gui_handle_drop(0, 0, 0, fnames, n);
2211         }
2212     } else
2213 #endif // FEAT_DND
2214     {
2215         [self handleOpenWithArguments:args];
2216     }
2219 - (void)handleDropString:(NSData *)data
2221     if (!data) return;
2223 #ifdef FEAT_DND
2224     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2225     const void *bytes = [data bytes];
2226     int len = *((int*)bytes);  bytes += sizeof(int);
2227     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2229     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2230     NSRange range = { 0, [string length] };
2231     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2232                                          withString:@"\x0a" options:0
2233                                               range:range];
2234     if (0 == n) {
2235         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2236                                        options:0 range:range];
2237     }
2239     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2240     char_u *s = (char_u*)[string UTF8String];
2241 #ifdef FEAT_MBYTE
2242     if (input_conv.vc_type != CONV_NONE)
2243         s = string_convert(&input_conv, s, &len);
2244 #endif
2245     dnd_yank_drag_data(s, len);
2246 #ifdef FEAT_MBYTE
2247     if (input_conv.vc_type != CONV_NONE)
2248         vim_free(s);
2249 #endif
2250     add_to_input_buf(dropkey, sizeof(dropkey));
2251 #endif // FEAT_DND
2254 - (void)startOdbEditWithArguments:(NSDictionary *)args
2256 #ifdef FEAT_ODB_EDITOR
2257     id obj = [args objectForKey:@"remoteID"];
2258     if (!obj) return;
2260     OSType serverID = [obj unsignedIntValue];
2261     NSString *remotePath = [args objectForKey:@"remotePath"];
2263     NSAppleEventDescriptor *token = nil;
2264     NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2265     obj = [args objectForKey:@"remoteTokenDescType"];
2266     if (tokenData && obj) {
2267         DescType tokenType = [obj unsignedLongValue];
2268         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2269                                                                 data:tokenData];
2270     }
2272     NSArray *filenames = [args objectForKey:@"filenames"];
2273     unsigned i, numFiles = [filenames count];
2274     for (i = 0; i < numFiles; ++i) {
2275         NSString *filename = [filenames objectAtIndex:i];
2276         char_u *s = [filename vimStringSave];
2277         buf_T *buf = buflist_findname(s);
2278         vim_free(s);
2280         if (buf) {
2281             if (buf->b_odb_token) {
2282                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2283                 buf->b_odb_token = NULL;
2284             }
2286             if (buf->b_odb_fname) {
2287                 vim_free(buf->b_odb_fname);
2288                 buf->b_odb_fname = NULL;
2289             }
2291             buf->b_odb_server_id = serverID;
2293             if (token)
2294                 buf->b_odb_token = [token retain];
2295             if (remotePath)
2296                 buf->b_odb_fname = [remotePath vimStringSave];
2297         } else {
2298             NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2299                     filename);
2300         }
2301     }
2302 #endif // FEAT_ODB_EDITOR
2305 - (void)handleXcodeMod:(NSData *)data
2307 #if 0
2308     const void *bytes = [data bytes];
2309     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2310     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2311     if (0 == len)
2312         return;
2314     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2315             descriptorWithDescriptorType:type
2316                                    bytes:bytes
2317                                   length:len];
2318 #endif
2321 - (void)handleOpenWithArguments:(NSDictionary *)args
2323     // ARGUMENT:                DESCRIPTION:
2324     // -------------------------------------------------------------
2325     // filenames                list of filenames
2326     // dontOpen                 don't open files specified in above argument
2327     // layout                   which layout to use to open files
2328     // selectionRange           range of lines to select
2329     // searchText               string to search for
2330     // cursorLine               line to position the cursor on
2331     // cursorColumn             column to position the cursor on
2332     //                          (only valid when "cursorLine" is set)
2333     // remoteID                 ODB parameter
2334     // remotePath               ODB parameter
2335     // remoteTokenDescType      ODB parameter
2336     // remoteTokenData          ODB parameter
2338     //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2340     NSArray *filenames = [args objectForKey:@"filenames"];
2341     int i, numFiles = filenames ? [filenames count] : 0;
2342     BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2343     int layout = [[args objectForKey:@"layout"] intValue];
2345     // Change to directory of first file to open if this is an "unused" editor
2346     // (but do not do this if editing remotely).
2347     if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2348             && (starting || [self unusedEditor]) ) {
2349         char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2350         vim_chdirfile(s);
2351         vim_free(s);
2352     }
2354     if (starting > 0) {
2355         // When Vim is starting we simply add the files to be opened to the
2356         // global arglist and Vim will take care of opening them for us.
2357         if (openFiles && numFiles > 0) {
2358             for (i = 0; i < numFiles; i++) {
2359                 NSString *fname = [filenames objectAtIndex:i];
2360                 char_u *p = NULL;
2362                 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2363                         || (p = [fname vimStringSave]) == NULL)
2364                     exit(2); // See comment in -[MMBackend exit]
2365                 else
2366                     alist_add(&global_alist, p, 2);
2367             }
2369             // Vim will take care of arranging the files added to the arglist
2370             // in windows or tabs; all we must do is to specify which layout to
2371             // use.
2372             initialWindowLayout = layout;
2373         }
2374     } else {
2375         // When Vim is already open we resort to some trickery to open the
2376         // files with the specified layout.
2377         //
2378         // TODO: Figure out a better way to handle this?
2379         if (openFiles && numFiles > 0) {
2380             BOOL oneWindowInTab = topframe ? YES
2381                                            : (topframe->fr_layout == FR_LEAF);
2382             BOOL bufChanged = NO;
2383             BOOL bufHasFilename = NO;
2384             if (curbuf) {
2385                 bufChanged = curbufIsChanged();
2386                 bufHasFilename = curbuf->b_ffname != NULL;
2387             }
2389             // Temporarily disable flushing since the following code may
2390             // potentially cause multiple redraws.
2391             flushDisabled = YES;
2393             BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2394             if (WIN_TABS == layout && !onlyOneTab) {
2395                 // By going to the last tabpage we ensure that the new tabs
2396                 // will appear last (if this call is left out, the taborder
2397                 // becomes messy).
2398                 goto_tabpage(9999);
2399             }
2401             // Make sure we're in normal mode first.
2402             [self addInput:@"<C-\\><C-N>"];
2404             if (numFiles > 1) {
2405                 // With "split layout" we open a new tab before opening
2406                 // multiple files if the current tab has more than one window
2407                 // or if there is exactly one window but whose buffer has a
2408                 // filename.  (The :drop command ensures modified buffers get
2409                 // their own window.)
2410                 if ((WIN_HOR == layout || WIN_VER == layout) &&
2411                         (!oneWindowInTab || bufHasFilename))
2412                     [self addInput:@":tabnew<CR>"];
2414                 // The files are opened by constructing a ":drop ..." command
2415                 // and executing it.
2416                 NSMutableString *cmd = (WIN_TABS == layout)
2417                         ? [NSMutableString stringWithString:@":tab drop"]
2418                         : [NSMutableString stringWithString:@":drop"];
2420                 for (i = 0; i < numFiles; ++i) {
2421                     NSString *file = [filenames objectAtIndex:i];
2422                     file = [file stringByEscapingSpecialFilenameCharacters];
2423                     [cmd appendString:@" "];
2424                     [cmd appendString:file];
2425                 }
2427                 // Temporarily clear 'suffixes' so that the files are opened in
2428                 // the same order as they appear in the "filenames" array.
2429                 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2431                 [self addInput:cmd];
2433                 // Split the view into multiple windows if requested.
2434                 if (WIN_HOR == layout)
2435                     [self addInput:@"|sall"];
2436                 else if (WIN_VER == layout)
2437                     [self addInput:@"|vert sall"];
2439                 // Restore the old value of 'suffixes'.
2440                 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2441             } else {
2442                 // When opening one file we try to reuse the current window,
2443                 // but not if its buffer is modified or has a filename.
2444                 // However, the 'arglist' layout always opens the file in the
2445                 // current window.
2446                 NSString *file = [[filenames lastObject]
2447                         stringByEscapingSpecialFilenameCharacters];
2448                 NSString *cmd;
2449                 if (WIN_HOR == layout) {
2450                     if (!(bufHasFilename || bufChanged))
2451                         cmd = [NSString stringWithFormat:@":e %@", file];
2452                     else
2453                         cmd = [NSString stringWithFormat:@":sp %@", file];
2454                 } else if (WIN_VER == layout) {
2455                     if (!(bufHasFilename || bufChanged))
2456                         cmd = [NSString stringWithFormat:@":e %@", file];
2457                     else
2458                         cmd = [NSString stringWithFormat:@":vsp %@", file];
2459                 } else if (WIN_TABS == layout) {
2460                     if (oneWindowInTab && !(bufHasFilename || bufChanged))
2461                         cmd = [NSString stringWithFormat:@":e %@", file];
2462                     else
2463                         cmd = [NSString stringWithFormat:@":tabe %@", file];
2464                 } else {
2465                     // (The :drop command will split if there is a modified
2466                     // buffer.)
2467                     cmd = [NSString stringWithFormat:@":drop %@", file];
2468                 }
2470                 [self addInput:cmd];
2471                 [self addInput:@"<CR>"];
2472             }
2474             // Force screen redraw (does it have to be this complicated?).
2475             // (This code was taken from the end of gui_handle_drop().)
2476             update_screen(NOT_VALID);
2477             setcursor();
2478             out_flush();
2479             gui_update_cursor(FALSE, FALSE);
2480             maketitle();
2482             flushDisabled = NO;
2483         }
2484     }
2486     if ([args objectForKey:@"remoteID"]) {
2487         // NOTE: We have to delay processing any ODB related arguments since
2488         // the file(s) may not be opened until the input buffer is processed.
2489         [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2490                                withObject:args
2491                             waitUntilDone:NO];
2492     }
2494     NSString *lineString = [args objectForKey:@"cursorLine"];
2495     if (lineString && [lineString intValue] > 0) {
2496         NSString *columnString = [args objectForKey:@"cursorColumn"];
2497         if (!(columnString && [columnString intValue] > 0))
2498             columnString = @"1";
2500         NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2501                 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2502         [self addInput:cmd];
2503     }
2505     NSString *rangeString = [args objectForKey:@"selectionRange"];
2506     if (rangeString) {
2507         // Build a command line string that will select the given range of
2508         // lines.  If range.length == 0, then position the cursor on the given
2509         // line but do not select.
2510         NSRange range = NSRangeFromString(rangeString);
2511         NSString *cmd;
2512         if (range.length > 0) {
2513             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2514                     NSMaxRange(range), range.location];
2515         } else {
2516             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2517                     range.location];
2518         }
2520         [self addInput:cmd];
2521     }
2523     NSString *searchText = [args objectForKey:@"searchText"];
2524     if (searchText) {
2525         [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2526                 searchText]];
2527     }
2530 - (BOOL)checkForModifiedBuffers
2532     buf_T *buf;
2533     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2534         if (bufIsChanged(buf)) {
2535             return YES;
2536         }
2537     }
2539     return NO;
2542 - (void)addInput:(NSString *)input
2544     // NOTE: This code is essentially identical to server_to_input_buf(),
2545     // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2546     char_u *string = [input vimStringSave];
2547     if (!string) return;
2549     /* Set 'cpoptions' the way we want it.
2550      *    B set - backslashes are *not* treated specially
2551      *    k set - keycodes are *not* reverse-engineered
2552      *    < unset - <Key> sequences *are* interpreted
2553      *  The last but one parameter of replace_termcodes() is TRUE so that the
2554      *  <lt> sequence is recognised - needed for a real backslash.
2555      */
2556     char_u *ptr = NULL;
2557     char_u *cpo_save = p_cpo;
2558     p_cpo = (char_u *)"Bk";
2559     char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2560     p_cpo = cpo_save;
2562     if (*ptr != NUL)    /* trailing CTRL-V results in nothing */
2563     {
2564         /*
2565          * Add the string to the input stream.
2566          * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2567          *
2568          * First clear typed characters from the typeahead buffer, there could
2569          * be half a mapping there.  Then append to the existing string, so
2570          * that multiple commands from a client are concatenated.
2571          */
2572         if (typebuf.tb_maplen < typebuf.tb_len)
2573             del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2574         (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2576         /* Let input_available() know we inserted text in the typeahead
2577          * buffer. */
2578         typebuf_was_filled = TRUE;
2579     }
2580     vim_free(ptr);
2581     vim_free(string);
2584 - (BOOL)unusedEditor
2586     BOOL oneWindowInTab = topframe ? YES
2587                                    : (topframe->fr_layout == FR_LEAF);
2588     BOOL bufChanged = NO;
2589     BOOL bufHasFilename = NO;
2590     if (curbuf) {
2591         bufChanged = curbufIsChanged();
2592         bufHasFilename = curbuf->b_ffname != NULL;
2593     }
2595     BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2597     return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2600 - (void)redrawScreen
2602     // Force screen redraw (does it have to be this complicated?).
2603     redraw_all_later(CLEAR);
2604     update_screen(NOT_VALID);
2605     setcursor();
2606     out_flush();
2607     gui_update_cursor(FALSE, FALSE);
2609     // HACK! The cursor is not put back at the command line by the above
2610     // "redraw commands".  The following test seems to do the trick though.
2611     if (State & CMDLINE)
2612         redrawcmdline();
2615 - (void)handleFindReplace:(NSDictionary *)args
2617     if (!args) return;
2619     NSString *findString = [args objectForKey:@"find"];
2620     if (!findString) return;
2622     char_u *find = [findString vimStringSave];
2623     char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2624     int flags = [[args objectForKey:@"flags"] intValue];
2626     // NOTE: The flag 0x100 is used to indicate a backward search.
2627     gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2629     vim_free(find);
2630     vim_free(replace);
2633 @end // MMBackend (Private)
2638 @implementation MMBackend (ClientServer)
2640 - (NSString *)connectionNameFromServerName:(NSString *)name
2642     NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2644     return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2645         lowercaseString];
2648 - (NSConnection *)connectionForServerName:(NSString *)name
2650     // TODO: Try 'name%d' if 'name' fails.
2651     NSString *connName = [self connectionNameFromServerName:name];
2652     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2654     if (!svrConn) {
2655         svrConn = [NSConnection connectionWithRegisteredName:connName
2656                                                            host:nil];
2657         // Try alternate server...
2658         if (!svrConn && alternateServerName) {
2659             //NSLog(@"  trying to connect to alternate server: %@",
2660             //        alternateServerName);
2661             connName = [self connectionNameFromServerName:alternateServerName];
2662             svrConn = [NSConnection connectionWithRegisteredName:connName
2663                                                             host:nil];
2664         }
2666         // Try looking for alternate servers...
2667         if (!svrConn) {
2668             //NSLog(@"  looking for alternate servers...");
2669             NSString *alt = [self alternateServerNameForName:name];
2670             if (alt != alternateServerName) {
2671                 //NSLog(@"  found alternate server: %@", string);
2672                 [alternateServerName release];
2673                 alternateServerName = [alt copy];
2674             }
2675         }
2677         // Try alternate server again...
2678         if (!svrConn && alternateServerName) {
2679             //NSLog(@"  trying to connect to alternate server: %@",
2680             //        alternateServerName);
2681             connName = [self connectionNameFromServerName:alternateServerName];
2682             svrConn = [NSConnection connectionWithRegisteredName:connName
2683                                                             host:nil];
2684         }
2686         if (svrConn) {
2687             [connectionNameDict setObject:svrConn forKey:connName];
2689             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2690             [[NSNotificationCenter defaultCenter] addObserver:self
2691                     selector:@selector(serverConnectionDidDie:)
2692                         name:NSConnectionDidDieNotification object:svrConn];
2693         }
2694     }
2696     return svrConn;
2699 - (NSConnection *)connectionForServerPort:(int)port
2701     NSConnection *conn;
2702     NSEnumerator *e = [connectionNameDict objectEnumerator];
2704     while ((conn = [e nextObject])) {
2705         // HACK! Assume connection uses mach ports.
2706         if (port == [(NSMachPort*)[conn sendPort] machPort])
2707             return conn;
2708     }
2710     return nil;
2713 - (void)serverConnectionDidDie:(NSNotification *)notification
2715     //NSLog(@"%s%@", _cmd, notification);
2717     NSConnection *svrConn = [notification object];
2719     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2720     [[NSNotificationCenter defaultCenter]
2721             removeObserver:self
2722                       name:NSConnectionDidDieNotification
2723                     object:svrConn];
2725     [connectionNameDict removeObjectsForKeys:
2726         [connectionNameDict allKeysForObject:svrConn]];
2728     // HACK! Assume connection uses mach ports.
2729     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2730     NSNumber *key = [NSNumber numberWithInt:port];
2732     [clientProxyDict removeObjectForKey:key];
2733     [serverReplyDict removeObjectForKey:key];
2736 - (void)addClient:(NSDistantObject *)client
2738     NSConnection *conn = [client connectionForProxy];
2739     // HACK! Assume connection uses mach ports.
2740     int port = [(NSMachPort*)[conn sendPort] machPort];
2741     NSNumber *key = [NSNumber numberWithInt:port];
2743     if (![clientProxyDict objectForKey:key]) {
2744         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2745         [clientProxyDict setObject:client forKey:key];
2746     }
2748     // NOTE: 'clientWindow' is a global variable which is used by <client>
2749     clientWindow = port;
2752 - (NSString *)alternateServerNameForName:(NSString *)name
2754     if (!(name && [name length] > 0))
2755         return nil;
2757     // Only look for alternates if 'name' doesn't end in a digit.
2758     unichar lastChar = [name characterAtIndex:[name length]-1];
2759     if (lastChar >= '0' && lastChar <= '9')
2760         return nil;
2762     // Look for alternates among all current servers.
2763     NSArray *list = [self serverList];
2764     if (!(list && [list count] > 0))
2765         return nil;
2767     // Filter out servers starting with 'name' and ending with a number. The
2768     // (?i) pattern ensures that the match is case insensitive.
2769     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2770     NSPredicate *pred = [NSPredicate predicateWithFormat:
2771             @"SELF MATCHES %@", pat];
2772     list = [list filteredArrayUsingPredicate:pred];
2773     if ([list count] > 0) {
2774         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2775         return [list objectAtIndex:0];
2776     }
2778     return nil;
2781 @end // MMBackend (ClientServer)
2786 @implementation NSString (MMServerNameCompare)
2787 - (NSComparisonResult)serverNameCompare:(NSString *)string
2789     return [self compare:string
2790                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2792 @end
2797 static int eventModifierFlagsToVimModMask(int modifierFlags)
2799     int modMask = 0;
2801     if (modifierFlags & NSShiftKeyMask)
2802         modMask |= MOD_MASK_SHIFT;
2803     if (modifierFlags & NSControlKeyMask)
2804         modMask |= MOD_MASK_CTRL;
2805     if (modifierFlags & NSAlternateKeyMask)
2806         modMask |= MOD_MASK_ALT;
2807     if (modifierFlags & NSCommandKeyMask)
2808         modMask |= MOD_MASK_CMD;
2810     return modMask;
2813 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2815     int modMask = 0;
2817     if (modifierFlags & NSShiftKeyMask)
2818         modMask |= MOUSE_SHIFT;
2819     if (modifierFlags & NSControlKeyMask)
2820         modMask |= MOUSE_CTRL;
2821     if (modifierFlags & NSAlternateKeyMask)
2822         modMask |= MOUSE_ALT;
2824     return modMask;
2827 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2829     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2831     return (buttonNumber >= 0 && buttonNumber < 3)
2832             ? mouseButton[buttonNumber] : -1;
2837 // This function is modeled after the VimToPython function found in if_python.c
2838 // NB This does a deep copy by value, it does not lookup references like the
2839 // VimToPython function does.  This is because I didn't want to deal with the
2840 // retain cycles that this would create, and we can cover 99% of the use cases
2841 // by ignoring it.  If we ever switch to using GC in MacVim then this
2842 // functionality can be implemented easily.
2843 static id vimToCocoa(typval_T * tv, int depth)
2845     id result = nil;
2846     id newObj = nil;
2849     // Avoid infinite recursion
2850     if (depth > 100) {
2851         return nil;
2852     }
2854     if (tv->v_type == VAR_STRING) {
2855         char_u * val = tv->vval.v_string;
2856         // val can be NULL if the string is empty
2857         if (!val) {
2858             result = [NSString string];
2859         } else {
2860 #ifdef FEAT_MBYTE
2861             val = CONVERT_TO_UTF8(val);
2862 #endif
2863             result = [NSString stringWithUTF8String:(char*)val];
2864 #ifdef FEAT_MBYTE
2865             CONVERT_TO_UTF8_FREE(val);
2866 #endif
2867         }
2868     } else if (tv->v_type == VAR_NUMBER) {
2869         // looks like sizeof(varnumber_T) is always <= sizeof(long)
2870         result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2871     } else if (tv->v_type == VAR_LIST) {
2872         list_T * list = tv->vval.v_list;
2873         listitem_T * curr;
2875         NSMutableArray * arr = result = [NSMutableArray array];
2877         if (list != NULL) {
2878             for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2879                 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2880                 [arr addObject:newObj];
2881             }
2882         }
2883     } else if (tv->v_type == VAR_DICT) {
2884         NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2886         if (tv->vval.v_dict != NULL) {
2887             hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2888             int todo = ht->ht_used;
2889             hashitem_T * hi;
2890             dictitem_T * di;
2892             for (hi = ht->ht_array; todo > 0; ++hi) {
2893                 if (!HASHITEM_EMPTY(hi)) {
2894                     --todo;
2896                     di = dict_lookup(hi);
2897                     newObj = vimToCocoa(&di->di_tv, depth + 1);
2899                     char_u * keyval = hi->hi_key;
2900 #ifdef FEAT_MBYTE
2901                     keyval = CONVERT_TO_UTF8(keyval);
2902 #endif
2903                     NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2904 #ifdef FEAT_MBYTE
2905                     CONVERT_TO_UTF8_FREE(keyval);
2906 #endif
2907                     [dict setObject:newObj forKey:key];
2908                 }
2909             }
2910         }
2911     } else { // only func refs should fall into this category?
2912         result = nil;
2913     }
2915     return result;
2919 // This function is modeled after eval_client_expr_to_string found in main.c
2920 // Returns nil if there was an error evaluating the expression, and writes a
2921 // message to errorStr.
2922 // TODO Get the error that occurred while evaluating the expression in vim
2923 // somehow.
2924 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2927     char_u *s = (char_u*)[expr UTF8String];
2929 #ifdef FEAT_MBYTE
2930     s = CONVERT_FROM_UTF8(s);
2931 #endif
2933     int save_dbl = debug_break_level;
2934     int save_ro = redir_off;
2936     debug_break_level = -1;
2937     redir_off = 0;
2938     ++emsg_skip;
2940     typval_T * tvres = eval_expr(s, NULL);
2942     debug_break_level = save_dbl;
2943     redir_off = save_ro;
2944     --emsg_skip;
2946     setcursor();
2947     out_flush();
2949 #ifdef FEAT_MBYTE
2950     CONVERT_FROM_UTF8_FREE(s);
2951 #endif
2953 #ifdef FEAT_GUI
2954     if (gui.in_use)
2955         gui_update_cursor(FALSE, FALSE);
2956 #endif
2958     if (tvres == NULL) {
2959         free_tv(tvres);
2960         *errstr = @"Expression evaluation failed.";
2961     }
2963     id res = vimToCocoa(tvres, 1);
2965     free_tv(tvres);
2967     if (res == nil) {
2968         *errstr = @"Conversion to cocoa values failed.";
2969     }
2971     return res;
2976 @implementation NSString (VimStrings)
2978 + (id)stringWithVimString:(char_u *)s
2980     // This method ensures a non-nil string is returned.  If 's' cannot be
2981     // converted to a utf-8 string it is assumed to be latin-1.  If conversion
2982     // still fails an empty NSString is returned.
2983     NSString *string = nil;
2984     if (s) {
2985 #ifdef FEAT_MBYTE
2986         s = CONVERT_TO_UTF8(s);
2987 #endif
2988         string = [NSString stringWithUTF8String:(char*)s];
2989         if (!string) {
2990             // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2991             // latin-1?
2992             string = [NSString stringWithCString:(char*)s
2993                                         encoding:NSISOLatin1StringEncoding];
2994         }
2995 #ifdef FEAT_MBYTE
2996         CONVERT_TO_UTF8_FREE(s);
2997 #endif
2998     }
3000     return string != nil ? string : [NSString string];
3003 - (char_u *)vimStringSave
3005     char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3007 #ifdef FEAT_MBYTE
3008     s = CONVERT_FROM_UTF8(s);
3009 #endif
3010     ret = vim_strsave(s);
3011 #ifdef FEAT_MBYTE
3012     CONVERT_FROM_UTF8_FREE(s);
3013 #endif
3015     return ret;
3018 @end // NSString (VimStrings)