Put cursor back on cmdline after Cmd-=
[MacVim.git] / src / MacVim / MMBackend.m
blobae4064f56ca4c4aec7822231d9e52f8793d5f412
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:(NSData *)data;
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 @end
116 @interface MMBackend (ClientServer)
117 - (NSString *)connectionNameFromServerName:(NSString *)name;
118 - (NSConnection *)connectionForServerName:(NSString *)name;
119 - (NSConnection *)connectionForServerPort:(int)port;
120 - (void)serverConnectionDidDie:(NSNotification *)notification;
121 - (void)addClient:(NSDistantObject *)client;
122 - (NSString *)alternateServerNameForName:(NSString *)name;
123 @end
127 @implementation MMBackend
129 + (MMBackend *)sharedInstance
131     static MMBackend *singleton = nil;
132     return singleton ? singleton : (singleton = [MMBackend new]);
135 - (id)init
137     self = [super init];
138     if (!self) return nil;
140     fontContainerRef = loadFonts();
142     outputQueue = [[NSMutableArray alloc] init];
143     inputQueue = [[NSMutableArray alloc] init];
144     drawData = [[NSMutableData alloc] initWithCapacity:1024];
145     connectionNameDict = [[NSMutableDictionary alloc] init];
146     clientProxyDict = [[NSMutableDictionary alloc] init];
147     serverReplyDict = [[NSMutableDictionary alloc] init];
149     NSBundle *mainBundle = [NSBundle mainBundle];
150     NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
151     if (path)
152         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
154     path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
155     if (path)
156         sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
157             retain];
159     path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
160     if (path)
161         actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
163     if (!(colorDict && sysColorDict && actionDict))
164         NSLog(@"ERROR: Failed to load dictionaries.%@",
165                 MMSymlinkWarningString);
167     return self;
170 - (void)dealloc
172     //NSLog(@"%@ %s", [self className], _cmd);
173     [[NSNotificationCenter defaultCenter] removeObserver:self];
175     [oldWideFont release];  oldWideFont = nil;
176     [blinkTimer release];  blinkTimer = nil;
177     [alternateServerName release];  alternateServerName = nil;
178     [serverReplyDict release];  serverReplyDict = nil;
179     [clientProxyDict release];  clientProxyDict = nil;
180     [connectionNameDict release];  connectionNameDict = nil;
181     [inputQueue release];  inputQueue = nil;
182     [outputQueue release];  outputQueue = nil;
183     [drawData release];  drawData = nil;
184     [frontendProxy release];  frontendProxy = nil;
185     [connection release];  connection = nil;
186     [actionDict release];  actionDict = nil;
187     [sysColorDict release];  sysColorDict = nil;
188     [colorDict release];  colorDict = nil;
190     [super dealloc];
193 - (void)setBackgroundColor:(int)color
195     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
198 - (void)setForegroundColor:(int)color
200     foregroundColor = MM_COLOR(color);
203 - (void)setSpecialColor:(int)color
205     specialColor = MM_COLOR(color);
208 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
210     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
211     defaultForegroundColor = MM_COLOR(fg);
213     NSMutableData *data = [NSMutableData data];
215     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
216     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
218     [self queueMessage:SetDefaultColorsMsgID data:data];
221 - (NSConnection *)connection
223     if (!connection) {
224         // NOTE!  If the name of the connection changes here it must also be
225         // updated in MMAppController.m.
226         NSString *name = [NSString stringWithFormat:@"%@-connection",
227                [[NSBundle mainBundle] bundlePath]];
229         connection = [NSConnection connectionWithRegisteredName:name host:nil];
230         [connection retain];
231     }
233     // NOTE: 'connection' may be nil here.
234     return connection;
237 - (NSDictionary *)actionDict
239     return actionDict;
242 - (int)initialWindowLayout
244     return initialWindowLayout;
247 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
249     [self queueMessage:msgid data:[props dictionaryAsData]];
252 - (BOOL)checkin
254     if (![self connection]) {
255         if (waitForAck) {
256             // This is a preloaded process and as such should not cause the
257             // MacVim to be opened.  We probably got here as a result of the
258             // user quitting MacVim while the process was preloading, so exit
259             // this process too.
260             // (Don't use mch_exit() since it assumes the process has properly
261             // started.)
262             exit(0);
263         }
265         NSBundle *mainBundle = [NSBundle mainBundle];
266 #if 0
267         OSStatus status;
268         FSRef ref;
270         // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
271         // the API to pass Apple Event parameters is broken on 10.4).
272         NSString *path = [mainBundle bundlePath];
273         status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
274         if (noErr == status) {
275             // Pass parameter to the 'Open' Apple Event that tells MacVim not
276             // to open an untitled window.
277             NSAppleEventDescriptor *desc =
278                     [NSAppleEventDescriptor recordDescriptor];
279             [desc setParamDescriptor:
280                     [NSAppleEventDescriptor descriptorWithBoolean:NO]
281                           forKeyword:keyMMUntitledWindow];
283             LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
284                     kLSLaunchDefaults, NULL };
285             status = LSOpenFromRefSpec(&spec, NULL);
286         }
288         if (noErr != status) {
289         NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
290                 path, MMSymlinkWarningString);
291             return NO;
292         }
293 #else
294         // Launch MacVim using NSTask.  For some reason the above code using
295         // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
296         // fails, the dock icon starts bouncing and never stops).  It seems
297         // like rebuilding the Launch Services database takes care of this
298         // problem, but the NSTask way seems more stable so stick with it.
299         //
300         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
301         // that the GUI won't be activated (or raised) so there is a hack in
302         // MMAppController which raises the app when a new window is opened.
303         NSMutableArray *args = [NSMutableArray arrayWithObjects:
304             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
305         NSString *exeName = [[mainBundle infoDictionary]
306                 objectForKey:@"CFBundleExecutable"];
307         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
308         if (!path) {
309             NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
310                     MMSymlinkWarningString);
311             return NO;
312         }
314         [NSTask launchedTaskWithLaunchPath:path arguments:args];
315 #endif
317         // HACK!  Poll the mach bootstrap server until it returns a valid
318         // connection to detect that MacVim has finished launching.  Also set a
319         // time-out date so that we don't get stuck doing this forever.
320         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
321         while (![self connection] &&
322                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
323             [[NSRunLoop currentRunLoop]
324                     runMode:NSDefaultRunLoopMode
325                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
327         // NOTE: [self connection] will set 'connection' as a side-effect.
328         if (!connection) {
329             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
330             return NO;
331         }
332     }
334     BOOL ok = NO;
335     @try {
336         [[NSNotificationCenter defaultCenter] addObserver:self
337                 selector:@selector(connectionDidDie:)
338                     name:NSConnectionDidDieNotification object:connection];
340         id proxy = [connection rootProxy];
341         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
343         int pid = [[NSProcessInfo processInfo] processIdentifier];
345         frontendProxy = [proxy connectBackend:self pid:pid];
346         if (frontendProxy) {
347             [frontendProxy retain];
348             [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
349             ok = YES;
350         }
351     }
352     @catch (NSException *e) {
353         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
354     }
356     return ok;
359 - (BOOL)openGUIWindow
361     [self queueMessage:OpenWindowMsgID data:nil];
362     return YES;
365 - (void)clearAll
367     int type = ClearAllDrawType;
369     // Any draw commands in queue are effectively obsolete since this clearAll
370     // will negate any effect they have, therefore we may as well clear the
371     // draw queue.
372     [drawData setLength:0];
374     [drawData appendBytes:&type length:sizeof(int)];
377 - (void)clearBlockFromRow:(int)row1 column:(int)col1
378                     toRow:(int)row2 column:(int)col2
380     int type = ClearBlockDrawType;
382     [drawData appendBytes:&type length:sizeof(int)];
384     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
385     [drawData appendBytes:&row1 length:sizeof(int)];
386     [drawData appendBytes:&col1 length:sizeof(int)];
387     [drawData appendBytes:&row2 length:sizeof(int)];
388     [drawData appendBytes:&col2 length:sizeof(int)];
391 - (void)deleteLinesFromRow:(int)row count:(int)count
392               scrollBottom:(int)bottom left:(int)left right:(int)right
394     int type = DeleteLinesDrawType;
396     [drawData appendBytes:&type length:sizeof(int)];
398     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
399     [drawData appendBytes:&row length:sizeof(int)];
400     [drawData appendBytes:&count length:sizeof(int)];
401     [drawData appendBytes:&bottom length:sizeof(int)];
402     [drawData appendBytes:&left length:sizeof(int)];
403     [drawData appendBytes:&right length:sizeof(int)];
406 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
407              cells:(int)cells flags:(int)flags
409     if (len <= 0 || cells <= 0) return;
411     int type = DrawStringDrawType;
413     [drawData appendBytes:&type length:sizeof(int)];
415     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
416     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
417     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
418     [drawData appendBytes:&row length:sizeof(int)];
419     [drawData appendBytes:&col length:sizeof(int)];
420     [drawData appendBytes:&cells length:sizeof(int)];
421     [drawData appendBytes:&flags length:sizeof(int)];
422     [drawData appendBytes:&len length:sizeof(int)];
423     [drawData appendBytes:s length:len];
426 - (void)insertLinesFromRow:(int)row count:(int)count
427               scrollBottom:(int)bottom left:(int)left right:(int)right
429     int type = InsertLinesDrawType;
431     [drawData appendBytes:&type length:sizeof(int)];
433     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
434     [drawData appendBytes:&row length:sizeof(int)];
435     [drawData appendBytes:&count length:sizeof(int)];
436     [drawData appendBytes:&bottom length:sizeof(int)];
437     [drawData appendBytes:&left length:sizeof(int)];
438     [drawData appendBytes:&right length:sizeof(int)];
441 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
442                fraction:(int)percent color:(int)color
444     int type = DrawCursorDrawType;
445     unsigned uc = MM_COLOR(color);
447     [drawData appendBytes:&type length:sizeof(int)];
449     [drawData appendBytes:&uc length:sizeof(unsigned)];
450     [drawData appendBytes:&row length:sizeof(int)];
451     [drawData appendBytes:&col length:sizeof(int)];
452     [drawData appendBytes:&shape length:sizeof(int)];
453     [drawData appendBytes:&percent length:sizeof(int)];
456 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
457                    numColumns:(int)nc invert:(int)invert
459     int type = DrawInvertedRectDrawType;
460     [drawData appendBytes:&type length:sizeof(int)];
462     [drawData appendBytes:&row length:sizeof(int)];
463     [drawData appendBytes:&col length:sizeof(int)];
464     [drawData appendBytes:&nr length:sizeof(int)];
465     [drawData appendBytes:&nc length:sizeof(int)];
466     [drawData appendBytes:&invert length:sizeof(int)];
469 - (void)update
471     // Keep running the run-loop until there is no more input to process.
472     while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
473             == kCFRunLoopRunHandledSource)
474         ;
477 - (void)flushQueue:(BOOL)force
479     // NOTE: This variable allows for better control over when the queue is
480     // flushed.  It can be set to YES at the beginning of a sequence of calls
481     // that may potentially add items to the queue, and then restored back to
482     // NO.
483     if (flushDisabled) return;
485     if ([drawData length] > 0) {
486         // HACK!  Detect changes to 'guifontwide'.
487         if (gui.wide_font != (GuiFont)oldWideFont) {
488             [oldWideFont release];
489             oldWideFont = [(NSFont*)gui.wide_font retain];
490             [self setWideFont:oldWideFont];
491         }
493         int type = SetCursorPosDrawType;
494         [drawData appendBytes:&type length:sizeof(type)];
495         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
496         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
498         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
499         [drawData setLength:0];
500     }
502     if ([outputQueue count] > 0) {
503         [self insertVimStateMessage];
505         @try {
506             [frontendProxy processCommandQueue:outputQueue];
507         }
508         @catch (NSException *e) {
509             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
510         }
512         [outputQueue removeAllObjects];
513     }
516 - (BOOL)waitForInput:(int)milliseconds
518     // Return NO if we timed out waiting for input, otherwise return YES.
519     BOOL inputReceived = NO;
521     // Only start the run loop if the input queue is empty, otherwise process
522     // the input first so that the input on queue isn't delayed.
523     if ([inputQueue count]) {
524         inputReceived = YES;
525     } else {
526         // Wait for the specified amount of time, unless 'milliseconds' is
527         // negative in which case we wait "forever" (1e6 seconds translates to
528         // approximately 11 days).
529         CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
531         while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
532                 == kCFRunLoopRunHandledSource) {
533             // In order to ensure that all input on the run-loop has been
534             // processed we set the timeout to 0 and keep processing until the
535             // run-loop times out.
536             dt = 0.0;
537             inputReceived = YES;
538         }
539     }
541     // The above calls may have placed messages on the input queue so process
542     // it now.  This call may enter a blocking loop.
543     if ([inputQueue count] > 0)
544         [self processInputQueue];
546     return inputReceived;
549 - (void)exit
551     // NOTE: This is called if mch_exit() is called.  Since we assume here that
552     // the process has started properly, be sure to use exit() instead of
553     // mch_exit() to prematurely terminate a process.
555     // To notify MacVim that this Vim process is exiting we could simply
556     // invalidate the connection and it would automatically receive a
557     // connectionDidDie: notification.  However, this notification seems to
558     // take up to 300 ms to arrive which is quite a noticeable delay.  Instead
559     // we immediately send a message to MacVim asking it to close the window
560     // belonging to this process, and then we invalidate the connection (in
561     // case the message got lost).
563     // Make sure no connectionDidDie: notification is received now that we are
564     // already exiting.
565     [[NSNotificationCenter defaultCenter] removeObserver:self];
567     if ([connection isValid]) {
568         @try {
569             // Flush the entire queue in case a VimLeave autocommand added
570             // something to the queue.
571             [self queueMessage:CloseWindowMsgID data:nil];
572             [frontendProxy processCommandQueue:outputQueue];
573         }
574         @catch (NSException *e) {
575             NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
576         }
578         [connection invalidate];
579     }
581 #ifdef MAC_CLIENTSERVER
582     // The default connection is used for the client/server code.
583     [[NSConnection defaultConnection] setRootObject:nil];
584     [[NSConnection defaultConnection] invalidate];
585 #endif
587     if (fontContainerRef) {
588         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
589         fontContainerRef = 0;
590     }
592     usleep(MMExitProcessDelay);
595 - (void)selectTab:(int)index
597     //NSLog(@"%s%d", _cmd, index);
599     index -= 1;
600     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
601     [self queueMessage:SelectTabMsgID data:data];
604 - (void)updateTabBar
606     //NSLog(@"%s", _cmd);
608     NSMutableData *data = [NSMutableData data];
610     int idx = tabpage_index(curtab) - 1;
611     [data appendBytes:&idx length:sizeof(int)];
613     tabpage_T *tp;
614     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
615         // This function puts the label of the tab in the global 'NameBuff'.
616         get_tabline_label(tp, FALSE);
617         char_u *s = NameBuff;
618         int len = STRLEN(s);
619         if (len <= 0) continue;
621 #ifdef FEAT_MBYTE
622         s = CONVERT_TO_UTF8(s);
623 #endif
625         // Count the number of windows in the tabpage.
626         //win_T *wp = tp->tp_firstwin;
627         //int wincount;
628         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
630         //[data appendBytes:&wincount length:sizeof(int)];
631         [data appendBytes:&len length:sizeof(int)];
632         [data appendBytes:s length:len];
634 #ifdef FEAT_MBYTE
635         CONVERT_TO_UTF8_FREE(s);
636 #endif
637     }
639     [self queueMessage:UpdateTabBarMsgID data:data];
642 - (BOOL)tabBarVisible
644     return tabBarVisible;
647 - (void)showTabBar:(BOOL)enable
649     tabBarVisible = enable;
651     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
652     [self queueMessage:msgid data:nil];
655 - (void)setRows:(int)rows columns:(int)cols
657     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
659     int dim[] = { rows, cols };
660     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
662     [self queueMessage:SetTextDimensionsMsgID data:data];
665 - (void)setWindowTitle:(char *)title
667     NSMutableData *data = [NSMutableData data];
668     int len = strlen(title);
669     if (len <= 0) return;
671     [data appendBytes:&len length:sizeof(int)];
672     [data appendBytes:title length:len];
674     [self queueMessage:SetWindowTitleMsgID data:data];
677 - (void)setDocumentFilename:(char *)filename
679     NSMutableData *data = [NSMutableData data];
680     int len = filename ? strlen(filename) : 0;
682     [data appendBytes:&len length:sizeof(int)];
683     if (len > 0)
684         [data appendBytes:filename length:len];
686     [self queueMessage:SetDocumentFilenameMsgID data:data];
689 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
691     char_u *s = NULL;
693     @try {
694         [frontendProxy showSavePanelWithAttributes:attr];
696         [self waitForDialogReturn];
698         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
699             char_u *ret = (char_u*)[dialogReturn UTF8String];
700 #ifdef FEAT_MBYTE
701             ret = CONVERT_FROM_UTF8(ret);
702 #endif
703             s = vim_strsave(ret);
704 #ifdef FEAT_MBYTE
705             CONVERT_FROM_UTF8_FREE(ret);
706 #endif
707         }
709         [dialogReturn release];  dialogReturn = nil;
710     }
711     @catch (NSException *e) {
712         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
713     }
715     return (char *)s;
718 - (oneway void)setDialogReturn:(in bycopy id)obj
720     // NOTE: This is called by
721     //   - [MMVimController panelDidEnd:::], and
722     //   - [MMVimController alertDidEnd:::],
723     // to indicate that a save/open panel or alert has finished.
725     // We want to distinguish between "no dialog return yet" and "dialog
726     // returned nothing".  The former can be tested with dialogReturn == nil,
727     // the latter with dialogReturn == [NSNull null].
728     if (!obj) obj = [NSNull null];
730     if (obj != dialogReturn) {
731         [dialogReturn release];
732         dialogReturn = [obj retain];
733     }
736 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
738     int retval = 0;
740     @try {
741         [frontendProxy presentDialogWithAttributes:attr];
743         [self waitForDialogReturn];
745         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
746                 && [dialogReturn count]) {
747             retval = [[dialogReturn objectAtIndex:0] intValue];
748             if (txtfield && [dialogReturn count] > 1) {
749                 NSString *retString = [dialogReturn objectAtIndex:1];
750                 char_u *ret = (char_u*)[retString UTF8String];
751 #ifdef FEAT_MBYTE
752                 ret = CONVERT_FROM_UTF8(ret);
753 #endif
754                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
755 #ifdef FEAT_MBYTE
756                 CONVERT_FROM_UTF8_FREE(ret);
757 #endif
758             }
759         }
761         [dialogReturn release]; dialogReturn = nil;
762     }
763     @catch (NSException *e) {
764         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
765     }
767     return retval;
770 - (void)showToolbar:(int)enable flags:(int)flags
772     NSMutableData *data = [NSMutableData data];
774     [data appendBytes:&enable length:sizeof(int)];
775     [data appendBytes:&flags length:sizeof(int)];
777     [self queueMessage:ShowToolbarMsgID data:data];
780 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
782     NSMutableData *data = [NSMutableData data];
784     [data appendBytes:&ident length:sizeof(long)];
785     [data appendBytes:&type length:sizeof(int)];
787     [self queueMessage:CreateScrollbarMsgID data:data];
790 - (void)destroyScrollbarWithIdentifier:(long)ident
792     NSMutableData *data = [NSMutableData data];
793     [data appendBytes:&ident length:sizeof(long)];
795     [self queueMessage:DestroyScrollbarMsgID data:data];
798 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
800     NSMutableData *data = [NSMutableData data];
802     [data appendBytes:&ident length:sizeof(long)];
803     [data appendBytes:&visible length:sizeof(int)];
805     [self queueMessage:ShowScrollbarMsgID data:data];
808 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
810     NSMutableData *data = [NSMutableData data];
812     [data appendBytes:&ident length:sizeof(long)];
813     [data appendBytes:&pos length:sizeof(int)];
814     [data appendBytes:&len length:sizeof(int)];
816     [self queueMessage:SetScrollbarPositionMsgID data:data];
819 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
820                     identifier:(long)ident
822     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
823     float prop = (float)size/(max+1);
824     if (fval < 0) fval = 0;
825     else if (fval > 1.0f) fval = 1.0f;
826     if (prop < 0) prop = 0;
827     else if (prop > 1.0f) prop = 1.0f;
829     NSMutableData *data = [NSMutableData data];
831     [data appendBytes:&ident length:sizeof(long)];
832     [data appendBytes:&fval length:sizeof(float)];
833     [data appendBytes:&prop length:sizeof(float)];
835     [self queueMessage:SetScrollbarThumbMsgID data:data];
838 - (void)setFont:(NSFont *)font
840     NSString *fontName = [font displayName];
841     float size = [font pointSize];
842     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
843     if (len > 0) {
844         NSMutableData *data = [NSMutableData data];
846         [data appendBytes:&size length:sizeof(float)];
847         [data appendBytes:&len length:sizeof(int)];
848         [data appendBytes:[fontName UTF8String] length:len];
850         [self queueMessage:SetFontMsgID data:data];
851     }
854 - (void)setWideFont:(NSFont *)font
856     NSString *fontName = [font displayName];
857     float size = [font pointSize];
858     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
859     NSMutableData *data = [NSMutableData data];
861     [data appendBytes:&size length:sizeof(float)];
862     [data appendBytes:&len length:sizeof(int)];
863     if (len > 0)
864         [data appendBytes:[fontName UTF8String] length:len];
866     [self queueMessage:SetWideFontMsgID data:data];
869 - (void)executeActionWithName:(NSString *)name
871     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
873     if (len > 0) {
874         NSMutableData *data = [NSMutableData data];
876         [data appendBytes:&len length:sizeof(int)];
877         [data appendBytes:[name UTF8String] length:len];
879         [self queueMessage:ExecuteActionMsgID data:data];
880     }
883 - (void)setMouseShape:(int)shape
885     NSMutableData *data = [NSMutableData data];
886     [data appendBytes:&shape length:sizeof(int)];
887     [self queueMessage:SetMouseShapeMsgID data:data];
890 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
892     // Vim specifies times in milliseconds, whereas Cocoa wants them in
893     // seconds.
894     blinkWaitInterval = .001f*wait;
895     blinkOnInterval = .001f*on;
896     blinkOffInterval = .001f*off;
899 - (void)startBlink
901     if (blinkTimer) {
902         [blinkTimer invalidate];
903         [blinkTimer release];
904         blinkTimer = nil;
905     }
907     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
908             && gui.in_focus) {
909         blinkState = MMBlinkStateOn;
910         blinkTimer =
911             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
912                                               target:self
913                                             selector:@selector(blinkTimerFired:)
914                                             userInfo:nil repeats:NO] retain];
915         gui_update_cursor(TRUE, FALSE);
916         [self flushQueue:YES];
917     }
920 - (void)stopBlink
922     if (MMBlinkStateOff == blinkState) {
923         gui_update_cursor(TRUE, FALSE);
924         [self flushQueue:YES];
925     }
927     blinkState = MMBlinkStateNone;
930 - (void)adjustLinespace:(int)linespace
932     NSMutableData *data = [NSMutableData data];
933     [data appendBytes:&linespace length:sizeof(int)];
934     [self queueMessage:AdjustLinespaceMsgID data:data];
937 - (void)activate
939     [self queueMessage:ActivateMsgID data:nil];
942 - (void)setPreEditRow:(int)row column:(int)col
944     NSMutableData *data = [NSMutableData data];
945     [data appendBytes:&row length:sizeof(int)];
946     [data appendBytes:&col length:sizeof(int)];
947     [self queueMessage:SetPreEditPositionMsgID data:data];
950 - (int)lookupColorWithKey:(NSString *)key
952     if (!(key && [key length] > 0))
953         return INVALCOLOR;
955     NSString *stripKey = [[[[key lowercaseString]
956         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
957             componentsSeparatedByString:@" "]
958                componentsJoinedByString:@""];
960     if (stripKey && [stripKey length] > 0) {
961         // First of all try to lookup key in the color dictionary; note that
962         // all keys in this dictionary are lowercase with no whitespace.
963         id obj = [colorDict objectForKey:stripKey];
964         if (obj) return [obj intValue];
966         // The key was not in the dictionary; is it perhaps of the form
967         // #rrggbb?
968         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
969             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
970             [scanner setScanLocation:1];
971             unsigned hex = 0;
972             if ([scanner scanHexInt:&hex]) {
973                 return (int)hex;
974             }
975         }
977         // As a last resort, check if it is one of the system defined colors.
978         // The keys in this dictionary are also lowercase with no whitespace.
979         obj = [sysColorDict objectForKey:stripKey];
980         if (obj) {
981             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
982             if (col) {
983                 float r, g, b, a;
984                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
985                 [col getRed:&r green:&g blue:&b alpha:&a];
986                 return (((int)(r*255+.5f) & 0xff) << 16)
987                      + (((int)(g*255+.5f) & 0xff) << 8)
988                      +  ((int)(b*255+.5f) & 0xff);
989             }
990         }
991     }
993     //NSLog(@"WARNING: No color with key %@ found.", stripKey);
994     return INVALCOLOR;
997 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
999     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1000     id obj;
1002     while ((obj = [e nextObject])) {
1003         if ([value isEqual:obj])
1004             return YES;
1005     }
1007     return NO;
1010 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1012     NSMutableData *data = [NSMutableData data];
1013     [data appendBytes:&fuoptions length:sizeof(int)];
1014     bg = MM_COLOR(bg);
1015     [data appendBytes:&bg length:sizeof(int)];
1016     [self queueMessage:EnterFullscreenMsgID data:data];
1019 - (void)leaveFullscreen
1021     [self queueMessage:LeaveFullscreenMsgID data:nil];
1024 - (void)setFullscreenBackgroundColor:(int)color
1026     NSMutableData *data = [NSMutableData data];
1027     color = MM_COLOR(color);
1028     [data appendBytes:&color length:sizeof(int)];
1030     [self queueMessage:SetFullscreenColorMsgID data:data];
1033 - (void)setAntialias:(BOOL)antialias
1035     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1037     [self queueMessage:msgid data:nil];
1040 - (void)updateModifiedFlag
1042     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1043     // vice versa.
1044     int msgid = [self checkForModifiedBuffers]
1045             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1047     [self queueMessage:msgid data:nil];
1050 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1052     // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1053     // queue is processed since that only happens in waitForInput: (and Vim
1054     // regularly checks for Ctrl-C in between waiting for input).
1056     BOOL interrupt = NO;
1057     if (msgid == InterruptMsgID) {
1058         interrupt = YES;
1059     } else if (InsertTextMsgID == msgid && data != nil && [data length] == 1) {
1060         char_u *str = (char_u*)[data bytes];
1061         if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1062                 (str[0] == intr_char && intr_char != Ctrl_C))
1063             interrupt = YES;
1064     }
1066     if (interrupt) {
1067         got_int = TRUE;
1068         [inputQueue removeAllObjects];
1069         return;
1070     }
1072     // Remove all previous instances of this message from the input queue, else
1073     // the input queue may fill up as a result of Vim not being able to keep up
1074     // with the speed at which new messages are received.  This avoids annoying
1075     // situations such as when the keyboard repeat rate is higher than what Vim
1076     // can cope with (which would cause a 'stutter' when scrolling by holding
1077     // down 'j' and then when 'j' was released the screen kept scrolling for a
1078     // little while).
1080     int i, count = [inputQueue count];
1081     for (i = 1; i < count; i+=2) {
1082         if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1083             [inputQueue removeObjectAtIndex:i];
1084             [inputQueue removeObjectAtIndex:i-1];
1085             break;
1086         }
1087     }
1089     [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1090     [inputQueue addObject:(data ? (id)data : [NSNull null])];
1093 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1095     // This is just a convenience method that allows the frontend to delay
1096     // sending messages.
1097     int i, count = [messages count];
1098     for (i = 1; i < count; i+=2)
1099         [self processInput:[[messages objectAtIndex:i-1] intValue]
1100                       data:[messages objectAtIndex:i]];
1103 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1104                   errorString:(out bycopy NSString **)errstr
1106     return evalExprCocoa(expr, errstr);
1110 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1112     NSString *eval = nil;
1113     char_u *s = (char_u*)[expr UTF8String];
1115 #ifdef FEAT_MBYTE
1116     s = CONVERT_FROM_UTF8(s);
1117 #endif
1119     char_u *res = eval_client_expr_to_string(s);
1121 #ifdef FEAT_MBYTE
1122     CONVERT_FROM_UTF8_FREE(s);
1123 #endif
1125     if (res != NULL) {
1126         s = res;
1127 #ifdef FEAT_MBYTE
1128         s = CONVERT_TO_UTF8(s);
1129 #endif
1130         eval = [NSString stringWithUTF8String:(char*)s];
1131 #ifdef FEAT_MBYTE
1132         CONVERT_TO_UTF8_FREE(s);
1133 #endif
1134         vim_free(res);
1135     }
1137     return eval;
1140 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1142     // TODO: This method should share code with clip_mch_request_selection().
1144     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1145         // If there is no pasteboard, return YES to indicate that there is text
1146         // to copy.
1147         if (!pboard)
1148             return YES;
1150         clip_copy_selection();
1152         // Get the text to put on the pasteboard.
1153         long_u llen = 0; char_u *str = 0;
1154         int type = clip_convert_selection(&str, &llen, &clip_star);
1155         if (type < 0)
1156             return NO;
1157         
1158         // TODO: Avoid overflow.
1159         int len = (int)llen;
1160 #ifdef FEAT_MBYTE
1161         if (output_conv.vc_type != CONV_NONE) {
1162             char_u *conv_str = string_convert(&output_conv, str, &len);
1163             if (conv_str) {
1164                 vim_free(str);
1165                 str = conv_str;
1166             }
1167         }
1168 #endif
1170         NSString *string = [[NSString alloc]
1171             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1173         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1174         [pboard declareTypes:types owner:nil];
1175         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1176     
1177         [string release];
1178         vim_free(str);
1180         return ok;
1181     }
1183     return NO;
1186 - (oneway void)addReply:(in bycopy NSString *)reply
1187                  server:(in byref id <MMVimServerProtocol>)server
1189     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1191     // Replies might come at any time and in any order so we keep them in an
1192     // array inside a dictionary with the send port used as key.
1194     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1195     // HACK! Assume connection uses mach ports.
1196     int port = [(NSMachPort*)[conn sendPort] machPort];
1197     NSNumber *key = [NSNumber numberWithInt:port];
1199     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1200     if (!replies) {
1201         replies = [NSMutableArray array];
1202         [serverReplyDict setObject:replies forKey:key];
1203     }
1205     [replies addObject:reply];
1208 - (void)addInput:(in bycopy NSString *)input
1209                  client:(in byref id <MMVimClientProtocol>)client
1211     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1213     [self addInput:input];
1214     [self addClient:(id)client];
1217 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1218                  client:(in byref id <MMVimClientProtocol>)client
1220     [self addClient:(id)client];
1221     return [self evaluateExpression:expr];
1224 - (void)registerServerWithName:(NSString *)name
1226     NSString *svrName = name;
1227     NSConnection *svrConn = [NSConnection defaultConnection];
1228     unsigned i;
1230     for (i = 0; i < MMServerMax; ++i) {
1231         NSString *connName = [self connectionNameFromServerName:svrName];
1233         if ([svrConn registerName:connName]) {
1234             //NSLog(@"Registered server with name: %@", svrName);
1236             // TODO: Set request/reply time-outs to something else?
1237             //
1238             // Don't wait for requests (time-out means that the message is
1239             // dropped).
1240             [svrConn setRequestTimeout:0];
1241             //[svrConn setReplyTimeout:MMReplyTimeout];
1242             [svrConn setRootObject:self];
1244             char_u *s = (char_u*)[svrName UTF8String];
1245 #ifdef FEAT_MBYTE
1246             s = CONVERT_FROM_UTF8(s);
1247 #endif
1248             // NOTE: 'serverName' is a global variable
1249             serverName = vim_strsave(s);
1250 #ifdef FEAT_MBYTE
1251             CONVERT_FROM_UTF8_FREE(s);
1252 #endif
1253 #ifdef FEAT_EVAL
1254             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1255 #endif
1256 #ifdef FEAT_TITLE
1257             need_maketitle = TRUE;
1258 #endif
1259             [self queueMessage:SetServerNameMsgID data:
1260                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1261             break;
1262         }
1264         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1265     }
1268 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1269                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1270               silent:(BOOL)silent
1272     // NOTE: If 'name' equals 'serverName' then the request is local (client
1273     // and server are the same).  This case is not handled separately, so a
1274     // connection will be set up anyway (this simplifies the code).
1276     NSConnection *conn = [self connectionForServerName:name];
1277     if (!conn) {
1278         if (!silent) {
1279             char_u *s = (char_u*)[name UTF8String];
1280 #ifdef FEAT_MBYTE
1281             s = CONVERT_FROM_UTF8(s);
1282 #endif
1283             EMSG2(_(e_noserver), s);
1284 #ifdef FEAT_MBYTE
1285             CONVERT_FROM_UTF8_FREE(s);
1286 #endif
1287         }
1288         return NO;
1289     }
1291     if (port) {
1292         // HACK! Assume connection uses mach ports.
1293         *port = [(NSMachPort*)[conn sendPort] machPort];
1294     }
1296     id proxy = [conn rootProxy];
1297     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1299     @try {
1300         if (expr) {
1301             NSString *eval = [proxy evaluateExpression:string client:self];
1302             if (reply) {
1303                 if (eval) {
1304                     char_u *r = (char_u*)[eval UTF8String];
1305 #ifdef FEAT_MBYTE
1306                     r = CONVERT_FROM_UTF8(r);
1307 #endif
1308                     *reply = vim_strsave(r);
1309 #ifdef FEAT_MBYTE
1310                     CONVERT_FROM_UTF8_FREE(r);
1311 #endif
1312                 } else {
1313                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1314                 }
1315             }
1317             if (!eval)
1318                 return NO;
1319         } else {
1320             [proxy addInput:string client:self];
1321         }
1322     }
1323     @catch (NSException *e) {
1324         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1325         return NO;
1326     }
1328     return YES;
1331 - (NSArray *)serverList
1333     NSArray *list = nil;
1335     if ([self connection]) {
1336         id proxy = [connection rootProxy];
1337         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1339         @try {
1340             list = [proxy serverList];
1341         }
1342         @catch (NSException *e) {
1343             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1344         }
1345     } else {
1346         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1347     }
1349     return list;
1352 - (NSString *)peekForReplyOnPort:(int)port
1354     //NSLog(@"%s%d", _cmd, port);
1356     NSNumber *key = [NSNumber numberWithInt:port];
1357     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1358     if (replies && [replies count]) {
1359         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1360         //        [replies objectAtIndex:0]);
1361         return [replies objectAtIndex:0];
1362     }
1364     //NSLog(@"    No replies");
1365     return nil;
1368 - (NSString *)waitForReplyOnPort:(int)port
1370     //NSLog(@"%s%d", _cmd, port);
1371     
1372     NSConnection *conn = [self connectionForServerPort:port];
1373     if (!conn)
1374         return nil;
1376     NSNumber *key = [NSNumber numberWithInt:port];
1377     NSMutableArray *replies = nil;
1378     NSString *reply = nil;
1380     // Wait for reply as long as the connection to the server is valid (unless
1381     // user interrupts wait with Ctrl-C).
1382     while (!got_int && [conn isValid] &&
1383             !(replies = [serverReplyDict objectForKey:key])) {
1384         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1385                                  beforeDate:[NSDate distantFuture]];
1386     }
1388     if (replies) {
1389         if ([replies count] > 0) {
1390             reply = [[replies objectAtIndex:0] retain];
1391             //NSLog(@"    Got reply: %@", reply);
1392             [replies removeObjectAtIndex:0];
1393             [reply autorelease];
1394         }
1396         if ([replies count] == 0)
1397             [serverReplyDict removeObjectForKey:key];
1398     }
1400     return reply;
1403 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1405     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1406     if (client) {
1407         @try {
1408             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1409             [client addReply:reply server:self];
1410             return YES;
1411         }
1412         @catch (NSException *e) {
1413             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1414         }
1415     } else {
1416         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1417     }
1419     return NO;
1422 - (BOOL)waitForAck
1424     return waitForAck;
1427 - (void)setWaitForAck:(BOOL)yn
1429     waitForAck = yn;
1432 - (void)waitForConnectionAcknowledgement
1434     if (!waitForAck) return;
1436     while (waitForAck && !got_int && [connection isValid] && !isTerminating) {
1437         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1438                                  beforeDate:[NSDate distantFuture]];
1439         //NSLog(@"  waitForAck=%d got_int=%d isTerminating=%d isValid=%d",
1440         //        waitForAck, got_int, isTerminating, [connection isValid]);
1441     }
1443     if (waitForAck) {
1444         // Never received a connection acknowledgement, so die.
1445         [[NSNotificationCenter defaultCenter] removeObserver:self];
1446         [frontendProxy release];  frontendProxy = nil;
1448         // NOTE: We intentionally do not call mch_exit() since this in turn
1449         // will lead to -[MMBackend exit] getting called which we want to
1450         // avoid.
1451         usleep(MMExitProcessDelay);
1452         exit(0);
1453     }
1455     [self processInputQueue];
1458 - (oneway void)acknowledgeConnection
1460     //NSLog(@"%s", _cmd);
1461     waitForAck = NO;
1464 @end // MMBackend
1468 @implementation MMBackend (Private)
1470 - (void)waitForDialogReturn
1472     // Keep processing the run loop until a dialog returns.  To avoid getting
1473     // stuck in an endless loop (could happen if the setDialogReturn: message
1474     // was lost) we also do some paranoia checks.
1475     //
1476     // Note that in Cocoa the user can still resize windows and select menu
1477     // items while a sheet is being displayed, so we can't just wait for the
1478     // first message to arrive and assume that is the setDialogReturn: call.
1480     while (nil == dialogReturn && !got_int && [connection isValid]
1481             && !isTerminating)
1482         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1483                                  beforeDate:[NSDate distantFuture]];
1485     // Search for any resize messages on the input queue.  All other messages
1486     // on the input queue are dropped.  The reason why we single out resize
1487     // messages is because the user may have resized the window while a sheet
1488     // was open.
1489     int i, count = [inputQueue count];
1490     if (count > 0) {
1491         id textDimData = nil;
1492         if (count%2 == 0) {
1493             for (i = count-2; i >= 0; i -= 2) {
1494                 int msgid = [[inputQueue objectAtIndex:i] intValue];
1495                 if (SetTextDimensionsMsgID == msgid) {
1496                     textDimData = [[inputQueue objectAtIndex:i+1] retain];
1497                     break;
1498                 }
1499             }
1500         }
1502         [inputQueue removeAllObjects];
1504         if (textDimData) {
1505             [inputQueue addObject:
1506                     [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1507             [inputQueue addObject:textDimData];
1508             [textDimData release];
1509         }
1510     }
1513 - (void)insertVimStateMessage
1515     // NOTE: This is the place to add Vim state that needs to be accessed from
1516     // MacVim.  Do not add state that could potentially require lots of memory
1517     // since this message gets sent each time the output queue is forcibly
1518     // flushed (e.g. storing the currently selected text would be a bad idea).
1519     // We take this approach of "pushing" the state to MacVim to avoid having
1520     // to make synchronous calls from MacVim to Vim in order to get state.
1522     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1523         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1524         [NSNumber numberWithInt:p_mh], @"p_mh",
1525         [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1526         nil];
1528     // Put the state before all other messages.
1529     int msgid = SetVimStateMsgID;
1530     [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1531     [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1532                       atIndex:0];
1535 - (void)processInputQueue
1537     if ([inputQueue count] == 0) return;
1539     // NOTE: One of the input events may cause this method to be called
1540     // recursively, so copy the input queue to a local variable and clear the
1541     // queue before starting to process input events (otherwise we could get
1542     // stuck in an endless loop).
1543     NSArray *q = [inputQueue copy];
1544     unsigned i, count = [q count];
1546     [inputQueue removeAllObjects];
1548     for (i = 1; i < count; i+=2) {
1549         int msgid = [[q objectAtIndex:i-1] intValue];
1550         id data = [q objectAtIndex:i];
1551         if ([data isEqual:[NSNull null]])
1552             data = nil;
1554         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1555         [self handleInputEvent:msgid data:data];
1556     }
1558     [q release];
1559     //NSLog(@"Clear input event queue");
1562 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1564     if (InsertTextMsgID == msgid) {
1565         [self handleInsertText:data];
1566     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1567         if (!data) return;
1568         const void *bytes = [data bytes];
1569         int mods = *((int*)bytes);  bytes += sizeof(int);
1570         int len = *((int*)bytes);  bytes += sizeof(int);
1571         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1572                                               encoding:NSUTF8StringEncoding];
1573         mods = eventModifierFlagsToVimModMask(mods);
1575         [self handleKeyDown:key modifiers:mods];
1577         [key release];
1578     } else if (ScrollWheelMsgID == msgid) {
1579         if (!data) return;
1580         const void *bytes = [data bytes];
1582         int row = *((int*)bytes);  bytes += sizeof(int);
1583         int col = *((int*)bytes);  bytes += sizeof(int);
1584         int flags = *((int*)bytes);  bytes += sizeof(int);
1585         float dy = *((float*)bytes);  bytes += sizeof(float);
1587         int button = MOUSE_5;
1588         if (dy > 0) button = MOUSE_4;
1590         flags = eventModifierFlagsToVimMouseModMask(flags);
1592         int numLines = (int)round(dy);
1593         if (numLines < 0) numLines = -numLines;
1594         if (numLines == 0) numLines = 1;
1596 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1597         gui.scroll_wheel_force = numLines;
1598 #endif
1600         gui_send_mouse_event(button, col, row, NO, flags);
1601     } else if (MouseDownMsgID == msgid) {
1602         if (!data) return;
1603         const void *bytes = [data bytes];
1605         int row = *((int*)bytes);  bytes += sizeof(int);
1606         int col = *((int*)bytes);  bytes += sizeof(int);
1607         int button = *((int*)bytes);  bytes += sizeof(int);
1608         int flags = *((int*)bytes);  bytes += sizeof(int);
1609         int count = *((int*)bytes);  bytes += sizeof(int);
1611         button = eventButtonNumberToVimMouseButton(button);
1612         if (button >= 0) {
1613             flags = eventModifierFlagsToVimMouseModMask(flags);
1614             gui_send_mouse_event(button, col, row, count>1, flags);
1615         }
1616     } else if (MouseUpMsgID == msgid) {
1617         if (!data) return;
1618         const void *bytes = [data bytes];
1620         int row = *((int*)bytes);  bytes += sizeof(int);
1621         int col = *((int*)bytes);  bytes += sizeof(int);
1622         int flags = *((int*)bytes);  bytes += sizeof(int);
1624         flags = eventModifierFlagsToVimMouseModMask(flags);
1626         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1627     } else if (MouseDraggedMsgID == msgid) {
1628         if (!data) return;
1629         const void *bytes = [data bytes];
1631         int row = *((int*)bytes);  bytes += sizeof(int);
1632         int col = *((int*)bytes);  bytes += sizeof(int);
1633         int flags = *((int*)bytes);  bytes += sizeof(int);
1635         flags = eventModifierFlagsToVimMouseModMask(flags);
1637         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1638     } else if (MouseMovedMsgID == msgid) {
1639         const void *bytes = [data bytes];
1640         int row = *((int*)bytes);  bytes += sizeof(int);
1641         int col = *((int*)bytes);  bytes += sizeof(int);
1643         gui_mouse_moved(col, row);
1644     } else if (AddInputMsgID == msgid) {
1645         NSString *string = [[NSString alloc] initWithData:data
1646                 encoding:NSUTF8StringEncoding];
1647         if (string) {
1648             [self addInput:string];
1649             [string release];
1650         }
1651     } else if (TerminateNowMsgID == msgid) {
1652         isTerminating = YES;
1653     } else if (SelectTabMsgID == msgid) {
1654         if (!data) return;
1655         const void *bytes = [data bytes];
1656         int idx = *((int*)bytes) + 1;
1657         //NSLog(@"Selecting tab %d", idx);
1658         send_tabline_event(idx);
1659     } else if (CloseTabMsgID == msgid) {
1660         if (!data) return;
1661         const void *bytes = [data bytes];
1662         int idx = *((int*)bytes) + 1;
1663         //NSLog(@"Closing tab %d", idx);
1664         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1665     } else if (AddNewTabMsgID == msgid) {
1666         //NSLog(@"Adding new tab");
1667         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1668     } else if (DraggedTabMsgID == msgid) {
1669         if (!data) return;
1670         const void *bytes = [data bytes];
1671         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1672         // based.
1673         int idx = *((int*)bytes);
1675         tabpage_move(idx);
1676     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1677             || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1678         if (!data) return;
1679         const void *bytes = [data bytes];
1680         int rows = Rows;
1681         if (SetTextColumnsMsgID != msgid) {
1682             rows = *((int*)bytes);  bytes += sizeof(int);
1683         }
1684         int cols = Columns;
1685         if (SetTextRowsMsgID != msgid) {
1686             cols = *((int*)bytes);  bytes += sizeof(int);
1687         }
1689         NSData *d = data;
1690         if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1691             int dim[2] = { rows, cols };
1692             d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1693             msgid = SetTextDimensionsMsgID;
1694         }
1696         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1697         // gui_resize_shell(), so we have to manually set the rows and columns
1698         // here.  (MacVim doesn't change the rows and columns to avoid
1699         // inconsistent states between Vim and MacVim.)
1700         [self queueMessage:msgid data:d];
1702         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1703         gui_resize_shell(cols, rows);
1704     } else if (ExecuteMenuMsgID == msgid) {
1705         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1706         if (attrs) {
1707             NSArray *desc = [attrs objectForKey:@"descriptor"];
1708             vimmenu_T *menu = menu_for_descriptor(desc);
1709             if (menu)
1710                 gui_menu_cb(menu);
1711         }
1712     } else if (ToggleToolbarMsgID == msgid) {
1713         [self handleToggleToolbar];
1714     } else if (ScrollbarEventMsgID == msgid) {
1715         [self handleScrollbarEvent:data];
1716     } else if (SetFontMsgID == msgid) {
1717         [self handleSetFont:data];
1718     } else if (VimShouldCloseMsgID == msgid) {
1719         gui_shell_closed();
1720     } else if (DropFilesMsgID == msgid) {
1721         [self handleDropFiles:data];
1722     } else if (DropStringMsgID == msgid) {
1723         [self handleDropString:data];
1724     } else if (GotFocusMsgID == msgid) {
1725         if (!gui.in_focus)
1726             [self focusChange:YES];
1727     } else if (LostFocusMsgID == msgid) {
1728         if (gui.in_focus)
1729             [self focusChange:NO];
1730     } else if (SetMouseShapeMsgID == msgid) {
1731         const void *bytes = [data bytes];
1732         int shape = *((int*)bytes);  bytes += sizeof(int);
1733         update_mouseshape(shape);
1734     } else if (XcodeModMsgID == msgid) {
1735         [self handleXcodeMod:data];
1736     } else if (OpenWithArgumentsMsgID == msgid) {
1737         [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1738     } else {
1739         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1740     }
1743 + (NSDictionary *)specialKeys
1745     static NSDictionary *specialKeys = nil;
1747     if (!specialKeys) {
1748         NSBundle *mainBundle = [NSBundle mainBundle];
1749         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1750                                               ofType:@"plist"];
1751         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1752     }
1754     return specialKeys;
1757 - (void)handleInsertText:(NSData *)data
1759     if (!data) return;
1761     NSString *key = [[NSString alloc] initWithData:data
1762                                           encoding:NSUTF8StringEncoding];
1763     char_u *str = (char_u*)[key UTF8String];
1764     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1766 #ifdef FEAT_MBYTE
1767     char_u *conv_str = NULL;
1768     if (input_conv.vc_type != CONV_NONE) {
1769         conv_str = string_convert(&input_conv, str, &len);
1770         if (conv_str)
1771             str = conv_str;
1772     }
1773 #endif
1775     for (i = 0; i < len; ++i) {
1776         add_to_input_buf(str+i, 1);
1777         if (CSI == str[i]) {
1778             // NOTE: If the converted string contains the byte CSI, then it
1779             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1780             // won't work.
1781             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1782             add_to_input_buf(extra, 2);
1783         }
1784     }
1786 #ifdef FEAT_MBYTE
1787     if (conv_str)
1788         vim_free(conv_str);
1789 #endif
1790     [key release];
1793 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1795     char_u special[3];
1796     char_u modChars[3];
1797     char_u *chars = (char_u*)[key UTF8String];
1798 #ifdef FEAT_MBYTE
1799     char_u *conv_str = NULL;
1800 #endif
1801     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1803     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1804     // that new keys can easily be added.
1805     NSString *specialString = [[MMBackend specialKeys]
1806             objectForKey:key];
1807     if (specialString && [specialString length] > 1) {
1808         //NSLog(@"special key: %@", specialString);
1809         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1810                 [specialString characterAtIndex:1]);
1812         ikey = simplify_key(ikey, &mods);
1813         if (ikey == CSI)
1814             ikey = K_CSI;
1816         special[0] = CSI;
1817         special[1] = K_SECOND(ikey);
1818         special[2] = K_THIRD(ikey);
1820         chars = special;
1821         length = 3;
1822     } else if (1 == length && TAB == chars[0]) {
1823         // Tab is a trouble child:
1824         // - <Tab> is added to the input buffer as is
1825         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1826         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1827         //   to be converted to utf-8
1828         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1829         // - <C-Tab> is reserved by Mac OS X
1830         // - <D-Tab> is reserved by Mac OS X
1831         chars = special;
1832         special[0] = TAB;
1833         length = 1;
1835         if (mods & MOD_MASK_SHIFT) {
1836             mods &= ~MOD_MASK_SHIFT;
1837             special[0] = CSI;
1838             special[1] = K_SECOND(K_S_TAB);
1839             special[2] = K_THIRD(K_S_TAB);
1840             length = 3;
1841         } else if (mods & MOD_MASK_ALT) {
1842             int mtab = 0x80 | TAB;
1843 #ifdef FEAT_MBYTE
1844             if (enc_utf8) {
1845                 // Convert to utf-8
1846                 special[0] = (mtab >> 6) + 0xc0;
1847                 special[1] = mtab & 0xbf;
1848                 length = 2;
1849             } else
1850 #endif
1851             {
1852                 special[0] = mtab;
1853                 length = 1;
1854             }
1855             mods &= ~MOD_MASK_ALT;
1856         }
1857     } else if (length > 0) {
1858         unichar c = [key characterAtIndex:0];
1860         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1861         //        [key characterAtIndex:0], mods);
1863         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1864         // cleared since they are already added to the key by the AppKit.
1865         // Unfortunately, the only way to deal with when to clear the modifiers
1866         // or not seems to be to have hard-wired rules like this.
1867         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1868                     || 0x9 == c || 0xd == c || ESC == c) ) {
1869             mods &= ~MOD_MASK_SHIFT;
1870             mods &= ~MOD_MASK_CTRL;
1871             //NSLog(@"clear shift ctrl");
1872         }
1874         // HACK!  All Option+key presses go via 'insert text' messages, except
1875         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1876         // not work to map to it.
1877         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1878             //NSLog(@"clear alt");
1879             mods &= ~MOD_MASK_ALT;
1880         }
1882 #ifdef FEAT_MBYTE
1883         if (input_conv.vc_type != CONV_NONE) {
1884             conv_str = string_convert(&input_conv, chars, &length);
1885             if (conv_str)
1886                 chars = conv_str;
1887         }
1888 #endif
1889     }
1891     if (chars && length > 0) {
1892         if (mods) {
1893             //NSLog(@"adding mods: %d", mods);
1894             modChars[0] = CSI;
1895             modChars[1] = KS_MODIFIER;
1896             modChars[2] = mods;
1897             add_to_input_buf(modChars, 3);
1898         }
1900         //NSLog(@"add to input buf: 0x%x", chars[0]);
1901         // TODO: Check for CSI bytes?
1902         add_to_input_buf(chars, length);
1903     }
1905 #ifdef FEAT_MBYTE
1906     if (conv_str)
1907         vim_free(conv_str);
1908 #endif
1911 - (void)queueMessage:(int)msgid data:(NSData *)data
1913     //if (msgid != EnableMenuItemMsgID)
1914     //    NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1916     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1917     if (data)
1918         [outputQueue addObject:data];
1919     else
1920         [outputQueue addObject:[NSData data]];
1923 - (void)connectionDidDie:(NSNotification *)notification
1925     // If the main connection to MacVim is lost this means that MacVim was
1926     // either quit (by the user chosing Quit on the MacVim menu), or it has
1927     // crashed.  In the former case the flag 'isTerminating' is set and we then
1928     // quit cleanly; in the latter case we make sure the swap files are left
1929     // for recovery.
1930     //
1931     // NOTE: This is not called if a Vim controller invalidates its connection.
1933     //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1934     if (isTerminating)
1935         getout(0);
1936     else
1937         getout_preserve_modified(1);
1940 - (void)blinkTimerFired:(NSTimer *)timer
1942     NSTimeInterval timeInterval = 0;
1944     [blinkTimer release];
1945     blinkTimer = nil;
1947     if (MMBlinkStateOn == blinkState) {
1948         gui_undraw_cursor();
1949         blinkState = MMBlinkStateOff;
1950         timeInterval = blinkOffInterval;
1951     } else if (MMBlinkStateOff == blinkState) {
1952         gui_update_cursor(TRUE, FALSE);
1953         blinkState = MMBlinkStateOn;
1954         timeInterval = blinkOnInterval;
1955     }
1957     if (timeInterval > 0) {
1958         blinkTimer = 
1959             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1960                                             selector:@selector(blinkTimerFired:)
1961                                             userInfo:nil repeats:NO] retain];
1962         [self flushQueue:YES];
1963     }
1966 - (void)focusChange:(BOOL)on
1968     gui_focus_change(on);
1971 - (void)handleToggleToolbar
1973     // If 'go' contains 'T', then remove it, else add it.
1975     char_u go[sizeof(GO_ALL)+2];
1976     char_u *p;
1977     int len;
1979     STRCPY(go, p_go);
1980     p = vim_strchr(go, GO_TOOLBAR);
1981     len = STRLEN(go);
1983     if (p != NULL) {
1984         char_u *end = go + len;
1985         while (p < end) {
1986             p[0] = p[1];
1987             ++p;
1988         }
1989     } else {
1990         go[len] = GO_TOOLBAR;
1991         go[len+1] = NUL;
1992     }
1994     set_option_value((char_u*)"guioptions", 0, go, 0);
1996     [self redrawScreen];
1999 - (void)handleScrollbarEvent:(NSData *)data
2001     if (!data) return;
2003     const void *bytes = [data bytes];
2004     long ident = *((long*)bytes);  bytes += sizeof(long);
2005     int hitPart = *((int*)bytes);  bytes += sizeof(int);
2006     float fval = *((float*)bytes);  bytes += sizeof(float);
2007     scrollbar_T *sb = gui_find_scrollbar(ident);
2009     if (sb) {
2010         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2011         long value = sb_info->value;
2012         long size = sb_info->size;
2013         long max = sb_info->max;
2014         BOOL isStillDragging = NO;
2015         BOOL updateKnob = YES;
2017         switch (hitPart) {
2018         case NSScrollerDecrementPage:
2019             value -= (size > 2 ? size - 2 : 1);
2020             break;
2021         case NSScrollerIncrementPage:
2022             value += (size > 2 ? size - 2 : 1);
2023             break;
2024         case NSScrollerDecrementLine:
2025             --value;
2026             break;
2027         case NSScrollerIncrementLine:
2028             ++value;
2029             break;
2030         case NSScrollerKnob:
2031             isStillDragging = YES;
2032             // fall through ...
2033         case NSScrollerKnobSlot:
2034             value = (long)(fval * (max - size + 1));
2035             // fall through ...
2036         default:
2037             updateKnob = NO;
2038             break;
2039         }
2041         //NSLog(@"value %d -> %d", sb_info->value, value);
2042         gui_drag_scrollbar(sb, value, isStillDragging);
2044         if (updateKnob) {
2045             // Dragging the knob or option+clicking automatically updates
2046             // the knob position (on the actual NSScroller), so we only
2047             // need to set the knob position in the other cases.
2048             if (sb->wp) {
2049                 // Update both the left&right vertical scrollbars.
2050                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2051                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2052                 [self setScrollbarThumbValue:value size:size max:max
2053                                   identifier:identLeft];
2054                 [self setScrollbarThumbValue:value size:size max:max
2055                                   identifier:identRight];
2056             } else {
2057                 // Update the horizontal scrollbar.
2058                 [self setScrollbarThumbValue:value size:size max:max
2059                                   identifier:ident];
2060             }
2061         }
2062     }
2065 - (void)handleSetFont:(NSData *)data
2067     if (!data) return;
2069     const void *bytes = [data bytes];
2070     float pointSize = *((float*)bytes);  bytes += sizeof(float);
2071     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2072     bytes += sizeof(unsigned);  // len not used
2074     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2075     [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
2076     char_u *s = (char_u*)[name UTF8String];
2078 #ifdef FEAT_MBYTE
2079     s = CONVERT_FROM_UTF8(s);
2080 #endif
2082     set_option_value((char_u*)"guifont", 0, s, 0);
2084 #ifdef FEAT_MBYTE
2085     CONVERT_FROM_UTF8_FREE(s);
2086 #endif
2088     [self redrawScreen];
2091 - (void)handleDropFiles:(NSData *)data
2093     // TODO: Get rid of this method; instead use Vim script directly.  At the
2094     // moment I know how to do this to open files in tabs, but I'm not sure how
2095     // to add the filenames to the command line when in command line mode.
2097     if (!data) return;
2099     NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2100     if (!args) return;
2102     id obj = [args objectForKey:@"forceOpen"];
2103     BOOL forceOpen = YES;
2104     if (obj)
2105         forceOpen = [obj boolValue];
2107     NSArray *filenames = [args objectForKey:@"filenames"];
2108     if (!(filenames && [filenames count] > 0)) return;
2110 #ifdef FEAT_DND
2111     if (!forceOpen && (State & CMDLINE)) {
2112         // HACK!  If Vim is in command line mode then the files names
2113         // should be added to the command line, instead of opening the
2114         // files in tabs (unless forceOpen is set).  This is taken care of by
2115         // gui_handle_drop().
2116         int n = [filenames count];
2117         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2118         if (fnames) {
2119             int i = 0;
2120             for (i = 0; i < n; ++i)
2121                 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2123             // NOTE!  This function will free 'fnames'.
2124             // HACK!  It is assumed that the 'x' and 'y' arguments are
2125             // unused when in command line mode.
2126             gui_handle_drop(0, 0, 0, fnames, n);
2127         }
2128     } else
2129 #endif // FEAT_DND
2130     {
2131         [self handleOpenWithArguments:args];
2132     }
2135 - (void)handleDropString:(NSData *)data
2137     if (!data) return;
2139 #ifdef FEAT_DND
2140     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2141     const void *bytes = [data bytes];
2142     int len = *((int*)bytes);  bytes += sizeof(int);
2143     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2145     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2146     NSRange range = { 0, [string length] };
2147     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2148                                          withString:@"\x0a" options:0
2149                                               range:range];
2150     if (0 == n) {
2151         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2152                                        options:0 range:range];
2153     }
2155     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2156     char_u *s = (char_u*)[string UTF8String];
2157 #ifdef FEAT_MBYTE
2158     if (input_conv.vc_type != CONV_NONE)
2159         s = string_convert(&input_conv, s, &len);
2160 #endif
2161     dnd_yank_drag_data(s, len);
2162 #ifdef FEAT_MBYTE
2163     if (input_conv.vc_type != CONV_NONE)
2164         vim_free(s);
2165 #endif
2166     add_to_input_buf(dropkey, sizeof(dropkey));
2167 #endif // FEAT_DND
2170 - (void)startOdbEditWithArguments:(NSDictionary *)args
2172 #ifdef FEAT_ODB_EDITOR
2173     id obj = [args objectForKey:@"remoteID"];
2174     if (!obj) return;
2176     OSType serverID = [obj unsignedIntValue];
2177     NSString *remotePath = [args objectForKey:@"remotePath"];
2179     NSAppleEventDescriptor *token = nil;
2180     NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2181     obj = [args objectForKey:@"remoteTokenDescType"];
2182     if (tokenData && obj) {
2183         DescType tokenType = [obj unsignedLongValue];
2184         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2185                                                                 data:tokenData];
2186     }
2188     NSArray *filenames = [args objectForKey:@"filenames"];
2189     unsigned i, numFiles = [filenames count];
2190     for (i = 0; i < numFiles; ++i) {
2191         NSString *filename = [filenames objectAtIndex:i];
2192         char_u *s = [filename vimStringSave];
2193         buf_T *buf = buflist_findname(s);
2194         vim_free(s);
2196         if (buf) {
2197             if (buf->b_odb_token) {
2198                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2199                 buf->b_odb_token = NULL;
2200             }
2202             if (buf->b_odb_fname) {
2203                 vim_free(buf->b_odb_fname);
2204                 buf->b_odb_fname = NULL;
2205             }
2207             buf->b_odb_server_id = serverID;
2209             if (token)
2210                 buf->b_odb_token = [token retain];
2211             if (remotePath)
2212                 buf->b_odb_fname = [remotePath vimStringSave];
2213         } else {
2214             NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2215                     filename);
2216         }
2217     }
2218 #endif // FEAT_ODB_EDITOR
2221 - (void)handleXcodeMod:(NSData *)data
2223 #if 0
2224     const void *bytes = [data bytes];
2225     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2226     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2227     if (0 == len)
2228         return;
2230     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2231             descriptorWithDescriptorType:type
2232                                    bytes:bytes
2233                                   length:len];
2234 #endif
2237 - (void)handleOpenWithArguments:(NSDictionary *)args
2239     // ARGUMENT:                DESCRIPTION:
2240     // -------------------------------------------------------------
2241     // filenames                list of filenames
2242     // dontOpen                 don't open files specified in above argument
2243     // layout                   which layout to use to open files
2244     // selectionRange           range of lines to select
2245     // searchText               string to search for
2246     // cursorLine               line to position the cursor on
2247     // cursorColumn             column to position the cursor on
2248     //                          (only valid when "cursorLine" is set)
2249     // remoteID                 ODB parameter
2250     // remotePath               ODB parameter
2251     // remoteTokenDescType      ODB parameter
2252     // remoteTokenData          ODB parameter
2254     //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2256     NSArray *filenames = [args objectForKey:@"filenames"];
2257     int i, numFiles = filenames ? [filenames count] : 0;
2258     BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2259     int layout = [[args objectForKey:@"layout"] intValue];
2261     // Change to directory of first file to open if this is an "unused" editor
2262     // (but do not do this if editing remotely).
2263     if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2264             && (starting || [self unusedEditor]) ) {
2265         char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2266         vim_chdirfile(s);
2267         vim_free(s);
2268     }
2270     if (starting > 0) {
2271         // When Vim is starting we simply add the files to be opened to the
2272         // global arglist and Vim will take care of opening them for us.
2273         if (openFiles && numFiles > 0) {
2274             for (i = 0; i < numFiles; i++) {
2275                 NSString *fname = [filenames objectAtIndex:i];
2276                 char_u *p = NULL;
2278                 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2279                         || (p = [fname vimStringSave]) == NULL)
2280                     exit(2); // See comment in -[MMBackend exit]
2281                 else
2282                     alist_add(&global_alist, p, 2);
2283             }
2285             // Vim will take care of arranging the files added to the arglist
2286             // in windows or tabs; all we must do is to specify which layout to
2287             // use.
2288             initialWindowLayout = layout;
2289         }
2290     } else {
2291         // When Vim is already open we resort to some trickery to open the
2292         // files with the specified layout.
2293         //
2294         // TODO: Figure out a better way to handle this?
2295         if (openFiles && numFiles > 0) {
2296             BOOL oneWindowInTab = topframe ? YES
2297                                            : (topframe->fr_layout == FR_LEAF);
2298             BOOL bufChanged = NO;
2299             BOOL bufHasFilename = NO;
2300             if (curbuf) {
2301                 bufChanged = curbufIsChanged();
2302                 bufHasFilename = curbuf->b_ffname != NULL;
2303             }
2305             // Temporarily disable flushing since the following code may
2306             // potentially cause multiple redraws.
2307             flushDisabled = YES;
2309             BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2310             if (WIN_TABS == layout && !onlyOneTab) {
2311                 // By going to the last tabpage we ensure that the new tabs
2312                 // will appear last (if this call is left out, the taborder
2313                 // becomes messy).
2314                 goto_tabpage(9999);
2315             }
2317             // Make sure we're in normal mode first.
2318             [self addInput:@"<C-\\><C-N>"];
2320             if (numFiles > 1) {
2321                 // With "split layout" we open a new tab before opening
2322                 // multiple files if the current tab has more than one window
2323                 // or if there is exactly one window but whose buffer has a
2324                 // filename.  (The :drop command ensures modified buffers get
2325                 // their own window.)
2326                 if ((WIN_HOR == layout || WIN_VER == layout) &&
2327                         (!oneWindowInTab || bufHasFilename))
2328                     [self addInput:@":tabnew<CR>"];
2330                 // The files are opened by constructing a ":drop ..." command
2331                 // and executing it.
2332                 NSMutableString *cmd = (WIN_TABS == layout)
2333                         ? [NSMutableString stringWithString:@":tab drop"]
2334                         : [NSMutableString stringWithString:@":drop"];
2336                 for (i = 0; i < numFiles; ++i) {
2337                     NSString *file = [filenames objectAtIndex:i];
2338                     file = [file stringByEscapingSpecialFilenameCharacters];
2339                     [cmd appendString:@" "];
2340                     [cmd appendString:file];
2341                 }
2343                 // Temporarily clear 'suffixes' so that the files are opened in
2344                 // the same order as they appear in the "filenames" array.
2345                 [self addInput:@":let t:mvim_oldsu=&su|set su=<CR>"];
2347                 [self addInput:cmd];
2349                 // Split the view into multiple windows if requested.
2350                 if (WIN_HOR == layout)
2351                     [self addInput:@"|sall"];
2352                 else if (WIN_VER == layout)
2353                     [self addInput:@"|vert sall"];
2355                 // Restore the old value of 'suffixes'.
2356                 [self addInput:@"|let &su=t:mvim_oldsu|unlet t:mvim_oldsu"];
2358                 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2359                 [self addInput:@"|redr|f<CR>"];
2360             } else {
2361                 // When opening one file we try to reuse the current window,
2362                 // but not if its buffer is modified or has a filename.
2363                 // However, the 'arglist' layout always opens the file in the
2364                 // current window.
2365                 NSString *file = [[filenames lastObject]
2366                         stringByEscapingSpecialFilenameCharacters];
2367                 NSString *cmd;
2368                 if (WIN_HOR == layout) {
2369                     if (!(bufHasFilename || bufChanged))
2370                         cmd = [NSString stringWithFormat:@":e %@", file];
2371                     else
2372                         cmd = [NSString stringWithFormat:@":sp %@", file];
2373                 } else if (WIN_VER == layout) {
2374                     if (!(bufHasFilename || bufChanged))
2375                         cmd = [NSString stringWithFormat:@":e %@", file];
2376                     else
2377                         cmd = [NSString stringWithFormat:@":vsp %@", file];
2378                 } else if (WIN_TABS == layout) {
2379                     if (oneWindowInTab && !(bufHasFilename || bufChanged))
2380                         cmd = [NSString stringWithFormat:@":e %@", file];
2381                     else
2382                         cmd = [NSString stringWithFormat:@":tabe %@", file];
2383                 } else {
2384                     // (The :drop command will split if there is a modified
2385                     // buffer.)
2386                     cmd = [NSString stringWithFormat:@":drop %@", file];
2387                 }
2389                 [self addInput:cmd];
2391                 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2392                 [self addInput:@"|redr|f<CR>"];
2393             }
2395             // Force screen redraw (does it have to be this complicated?).
2396             // (This code was taken from the end of gui_handle_drop().)
2397             update_screen(NOT_VALID);
2398             setcursor();
2399             out_flush();
2400             gui_update_cursor(FALSE, FALSE);
2401             maketitle();
2403             flushDisabled = NO;
2404         }
2405     }
2407     if ([args objectForKey:@"remoteID"]) {
2408         // NOTE: We have to delay processing any ODB related arguments since
2409         // the file(s) may not be opened until the input buffer is processed.
2410         [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2411                                withObject:args
2412                             waitUntilDone:NO];
2413     }
2415     NSString *lineString = [args objectForKey:@"cursorLine"];
2416     if (lineString && [lineString intValue] > 0) {
2417         NSString *columnString = [args objectForKey:@"cursorColumn"];
2418         if (!(columnString && [columnString intValue] > 0))
2419             columnString = @"1";
2421         NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2422                 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2423         [self addInput:cmd];
2424     }
2426     NSString *rangeString = [args objectForKey:@"selectionRange"];
2427     if (rangeString) {
2428         // Build a command line string that will select the given range of
2429         // lines.  If range.length == 0, then position the cursor on the given
2430         // line but do not select.
2431         NSRange range = NSRangeFromString(rangeString);
2432         NSString *cmd;
2433         if (range.length > 0) {
2434             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2435                     NSMaxRange(range), range.location];
2436         } else {
2437             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2438                     range.location];
2439         }
2441         [self addInput:cmd];
2442     }
2444     NSString *searchText = [args objectForKey:@"searchText"];
2445     if (searchText) {
2446         // TODO: Searching is an exclusive motion, so if the pattern would
2447         // match on row 0 column 0 then this pattern will miss that match.
2448         [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@<CR>",
2449                 searchText]];
2450     }
2453 - (BOOL)checkForModifiedBuffers
2455     buf_T *buf;
2456     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2457         if (bufIsChanged(buf)) {
2458             return YES;
2459         }
2460     }
2462     return NO;
2465 - (void)addInput:(NSString *)input
2467     char_u *s = (char_u*)[input UTF8String];
2469 #ifdef FEAT_MBYTE
2470     s = CONVERT_FROM_UTF8(s);
2471 #endif
2473     server_to_input_buf(s);
2475 #ifdef FEAT_MBYTE
2476     CONVERT_FROM_UTF8_FREE(s);
2477 #endif
2480 - (BOOL)unusedEditor
2482     BOOL oneWindowInTab = topframe ? YES
2483                                    : (topframe->fr_layout == FR_LEAF);
2484     BOOL bufChanged = NO;
2485     BOOL bufHasFilename = NO;
2486     if (curbuf) {
2487         bufChanged = curbufIsChanged();
2488         bufHasFilename = curbuf->b_ffname != NULL;
2489     }
2491     BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2493     return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2496 - (void)redrawScreen
2498     // Force screen redraw (does it have to be this complicated?).
2499     redraw_all_later(CLEAR);
2500     update_screen(NOT_VALID);
2501     setcursor();
2502     out_flush();
2503     gui_update_cursor(FALSE, FALSE);
2505     // HACK! The cursor is not put back at the command line by the above
2506     // "redraw commands".  The following test seems to do the trick though.
2507     if (State & CMDLINE)
2508         redrawcmdline();
2511 @end // MMBackend (Private)
2516 @implementation MMBackend (ClientServer)
2518 - (NSString *)connectionNameFromServerName:(NSString *)name
2520     NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2522     return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2523         lowercaseString];
2526 - (NSConnection *)connectionForServerName:(NSString *)name
2528     // TODO: Try 'name%d' if 'name' fails.
2529     NSString *connName = [self connectionNameFromServerName:name];
2530     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2532     if (!svrConn) {
2533         svrConn = [NSConnection connectionWithRegisteredName:connName
2534                                                            host:nil];
2535         // Try alternate server...
2536         if (!svrConn && alternateServerName) {
2537             //NSLog(@"  trying to connect to alternate server: %@",
2538             //        alternateServerName);
2539             connName = [self connectionNameFromServerName:alternateServerName];
2540             svrConn = [NSConnection connectionWithRegisteredName:connName
2541                                                             host:nil];
2542         }
2544         // Try looking for alternate servers...
2545         if (!svrConn) {
2546             //NSLog(@"  looking for alternate servers...");
2547             NSString *alt = [self alternateServerNameForName:name];
2548             if (alt != alternateServerName) {
2549                 //NSLog(@"  found alternate server: %@", string);
2550                 [alternateServerName release];
2551                 alternateServerName = [alt copy];
2552             }
2553         }
2555         // Try alternate server again...
2556         if (!svrConn && alternateServerName) {
2557             //NSLog(@"  trying to connect to alternate server: %@",
2558             //        alternateServerName);
2559             connName = [self connectionNameFromServerName:alternateServerName];
2560             svrConn = [NSConnection connectionWithRegisteredName:connName
2561                                                             host:nil];
2562         }
2564         if (svrConn) {
2565             [connectionNameDict setObject:svrConn forKey:connName];
2567             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2568             [[NSNotificationCenter defaultCenter] addObserver:self
2569                     selector:@selector(serverConnectionDidDie:)
2570                         name:NSConnectionDidDieNotification object:svrConn];
2571         }
2572     }
2574     return svrConn;
2577 - (NSConnection *)connectionForServerPort:(int)port
2579     NSConnection *conn;
2580     NSEnumerator *e = [connectionNameDict objectEnumerator];
2582     while ((conn = [e nextObject])) {
2583         // HACK! Assume connection uses mach ports.
2584         if (port == [(NSMachPort*)[conn sendPort] machPort])
2585             return conn;
2586     }
2588     return nil;
2591 - (void)serverConnectionDidDie:(NSNotification *)notification
2593     //NSLog(@"%s%@", _cmd, notification);
2595     NSConnection *svrConn = [notification object];
2597     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2598     [[NSNotificationCenter defaultCenter]
2599             removeObserver:self
2600                       name:NSConnectionDidDieNotification
2601                     object:svrConn];
2603     [connectionNameDict removeObjectsForKeys:
2604         [connectionNameDict allKeysForObject:svrConn]];
2606     // HACK! Assume connection uses mach ports.
2607     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2608     NSNumber *key = [NSNumber numberWithInt:port];
2610     [clientProxyDict removeObjectForKey:key];
2611     [serverReplyDict removeObjectForKey:key];
2614 - (void)addClient:(NSDistantObject *)client
2616     NSConnection *conn = [client connectionForProxy];
2617     // HACK! Assume connection uses mach ports.
2618     int port = [(NSMachPort*)[conn sendPort] machPort];
2619     NSNumber *key = [NSNumber numberWithInt:port];
2621     if (![clientProxyDict objectForKey:key]) {
2622         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2623         [clientProxyDict setObject:client forKey:key];
2624     }
2626     // NOTE: 'clientWindow' is a global variable which is used by <client>
2627     clientWindow = port;
2630 - (NSString *)alternateServerNameForName:(NSString *)name
2632     if (!(name && [name length] > 0))
2633         return nil;
2635     // Only look for alternates if 'name' doesn't end in a digit.
2636     unichar lastChar = [name characterAtIndex:[name length]-1];
2637     if (lastChar >= '0' && lastChar <= '9')
2638         return nil;
2640     // Look for alternates among all current servers.
2641     NSArray *list = [self serverList];
2642     if (!(list && [list count] > 0))
2643         return nil;
2645     // Filter out servers starting with 'name' and ending with a number. The
2646     // (?i) pattern ensures that the match is case insensitive.
2647     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2648     NSPredicate *pred = [NSPredicate predicateWithFormat:
2649             @"SELF MATCHES %@", pat];
2650     list = [list filteredArrayUsingPredicate:pred];
2651     if ([list count] > 0) {
2652         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2653         return [list objectAtIndex:0];
2654     }
2656     return nil;
2659 @end // MMBackend (ClientServer)
2664 @implementation NSString (MMServerNameCompare)
2665 - (NSComparisonResult)serverNameCompare:(NSString *)string
2667     return [self compare:string
2668                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2670 @end
2675 static int eventModifierFlagsToVimModMask(int modifierFlags)
2677     int modMask = 0;
2679     if (modifierFlags & NSShiftKeyMask)
2680         modMask |= MOD_MASK_SHIFT;
2681     if (modifierFlags & NSControlKeyMask)
2682         modMask |= MOD_MASK_CTRL;
2683     if (modifierFlags & NSAlternateKeyMask)
2684         modMask |= MOD_MASK_ALT;
2685     if (modifierFlags & NSCommandKeyMask)
2686         modMask |= MOD_MASK_CMD;
2688     return modMask;
2691 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2693     int modMask = 0;
2695     if (modifierFlags & NSShiftKeyMask)
2696         modMask |= MOUSE_SHIFT;
2697     if (modifierFlags & NSControlKeyMask)
2698         modMask |= MOUSE_CTRL;
2699     if (modifierFlags & NSAlternateKeyMask)
2700         modMask |= MOUSE_ALT;
2702     return modMask;
2705 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2707     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2709     return (buttonNumber >= 0 && buttonNumber < 3)
2710             ? mouseButton[buttonNumber] : -1;
2715 // This function is modeled after the VimToPython function found in if_python.c
2716 // NB This does a deep copy by value, it does not lookup references like the
2717 // VimToPython function does.  This is because I didn't want to deal with the
2718 // retain cycles that this would create, and we can cover 99% of the use cases
2719 // by ignoring it.  If we ever switch to using GC in MacVim then this
2720 // functionality can be implemented easily.
2721 static id vimToCocoa(typval_T * tv, int depth)
2723     id result = nil;
2724     id newObj = nil;
2727     // Avoid infinite recursion
2728     if (depth > 100) {
2729         return nil;
2730     }
2732     if (tv->v_type == VAR_STRING) {
2733         char_u * val = tv->vval.v_string;
2734         // val can be NULL if the string is empty
2735         if (!val) {
2736             result = [NSString string];
2737         } else {
2738 #ifdef FEAT_MBYTE
2739             val = CONVERT_TO_UTF8(val);
2740 #endif
2741             result = [NSString stringWithUTF8String:(char*)val];
2742 #ifdef FEAT_MBYTE
2743             CONVERT_TO_UTF8_FREE(val);
2744 #endif
2745         }
2746     } else if (tv->v_type == VAR_NUMBER) {
2747         // looks like sizeof(varnumber_T) is always <= sizeof(long)
2748         result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2749     } else if (tv->v_type == VAR_LIST) {
2750         list_T * list = tv->vval.v_list;
2751         listitem_T * curr;
2753         NSMutableArray * arr = result = [NSMutableArray array];
2755         if (list != NULL) {
2756             for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2757                 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2758                 [arr addObject:newObj];
2759             }
2760         }
2761     } else if (tv->v_type == VAR_DICT) {
2762         NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2764         if (tv->vval.v_dict != NULL) {
2765             hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2766             int todo = ht->ht_used;
2767             hashitem_T * hi;
2768             dictitem_T * di;
2770             for (hi = ht->ht_array; todo > 0; ++hi) {
2771                 if (!HASHITEM_EMPTY(hi)) {
2772                     --todo;
2774                     di = dict_lookup(hi);
2775                     newObj = vimToCocoa(&di->di_tv, depth + 1);
2777                     char_u * keyval = hi->hi_key;
2778 #ifdef FEAT_MBYTE
2779                     keyval = CONVERT_TO_UTF8(keyval);
2780 #endif
2781                     NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2782 #ifdef FEAT_MBYTE
2783                     CONVERT_TO_UTF8_FREE(keyval);
2784 #endif
2785                     [dict setObject:newObj forKey:key];
2786                 }
2787             }
2788         }
2789     } else { // only func refs should fall into this category?
2790         result = nil;
2791     }
2793     return result;
2797 // This function is modeled after eval_client_expr_to_string found in main.c
2798 // Returns nil if there was an error evaluating the expression, and writes a
2799 // message to errorStr.
2800 // TODO Get the error that occurred while evaluating the expression in vim
2801 // somehow.
2802 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2805     char_u *s = (char_u*)[expr UTF8String];
2807 #ifdef FEAT_MBYTE
2808     s = CONVERT_FROM_UTF8(s);
2809 #endif
2811     int save_dbl = debug_break_level;
2812     int save_ro = redir_off;
2814     debug_break_level = -1;
2815     redir_off = 0;
2816     ++emsg_skip;
2818     typval_T * tvres = eval_expr(s, NULL);
2820     debug_break_level = save_dbl;
2821     redir_off = save_ro;
2822     --emsg_skip;
2824     setcursor();
2825     out_flush();
2827 #ifdef FEAT_MBYTE
2828     CONVERT_FROM_UTF8_FREE(s);
2829 #endif
2831 #ifdef FEAT_GUI
2832     if (gui.in_use)
2833         gui_update_cursor(FALSE, FALSE);
2834 #endif
2836     if (tvres == NULL) {
2837         free_tv(tvres);
2838         *errstr = @"Expression evaluation failed.";
2839     }
2841     id res = vimToCocoa(tvres, 1);
2843     free_tv(tvres);
2845     if (res == nil) {
2846         *errstr = @"Conversion to cocoa values failed.";
2847     }
2849     return res;
2854 @implementation NSString (VimStrings)
2856 + (id)stringWithVimString:(char_u *)s
2858     // This method ensures a non-nil string is returned.  If 's' cannot be
2859     // converted to a utf-8 string it is assumed to be latin-1.  If conversion
2860     // still fails an empty NSString is returned.
2861     NSString *string = nil;
2862     if (s) {
2863 #ifdef FEAT_MBYTE
2864         s = CONVERT_TO_UTF8(s);
2865 #endif
2866         string = [NSString stringWithUTF8String:(char*)s];
2867         if (!string) {
2868             // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2869             // latin-1?
2870             string = [NSString stringWithCString:(char*)s
2871                                         encoding:NSISOLatin1StringEncoding];
2872         }
2873 #ifdef FEAT_MBYTE
2874         CONVERT_TO_UTF8_FREE(s);
2875 #endif
2876     }
2878     return string != nil ? string : [NSString string];
2881 - (char_u *)vimStringSave
2883     char_u *s = (char_u*)[self UTF8String], *ret = NULL;
2885 #ifdef FEAT_MBYTE
2886     s = CONVERT_FROM_UTF8(s);
2887 #endif
2888     ret = vim_strsave(s);
2889 #ifdef FEAT_MBYTE
2890     CONVERT_FROM_UTF8_FREE(s);
2891 #endif
2893     return ret;
2896 @end // NSString (VimStrings)