Keep whole window visible on ":set lines=X columns=Y"
[MacVim.git] / src / MacVim / MMBackend.m
blob0daa4d5132231a74306f2bbc60da7b1e5a4e4197
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
11  * MMBackend
12  *
13  * MMBackend communicates with the frontend (MacVim).  It maintains a queue of
14  * output which is flushed to the frontend under controlled circumstances (so
15  * as to maintain a steady framerate).  Input from the frontend is also handled
16  * here.
17  *
18  * The frontend communicates with the backend via the MMBackendProtocol.  In
19  * particular, input is sent to the backend via processInput:data: and Vim
20  * state can be queried from the frontend with evaluateExpression:.
21  *
22  * It is very important to realize that all state is held by the backend, the
23  * frontend must either ask for state [MMBackend evaluateExpression:] or wait
24  * for the backend to update [MMVimController processCommandQueue:].
25  *
26  * The client/server functionality of Vim is handled by the backend.  It sets
27  * up a named NSConnection to which other Vim processes can connect.
28  */
30 #import "MMBackend.h"
34 // NOTE: Colors in MMBackend are stored as unsigned ints on the form 0xaarrggbb
35 // whereas colors in Vim are int without the alpha component.  Also note that
36 // 'transp' is assumed to be a value between 0 and 100.
37 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
38 #define MM_COLOR_WITH_TRANSP(col,transp) \
39     ((unsigned)( ((col)&0xffffff) \
40         | ((((unsigned)((((100-(transp))*255)/100)+.5f))&0xff)<<24) ))
42 // Values for window layout (must match values in main.c).
43 #define WIN_HOR     1       // "-o" horizontally split windows
44 #define WIN_VER     2       // "-O" vertically split windows
45 #define WIN_TABS    3       // "-p" windows on tab pages
47 static unsigned MMServerMax = 1000;
49 // TODO: Move to separate file.
50 static int eventModifierFlagsToVimModMask(int modifierFlags);
51 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
52 static int eventButtonNumberToVimMouseButton(int buttonNumber);
54 // Before exiting process, sleep for this many microseconds.  This is to allow
55 // any distributed object messages in transit to be received by MacVim before
56 // the process dies (otherwise an error message is logged by Cocoa).  Note that
57 // this delay is only necessary if an NSConnection to MacVim has been
58 // established.
59 static useconds_t MMExitProcessDelay = 300000;
61 // In gui_macvim.m
62 vimmenu_T *menu_for_descriptor(NSArray *desc);
64 static id evalExprCocoa(NSString * expr, NSString ** errstr);
67 enum {
68     MMBlinkStateNone = 0,
69     MMBlinkStateOn,
70     MMBlinkStateOff
73 static NSString *MMSymlinkWarningString =
74     @"\n\n\tMost likely this is because you have symlinked directly to\n"
75      "\tthe Vim binary, which Cocoa does not allow.  Please use an\n"
76      "\talias or the mvim shell script instead.  If you have not used\n"
77      "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
81 @interface NSString (MMServerNameCompare)
82 - (NSComparisonResult)serverNameCompare:(NSString *)string;
83 @end
88 @interface MMBackend (Private)
89 - (void)waitForDialogReturn;
90 - (void)insertVimStateMessage;
91 - (void)processInputQueue;
92 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
93 + (NSDictionary *)specialKeys;
94 - (void)handleInsertText:(NSString *)text;
95 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
96 - (void)queueMessage:(int)msgid data:(NSData *)data;
97 - (void)connectionDidDie:(NSNotification *)notification;
98 - (void)blinkTimerFired:(NSTimer *)timer;
99 - (void)focusChange:(BOOL)on;
100 - (void)handleToggleToolbar;
101 - (void)handleScrollbarEvent:(NSData *)data;
102 - (void)handleSetFont:(NSData *)data;
103 - (void)handleDropFiles:(NSData *)data;
104 - (void)handleDropString:(NSData *)data;
105 - (void)startOdbEditWithArguments:(NSDictionary *)args;
106 - (void)handleXcodeMod:(NSData *)data;
107 - (void)handleOpenWithArguments:(NSDictionary *)args;
108 - (BOOL)checkForModifiedBuffers;
109 - (void)addInput:(NSString *)input;
110 - (BOOL)unusedEditor;
111 - (void)redrawScreen;
112 - (void)handleFindReplace:(NSDictionary *)args;
113 @end
117 @interface MMBackend (ClientServer)
118 - (NSString *)connectionNameFromServerName:(NSString *)name;
119 - (NSConnection *)connectionForServerName:(NSString *)name;
120 - (NSConnection *)connectionForServerPort:(int)port;
121 - (void)serverConnectionDidDie:(NSNotification *)notification;
122 - (void)addClient:(NSDistantObject *)client;
123 - (NSString *)alternateServerNameForName:(NSString *)name;
124 @end
128 @implementation MMBackend
130 + (MMBackend *)sharedInstance
132     static MMBackend *singleton = nil;
133     return singleton ? singleton : (singleton = [MMBackend new]);
136 - (id)init
138     self = [super init];
139     if (!self) return nil;
141     fontContainerRef = loadFonts();
143     outputQueue = [[NSMutableArray alloc] init];
144     inputQueue = [[NSMutableArray alloc] init];
145     drawData = [[NSMutableData alloc] initWithCapacity:1024];
146     connectionNameDict = [[NSMutableDictionary alloc] init];
147     clientProxyDict = [[NSMutableDictionary alloc] init];
148     serverReplyDict = [[NSMutableDictionary alloc] init];
150     NSBundle *mainBundle = [NSBundle mainBundle];
151     NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
152     if (path)
153         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
155     path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
156     if (path)
157         sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
158             retain];
160     path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
161     if (path)
162         actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
164     if (!(colorDict && sysColorDict && actionDict))
165         NSLog(@"ERROR: Failed to load dictionaries.%@",
166                 MMSymlinkWarningString);
168     return self;
171 - (void)dealloc
173     //NSLog(@"%@ %s", [self className], _cmd);
174     [[NSNotificationCenter defaultCenter] removeObserver:self];
176     [oldWideFont release];  oldWideFont = nil;
177     [blinkTimer release];  blinkTimer = nil;
178     [alternateServerName release];  alternateServerName = nil;
179     [serverReplyDict release];  serverReplyDict = nil;
180     [clientProxyDict release];  clientProxyDict = nil;
181     [connectionNameDict release];  connectionNameDict = nil;
182     [inputQueue release];  inputQueue = nil;
183     [outputQueue release];  outputQueue = nil;
184     [drawData release];  drawData = nil;
185     [frontendProxy release];  frontendProxy = nil;
186     [connection release];  connection = nil;
187     [actionDict release];  actionDict = nil;
188     [sysColorDict release];  sysColorDict = nil;
189     [colorDict release];  colorDict = nil;
191     [super dealloc];
194 - (void)setBackgroundColor:(int)color
196     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
199 - (void)setForegroundColor:(int)color
201     foregroundColor = MM_COLOR(color);
204 - (void)setSpecialColor:(int)color
206     specialColor = MM_COLOR(color);
209 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
211     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
212     defaultForegroundColor = MM_COLOR(fg);
214     NSMutableData *data = [NSMutableData data];
216     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
217     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
219     [self queueMessage:SetDefaultColorsMsgID data:data];
222 - (NSConnection *)connection
224     if (!connection) {
225         // NOTE!  If the name of the connection changes here it must also be
226         // updated in MMAppController.m.
227         NSString *name = [NSString stringWithFormat:@"%@-connection",
228                [[NSBundle mainBundle] bundlePath]];
230         connection = [NSConnection connectionWithRegisteredName:name host:nil];
231         [connection retain];
232     }
234     // NOTE: 'connection' may be nil here.
235     return connection;
238 - (NSDictionary *)actionDict
240     return actionDict;
243 - (int)initialWindowLayout
245     return initialWindowLayout;
248 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
250     [self queueMessage:msgid data:[props dictionaryAsData]];
253 - (BOOL)checkin
255     if (![self connection]) {
256         if (waitForAck) {
257             // This is a preloaded process and as such should not cause the
258             // MacVim to be opened.  We probably got here as a result of the
259             // user quitting MacVim while the process was preloading, so exit
260             // this process too.
261             // (Don't use mch_exit() since it assumes the process has properly
262             // started.)
263             exit(0);
264         }
266         NSBundle *mainBundle = [NSBundle mainBundle];
267 #if 0
268         OSStatus status;
269         FSRef ref;
271         // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
272         // the API to pass Apple Event parameters is broken on 10.4).
273         NSString *path = [mainBundle bundlePath];
274         status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
275         if (noErr == status) {
276             // Pass parameter to the 'Open' Apple Event that tells MacVim not
277             // to open an untitled window.
278             NSAppleEventDescriptor *desc =
279                     [NSAppleEventDescriptor recordDescriptor];
280             [desc setParamDescriptor:
281                     [NSAppleEventDescriptor descriptorWithBoolean:NO]
282                           forKeyword:keyMMUntitledWindow];
284             LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
285                     kLSLaunchDefaults, NULL };
286             status = LSOpenFromRefSpec(&spec, NULL);
287         }
289         if (noErr != status) {
290         NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
291                 path, MMSymlinkWarningString);
292             return NO;
293         }
294 #else
295         // Launch MacVim using NSTask.  For some reason the above code using
296         // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
297         // fails, the dock icon starts bouncing and never stops).  It seems
298         // like rebuilding the Launch Services database takes care of this
299         // problem, but the NSTask way seems more stable so stick with it.
300         //
301         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
302         // that the GUI won't be activated (or raised) so there is a hack in
303         // MMAppController which raises the app when a new window is opened.
304         NSMutableArray *args = [NSMutableArray arrayWithObjects:
305             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
306         NSString *exeName = [[mainBundle infoDictionary]
307                 objectForKey:@"CFBundleExecutable"];
308         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
309         if (!path) {
310             NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
311                     MMSymlinkWarningString);
312             return NO;
313         }
315         [NSTask launchedTaskWithLaunchPath:path arguments:args];
316 #endif
318         // HACK!  Poll the mach bootstrap server until it returns a valid
319         // connection to detect that MacVim has finished launching.  Also set a
320         // time-out date so that we don't get stuck doing this forever.
321         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
322         while (![self connection] &&
323                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
324             [[NSRunLoop currentRunLoop]
325                     runMode:NSDefaultRunLoopMode
326                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
328         // NOTE: [self connection] will set 'connection' as a side-effect.
329         if (!connection) {
330             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
331             return NO;
332         }
333     }
335     BOOL ok = NO;
336     @try {
337         [[NSNotificationCenter defaultCenter] addObserver:self
338                 selector:@selector(connectionDidDie:)
339                     name:NSConnectionDidDieNotification object:connection];
341         id proxy = [connection rootProxy];
342         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
344         int pid = [[NSProcessInfo processInfo] processIdentifier];
346         frontendProxy = [proxy connectBackend:self pid:pid];
347         if (frontendProxy) {
348             [frontendProxy retain];
349             [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
350             ok = YES;
351         }
352     }
353     @catch (NSException *e) {
354         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
355     }
357     return ok;
360 - (BOOL)openGUIWindow
362     [self queueMessage:OpenWindowMsgID data:nil];
363     return YES;
366 - (void)clearAll
368     int type = ClearAllDrawType;
370     // Any draw commands in queue are effectively obsolete since this clearAll
371     // will negate any effect they have, therefore we may as well clear the
372     // draw queue.
373     [drawData setLength:0];
375     [drawData appendBytes:&type length:sizeof(int)];
378 - (void)clearBlockFromRow:(int)row1 column:(int)col1
379                     toRow:(int)row2 column:(int)col2
381     int type = ClearBlockDrawType;
383     [drawData appendBytes:&type length:sizeof(int)];
385     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
386     [drawData appendBytes:&row1 length:sizeof(int)];
387     [drawData appendBytes:&col1 length:sizeof(int)];
388     [drawData appendBytes:&row2 length:sizeof(int)];
389     [drawData appendBytes:&col2 length:sizeof(int)];
392 - (void)deleteLinesFromRow:(int)row count:(int)count
393               scrollBottom:(int)bottom left:(int)left right:(int)right
395     int type = DeleteLinesDrawType;
397     [drawData appendBytes:&type length:sizeof(int)];
399     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
400     [drawData appendBytes:&row length:sizeof(int)];
401     [drawData appendBytes:&count length:sizeof(int)];
402     [drawData appendBytes:&bottom length:sizeof(int)];
403     [drawData appendBytes:&left length:sizeof(int)];
404     [drawData appendBytes:&right length:sizeof(int)];
407 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
408              cells:(int)cells flags:(int)flags
410     if (len <= 0 || cells <= 0) return;
412     int type = DrawStringDrawType;
414     [drawData appendBytes:&type length:sizeof(int)];
416     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
417     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
418     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
419     [drawData appendBytes:&row length:sizeof(int)];
420     [drawData appendBytes:&col length:sizeof(int)];
421     [drawData appendBytes:&cells length:sizeof(int)];
422     [drawData appendBytes:&flags length:sizeof(int)];
423     [drawData appendBytes:&len length:sizeof(int)];
424     [drawData appendBytes:s length:len];
427 - (void)insertLinesFromRow:(int)row count:(int)count
428               scrollBottom:(int)bottom left:(int)left right:(int)right
430     int type = InsertLinesDrawType;
432     [drawData appendBytes:&type length:sizeof(int)];
434     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
435     [drawData appendBytes:&row length:sizeof(int)];
436     [drawData appendBytes:&count length:sizeof(int)];
437     [drawData appendBytes:&bottom length:sizeof(int)];
438     [drawData appendBytes:&left length:sizeof(int)];
439     [drawData appendBytes:&right length:sizeof(int)];
442 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
443                fraction:(int)percent color:(int)color
445     int type = DrawCursorDrawType;
446     unsigned uc = MM_COLOR(color);
448     [drawData appendBytes:&type length:sizeof(int)];
450     [drawData appendBytes:&uc length:sizeof(unsigned)];
451     [drawData appendBytes:&row length:sizeof(int)];
452     [drawData appendBytes:&col length:sizeof(int)];
453     [drawData appendBytes:&shape length:sizeof(int)];
454     [drawData appendBytes:&percent length:sizeof(int)];
457 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
458                    numColumns:(int)nc invert:(int)invert
460     int type = DrawInvertedRectDrawType;
461     [drawData appendBytes:&type length:sizeof(int)];
463     [drawData appendBytes:&row length:sizeof(int)];
464     [drawData appendBytes:&col length:sizeof(int)];
465     [drawData appendBytes:&nr length:sizeof(int)];
466     [drawData appendBytes:&nc length:sizeof(int)];
467     [drawData appendBytes:&invert length:sizeof(int)];
470 - (void)update
472     // Keep running the run-loop until there is no more input to process.
473     while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
474             == kCFRunLoopRunHandledSource)
475         ;
478 - (void)flushQueue:(BOOL)force
480     // NOTE: This variable allows for better control over when the queue is
481     // flushed.  It can be set to YES at the beginning of a sequence of calls
482     // that may potentially add items to the queue, and then restored back to
483     // NO.
484     if (flushDisabled) return;
486     if ([drawData length] > 0) {
487         // HACK!  Detect changes to 'guifontwide'.
488         if (gui.wide_font != (GuiFont)oldWideFont) {
489             [oldWideFont release];
490             oldWideFont = [(NSFont*)gui.wide_font retain];
491             [self setWideFont:oldWideFont];
492         }
494         int type = SetCursorPosDrawType;
495         [drawData appendBytes:&type length:sizeof(type)];
496         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
497         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
499         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
500         [drawData setLength:0];
501     }
503     if ([outputQueue count] > 0) {
504         [self insertVimStateMessage];
506         @try {
507             [frontendProxy processCommandQueue:outputQueue];
508         }
509         @catch (NSException *e) {
510             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
511         }
513         [outputQueue removeAllObjects];
514     }
517 - (BOOL)waitForInput:(int)milliseconds
519     // Return NO if we timed out waiting for input, otherwise return YES.
520     BOOL inputReceived = NO;
522     // Only start the run loop if the input queue is empty, otherwise process
523     // the input first so that the input on queue isn't delayed.
524     if ([inputQueue count]) {
525         inputReceived = YES;
526     } else {
527         // Wait for the specified amount of time, unless 'milliseconds' is
528         // negative in which case we wait "forever" (1e6 seconds translates to
529         // approximately 11 days).
530         CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
532         while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
533                 == kCFRunLoopRunHandledSource) {
534             // In order to ensure that all input on the run-loop has been
535             // processed we set the timeout to 0 and keep processing until the
536             // run-loop times out.
537             dt = 0.0;
538             inputReceived = YES;
539         }
540     }
542     // The above calls may have placed messages on the input queue so process
543     // it now.  This call may enter a blocking loop.
544     if ([inputQueue count] > 0)
545         [self processInputQueue];
547     return inputReceived;
550 - (void)exit
552     // NOTE: This is called if mch_exit() is called.  Since we assume here that
553     // the process has started properly, be sure to use exit() instead of
554     // mch_exit() to prematurely terminate a process.
556     // To notify MacVim that this Vim process is exiting we could simply
557     // invalidate the connection and it would automatically receive a
558     // connectionDidDie: notification.  However, this notification seems to
559     // take up to 300 ms to arrive which is quite a noticeable delay.  Instead
560     // we immediately send a message to MacVim asking it to close the window
561     // belonging to this process, and then we invalidate the connection (in
562     // case the message got lost).
564     // Make sure no connectionDidDie: notification is received now that we are
565     // already exiting.
566     [[NSNotificationCenter defaultCenter] removeObserver:self];
568     if ([connection isValid]) {
569         @try {
570             // Flush the entire queue in case a VimLeave autocommand added
571             // something to the queue.
572             [self queueMessage:CloseWindowMsgID data:nil];
573             [frontendProxy processCommandQueue:outputQueue];
574         }
575         @catch (NSException *e) {
576             NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
577         }
579         [connection invalidate];
580     }
582 #ifdef MAC_CLIENTSERVER
583     // The default connection is used for the client/server code.
584     [[NSConnection defaultConnection] setRootObject:nil];
585     [[NSConnection defaultConnection] invalidate];
586 #endif
588     if (fontContainerRef) {
589         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
590         fontContainerRef = 0;
591     }
593     usleep(MMExitProcessDelay);
596 - (void)selectTab:(int)index
598     //NSLog(@"%s%d", _cmd, index);
600     index -= 1;
601     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
602     [self queueMessage:SelectTabMsgID data:data];
605 - (void)updateTabBar
607     //NSLog(@"%s", _cmd);
609     NSMutableData *data = [NSMutableData data];
611     int idx = tabpage_index(curtab) - 1;
612     [data appendBytes:&idx length:sizeof(int)];
614     tabpage_T *tp;
615     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
616         // This function puts the label of the tab in the global 'NameBuff'.
617         get_tabline_label(tp, FALSE);
618         char_u *s = NameBuff;
619         int len = STRLEN(s);
620         if (len <= 0) continue;
622 #ifdef FEAT_MBYTE
623         s = CONVERT_TO_UTF8(s);
624 #endif
626         // Count the number of windows in the tabpage.
627         //win_T *wp = tp->tp_firstwin;
628         //int wincount;
629         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
631         //[data appendBytes:&wincount length:sizeof(int)];
632         [data appendBytes:&len length:sizeof(int)];
633         [data appendBytes:s length:len];
635 #ifdef FEAT_MBYTE
636         CONVERT_TO_UTF8_FREE(s);
637 #endif
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     @try {
695         [frontendProxy showSavePanelWithAttributes:attr];
697         [self waitForDialogReturn];
699         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
700             s = [dialogReturn vimStringSave];
702         [dialogReturn release];  dialogReturn = nil;
703     }
704     @catch (NSException *e) {
705         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
706     }
708     return (char *)s;
711 - (oneway void)setDialogReturn:(in bycopy id)obj
713     // NOTE: This is called by
714     //   - [MMVimController panelDidEnd:::], and
715     //   - [MMVimController alertDidEnd:::],
716     // to indicate that a save/open panel or alert has finished.
718     // We want to distinguish between "no dialog return yet" and "dialog
719     // returned nothing".  The former can be tested with dialogReturn == nil,
720     // the latter with dialogReturn == [NSNull null].
721     if (!obj) obj = [NSNull null];
723     if (obj != dialogReturn) {
724         [dialogReturn release];
725         dialogReturn = [obj retain];
726     }
729 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
731     int retval = 0;
733     @try {
734         [frontendProxy presentDialogWithAttributes:attr];
736         [self waitForDialogReturn];
738         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
739                 && [dialogReturn count]) {
740             retval = [[dialogReturn objectAtIndex:0] intValue];
741             if (txtfield && [dialogReturn count] > 1) {
742                 NSString *retString = [dialogReturn objectAtIndex:1];
743                 char_u *ret = (char_u*)[retString UTF8String];
744 #ifdef FEAT_MBYTE
745                 ret = CONVERT_FROM_UTF8(ret);
746 #endif
747                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
748 #ifdef FEAT_MBYTE
749                 CONVERT_FROM_UTF8_FREE(ret);
750 #endif
751             }
752         }
754         [dialogReturn release]; dialogReturn = nil;
755     }
756     @catch (NSException *e) {
757         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
758     }
760     return retval;
763 - (void)showToolbar:(int)enable flags:(int)flags
765     NSMutableData *data = [NSMutableData data];
767     [data appendBytes:&enable length:sizeof(int)];
768     [data appendBytes:&flags length:sizeof(int)];
770     [self queueMessage:ShowToolbarMsgID data:data];
773 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
775     NSMutableData *data = [NSMutableData data];
777     [data appendBytes:&ident length:sizeof(long)];
778     [data appendBytes:&type length:sizeof(int)];
780     [self queueMessage:CreateScrollbarMsgID data:data];
783 - (void)destroyScrollbarWithIdentifier:(long)ident
785     NSMutableData *data = [NSMutableData data];
786     [data appendBytes:&ident length:sizeof(long)];
788     [self queueMessage:DestroyScrollbarMsgID data:data];
791 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
793     NSMutableData *data = [NSMutableData data];
795     [data appendBytes:&ident length:sizeof(long)];
796     [data appendBytes:&visible length:sizeof(int)];
798     [self queueMessage:ShowScrollbarMsgID data:data];
801 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
803     NSMutableData *data = [NSMutableData data];
805     [data appendBytes:&ident length:sizeof(long)];
806     [data appendBytes:&pos length:sizeof(int)];
807     [data appendBytes:&len length:sizeof(int)];
809     [self queueMessage:SetScrollbarPositionMsgID data:data];
812 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
813                     identifier:(long)ident
815     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
816     float prop = (float)size/(max+1);
817     if (fval < 0) fval = 0;
818     else if (fval > 1.0f) fval = 1.0f;
819     if (prop < 0) prop = 0;
820     else if (prop > 1.0f) prop = 1.0f;
822     NSMutableData *data = [NSMutableData data];
824     [data appendBytes:&ident length:sizeof(long)];
825     [data appendBytes:&fval length:sizeof(float)];
826     [data appendBytes:&prop length:sizeof(float)];
828     [self queueMessage:SetScrollbarThumbMsgID data:data];
831 - (void)setFont:(NSFont *)font
833     NSString *fontName = [font displayName];
834     float size = [font pointSize];
835     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
836     if (len > 0) {
837         NSMutableData *data = [NSMutableData data];
839         [data appendBytes:&size length:sizeof(float)];
840         [data appendBytes:&len length:sizeof(int)];
841         [data appendBytes:[fontName UTF8String] length:len];
843         [self queueMessage:SetFontMsgID data:data];
844     }
847 - (void)setWideFont:(NSFont *)font
849     NSString *fontName = [font displayName];
850     float size = [font pointSize];
851     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
852     NSMutableData *data = [NSMutableData data];
854     [data appendBytes:&size length:sizeof(float)];
855     [data appendBytes:&len length:sizeof(int)];
856     if (len > 0)
857         [data appendBytes:[fontName UTF8String] length:len];
859     [self queueMessage:SetWideFontMsgID data:data];
862 - (void)executeActionWithName:(NSString *)name
864     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
866     if (len > 0) {
867         NSMutableData *data = [NSMutableData data];
869         [data appendBytes:&len length:sizeof(int)];
870         [data appendBytes:[name UTF8String] length:len];
872         [self queueMessage:ExecuteActionMsgID data:data];
873     }
876 - (void)setMouseShape:(int)shape
878     NSMutableData *data = [NSMutableData data];
879     [data appendBytes:&shape length:sizeof(int)];
880     [self queueMessage:SetMouseShapeMsgID data:data];
883 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
885     // Vim specifies times in milliseconds, whereas Cocoa wants them in
886     // seconds.
887     blinkWaitInterval = .001f*wait;
888     blinkOnInterval = .001f*on;
889     blinkOffInterval = .001f*off;
892 - (void)startBlink
894     if (blinkTimer) {
895         [blinkTimer invalidate];
896         [blinkTimer release];
897         blinkTimer = nil;
898     }
900     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
901             && gui.in_focus) {
902         blinkState = MMBlinkStateOn;
903         blinkTimer =
904             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
905                                               target:self
906                                             selector:@selector(blinkTimerFired:)
907                                             userInfo:nil repeats:NO] retain];
908         gui_update_cursor(TRUE, FALSE);
909         [self flushQueue:YES];
910     }
913 - (void)stopBlink
915     if (MMBlinkStateOff == blinkState) {
916         gui_update_cursor(TRUE, FALSE);
917         [self flushQueue:YES];
918     }
920     blinkState = MMBlinkStateNone;
923 - (void)adjustLinespace:(int)linespace
925     NSMutableData *data = [NSMutableData data];
926     [data appendBytes:&linespace length:sizeof(int)];
927     [self queueMessage:AdjustLinespaceMsgID data:data];
930 - (void)activate
932     [self queueMessage:ActivateMsgID data:nil];
935 - (void)setPreEditRow:(int)row column:(int)col
937     NSMutableData *data = [NSMutableData data];
938     [data appendBytes:&row length:sizeof(int)];
939     [data appendBytes:&col length:sizeof(int)];
940     [self queueMessage:SetPreEditPositionMsgID data:data];
943 - (int)lookupColorWithKey:(NSString *)key
945     if (!(key && [key length] > 0))
946         return INVALCOLOR;
948     NSString *stripKey = [[[[key lowercaseString]
949         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
950             componentsSeparatedByString:@" "]
951                componentsJoinedByString:@""];
953     if (stripKey && [stripKey length] > 0) {
954         // First of all try to lookup key in the color dictionary; note that
955         // all keys in this dictionary are lowercase with no whitespace.
956         id obj = [colorDict objectForKey:stripKey];
957         if (obj) return [obj intValue];
959         // The key was not in the dictionary; is it perhaps of the form
960         // #rrggbb?
961         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
962             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
963             [scanner setScanLocation:1];
964             unsigned hex = 0;
965             if ([scanner scanHexInt:&hex]) {
966                 return (int)hex;
967             }
968         }
970         // As a last resort, check if it is one of the system defined colors.
971         // The keys in this dictionary are also lowercase with no whitespace.
972         obj = [sysColorDict objectForKey:stripKey];
973         if (obj) {
974             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
975             if (col) {
976                 float r, g, b, a;
977                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
978                 [col getRed:&r green:&g blue:&b alpha:&a];
979                 return (((int)(r*255+.5f) & 0xff) << 16)
980                      + (((int)(g*255+.5f) & 0xff) << 8)
981                      +  ((int)(b*255+.5f) & 0xff);
982             }
983         }
984     }
986     //NSLog(@"WARNING: No color with key %@ found.", stripKey);
987     return INVALCOLOR;
990 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
992     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
993     id obj;
995     while ((obj = [e nextObject])) {
996         if ([value isEqual:obj])
997             return YES;
998     }
1000     return NO;
1003 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1005     NSMutableData *data = [NSMutableData data];
1006     [data appendBytes:&fuoptions length:sizeof(int)];
1007     bg = MM_COLOR(bg);
1008     [data appendBytes:&bg length:sizeof(int)];
1009     [self queueMessage:EnterFullscreenMsgID data:data];
1012 - (void)leaveFullscreen
1014     [self queueMessage:LeaveFullscreenMsgID data:nil];
1017 - (void)setFullscreenBackgroundColor:(int)color
1019     NSMutableData *data = [NSMutableData data];
1020     color = MM_COLOR(color);
1021     [data appendBytes:&color length:sizeof(int)];
1023     [self queueMessage:SetFullscreenColorMsgID data:data];
1026 - (void)setAntialias:(BOOL)antialias
1028     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1030     [self queueMessage:msgid data:nil];
1033 - (void)updateModifiedFlag
1035     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1036     // vice versa.
1037     int msgid = [self checkForModifiedBuffers]
1038             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1040     [self queueMessage:msgid data:nil];
1043 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1045     // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1046     // queue is processed since that only happens in waitForInput: (and Vim
1047     // regularly checks for Ctrl-C in between waiting for input).
1049     BOOL interrupt = NO;
1050     if (msgid == InterruptMsgID) {
1051         interrupt = YES;
1052     } else if (InsertTextMsgID == msgid && data != nil) {
1053         const void *bytes = [data bytes];
1054         bytes += sizeof(int);
1055         int len = *((int*)bytes);  bytes += sizeof(int);
1056         if (1 == len) {
1057             char_u *str = (char_u*)bytes;
1058             if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1059                     (str[0] == intr_char && intr_char != Ctrl_C))
1060                 interrupt = YES;
1061         }
1062     }
1064     if (interrupt) {
1065         got_int = TRUE;
1066         [inputQueue removeAllObjects];
1067         return;
1068     }
1070     // Remove all previous instances of this message from the input queue, else
1071     // the input queue may fill up as a result of Vim not being able to keep up
1072     // with the speed at which new messages are received.
1073     // Keyboard input is never dropped, unless the input represents and
1074     // auto-repeated key.
1076     BOOL isKeyRepeat = NO;
1077     BOOL isKeyboardInput = NO;
1079     if (data && (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1080             CmdKeyMsgID == msgid)) {
1081         isKeyboardInput = YES;
1083         // The lowest bit of the first int is set if this key is a repeat.
1084         int flags = *((int*)[data bytes]);
1085         if (flags & 1)
1086             isKeyRepeat = YES;
1087     }
1089     // Keyboard input is not removed from the queue; repeats are ignored if
1090     // there already is keyboard input on the input queue.
1091     if (isKeyRepeat || !isKeyboardInput) {
1092         int i, count = [inputQueue count];
1093         for (i = 1; i < count; i+=2) {
1094             if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1095                 if (isKeyRepeat)
1096                     return;
1098                 [inputQueue removeObjectAtIndex:i];
1099                 [inputQueue removeObjectAtIndex:i-1];
1100                 break;
1101             }
1102         }
1103     }
1105     [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1106     [inputQueue addObject:(data ? (id)data : [NSNull null])];
1109 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1111     // This is just a convenience method that allows the frontend to delay
1112     // sending messages.
1113     int i, count = [messages count];
1114     for (i = 1; i < count; i+=2)
1115         [self processInput:[[messages objectAtIndex:i-1] intValue]
1116                       data:[messages objectAtIndex:i]];
1119 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1120                   errorString:(out bycopy NSString **)errstr
1122     return evalExprCocoa(expr, errstr);
1126 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1128     NSString *eval = nil;
1129     char_u *s = (char_u*)[expr UTF8String];
1131 #ifdef FEAT_MBYTE
1132     s = CONVERT_FROM_UTF8(s);
1133 #endif
1135     char_u *res = eval_client_expr_to_string(s);
1137 #ifdef FEAT_MBYTE
1138     CONVERT_FROM_UTF8_FREE(s);
1139 #endif
1141     if (res != NULL) {
1142         s = res;
1143 #ifdef FEAT_MBYTE
1144         s = CONVERT_TO_UTF8(s);
1145 #endif
1146         eval = [NSString stringWithUTF8String:(char*)s];
1147 #ifdef FEAT_MBYTE
1148         CONVERT_TO_UTF8_FREE(s);
1149 #endif
1150         vim_free(res);
1151     }
1153     return eval;
1156 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1158     // TODO: This method should share code with clip_mch_request_selection().
1160     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1161         // If there is no pasteboard, return YES to indicate that there is text
1162         // to copy.
1163         if (!pboard)
1164             return YES;
1166         clip_copy_selection();
1168         // Get the text to put on the pasteboard.
1169         long_u llen = 0; char_u *str = 0;
1170         int type = clip_convert_selection(&str, &llen, &clip_star);
1171         if (type < 0)
1172             return NO;
1173         
1174         // TODO: Avoid overflow.
1175         int len = (int)llen;
1176 #ifdef FEAT_MBYTE
1177         if (output_conv.vc_type != CONV_NONE) {
1178             char_u *conv_str = string_convert(&output_conv, str, &len);
1179             if (conv_str) {
1180                 vim_free(str);
1181                 str = conv_str;
1182             }
1183         }
1184 #endif
1186         NSString *string = [[NSString alloc]
1187             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1189         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1190         [pboard declareTypes:types owner:nil];
1191         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1192     
1193         [string release];
1194         vim_free(str);
1196         return ok;
1197     }
1199     return NO;
1202 - (oneway void)addReply:(in bycopy NSString *)reply
1203                  server:(in byref id <MMVimServerProtocol>)server
1205     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1207     // Replies might come at any time and in any order so we keep them in an
1208     // array inside a dictionary with the send port used as key.
1210     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1211     // HACK! Assume connection uses mach ports.
1212     int port = [(NSMachPort*)[conn sendPort] machPort];
1213     NSNumber *key = [NSNumber numberWithInt:port];
1215     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1216     if (!replies) {
1217         replies = [NSMutableArray array];
1218         [serverReplyDict setObject:replies forKey:key];
1219     }
1221     [replies addObject:reply];
1224 - (void)addInput:(in bycopy NSString *)input
1225                  client:(in byref id <MMVimClientProtocol>)client
1227     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1229     [self addInput:input];
1230     [self addClient:(id)client];
1233 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1234                  client:(in byref id <MMVimClientProtocol>)client
1236     [self addClient:(id)client];
1237     return [self evaluateExpression:expr];
1240 - (void)registerServerWithName:(NSString *)name
1242     NSString *svrName = name;
1243     NSConnection *svrConn = [NSConnection defaultConnection];
1244     unsigned i;
1246     for (i = 0; i < MMServerMax; ++i) {
1247         NSString *connName = [self connectionNameFromServerName:svrName];
1249         if ([svrConn registerName:connName]) {
1250             //NSLog(@"Registered server with name: %@", svrName);
1252             // TODO: Set request/reply time-outs to something else?
1253             //
1254             // Don't wait for requests (time-out means that the message is
1255             // dropped).
1256             [svrConn setRequestTimeout:0];
1257             //[svrConn setReplyTimeout:MMReplyTimeout];
1258             [svrConn setRootObject:self];
1260             // NOTE: 'serverName' is a global variable
1261             serverName = [svrName vimStringSave];
1262 #ifdef FEAT_EVAL
1263             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1264 #endif
1265 #ifdef FEAT_TITLE
1266             need_maketitle = TRUE;
1267 #endif
1268             [self queueMessage:SetServerNameMsgID data:
1269                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1270             break;
1271         }
1273         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1274     }
1277 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1278                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1279               silent:(BOOL)silent
1281     // NOTE: If 'name' equals 'serverName' then the request is local (client
1282     // and server are the same).  This case is not handled separately, so a
1283     // connection will be set up anyway (this simplifies the code).
1285     NSConnection *conn = [self connectionForServerName:name];
1286     if (!conn) {
1287         if (!silent) {
1288             char_u *s = (char_u*)[name UTF8String];
1289 #ifdef FEAT_MBYTE
1290             s = CONVERT_FROM_UTF8(s);
1291 #endif
1292             EMSG2(_(e_noserver), s);
1293 #ifdef FEAT_MBYTE
1294             CONVERT_FROM_UTF8_FREE(s);
1295 #endif
1296         }
1297         return NO;
1298     }
1300     if (port) {
1301         // HACK! Assume connection uses mach ports.
1302         *port = [(NSMachPort*)[conn sendPort] machPort];
1303     }
1305     id proxy = [conn rootProxy];
1306     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1308     @try {
1309         if (expr) {
1310             NSString *eval = [proxy evaluateExpression:string client:self];
1311             if (reply) {
1312                 if (eval) {
1313                     *reply = [eval vimStringSave];
1314                 } else {
1315                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1316                 }
1317             }
1319             if (!eval)
1320                 return NO;
1321         } else {
1322             [proxy addInput:string client:self];
1323         }
1324     }
1325     @catch (NSException *e) {
1326         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1327         return NO;
1328     }
1330     return YES;
1333 - (NSArray *)serverList
1335     NSArray *list = nil;
1337     if ([self connection]) {
1338         id proxy = [connection rootProxy];
1339         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1341         @try {
1342             list = [proxy serverList];
1343         }
1344         @catch (NSException *e) {
1345             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1346         }
1347     } else {
1348         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1349     }
1351     return list;
1354 - (NSString *)peekForReplyOnPort:(int)port
1356     //NSLog(@"%s%d", _cmd, port);
1358     NSNumber *key = [NSNumber numberWithInt:port];
1359     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1360     if (replies && [replies count]) {
1361         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1362         //        [replies objectAtIndex:0]);
1363         return [replies objectAtIndex:0];
1364     }
1366     //NSLog(@"    No replies");
1367     return nil;
1370 - (NSString *)waitForReplyOnPort:(int)port
1372     //NSLog(@"%s%d", _cmd, port);
1373     
1374     NSConnection *conn = [self connectionForServerPort:port];
1375     if (!conn)
1376         return nil;
1378     NSNumber *key = [NSNumber numberWithInt:port];
1379     NSMutableArray *replies = nil;
1380     NSString *reply = nil;
1382     // Wait for reply as long as the connection to the server is valid (unless
1383     // user interrupts wait with Ctrl-C).
1384     while (!got_int && [conn isValid] &&
1385             !(replies = [serverReplyDict objectForKey:key])) {
1386         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1387                                  beforeDate:[NSDate distantFuture]];
1388     }
1390     if (replies) {
1391         if ([replies count] > 0) {
1392             reply = [[replies objectAtIndex:0] retain];
1393             //NSLog(@"    Got reply: %@", reply);
1394             [replies removeObjectAtIndex:0];
1395             [reply autorelease];
1396         }
1398         if ([replies count] == 0)
1399             [serverReplyDict removeObjectForKey:key];
1400     }
1402     return reply;
1405 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1407     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1408     if (client) {
1409         @try {
1410             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1411             [client addReply:reply server:self];
1412             return YES;
1413         }
1414         @catch (NSException *e) {
1415             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1416         }
1417     } else {
1418         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1419     }
1421     return NO;
1424 - (BOOL)waitForAck
1426     return waitForAck;
1429 - (void)setWaitForAck:(BOOL)yn
1431     waitForAck = yn;
1434 - (void)waitForConnectionAcknowledgement
1436     if (!waitForAck) return;
1438     while (waitForAck && !got_int && [connection isValid] && !isTerminating) {
1439         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1440                                  beforeDate:[NSDate distantFuture]];
1441         //NSLog(@"  waitForAck=%d got_int=%d isTerminating=%d isValid=%d",
1442         //        waitForAck, got_int, isTerminating, [connection isValid]);
1443     }
1445     if (waitForAck) {
1446         // Never received a connection acknowledgement, so die.
1447         [[NSNotificationCenter defaultCenter] removeObserver:self];
1448         [frontendProxy release];  frontendProxy = nil;
1450         // NOTE: We intentionally do not call mch_exit() since this in turn
1451         // will lead to -[MMBackend exit] getting called which we want to
1452         // avoid.
1453         usleep(MMExitProcessDelay);
1454         exit(0);
1455     }
1457     [self processInputQueue];
1460 - (oneway void)acknowledgeConnection
1462     //NSLog(@"%s", _cmd);
1463     waitForAck = NO;
1466 @end // MMBackend
1470 @implementation MMBackend (Private)
1472 - (void)waitForDialogReturn
1474     // Keep processing the run loop until a dialog returns.  To avoid getting
1475     // stuck in an endless loop (could happen if the setDialogReturn: message
1476     // was lost) we also do some paranoia checks.
1477     //
1478     // Note that in Cocoa the user can still resize windows and select menu
1479     // items while a sheet is being displayed, so we can't just wait for the
1480     // first message to arrive and assume that is the setDialogReturn: call.
1482     while (nil == dialogReturn && !got_int && [connection isValid]
1483             && !isTerminating)
1484         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1485                                  beforeDate:[NSDate distantFuture]];
1487     // Search for any resize messages on the input queue.  All other messages
1488     // on the input queue are dropped.  The reason why we single out resize
1489     // messages is because the user may have resized the window while a sheet
1490     // was open.
1491     int i, count = [inputQueue count];
1492     if (count > 0) {
1493         id textDimData = nil;
1494         if (count%2 == 0) {
1495             for (i = count-2; i >= 0; i -= 2) {
1496                 int msgid = [[inputQueue objectAtIndex:i] intValue];
1497                 if (SetTextDimensionsMsgID == msgid) {
1498                     textDimData = [[inputQueue objectAtIndex:i+1] retain];
1499                     break;
1500                 }
1501             }
1502         }
1504         [inputQueue removeAllObjects];
1506         if (textDimData) {
1507             [inputQueue addObject:
1508                     [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1509             [inputQueue addObject:textDimData];
1510             [textDimData release];
1511         }
1512     }
1515 - (void)insertVimStateMessage
1517     // NOTE: This is the place to add Vim state that needs to be accessed from
1518     // MacVim.  Do not add state that could potentially require lots of memory
1519     // since this message gets sent each time the output queue is forcibly
1520     // flushed (e.g. storing the currently selected text would be a bad idea).
1521     // We take this approach of "pushing" the state to MacVim to avoid having
1522     // to make synchronous calls from MacVim to Vim in order to get state.
1524     BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1526     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1527         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1528         [NSNumber numberWithInt:p_mh], @"p_mh",
1529         [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1530         [NSNumber numberWithBool:mmta], @"p_mmta",
1531         nil];
1533     // Put the state before all other messages.
1534     int msgid = SetVimStateMsgID;
1535     [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1536     [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1537                       atIndex:0];
1540 - (void)processInputQueue
1542     if ([inputQueue count] == 0) return;
1544     // NOTE: One of the input events may cause this method to be called
1545     // recursively, so copy the input queue to a local variable and clear the
1546     // queue before starting to process input events (otherwise we could get
1547     // stuck in an endless loop).
1548     NSArray *q = [inputQueue copy];
1549     unsigned i, count = [q count];
1551     [inputQueue removeAllObjects];
1553     for (i = 1; i < count; i+=2) {
1554         int msgid = [[q objectAtIndex:i-1] intValue];
1555         id data = [q objectAtIndex:i];
1556         if ([data isEqual:[NSNull null]])
1557             data = nil;
1559         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1560         [self handleInputEvent:msgid data:data];
1561     }
1563     [q release];
1564     //NSLog(@"Clear input event queue");
1567 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1569     if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1570             CmdKeyMsgID == msgid) {
1571         if (!data) return;
1572         const void *bytes = [data bytes];
1573         int mods = *((int*)bytes);  bytes += sizeof(int);
1574         int len = *((int*)bytes);  bytes += sizeof(int);
1575         NSString *key = [[NSString alloc] initWithBytes:bytes
1576                                                  length:len
1577                                                encoding:NSUTF8StringEncoding];
1578         mods = eventModifierFlagsToVimModMask(mods);
1580         if (InsertTextMsgID == msgid)
1581             [self handleInsertText:key];
1582         else
1583             [self handleKeyDown:key modifiers:mods];
1585         [key release];
1586     } else if (ScrollWheelMsgID == msgid) {
1587         if (!data) return;
1588         const void *bytes = [data bytes];
1590         int row = *((int*)bytes);  bytes += sizeof(int);
1591         int col = *((int*)bytes);  bytes += sizeof(int);
1592         int flags = *((int*)bytes);  bytes += sizeof(int);
1593         float dy = *((float*)bytes);  bytes += sizeof(float);
1595         int button = MOUSE_5;
1596         if (dy > 0) button = MOUSE_4;
1598         flags = eventModifierFlagsToVimMouseModMask(flags);
1600         int numLines = (int)round(dy);
1601         if (numLines < 0) numLines = -numLines;
1602         if (numLines == 0) numLines = 1;
1604 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1605         gui.scroll_wheel_force = numLines;
1606 #endif
1608         gui_send_mouse_event(button, col, row, NO, flags);
1609     } else if (MouseDownMsgID == msgid) {
1610         if (!data) return;
1611         const void *bytes = [data bytes];
1613         int row = *((int*)bytes);  bytes += sizeof(int);
1614         int col = *((int*)bytes);  bytes += sizeof(int);
1615         int button = *((int*)bytes);  bytes += sizeof(int);
1616         int flags = *((int*)bytes);  bytes += sizeof(int);
1617         int count = *((int*)bytes);  bytes += sizeof(int);
1619         button = eventButtonNumberToVimMouseButton(button);
1620         if (button >= 0) {
1621             flags = eventModifierFlagsToVimMouseModMask(flags);
1622             gui_send_mouse_event(button, col, row, count>1, flags);
1623         }
1624     } else if (MouseUpMsgID == msgid) {
1625         if (!data) return;
1626         const void *bytes = [data bytes];
1628         int row = *((int*)bytes);  bytes += sizeof(int);
1629         int col = *((int*)bytes);  bytes += sizeof(int);
1630         int flags = *((int*)bytes);  bytes += sizeof(int);
1632         flags = eventModifierFlagsToVimMouseModMask(flags);
1634         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1635     } else if (MouseDraggedMsgID == msgid) {
1636         if (!data) return;
1637         const void *bytes = [data bytes];
1639         int row = *((int*)bytes);  bytes += sizeof(int);
1640         int col = *((int*)bytes);  bytes += sizeof(int);
1641         int flags = *((int*)bytes);  bytes += sizeof(int);
1643         flags = eventModifierFlagsToVimMouseModMask(flags);
1645         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1646     } else if (MouseMovedMsgID == msgid) {
1647         const void *bytes = [data bytes];
1648         int row = *((int*)bytes);  bytes += sizeof(int);
1649         int col = *((int*)bytes);  bytes += sizeof(int);
1651         gui_mouse_moved(col, row);
1652     } else if (AddInputMsgID == msgid) {
1653         NSString *string = [[NSString alloc] initWithData:data
1654                 encoding:NSUTF8StringEncoding];
1655         if (string) {
1656             [self addInput:string];
1657             [string release];
1658         }
1659     } else if (TerminateNowMsgID == msgid) {
1660         isTerminating = YES;
1661     } else if (SelectTabMsgID == msgid) {
1662         if (!data) return;
1663         const void *bytes = [data bytes];
1664         int idx = *((int*)bytes) + 1;
1665         //NSLog(@"Selecting tab %d", idx);
1666         send_tabline_event(idx);
1667     } else if (CloseTabMsgID == msgid) {
1668         if (!data) return;
1669         const void *bytes = [data bytes];
1670         int idx = *((int*)bytes) + 1;
1671         //NSLog(@"Closing tab %d", idx);
1672         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1673     } else if (AddNewTabMsgID == msgid) {
1674         //NSLog(@"Adding new tab");
1675         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1676     } else if (DraggedTabMsgID == msgid) {
1677         if (!data) return;
1678         const void *bytes = [data bytes];
1679         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1680         // based.
1681         int idx = *((int*)bytes);
1683         tabpage_move(idx);
1684     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1685             || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1686         if (!data) return;
1687         const void *bytes = [data bytes];
1688         int rows = Rows;
1689         if (SetTextColumnsMsgID != msgid) {
1690             rows = *((int*)bytes);  bytes += sizeof(int);
1691         }
1692         int cols = Columns;
1693         if (SetTextRowsMsgID != msgid) {
1694             cols = *((int*)bytes);  bytes += sizeof(int);
1695         }
1697         NSData *d = data;
1698         if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1699             int dim[2] = { rows, cols };
1700             d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1701             msgid = SetTextDimensionsReplyMsgID;
1702         }
1704         if (SetTextDimensionsMsgID == msgid)
1705             msgid = SetTextDimensionsReplyMsgID;
1707         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1708         // gui_resize_shell(), so we have to manually set the rows and columns
1709         // here since MacVim doesn't change the rows and columns to avoid
1710         // inconsistent states between Vim and MacVim.  The message sent back
1711         // indicates that it is a reply to a message that originated in MacVim
1712         // since we need to be able to determine where a message originated.
1713         [self queueMessage:msgid data:d];
1715         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1716         gui_resize_shell(cols, rows);
1717     } else if (ExecuteMenuMsgID == msgid) {
1718         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1719         if (attrs) {
1720             NSArray *desc = [attrs objectForKey:@"descriptor"];
1721             vimmenu_T *menu = menu_for_descriptor(desc);
1722             if (menu)
1723                 gui_menu_cb(menu);
1724         }
1725     } else if (ToggleToolbarMsgID == msgid) {
1726         [self handleToggleToolbar];
1727     } else if (ScrollbarEventMsgID == msgid) {
1728         [self handleScrollbarEvent:data];
1729     } else if (SetFontMsgID == msgid) {
1730         [self handleSetFont:data];
1731     } else if (VimShouldCloseMsgID == msgid) {
1732         gui_shell_closed();
1733     } else if (DropFilesMsgID == msgid) {
1734         [self handleDropFiles:data];
1735     } else if (DropStringMsgID == msgid) {
1736         [self handleDropString:data];
1737     } else if (GotFocusMsgID == msgid) {
1738         if (!gui.in_focus)
1739             [self focusChange:YES];
1740     } else if (LostFocusMsgID == msgid) {
1741         if (gui.in_focus)
1742             [self focusChange:NO];
1743     } else if (SetMouseShapeMsgID == msgid) {
1744         const void *bytes = [data bytes];
1745         int shape = *((int*)bytes);  bytes += sizeof(int);
1746         update_mouseshape(shape);
1747     } else if (XcodeModMsgID == msgid) {
1748         [self handleXcodeMod:data];
1749     } else if (OpenWithArgumentsMsgID == msgid) {
1750         [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1751     } else if (FindReplaceMsgID == msgid) {
1752         [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1753     } else {
1754         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1755     }
1758 + (NSDictionary *)specialKeys
1760     static NSDictionary *specialKeys = nil;
1762     if (!specialKeys) {
1763         NSBundle *mainBundle = [NSBundle mainBundle];
1764         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1765                                               ofType:@"plist"];
1766         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1767     }
1769     return specialKeys;
1772 - (void)handleInsertText:(NSString *)text
1774     if (!text) return;
1776     char_u *str = (char_u*)[text UTF8String];
1777     int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1779 #ifdef FEAT_MBYTE
1780     char_u *conv_str = NULL;
1781     if (input_conv.vc_type != CONV_NONE) {
1782         conv_str = string_convert(&input_conv, str, &len);
1783         if (conv_str)
1784             str = conv_str;
1785     }
1786 #endif
1788     for (i = 0; i < len; ++i) {
1789         add_to_input_buf(str+i, 1);
1790         if (CSI == str[i]) {
1791             // NOTE: If the converted string contains the byte CSI, then it
1792             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1793             // won't work.
1794             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1795             add_to_input_buf(extra, 2);
1796         }
1797     }
1799 #ifdef FEAT_MBYTE
1800     if (conv_str)
1801         vim_free(conv_str);
1802 #endif
1805 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1807     // TODO: This code is a horrible mess -- clean up!
1808     char_u special[3];
1809     char_u modChars[3];
1810     char_u *chars = (char_u*)[key UTF8String];
1811 #ifdef FEAT_MBYTE
1812     char_u *conv_str = NULL;
1813 #endif
1814     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1816     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1817     // that new keys can easily be added.
1818     NSString *specialString = [[MMBackend specialKeys]
1819             objectForKey:key];
1820     if (specialString && [specialString length] > 1) {
1821         //NSLog(@"special key: %@", specialString);
1822         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1823                 [specialString characterAtIndex:1]);
1825         ikey = simplify_key(ikey, &mods);
1826         if (ikey == CSI)
1827             ikey = K_CSI;
1829         special[0] = CSI;
1830         special[1] = K_SECOND(ikey);
1831         special[2] = K_THIRD(ikey);
1833         chars = special;
1834         length = 3;
1835     } else if (1 == length && TAB == chars[0]) {
1836         // Tab is a trouble child:
1837         // - <Tab> is added to the input buffer as is
1838         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1839         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1840         //   to be converted to utf-8
1841         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1842         // - <C-Tab> is reserved by Mac OS X
1843         // - <D-Tab> is reserved by Mac OS X
1844         chars = special;
1845         special[0] = TAB;
1846         length = 1;
1848         if (mods & MOD_MASK_SHIFT) {
1849             mods &= ~MOD_MASK_SHIFT;
1850             special[0] = CSI;
1851             special[1] = K_SECOND(K_S_TAB);
1852             special[2] = K_THIRD(K_S_TAB);
1853             length = 3;
1854         } else if (mods & MOD_MASK_ALT) {
1855             int mtab = 0x80 | TAB;
1856 #ifdef FEAT_MBYTE
1857             if (enc_utf8) {
1858                 // Convert to utf-8
1859                 special[0] = (mtab >> 6) + 0xc0;
1860                 special[1] = mtab & 0xbf;
1861                 length = 2;
1862             } else
1863 #endif
1864             {
1865                 special[0] = mtab;
1866                 length = 1;
1867             }
1868             mods &= ~MOD_MASK_ALT;
1869         }
1870     } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1871         // META key is treated separately.  This code was taken from gui_w48.c
1872         // and gui_gtk_x11.c.
1873         char_u string[7];
1874         int ch = simplify_key(chars[0], &mods);
1876         // Remove the SHIFT modifier for keys where it's already included,
1877         // e.g., '(' and '*'
1878         if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1879             mods &= ~MOD_MASK_SHIFT;
1881         // Interpret the ALT key as making the key META, include SHIFT, etc.
1882         ch = extract_modifiers(ch, &mods);
1883         if (ch == CSI)
1884             ch = K_CSI;
1886         int len = 0;
1887         if (mods) {
1888             string[len++] = CSI;
1889             string[len++] = KS_MODIFIER;
1890             string[len++] = mods;
1891         }
1893         if (IS_SPECIAL(ch)) {
1894             string[len++] = CSI;
1895             string[len++] = K_SECOND(ch);
1896             string[len++] = K_THIRD(ch);
1897         } else {
1898             string[len++] = ch;
1899 #ifdef FEAT_MBYTE
1900             // TODO: What if 'enc' is not "utf-8"?
1901             if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1902                 string[len++] = ch & 0xbf;
1903                 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1904                 if (string[len-1] == CSI) {
1905                     string[len++] = KS_EXTRA;
1906                     string[len++] = (int)KE_CSI;
1907                 }
1908             }
1909 #endif
1910         }
1912         add_to_input_buf(string, len);
1913         return;
1914     } else if (length > 0) {
1915         unichar c = [key characterAtIndex:0];
1916         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1917         //        [key characterAtIndex:0], mods);
1919         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1920         // cleared since they are already added to the key by the AppKit.
1921         // Unfortunately, the only way to deal with when to clear the modifiers
1922         // or not seems to be to have hard-wired rules like this.
1923         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1924                     || 0x9 == c || 0xd == c || ESC == c) ) {
1925             mods &= ~MOD_MASK_SHIFT;
1926             mods &= ~MOD_MASK_CTRL;
1927             //NSLog(@"clear shift ctrl");
1928         }
1930 #ifdef FEAT_MBYTE
1931         if (input_conv.vc_type != CONV_NONE) {
1932             conv_str = string_convert(&input_conv, chars, &length);
1933             if (conv_str)
1934                 chars = conv_str;
1935         }
1936 #endif
1937     }
1939     if (chars && length > 0) {
1940         if (mods) {
1941             //NSLog(@"adding mods: %d", mods);
1942             modChars[0] = CSI;
1943             modChars[1] = KS_MODIFIER;
1944             modChars[2] = mods;
1945             add_to_input_buf(modChars, 3);
1946         }
1948         //NSLog(@"add to input buf: 0x%x", chars[0]);
1949         // TODO: Check for CSI bytes?
1950         add_to_input_buf(chars, length);
1951     }
1953 #ifdef FEAT_MBYTE
1954     if (conv_str)
1955         vim_free(conv_str);
1956 #endif
1959 - (void)queueMessage:(int)msgid data:(NSData *)data
1961     //if (msgid != EnableMenuItemMsgID)
1962     //    NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1964     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1965     if (data)
1966         [outputQueue addObject:data];
1967     else
1968         [outputQueue addObject:[NSData data]];
1971 - (void)connectionDidDie:(NSNotification *)notification
1973     // If the main connection to MacVim is lost this means that MacVim was
1974     // either quit (by the user chosing Quit on the MacVim menu), or it has
1975     // crashed.  In the former case the flag 'isTerminating' is set and we then
1976     // quit cleanly; in the latter case we make sure the swap files are left
1977     // for recovery.
1978     //
1979     // NOTE: This is not called if a Vim controller invalidates its connection.
1981     //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1982     if (isTerminating)
1983         getout(0);
1984     else
1985         getout_preserve_modified(1);
1988 - (void)blinkTimerFired:(NSTimer *)timer
1990     NSTimeInterval timeInterval = 0;
1992     [blinkTimer release];
1993     blinkTimer = nil;
1995     if (MMBlinkStateOn == blinkState) {
1996         gui_undraw_cursor();
1997         blinkState = MMBlinkStateOff;
1998         timeInterval = blinkOffInterval;
1999     } else if (MMBlinkStateOff == blinkState) {
2000         gui_update_cursor(TRUE, FALSE);
2001         blinkState = MMBlinkStateOn;
2002         timeInterval = blinkOnInterval;
2003     }
2005     if (timeInterval > 0) {
2006         blinkTimer = 
2007             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2008                                             selector:@selector(blinkTimerFired:)
2009                                             userInfo:nil repeats:NO] retain];
2010         [self flushQueue:YES];
2011     }
2014 - (void)focusChange:(BOOL)on
2016     gui_focus_change(on);
2019 - (void)handleToggleToolbar
2021     // If 'go' contains 'T', then remove it, else add it.
2023     char_u go[sizeof(GO_ALL)+2];
2024     char_u *p;
2025     int len;
2027     STRCPY(go, p_go);
2028     p = vim_strchr(go, GO_TOOLBAR);
2029     len = STRLEN(go);
2031     if (p != NULL) {
2032         char_u *end = go + len;
2033         while (p < end) {
2034             p[0] = p[1];
2035             ++p;
2036         }
2037     } else {
2038         go[len] = GO_TOOLBAR;
2039         go[len+1] = NUL;
2040     }
2042     set_option_value((char_u*)"guioptions", 0, go, 0);
2044     [self redrawScreen];
2047 - (void)handleScrollbarEvent:(NSData *)data
2049     if (!data) return;
2051     const void *bytes = [data bytes];
2052     long ident = *((long*)bytes);  bytes += sizeof(long);
2053     int hitPart = *((int*)bytes);  bytes += sizeof(int);
2054     float fval = *((float*)bytes);  bytes += sizeof(float);
2055     scrollbar_T *sb = gui_find_scrollbar(ident);
2057     if (sb) {
2058         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2059         long value = sb_info->value;
2060         long size = sb_info->size;
2061         long max = sb_info->max;
2062         BOOL isStillDragging = NO;
2063         BOOL updateKnob = YES;
2065         switch (hitPart) {
2066         case NSScrollerDecrementPage:
2067             value -= (size > 2 ? size - 2 : 1);
2068             break;
2069         case NSScrollerIncrementPage:
2070             value += (size > 2 ? size - 2 : 1);
2071             break;
2072         case NSScrollerDecrementLine:
2073             --value;
2074             break;
2075         case NSScrollerIncrementLine:
2076             ++value;
2077             break;
2078         case NSScrollerKnob:
2079             isStillDragging = YES;
2080             // fall through ...
2081         case NSScrollerKnobSlot:
2082             value = (long)(fval * (max - size + 1));
2083             // fall through ...
2084         default:
2085             updateKnob = NO;
2086             break;
2087         }
2089         //NSLog(@"value %d -> %d", sb_info->value, value);
2090         gui_drag_scrollbar(sb, value, isStillDragging);
2092         if (updateKnob) {
2093             // Dragging the knob or option+clicking automatically updates
2094             // the knob position (on the actual NSScroller), so we only
2095             // need to set the knob position in the other cases.
2096             if (sb->wp) {
2097                 // Update both the left&right vertical scrollbars.
2098                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2099                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2100                 [self setScrollbarThumbValue:value size:size max:max
2101                                   identifier:identLeft];
2102                 [self setScrollbarThumbValue:value size:size max:max
2103                                   identifier:identRight];
2104             } else {
2105                 // Update the horizontal scrollbar.
2106                 [self setScrollbarThumbValue:value size:size max:max
2107                                   identifier:ident];
2108             }
2109         }
2110     }
2113 - (void)handleSetFont:(NSData *)data
2115     if (!data) return;
2117     const void *bytes = [data bytes];
2118     float pointSize = *((float*)bytes);  bytes += sizeof(float);
2119     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2120     bytes += sizeof(unsigned);  // len not used
2122     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2123     [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
2124     char_u *s = (char_u*)[name UTF8String];
2126 #ifdef FEAT_MBYTE
2127     s = CONVERT_FROM_UTF8(s);
2128 #endif
2130     set_option_value((char_u*)"guifont", 0, s, 0);
2132 #ifdef FEAT_MBYTE
2133     CONVERT_FROM_UTF8_FREE(s);
2134 #endif
2136     [self redrawScreen];
2139 - (void)handleDropFiles:(NSData *)data
2141     // TODO: Get rid of this method; instead use Vim script directly.  At the
2142     // moment I know how to do this to open files in tabs, but I'm not sure how
2143     // to add the filenames to the command line when in command line mode.
2145     if (!data) return;
2147     NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2148     if (!args) return;
2150     id obj = [args objectForKey:@"forceOpen"];
2151     BOOL forceOpen = YES;
2152     if (obj)
2153         forceOpen = [obj boolValue];
2155     NSArray *filenames = [args objectForKey:@"filenames"];
2156     if (!(filenames && [filenames count] > 0)) return;
2158 #ifdef FEAT_DND
2159     if (!forceOpen && (State & CMDLINE)) {
2160         // HACK!  If Vim is in command line mode then the files names
2161         // should be added to the command line, instead of opening the
2162         // files in tabs (unless forceOpen is set).  This is taken care of by
2163         // gui_handle_drop().
2164         int n = [filenames count];
2165         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2166         if (fnames) {
2167             int i = 0;
2168             for (i = 0; i < n; ++i)
2169                 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2171             // NOTE!  This function will free 'fnames'.
2172             // HACK!  It is assumed that the 'x' and 'y' arguments are
2173             // unused when in command line mode.
2174             gui_handle_drop(0, 0, 0, fnames, n);
2175         }
2176     } else
2177 #endif // FEAT_DND
2178     {
2179         [self handleOpenWithArguments:args];
2180     }
2183 - (void)handleDropString:(NSData *)data
2185     if (!data) return;
2187 #ifdef FEAT_DND
2188     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2189     const void *bytes = [data bytes];
2190     int len = *((int*)bytes);  bytes += sizeof(int);
2191     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2193     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2194     NSRange range = { 0, [string length] };
2195     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2196                                          withString:@"\x0a" options:0
2197                                               range:range];
2198     if (0 == n) {
2199         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2200                                        options:0 range:range];
2201     }
2203     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2204     char_u *s = (char_u*)[string UTF8String];
2205 #ifdef FEAT_MBYTE
2206     if (input_conv.vc_type != CONV_NONE)
2207         s = string_convert(&input_conv, s, &len);
2208 #endif
2209     dnd_yank_drag_data(s, len);
2210 #ifdef FEAT_MBYTE
2211     if (input_conv.vc_type != CONV_NONE)
2212         vim_free(s);
2213 #endif
2214     add_to_input_buf(dropkey, sizeof(dropkey));
2215 #endif // FEAT_DND
2218 - (void)startOdbEditWithArguments:(NSDictionary *)args
2220 #ifdef FEAT_ODB_EDITOR
2221     id obj = [args objectForKey:@"remoteID"];
2222     if (!obj) return;
2224     OSType serverID = [obj unsignedIntValue];
2225     NSString *remotePath = [args objectForKey:@"remotePath"];
2227     NSAppleEventDescriptor *token = nil;
2228     NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2229     obj = [args objectForKey:@"remoteTokenDescType"];
2230     if (tokenData && obj) {
2231         DescType tokenType = [obj unsignedLongValue];
2232         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2233                                                                 data:tokenData];
2234     }
2236     NSArray *filenames = [args objectForKey:@"filenames"];
2237     unsigned i, numFiles = [filenames count];
2238     for (i = 0; i < numFiles; ++i) {
2239         NSString *filename = [filenames objectAtIndex:i];
2240         char_u *s = [filename vimStringSave];
2241         buf_T *buf = buflist_findname(s);
2242         vim_free(s);
2244         if (buf) {
2245             if (buf->b_odb_token) {
2246                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2247                 buf->b_odb_token = NULL;
2248             }
2250             if (buf->b_odb_fname) {
2251                 vim_free(buf->b_odb_fname);
2252                 buf->b_odb_fname = NULL;
2253             }
2255             buf->b_odb_server_id = serverID;
2257             if (token)
2258                 buf->b_odb_token = [token retain];
2259             if (remotePath)
2260                 buf->b_odb_fname = [remotePath vimStringSave];
2261         } else {
2262             NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2263                     filename);
2264         }
2265     }
2266 #endif // FEAT_ODB_EDITOR
2269 - (void)handleXcodeMod:(NSData *)data
2271 #if 0
2272     const void *bytes = [data bytes];
2273     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2274     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2275     if (0 == len)
2276         return;
2278     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2279             descriptorWithDescriptorType:type
2280                                    bytes:bytes
2281                                   length:len];
2282 #endif
2285 - (void)handleOpenWithArguments:(NSDictionary *)args
2287     // ARGUMENT:                DESCRIPTION:
2288     // -------------------------------------------------------------
2289     // filenames                list of filenames
2290     // dontOpen                 don't open files specified in above argument
2291     // layout                   which layout to use to open files
2292     // selectionRange           range of lines to select
2293     // searchText               string to search for
2294     // cursorLine               line to position the cursor on
2295     // cursorColumn             column to position the cursor on
2296     //                          (only valid when "cursorLine" is set)
2297     // remoteID                 ODB parameter
2298     // remotePath               ODB parameter
2299     // remoteTokenDescType      ODB parameter
2300     // remoteTokenData          ODB parameter
2302     //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2304     NSArray *filenames = [args objectForKey:@"filenames"];
2305     int i, numFiles = filenames ? [filenames count] : 0;
2306     BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2307     int layout = [[args objectForKey:@"layout"] intValue];
2309     // Change to directory of first file to open if this is an "unused" editor
2310     // (but do not do this if editing remotely).
2311     if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2312             && (starting || [self unusedEditor]) ) {
2313         char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2314         vim_chdirfile(s);
2315         vim_free(s);
2316     }
2318     if (starting > 0) {
2319         // When Vim is starting we simply add the files to be opened to the
2320         // global arglist and Vim will take care of opening them for us.
2321         if (openFiles && numFiles > 0) {
2322             for (i = 0; i < numFiles; i++) {
2323                 NSString *fname = [filenames objectAtIndex:i];
2324                 char_u *p = NULL;
2326                 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2327                         || (p = [fname vimStringSave]) == NULL)
2328                     exit(2); // See comment in -[MMBackend exit]
2329                 else
2330                     alist_add(&global_alist, p, 2);
2331             }
2333             // Vim will take care of arranging the files added to the arglist
2334             // in windows or tabs; all we must do is to specify which layout to
2335             // use.
2336             initialWindowLayout = layout;
2337         }
2338     } else {
2339         // When Vim is already open we resort to some trickery to open the
2340         // files with the specified layout.
2341         //
2342         // TODO: Figure out a better way to handle this?
2343         if (openFiles && numFiles > 0) {
2344             BOOL oneWindowInTab = topframe ? YES
2345                                            : (topframe->fr_layout == FR_LEAF);
2346             BOOL bufChanged = NO;
2347             BOOL bufHasFilename = NO;
2348             if (curbuf) {
2349                 bufChanged = curbufIsChanged();
2350                 bufHasFilename = curbuf->b_ffname != NULL;
2351             }
2353             // Temporarily disable flushing since the following code may
2354             // potentially cause multiple redraws.
2355             flushDisabled = YES;
2357             BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2358             if (WIN_TABS == layout && !onlyOneTab) {
2359                 // By going to the last tabpage we ensure that the new tabs
2360                 // will appear last (if this call is left out, the taborder
2361                 // becomes messy).
2362                 goto_tabpage(9999);
2363             }
2365             // Make sure we're in normal mode first.
2366             [self addInput:@"<C-\\><C-N>"];
2368             if (numFiles > 1) {
2369                 // With "split layout" we open a new tab before opening
2370                 // multiple files if the current tab has more than one window
2371                 // or if there is exactly one window but whose buffer has a
2372                 // filename.  (The :drop command ensures modified buffers get
2373                 // their own window.)
2374                 if ((WIN_HOR == layout || WIN_VER == layout) &&
2375                         (!oneWindowInTab || bufHasFilename))
2376                     [self addInput:@":tabnew<CR>"];
2378                 // The files are opened by constructing a ":drop ..." command
2379                 // and executing it.
2380                 NSMutableString *cmd = (WIN_TABS == layout)
2381                         ? [NSMutableString stringWithString:@":tab drop"]
2382                         : [NSMutableString stringWithString:@":drop"];
2384                 for (i = 0; i < numFiles; ++i) {
2385                     NSString *file = [filenames objectAtIndex:i];
2386                     file = [file stringByEscapingSpecialFilenameCharacters];
2387                     [cmd appendString:@" "];
2388                     [cmd appendString:file];
2389                 }
2391                 // Temporarily clear 'suffixes' so that the files are opened in
2392                 // the same order as they appear in the "filenames" array.
2393                 [self addInput:@":let t:mvim_oldsu=&su|set su=<CR>"];
2395                 [self addInput:cmd];
2397                 // Split the view into multiple windows if requested.
2398                 if (WIN_HOR == layout)
2399                     [self addInput:@"|sall"];
2400                 else if (WIN_VER == layout)
2401                     [self addInput:@"|vert sall"];
2403                 // Restore the old value of 'suffixes'.
2404                 [self addInput:@"|let &su=t:mvim_oldsu|unlet t:mvim_oldsu"];
2406                 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2407                 [self addInput:@"|redr|f<CR>"];
2408             } else {
2409                 // When opening one file we try to reuse the current window,
2410                 // but not if its buffer is modified or has a filename.
2411                 // However, the 'arglist' layout always opens the file in the
2412                 // current window.
2413                 NSString *file = [[filenames lastObject]
2414                         stringByEscapingSpecialFilenameCharacters];
2415                 NSString *cmd;
2416                 if (WIN_HOR == layout) {
2417                     if (!(bufHasFilename || bufChanged))
2418                         cmd = [NSString stringWithFormat:@":e %@", file];
2419                     else
2420                         cmd = [NSString stringWithFormat:@":sp %@", file];
2421                 } else if (WIN_VER == layout) {
2422                     if (!(bufHasFilename || bufChanged))
2423                         cmd = [NSString stringWithFormat:@":e %@", file];
2424                     else
2425                         cmd = [NSString stringWithFormat:@":vsp %@", file];
2426                 } else if (WIN_TABS == layout) {
2427                     if (oneWindowInTab && !(bufHasFilename || bufChanged))
2428                         cmd = [NSString stringWithFormat:@":e %@", file];
2429                     else
2430                         cmd = [NSString stringWithFormat:@":tabe %@", file];
2431                 } else {
2432                     // (The :drop command will split if there is a modified
2433                     // buffer.)
2434                     cmd = [NSString stringWithFormat:@":drop %@", file];
2435                 }
2437                 [self addInput:cmd];
2439                 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2440                 [self addInput:@"|redr|f<CR>"];
2441             }
2443             // Force screen redraw (does it have to be this complicated?).
2444             // (This code was taken from the end of gui_handle_drop().)
2445             update_screen(NOT_VALID);
2446             setcursor();
2447             out_flush();
2448             gui_update_cursor(FALSE, FALSE);
2449             maketitle();
2451             flushDisabled = NO;
2452         }
2453     }
2455     if ([args objectForKey:@"remoteID"]) {
2456         // NOTE: We have to delay processing any ODB related arguments since
2457         // the file(s) may not be opened until the input buffer is processed.
2458         [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2459                                withObject:args
2460                             waitUntilDone:NO];
2461     }
2463     NSString *lineString = [args objectForKey:@"cursorLine"];
2464     if (lineString && [lineString intValue] > 0) {
2465         NSString *columnString = [args objectForKey:@"cursorColumn"];
2466         if (!(columnString && [columnString intValue] > 0))
2467             columnString = @"1";
2469         NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2470                 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2471         [self addInput:cmd];
2472     }
2474     NSString *rangeString = [args objectForKey:@"selectionRange"];
2475     if (rangeString) {
2476         // Build a command line string that will select the given range of
2477         // lines.  If range.length == 0, then position the cursor on the given
2478         // line but do not select.
2479         NSRange range = NSRangeFromString(rangeString);
2480         NSString *cmd;
2481         if (range.length > 0) {
2482             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2483                     NSMaxRange(range), range.location];
2484         } else {
2485             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2486                     range.location];
2487         }
2489         [self addInput:cmd];
2490     }
2492     NSString *searchText = [args objectForKey:@"searchText"];
2493     if (searchText) {
2494         // TODO: Searching is an exclusive motion, so if the pattern would
2495         // match on row 0 column 0 then this pattern will miss that match.
2496         [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@<CR>",
2497                 searchText]];
2498     }
2501 - (BOOL)checkForModifiedBuffers
2503     buf_T *buf;
2504     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2505         if (bufIsChanged(buf)) {
2506             return YES;
2507         }
2508     }
2510     return NO;
2513 - (void)addInput:(NSString *)input
2515     char_u *s = (char_u*)[input UTF8String];
2517 #ifdef FEAT_MBYTE
2518     s = CONVERT_FROM_UTF8(s);
2519 #endif
2521     server_to_input_buf(s);
2523 #ifdef FEAT_MBYTE
2524     CONVERT_FROM_UTF8_FREE(s);
2525 #endif
2528 - (BOOL)unusedEditor
2530     BOOL oneWindowInTab = topframe ? YES
2531                                    : (topframe->fr_layout == FR_LEAF);
2532     BOOL bufChanged = NO;
2533     BOOL bufHasFilename = NO;
2534     if (curbuf) {
2535         bufChanged = curbufIsChanged();
2536         bufHasFilename = curbuf->b_ffname != NULL;
2537     }
2539     BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2541     return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2544 - (void)redrawScreen
2546     // Force screen redraw (does it have to be this complicated?).
2547     redraw_all_later(CLEAR);
2548     update_screen(NOT_VALID);
2549     setcursor();
2550     out_flush();
2551     gui_update_cursor(FALSE, FALSE);
2553     // HACK! The cursor is not put back at the command line by the above
2554     // "redraw commands".  The following test seems to do the trick though.
2555     if (State & CMDLINE)
2556         redrawcmdline();
2559 - (void)handleFindReplace:(NSDictionary *)args
2561     if (!args) return;
2563     NSString *findString = [args objectForKey:@"find"];
2564     if (!findString) return;
2566     char_u *find = [findString vimStringSave];
2567     char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2568     int flags = [[args objectForKey:@"flags"] intValue];
2570     // NOTE: The flag 0x100 is used to indicate a backward search.
2571     gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2573     vim_free(find);
2574     vim_free(replace);
2577 @end // MMBackend (Private)
2582 @implementation MMBackend (ClientServer)
2584 - (NSString *)connectionNameFromServerName:(NSString *)name
2586     NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2588     return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2589         lowercaseString];
2592 - (NSConnection *)connectionForServerName:(NSString *)name
2594     // TODO: Try 'name%d' if 'name' fails.
2595     NSString *connName = [self connectionNameFromServerName:name];
2596     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2598     if (!svrConn) {
2599         svrConn = [NSConnection connectionWithRegisteredName:connName
2600                                                            host:nil];
2601         // Try alternate server...
2602         if (!svrConn && alternateServerName) {
2603             //NSLog(@"  trying to connect to alternate server: %@",
2604             //        alternateServerName);
2605             connName = [self connectionNameFromServerName:alternateServerName];
2606             svrConn = [NSConnection connectionWithRegisteredName:connName
2607                                                             host:nil];
2608         }
2610         // Try looking for alternate servers...
2611         if (!svrConn) {
2612             //NSLog(@"  looking for alternate servers...");
2613             NSString *alt = [self alternateServerNameForName:name];
2614             if (alt != alternateServerName) {
2615                 //NSLog(@"  found alternate server: %@", string);
2616                 [alternateServerName release];
2617                 alternateServerName = [alt copy];
2618             }
2619         }
2621         // Try alternate server again...
2622         if (!svrConn && alternateServerName) {
2623             //NSLog(@"  trying to connect to alternate server: %@",
2624             //        alternateServerName);
2625             connName = [self connectionNameFromServerName:alternateServerName];
2626             svrConn = [NSConnection connectionWithRegisteredName:connName
2627                                                             host:nil];
2628         }
2630         if (svrConn) {
2631             [connectionNameDict setObject:svrConn forKey:connName];
2633             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2634             [[NSNotificationCenter defaultCenter] addObserver:self
2635                     selector:@selector(serverConnectionDidDie:)
2636                         name:NSConnectionDidDieNotification object:svrConn];
2637         }
2638     }
2640     return svrConn;
2643 - (NSConnection *)connectionForServerPort:(int)port
2645     NSConnection *conn;
2646     NSEnumerator *e = [connectionNameDict objectEnumerator];
2648     while ((conn = [e nextObject])) {
2649         // HACK! Assume connection uses mach ports.
2650         if (port == [(NSMachPort*)[conn sendPort] machPort])
2651             return conn;
2652     }
2654     return nil;
2657 - (void)serverConnectionDidDie:(NSNotification *)notification
2659     //NSLog(@"%s%@", _cmd, notification);
2661     NSConnection *svrConn = [notification object];
2663     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2664     [[NSNotificationCenter defaultCenter]
2665             removeObserver:self
2666                       name:NSConnectionDidDieNotification
2667                     object:svrConn];
2669     [connectionNameDict removeObjectsForKeys:
2670         [connectionNameDict allKeysForObject:svrConn]];
2672     // HACK! Assume connection uses mach ports.
2673     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2674     NSNumber *key = [NSNumber numberWithInt:port];
2676     [clientProxyDict removeObjectForKey:key];
2677     [serverReplyDict removeObjectForKey:key];
2680 - (void)addClient:(NSDistantObject *)client
2682     NSConnection *conn = [client connectionForProxy];
2683     // HACK! Assume connection uses mach ports.
2684     int port = [(NSMachPort*)[conn sendPort] machPort];
2685     NSNumber *key = [NSNumber numberWithInt:port];
2687     if (![clientProxyDict objectForKey:key]) {
2688         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2689         [clientProxyDict setObject:client forKey:key];
2690     }
2692     // NOTE: 'clientWindow' is a global variable which is used by <client>
2693     clientWindow = port;
2696 - (NSString *)alternateServerNameForName:(NSString *)name
2698     if (!(name && [name length] > 0))
2699         return nil;
2701     // Only look for alternates if 'name' doesn't end in a digit.
2702     unichar lastChar = [name characterAtIndex:[name length]-1];
2703     if (lastChar >= '0' && lastChar <= '9')
2704         return nil;
2706     // Look for alternates among all current servers.
2707     NSArray *list = [self serverList];
2708     if (!(list && [list count] > 0))
2709         return nil;
2711     // Filter out servers starting with 'name' and ending with a number. The
2712     // (?i) pattern ensures that the match is case insensitive.
2713     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2714     NSPredicate *pred = [NSPredicate predicateWithFormat:
2715             @"SELF MATCHES %@", pat];
2716     list = [list filteredArrayUsingPredicate:pred];
2717     if ([list count] > 0) {
2718         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2719         return [list objectAtIndex:0];
2720     }
2722     return nil;
2725 @end // MMBackend (ClientServer)
2730 @implementation NSString (MMServerNameCompare)
2731 - (NSComparisonResult)serverNameCompare:(NSString *)string
2733     return [self compare:string
2734                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2736 @end
2741 static int eventModifierFlagsToVimModMask(int modifierFlags)
2743     int modMask = 0;
2745     if (modifierFlags & NSShiftKeyMask)
2746         modMask |= MOD_MASK_SHIFT;
2747     if (modifierFlags & NSControlKeyMask)
2748         modMask |= MOD_MASK_CTRL;
2749     if (modifierFlags & NSAlternateKeyMask)
2750         modMask |= MOD_MASK_ALT;
2751     if (modifierFlags & NSCommandKeyMask)
2752         modMask |= MOD_MASK_CMD;
2754     return modMask;
2757 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2759     int modMask = 0;
2761     if (modifierFlags & NSShiftKeyMask)
2762         modMask |= MOUSE_SHIFT;
2763     if (modifierFlags & NSControlKeyMask)
2764         modMask |= MOUSE_CTRL;
2765     if (modifierFlags & NSAlternateKeyMask)
2766         modMask |= MOUSE_ALT;
2768     return modMask;
2771 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2773     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2775     return (buttonNumber >= 0 && buttonNumber < 3)
2776             ? mouseButton[buttonNumber] : -1;
2781 // This function is modeled after the VimToPython function found in if_python.c
2782 // NB This does a deep copy by value, it does not lookup references like the
2783 // VimToPython function does.  This is because I didn't want to deal with the
2784 // retain cycles that this would create, and we can cover 99% of the use cases
2785 // by ignoring it.  If we ever switch to using GC in MacVim then this
2786 // functionality can be implemented easily.
2787 static id vimToCocoa(typval_T * tv, int depth)
2789     id result = nil;
2790     id newObj = nil;
2793     // Avoid infinite recursion
2794     if (depth > 100) {
2795         return nil;
2796     }
2798     if (tv->v_type == VAR_STRING) {
2799         char_u * val = tv->vval.v_string;
2800         // val can be NULL if the string is empty
2801         if (!val) {
2802             result = [NSString string];
2803         } else {
2804 #ifdef FEAT_MBYTE
2805             val = CONVERT_TO_UTF8(val);
2806 #endif
2807             result = [NSString stringWithUTF8String:(char*)val];
2808 #ifdef FEAT_MBYTE
2809             CONVERT_TO_UTF8_FREE(val);
2810 #endif
2811         }
2812     } else if (tv->v_type == VAR_NUMBER) {
2813         // looks like sizeof(varnumber_T) is always <= sizeof(long)
2814         result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2815     } else if (tv->v_type == VAR_LIST) {
2816         list_T * list = tv->vval.v_list;
2817         listitem_T * curr;
2819         NSMutableArray * arr = result = [NSMutableArray array];
2821         if (list != NULL) {
2822             for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2823                 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2824                 [arr addObject:newObj];
2825             }
2826         }
2827     } else if (tv->v_type == VAR_DICT) {
2828         NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2830         if (tv->vval.v_dict != NULL) {
2831             hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2832             int todo = ht->ht_used;
2833             hashitem_T * hi;
2834             dictitem_T * di;
2836             for (hi = ht->ht_array; todo > 0; ++hi) {
2837                 if (!HASHITEM_EMPTY(hi)) {
2838                     --todo;
2840                     di = dict_lookup(hi);
2841                     newObj = vimToCocoa(&di->di_tv, depth + 1);
2843                     char_u * keyval = hi->hi_key;
2844 #ifdef FEAT_MBYTE
2845                     keyval = CONVERT_TO_UTF8(keyval);
2846 #endif
2847                     NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2848 #ifdef FEAT_MBYTE
2849                     CONVERT_TO_UTF8_FREE(keyval);
2850 #endif
2851                     [dict setObject:newObj forKey:key];
2852                 }
2853             }
2854         }
2855     } else { // only func refs should fall into this category?
2856         result = nil;
2857     }
2859     return result;
2863 // This function is modeled after eval_client_expr_to_string found in main.c
2864 // Returns nil if there was an error evaluating the expression, and writes a
2865 // message to errorStr.
2866 // TODO Get the error that occurred while evaluating the expression in vim
2867 // somehow.
2868 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2871     char_u *s = (char_u*)[expr UTF8String];
2873 #ifdef FEAT_MBYTE
2874     s = CONVERT_FROM_UTF8(s);
2875 #endif
2877     int save_dbl = debug_break_level;
2878     int save_ro = redir_off;
2880     debug_break_level = -1;
2881     redir_off = 0;
2882     ++emsg_skip;
2884     typval_T * tvres = eval_expr(s, NULL);
2886     debug_break_level = save_dbl;
2887     redir_off = save_ro;
2888     --emsg_skip;
2890     setcursor();
2891     out_flush();
2893 #ifdef FEAT_MBYTE
2894     CONVERT_FROM_UTF8_FREE(s);
2895 #endif
2897 #ifdef FEAT_GUI
2898     if (gui.in_use)
2899         gui_update_cursor(FALSE, FALSE);
2900 #endif
2902     if (tvres == NULL) {
2903         free_tv(tvres);
2904         *errstr = @"Expression evaluation failed.";
2905     }
2907     id res = vimToCocoa(tvres, 1);
2909     free_tv(tvres);
2911     if (res == nil) {
2912         *errstr = @"Conversion to cocoa values failed.";
2913     }
2915     return res;
2920 @implementation NSString (VimStrings)
2922 + (id)stringWithVimString:(char_u *)s
2924     // This method ensures a non-nil string is returned.  If 's' cannot be
2925     // converted to a utf-8 string it is assumed to be latin-1.  If conversion
2926     // still fails an empty NSString is returned.
2927     NSString *string = nil;
2928     if (s) {
2929 #ifdef FEAT_MBYTE
2930         s = CONVERT_TO_UTF8(s);
2931 #endif
2932         string = [NSString stringWithUTF8String:(char*)s];
2933         if (!string) {
2934             // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2935             // latin-1?
2936             string = [NSString stringWithCString:(char*)s
2937                                         encoding:NSISOLatin1StringEncoding];
2938         }
2939 #ifdef FEAT_MBYTE
2940         CONVERT_TO_UTF8_FREE(s);
2941 #endif
2942     }
2944     return string != nil ? string : [NSString string];
2947 - (char_u *)vimStringSave
2949     char_u *s = (char_u*)[self UTF8String], *ret = NULL;
2951 #ifdef FEAT_MBYTE
2952     s = CONVERT_FROM_UTF8(s);
2953 #endif
2954     ret = vim_strsave(s);
2955 #ifdef FEAT_MBYTE
2956     CONVERT_FROM_UTF8_FREE(s);
2957 #endif
2959     return ret;
2962 @end // NSString (VimStrings)