Use -[NSString vimStringSave]
[MacVim.git] / src / MacVim / MMBackend.m
blob1fb0ad2ffd1993b563b3aa17f452ef4e82345a8b
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             s = [dialogReturn vimStringSave];
701         [dialogReturn release];  dialogReturn = nil;
702     }
703     @catch (NSException *e) {
704         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
705     }
707     return (char *)s;
710 - (oneway void)setDialogReturn:(in bycopy id)obj
712     // NOTE: This is called by
713     //   - [MMVimController panelDidEnd:::], and
714     //   - [MMVimController alertDidEnd:::],
715     // to indicate that a save/open panel or alert has finished.
717     // We want to distinguish between "no dialog return yet" and "dialog
718     // returned nothing".  The former can be tested with dialogReturn == nil,
719     // the latter with dialogReturn == [NSNull null].
720     if (!obj) obj = [NSNull null];
722     if (obj != dialogReturn) {
723         [dialogReturn release];
724         dialogReturn = [obj retain];
725     }
728 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
730     int retval = 0;
732     @try {
733         [frontendProxy presentDialogWithAttributes:attr];
735         [self waitForDialogReturn];
737         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
738                 && [dialogReturn count]) {
739             retval = [[dialogReturn objectAtIndex:0] intValue];
740             if (txtfield && [dialogReturn count] > 1) {
741                 NSString *retString = [dialogReturn objectAtIndex:1];
742                 char_u *ret = (char_u*)[retString UTF8String];
743 #ifdef FEAT_MBYTE
744                 ret = CONVERT_FROM_UTF8(ret);
745 #endif
746                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
747 #ifdef FEAT_MBYTE
748                 CONVERT_FROM_UTF8_FREE(ret);
749 #endif
750             }
751         }
753         [dialogReturn release]; dialogReturn = nil;
754     }
755     @catch (NSException *e) {
756         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
757     }
759     return retval;
762 - (void)showToolbar:(int)enable flags:(int)flags
764     NSMutableData *data = [NSMutableData data];
766     [data appendBytes:&enable length:sizeof(int)];
767     [data appendBytes:&flags length:sizeof(int)];
769     [self queueMessage:ShowToolbarMsgID data:data];
772 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
774     NSMutableData *data = [NSMutableData data];
776     [data appendBytes:&ident length:sizeof(long)];
777     [data appendBytes:&type length:sizeof(int)];
779     [self queueMessage:CreateScrollbarMsgID data:data];
782 - (void)destroyScrollbarWithIdentifier:(long)ident
784     NSMutableData *data = [NSMutableData data];
785     [data appendBytes:&ident length:sizeof(long)];
787     [self queueMessage:DestroyScrollbarMsgID data:data];
790 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
792     NSMutableData *data = [NSMutableData data];
794     [data appendBytes:&ident length:sizeof(long)];
795     [data appendBytes:&visible length:sizeof(int)];
797     [self queueMessage:ShowScrollbarMsgID data:data];
800 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
802     NSMutableData *data = [NSMutableData data];
804     [data appendBytes:&ident length:sizeof(long)];
805     [data appendBytes:&pos length:sizeof(int)];
806     [data appendBytes:&len length:sizeof(int)];
808     [self queueMessage:SetScrollbarPositionMsgID data:data];
811 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
812                     identifier:(long)ident
814     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
815     float prop = (float)size/(max+1);
816     if (fval < 0) fval = 0;
817     else if (fval > 1.0f) fval = 1.0f;
818     if (prop < 0) prop = 0;
819     else if (prop > 1.0f) prop = 1.0f;
821     NSMutableData *data = [NSMutableData data];
823     [data appendBytes:&ident length:sizeof(long)];
824     [data appendBytes:&fval length:sizeof(float)];
825     [data appendBytes:&prop length:sizeof(float)];
827     [self queueMessage:SetScrollbarThumbMsgID data:data];
830 - (void)setFont:(NSFont *)font
832     NSString *fontName = [font displayName];
833     float size = [font pointSize];
834     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
835     if (len > 0) {
836         NSMutableData *data = [NSMutableData data];
838         [data appendBytes:&size length:sizeof(float)];
839         [data appendBytes:&len length:sizeof(int)];
840         [data appendBytes:[fontName UTF8String] length:len];
842         [self queueMessage:SetFontMsgID data:data];
843     }
846 - (void)setWideFont:(NSFont *)font
848     NSString *fontName = [font displayName];
849     float size = [font pointSize];
850     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
851     NSMutableData *data = [NSMutableData data];
853     [data appendBytes:&size length:sizeof(float)];
854     [data appendBytes:&len length:sizeof(int)];
855     if (len > 0)
856         [data appendBytes:[fontName UTF8String] length:len];
858     [self queueMessage:SetWideFontMsgID data:data];
861 - (void)executeActionWithName:(NSString *)name
863     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
865     if (len > 0) {
866         NSMutableData *data = [NSMutableData data];
868         [data appendBytes:&len length:sizeof(int)];
869         [data appendBytes:[name UTF8String] length:len];
871         [self queueMessage:ExecuteActionMsgID data:data];
872     }
875 - (void)setMouseShape:(int)shape
877     NSMutableData *data = [NSMutableData data];
878     [data appendBytes:&shape length:sizeof(int)];
879     [self queueMessage:SetMouseShapeMsgID data:data];
882 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
884     // Vim specifies times in milliseconds, whereas Cocoa wants them in
885     // seconds.
886     blinkWaitInterval = .001f*wait;
887     blinkOnInterval = .001f*on;
888     blinkOffInterval = .001f*off;
891 - (void)startBlink
893     if (blinkTimer) {
894         [blinkTimer invalidate];
895         [blinkTimer release];
896         blinkTimer = nil;
897     }
899     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
900             && gui.in_focus) {
901         blinkState = MMBlinkStateOn;
902         blinkTimer =
903             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
904                                               target:self
905                                             selector:@selector(blinkTimerFired:)
906                                             userInfo:nil repeats:NO] retain];
907         gui_update_cursor(TRUE, FALSE);
908         [self flushQueue:YES];
909     }
912 - (void)stopBlink
914     if (MMBlinkStateOff == blinkState) {
915         gui_update_cursor(TRUE, FALSE);
916         [self flushQueue:YES];
917     }
919     blinkState = MMBlinkStateNone;
922 - (void)adjustLinespace:(int)linespace
924     NSMutableData *data = [NSMutableData data];
925     [data appendBytes:&linespace length:sizeof(int)];
926     [self queueMessage:AdjustLinespaceMsgID data:data];
929 - (void)activate
931     [self queueMessage:ActivateMsgID data:nil];
934 - (void)setPreEditRow:(int)row column:(int)col
936     NSMutableData *data = [NSMutableData data];
937     [data appendBytes:&row length:sizeof(int)];
938     [data appendBytes:&col length:sizeof(int)];
939     [self queueMessage:SetPreEditPositionMsgID data:data];
942 - (int)lookupColorWithKey:(NSString *)key
944     if (!(key && [key length] > 0))
945         return INVALCOLOR;
947     NSString *stripKey = [[[[key lowercaseString]
948         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
949             componentsSeparatedByString:@" "]
950                componentsJoinedByString:@""];
952     if (stripKey && [stripKey length] > 0) {
953         // First of all try to lookup key in the color dictionary; note that
954         // all keys in this dictionary are lowercase with no whitespace.
955         id obj = [colorDict objectForKey:stripKey];
956         if (obj) return [obj intValue];
958         // The key was not in the dictionary; is it perhaps of the form
959         // #rrggbb?
960         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
961             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
962             [scanner setScanLocation:1];
963             unsigned hex = 0;
964             if ([scanner scanHexInt:&hex]) {
965                 return (int)hex;
966             }
967         }
969         // As a last resort, check if it is one of the system defined colors.
970         // The keys in this dictionary are also lowercase with no whitespace.
971         obj = [sysColorDict objectForKey:stripKey];
972         if (obj) {
973             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
974             if (col) {
975                 float r, g, b, a;
976                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
977                 [col getRed:&r green:&g blue:&b alpha:&a];
978                 return (((int)(r*255+.5f) & 0xff) << 16)
979                      + (((int)(g*255+.5f) & 0xff) << 8)
980                      +  ((int)(b*255+.5f) & 0xff);
981             }
982         }
983     }
985     //NSLog(@"WARNING: No color with key %@ found.", stripKey);
986     return INVALCOLOR;
989 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
991     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
992     id obj;
994     while ((obj = [e nextObject])) {
995         if ([value isEqual:obj])
996             return YES;
997     }
999     return NO;
1002 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1004     NSMutableData *data = [NSMutableData data];
1005     [data appendBytes:&fuoptions length:sizeof(int)];
1006     bg = MM_COLOR(bg);
1007     [data appendBytes:&bg length:sizeof(int)];
1008     [self queueMessage:EnterFullscreenMsgID data:data];
1011 - (void)leaveFullscreen
1013     [self queueMessage:LeaveFullscreenMsgID data:nil];
1016 - (void)setFullscreenBackgroundColor:(int)color
1018     NSMutableData *data = [NSMutableData data];
1019     color = MM_COLOR(color);
1020     [data appendBytes:&color length:sizeof(int)];
1022     [self queueMessage:SetFullscreenColorMsgID data:data];
1025 - (void)setAntialias:(BOOL)antialias
1027     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1029     [self queueMessage:msgid data:nil];
1032 - (void)updateModifiedFlag
1034     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1035     // vice versa.
1036     int msgid = [self checkForModifiedBuffers]
1037             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1039     [self queueMessage:msgid data:nil];
1042 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1044     // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1045     // queue is processed since that only happens in waitForInput: (and Vim
1046     // regularly checks for Ctrl-C in between waiting for input).
1048     BOOL interrupt = NO;
1049     if (msgid == InterruptMsgID) {
1050         interrupt = YES;
1051     } else if (InsertTextMsgID == msgid && data != nil && [data length] == 1) {
1052         char_u *str = (char_u*)[data bytes];
1053         if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1054                 (str[0] == intr_char && intr_char != Ctrl_C))
1055             interrupt = YES;
1056     }
1058     if (interrupt) {
1059         got_int = TRUE;
1060         [inputQueue removeAllObjects];
1061         return;
1062     }
1064     // Remove all previous instances of this message from the input queue, else
1065     // the input queue may fill up as a result of Vim not being able to keep up
1066     // with the speed at which new messages are received.  This avoids annoying
1067     // situations such as when the keyboard repeat rate is higher than what Vim
1068     // can cope with (which would cause a 'stutter' when scrolling by holding
1069     // down 'j' and then when 'j' was released the screen kept scrolling for a
1070     // little while).
1072     int i, count = [inputQueue count];
1073     for (i = 1; i < count; i+=2) {
1074         if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1075             [inputQueue removeObjectAtIndex:i];
1076             [inputQueue removeObjectAtIndex:i-1];
1077             break;
1078         }
1079     }
1081     [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1082     [inputQueue addObject:(data ? (id)data : [NSNull null])];
1085 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1087     // This is just a convenience method that allows the frontend to delay
1088     // sending messages.
1089     int i, count = [messages count];
1090     for (i = 1; i < count; i+=2)
1091         [self processInput:[[messages objectAtIndex:i-1] intValue]
1092                       data:[messages objectAtIndex:i]];
1095 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1096                   errorString:(out bycopy NSString **)errstr
1098     return evalExprCocoa(expr, errstr);
1102 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1104     NSString *eval = nil;
1105     char_u *s = (char_u*)[expr UTF8String];
1107 #ifdef FEAT_MBYTE
1108     s = CONVERT_FROM_UTF8(s);
1109 #endif
1111     char_u *res = eval_client_expr_to_string(s);
1113 #ifdef FEAT_MBYTE
1114     CONVERT_FROM_UTF8_FREE(s);
1115 #endif
1117     if (res != NULL) {
1118         s = res;
1119 #ifdef FEAT_MBYTE
1120         s = CONVERT_TO_UTF8(s);
1121 #endif
1122         eval = [NSString stringWithUTF8String:(char*)s];
1123 #ifdef FEAT_MBYTE
1124         CONVERT_TO_UTF8_FREE(s);
1125 #endif
1126         vim_free(res);
1127     }
1129     return eval;
1132 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1134     // TODO: This method should share code with clip_mch_request_selection().
1136     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1137         // If there is no pasteboard, return YES to indicate that there is text
1138         // to copy.
1139         if (!pboard)
1140             return YES;
1142         clip_copy_selection();
1144         // Get the text to put on the pasteboard.
1145         long_u llen = 0; char_u *str = 0;
1146         int type = clip_convert_selection(&str, &llen, &clip_star);
1147         if (type < 0)
1148             return NO;
1149         
1150         // TODO: Avoid overflow.
1151         int len = (int)llen;
1152 #ifdef FEAT_MBYTE
1153         if (output_conv.vc_type != CONV_NONE) {
1154             char_u *conv_str = string_convert(&output_conv, str, &len);
1155             if (conv_str) {
1156                 vim_free(str);
1157                 str = conv_str;
1158             }
1159         }
1160 #endif
1162         NSString *string = [[NSString alloc]
1163             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1165         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1166         [pboard declareTypes:types owner:nil];
1167         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1168     
1169         [string release];
1170         vim_free(str);
1172         return ok;
1173     }
1175     return NO;
1178 - (oneway void)addReply:(in bycopy NSString *)reply
1179                  server:(in byref id <MMVimServerProtocol>)server
1181     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1183     // Replies might come at any time and in any order so we keep them in an
1184     // array inside a dictionary with the send port used as key.
1186     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1187     // HACK! Assume connection uses mach ports.
1188     int port = [(NSMachPort*)[conn sendPort] machPort];
1189     NSNumber *key = [NSNumber numberWithInt:port];
1191     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1192     if (!replies) {
1193         replies = [NSMutableArray array];
1194         [serverReplyDict setObject:replies forKey:key];
1195     }
1197     [replies addObject:reply];
1200 - (void)addInput:(in bycopy NSString *)input
1201                  client:(in byref id <MMVimClientProtocol>)client
1203     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1205     [self addInput:input];
1206     [self addClient:(id)client];
1209 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1210                  client:(in byref id <MMVimClientProtocol>)client
1212     [self addClient:(id)client];
1213     return [self evaluateExpression:expr];
1216 - (void)registerServerWithName:(NSString *)name
1218     NSString *svrName = name;
1219     NSConnection *svrConn = [NSConnection defaultConnection];
1220     unsigned i;
1222     for (i = 0; i < MMServerMax; ++i) {
1223         NSString *connName = [self connectionNameFromServerName:svrName];
1225         if ([svrConn registerName:connName]) {
1226             //NSLog(@"Registered server with name: %@", svrName);
1228             // TODO: Set request/reply time-outs to something else?
1229             //
1230             // Don't wait for requests (time-out means that the message is
1231             // dropped).
1232             [svrConn setRequestTimeout:0];
1233             //[svrConn setReplyTimeout:MMReplyTimeout];
1234             [svrConn setRootObject:self];
1236             // NOTE: 'serverName' is a global variable
1237             serverName = [svrName vimStringSave];
1238 #ifdef FEAT_EVAL
1239             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1240 #endif
1241 #ifdef FEAT_TITLE
1242             need_maketitle = TRUE;
1243 #endif
1244             [self queueMessage:SetServerNameMsgID data:
1245                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1246             break;
1247         }
1249         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1250     }
1253 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1254                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1255               silent:(BOOL)silent
1257     // NOTE: If 'name' equals 'serverName' then the request is local (client
1258     // and server are the same).  This case is not handled separately, so a
1259     // connection will be set up anyway (this simplifies the code).
1261     NSConnection *conn = [self connectionForServerName:name];
1262     if (!conn) {
1263         if (!silent) {
1264             char_u *s = (char_u*)[name UTF8String];
1265 #ifdef FEAT_MBYTE
1266             s = CONVERT_FROM_UTF8(s);
1267 #endif
1268             EMSG2(_(e_noserver), s);
1269 #ifdef FEAT_MBYTE
1270             CONVERT_FROM_UTF8_FREE(s);
1271 #endif
1272         }
1273         return NO;
1274     }
1276     if (port) {
1277         // HACK! Assume connection uses mach ports.
1278         *port = [(NSMachPort*)[conn sendPort] machPort];
1279     }
1281     id proxy = [conn rootProxy];
1282     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1284     @try {
1285         if (expr) {
1286             NSString *eval = [proxy evaluateExpression:string client:self];
1287             if (reply) {
1288                 if (eval) {
1289                     *reply = [eval vimStringSave];
1290                 } else {
1291                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1292                 }
1293             }
1295             if (!eval)
1296                 return NO;
1297         } else {
1298             [proxy addInput:string client:self];
1299         }
1300     }
1301     @catch (NSException *e) {
1302         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1303         return NO;
1304     }
1306     return YES;
1309 - (NSArray *)serverList
1311     NSArray *list = nil;
1313     if ([self connection]) {
1314         id proxy = [connection rootProxy];
1315         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1317         @try {
1318             list = [proxy serverList];
1319         }
1320         @catch (NSException *e) {
1321             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1322         }
1323     } else {
1324         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1325     }
1327     return list;
1330 - (NSString *)peekForReplyOnPort:(int)port
1332     //NSLog(@"%s%d", _cmd, port);
1334     NSNumber *key = [NSNumber numberWithInt:port];
1335     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1336     if (replies && [replies count]) {
1337         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1338         //        [replies objectAtIndex:0]);
1339         return [replies objectAtIndex:0];
1340     }
1342     //NSLog(@"    No replies");
1343     return nil;
1346 - (NSString *)waitForReplyOnPort:(int)port
1348     //NSLog(@"%s%d", _cmd, port);
1349     
1350     NSConnection *conn = [self connectionForServerPort:port];
1351     if (!conn)
1352         return nil;
1354     NSNumber *key = [NSNumber numberWithInt:port];
1355     NSMutableArray *replies = nil;
1356     NSString *reply = nil;
1358     // Wait for reply as long as the connection to the server is valid (unless
1359     // user interrupts wait with Ctrl-C).
1360     while (!got_int && [conn isValid] &&
1361             !(replies = [serverReplyDict objectForKey:key])) {
1362         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1363                                  beforeDate:[NSDate distantFuture]];
1364     }
1366     if (replies) {
1367         if ([replies count] > 0) {
1368             reply = [[replies objectAtIndex:0] retain];
1369             //NSLog(@"    Got reply: %@", reply);
1370             [replies removeObjectAtIndex:0];
1371             [reply autorelease];
1372         }
1374         if ([replies count] == 0)
1375             [serverReplyDict removeObjectForKey:key];
1376     }
1378     return reply;
1381 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1383     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1384     if (client) {
1385         @try {
1386             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1387             [client addReply:reply server:self];
1388             return YES;
1389         }
1390         @catch (NSException *e) {
1391             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1392         }
1393     } else {
1394         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1395     }
1397     return NO;
1400 - (BOOL)waitForAck
1402     return waitForAck;
1405 - (void)setWaitForAck:(BOOL)yn
1407     waitForAck = yn;
1410 - (void)waitForConnectionAcknowledgement
1412     if (!waitForAck) return;
1414     while (waitForAck && !got_int && [connection isValid] && !isTerminating) {
1415         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1416                                  beforeDate:[NSDate distantFuture]];
1417         //NSLog(@"  waitForAck=%d got_int=%d isTerminating=%d isValid=%d",
1418         //        waitForAck, got_int, isTerminating, [connection isValid]);
1419     }
1421     if (waitForAck) {
1422         // Never received a connection acknowledgement, so die.
1423         [[NSNotificationCenter defaultCenter] removeObserver:self];
1424         [frontendProxy release];  frontendProxy = nil;
1426         // NOTE: We intentionally do not call mch_exit() since this in turn
1427         // will lead to -[MMBackend exit] getting called which we want to
1428         // avoid.
1429         usleep(MMExitProcessDelay);
1430         exit(0);
1431     }
1433     [self processInputQueue];
1436 - (oneway void)acknowledgeConnection
1438     //NSLog(@"%s", _cmd);
1439     waitForAck = NO;
1442 @end // MMBackend
1446 @implementation MMBackend (Private)
1448 - (void)waitForDialogReturn
1450     // Keep processing the run loop until a dialog returns.  To avoid getting
1451     // stuck in an endless loop (could happen if the setDialogReturn: message
1452     // was lost) we also do some paranoia checks.
1453     //
1454     // Note that in Cocoa the user can still resize windows and select menu
1455     // items while a sheet is being displayed, so we can't just wait for the
1456     // first message to arrive and assume that is the setDialogReturn: call.
1458     while (nil == dialogReturn && !got_int && [connection isValid]
1459             && !isTerminating)
1460         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1461                                  beforeDate:[NSDate distantFuture]];
1463     // Search for any resize messages on the input queue.  All other messages
1464     // on the input queue are dropped.  The reason why we single out resize
1465     // messages is because the user may have resized the window while a sheet
1466     // was open.
1467     int i, count = [inputQueue count];
1468     if (count > 0) {
1469         id textDimData = nil;
1470         if (count%2 == 0) {
1471             for (i = count-2; i >= 0; i -= 2) {
1472                 int msgid = [[inputQueue objectAtIndex:i] intValue];
1473                 if (SetTextDimensionsMsgID == msgid) {
1474                     textDimData = [[inputQueue objectAtIndex:i+1] retain];
1475                     break;
1476                 }
1477             }
1478         }
1480         [inputQueue removeAllObjects];
1482         if (textDimData) {
1483             [inputQueue addObject:
1484                     [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1485             [inputQueue addObject:textDimData];
1486             [textDimData release];
1487         }
1488     }
1491 - (void)insertVimStateMessage
1493     // NOTE: This is the place to add Vim state that needs to be accessed from
1494     // MacVim.  Do not add state that could potentially require lots of memory
1495     // since this message gets sent each time the output queue is forcibly
1496     // flushed (e.g. storing the currently selected text would be a bad idea).
1497     // We take this approach of "pushing" the state to MacVim to avoid having
1498     // to make synchronous calls from MacVim to Vim in order to get state.
1500     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1501         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1502         [NSNumber numberWithInt:p_mh], @"p_mh",
1503         [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1504         nil];
1506     // Put the state before all other messages.
1507     int msgid = SetVimStateMsgID;
1508     [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1509     [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1510                       atIndex:0];
1513 - (void)processInputQueue
1515     if ([inputQueue count] == 0) return;
1517     // NOTE: One of the input events may cause this method to be called
1518     // recursively, so copy the input queue to a local variable and clear the
1519     // queue before starting to process input events (otherwise we could get
1520     // stuck in an endless loop).
1521     NSArray *q = [inputQueue copy];
1522     unsigned i, count = [q count];
1524     [inputQueue removeAllObjects];
1526     for (i = 1; i < count; i+=2) {
1527         int msgid = [[q objectAtIndex:i-1] intValue];
1528         id data = [q objectAtIndex:i];
1529         if ([data isEqual:[NSNull null]])
1530             data = nil;
1532         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1533         [self handleInputEvent:msgid data:data];
1534     }
1536     [q release];
1537     //NSLog(@"Clear input event queue");
1540 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1542     if (InsertTextMsgID == msgid) {
1543         [self handleInsertText:data];
1544     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1545         if (!data) return;
1546         const void *bytes = [data bytes];
1547         int mods = *((int*)bytes);  bytes += sizeof(int);
1548         int len = *((int*)bytes);  bytes += sizeof(int);
1549         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1550                                               encoding:NSUTF8StringEncoding];
1551         mods = eventModifierFlagsToVimModMask(mods);
1553         [self handleKeyDown:key modifiers:mods];
1555         [key release];
1556     } else if (ScrollWheelMsgID == msgid) {
1557         if (!data) return;
1558         const void *bytes = [data bytes];
1560         int row = *((int*)bytes);  bytes += sizeof(int);
1561         int col = *((int*)bytes);  bytes += sizeof(int);
1562         int flags = *((int*)bytes);  bytes += sizeof(int);
1563         float dy = *((float*)bytes);  bytes += sizeof(float);
1565         int button = MOUSE_5;
1566         if (dy > 0) button = MOUSE_4;
1568         flags = eventModifierFlagsToVimMouseModMask(flags);
1570         int numLines = (int)round(dy);
1571         if (numLines < 0) numLines = -numLines;
1572         if (numLines == 0) numLines = 1;
1574 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1575         gui.scroll_wheel_force = numLines;
1576 #endif
1578         gui_send_mouse_event(button, col, row, NO, flags);
1579     } else if (MouseDownMsgID == msgid) {
1580         if (!data) return;
1581         const void *bytes = [data bytes];
1583         int row = *((int*)bytes);  bytes += sizeof(int);
1584         int col = *((int*)bytes);  bytes += sizeof(int);
1585         int button = *((int*)bytes);  bytes += sizeof(int);
1586         int flags = *((int*)bytes);  bytes += sizeof(int);
1587         int count = *((int*)bytes);  bytes += sizeof(int);
1589         button = eventButtonNumberToVimMouseButton(button);
1590         if (button >= 0) {
1591             flags = eventModifierFlagsToVimMouseModMask(flags);
1592             gui_send_mouse_event(button, col, row, count>1, flags);
1593         }
1594     } else if (MouseUpMsgID == msgid) {
1595         if (!data) return;
1596         const void *bytes = [data bytes];
1598         int row = *((int*)bytes);  bytes += sizeof(int);
1599         int col = *((int*)bytes);  bytes += sizeof(int);
1600         int flags = *((int*)bytes);  bytes += sizeof(int);
1602         flags = eventModifierFlagsToVimMouseModMask(flags);
1604         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1605     } else if (MouseDraggedMsgID == msgid) {
1606         if (!data) return;
1607         const void *bytes = [data bytes];
1609         int row = *((int*)bytes);  bytes += sizeof(int);
1610         int col = *((int*)bytes);  bytes += sizeof(int);
1611         int flags = *((int*)bytes);  bytes += sizeof(int);
1613         flags = eventModifierFlagsToVimMouseModMask(flags);
1615         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1616     } else if (MouseMovedMsgID == msgid) {
1617         const void *bytes = [data bytes];
1618         int row = *((int*)bytes);  bytes += sizeof(int);
1619         int col = *((int*)bytes);  bytes += sizeof(int);
1621         gui_mouse_moved(col, row);
1622     } else if (AddInputMsgID == msgid) {
1623         NSString *string = [[NSString alloc] initWithData:data
1624                 encoding:NSUTF8StringEncoding];
1625         if (string) {
1626             [self addInput:string];
1627             [string release];
1628         }
1629     } else if (TerminateNowMsgID == msgid) {
1630         isTerminating = YES;
1631     } else if (SelectTabMsgID == msgid) {
1632         if (!data) return;
1633         const void *bytes = [data bytes];
1634         int idx = *((int*)bytes) + 1;
1635         //NSLog(@"Selecting tab %d", idx);
1636         send_tabline_event(idx);
1637     } else if (CloseTabMsgID == msgid) {
1638         if (!data) return;
1639         const void *bytes = [data bytes];
1640         int idx = *((int*)bytes) + 1;
1641         //NSLog(@"Closing tab %d", idx);
1642         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1643     } else if (AddNewTabMsgID == msgid) {
1644         //NSLog(@"Adding new tab");
1645         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1646     } else if (DraggedTabMsgID == msgid) {
1647         if (!data) return;
1648         const void *bytes = [data bytes];
1649         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1650         // based.
1651         int idx = *((int*)bytes);
1653         tabpage_move(idx);
1654     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1655             || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1656         if (!data) return;
1657         const void *bytes = [data bytes];
1658         int rows = Rows;
1659         if (SetTextColumnsMsgID != msgid) {
1660             rows = *((int*)bytes);  bytes += sizeof(int);
1661         }
1662         int cols = Columns;
1663         if (SetTextRowsMsgID != msgid) {
1664             cols = *((int*)bytes);  bytes += sizeof(int);
1665         }
1667         NSData *d = data;
1668         if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1669             int dim[2] = { rows, cols };
1670             d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1671             msgid = SetTextDimensionsMsgID;
1672         }
1674         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1675         // gui_resize_shell(), so we have to manually set the rows and columns
1676         // here.  (MacVim doesn't change the rows and columns to avoid
1677         // inconsistent states between Vim and MacVim.)
1678         [self queueMessage:msgid data:d];
1680         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1681         gui_resize_shell(cols, rows);
1682     } else if (ExecuteMenuMsgID == msgid) {
1683         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1684         if (attrs) {
1685             NSArray *desc = [attrs objectForKey:@"descriptor"];
1686             vimmenu_T *menu = menu_for_descriptor(desc);
1687             if (menu)
1688                 gui_menu_cb(menu);
1689         }
1690     } else if (ToggleToolbarMsgID == msgid) {
1691         [self handleToggleToolbar];
1692     } else if (ScrollbarEventMsgID == msgid) {
1693         [self handleScrollbarEvent:data];
1694     } else if (SetFontMsgID == msgid) {
1695         [self handleSetFont:data];
1696     } else if (VimShouldCloseMsgID == msgid) {
1697         gui_shell_closed();
1698     } else if (DropFilesMsgID == msgid) {
1699         [self handleDropFiles:data];
1700     } else if (DropStringMsgID == msgid) {
1701         [self handleDropString:data];
1702     } else if (GotFocusMsgID == msgid) {
1703         if (!gui.in_focus)
1704             [self focusChange:YES];
1705     } else if (LostFocusMsgID == msgid) {
1706         if (gui.in_focus)
1707             [self focusChange:NO];
1708     } else if (SetMouseShapeMsgID == msgid) {
1709         const void *bytes = [data bytes];
1710         int shape = *((int*)bytes);  bytes += sizeof(int);
1711         update_mouseshape(shape);
1712     } else if (XcodeModMsgID == msgid) {
1713         [self handleXcodeMod:data];
1714     } else if (OpenWithArgumentsMsgID == msgid) {
1715         [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1716     } else {
1717         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1718     }
1721 + (NSDictionary *)specialKeys
1723     static NSDictionary *specialKeys = nil;
1725     if (!specialKeys) {
1726         NSBundle *mainBundle = [NSBundle mainBundle];
1727         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1728                                               ofType:@"plist"];
1729         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1730     }
1732     return specialKeys;
1735 - (void)handleInsertText:(NSData *)data
1737     if (!data) return;
1739     NSString *key = [[NSString alloc] initWithData:data
1740                                           encoding:NSUTF8StringEncoding];
1741     char_u *str = (char_u*)[key UTF8String];
1742     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1744 #ifdef FEAT_MBYTE
1745     char_u *conv_str = NULL;
1746     if (input_conv.vc_type != CONV_NONE) {
1747         conv_str = string_convert(&input_conv, str, &len);
1748         if (conv_str)
1749             str = conv_str;
1750     }
1751 #endif
1753     for (i = 0; i < len; ++i) {
1754         add_to_input_buf(str+i, 1);
1755         if (CSI == str[i]) {
1756             // NOTE: If the converted string contains the byte CSI, then it
1757             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1758             // won't work.
1759             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1760             add_to_input_buf(extra, 2);
1761         }
1762     }
1764 #ifdef FEAT_MBYTE
1765     if (conv_str)
1766         vim_free(conv_str);
1767 #endif
1768     [key release];
1771 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1773     char_u special[3];
1774     char_u modChars[3];
1775     char_u *chars = (char_u*)[key UTF8String];
1776 #ifdef FEAT_MBYTE
1777     char_u *conv_str = NULL;
1778 #endif
1779     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1781     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1782     // that new keys can easily be added.
1783     NSString *specialString = [[MMBackend specialKeys]
1784             objectForKey:key];
1785     if (specialString && [specialString length] > 1) {
1786         //NSLog(@"special key: %@", specialString);
1787         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1788                 [specialString characterAtIndex:1]);
1790         ikey = simplify_key(ikey, &mods);
1791         if (ikey == CSI)
1792             ikey = K_CSI;
1794         special[0] = CSI;
1795         special[1] = K_SECOND(ikey);
1796         special[2] = K_THIRD(ikey);
1798         chars = special;
1799         length = 3;
1800     } else if (1 == length && TAB == chars[0]) {
1801         // Tab is a trouble child:
1802         // - <Tab> is added to the input buffer as is
1803         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1804         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1805         //   to be converted to utf-8
1806         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1807         // - <C-Tab> is reserved by Mac OS X
1808         // - <D-Tab> is reserved by Mac OS X
1809         chars = special;
1810         special[0] = TAB;
1811         length = 1;
1813         if (mods & MOD_MASK_SHIFT) {
1814             mods &= ~MOD_MASK_SHIFT;
1815             special[0] = CSI;
1816             special[1] = K_SECOND(K_S_TAB);
1817             special[2] = K_THIRD(K_S_TAB);
1818             length = 3;
1819         } else if (mods & MOD_MASK_ALT) {
1820             int mtab = 0x80 | TAB;
1821 #ifdef FEAT_MBYTE
1822             if (enc_utf8) {
1823                 // Convert to utf-8
1824                 special[0] = (mtab >> 6) + 0xc0;
1825                 special[1] = mtab & 0xbf;
1826                 length = 2;
1827             } else
1828 #endif
1829             {
1830                 special[0] = mtab;
1831                 length = 1;
1832             }
1833             mods &= ~MOD_MASK_ALT;
1834         }
1835     } else if (length > 0) {
1836         unichar c = [key characterAtIndex:0];
1838         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1839         //        [key characterAtIndex:0], mods);
1841         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1842         // cleared since they are already added to the key by the AppKit.
1843         // Unfortunately, the only way to deal with when to clear the modifiers
1844         // or not seems to be to have hard-wired rules like this.
1845         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1846                     || 0x9 == c || 0xd == c || ESC == c) ) {
1847             mods &= ~MOD_MASK_SHIFT;
1848             mods &= ~MOD_MASK_CTRL;
1849             //NSLog(@"clear shift ctrl");
1850         }
1852         // HACK!  All Option+key presses go via 'insert text' messages, except
1853         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1854         // not work to map to it.
1855         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1856             //NSLog(@"clear alt");
1857             mods &= ~MOD_MASK_ALT;
1858         }
1860 #ifdef FEAT_MBYTE
1861         if (input_conv.vc_type != CONV_NONE) {
1862             conv_str = string_convert(&input_conv, chars, &length);
1863             if (conv_str)
1864                 chars = conv_str;
1865         }
1866 #endif
1867     }
1869     if (chars && length > 0) {
1870         if (mods) {
1871             //NSLog(@"adding mods: %d", mods);
1872             modChars[0] = CSI;
1873             modChars[1] = KS_MODIFIER;
1874             modChars[2] = mods;
1875             add_to_input_buf(modChars, 3);
1876         }
1878         //NSLog(@"add to input buf: 0x%x", chars[0]);
1879         // TODO: Check for CSI bytes?
1880         add_to_input_buf(chars, length);
1881     }
1883 #ifdef FEAT_MBYTE
1884     if (conv_str)
1885         vim_free(conv_str);
1886 #endif
1889 - (void)queueMessage:(int)msgid data:(NSData *)data
1891     //if (msgid != EnableMenuItemMsgID)
1892     //    NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1894     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1895     if (data)
1896         [outputQueue addObject:data];
1897     else
1898         [outputQueue addObject:[NSData data]];
1901 - (void)connectionDidDie:(NSNotification *)notification
1903     // If the main connection to MacVim is lost this means that MacVim was
1904     // either quit (by the user chosing Quit on the MacVim menu), or it has
1905     // crashed.  In the former case the flag 'isTerminating' is set and we then
1906     // quit cleanly; in the latter case we make sure the swap files are left
1907     // for recovery.
1908     //
1909     // NOTE: This is not called if a Vim controller invalidates its connection.
1911     //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1912     if (isTerminating)
1913         getout(0);
1914     else
1915         getout_preserve_modified(1);
1918 - (void)blinkTimerFired:(NSTimer *)timer
1920     NSTimeInterval timeInterval = 0;
1922     [blinkTimer release];
1923     blinkTimer = nil;
1925     if (MMBlinkStateOn == blinkState) {
1926         gui_undraw_cursor();
1927         blinkState = MMBlinkStateOff;
1928         timeInterval = blinkOffInterval;
1929     } else if (MMBlinkStateOff == blinkState) {
1930         gui_update_cursor(TRUE, FALSE);
1931         blinkState = MMBlinkStateOn;
1932         timeInterval = blinkOnInterval;
1933     }
1935     if (timeInterval > 0) {
1936         blinkTimer = 
1937             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1938                                             selector:@selector(blinkTimerFired:)
1939                                             userInfo:nil repeats:NO] retain];
1940         [self flushQueue:YES];
1941     }
1944 - (void)focusChange:(BOOL)on
1946     gui_focus_change(on);
1949 - (void)handleToggleToolbar
1951     // If 'go' contains 'T', then remove it, else add it.
1953     char_u go[sizeof(GO_ALL)+2];
1954     char_u *p;
1955     int len;
1957     STRCPY(go, p_go);
1958     p = vim_strchr(go, GO_TOOLBAR);
1959     len = STRLEN(go);
1961     if (p != NULL) {
1962         char_u *end = go + len;
1963         while (p < end) {
1964             p[0] = p[1];
1965             ++p;
1966         }
1967     } else {
1968         go[len] = GO_TOOLBAR;
1969         go[len+1] = NUL;
1970     }
1972     set_option_value((char_u*)"guioptions", 0, go, 0);
1974     [self redrawScreen];
1977 - (void)handleScrollbarEvent:(NSData *)data
1979     if (!data) return;
1981     const void *bytes = [data bytes];
1982     long ident = *((long*)bytes);  bytes += sizeof(long);
1983     int hitPart = *((int*)bytes);  bytes += sizeof(int);
1984     float fval = *((float*)bytes);  bytes += sizeof(float);
1985     scrollbar_T *sb = gui_find_scrollbar(ident);
1987     if (sb) {
1988         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1989         long value = sb_info->value;
1990         long size = sb_info->size;
1991         long max = sb_info->max;
1992         BOOL isStillDragging = NO;
1993         BOOL updateKnob = YES;
1995         switch (hitPart) {
1996         case NSScrollerDecrementPage:
1997             value -= (size > 2 ? size - 2 : 1);
1998             break;
1999         case NSScrollerIncrementPage:
2000             value += (size > 2 ? size - 2 : 1);
2001             break;
2002         case NSScrollerDecrementLine:
2003             --value;
2004             break;
2005         case NSScrollerIncrementLine:
2006             ++value;
2007             break;
2008         case NSScrollerKnob:
2009             isStillDragging = YES;
2010             // fall through ...
2011         case NSScrollerKnobSlot:
2012             value = (long)(fval * (max - size + 1));
2013             // fall through ...
2014         default:
2015             updateKnob = NO;
2016             break;
2017         }
2019         //NSLog(@"value %d -> %d", sb_info->value, value);
2020         gui_drag_scrollbar(sb, value, isStillDragging);
2022         if (updateKnob) {
2023             // Dragging the knob or option+clicking automatically updates
2024             // the knob position (on the actual NSScroller), so we only
2025             // need to set the knob position in the other cases.
2026             if (sb->wp) {
2027                 // Update both the left&right vertical scrollbars.
2028                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2029                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2030                 [self setScrollbarThumbValue:value size:size max:max
2031                                   identifier:identLeft];
2032                 [self setScrollbarThumbValue:value size:size max:max
2033                                   identifier:identRight];
2034             } else {
2035                 // Update the horizontal scrollbar.
2036                 [self setScrollbarThumbValue:value size:size max:max
2037                                   identifier:ident];
2038             }
2039         }
2040     }
2043 - (void)handleSetFont:(NSData *)data
2045     if (!data) return;
2047     const void *bytes = [data bytes];
2048     float pointSize = *((float*)bytes);  bytes += sizeof(float);
2049     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2050     bytes += sizeof(unsigned);  // len not used
2052     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2053     [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
2054     char_u *s = (char_u*)[name UTF8String];
2056 #ifdef FEAT_MBYTE
2057     s = CONVERT_FROM_UTF8(s);
2058 #endif
2060     set_option_value((char_u*)"guifont", 0, s, 0);
2062 #ifdef FEAT_MBYTE
2063     CONVERT_FROM_UTF8_FREE(s);
2064 #endif
2066     [self redrawScreen];
2069 - (void)handleDropFiles:(NSData *)data
2071     // TODO: Get rid of this method; instead use Vim script directly.  At the
2072     // moment I know how to do this to open files in tabs, but I'm not sure how
2073     // to add the filenames to the command line when in command line mode.
2075     if (!data) return;
2077     NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2078     if (!args) return;
2080     id obj = [args objectForKey:@"forceOpen"];
2081     BOOL forceOpen = YES;
2082     if (obj)
2083         forceOpen = [obj boolValue];
2085     NSArray *filenames = [args objectForKey:@"filenames"];
2086     if (!(filenames && [filenames count] > 0)) return;
2088 #ifdef FEAT_DND
2089     if (!forceOpen && (State & CMDLINE)) {
2090         // HACK!  If Vim is in command line mode then the files names
2091         // should be added to the command line, instead of opening the
2092         // files in tabs (unless forceOpen is set).  This is taken care of by
2093         // gui_handle_drop().
2094         int n = [filenames count];
2095         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2096         if (fnames) {
2097             int i = 0;
2098             for (i = 0; i < n; ++i)
2099                 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2101             // NOTE!  This function will free 'fnames'.
2102             // HACK!  It is assumed that the 'x' and 'y' arguments are
2103             // unused when in command line mode.
2104             gui_handle_drop(0, 0, 0, fnames, n);
2105         }
2106     } else
2107 #endif // FEAT_DND
2108     {
2109         [self handleOpenWithArguments:args];
2110     }
2113 - (void)handleDropString:(NSData *)data
2115     if (!data) return;
2117 #ifdef FEAT_DND
2118     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2119     const void *bytes = [data bytes];
2120     int len = *((int*)bytes);  bytes += sizeof(int);
2121     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2123     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2124     NSRange range = { 0, [string length] };
2125     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2126                                          withString:@"\x0a" options:0
2127                                               range:range];
2128     if (0 == n) {
2129         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2130                                        options:0 range:range];
2131     }
2133     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2134     char_u *s = (char_u*)[string UTF8String];
2135 #ifdef FEAT_MBYTE
2136     if (input_conv.vc_type != CONV_NONE)
2137         s = string_convert(&input_conv, s, &len);
2138 #endif
2139     dnd_yank_drag_data(s, len);
2140 #ifdef FEAT_MBYTE
2141     if (input_conv.vc_type != CONV_NONE)
2142         vim_free(s);
2143 #endif
2144     add_to_input_buf(dropkey, sizeof(dropkey));
2145 #endif // FEAT_DND
2148 - (void)startOdbEditWithArguments:(NSDictionary *)args
2150 #ifdef FEAT_ODB_EDITOR
2151     id obj = [args objectForKey:@"remoteID"];
2152     if (!obj) return;
2154     OSType serverID = [obj unsignedIntValue];
2155     NSString *remotePath = [args objectForKey:@"remotePath"];
2157     NSAppleEventDescriptor *token = nil;
2158     NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2159     obj = [args objectForKey:@"remoteTokenDescType"];
2160     if (tokenData && obj) {
2161         DescType tokenType = [obj unsignedLongValue];
2162         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2163                                                                 data:tokenData];
2164     }
2166     NSArray *filenames = [args objectForKey:@"filenames"];
2167     unsigned i, numFiles = [filenames count];
2168     for (i = 0; i < numFiles; ++i) {
2169         NSString *filename = [filenames objectAtIndex:i];
2170         char_u *s = [filename vimStringSave];
2171         buf_T *buf = buflist_findname(s);
2172         vim_free(s);
2174         if (buf) {
2175             if (buf->b_odb_token) {
2176                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2177                 buf->b_odb_token = NULL;
2178             }
2180             if (buf->b_odb_fname) {
2181                 vim_free(buf->b_odb_fname);
2182                 buf->b_odb_fname = NULL;
2183             }
2185             buf->b_odb_server_id = serverID;
2187             if (token)
2188                 buf->b_odb_token = [token retain];
2189             if (remotePath)
2190                 buf->b_odb_fname = [remotePath vimStringSave];
2191         } else {
2192             NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2193                     filename);
2194         }
2195     }
2196 #endif // FEAT_ODB_EDITOR
2199 - (void)handleXcodeMod:(NSData *)data
2201 #if 0
2202     const void *bytes = [data bytes];
2203     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2204     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2205     if (0 == len)
2206         return;
2208     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2209             descriptorWithDescriptorType:type
2210                                    bytes:bytes
2211                                   length:len];
2212 #endif
2215 - (void)handleOpenWithArguments:(NSDictionary *)args
2217     // ARGUMENT:                DESCRIPTION:
2218     // -------------------------------------------------------------
2219     // filenames                list of filenames
2220     // dontOpen                 don't open files specified in above argument
2221     // layout                   which layout to use to open files
2222     // selectionRange           range of lines to select
2223     // searchText               string to search for
2224     // cursorLine               line to position the cursor on
2225     // cursorColumn             column to position the cursor on
2226     //                          (only valid when "cursorLine" is set)
2227     // remoteID                 ODB parameter
2228     // remotePath               ODB parameter
2229     // remoteTokenDescType      ODB parameter
2230     // remoteTokenData          ODB parameter
2232     //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2234     NSArray *filenames = [args objectForKey:@"filenames"];
2235     int i, numFiles = filenames ? [filenames count] : 0;
2236     BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2237     int layout = [[args objectForKey:@"layout"] intValue];
2239     // Change to directory of first file to open if this is an "unused" editor
2240     // (but do not do this if editing remotely).
2241     if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2242             && (starting || [self unusedEditor]) ) {
2243         char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2244         vim_chdirfile(s);
2245         vim_free(s);
2246     }
2248     if (starting > 0) {
2249         // When Vim is starting we simply add the files to be opened to the
2250         // global arglist and Vim will take care of opening them for us.
2251         if (openFiles && numFiles > 0) {
2252             for (i = 0; i < numFiles; i++) {
2253                 NSString *fname = [filenames objectAtIndex:i];
2254                 char_u *p = NULL;
2256                 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2257                         || (p = [fname vimStringSave]) == NULL)
2258                     exit(2); // See comment in -[MMBackend exit]
2259                 else
2260                     alist_add(&global_alist, p, 2);
2261             }
2263             // Vim will take care of arranging the files added to the arglist
2264             // in windows or tabs; all we must do is to specify which layout to
2265             // use.
2266             initialWindowLayout = layout;
2267         }
2268     } else {
2269         // When Vim is already open we resort to some trickery to open the
2270         // files with the specified layout.
2271         //
2272         // TODO: Figure out a better way to handle this?
2273         if (openFiles && numFiles > 0) {
2274             BOOL oneWindowInTab = topframe ? YES
2275                                            : (topframe->fr_layout == FR_LEAF);
2276             BOOL bufChanged = NO;
2277             BOOL bufHasFilename = NO;
2278             if (curbuf) {
2279                 bufChanged = curbufIsChanged();
2280                 bufHasFilename = curbuf->b_ffname != NULL;
2281             }
2283             // Temporarily disable flushing since the following code may
2284             // potentially cause multiple redraws.
2285             flushDisabled = YES;
2287             BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2288             if (WIN_TABS == layout && !onlyOneTab) {
2289                 // By going to the last tabpage we ensure that the new tabs
2290                 // will appear last (if this call is left out, the taborder
2291                 // becomes messy).
2292                 goto_tabpage(9999);
2293             }
2295             // Make sure we're in normal mode first.
2296             [self addInput:@"<C-\\><C-N>"];
2298             if (numFiles > 1) {
2299                 // With "split layout" we open a new tab before opening
2300                 // multiple files if the current tab has more than one window
2301                 // or if there is exactly one window but whose buffer has a
2302                 // filename.  (The :drop command ensures modified buffers get
2303                 // their own window.)
2304                 if ((WIN_HOR == layout || WIN_VER == layout) &&
2305                         (!oneWindowInTab || bufHasFilename))
2306                     [self addInput:@":tabnew<CR>"];
2308                 // The files are opened by constructing a ":drop ..." command
2309                 // and executing it.
2310                 NSMutableString *cmd = (WIN_TABS == layout)
2311                         ? [NSMutableString stringWithString:@":tab drop"]
2312                         : [NSMutableString stringWithString:@":drop"];
2314                 for (i = 0; i < numFiles; ++i) {
2315                     NSString *file = [filenames objectAtIndex:i];
2316                     file = [file stringByEscapingSpecialFilenameCharacters];
2317                     [cmd appendString:@" "];
2318                     [cmd appendString:file];
2319                 }
2321                 // Temporarily clear 'suffixes' so that the files are opened in
2322                 // the same order as they appear in the "filenames" array.
2323                 [self addInput:@":let t:mvim_oldsu=&su|set su=<CR>"];
2325                 [self addInput:cmd];
2327                 // Split the view into multiple windows if requested.
2328                 if (WIN_HOR == layout)
2329                     [self addInput:@"|sall"];
2330                 else if (WIN_VER == layout)
2331                     [self addInput:@"|vert sall"];
2333                 // Restore the old value of 'suffixes'.
2334                 [self addInput:@"|let &su=t:mvim_oldsu|unlet t:mvim_oldsu"];
2336                 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2337                 [self addInput:@"|redr|f<CR>"];
2338             } else {
2339                 // When opening one file we try to reuse the current window,
2340                 // but not if its buffer is modified or has a filename.
2341                 // However, the 'arglist' layout always opens the file in the
2342                 // current window.
2343                 NSString *file = [[filenames lastObject]
2344                         stringByEscapingSpecialFilenameCharacters];
2345                 NSString *cmd;
2346                 if (WIN_HOR == layout) {
2347                     if (!(bufHasFilename || bufChanged))
2348                         cmd = [NSString stringWithFormat:@":e %@", file];
2349                     else
2350                         cmd = [NSString stringWithFormat:@":sp %@", file];
2351                 } else if (WIN_VER == layout) {
2352                     if (!(bufHasFilename || bufChanged))
2353                         cmd = [NSString stringWithFormat:@":e %@", file];
2354                     else
2355                         cmd = [NSString stringWithFormat:@":vsp %@", file];
2356                 } else if (WIN_TABS == layout) {
2357                     if (oneWindowInTab && !(bufHasFilename || bufChanged))
2358                         cmd = [NSString stringWithFormat:@":e %@", file];
2359                     else
2360                         cmd = [NSString stringWithFormat:@":tabe %@", file];
2361                 } else {
2362                     // (The :drop command will split if there is a modified
2363                     // buffer.)
2364                     cmd = [NSString stringWithFormat:@":drop %@", file];
2365                 }
2367                 [self addInput:cmd];
2369                 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2370                 [self addInput:@"|redr|f<CR>"];
2371             }
2373             // Force screen redraw (does it have to be this complicated?).
2374             // (This code was taken from the end of gui_handle_drop().)
2375             update_screen(NOT_VALID);
2376             setcursor();
2377             out_flush();
2378             gui_update_cursor(FALSE, FALSE);
2379             maketitle();
2381             flushDisabled = NO;
2382         }
2383     }
2385     if ([args objectForKey:@"remoteID"]) {
2386         // NOTE: We have to delay processing any ODB related arguments since
2387         // the file(s) may not be opened until the input buffer is processed.
2388         [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2389                                withObject:args
2390                             waitUntilDone:NO];
2391     }
2393     NSString *lineString = [args objectForKey:@"cursorLine"];
2394     if (lineString && [lineString intValue] > 0) {
2395         NSString *columnString = [args objectForKey:@"cursorColumn"];
2396         if (!(columnString && [columnString intValue] > 0))
2397             columnString = @"1";
2399         NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2400                 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2401         [self addInput:cmd];
2402     }
2404     NSString *rangeString = [args objectForKey:@"selectionRange"];
2405     if (rangeString) {
2406         // Build a command line string that will select the given range of
2407         // lines.  If range.length == 0, then position the cursor on the given
2408         // line but do not select.
2409         NSRange range = NSRangeFromString(rangeString);
2410         NSString *cmd;
2411         if (range.length > 0) {
2412             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2413                     NSMaxRange(range), range.location];
2414         } else {
2415             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2416                     range.location];
2417         }
2419         [self addInput:cmd];
2420     }
2422     NSString *searchText = [args objectForKey:@"searchText"];
2423     if (searchText) {
2424         // TODO: Searching is an exclusive motion, so if the pattern would
2425         // match on row 0 column 0 then this pattern will miss that match.
2426         [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@<CR>",
2427                 searchText]];
2428     }
2431 - (BOOL)checkForModifiedBuffers
2433     buf_T *buf;
2434     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2435         if (bufIsChanged(buf)) {
2436             return YES;
2437         }
2438     }
2440     return NO;
2443 - (void)addInput:(NSString *)input
2445     char_u *s = (char_u*)[input UTF8String];
2447 #ifdef FEAT_MBYTE
2448     s = CONVERT_FROM_UTF8(s);
2449 #endif
2451     server_to_input_buf(s);
2453 #ifdef FEAT_MBYTE
2454     CONVERT_FROM_UTF8_FREE(s);
2455 #endif
2458 - (BOOL)unusedEditor
2460     BOOL oneWindowInTab = topframe ? YES
2461                                    : (topframe->fr_layout == FR_LEAF);
2462     BOOL bufChanged = NO;
2463     BOOL bufHasFilename = NO;
2464     if (curbuf) {
2465         bufChanged = curbufIsChanged();
2466         bufHasFilename = curbuf->b_ffname != NULL;
2467     }
2469     BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2471     return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2474 - (void)redrawScreen
2476     // Force screen redraw (does it have to be this complicated?).
2477     redraw_all_later(CLEAR);
2478     update_screen(NOT_VALID);
2479     setcursor();
2480     out_flush();
2481     gui_update_cursor(FALSE, FALSE);
2483     // HACK! The cursor is not put back at the command line by the above
2484     // "redraw commands".  The following test seems to do the trick though.
2485     if (State & CMDLINE)
2486         redrawcmdline();
2489 @end // MMBackend (Private)
2494 @implementation MMBackend (ClientServer)
2496 - (NSString *)connectionNameFromServerName:(NSString *)name
2498     NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2500     return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2501         lowercaseString];
2504 - (NSConnection *)connectionForServerName:(NSString *)name
2506     // TODO: Try 'name%d' if 'name' fails.
2507     NSString *connName = [self connectionNameFromServerName:name];
2508     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2510     if (!svrConn) {
2511         svrConn = [NSConnection connectionWithRegisteredName:connName
2512                                                            host:nil];
2513         // Try alternate server...
2514         if (!svrConn && alternateServerName) {
2515             //NSLog(@"  trying to connect to alternate server: %@",
2516             //        alternateServerName);
2517             connName = [self connectionNameFromServerName:alternateServerName];
2518             svrConn = [NSConnection connectionWithRegisteredName:connName
2519                                                             host:nil];
2520         }
2522         // Try looking for alternate servers...
2523         if (!svrConn) {
2524             //NSLog(@"  looking for alternate servers...");
2525             NSString *alt = [self alternateServerNameForName:name];
2526             if (alt != alternateServerName) {
2527                 //NSLog(@"  found alternate server: %@", string);
2528                 [alternateServerName release];
2529                 alternateServerName = [alt copy];
2530             }
2531         }
2533         // Try alternate server again...
2534         if (!svrConn && alternateServerName) {
2535             //NSLog(@"  trying to connect to alternate server: %@",
2536             //        alternateServerName);
2537             connName = [self connectionNameFromServerName:alternateServerName];
2538             svrConn = [NSConnection connectionWithRegisteredName:connName
2539                                                             host:nil];
2540         }
2542         if (svrConn) {
2543             [connectionNameDict setObject:svrConn forKey:connName];
2545             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2546             [[NSNotificationCenter defaultCenter] addObserver:self
2547                     selector:@selector(serverConnectionDidDie:)
2548                         name:NSConnectionDidDieNotification object:svrConn];
2549         }
2550     }
2552     return svrConn;
2555 - (NSConnection *)connectionForServerPort:(int)port
2557     NSConnection *conn;
2558     NSEnumerator *e = [connectionNameDict objectEnumerator];
2560     while ((conn = [e nextObject])) {
2561         // HACK! Assume connection uses mach ports.
2562         if (port == [(NSMachPort*)[conn sendPort] machPort])
2563             return conn;
2564     }
2566     return nil;
2569 - (void)serverConnectionDidDie:(NSNotification *)notification
2571     //NSLog(@"%s%@", _cmd, notification);
2573     NSConnection *svrConn = [notification object];
2575     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2576     [[NSNotificationCenter defaultCenter]
2577             removeObserver:self
2578                       name:NSConnectionDidDieNotification
2579                     object:svrConn];
2581     [connectionNameDict removeObjectsForKeys:
2582         [connectionNameDict allKeysForObject:svrConn]];
2584     // HACK! Assume connection uses mach ports.
2585     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2586     NSNumber *key = [NSNumber numberWithInt:port];
2588     [clientProxyDict removeObjectForKey:key];
2589     [serverReplyDict removeObjectForKey:key];
2592 - (void)addClient:(NSDistantObject *)client
2594     NSConnection *conn = [client connectionForProxy];
2595     // HACK! Assume connection uses mach ports.
2596     int port = [(NSMachPort*)[conn sendPort] machPort];
2597     NSNumber *key = [NSNumber numberWithInt:port];
2599     if (![clientProxyDict objectForKey:key]) {
2600         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2601         [clientProxyDict setObject:client forKey:key];
2602     }
2604     // NOTE: 'clientWindow' is a global variable which is used by <client>
2605     clientWindow = port;
2608 - (NSString *)alternateServerNameForName:(NSString *)name
2610     if (!(name && [name length] > 0))
2611         return nil;
2613     // Only look for alternates if 'name' doesn't end in a digit.
2614     unichar lastChar = [name characterAtIndex:[name length]-1];
2615     if (lastChar >= '0' && lastChar <= '9')
2616         return nil;
2618     // Look for alternates among all current servers.
2619     NSArray *list = [self serverList];
2620     if (!(list && [list count] > 0))
2621         return nil;
2623     // Filter out servers starting with 'name' and ending with a number. The
2624     // (?i) pattern ensures that the match is case insensitive.
2625     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2626     NSPredicate *pred = [NSPredicate predicateWithFormat:
2627             @"SELF MATCHES %@", pat];
2628     list = [list filteredArrayUsingPredicate:pred];
2629     if ([list count] > 0) {
2630         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2631         return [list objectAtIndex:0];
2632     }
2634     return nil;
2637 @end // MMBackend (ClientServer)
2642 @implementation NSString (MMServerNameCompare)
2643 - (NSComparisonResult)serverNameCompare:(NSString *)string
2645     return [self compare:string
2646                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2648 @end
2653 static int eventModifierFlagsToVimModMask(int modifierFlags)
2655     int modMask = 0;
2657     if (modifierFlags & NSShiftKeyMask)
2658         modMask |= MOD_MASK_SHIFT;
2659     if (modifierFlags & NSControlKeyMask)
2660         modMask |= MOD_MASK_CTRL;
2661     if (modifierFlags & NSAlternateKeyMask)
2662         modMask |= MOD_MASK_ALT;
2663     if (modifierFlags & NSCommandKeyMask)
2664         modMask |= MOD_MASK_CMD;
2666     return modMask;
2669 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2671     int modMask = 0;
2673     if (modifierFlags & NSShiftKeyMask)
2674         modMask |= MOUSE_SHIFT;
2675     if (modifierFlags & NSControlKeyMask)
2676         modMask |= MOUSE_CTRL;
2677     if (modifierFlags & NSAlternateKeyMask)
2678         modMask |= MOUSE_ALT;
2680     return modMask;
2683 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2685     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2687     return (buttonNumber >= 0 && buttonNumber < 3)
2688             ? mouseButton[buttonNumber] : -1;
2693 // This function is modeled after the VimToPython function found in if_python.c
2694 // NB This does a deep copy by value, it does not lookup references like the
2695 // VimToPython function does.  This is because I didn't want to deal with the
2696 // retain cycles that this would create, and we can cover 99% of the use cases
2697 // by ignoring it.  If we ever switch to using GC in MacVim then this
2698 // functionality can be implemented easily.
2699 static id vimToCocoa(typval_T * tv, int depth)
2701     id result = nil;
2702     id newObj = nil;
2705     // Avoid infinite recursion
2706     if (depth > 100) {
2707         return nil;
2708     }
2710     if (tv->v_type == VAR_STRING) {
2711         char_u * val = tv->vval.v_string;
2712         // val can be NULL if the string is empty
2713         if (!val) {
2714             result = [NSString string];
2715         } else {
2716 #ifdef FEAT_MBYTE
2717             val = CONVERT_TO_UTF8(val);
2718 #endif
2719             result = [NSString stringWithUTF8String:(char*)val];
2720 #ifdef FEAT_MBYTE
2721             CONVERT_TO_UTF8_FREE(val);
2722 #endif
2723         }
2724     } else if (tv->v_type == VAR_NUMBER) {
2725         // looks like sizeof(varnumber_T) is always <= sizeof(long)
2726         result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2727     } else if (tv->v_type == VAR_LIST) {
2728         list_T * list = tv->vval.v_list;
2729         listitem_T * curr;
2731         NSMutableArray * arr = result = [NSMutableArray array];
2733         if (list != NULL) {
2734             for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2735                 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2736                 [arr addObject:newObj];
2737             }
2738         }
2739     } else if (tv->v_type == VAR_DICT) {
2740         NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2742         if (tv->vval.v_dict != NULL) {
2743             hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2744             int todo = ht->ht_used;
2745             hashitem_T * hi;
2746             dictitem_T * di;
2748             for (hi = ht->ht_array; todo > 0; ++hi) {
2749                 if (!HASHITEM_EMPTY(hi)) {
2750                     --todo;
2752                     di = dict_lookup(hi);
2753                     newObj = vimToCocoa(&di->di_tv, depth + 1);
2755                     char_u * keyval = hi->hi_key;
2756 #ifdef FEAT_MBYTE
2757                     keyval = CONVERT_TO_UTF8(keyval);
2758 #endif
2759                     NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2760 #ifdef FEAT_MBYTE
2761                     CONVERT_TO_UTF8_FREE(keyval);
2762 #endif
2763                     [dict setObject:newObj forKey:key];
2764                 }
2765             }
2766         }
2767     } else { // only func refs should fall into this category?
2768         result = nil;
2769     }
2771     return result;
2775 // This function is modeled after eval_client_expr_to_string found in main.c
2776 // Returns nil if there was an error evaluating the expression, and writes a
2777 // message to errorStr.
2778 // TODO Get the error that occurred while evaluating the expression in vim
2779 // somehow.
2780 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2783     char_u *s = (char_u*)[expr UTF8String];
2785 #ifdef FEAT_MBYTE
2786     s = CONVERT_FROM_UTF8(s);
2787 #endif
2789     int save_dbl = debug_break_level;
2790     int save_ro = redir_off;
2792     debug_break_level = -1;
2793     redir_off = 0;
2794     ++emsg_skip;
2796     typval_T * tvres = eval_expr(s, NULL);
2798     debug_break_level = save_dbl;
2799     redir_off = save_ro;
2800     --emsg_skip;
2802     setcursor();
2803     out_flush();
2805 #ifdef FEAT_MBYTE
2806     CONVERT_FROM_UTF8_FREE(s);
2807 #endif
2809 #ifdef FEAT_GUI
2810     if (gui.in_use)
2811         gui_update_cursor(FALSE, FALSE);
2812 #endif
2814     if (tvres == NULL) {
2815         free_tv(tvres);
2816         *errstr = @"Expression evaluation failed.";
2817     }
2819     id res = vimToCocoa(tvres, 1);
2821     free_tv(tvres);
2823     if (res == nil) {
2824         *errstr = @"Conversion to cocoa values failed.";
2825     }
2827     return res;
2832 @implementation NSString (VimStrings)
2834 + (id)stringWithVimString:(char_u *)s
2836     // This method ensures a non-nil string is returned.  If 's' cannot be
2837     // converted to a utf-8 string it is assumed to be latin-1.  If conversion
2838     // still fails an empty NSString is returned.
2839     NSString *string = nil;
2840     if (s) {
2841 #ifdef FEAT_MBYTE
2842         s = CONVERT_TO_UTF8(s);
2843 #endif
2844         string = [NSString stringWithUTF8String:(char*)s];
2845         if (!string) {
2846             // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2847             // latin-1?
2848             string = [NSString stringWithCString:(char*)s
2849                                         encoding:NSISOLatin1StringEncoding];
2850         }
2851 #ifdef FEAT_MBYTE
2852         CONVERT_TO_UTF8_FREE(s);
2853 #endif
2854     }
2856     return string != nil ? string : [NSString string];
2859 - (char_u *)vimStringSave
2861     char_u *s = (char_u*)[self UTF8String], *ret = NULL;
2863 #ifdef FEAT_MBYTE
2864     s = CONVERT_FROM_UTF8(s);
2865 #endif
2866     ret = vim_strsave(s);
2867 #ifdef FEAT_MBYTE
2868     CONVERT_FROM_UTF8_FREE(s);
2869 #endif
2871     return ret;
2874 @end // NSString (VimStrings)