Be more conservative about flushing output queue
[MacVim.git] / src / MacVim / MMBackend.m
blob1c21b12685e0e0c0d4963dfbd9147160f314c1ca
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);
66 enum {
67     MMBlinkStateNone = 0,
68     MMBlinkStateOn,
69     MMBlinkStateOff
72 static NSString *MMSymlinkWarningString =
73     @"\n\n\tMost likely this is because you have symlinked directly to\n"
74      "\tthe Vim binary, which Cocoa does not allow.  Please use an\n"
75      "\talias or the mvim shell script instead.  If you have not used\n"
76      "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
80 @interface NSString (MMServerNameCompare)
81 - (NSComparisonResult)serverNameCompare:(NSString *)string;
82 @end
87 @interface MMBackend (Private)
88 - (void)waitForDialogReturn;
89 - (void)insertVimStateMessage;
90 - (void)processInputQueue;
91 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
92 + (NSDictionary *)specialKeys;
93 - (void)handleInsertText:(NSData *)data;
94 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
95 - (void)queueMessage:(int)msgid data:(NSData *)data;
96 - (void)connectionDidDie:(NSNotification *)notification;
97 - (void)blinkTimerFired:(NSTimer *)timer;
98 - (void)focusChange:(BOOL)on;
99 - (void)handleToggleToolbar;
100 - (void)handleScrollbarEvent:(NSData *)data;
101 - (void)handleSetFont:(NSData *)data;
102 - (void)handleDropFiles:(NSData *)data;
103 - (void)handleDropString:(NSData *)data;
104 - (void)startOdbEditWithArguments:(NSDictionary *)args;
105 - (void)handleXcodeMod:(NSData *)data;
106 - (void)handleOpenWithArguments:(NSDictionary *)args;
107 - (BOOL)checkForModifiedBuffers;
108 - (void)addInput:(NSString *)input;
109 - (BOOL)unusedEditor;
110 @end
114 @interface MMBackend (ClientServer)
115 - (NSString *)connectionNameFromServerName:(NSString *)name;
116 - (NSConnection *)connectionForServerName:(NSString *)name;
117 - (NSConnection *)connectionForServerPort:(int)port;
118 - (void)serverConnectionDidDie:(NSNotification *)notification;
119 - (void)addClient:(NSDistantObject *)client;
120 - (NSString *)alternateServerNameForName:(NSString *)name;
121 @end
125 @implementation MMBackend
127 + (MMBackend *)sharedInstance
129     static MMBackend *singleton = nil;
130     return singleton ? singleton : (singleton = [MMBackend new]);
133 - (id)init
135     self = [super init];
136     if (!self) return nil;
138     fontContainerRef = loadFonts();
140     outputQueue = [[NSMutableArray alloc] init];
141     inputQueue = [[NSMutableArray alloc] init];
142     drawData = [[NSMutableData alloc] initWithCapacity:1024];
143     connectionNameDict = [[NSMutableDictionary alloc] init];
144     clientProxyDict = [[NSMutableDictionary alloc] init];
145     serverReplyDict = [[NSMutableDictionary alloc] init];
147     NSBundle *mainBundle = [NSBundle mainBundle];
148     NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
149     if (path)
150         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
152     path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
153     if (path)
154         sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
155             retain];
157     path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
158     if (path)
159         actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
161     if (!(colorDict && sysColorDict && actionDict))
162         NSLog(@"ERROR: Failed to load dictionaries.%@",
163                 MMSymlinkWarningString);
165     return self;
168 - (void)dealloc
170     //NSLog(@"%@ %s", [self className], _cmd);
171     [[NSNotificationCenter defaultCenter] removeObserver:self];
173     [oldWideFont release];  oldWideFont = nil;
174     [blinkTimer release];  blinkTimer = nil;
175     [alternateServerName release];  alternateServerName = nil;
176     [serverReplyDict release];  serverReplyDict = nil;
177     [clientProxyDict release];  clientProxyDict = nil;
178     [connectionNameDict release];  connectionNameDict = nil;
179     [inputQueue release];  inputQueue = nil;
180     [outputQueue release];  outputQueue = nil;
181     [drawData release];  drawData = nil;
182     [frontendProxy release];  frontendProxy = nil;
183     [connection release];  connection = nil;
184     [actionDict release];  actionDict = nil;
185     [sysColorDict release];  sysColorDict = nil;
186     [colorDict release];  colorDict = nil;
188     [super dealloc];
191 - (void)setBackgroundColor:(int)color
193     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
196 - (void)setForegroundColor:(int)color
198     foregroundColor = MM_COLOR(color);
201 - (void)setSpecialColor:(int)color
203     specialColor = MM_COLOR(color);
206 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
208     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
209     defaultForegroundColor = MM_COLOR(fg);
211     NSMutableData *data = [NSMutableData data];
213     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
214     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
216     [self queueMessage:SetDefaultColorsMsgID data:data];
219 - (NSConnection *)connection
221     if (!connection) {
222         // NOTE!  If the name of the connection changes here it must also be
223         // updated in MMAppController.m.
224         NSString *name = [NSString stringWithFormat:@"%@-connection",
225                [[NSBundle mainBundle] bundlePath]];
227         connection = [NSConnection connectionWithRegisteredName:name host:nil];
228         [connection retain];
229     }
231     // NOTE: 'connection' may be nil here.
232     return connection;
235 - (NSDictionary *)actionDict
237     return actionDict;
240 - (int)initialWindowLayout
242     return initialWindowLayout;
245 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
247     [self queueMessage:msgid data:[props dictionaryAsData]];
250 - (BOOL)checkin
252     if (![self connection]) {
253         if (waitForAck) {
254             // This is a preloaded process and as such should not cause the
255             // MacVim to be opened.  We probably got here as a result of the
256             // user quitting MacVim while the process was preloading, so exit
257             // this process too.
258             // (Don't use mch_exit() since it assumes the process has properly
259             // started.)
260             exit(0);
261         }
263         NSBundle *mainBundle = [NSBundle mainBundle];
264 #if 0
265         OSStatus status;
266         FSRef ref;
268         // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
269         // the API to pass Apple Event parameters is broken on 10.4).
270         NSString *path = [mainBundle bundlePath];
271         status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
272         if (noErr == status) {
273             // Pass parameter to the 'Open' Apple Event that tells MacVim not
274             // to open an untitled window.
275             NSAppleEventDescriptor *desc =
276                     [NSAppleEventDescriptor recordDescriptor];
277             [desc setParamDescriptor:
278                     [NSAppleEventDescriptor descriptorWithBoolean:NO]
279                           forKeyword:keyMMUntitledWindow];
281             LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
282                     kLSLaunchDefaults, NULL };
283             status = LSOpenFromRefSpec(&spec, NULL);
284         }
286         if (noErr != status) {
287         NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
288                 path, MMSymlinkWarningString);
289             return NO;
290         }
291 #else
292         // Launch MacVim using NSTask.  For some reason the above code using
293         // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
294         // fails, the dock icon starts bouncing and never stops).  It seems
295         // like rebuilding the Launch Services database takes care of this
296         // problem, but the NSTask way seems more stable so stick with it.
297         //
298         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
299         // that the GUI won't be activated (or raised) so there is a hack in
300         // MMAppController which raises the app when a new window is opened.
301         NSMutableArray *args = [NSMutableArray arrayWithObjects:
302             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
303         NSString *exeName = [[mainBundle infoDictionary]
304                 objectForKey:@"CFBundleExecutable"];
305         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
306         if (!path) {
307             NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
308                     MMSymlinkWarningString);
309             return NO;
310         }
312         [NSTask launchedTaskWithLaunchPath:path arguments:args];
313 #endif
315         // HACK!  Poll the mach bootstrap server until it returns a valid
316         // connection to detect that MacVim has finished launching.  Also set a
317         // time-out date so that we don't get stuck doing this forever.
318         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
319         while (![self connection] &&
320                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
321             [[NSRunLoop currentRunLoop]
322                     runMode:NSDefaultRunLoopMode
323                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
325         // NOTE: [self connection] will set 'connection' as a side-effect.
326         if (!connection) {
327             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
328             return NO;
329         }
330     }
332     BOOL ok = NO;
333     @try {
334         [[NSNotificationCenter defaultCenter] addObserver:self
335                 selector:@selector(connectionDidDie:)
336                     name:NSConnectionDidDieNotification object:connection];
338         id proxy = [connection rootProxy];
339         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
341         int pid = [[NSProcessInfo processInfo] processIdentifier];
343         frontendProxy = [proxy connectBackend:self pid:pid];
344         if (frontendProxy) {
345             [frontendProxy retain];
346             [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
347             ok = YES;
348         }
349     }
350     @catch (NSException *e) {
351         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
352     }
354     return ok;
357 - (BOOL)openGUIWindow
359     [self queueMessage:OpenWindowMsgID data:nil];
360     return YES;
363 - (void)clearAll
365     int type = ClearAllDrawType;
367     // Any draw commands in queue are effectively obsolete since this clearAll
368     // will negate any effect they have, therefore we may as well clear the
369     // draw queue.
370     [drawData setLength:0];
372     [drawData appendBytes:&type length:sizeof(int)];
375 - (void)clearBlockFromRow:(int)row1 column:(int)col1
376                     toRow:(int)row2 column:(int)col2
378     int type = ClearBlockDrawType;
380     [drawData appendBytes:&type length:sizeof(int)];
382     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
383     [drawData appendBytes:&row1 length:sizeof(int)];
384     [drawData appendBytes:&col1 length:sizeof(int)];
385     [drawData appendBytes:&row2 length:sizeof(int)];
386     [drawData appendBytes:&col2 length:sizeof(int)];
389 - (void)deleteLinesFromRow:(int)row count:(int)count
390               scrollBottom:(int)bottom left:(int)left right:(int)right
392     int type = DeleteLinesDrawType;
394     [drawData appendBytes:&type length:sizeof(int)];
396     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
397     [drawData appendBytes:&row length:sizeof(int)];
398     [drawData appendBytes:&count length:sizeof(int)];
399     [drawData appendBytes:&bottom length:sizeof(int)];
400     [drawData appendBytes:&left length:sizeof(int)];
401     [drawData appendBytes:&right length:sizeof(int)];
404 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
405              cells:(int)cells flags:(int)flags
407     if (len <= 0 || cells <= 0) return;
409     int type = DrawStringDrawType;
411     [drawData appendBytes:&type length:sizeof(int)];
413     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
414     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
415     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
416     [drawData appendBytes:&row length:sizeof(int)];
417     [drawData appendBytes:&col length:sizeof(int)];
418     [drawData appendBytes:&cells length:sizeof(int)];
419     [drawData appendBytes:&flags length:sizeof(int)];
420     [drawData appendBytes:&len length:sizeof(int)];
421     [drawData appendBytes:s length:len];
424 - (void)insertLinesFromRow:(int)row count:(int)count
425               scrollBottom:(int)bottom left:(int)left right:(int)right
427     int type = InsertLinesDrawType;
429     [drawData appendBytes:&type length:sizeof(int)];
431     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
432     [drawData appendBytes:&row length:sizeof(int)];
433     [drawData appendBytes:&count length:sizeof(int)];
434     [drawData appendBytes:&bottom length:sizeof(int)];
435     [drawData appendBytes:&left length:sizeof(int)];
436     [drawData appendBytes:&right length:sizeof(int)];
439 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
440                fraction:(int)percent color:(int)color
442     int type = DrawCursorDrawType;
443     unsigned uc = MM_COLOR(color);
445     [drawData appendBytes:&type length:sizeof(int)];
447     [drawData appendBytes:&uc length:sizeof(unsigned)];
448     [drawData appendBytes:&row length:sizeof(int)];
449     [drawData appendBytes:&col length:sizeof(int)];
450     [drawData appendBytes:&shape length:sizeof(int)];
451     [drawData appendBytes:&percent length:sizeof(int)];
454 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
455                    numColumns:(int)nc invert:(int)invert
457     int type = DrawInvertedRectDrawType;
458     [drawData appendBytes:&type length:sizeof(int)];
460     [drawData appendBytes:&row length:sizeof(int)];
461     [drawData appendBytes:&col length:sizeof(int)];
462     [drawData appendBytes:&nr length:sizeof(int)];
463     [drawData appendBytes:&nc length:sizeof(int)];
464     [drawData appendBytes:&invert length:sizeof(int)];
467 - (void)update
469     // Tend to the run loop, returning immediately if there are no events
470     // waiting.
471     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
472                              beforeDate:[NSDate distantPast]];
475 - (void)flushQueue:(BOOL)force
477     // NOTE: This variable allows for better control over when the queue is
478     // flushed.  It can be set to YES at the beginning of a sequence of calls
479     // that may potentially add items to the queue, and then restored back to
480     // NO.
481     if (flushDisabled) return;
483     if ([drawData length] > 0) {
484         // HACK!  Detect changes to 'guifontwide'.
485         if (gui.wide_font != (GuiFont)oldWideFont) {
486             [oldWideFont release];
487             oldWideFont = [(NSFont*)gui.wide_font retain];
488             [self setWideFont:oldWideFont];
489         }
491         int type = SetCursorPosDrawType;
492         [drawData appendBytes:&type length:sizeof(type)];
493         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
494         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
496         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
497         [drawData setLength:0];
498     }
500     if ([outputQueue count] > 0) {
501         [self insertVimStateMessage];
503         @try {
504             [frontendProxy processCommandQueue:outputQueue];
505         }
506         @catch (NSException *e) {
507             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
508         }
510         [outputQueue removeAllObjects];
511     }
514 - (BOOL)waitForInput:(int)milliseconds
516     // Return NO if we timed out waiting for input, otherwise return YES.
517     BOOL inputReceived = NO;
519     // Only start the run loop if the input queue is empty, otherwise process
520     // the input first so that the input on queue isn't delayed.
521     if ([inputQueue count]) {
522         inputReceived = YES;
523     } else {
524         // Wait for the specified amount of time, unless 'milliseconds' is
525         // negative in which case we wait "forever" (1e6 seconds translates to
526         // approximately 11 days).
527         CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
529         while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
530                 == kCFRunLoopRunHandledSource) {
531             // In order to ensure that all input on the run-loop has been
532             // processed we set the timeout to 0 and keep processing until the
533             // run-loop times out.
534             dt = 0.0;
535             inputReceived = YES;
536         }
537     }
539     // The above calls may have placed messages on the input queue so process
540     // it now.  This call may enter a blocking loop.
541     if ([inputQueue count] > 0)
542         [self processInputQueue];
544     return inputReceived;
547 - (void)exit
549     // NOTE: This is called if mch_exit() is called.  Since we assume here that
550     // the process has started properly, be sure to use exit() instead of
551     // mch_exit() to prematurely terminate a process.
553     // To notify MacVim that this Vim process is exiting we could simply
554     // invalidate the connection and it would automatically receive a
555     // connectionDidDie: notification.  However, this notification seems to
556     // take up to 300 ms to arrive which is quite a noticeable delay.  Instead
557     // we immediately send a message to MacVim asking it to close the window
558     // belonging to this process, and then we invalidate the connection (in
559     // case the message got lost).
561     // Make sure no connectionDidDie: notification is received now that we are
562     // already exiting.
563     [[NSNotificationCenter defaultCenter] removeObserver:self];
565     if ([connection isValid]) {
566         @try {
567             // Flush the entire queue in case a VimLeave autocommand added
568             // something to the queue.
569             [self queueMessage:CloseWindowMsgID data:nil];
570             [frontendProxy processCommandQueue:outputQueue];
571         }
572         @catch (NSException *e) {
573             NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
574         }
576         [connection invalidate];
577     }
579 #ifdef MAC_CLIENTSERVER
580     // The default connection is used for the client/server code.
581     [[NSConnection defaultConnection] setRootObject:nil];
582     [[NSConnection defaultConnection] invalidate];
583 #endif
585     if (fontContainerRef) {
586         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
587         fontContainerRef = 0;
588     }
590     usleep(MMExitProcessDelay);
593 - (void)selectTab:(int)index
595     //NSLog(@"%s%d", _cmd, index);
597     index -= 1;
598     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
599     [self queueMessage:SelectTabMsgID data:data];
602 - (void)updateTabBar
604     //NSLog(@"%s", _cmd);
606     NSMutableData *data = [NSMutableData data];
608     int idx = tabpage_index(curtab) - 1;
609     [data appendBytes:&idx length:sizeof(int)];
611     tabpage_T *tp;
612     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
613         // This function puts the label of the tab in the global 'NameBuff'.
614         get_tabline_label(tp, FALSE);
615         char_u *s = NameBuff;
616         int len = STRLEN(s);
617         if (len <= 0) continue;
619 #ifdef FEAT_MBYTE
620         s = CONVERT_TO_UTF8(s);
621 #endif
623         // Count the number of windows in the tabpage.
624         //win_T *wp = tp->tp_firstwin;
625         //int wincount;
626         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
628         //[data appendBytes:&wincount length:sizeof(int)];
629         [data appendBytes:&len length:sizeof(int)];
630         [data appendBytes:s length:len];
632 #ifdef FEAT_MBYTE
633         CONVERT_TO_UTF8_FREE(s);
634 #endif
635     }
637     [self queueMessage:UpdateTabBarMsgID data:data];
640 - (BOOL)tabBarVisible
642     return tabBarVisible;
645 - (void)showTabBar:(BOOL)enable
647     tabBarVisible = enable;
649     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
650     [self queueMessage:msgid data:nil];
653 - (void)setRows:(int)rows columns:(int)cols
655     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
657     int dim[] = { rows, cols };
658     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
660     [self queueMessage:SetTextDimensionsMsgID data:data];
663 - (void)setWindowTitle:(char *)title
665     NSMutableData *data = [NSMutableData data];
666     int len = strlen(title);
667     if (len <= 0) return;
669     [data appendBytes:&len length:sizeof(int)];
670     [data appendBytes:title length:len];
672     [self queueMessage:SetWindowTitleMsgID data:data];
675 - (void)setDocumentFilename:(char *)filename
677     NSMutableData *data = [NSMutableData data];
678     int len = filename ? strlen(filename) : 0;
680     [data appendBytes:&len length:sizeof(int)];
681     if (len > 0)
682         [data appendBytes:filename length:len];
684     [self queueMessage:SetDocumentFilenameMsgID data:data];
687 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
689     char_u *s = NULL;
691     @try {
692         [frontendProxy showSavePanelWithAttributes:attr];
694         [self waitForDialogReturn];
696         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
697             char_u *ret = (char_u*)[dialogReturn UTF8String];
698 #ifdef FEAT_MBYTE
699             ret = CONVERT_FROM_UTF8(ret);
700 #endif
701             s = vim_strsave(ret);
702 #ifdef FEAT_MBYTE
703             CONVERT_FROM_UTF8_FREE(ret);
704 #endif
705         }
707         [dialogReturn release];  dialogReturn = nil;
708     }
709     @catch (NSException *e) {
710         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
711     }
713     return (char *)s;
716 - (oneway void)setDialogReturn:(in bycopy id)obj
718     // NOTE: This is called by
719     //   - [MMVimController panelDidEnd:::], and
720     //   - [MMVimController alertDidEnd:::],
721     // to indicate that a save/open panel or alert has finished.
723     // We want to distinguish between "no dialog return yet" and "dialog
724     // returned nothing".  The former can be tested with dialogReturn == nil,
725     // the latter with dialogReturn == [NSNull null].
726     if (!obj) obj = [NSNull null];
728     if (obj != dialogReturn) {
729         [dialogReturn release];
730         dialogReturn = [obj retain];
731     }
734 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
736     int retval = 0;
738     @try {
739         [frontendProxy presentDialogWithAttributes:attr];
741         [self waitForDialogReturn];
743         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
744                 && [dialogReturn count]) {
745             retval = [[dialogReturn objectAtIndex:0] intValue];
746             if (txtfield && [dialogReturn count] > 1) {
747                 NSString *retString = [dialogReturn objectAtIndex:1];
748                 char_u *ret = (char_u*)[retString UTF8String];
749 #ifdef FEAT_MBYTE
750                 ret = CONVERT_FROM_UTF8(ret);
751 #endif
752                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
753 #ifdef FEAT_MBYTE
754                 CONVERT_FROM_UTF8_FREE(ret);
755 #endif
756             }
757         }
759         [dialogReturn release]; dialogReturn = nil;
760     }
761     @catch (NSException *e) {
762         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
763     }
765     return retval;
768 - (void)showToolbar:(int)enable flags:(int)flags
770     NSMutableData *data = [NSMutableData data];
772     [data appendBytes:&enable length:sizeof(int)];
773     [data appendBytes:&flags length:sizeof(int)];
775     [self queueMessage:ShowToolbarMsgID data:data];
778 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
780     NSMutableData *data = [NSMutableData data];
782     [data appendBytes:&ident length:sizeof(long)];
783     [data appendBytes:&type length:sizeof(int)];
785     [self queueMessage:CreateScrollbarMsgID data:data];
788 - (void)destroyScrollbarWithIdentifier:(long)ident
790     NSMutableData *data = [NSMutableData data];
791     [data appendBytes:&ident length:sizeof(long)];
793     [self queueMessage:DestroyScrollbarMsgID data:data];
796 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
798     NSMutableData *data = [NSMutableData data];
800     [data appendBytes:&ident length:sizeof(long)];
801     [data appendBytes:&visible length:sizeof(int)];
803     [self queueMessage:ShowScrollbarMsgID data:data];
806 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
808     NSMutableData *data = [NSMutableData data];
810     [data appendBytes:&ident length:sizeof(long)];
811     [data appendBytes:&pos length:sizeof(int)];
812     [data appendBytes:&len length:sizeof(int)];
814     [self queueMessage:SetScrollbarPositionMsgID data:data];
817 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
818                     identifier:(long)ident
820     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
821     float prop = (float)size/(max+1);
822     if (fval < 0) fval = 0;
823     else if (fval > 1.0f) fval = 1.0f;
824     if (prop < 0) prop = 0;
825     else if (prop > 1.0f) prop = 1.0f;
827     NSMutableData *data = [NSMutableData data];
829     [data appendBytes:&ident length:sizeof(long)];
830     [data appendBytes:&fval length:sizeof(float)];
831     [data appendBytes:&prop length:sizeof(float)];
833     [self queueMessage:SetScrollbarThumbMsgID data:data];
836 - (void)setFont:(NSFont *)font
838     NSString *fontName = [font displayName];
839     float size = [font pointSize];
840     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
841     if (len > 0) {
842         NSMutableData *data = [NSMutableData data];
844         [data appendBytes:&size length:sizeof(float)];
845         [data appendBytes:&len length:sizeof(int)];
846         [data appendBytes:[fontName UTF8String] length:len];
848         [self queueMessage:SetFontMsgID data:data];
849     }
852 - (void)setWideFont:(NSFont *)font
854     NSString *fontName = [font displayName];
855     float size = [font pointSize];
856     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
857     NSMutableData *data = [NSMutableData data];
859     [data appendBytes:&size length:sizeof(float)];
860     [data appendBytes:&len length:sizeof(int)];
861     if (len > 0)
862         [data appendBytes:[fontName UTF8String] length:len];
864     [self queueMessage:SetWideFontMsgID data:data];
867 - (void)executeActionWithName:(NSString *)name
869     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
871     if (len > 0) {
872         NSMutableData *data = [NSMutableData data];
874         [data appendBytes:&len length:sizeof(int)];
875         [data appendBytes:[name UTF8String] length:len];
877         [self queueMessage:ExecuteActionMsgID data:data];
878     }
881 - (void)setMouseShape:(int)shape
883     NSMutableData *data = [NSMutableData data];
884     [data appendBytes:&shape length:sizeof(int)];
885     [self queueMessage:SetMouseShapeMsgID data:data];
888 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
890     // Vim specifies times in milliseconds, whereas Cocoa wants them in
891     // seconds.
892     blinkWaitInterval = .001f*wait;
893     blinkOnInterval = .001f*on;
894     blinkOffInterval = .001f*off;
897 - (void)startBlink
899     if (blinkTimer) {
900         [blinkTimer invalidate];
901         [blinkTimer release];
902         blinkTimer = nil;
903     }
905     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
906             && gui.in_focus) {
907         blinkState = MMBlinkStateOn;
908         blinkTimer =
909             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
910                                               target:self
911                                             selector:@selector(blinkTimerFired:)
912                                             userInfo:nil repeats:NO] retain];
913         gui_update_cursor(TRUE, FALSE);
914         [self flushQueue:YES];
915     }
918 - (void)stopBlink
920     if (MMBlinkStateOff == blinkState) {
921         gui_update_cursor(TRUE, FALSE);
922         [self flushQueue:YES];
923     }
925     blinkState = MMBlinkStateNone;
928 - (void)adjustLinespace:(int)linespace
930     NSMutableData *data = [NSMutableData data];
931     [data appendBytes:&linespace length:sizeof(int)];
932     [self queueMessage:AdjustLinespaceMsgID data:data];
935 - (void)activate
937     [self queueMessage:ActivateMsgID data:nil];
940 - (void)setPreEditRow:(int)row column:(int)col
942     NSMutableData *data = [NSMutableData data];
943     [data appendBytes:&row length:sizeof(int)];
944     [data appendBytes:&col length:sizeof(int)];
945     [self queueMessage:SetPreEditPositionMsgID data:data];
948 - (int)lookupColorWithKey:(NSString *)key
950     if (!(key && [key length] > 0))
951         return INVALCOLOR;
953     NSString *stripKey = [[[[key lowercaseString]
954         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
955             componentsSeparatedByString:@" "]
956                componentsJoinedByString:@""];
958     if (stripKey && [stripKey length] > 0) {
959         // First of all try to lookup key in the color dictionary; note that
960         // all keys in this dictionary are lowercase with no whitespace.
961         id obj = [colorDict objectForKey:stripKey];
962         if (obj) return [obj intValue];
964         // The key was not in the dictionary; is it perhaps of the form
965         // #rrggbb?
966         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
967             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
968             [scanner setScanLocation:1];
969             unsigned hex = 0;
970             if ([scanner scanHexInt:&hex]) {
971                 return (int)hex;
972             }
973         }
975         // As a last resort, check if it is one of the system defined colors.
976         // The keys in this dictionary are also lowercase with no whitespace.
977         obj = [sysColorDict objectForKey:stripKey];
978         if (obj) {
979             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
980             if (col) {
981                 float r, g, b, a;
982                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
983                 [col getRed:&r green:&g blue:&b alpha:&a];
984                 return (((int)(r*255+.5f) & 0xff) << 16)
985                      + (((int)(g*255+.5f) & 0xff) << 8)
986                      +  ((int)(b*255+.5f) & 0xff);
987             }
988         }
989     }
991     //NSLog(@"WARNING: No color with key %@ found.", stripKey);
992     return INVALCOLOR;
995 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
997     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
998     id obj;
1000     while ((obj = [e nextObject])) {
1001         if ([value isEqual:obj])
1002             return YES;
1003     }
1005     return NO;
1008 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1010     NSMutableData *data = [NSMutableData data];
1011     [data appendBytes:&fuoptions length:sizeof(int)];
1012     bg = MM_COLOR(bg);
1013     [data appendBytes:&bg length:sizeof(int)];
1014     [self queueMessage:EnterFullscreenMsgID data:data];
1017 - (void)leaveFullscreen
1019     [self queueMessage:LeaveFullscreenMsgID data:nil];
1022 - (void)setAntialias:(BOOL)antialias
1024     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1026     [self queueMessage:msgid data:nil];
1029 - (void)updateModifiedFlag
1031     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1032     // vice versa.
1033     int msgid = [self checkForModifiedBuffers]
1034             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1036     [self queueMessage:msgid data:nil];
1039 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1041     // Remove all previous instances of this message from the input queue, else
1042     // the input queue may fill up as a result of Vim not being able to keep up
1043     // with the speed at which new messages are received.  This avoids annoying
1044     // situations such as when the keyboard repeat rate is higher than what Vim
1045     // can cope with (which would cause a 'stutter' when scrolling by holding
1046     // down 'j' and then when 'j' was released the screen kept scrolling for a
1047     // little while).
1049     int i, count = [inputQueue count];
1050     for (i = 1; i < count; i+=2) {
1051         if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1052             [inputQueue removeObjectAtIndex:i];
1053             [inputQueue removeObjectAtIndex:i-1];
1054             break;
1055         }
1056     }
1058     [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1059     [inputQueue addObject:(data ? (id)data : [NSNull null])];
1062 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1064     // This is just a convenience method that allows the frontend to delay
1065     // sending messages.
1066     int i, count = [messages count];
1067     for (i = 1; i < count; i+=2)
1068         [self processInput:[[messages objectAtIndex:i-1] intValue]
1069                       data:[messages objectAtIndex:i]];
1072 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1073                   errorString:(out bycopy NSString **)errstr
1075     return evalExprCocoa(expr, errstr);
1079 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1081     NSString *eval = nil;
1082     char_u *s = (char_u*)[expr UTF8String];
1084 #ifdef FEAT_MBYTE
1085     s = CONVERT_FROM_UTF8(s);
1086 #endif
1088     char_u *res = eval_client_expr_to_string(s);
1090 #ifdef FEAT_MBYTE
1091     CONVERT_FROM_UTF8_FREE(s);
1092 #endif
1094     if (res != NULL) {
1095         s = res;
1096 #ifdef FEAT_MBYTE
1097         s = CONVERT_TO_UTF8(s);
1098 #endif
1099         eval = [NSString stringWithUTF8String:(char*)s];
1100 #ifdef FEAT_MBYTE
1101         CONVERT_TO_UTF8_FREE(s);
1102 #endif
1103         vim_free(res);
1104     }
1106     return eval;
1109 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1111     // TODO: This method should share code with clip_mch_request_selection().
1113     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1114         // If there is no pasteboard, return YES to indicate that there is text
1115         // to copy.
1116         if (!pboard)
1117             return YES;
1119         clip_copy_selection();
1121         // Get the text to put on the pasteboard.
1122         long_u llen = 0; char_u *str = 0;
1123         int type = clip_convert_selection(&str, &llen, &clip_star);
1124         if (type < 0)
1125             return NO;
1126         
1127         // TODO: Avoid overflow.
1128         int len = (int)llen;
1129 #ifdef FEAT_MBYTE
1130         if (output_conv.vc_type != CONV_NONE) {
1131             char_u *conv_str = string_convert(&output_conv, str, &len);
1132             if (conv_str) {
1133                 vim_free(str);
1134                 str = conv_str;
1135             }
1136         }
1137 #endif
1139         NSString *string = [[NSString alloc]
1140             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1142         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1143         [pboard declareTypes:types owner:nil];
1144         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1145     
1146         [string release];
1147         vim_free(str);
1149         return ok;
1150     }
1152     return NO;
1155 - (oneway void)addReply:(in bycopy NSString *)reply
1156                  server:(in byref id <MMVimServerProtocol>)server
1158     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1160     // Replies might come at any time and in any order so we keep them in an
1161     // array inside a dictionary with the send port used as key.
1163     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1164     // HACK! Assume connection uses mach ports.
1165     int port = [(NSMachPort*)[conn sendPort] machPort];
1166     NSNumber *key = [NSNumber numberWithInt:port];
1168     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1169     if (!replies) {
1170         replies = [NSMutableArray array];
1171         [serverReplyDict setObject:replies forKey:key];
1172     }
1174     [replies addObject:reply];
1177 - (void)addInput:(in bycopy NSString *)input
1178                  client:(in byref id <MMVimClientProtocol>)client
1180     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1182     [self addInput:input];
1183     [self addClient:(id)client];
1186 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1187                  client:(in byref id <MMVimClientProtocol>)client
1189     [self addClient:(id)client];
1190     return [self evaluateExpression:expr];
1193 - (void)registerServerWithName:(NSString *)name
1195     NSString *svrName = name;
1196     NSConnection *svrConn = [NSConnection defaultConnection];
1197     unsigned i;
1199     for (i = 0; i < MMServerMax; ++i) {
1200         NSString *connName = [self connectionNameFromServerName:svrName];
1202         if ([svrConn registerName:connName]) {
1203             //NSLog(@"Registered server with name: %@", svrName);
1205             // TODO: Set request/reply time-outs to something else?
1206             //
1207             // Don't wait for requests (time-out means that the message is
1208             // dropped).
1209             [svrConn setRequestTimeout:0];
1210             //[svrConn setReplyTimeout:MMReplyTimeout];
1211             [svrConn setRootObject:self];
1213             char_u *s = (char_u*)[svrName UTF8String];
1214 #ifdef FEAT_MBYTE
1215             s = CONVERT_FROM_UTF8(s);
1216 #endif
1217             // NOTE: 'serverName' is a global variable
1218             serverName = vim_strsave(s);
1219 #ifdef FEAT_MBYTE
1220             CONVERT_FROM_UTF8_FREE(s);
1221 #endif
1222 #ifdef FEAT_EVAL
1223             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1224 #endif
1225 #ifdef FEAT_TITLE
1226             need_maketitle = TRUE;
1227 #endif
1228             [self queueMessage:SetServerNameMsgID data:
1229                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1230             break;
1231         }
1233         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1234     }
1237 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1238                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1239               silent:(BOOL)silent
1241     // NOTE: If 'name' equals 'serverName' then the request is local (client
1242     // and server are the same).  This case is not handled separately, so a
1243     // connection will be set up anyway (this simplifies the code).
1245     NSConnection *conn = [self connectionForServerName:name];
1246     if (!conn) {
1247         if (!silent) {
1248             char_u *s = (char_u*)[name UTF8String];
1249 #ifdef FEAT_MBYTE
1250             s = CONVERT_FROM_UTF8(s);
1251 #endif
1252             EMSG2(_(e_noserver), s);
1253 #ifdef FEAT_MBYTE
1254             CONVERT_FROM_UTF8_FREE(s);
1255 #endif
1256         }
1257         return NO;
1258     }
1260     if (port) {
1261         // HACK! Assume connection uses mach ports.
1262         *port = [(NSMachPort*)[conn sendPort] machPort];
1263     }
1265     id proxy = [conn rootProxy];
1266     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1268     @try {
1269         if (expr) {
1270             NSString *eval = [proxy evaluateExpression:string client:self];
1271             if (reply) {
1272                 if (eval) {
1273                     char_u *r = (char_u*)[eval UTF8String];
1274 #ifdef FEAT_MBYTE
1275                     r = CONVERT_FROM_UTF8(r);
1276 #endif
1277                     *reply = vim_strsave(r);
1278 #ifdef FEAT_MBYTE
1279                     CONVERT_FROM_UTF8_FREE(r);
1280 #endif
1281                 } else {
1282                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1283                 }
1284             }
1286             if (!eval)
1287                 return NO;
1288         } else {
1289             [proxy addInput:string client:self];
1290         }
1291     }
1292     @catch (NSException *e) {
1293         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1294         return NO;
1295     }
1297     return YES;
1300 - (NSArray *)serverList
1302     NSArray *list = nil;
1304     if ([self connection]) {
1305         id proxy = [connection rootProxy];
1306         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1308         @try {
1309             list = [proxy serverList];
1310         }
1311         @catch (NSException *e) {
1312             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1313         }
1314     } else {
1315         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1316     }
1318     return list;
1321 - (NSString *)peekForReplyOnPort:(int)port
1323     //NSLog(@"%s%d", _cmd, port);
1325     NSNumber *key = [NSNumber numberWithInt:port];
1326     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1327     if (replies && [replies count]) {
1328         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1329         //        [replies objectAtIndex:0]);
1330         return [replies objectAtIndex:0];
1331     }
1333     //NSLog(@"    No replies");
1334     return nil;
1337 - (NSString *)waitForReplyOnPort:(int)port
1339     //NSLog(@"%s%d", _cmd, port);
1340     
1341     NSConnection *conn = [self connectionForServerPort:port];
1342     if (!conn)
1343         return nil;
1345     NSNumber *key = [NSNumber numberWithInt:port];
1346     NSMutableArray *replies = nil;
1347     NSString *reply = nil;
1349     // Wait for reply as long as the connection to the server is valid (unless
1350     // user interrupts wait with Ctrl-C).
1351     while (!got_int && [conn isValid] &&
1352             !(replies = [serverReplyDict objectForKey:key])) {
1353         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1354                                  beforeDate:[NSDate distantFuture]];
1355     }
1357     if (replies) {
1358         if ([replies count] > 0) {
1359             reply = [[replies objectAtIndex:0] retain];
1360             //NSLog(@"    Got reply: %@", reply);
1361             [replies removeObjectAtIndex:0];
1362             [reply autorelease];
1363         }
1365         if ([replies count] == 0)
1366             [serverReplyDict removeObjectForKey:key];
1367     }
1369     return reply;
1372 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1374     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1375     if (client) {
1376         @try {
1377             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1378             [client addReply:reply server:self];
1379             return YES;
1380         }
1381         @catch (NSException *e) {
1382             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1383         }
1384     } else {
1385         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1386     }
1388     return NO;
1391 - (BOOL)waitForAck
1393     return waitForAck;
1396 - (void)setWaitForAck:(BOOL)yn
1398     waitForAck = yn;
1401 - (void)waitForConnectionAcknowledgement
1403     if (!waitForAck) return;
1405     while (waitForAck && !got_int && [connection isValid] && !isTerminating) {
1406         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1407                                  beforeDate:[NSDate distantFuture]];
1408         //NSLog(@"  waitForAck=%d got_int=%d isTerminating=%d isValid=%d",
1409         //        waitForAck, got_int, isTerminating, [connection isValid]);
1410     }
1412     if (waitForAck) {
1413         // Never received a connection acknowledgement, so die.
1414         [[NSNotificationCenter defaultCenter] removeObserver:self];
1415         [frontendProxy release];  frontendProxy = nil;
1417         // NOTE: We intentionally do not call mch_exit() since this in turn
1418         // will lead to -[MMBackend exit] getting called which we want to
1419         // avoid.
1420         usleep(MMExitProcessDelay);
1421         exit(0);
1422     }
1424     [self processInputQueue];
1427 - (oneway void)acknowledgeConnection
1429     //NSLog(@"%s", _cmd);
1430     waitForAck = NO;
1433 @end // MMBackend
1437 @implementation MMBackend (Private)
1439 - (void)waitForDialogReturn
1441     // Keep processing the run loop until a dialog returns.  To avoid getting
1442     // stuck in an endless loop (could happen if the setDialogReturn: message
1443     // was lost) we also do some paranoia checks.
1444     //
1445     // Note that in Cocoa the user can still resize windows and select menu
1446     // items while a sheet is being displayed, so we can't just wait for the
1447     // first message to arrive and assume that is the setDialogReturn: call.
1449     while (nil == dialogReturn && !got_int && [connection isValid]
1450             && !isTerminating)
1451         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1452                                  beforeDate:[NSDate distantFuture]];
1454     // Search for any resize messages on the input queue.  All other messages
1455     // on the input queue are dropped.  The reason why we single out resize
1456     // messages is because the user may have resized the window while a sheet
1457     // was open.
1458     int i, count = [inputQueue count];
1459     if (count > 0) {
1460         id textDimData = nil;
1461         if (count%2 == 0) {
1462             for (i = count-2; i >= 0; i -= 2) {
1463                 int msgid = [[inputQueue objectAtIndex:i] intValue];
1464                 if (SetTextDimensionsMsgID == msgid) {
1465                     textDimData = [[inputQueue objectAtIndex:i+1] retain];
1466                     break;
1467                 }
1468             }
1469         }
1471         [inputQueue removeAllObjects];
1473         if (textDimData) {
1474             [inputQueue addObject:
1475                     [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1476             [inputQueue addObject:textDimData];
1477             [textDimData release];
1478         }
1479     }
1482 - (void)insertVimStateMessage
1484     // NOTE: This is the place to add Vim state that needs to be accessed from
1485     // MacVim.  Do not add state that could potentially require lots of memory
1486     // since this message gets sent each time the output queue is forcibly
1487     // flushed (e.g. storing the currently selected text would be a bad idea).
1488     // We take this approach of "pushing" the state to MacVim to avoid having
1489     // to make synchronous calls from MacVim to Vim in order to get state.
1491     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1492         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1493         [NSNumber numberWithInt:p_mh], @"p_mh",
1494         [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1495         nil];
1497     // Put the state before all other messages.
1498     int msgid = SetVimStateMsgID;
1499     [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1500     [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1501                       atIndex:0];
1504 - (void)processInputQueue
1506     if ([inputQueue count] == 0) return;
1508     // NOTE: One of the input events may cause this method to be called
1509     // recursively, so copy the input queue to a local variable and clear the
1510     // queue before starting to process input events (otherwise we could get
1511     // stuck in an endless loop).
1512     NSArray *q = [inputQueue copy];
1513     unsigned i, count = [q count];
1515     [inputQueue removeAllObjects];
1517     for (i = 1; i < count; i+=2) {
1518         int msgid = [[q objectAtIndex:i-1] intValue];
1519         id data = [q objectAtIndex:i];
1520         if ([data isEqual:[NSNull null]])
1521             data = nil;
1523         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1524         [self handleInputEvent:msgid data:data];
1525     }
1527     [q release];
1528     //NSLog(@"Clear input event queue");
1531 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1533     if (InsertTextMsgID == msgid) {
1534         [self handleInsertText:data];
1535     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1536         if (!data) return;
1537         const void *bytes = [data bytes];
1538         int mods = *((int*)bytes);  bytes += sizeof(int);
1539         int len = *((int*)bytes);  bytes += sizeof(int);
1540         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1541                                               encoding:NSUTF8StringEncoding];
1542         mods = eventModifierFlagsToVimModMask(mods);
1544         [self handleKeyDown:key modifiers:mods];
1546         [key release];
1547     } else if (ScrollWheelMsgID == msgid) {
1548         if (!data) return;
1549         const void *bytes = [data bytes];
1551         int row = *((int*)bytes);  bytes += sizeof(int);
1552         int col = *((int*)bytes);  bytes += sizeof(int);
1553         int flags = *((int*)bytes);  bytes += sizeof(int);
1554         float dy = *((float*)bytes);  bytes += sizeof(float);
1556         int button = MOUSE_5;
1557         if (dy > 0) button = MOUSE_4;
1559         flags = eventModifierFlagsToVimMouseModMask(flags);
1561         int numLines = (int)round(dy);
1562         if (numLines < 0) numLines = -numLines;
1563         if (numLines == 0) numLines = 1;
1565 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1566         gui.scroll_wheel_force = numLines;
1567 #endif
1569         gui_send_mouse_event(button, col, row, NO, flags);
1570     } else if (MouseDownMsgID == msgid) {
1571         if (!data) return;
1572         const void *bytes = [data bytes];
1574         int row = *((int*)bytes);  bytes += sizeof(int);
1575         int col = *((int*)bytes);  bytes += sizeof(int);
1576         int button = *((int*)bytes);  bytes += sizeof(int);
1577         int flags = *((int*)bytes);  bytes += sizeof(int);
1578         int count = *((int*)bytes);  bytes += sizeof(int);
1580         button = eventButtonNumberToVimMouseButton(button);
1581         if (button >= 0) {
1582             flags = eventModifierFlagsToVimMouseModMask(flags);
1583             gui_send_mouse_event(button, col, row, count>1, flags);
1584         }
1585     } else if (MouseUpMsgID == msgid) {
1586         if (!data) return;
1587         const void *bytes = [data bytes];
1589         int row = *((int*)bytes);  bytes += sizeof(int);
1590         int col = *((int*)bytes);  bytes += sizeof(int);
1591         int flags = *((int*)bytes);  bytes += sizeof(int);
1593         flags = eventModifierFlagsToVimMouseModMask(flags);
1595         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1596     } else if (MouseDraggedMsgID == msgid) {
1597         if (!data) return;
1598         const void *bytes = [data bytes];
1600         int row = *((int*)bytes);  bytes += sizeof(int);
1601         int col = *((int*)bytes);  bytes += sizeof(int);
1602         int flags = *((int*)bytes);  bytes += sizeof(int);
1604         flags = eventModifierFlagsToVimMouseModMask(flags);
1606         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1607     } else if (MouseMovedMsgID == msgid) {
1608         const void *bytes = [data bytes];
1609         int row = *((int*)bytes);  bytes += sizeof(int);
1610         int col = *((int*)bytes);  bytes += sizeof(int);
1612         gui_mouse_moved(col, row);
1613     } else if (AddInputMsgID == msgid) {
1614         NSString *string = [[NSString alloc] initWithData:data
1615                 encoding:NSUTF8StringEncoding];
1616         if (string) {
1617             [self addInput:string];
1618             [string release];
1619         }
1620     } else if (TerminateNowMsgID == msgid) {
1621         isTerminating = YES;
1622     } else if (SelectTabMsgID == msgid) {
1623         if (!data) return;
1624         const void *bytes = [data bytes];
1625         int idx = *((int*)bytes) + 1;
1626         //NSLog(@"Selecting tab %d", idx);
1627         send_tabline_event(idx);
1628     } else if (CloseTabMsgID == msgid) {
1629         if (!data) return;
1630         const void *bytes = [data bytes];
1631         int idx = *((int*)bytes) + 1;
1632         //NSLog(@"Closing tab %d", idx);
1633         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1634     } else if (AddNewTabMsgID == msgid) {
1635         //NSLog(@"Adding new tab");
1636         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1637     } else if (DraggedTabMsgID == msgid) {
1638         if (!data) return;
1639         const void *bytes = [data bytes];
1640         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1641         // based.
1642         int idx = *((int*)bytes);
1644         tabpage_move(idx);
1645     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1646             || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1647         if (!data) return;
1648         const void *bytes = [data bytes];
1649         int rows = Rows;
1650         if (SetTextColumnsMsgID != msgid) {
1651             rows = *((int*)bytes);  bytes += sizeof(int);
1652         }
1653         int cols = Columns;
1654         if (SetTextRowsMsgID != msgid) {
1655             cols = *((int*)bytes);  bytes += sizeof(int);
1656         }
1658         NSData *d = data;
1659         if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1660             int dim[2] = { rows, cols };
1661             d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1662             msgid = SetTextDimensionsMsgID;
1663         }
1665         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1666         // gui_resize_shell(), so we have to manually set the rows and columns
1667         // here.  (MacVim doesn't change the rows and columns to avoid
1668         // inconsistent states between Vim and MacVim.)
1669         [self queueMessage:msgid data:d];
1671         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1672         gui_resize_shell(cols, rows);
1673     } else if (ExecuteMenuMsgID == msgid) {
1674         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1675         if (attrs) {
1676             NSArray *desc = [attrs objectForKey:@"descriptor"];
1677             vimmenu_T *menu = menu_for_descriptor(desc);
1678             if (menu)
1679                 gui_menu_cb(menu);
1680         }
1681     } else if (ToggleToolbarMsgID == msgid) {
1682         [self handleToggleToolbar];
1683     } else if (ScrollbarEventMsgID == msgid) {
1684         [self handleScrollbarEvent:data];
1685     } else if (SetFontMsgID == msgid) {
1686         [self handleSetFont:data];
1687     } else if (VimShouldCloseMsgID == msgid) {
1688         gui_shell_closed();
1689     } else if (DropFilesMsgID == msgid) {
1690         [self handleDropFiles:data];
1691     } else if (DropStringMsgID == msgid) {
1692         [self handleDropString:data];
1693     } else if (GotFocusMsgID == msgid) {
1694         if (!gui.in_focus)
1695             [self focusChange:YES];
1696     } else if (LostFocusMsgID == msgid) {
1697         if (gui.in_focus)
1698             [self focusChange:NO];
1699     } else if (SetMouseShapeMsgID == msgid) {
1700         const void *bytes = [data bytes];
1701         int shape = *((int*)bytes);  bytes += sizeof(int);
1702         update_mouseshape(shape);
1703     } else if (XcodeModMsgID == msgid) {
1704         [self handleXcodeMod:data];
1705     } else if (OpenWithArgumentsMsgID == msgid) {
1706         [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1707     } else {
1708         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1709     }
1712 + (NSDictionary *)specialKeys
1714     static NSDictionary *specialKeys = nil;
1716     if (!specialKeys) {
1717         NSBundle *mainBundle = [NSBundle mainBundle];
1718         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1719                                               ofType:@"plist"];
1720         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1721     }
1723     return specialKeys;
1726 - (void)handleInsertText:(NSData *)data
1728     if (!data) return;
1730     NSString *key = [[NSString alloc] initWithData:data
1731                                           encoding:NSUTF8StringEncoding];
1732     char_u *str = (char_u*)[key UTF8String];
1733     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1735 #ifdef FEAT_MBYTE
1736     char_u *conv_str = NULL;
1737     if (input_conv.vc_type != CONV_NONE) {
1738         conv_str = string_convert(&input_conv, str, &len);
1739         if (conv_str)
1740             str = conv_str;
1741     }
1742 #endif
1744     if (len == 1 && ((str[0] == Ctrl_C && ctrl_c_interrupts)
1745             || (str[0] == intr_char && intr_char != Ctrl_C))) {
1746         trash_input_buf();
1747         got_int = TRUE;
1748     }
1750     for (i = 0; i < len; ++i) {
1751         add_to_input_buf(str+i, 1);
1752         if (CSI == str[i]) {
1753             // NOTE: If the converted string contains the byte CSI, then it
1754             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1755             // won't work.
1756             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1757             add_to_input_buf(extra, 2);
1758         }
1759     }
1761 #ifdef FEAT_MBYTE
1762     if (conv_str)
1763         vim_free(conv_str);
1764 #endif
1765     [key release];
1768 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1770     char_u special[3];
1771     char_u modChars[3];
1772     char_u *chars = (char_u*)[key UTF8String];
1773 #ifdef FEAT_MBYTE
1774     char_u *conv_str = NULL;
1775 #endif
1776     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1778     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1779     // that new keys can easily be added.
1780     NSString *specialString = [[MMBackend specialKeys]
1781             objectForKey:key];
1782     if (specialString && [specialString length] > 1) {
1783         //NSLog(@"special key: %@", specialString);
1784         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1785                 [specialString characterAtIndex:1]);
1787         ikey = simplify_key(ikey, &mods);
1788         if (ikey == CSI)
1789             ikey = K_CSI;
1791         special[0] = CSI;
1792         special[1] = K_SECOND(ikey);
1793         special[2] = K_THIRD(ikey);
1795         chars = special;
1796         length = 3;
1797     } else if (1 == length && TAB == chars[0]) {
1798         // Tab is a trouble child:
1799         // - <Tab> is added to the input buffer as is
1800         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1801         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1802         //   to be converted to utf-8
1803         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1804         // - <C-Tab> is reserved by Mac OS X
1805         // - <D-Tab> is reserved by Mac OS X
1806         chars = special;
1807         special[0] = TAB;
1808         length = 1;
1810         if (mods & MOD_MASK_SHIFT) {
1811             mods &= ~MOD_MASK_SHIFT;
1812             special[0] = CSI;
1813             special[1] = K_SECOND(K_S_TAB);
1814             special[2] = K_THIRD(K_S_TAB);
1815             length = 3;
1816         } else if (mods & MOD_MASK_ALT) {
1817             int mtab = 0x80 | TAB;
1818 #ifdef FEAT_MBYTE
1819             if (enc_utf8) {
1820                 // Convert to utf-8
1821                 special[0] = (mtab >> 6) + 0xc0;
1822                 special[1] = mtab & 0xbf;
1823                 length = 2;
1824             } else
1825 #endif
1826             {
1827                 special[0] = mtab;
1828                 length = 1;
1829             }
1830             mods &= ~MOD_MASK_ALT;
1831         }
1832     } else if (length > 0) {
1833         unichar c = [key characterAtIndex:0];
1835         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1836         //        [key characterAtIndex:0], mods);
1838         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1839                 || (c == intr_char && intr_char != Ctrl_C))) {
1840             trash_input_buf();
1841             got_int = TRUE;
1842         }
1844         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1845         // cleared since they are already added to the key by the AppKit.
1846         // Unfortunately, the only way to deal with when to clear the modifiers
1847         // or not seems to be to have hard-wired rules like this.
1848         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1849                     || 0x9 == c || 0xd == c || ESC == c) ) {
1850             mods &= ~MOD_MASK_SHIFT;
1851             mods &= ~MOD_MASK_CTRL;
1852             //NSLog(@"clear shift ctrl");
1853         }
1855         // HACK!  All Option+key presses go via 'insert text' messages, except
1856         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1857         // not work to map to it.
1858         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1859             //NSLog(@"clear alt");
1860             mods &= ~MOD_MASK_ALT;
1861         }
1863 #ifdef FEAT_MBYTE
1864         if (input_conv.vc_type != CONV_NONE) {
1865             conv_str = string_convert(&input_conv, chars, &length);
1866             if (conv_str)
1867                 chars = conv_str;
1868         }
1869 #endif
1870     }
1872     if (chars && length > 0) {
1873         if (mods) {
1874             //NSLog(@"adding mods: %d", mods);
1875             modChars[0] = CSI;
1876             modChars[1] = KS_MODIFIER;
1877             modChars[2] = mods;
1878             add_to_input_buf(modChars, 3);
1879         }
1881         //NSLog(@"add to input buf: 0x%x", chars[0]);
1882         // TODO: Check for CSI bytes?
1883         add_to_input_buf(chars, length);
1884     }
1886 #ifdef FEAT_MBYTE
1887     if (conv_str)
1888         vim_free(conv_str);
1889 #endif
1892 - (void)queueMessage:(int)msgid data:(NSData *)data
1894     //if (msgid != EnableMenuItemMsgID)
1895     //    NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1897     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1898     if (data)
1899         [outputQueue addObject:data];
1900     else
1901         [outputQueue addObject:[NSData data]];
1904 - (void)connectionDidDie:(NSNotification *)notification
1906     // If the main connection to MacVim is lost this means that MacVim was
1907     // either quit (by the user chosing Quit on the MacVim menu), or it has
1908     // crashed.  In the former case the flag 'isTerminating' is set and we then
1909     // quit cleanly; in the latter case we make sure the swap files are left
1910     // for recovery.
1911     //
1912     // NOTE: This is not called if a Vim controller invalidates its connection.
1914     //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1915     if (isTerminating)
1916         getout(0);
1917     else
1918         getout_preserve_modified(1);
1921 - (void)blinkTimerFired:(NSTimer *)timer
1923     NSTimeInterval timeInterval = 0;
1925     [blinkTimer release];
1926     blinkTimer = nil;
1928     if (MMBlinkStateOn == blinkState) {
1929         gui_undraw_cursor();
1930         blinkState = MMBlinkStateOff;
1931         timeInterval = blinkOffInterval;
1932     } else if (MMBlinkStateOff == blinkState) {
1933         gui_update_cursor(TRUE, FALSE);
1934         blinkState = MMBlinkStateOn;
1935         timeInterval = blinkOnInterval;
1936     }
1938     if (timeInterval > 0) {
1939         blinkTimer = 
1940             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1941                                             selector:@selector(blinkTimerFired:)
1942                                             userInfo:nil repeats:NO] retain];
1943         [self flushQueue:YES];
1944     }
1947 - (void)focusChange:(BOOL)on
1949     gui_focus_change(on);
1952 - (void)handleToggleToolbar
1954     // If 'go' contains 'T', then remove it, else add it.
1956     char_u go[sizeof(GO_ALL)+2];
1957     char_u *p;
1958     int len;
1960     STRCPY(go, p_go);
1961     p = vim_strchr(go, GO_TOOLBAR);
1962     len = STRLEN(go);
1964     if (p != NULL) {
1965         char_u *end = go + len;
1966         while (p < end) {
1967             p[0] = p[1];
1968             ++p;
1969         }
1970     } else {
1971         go[len] = GO_TOOLBAR;
1972         go[len+1] = NUL;
1973     }
1975     set_option_value((char_u*)"guioptions", 0, go, 0);
1977     // Force screen redraw (does it have to be this complicated?).
1978     redraw_all_later(CLEAR);
1979     update_screen(NOT_VALID);
1980     setcursor();
1981     out_flush();
1982     gui_update_cursor(FALSE, FALSE);
1983     gui_mch_flush();
1986 - (void)handleScrollbarEvent:(NSData *)data
1988     if (!data) return;
1990     const void *bytes = [data bytes];
1991     long ident = *((long*)bytes);  bytes += sizeof(long);
1992     int hitPart = *((int*)bytes);  bytes += sizeof(int);
1993     float fval = *((float*)bytes);  bytes += sizeof(float);
1994     scrollbar_T *sb = gui_find_scrollbar(ident);
1996     if (sb) {
1997         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1998         long value = sb_info->value;
1999         long size = sb_info->size;
2000         long max = sb_info->max;
2001         BOOL isStillDragging = NO;
2002         BOOL updateKnob = YES;
2004         switch (hitPart) {
2005         case NSScrollerDecrementPage:
2006             value -= (size > 2 ? size - 2 : 1);
2007             break;
2008         case NSScrollerIncrementPage:
2009             value += (size > 2 ? size - 2 : 1);
2010             break;
2011         case NSScrollerDecrementLine:
2012             --value;
2013             break;
2014         case NSScrollerIncrementLine:
2015             ++value;
2016             break;
2017         case NSScrollerKnob:
2018             isStillDragging = YES;
2019             // fall through ...
2020         case NSScrollerKnobSlot:
2021             value = (long)(fval * (max - size + 1));
2022             // fall through ...
2023         default:
2024             updateKnob = NO;
2025             break;
2026         }
2028         //NSLog(@"value %d -> %d", sb_info->value, value);
2029         gui_drag_scrollbar(sb, value, isStillDragging);
2031         if (updateKnob) {
2032             // Dragging the knob or option+clicking automatically updates
2033             // the knob position (on the actual NSScroller), so we only
2034             // need to set the knob position in the other cases.
2035             if (sb->wp) {
2036                 // Update both the left&right vertical scrollbars.
2037                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2038                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2039                 [self setScrollbarThumbValue:value size:size max:max
2040                                   identifier:identLeft];
2041                 [self setScrollbarThumbValue:value size:size max:max
2042                                   identifier:identRight];
2043             } else {
2044                 // Update the horizontal scrollbar.
2045                 [self setScrollbarThumbValue:value size:size max:max
2046                                   identifier:ident];
2047             }
2048         }
2049     }
2052 - (void)handleSetFont:(NSData *)data
2054     if (!data) return;
2056     const void *bytes = [data bytes];
2057     float pointSize = *((float*)bytes);  bytes += sizeof(float);
2058     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2059     bytes += sizeof(unsigned);  // len not used
2061     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2062     [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
2063     char_u *s = (char_u*)[name UTF8String];
2065 #ifdef FEAT_MBYTE
2066     s = CONVERT_FROM_UTF8(s);
2067 #endif
2069     set_option_value((char_u*)"guifont", 0, s, 0);
2071 #ifdef FEAT_MBYTE
2072     CONVERT_FROM_UTF8_FREE(s);
2073 #endif
2075     // Force screen redraw (does it have to be this complicated?).
2076     redraw_all_later(CLEAR);
2077     update_screen(NOT_VALID);
2078     setcursor();
2079     out_flush();
2080     gui_update_cursor(FALSE, FALSE);
2081     gui_mch_flush();
2084 - (void)handleDropFiles:(NSData *)data
2086     // TODO: Get rid of this method; instead use Vim script directly.  At the
2087     // moment I know how to do this to open files in tabs, but I'm not sure how
2088     // to add the filenames to the command line when in command line mode.
2090     if (!data) return;
2092     NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2093     if (!args) return;
2095     id obj = [args objectForKey:@"forceOpen"];
2096     BOOL forceOpen = YES;
2097     if (obj)
2098         forceOpen = [obj boolValue];
2100     NSArray *filenames = [args objectForKey:@"filenames"];
2101     if (!(filenames && [filenames count] > 0)) return;
2103 #ifdef FEAT_DND
2104     if (!forceOpen && (State & CMDLINE)) {
2105         // HACK!  If Vim is in command line mode then the files names
2106         // should be added to the command line, instead of opening the
2107         // files in tabs (unless forceOpen is set).  This is taken care of by
2108         // gui_handle_drop().
2109         int n = [filenames count];
2110         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2111         if (fnames) {
2112             int i = 0;
2113             for (i = 0; i < n; ++i)
2114                 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2116             // NOTE!  This function will free 'fnames'.
2117             // HACK!  It is assumed that the 'x' and 'y' arguments are
2118             // unused when in command line mode.
2119             gui_handle_drop(0, 0, 0, fnames, n);
2120         }
2121     } else
2122 #endif // FEAT_DND
2123     {
2124         [self handleOpenWithArguments:args];
2125     }
2128 - (void)handleDropString:(NSData *)data
2130     if (!data) return;
2132 #ifdef FEAT_DND
2133     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2134     const void *bytes = [data bytes];
2135     int len = *((int*)bytes);  bytes += sizeof(int);
2136     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2138     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2139     NSRange range = { 0, [string length] };
2140     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2141                                          withString:@"\x0a" options:0
2142                                               range:range];
2143     if (0 == n) {
2144         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2145                                        options:0 range:range];
2146     }
2148     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2149     char_u *s = (char_u*)[string UTF8String];
2150 #ifdef FEAT_MBYTE
2151     if (input_conv.vc_type != CONV_NONE)
2152         s = string_convert(&input_conv, s, &len);
2153 #endif
2154     dnd_yank_drag_data(s, len);
2155 #ifdef FEAT_MBYTE
2156     if (input_conv.vc_type != CONV_NONE)
2157         vim_free(s);
2158 #endif
2159     add_to_input_buf(dropkey, sizeof(dropkey));
2160 #endif // FEAT_DND
2163 - (void)startOdbEditWithArguments:(NSDictionary *)args
2165 #ifdef FEAT_ODB_EDITOR
2166     id obj = [args objectForKey:@"remoteID"];
2167     if (!obj) return;
2169     OSType serverID = [obj unsignedIntValue];
2170     NSString *remotePath = [args objectForKey:@"remotePath"];
2172     NSAppleEventDescriptor *token = nil;
2173     NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2174     obj = [args objectForKey:@"remoteTokenDescType"];
2175     if (tokenData && obj) {
2176         DescType tokenType = [obj unsignedLongValue];
2177         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2178                                                                 data:tokenData];
2179     }
2181     NSArray *filenames = [args objectForKey:@"filenames"];
2182     unsigned i, numFiles = [filenames count];
2183     for (i = 0; i < numFiles; ++i) {
2184         NSString *filename = [filenames objectAtIndex:i];
2185         char_u *s = [filename vimStringSave];
2186         buf_T *buf = buflist_findname(s);
2187         vim_free(s);
2189         if (buf) {
2190             if (buf->b_odb_token) {
2191                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2192                 buf->b_odb_token = NULL;
2193             }
2195             if (buf->b_odb_fname) {
2196                 vim_free(buf->b_odb_fname);
2197                 buf->b_odb_fname = NULL;
2198             }
2200             buf->b_odb_server_id = serverID;
2202             if (token)
2203                 buf->b_odb_token = [token retain];
2204             if (remotePath)
2205                 buf->b_odb_fname = [remotePath vimStringSave];
2206         } else {
2207             NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2208                     filename);
2209         }
2210     }
2211 #endif // FEAT_ODB_EDITOR
2214 - (void)handleXcodeMod:(NSData *)data
2216 #if 0
2217     const void *bytes = [data bytes];
2218     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2219     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2220     if (0 == len)
2221         return;
2223     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2224             descriptorWithDescriptorType:type
2225                                    bytes:bytes
2226                                   length:len];
2227 #endif
2230 - (void)handleOpenWithArguments:(NSDictionary *)args
2232     // ARGUMENT:                DESCRIPTION:
2233     // -------------------------------------------------------------
2234     // filenames                list of filenames
2235     // dontOpen                 don't open files specified in above argument
2236     // layout                   which layout to use to open files
2237     // selectionRange           range of lines to select
2238     // searchText               string to search for
2239     // cursorLine               line to position the cursor on
2240     // cursorColumn             column to position the cursor on
2241     //                          (only valid when "cursorLine" is set)
2242     // remoteID                 ODB parameter
2243     // remotePath               ODB parameter
2244     // remoteTokenDescType      ODB parameter
2245     // remoteTokenData          ODB parameter
2247     //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2249     NSArray *filenames = [args objectForKey:@"filenames"];
2250     int i, numFiles = filenames ? [filenames count] : 0;
2251     BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2252     int layout = [[args objectForKey:@"layout"] intValue];
2254     // Change to directory of first file to open if this is an "unused" editor
2255     // (but do not do this if editing remotely).
2256     if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2257             && (starting || [self unusedEditor]) ) {
2258         char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2259         vim_chdirfile(s);
2260         vim_free(s);
2261     }
2263     if (starting > 0) {
2264         // When Vim is starting we simply add the files to be opened to the
2265         // global arglist and Vim will take care of opening them for us.
2266         if (openFiles && numFiles > 0) {
2267             for (i = 0; i < numFiles; i++) {
2268                 NSString *fname = [filenames objectAtIndex:i];
2269                 char_u *p = NULL;
2271                 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2272                         || (p = [fname vimStringSave]) == NULL)
2273                     exit(2); // See comment in -[MMBackend exit]
2274                 else
2275                     alist_add(&global_alist, p, 2);
2276             }
2278             // Vim will take care of arranging the files added to the arglist
2279             // in windows or tabs; all we must do is to specify which layout to
2280             // use.
2281             initialWindowLayout = layout;
2282         }
2283     } else {
2284         // When Vim is already open we resort to some trickery to open the
2285         // files with the specified layout.
2286         //
2287         // TODO: Figure out a better way to handle this?
2288         if (openFiles && numFiles > 0) {
2289             BOOL oneWindowInTab = topframe ? YES
2290                                            : (topframe->fr_layout == FR_LEAF);
2291             BOOL bufChanged = NO;
2292             BOOL bufHasFilename = NO;
2293             if (curbuf) {
2294                 bufChanged = curbufIsChanged();
2295                 bufHasFilename = curbuf->b_ffname != NULL;
2296             }
2298             // Temporarily disable flushing since the following code may
2299             // potentially cause multiple redraws.
2300             flushDisabled = YES;
2302             BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2303             if (WIN_TABS == layout && !onlyOneTab) {
2304                 // By going to the last tabpage we ensure that the new tabs
2305                 // will appear last (if this call is left out, the taborder
2306                 // becomes messy).
2307                 goto_tabpage(9999);
2308             }
2310             // Make sure we're in normal mode first.
2311             [self addInput:@"<C-\\><C-N>"];
2313             if (numFiles > 1) {
2314                 // With "split layout" we open a new tab before opening
2315                 // multiple files if the current tab has more than one window
2316                 // or if there is exactly one window but whose buffer has a
2317                 // filename.  (The :drop command ensures modified buffers get
2318                 // their own window.)
2319                 if ((WIN_HOR == layout || WIN_VER == layout) &&
2320                         (!oneWindowInTab || bufHasFilename))
2321                     [self addInput:@":tabnew<CR>"];
2323                 // The files are opened by constructing a ":drop ..." command
2324                 // and executing it.
2325                 NSMutableString *cmd = (WIN_TABS == layout)
2326                         ? [NSMutableString stringWithString:@":tab drop"]
2327                         : [NSMutableString stringWithString:@":drop"];
2329                 for (i = 0; i < numFiles; ++i) {
2330                     NSString *file = [filenames objectAtIndex:i];
2331                     file = [file stringByEscapingSpecialFilenameCharacters];
2332                     [cmd appendString:@" "];
2333                     [cmd appendString:file];
2334                 }
2336                 // Temporarily clear 'suffixes' so that the files are opened in
2337                 // the same order as they appear in the "filenames" array.
2338                 [self addInput:@":let t:mvim_oldsu=&su|set su=<CR>"];
2340                 [self addInput:cmd];
2342                 // Split the view into multiple windows if requested.
2343                 if (WIN_HOR == layout)
2344                     [self addInput:@"|sall"];
2345                 else if (WIN_VER == layout)
2346                     [self addInput:@"|vert sall"];
2348                 // Restore the old value of 'suffixes'.
2349                 [self addInput:@"|let &su=t:mvim_oldsu|unlet t:mvim_oldsu"];
2351                 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2352                 [self addInput:@"|redr|f<CR>"];
2353             } else {
2354                 // When opening one file we try to reuse the current window,
2355                 // but not if its buffer is modified or has a filename.
2356                 // However, the 'arglist' layout always opens the file in the
2357                 // current window.
2358                 NSString *file = [[filenames lastObject]
2359                         stringByEscapingSpecialFilenameCharacters];
2360                 NSString *cmd;
2361                 if (WIN_HOR == layout) {
2362                     if (!(bufHasFilename || bufChanged))
2363                         cmd = [NSString stringWithFormat:@":e %@", file];
2364                     else
2365                         cmd = [NSString stringWithFormat:@":sp %@", file];
2366                 } else if (WIN_VER == layout) {
2367                     if (!(bufHasFilename || bufChanged))
2368                         cmd = [NSString stringWithFormat:@":e %@", file];
2369                     else
2370                         cmd = [NSString stringWithFormat:@":vsp %@", file];
2371                 } else if (WIN_TABS == layout) {
2372                     if (oneWindowInTab && !(bufHasFilename || bufChanged))
2373                         cmd = [NSString stringWithFormat:@":e %@", file];
2374                     else
2375                         cmd = [NSString stringWithFormat:@":tabe %@", file];
2376                 } else {
2377                     // (The :drop command will split if there is a modified
2378                     // buffer.)
2379                     cmd = [NSString stringWithFormat:@":drop %@", file];
2380                 }
2382                 [self addInput:cmd];
2384                 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2385                 [self addInput:@"|redr|f<CR>"];
2386             }
2388             // Force screen redraw (does it have to be this complicated?).
2389             // (This code was taken from the end of gui_handle_drop().)
2390             update_screen(NOT_VALID);
2391             setcursor();
2392             out_flush();
2393             gui_update_cursor(FALSE, FALSE);
2394             maketitle();
2396             flushDisabled = NO;
2397             gui_mch_flush();
2398         }
2399     }
2401     if ([args objectForKey:@"remoteID"]) {
2402         // NOTE: We have to delay processing any ODB related arguments since
2403         // the file(s) may not be opened until the input buffer is processed.
2404         [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2405                                withObject:args
2406                             waitUntilDone:NO];
2407     }
2409     NSString *lineString = [args objectForKey:@"cursorLine"];
2410     if (lineString && [lineString intValue] > 0) {
2411         NSString *columnString = [args objectForKey:@"cursorColumn"];
2412         if (!(columnString && [columnString intValue] > 0))
2413             columnString = @"1";
2415         NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2416                 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2417         [self addInput:cmd];
2418     }
2420     NSString *rangeString = [args objectForKey:@"selectionRange"];
2421     if (rangeString) {
2422         // Build a command line string that will select the given range of
2423         // lines.  If range.length == 0, then position the cursor on the given
2424         // line but do not select.
2425         NSRange range = NSRangeFromString(rangeString);
2426         NSString *cmd;
2427         if (range.length > 0) {
2428             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2429                     NSMaxRange(range), range.location];
2430         } else {
2431             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2432                     range.location];
2433         }
2435         [self addInput:cmd];
2436     }
2438     NSString *searchText = [args objectForKey:@"searchText"];
2439     if (searchText) {
2440         // TODO: Searching is an exclusive motion, so if the pattern would
2441         // match on row 0 column 0 then this pattern will miss that match.
2442         [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@<CR>",
2443                 searchText]];
2444     }
2447 - (BOOL)checkForModifiedBuffers
2449     buf_T *buf;
2450     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2451         if (bufIsChanged(buf)) {
2452             return YES;
2453         }
2454     }
2456     return NO;
2459 - (void)addInput:(NSString *)input
2461     char_u *s = (char_u*)[input UTF8String];
2463 #ifdef FEAT_MBYTE
2464     s = CONVERT_FROM_UTF8(s);
2465 #endif
2467     server_to_input_buf(s);
2469 #ifdef FEAT_MBYTE
2470     CONVERT_FROM_UTF8_FREE(s);
2471 #endif
2474 - (BOOL)unusedEditor
2476     BOOL oneWindowInTab = topframe ? YES
2477                                    : (topframe->fr_layout == FR_LEAF);
2478     BOOL bufChanged = NO;
2479     BOOL bufHasFilename = NO;
2480     if (curbuf) {
2481         bufChanged = curbufIsChanged();
2482         bufHasFilename = curbuf->b_ffname != NULL;
2483     }
2485     BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2487     return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2490 @end // MMBackend (Private)
2495 @implementation MMBackend (ClientServer)
2497 - (NSString *)connectionNameFromServerName:(NSString *)name
2499     NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2501     return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2502         lowercaseString];
2505 - (NSConnection *)connectionForServerName:(NSString *)name
2507     // TODO: Try 'name%d' if 'name' fails.
2508     NSString *connName = [self connectionNameFromServerName:name];
2509     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2511     if (!svrConn) {
2512         svrConn = [NSConnection connectionWithRegisteredName:connName
2513                                                            host:nil];
2514         // Try alternate server...
2515         if (!svrConn && alternateServerName) {
2516             //NSLog(@"  trying to connect to alternate server: %@",
2517             //        alternateServerName);
2518             connName = [self connectionNameFromServerName:alternateServerName];
2519             svrConn = [NSConnection connectionWithRegisteredName:connName
2520                                                             host:nil];
2521         }
2523         // Try looking for alternate servers...
2524         if (!svrConn) {
2525             //NSLog(@"  looking for alternate servers...");
2526             NSString *alt = [self alternateServerNameForName:name];
2527             if (alt != alternateServerName) {
2528                 //NSLog(@"  found alternate server: %@", string);
2529                 [alternateServerName release];
2530                 alternateServerName = [alt copy];
2531             }
2532         }
2534         // Try alternate server again...
2535         if (!svrConn && alternateServerName) {
2536             //NSLog(@"  trying to connect to alternate server: %@",
2537             //        alternateServerName);
2538             connName = [self connectionNameFromServerName:alternateServerName];
2539             svrConn = [NSConnection connectionWithRegisteredName:connName
2540                                                             host:nil];
2541         }
2543         if (svrConn) {
2544             [connectionNameDict setObject:svrConn forKey:connName];
2546             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2547             [[NSNotificationCenter defaultCenter] addObserver:self
2548                     selector:@selector(serverConnectionDidDie:)
2549                         name:NSConnectionDidDieNotification object:svrConn];
2550         }
2551     }
2553     return svrConn;
2556 - (NSConnection *)connectionForServerPort:(int)port
2558     NSConnection *conn;
2559     NSEnumerator *e = [connectionNameDict objectEnumerator];
2561     while ((conn = [e nextObject])) {
2562         // HACK! Assume connection uses mach ports.
2563         if (port == [(NSMachPort*)[conn sendPort] machPort])
2564             return conn;
2565     }
2567     return nil;
2570 - (void)serverConnectionDidDie:(NSNotification *)notification
2572     //NSLog(@"%s%@", _cmd, notification);
2574     NSConnection *svrConn = [notification object];
2576     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2577     [[NSNotificationCenter defaultCenter]
2578             removeObserver:self
2579                       name:NSConnectionDidDieNotification
2580                     object:svrConn];
2582     [connectionNameDict removeObjectsForKeys:
2583         [connectionNameDict allKeysForObject:svrConn]];
2585     // HACK! Assume connection uses mach ports.
2586     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2587     NSNumber *key = [NSNumber numberWithInt:port];
2589     [clientProxyDict removeObjectForKey:key];
2590     [serverReplyDict removeObjectForKey:key];
2593 - (void)addClient:(NSDistantObject *)client
2595     NSConnection *conn = [client connectionForProxy];
2596     // HACK! Assume connection uses mach ports.
2597     int port = [(NSMachPort*)[conn sendPort] machPort];
2598     NSNumber *key = [NSNumber numberWithInt:port];
2600     if (![clientProxyDict objectForKey:key]) {
2601         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2602         [clientProxyDict setObject:client forKey:key];
2603     }
2605     // NOTE: 'clientWindow' is a global variable which is used by <client>
2606     clientWindow = port;
2609 - (NSString *)alternateServerNameForName:(NSString *)name
2611     if (!(name && [name length] > 0))
2612         return nil;
2614     // Only look for alternates if 'name' doesn't end in a digit.
2615     unichar lastChar = [name characterAtIndex:[name length]-1];
2616     if (lastChar >= '0' && lastChar <= '9')
2617         return nil;
2619     // Look for alternates among all current servers.
2620     NSArray *list = [self serverList];
2621     if (!(list && [list count] > 0))
2622         return nil;
2624     // Filter out servers starting with 'name' and ending with a number. The
2625     // (?i) pattern ensures that the match is case insensitive.
2626     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2627     NSPredicate *pred = [NSPredicate predicateWithFormat:
2628             @"SELF MATCHES %@", pat];
2629     list = [list filteredArrayUsingPredicate:pred];
2630     if ([list count] > 0) {
2631         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2632         return [list objectAtIndex:0];
2633     }
2635     return nil;
2638 @end // MMBackend (ClientServer)
2643 @implementation NSString (MMServerNameCompare)
2644 - (NSComparisonResult)serverNameCompare:(NSString *)string
2646     return [self compare:string
2647                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2649 @end
2654 static int eventModifierFlagsToVimModMask(int modifierFlags)
2656     int modMask = 0;
2658     if (modifierFlags & NSShiftKeyMask)
2659         modMask |= MOD_MASK_SHIFT;
2660     if (modifierFlags & NSControlKeyMask)
2661         modMask |= MOD_MASK_CTRL;
2662     if (modifierFlags & NSAlternateKeyMask)
2663         modMask |= MOD_MASK_ALT;
2664     if (modifierFlags & NSCommandKeyMask)
2665         modMask |= MOD_MASK_CMD;
2667     return modMask;
2670 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2672     int modMask = 0;
2674     if (modifierFlags & NSShiftKeyMask)
2675         modMask |= MOUSE_SHIFT;
2676     if (modifierFlags & NSControlKeyMask)
2677         modMask |= MOUSE_CTRL;
2678     if (modifierFlags & NSAlternateKeyMask)
2679         modMask |= MOUSE_ALT;
2681     return modMask;
2684 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2686     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2688     return (buttonNumber >= 0 && buttonNumber < 3)
2689             ? mouseButton[buttonNumber] : -1;
2694 // This function is modeled after the VimToPython function found in if_python.c
2695 // NB This does a deep copy by value, it does not lookup references like the
2696 // VimToPython function does.  This is because I didn't want to deal with the
2697 // retain cycles that this would create, and we can cover 99% of the use cases
2698 // by ignoring it.  If we ever switch to using GC in MacVim then this
2699 // functionality can be implemented easily.
2700 static id vimToCocoa(typval_T * tv, int depth)
2702     id result = nil;
2703     id newObj = nil;
2706     // Avoid infinite recursion
2707     if (depth > 100) {
2708         return nil;
2709     }
2711     if (tv->v_type == VAR_STRING) {
2712         char_u * val = tv->vval.v_string;
2713         // val can be NULL if the string is empty
2714         if (!val) {
2715             result = [NSString string];
2716         } else {
2717 #ifdef FEAT_MBYTE
2718             val = CONVERT_TO_UTF8(val);
2719 #endif
2720             result = [NSString stringWithUTF8String:(char*)val];
2721 #ifdef FEAT_MBYTE
2722             CONVERT_TO_UTF8_FREE(val);
2723 #endif
2724         }
2725     } else if (tv->v_type == VAR_NUMBER) {
2726         // looks like sizeof(varnumber_T) is always <= sizeof(long)
2727         result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2728     } else if (tv->v_type == VAR_LIST) {
2729         list_T * list = tv->vval.v_list;
2730         listitem_T * curr;
2732         NSMutableArray * arr = result = [NSMutableArray array];
2734         if (list != NULL) {
2735             for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2736                 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2737                 [arr addObject:newObj];
2738             }
2739         }
2740     } else if (tv->v_type == VAR_DICT) {
2741         NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2743         if (tv->vval.v_dict != NULL) {
2744             hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2745             int todo = ht->ht_used;
2746             hashitem_T * hi;
2747             dictitem_T * di;
2749             for (hi = ht->ht_array; todo > 0; ++hi) {
2750                 if (!HASHITEM_EMPTY(hi)) {
2751                     --todo;
2753                     di = dict_lookup(hi);
2754                     newObj = vimToCocoa(&di->di_tv, depth + 1);
2756                     char_u * keyval = hi->hi_key;
2757 #ifdef FEAT_MBYTE
2758                     keyval = CONVERT_TO_UTF8(keyval);
2759 #endif
2760                     NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2761 #ifdef FEAT_MBYTE
2762                     CONVERT_TO_UTF8_FREE(keyval);
2763 #endif
2764                     [dict setObject:newObj forKey:key];
2765                 }
2766             }
2767         }
2768     } else { // only func refs should fall into this category?
2769         result = nil;
2770     }
2772     return result;
2776 // This function is modeled after eval_client_expr_to_string found in main.c
2777 // Returns nil if there was an error evaluating the expression, and writes a
2778 // message to errorStr.
2779 // TODO Get the error that occurred while evaluating the expression in vim
2780 // somehow.
2781 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2784     char_u *s = (char_u*)[expr UTF8String];
2786 #ifdef FEAT_MBYTE
2787     s = CONVERT_FROM_UTF8(s);
2788 #endif
2790     int save_dbl = debug_break_level;
2791     int save_ro = redir_off;
2793     debug_break_level = -1;
2794     redir_off = 0;
2795     ++emsg_skip;
2797     typval_T * tvres = eval_expr(s, NULL);
2799     debug_break_level = save_dbl;
2800     redir_off = save_ro;
2801     --emsg_skip;
2803     setcursor();
2804     out_flush();
2806 #ifdef FEAT_MBYTE
2807     CONVERT_FROM_UTF8_FREE(s);
2808 #endif
2810 #ifdef FEAT_GUI
2811     if (gui.in_use)
2812         gui_update_cursor(FALSE, FALSE);
2813 #endif
2815     if (tvres == NULL) {
2816         free_tv(tvres);
2817         *errstr = @"Expression evaluation failed.";
2818     }
2820     id res = vimToCocoa(tvres, 1);
2822     free_tv(tvres);
2824     if (res == nil) {
2825         *errstr = @"Conversion to cocoa values failed.";
2826     }
2828     return res;
2834 @implementation NSString (VimStrings)
2836 + (id)stringWithVimString:(char_u *)s
2838     // This method ensures a non-nil string is returned.  If 's' cannot be
2839     // converted to a utf-8 string it is assumed to be latin-1.  If conversion
2840     // still fails an empty NSString is returned.
2841     NSString *string = nil;
2842     if (s) {
2843 #ifdef FEAT_MBYTE
2844         s = CONVERT_TO_UTF8(s);
2845 #endif
2846         string = [NSString stringWithUTF8String:(char*)s];
2847         if (!string) {
2848             // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2849             // latin-1?
2850             string = [NSString stringWithCString:(char*)s
2851                                         encoding:NSISOLatin1StringEncoding];
2852         }
2853 #ifdef FEAT_MBYTE
2854         CONVERT_TO_UTF8_FREE(s);
2855 #endif
2856     }
2858     return string != nil ? string : [NSString string];
2861 - (char_u *)vimStringSave
2863     char_u *s = (char_u*)[self UTF8String], *ret = NULL;
2865 #ifdef FEAT_MBYTE
2866     s = CONVERT_FROM_UTF8(s);
2867 #endif
2868     ret = vim_strsave(s);
2869 #ifdef FEAT_MBYTE
2870     CONVERT_FROM_UTF8_FREE(s);
2871 #endif
2873     return ret;
2876 @end // NSString (VimStrings)