Fix problems with 'fullscreen' and :mksession
[MacVim.git] / src / MacVim / MMBackend.m
blob011bf20281a9eebc96ecd86b23d4b02ab38205d3
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 // This constant controls how often the command queue may be flushed.  If it is
48 // too small the app might feel unresponsive; if it is too large there might be
49 // long periods without the screen updating (e.g. when sourcing a large session
50 // file).  (The unit is seconds.)
51 static float MMFlushTimeoutInterval = 0.1f;
52 static int MMFlushQueueLenHint = 80*40;
54 static unsigned MMServerMax = 1000;
56 // TODO: Move to separate file.
57 static int eventModifierFlagsToVimModMask(int modifierFlags);
58 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
59 static int eventButtonNumberToVimMouseButton(int buttonNumber);
61 // Before exiting process, sleep for this many microseconds.  This is to allow
62 // any distributed object messages in transit to be received by MacVim before
63 // the process dies (otherwise an error message is logged by Cocoa).  Note that
64 // this delay is only necessary if an NSConnection to MacVim has been
65 // established.
66 static useconds_t MMExitProcessDelay = 300000;
68 // In gui_macvim.m
69 vimmenu_T *menu_for_descriptor(NSArray *desc);
71 static id evalExprCocoa(NSString * expr, NSString ** errstr);
73 enum {
74     MMBlinkStateNone = 0,
75     MMBlinkStateOn,
76     MMBlinkStateOff
79 static NSString *MMSymlinkWarningString =
80     @"\n\n\tMost likely this is because you have symlinked directly to\n"
81      "\tthe Vim binary, which Cocoa does not allow.  Please use an\n"
82      "\talias or the mvim shell script instead.  If you have not used\n"
83      "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
87 @interface NSString (MMServerNameCompare)
88 - (NSComparisonResult)serverNameCompare:(NSString *)string;
89 @end
94 @interface MMBackend (Private)
95 - (void)waitForDialogReturn;
96 - (void)insertVimStateMessage;
97 - (void)processInputQueue;
98 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
99 + (NSDictionary *)specialKeys;
100 - (void)handleInsertText:(NSData *)data;
101 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
102 - (void)queueMessage:(int)msgid data:(NSData *)data;
103 - (void)connectionDidDie:(NSNotification *)notification;
104 - (void)blinkTimerFired:(NSTimer *)timer;
105 - (void)focusChange:(BOOL)on;
106 - (void)handleToggleToolbar;
107 - (void)handleScrollbarEvent:(NSData *)data;
108 - (void)handleSetFont:(NSData *)data;
109 - (void)handleDropFiles:(NSData *)data;
110 - (void)handleDropString:(NSData *)data;
111 - (void)startOdbEditWithArguments:(NSDictionary *)args;
112 - (void)handleXcodeMod:(NSData *)data;
113 - (void)handleOpenWithArguments:(NSDictionary *)args;
114 - (BOOL)checkForModifiedBuffers;
115 - (void)addInput:(NSString *)input;
116 - (BOOL)unusedEditor;
117 @end
121 @interface MMBackend (ClientServer)
122 - (NSString *)connectionNameFromServerName:(NSString *)name;
123 - (NSConnection *)connectionForServerName:(NSString *)name;
124 - (NSConnection *)connectionForServerPort:(int)port;
125 - (void)serverConnectionDidDie:(NSNotification *)notification;
126 - (void)addClient:(NSDistantObject *)client;
127 - (NSString *)alternateServerNameForName:(NSString *)name;
128 @end
132 @implementation MMBackend
134 + (MMBackend *)sharedInstance
136     static MMBackend *singleton = nil;
137     return singleton ? singleton : (singleton = [MMBackend new]);
140 - (id)init
142     self = [super init];
143     if (!self) return nil;
145     fontContainerRef = loadFonts();
147     outputQueue = [[NSMutableArray alloc] init];
148     inputQueue = [[NSMutableArray alloc] init];
149     drawData = [[NSMutableData alloc] initWithCapacity:1024];
150     connectionNameDict = [[NSMutableDictionary alloc] init];
151     clientProxyDict = [[NSMutableDictionary alloc] init];
152     serverReplyDict = [[NSMutableDictionary alloc] init];
154     NSBundle *mainBundle = [NSBundle mainBundle];
155     NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
156     if (path)
157         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
159     path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
160     if (path)
161         sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
162             retain];
164     path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
165     if (path)
166         actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
168     if (!(colorDict && sysColorDict && actionDict))
169         NSLog(@"ERROR: Failed to load dictionaries.%@",
170                 MMSymlinkWarningString);
172     return self;
175 - (void)dealloc
177     //NSLog(@"%@ %s", [self className], _cmd);
178     [[NSNotificationCenter defaultCenter] removeObserver:self];
180     [oldWideFont release];  oldWideFont = nil;
181     [blinkTimer release];  blinkTimer = nil;
182     [alternateServerName release];  alternateServerName = nil;
183     [serverReplyDict release];  serverReplyDict = nil;
184     [clientProxyDict release];  clientProxyDict = nil;
185     [connectionNameDict release];  connectionNameDict = nil;
186     [inputQueue release];  inputQueue = nil;
187     [outputQueue release];  outputQueue = nil;
188     [drawData release];  drawData = nil;
189     [frontendProxy release];  frontendProxy = nil;
190     [connection release];  connection = nil;
191     [actionDict release];  actionDict = nil;
192     [sysColorDict release];  sysColorDict = nil;
193     [colorDict release];  colorDict = nil;
195     [super dealloc];
198 - (void)setBackgroundColor:(int)color
200     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
203 - (void)setForegroundColor:(int)color
205     foregroundColor = MM_COLOR(color);
208 - (void)setSpecialColor:(int)color
210     specialColor = MM_COLOR(color);
213 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
215     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
216     defaultForegroundColor = MM_COLOR(fg);
218     NSMutableData *data = [NSMutableData data];
220     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
221     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
223     [self queueMessage:SetDefaultColorsMsgID data:data];
226 - (NSConnection *)connection
228     if (!connection) {
229         // NOTE!  If the name of the connection changes here it must also be
230         // updated in MMAppController.m.
231         NSString *name = [NSString stringWithFormat:@"%@-connection",
232                [[NSBundle mainBundle] bundlePath]];
234         connection = [NSConnection connectionWithRegisteredName:name host:nil];
235         [connection retain];
236     }
238     // NOTE: 'connection' may be nil here.
239     return connection;
242 - (NSDictionary *)actionDict
244     return actionDict;
247 - (int)initialWindowLayout
249     return initialWindowLayout;
252 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
254     [self queueMessage:msgid data:[props dictionaryAsData]];
257 - (BOOL)checkin
259     if (![self connection]) {
260         if (waitForAck) {
261             // This is a preloaded process and as such should not cause the
262             // MacVim to be opened.  We probably got here as a result of the
263             // user quitting MacVim while the process was preloading, so exit
264             // this process too.
265             // (Don't use mch_exit() since it assumes the process has properly
266             // started.)
267             exit(0);
268         }
270         NSBundle *mainBundle = [NSBundle mainBundle];
271 #if 0
272         OSStatus status;
273         FSRef ref;
275         // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
276         // the API to pass Apple Event parameters is broken on 10.4).
277         NSString *path = [mainBundle bundlePath];
278         status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
279         if (noErr == status) {
280             // Pass parameter to the 'Open' Apple Event that tells MacVim not
281             // to open an untitled window.
282             NSAppleEventDescriptor *desc =
283                     [NSAppleEventDescriptor recordDescriptor];
284             [desc setParamDescriptor:
285                     [NSAppleEventDescriptor descriptorWithBoolean:NO]
286                           forKeyword:keyMMUntitledWindow];
288             LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
289                     kLSLaunchDefaults, NULL };
290             status = LSOpenFromRefSpec(&spec, NULL);
291         }
293         if (noErr != status) {
294         NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
295                 path, MMSymlinkWarningString);
296             return NO;
297         }
298 #else
299         // Launch MacVim using NSTask.  For some reason the above code using
300         // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
301         // fails, the dock icon starts bouncing and never stops).  It seems
302         // like rebuilding the Launch Services database takes care of this
303         // problem, but the NSTask way seems more stable so stick with it.
304         //
305         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
306         // that the GUI won't be activated (or raised) so there is a hack in
307         // MMAppController which raises the app when a new window is opened.
308         NSMutableArray *args = [NSMutableArray arrayWithObjects:
309             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
310         NSString *exeName = [[mainBundle infoDictionary]
311                 objectForKey:@"CFBundleExecutable"];
312         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
313         if (!path) {
314             NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
315                     MMSymlinkWarningString);
316             return NO;
317         }
319         [NSTask launchedTaskWithLaunchPath:path arguments:args];
320 #endif
322         // HACK!  Poll the mach bootstrap server until it returns a valid
323         // connection to detect that MacVim has finished launching.  Also set a
324         // time-out date so that we don't get stuck doing this forever.
325         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
326         while (![self connection] &&
327                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
328             [[NSRunLoop currentRunLoop]
329                     runMode:NSDefaultRunLoopMode
330                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
332         // NOTE: [self connection] will set 'connection' as a side-effect.
333         if (!connection) {
334             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
335             return NO;
336         }
337     }
339     BOOL ok = NO;
340     @try {
341         [[NSNotificationCenter defaultCenter] addObserver:self
342                 selector:@selector(connectionDidDie:)
343                     name:NSConnectionDidDieNotification object:connection];
345         id proxy = [connection rootProxy];
346         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
348         int pid = [[NSProcessInfo processInfo] processIdentifier];
350         frontendProxy = [proxy connectBackend:self pid:pid];
351         if (frontendProxy) {
352             [frontendProxy retain];
353             [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
354             ok = YES;
355         }
356     }
357     @catch (NSException *e) {
358         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
359     }
361     return ok;
364 - (BOOL)openGUIWindow
366     [self queueMessage:OpenWindowMsgID data:nil];
367     return YES;
370 - (void)clearAll
372     int type = ClearAllDrawType;
374     // Any draw commands in queue are effectively obsolete since this clearAll
375     // will negate any effect they have, therefore we may as well clear the
376     // draw queue.
377     [drawData setLength:0];
379     [drawData appendBytes:&type length:sizeof(int)];
382 - (void)clearBlockFromRow:(int)row1 column:(int)col1
383                     toRow:(int)row2 column:(int)col2
385     int type = ClearBlockDrawType;
387     [drawData appendBytes:&type length:sizeof(int)];
389     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
390     [drawData appendBytes:&row1 length:sizeof(int)];
391     [drawData appendBytes:&col1 length:sizeof(int)];
392     [drawData appendBytes:&row2 length:sizeof(int)];
393     [drawData appendBytes:&col2 length:sizeof(int)];
396 - (void)deleteLinesFromRow:(int)row count:(int)count
397               scrollBottom:(int)bottom left:(int)left right:(int)right
399     int type = DeleteLinesDrawType;
401     [drawData appendBytes:&type length:sizeof(int)];
403     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
404     [drawData appendBytes:&row length:sizeof(int)];
405     [drawData appendBytes:&count length:sizeof(int)];
406     [drawData appendBytes:&bottom length:sizeof(int)];
407     [drawData appendBytes:&left length:sizeof(int)];
408     [drawData appendBytes:&right length:sizeof(int)];
411 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
412              cells:(int)cells flags:(int)flags
414     if (len <= 0 || cells <= 0) return;
416     int type = DrawStringDrawType;
418     [drawData appendBytes:&type length:sizeof(int)];
420     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
421     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
422     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
423     [drawData appendBytes:&row length:sizeof(int)];
424     [drawData appendBytes:&col length:sizeof(int)];
425     [drawData appendBytes:&cells length:sizeof(int)];
426     [drawData appendBytes:&flags length:sizeof(int)];
427     [drawData appendBytes:&len length:sizeof(int)];
428     [drawData appendBytes:s length:len];
431 - (void)insertLinesFromRow:(int)row count:(int)count
432               scrollBottom:(int)bottom left:(int)left right:(int)right
434     int type = InsertLinesDrawType;
436     [drawData appendBytes:&type length:sizeof(int)];
438     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
439     [drawData appendBytes:&row length:sizeof(int)];
440     [drawData appendBytes:&count length:sizeof(int)];
441     [drawData appendBytes:&bottom length:sizeof(int)];
442     [drawData appendBytes:&left length:sizeof(int)];
443     [drawData appendBytes:&right length:sizeof(int)];
446 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
447                fraction:(int)percent color:(int)color
449     int type = DrawCursorDrawType;
450     unsigned uc = MM_COLOR(color);
452     [drawData appendBytes:&type length:sizeof(int)];
454     [drawData appendBytes:&uc length:sizeof(unsigned)];
455     [drawData appendBytes:&row length:sizeof(int)];
456     [drawData appendBytes:&col length:sizeof(int)];
457     [drawData appendBytes:&shape length:sizeof(int)];
458     [drawData appendBytes:&percent length:sizeof(int)];
461 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
462                    numColumns:(int)nc invert:(int)invert
464     int type = DrawInvertedRectDrawType;
465     [drawData appendBytes:&type length:sizeof(int)];
467     [drawData appendBytes:&row length:sizeof(int)];
468     [drawData appendBytes:&col length:sizeof(int)];
469     [drawData appendBytes:&nr length:sizeof(int)];
470     [drawData appendBytes:&nc length:sizeof(int)];
471     [drawData appendBytes:&invert length:sizeof(int)];
474 - (void)update
476     // Tend to the run loop, returning immediately if there are no events
477     // waiting.
478     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
479                              beforeDate:[NSDate distantPast]];
481 #if 0
482     // Keyboard and mouse input is handled directly, other input is queued and
483     // processed here.  This call may enter a blocking loop.
484     if ([inputQueue count] > 0)
485         [self processInputQueue];
486 #endif
489 - (void)flushQueue:(BOOL)force
491     // NOTE: This variable allows for better control over when the queue is
492     // flushed.  It can be set to YES at the beginning of a sequence of calls
493     // that may potentially add items to the queue, and then restored back to
494     // NO.
495     if (flushDisabled) return;
497     // NOTE! This method gets called a lot; if we were to flush every time it
498     // got called MacVim would feel unresponsive.  So there is a time out which
499     // ensures that the queue isn't flushed too often.
500     if (!force && lastFlushDate
501             && -[lastFlushDate timeIntervalSinceNow] < MMFlushTimeoutInterval
502             && [drawData length] < MMFlushQueueLenHint)
503         return;
505     if ([drawData length] > 0) {
506         // HACK!  Detect changes to 'guifontwide'.
507         if (gui.wide_font != (GuiFont)oldWideFont) {
508             [oldWideFont release];
509             oldWideFont = [(NSFont*)gui.wide_font retain];
510             [self setWideFont:oldWideFont];
511         }
513         int type = SetCursorPosDrawType;
514         [drawData appendBytes:&type length:sizeof(type)];
515         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
516         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
518         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
519         [drawData setLength:0];
520     }
522     if ([outputQueue count] > 0) {
523         [self insertVimStateMessage];
525         @try {
526             [frontendProxy processCommandQueue:outputQueue];
527         }
528         @catch (NSException *e) {
529             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
530         }
532         [outputQueue removeAllObjects];
534         [lastFlushDate release];
535         lastFlushDate = [[NSDate date] retain];
536     }
539 - (BOOL)waitForInput:(int)milliseconds
541     //NSLog(@"|ENTER| %s%d",  _cmd, milliseconds);
543     // Only start the run loop if the input queue is empty, otherwise process
544     // the input first so that the input on queue isn't delayed.
545     if ([inputQueue count]) {
546         inputReceived = YES;
547     } else {
548         NSDate *date = milliseconds > 0 ?
549                 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
550                 [NSDate distantFuture];
552         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
553                                  beforeDate:date];
554     }
556     // I know of no way to figure out if the run loop exited because input was
557     // found or because of a time out, so I need to manually indicate when
558     // input was received in processInput:data: and then reset it every time
559     // here.
560     BOOL yn = inputReceived;
561     inputReceived = NO;
563     // Keyboard and mouse input is handled directly, other input is queued and
564     // processed here.  This call may enter a blocking loop.
565     if ([inputQueue count] > 0)
566         [self processInputQueue];
568     //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
569     return yn;
572 - (void)exit
574     // NOTE: This is called if mch_exit() is called.  Since we assume here that
575     // the process has started properly, be sure to use exit() instead of
576     // mch_exit() to prematurely terminate a process.
578     // To notify MacVim that this Vim process is exiting we could simply
579     // invalidate the connection and it would automatically receive a
580     // connectionDidDie: notification.  However, this notification seems to
581     // take up to 300 ms to arrive which is quite a noticeable delay.  Instead
582     // we immediately send a message to MacVim asking it to close the window
583     // belonging to this process, and then we invalidate the connection (in
584     // case the message got lost).
586     // Make sure no connectionDidDie: notification is received now that we are
587     // already exiting.
588     [[NSNotificationCenter defaultCenter] removeObserver:self];
590     if ([connection isValid]) {
591         @try {
592             // Flush the entire queue in case a VimLeave autocommand added
593             // something to the queue.
594             [self queueMessage:CloseWindowMsgID data:nil];
595             [frontendProxy processCommandQueue:outputQueue];
596         }
597         @catch (NSException *e) {
598             NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
599         }
601         [connection invalidate];
602     }
604 #ifdef MAC_CLIENTSERVER
605     // The default connection is used for the client/server code.
606     [[NSConnection defaultConnection] setRootObject:nil];
607     [[NSConnection defaultConnection] invalidate];
608 #endif
610     if (fontContainerRef) {
611         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
612         fontContainerRef = 0;
613     }
615     usleep(MMExitProcessDelay);
618 - (void)selectTab:(int)index
620     //NSLog(@"%s%d", _cmd, index);
622     index -= 1;
623     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
624     [self queueMessage:SelectTabMsgID data:data];
627 - (void)updateTabBar
629     //NSLog(@"%s", _cmd);
631     NSMutableData *data = [NSMutableData data];
633     int idx = tabpage_index(curtab) - 1;
634     [data appendBytes:&idx length:sizeof(int)];
636     tabpage_T *tp;
637     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
638         // This function puts the label of the tab in the global 'NameBuff'.
639         get_tabline_label(tp, FALSE);
640         char_u *s = NameBuff;
641         int len = STRLEN(s);
642         if (len <= 0) continue;
644 #ifdef FEAT_MBYTE
645         s = CONVERT_TO_UTF8(s);
646 #endif
648         // Count the number of windows in the tabpage.
649         //win_T *wp = tp->tp_firstwin;
650         //int wincount;
651         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
653         //[data appendBytes:&wincount length:sizeof(int)];
654         [data appendBytes:&len length:sizeof(int)];
655         [data appendBytes:s length:len];
657 #ifdef FEAT_MBYTE
658         CONVERT_TO_UTF8_FREE(s);
659 #endif
660     }
662     [self queueMessage:UpdateTabBarMsgID data:data];
665 - (BOOL)tabBarVisible
667     return tabBarVisible;
670 - (void)showTabBar:(BOOL)enable
672     tabBarVisible = enable;
674     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
675     [self queueMessage:msgid data:nil];
678 - (void)setRows:(int)rows columns:(int)cols
680     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
682     int dim[] = { rows, cols };
683     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
685     [self queueMessage:SetTextDimensionsMsgID data:data];
688 - (void)setWindowTitle:(char *)title
690     NSMutableData *data = [NSMutableData data];
691     int len = strlen(title);
692     if (len <= 0) return;
694     [data appendBytes:&len length:sizeof(int)];
695     [data appendBytes:title length:len];
697     [self queueMessage:SetWindowTitleMsgID data:data];
700 - (void)setDocumentFilename:(char *)filename
702     NSMutableData *data = [NSMutableData data];
703     int len = filename ? strlen(filename) : 0;
705     [data appendBytes:&len length:sizeof(int)];
706     if (len > 0)
707         [data appendBytes:filename length:len];
709     [self queueMessage:SetDocumentFilenameMsgID data:data];
712 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
714     char_u *s = NULL;
716     @try {
717         [frontendProxy showSavePanelWithAttributes:attr];
719         [self waitForDialogReturn];
721         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
722             char_u *ret = (char_u*)[dialogReturn UTF8String];
723 #ifdef FEAT_MBYTE
724             ret = CONVERT_FROM_UTF8(ret);
725 #endif
726             s = vim_strsave(ret);
727 #ifdef FEAT_MBYTE
728             CONVERT_FROM_UTF8_FREE(ret);
729 #endif
730         }
732         [dialogReturn release];  dialogReturn = nil;
733     }
734     @catch (NSException *e) {
735         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
736     }
738     return (char *)s;
741 - (oneway void)setDialogReturn:(in bycopy id)obj
743     // NOTE: This is called by
744     //   - [MMVimController panelDidEnd:::], and
745     //   - [MMVimController alertDidEnd:::],
746     // to indicate that a save/open panel or alert has finished.
748     // We want to distinguish between "no dialog return yet" and "dialog
749     // returned nothing".  The former can be tested with dialogReturn == nil,
750     // the latter with dialogReturn == [NSNull null].
751     if (!obj) obj = [NSNull null];
753     if (obj != dialogReturn) {
754         [dialogReturn release];
755         dialogReturn = [obj retain];
756     }
759 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
761     int retval = 0;
763     @try {
764         [frontendProxy presentDialogWithAttributes:attr];
766         [self waitForDialogReturn];
768         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
769                 && [dialogReturn count]) {
770             retval = [[dialogReturn objectAtIndex:0] intValue];
771             if (txtfield && [dialogReturn count] > 1) {
772                 NSString *retString = [dialogReturn objectAtIndex:1];
773                 char_u *ret = (char_u*)[retString UTF8String];
774 #ifdef FEAT_MBYTE
775                 ret = CONVERT_FROM_UTF8(ret);
776 #endif
777                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
778 #ifdef FEAT_MBYTE
779                 CONVERT_FROM_UTF8_FREE(ret);
780 #endif
781             }
782         }
784         [dialogReturn release]; dialogReturn = nil;
785     }
786     @catch (NSException *e) {
787         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
788     }
790     return retval;
793 - (void)showToolbar:(int)enable flags:(int)flags
795     NSMutableData *data = [NSMutableData data];
797     [data appendBytes:&enable length:sizeof(int)];
798     [data appendBytes:&flags length:sizeof(int)];
800     [self queueMessage:ShowToolbarMsgID data:data];
803 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
805     NSMutableData *data = [NSMutableData data];
807     [data appendBytes:&ident length:sizeof(long)];
808     [data appendBytes:&type length:sizeof(int)];
810     [self queueMessage:CreateScrollbarMsgID data:data];
813 - (void)destroyScrollbarWithIdentifier:(long)ident
815     NSMutableData *data = [NSMutableData data];
816     [data appendBytes:&ident length:sizeof(long)];
818     [self queueMessage:DestroyScrollbarMsgID data:data];
821 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
823     NSMutableData *data = [NSMutableData data];
825     [data appendBytes:&ident length:sizeof(long)];
826     [data appendBytes:&visible length:sizeof(int)];
828     [self queueMessage:ShowScrollbarMsgID data:data];
831 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
833     NSMutableData *data = [NSMutableData data];
835     [data appendBytes:&ident length:sizeof(long)];
836     [data appendBytes:&pos length:sizeof(int)];
837     [data appendBytes:&len length:sizeof(int)];
839     [self queueMessage:SetScrollbarPositionMsgID data:data];
842 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
843                     identifier:(long)ident
845     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
846     float prop = (float)size/(max+1);
847     if (fval < 0) fval = 0;
848     else if (fval > 1.0f) fval = 1.0f;
849     if (prop < 0) prop = 0;
850     else if (prop > 1.0f) prop = 1.0f;
852     NSMutableData *data = [NSMutableData data];
854     [data appendBytes:&ident length:sizeof(long)];
855     [data appendBytes:&fval length:sizeof(float)];
856     [data appendBytes:&prop length:sizeof(float)];
858     [self queueMessage:SetScrollbarThumbMsgID data:data];
861 - (void)setFont:(NSFont *)font
863     NSString *fontName = [font displayName];
864     float size = [font pointSize];
865     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
866     if (len > 0) {
867         NSMutableData *data = [NSMutableData data];
869         [data appendBytes:&size length:sizeof(float)];
870         [data appendBytes:&len length:sizeof(int)];
871         [data appendBytes:[fontName UTF8String] length:len];
873         [self queueMessage:SetFontMsgID data:data];
874     }
877 - (void)setWideFont:(NSFont *)font
879     NSString *fontName = [font displayName];
880     float size = [font pointSize];
881     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
882     NSMutableData *data = [NSMutableData data];
884     [data appendBytes:&size length:sizeof(float)];
885     [data appendBytes:&len length:sizeof(int)];
886     if (len > 0)
887         [data appendBytes:[fontName UTF8String] length:len];
889     [self queueMessage:SetWideFontMsgID data:data];
892 - (void)executeActionWithName:(NSString *)name
894     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
896     if (len > 0) {
897         NSMutableData *data = [NSMutableData data];
899         [data appendBytes:&len length:sizeof(int)];
900         [data appendBytes:[name UTF8String] length:len];
902         [self queueMessage:ExecuteActionMsgID data:data];
903     }
906 - (void)setMouseShape:(int)shape
908     NSMutableData *data = [NSMutableData data];
909     [data appendBytes:&shape length:sizeof(int)];
910     [self queueMessage:SetMouseShapeMsgID data:data];
913 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
915     // Vim specifies times in milliseconds, whereas Cocoa wants them in
916     // seconds.
917     blinkWaitInterval = .001f*wait;
918     blinkOnInterval = .001f*on;
919     blinkOffInterval = .001f*off;
922 - (void)startBlink
924     if (blinkTimer) {
925         [blinkTimer invalidate];
926         [blinkTimer release];
927         blinkTimer = nil;
928     }
930     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
931             && gui.in_focus) {
932         blinkState = MMBlinkStateOn;
933         blinkTimer =
934             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
935                                               target:self
936                                             selector:@selector(blinkTimerFired:)
937                                             userInfo:nil repeats:NO] retain];
938         gui_update_cursor(TRUE, FALSE);
939         [self flushQueue:YES];
940     }
943 - (void)stopBlink
945     if (MMBlinkStateOff == blinkState) {
946         gui_update_cursor(TRUE, FALSE);
947         [self flushQueue:YES];
948     }
950     blinkState = MMBlinkStateNone;
953 - (void)adjustLinespace:(int)linespace
955     NSMutableData *data = [NSMutableData data];
956     [data appendBytes:&linespace length:sizeof(int)];
957     [self queueMessage:AdjustLinespaceMsgID data:data];
960 - (void)activate
962     [self queueMessage:ActivateMsgID data:nil];
965 - (void)setPreEditRow:(int)row column:(int)col
967     NSMutableData *data = [NSMutableData data];
968     [data appendBytes:&row length:sizeof(int)];
969     [data appendBytes:&col length:sizeof(int)];
970     [self queueMessage:SetPreEditPositionMsgID data:data];
973 - (int)lookupColorWithKey:(NSString *)key
975     if (!(key && [key length] > 0))
976         return INVALCOLOR;
978     NSString *stripKey = [[[[key lowercaseString]
979         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
980             componentsSeparatedByString:@" "]
981                componentsJoinedByString:@""];
983     if (stripKey && [stripKey length] > 0) {
984         // First of all try to lookup key in the color dictionary; note that
985         // all keys in this dictionary are lowercase with no whitespace.
986         id obj = [colorDict objectForKey:stripKey];
987         if (obj) return [obj intValue];
989         // The key was not in the dictionary; is it perhaps of the form
990         // #rrggbb?
991         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
992             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
993             [scanner setScanLocation:1];
994             unsigned hex = 0;
995             if ([scanner scanHexInt:&hex]) {
996                 return (int)hex;
997             }
998         }
1000         // As a last resort, check if it is one of the system defined colors.
1001         // The keys in this dictionary are also lowercase with no whitespace.
1002         obj = [sysColorDict objectForKey:stripKey];
1003         if (obj) {
1004             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
1005             if (col) {
1006                 float r, g, b, a;
1007                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
1008                 [col getRed:&r green:&g blue:&b alpha:&a];
1009                 return (((int)(r*255+.5f) & 0xff) << 16)
1010                      + (((int)(g*255+.5f) & 0xff) << 8)
1011                      +  ((int)(b*255+.5f) & 0xff);
1012             }
1013         }
1014     }
1016     //NSLog(@"WARNING: No color with key %@ found.", stripKey);
1017     return INVALCOLOR;
1020 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
1022     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1023     id obj;
1025     while ((obj = [e nextObject])) {
1026         if ([value isEqual:obj])
1027             return YES;
1028     }
1030     return NO;
1033 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1035     NSMutableData *data = [NSMutableData data];
1036     [data appendBytes:&fuoptions length:sizeof(int)];
1037     bg = MM_COLOR(bg);
1038     [data appendBytes:&bg length:sizeof(int)];
1039     [self queueMessage:EnterFullscreenMsgID data:data];
1042 - (void)leaveFullscreen
1044     [self queueMessage:LeaveFullscreenMsgID data:nil];
1047 - (void)setAntialias:(BOOL)antialias
1049     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1051     [self queueMessage:msgid data:nil];
1054 - (void)updateModifiedFlag
1056     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1057     // vice versa.
1058     int msgid = [self checkForModifiedBuffers]
1059             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1061     [self queueMessage:msgid data:nil];
1064 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1066     // NOTE: This method might get called whenever the run loop is tended to.
1067     // Normal keyboard and mouse input is added to input buffers, so there is
1068     // no risk in handling these events directly (they return immediately, and
1069     // do not call any other Vim functions).  However, other events such
1070     // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
1071     // events which would cause this method to be called recursively.  This
1072     // in turn leads to various difficulties that we do not want to have to
1073     // deal with.  To avoid recursive calls here we add all events except
1074     // keyboard and mouse events to an input queue which is processed whenever
1075     // gui_mch_update() is called (see processInputQueue).
1077     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1079     // Don't flush too soon after receiving input or update speed will suffer.
1080     [lastFlushDate release];
1081     lastFlushDate = [[NSDate date] retain];
1083     // Handle keyboard and mouse input now.  All other events are queued.
1084     if (InsertTextMsgID == msgid) {
1085         [self handleInsertText:data];
1086     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1087         if (!data) return;
1088         const void *bytes = [data bytes];
1089         int mods = *((int*)bytes);  bytes += sizeof(int);
1090         int len = *((int*)bytes);  bytes += sizeof(int);
1091         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1092                                               encoding:NSUTF8StringEncoding];
1093         mods = eventModifierFlagsToVimModMask(mods);
1095         [self handleKeyDown:key modifiers:mods];
1097         [key release];
1098     } else if (ScrollWheelMsgID == msgid) {
1099         if (!data) return;
1100         const void *bytes = [data bytes];
1102         int row = *((int*)bytes);  bytes += sizeof(int);
1103         int col = *((int*)bytes);  bytes += sizeof(int);
1104         int flags = *((int*)bytes);  bytes += sizeof(int);
1105         float dy = *((float*)bytes);  bytes += sizeof(float);
1107         int button = MOUSE_5;
1108         if (dy > 0) button = MOUSE_4;
1110         flags = eventModifierFlagsToVimMouseModMask(flags);
1112         int numLines = (int)round(dy);
1113         if (numLines < 0) numLines = -numLines;
1114         if (numLines == 0) numLines = 1;
1116 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1117         gui.scroll_wheel_force = numLines;
1118 #endif
1120         gui_send_mouse_event(button, col, row, NO, flags);
1121     } else if (MouseDownMsgID == msgid) {
1122         if (!data) return;
1123         const void *bytes = [data bytes];
1125         int row = *((int*)bytes);  bytes += sizeof(int);
1126         int col = *((int*)bytes);  bytes += sizeof(int);
1127         int button = *((int*)bytes);  bytes += sizeof(int);
1128         int flags = *((int*)bytes);  bytes += sizeof(int);
1129         int count = *((int*)bytes);  bytes += sizeof(int);
1131         button = eventButtonNumberToVimMouseButton(button);
1132         if (button >= 0) {
1133             flags = eventModifierFlagsToVimMouseModMask(flags);
1134             gui_send_mouse_event(button, col, row, count>1, flags);
1135         }
1136     } else if (MouseUpMsgID == msgid) {
1137         if (!data) return;
1138         const void *bytes = [data bytes];
1140         int row = *((int*)bytes);  bytes += sizeof(int);
1141         int col = *((int*)bytes);  bytes += sizeof(int);
1142         int flags = *((int*)bytes);  bytes += sizeof(int);
1144         flags = eventModifierFlagsToVimMouseModMask(flags);
1146         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1147     } else if (MouseDraggedMsgID == msgid) {
1148         if (!data) return;
1149         const void *bytes = [data bytes];
1151         int row = *((int*)bytes);  bytes += sizeof(int);
1152         int col = *((int*)bytes);  bytes += sizeof(int);
1153         int flags = *((int*)bytes);  bytes += sizeof(int);
1155         flags = eventModifierFlagsToVimMouseModMask(flags);
1157         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1158     } else if (MouseMovedMsgID == msgid) {
1159         const void *bytes = [data bytes];
1160         int row = *((int*)bytes);  bytes += sizeof(int);
1161         int col = *((int*)bytes);  bytes += sizeof(int);
1163         gui_mouse_moved(col, row);
1164     } else if (AddInputMsgID == msgid) {
1165         NSString *string = [[NSString alloc] initWithData:data
1166                 encoding:NSUTF8StringEncoding];
1167         if (string) {
1168             [self addInput:string];
1169             [string release];
1170         }
1171     } else if (TerminateNowMsgID == msgid) {
1172         isTerminating = YES;
1173     } else {
1174         // Not keyboard or mouse event, queue it and handle later.
1175         //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
1176         [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1177         [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
1178     }
1180     // See waitForInput: for an explanation of this flag.
1181     inputReceived = YES;
1184 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1186     // TODO: Get rid of this method?
1187     //NSLog(@"%s%@", _cmd, messages);
1189     unsigned i, count = [messages count];
1190     for (i = 0; i < count; i += 2) {
1191         int msgid = [[messages objectAtIndex:i] intValue];
1192         id data = [messages objectAtIndex:i+1];
1193         if ([data isEqual:[NSNull null]])
1194             data = nil;
1196         [self processInput:msgid data:data];
1197     }
1200 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1201                   errorString:(out bycopy NSString **)errstr
1203     return evalExprCocoa(expr, errstr);
1207 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1209     NSString *eval = nil;
1210     char_u *s = (char_u*)[expr UTF8String];
1212 #ifdef FEAT_MBYTE
1213     s = CONVERT_FROM_UTF8(s);
1214 #endif
1216     char_u *res = eval_client_expr_to_string(s);
1218 #ifdef FEAT_MBYTE
1219     CONVERT_FROM_UTF8_FREE(s);
1220 #endif
1222     if (res != NULL) {
1223         s = res;
1224 #ifdef FEAT_MBYTE
1225         s = CONVERT_TO_UTF8(s);
1226 #endif
1227         eval = [NSString stringWithUTF8String:(char*)s];
1228 #ifdef FEAT_MBYTE
1229         CONVERT_TO_UTF8_FREE(s);
1230 #endif
1231         vim_free(res);
1232     }
1234     return eval;
1237 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1239     // TODO: This method should share code with clip_mch_request_selection().
1241     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1242         // If there is no pasteboard, return YES to indicate that there is text
1243         // to copy.
1244         if (!pboard)
1245             return YES;
1247         clip_copy_selection();
1249         // Get the text to put on the pasteboard.
1250         long_u llen = 0; char_u *str = 0;
1251         int type = clip_convert_selection(&str, &llen, &clip_star);
1252         if (type < 0)
1253             return NO;
1254         
1255         // TODO: Avoid overflow.
1256         int len = (int)llen;
1257 #ifdef FEAT_MBYTE
1258         if (output_conv.vc_type != CONV_NONE) {
1259             char_u *conv_str = string_convert(&output_conv, str, &len);
1260             if (conv_str) {
1261                 vim_free(str);
1262                 str = conv_str;
1263             }
1264         }
1265 #endif
1267         NSString *string = [[NSString alloc]
1268             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1270         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1271         [pboard declareTypes:types owner:nil];
1272         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1273     
1274         [string release];
1275         vim_free(str);
1277         return ok;
1278     }
1280     return NO;
1283 - (oneway void)addReply:(in bycopy NSString *)reply
1284                  server:(in byref id <MMVimServerProtocol>)server
1286     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1288     // Replies might come at any time and in any order so we keep them in an
1289     // array inside a dictionary with the send port used as key.
1291     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1292     // HACK! Assume connection uses mach ports.
1293     int port = [(NSMachPort*)[conn sendPort] machPort];
1294     NSNumber *key = [NSNumber numberWithInt:port];
1296     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1297     if (!replies) {
1298         replies = [NSMutableArray array];
1299         [serverReplyDict setObject:replies forKey:key];
1300     }
1302     [replies addObject:reply];
1305 - (void)addInput:(in bycopy NSString *)input
1306                  client:(in byref id <MMVimClientProtocol>)client
1308     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1310     [self addInput:input];
1311     [self addClient:(id)client];
1313     inputReceived = YES;
1316 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1317                  client:(in byref id <MMVimClientProtocol>)client
1319     [self addClient:(id)client];
1320     return [self evaluateExpression:expr];
1323 - (void)registerServerWithName:(NSString *)name
1325     NSString *svrName = name;
1326     NSConnection *svrConn = [NSConnection defaultConnection];
1327     unsigned i;
1329     for (i = 0; i < MMServerMax; ++i) {
1330         NSString *connName = [self connectionNameFromServerName:svrName];
1332         if ([svrConn registerName:connName]) {
1333             //NSLog(@"Registered server with name: %@", svrName);
1335             // TODO: Set request/reply time-outs to something else?
1336             //
1337             // Don't wait for requests (time-out means that the message is
1338             // dropped).
1339             [svrConn setRequestTimeout:0];
1340             //[svrConn setReplyTimeout:MMReplyTimeout];
1341             [svrConn setRootObject:self];
1343             char_u *s = (char_u*)[svrName UTF8String];
1344 #ifdef FEAT_MBYTE
1345             s = CONVERT_FROM_UTF8(s);
1346 #endif
1347             // NOTE: 'serverName' is a global variable
1348             serverName = vim_strsave(s);
1349 #ifdef FEAT_MBYTE
1350             CONVERT_FROM_UTF8_FREE(s);
1351 #endif
1352 #ifdef FEAT_EVAL
1353             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1354 #endif
1355 #ifdef FEAT_TITLE
1356             need_maketitle = TRUE;
1357 #endif
1358             [self queueMessage:SetServerNameMsgID data:
1359                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1360             break;
1361         }
1363         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1364     }
1367 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1368                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1369               silent:(BOOL)silent
1371     // NOTE: If 'name' equals 'serverName' then the request is local (client
1372     // and server are the same).  This case is not handled separately, so a
1373     // connection will be set up anyway (this simplifies the code).
1375     NSConnection *conn = [self connectionForServerName:name];
1376     if (!conn) {
1377         if (!silent) {
1378             char_u *s = (char_u*)[name UTF8String];
1379 #ifdef FEAT_MBYTE
1380             s = CONVERT_FROM_UTF8(s);
1381 #endif
1382             EMSG2(_(e_noserver), s);
1383 #ifdef FEAT_MBYTE
1384             CONVERT_FROM_UTF8_FREE(s);
1385 #endif
1386         }
1387         return NO;
1388     }
1390     if (port) {
1391         // HACK! Assume connection uses mach ports.
1392         *port = [(NSMachPort*)[conn sendPort] machPort];
1393     }
1395     id proxy = [conn rootProxy];
1396     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1398     @try {
1399         if (expr) {
1400             NSString *eval = [proxy evaluateExpression:string client:self];
1401             if (reply) {
1402                 if (eval) {
1403                     char_u *r = (char_u*)[eval UTF8String];
1404 #ifdef FEAT_MBYTE
1405                     r = CONVERT_FROM_UTF8(r);
1406 #endif
1407                     *reply = vim_strsave(r);
1408 #ifdef FEAT_MBYTE
1409                     CONVERT_FROM_UTF8_FREE(r);
1410 #endif
1411                 } else {
1412                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1413                 }
1414             }
1416             if (!eval)
1417                 return NO;
1418         } else {
1419             [proxy addInput:string client:self];
1420         }
1421     }
1422     @catch (NSException *e) {
1423         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1424         return NO;
1425     }
1427     return YES;
1430 - (NSArray *)serverList
1432     NSArray *list = nil;
1434     if ([self connection]) {
1435         id proxy = [connection rootProxy];
1436         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1438         @try {
1439             list = [proxy serverList];
1440         }
1441         @catch (NSException *e) {
1442             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1443         }
1444     } else {
1445         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1446     }
1448     return list;
1451 - (NSString *)peekForReplyOnPort:(int)port
1453     //NSLog(@"%s%d", _cmd, port);
1455     NSNumber *key = [NSNumber numberWithInt:port];
1456     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1457     if (replies && [replies count]) {
1458         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1459         //        [replies objectAtIndex:0]);
1460         return [replies objectAtIndex:0];
1461     }
1463     //NSLog(@"    No replies");
1464     return nil;
1467 - (NSString *)waitForReplyOnPort:(int)port
1469     //NSLog(@"%s%d", _cmd, port);
1470     
1471     NSConnection *conn = [self connectionForServerPort:port];
1472     if (!conn)
1473         return nil;
1475     NSNumber *key = [NSNumber numberWithInt:port];
1476     NSMutableArray *replies = nil;
1477     NSString *reply = nil;
1479     // Wait for reply as long as the connection to the server is valid (unless
1480     // user interrupts wait with Ctrl-C).
1481     while (!got_int && [conn isValid] &&
1482             !(replies = [serverReplyDict objectForKey:key])) {
1483         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1484                                  beforeDate:[NSDate distantFuture]];
1485     }
1487     if (replies) {
1488         if ([replies count] > 0) {
1489             reply = [[replies objectAtIndex:0] retain];
1490             //NSLog(@"    Got reply: %@", reply);
1491             [replies removeObjectAtIndex:0];
1492             [reply autorelease];
1493         }
1495         if ([replies count] == 0)
1496             [serverReplyDict removeObjectForKey:key];
1497     }
1499     return reply;
1502 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1504     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1505     if (client) {
1506         @try {
1507             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1508             [client addReply:reply server:self];
1509             return YES;
1510         }
1511         @catch (NSException *e) {
1512             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1513         }
1514     } else {
1515         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1516     }
1518     return NO;
1521 - (BOOL)waitForAck
1523     return waitForAck;
1526 - (void)setWaitForAck:(BOOL)yn
1528     waitForAck = yn;
1531 - (void)waitForConnectionAcknowledgement
1533     if (!waitForAck) return;
1535     while (waitForAck && !got_int && [connection isValid] && !isTerminating) {
1536         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1537                                  beforeDate:[NSDate distantFuture]];
1538         //NSLog(@"  waitForAck=%d got_int=%d isTerminating=%d isValid=%d",
1539         //        waitForAck, got_int, isTerminating, [connection isValid]);
1540     }
1542     if (waitForAck) {
1543         // Never received a connection acknowledgement, so die.
1544         [[NSNotificationCenter defaultCenter] removeObserver:self];
1545         [frontendProxy release];  frontendProxy = nil;
1547         // NOTE: We intentionally do not call mch_exit() since this in turn
1548         // will lead to -[MMBackend exit] getting called which we want to
1549         // avoid.
1550         usleep(MMExitProcessDelay);
1551         exit(0);
1552     }
1554     [self processInputQueue];
1557 - (oneway void)acknowledgeConnection
1559     //NSLog(@"%s", _cmd);
1560     waitForAck = NO;
1563 @end // MMBackend
1567 @implementation MMBackend (Private)
1569 - (void)waitForDialogReturn
1571     // Keep processing the run loop until a dialog returns.  To avoid getting
1572     // stuck in an endless loop (could happen if the setDialogReturn: message
1573     // was lost) we also do some paranoia checks.
1574     //
1575     // Note that in Cocoa the user can still resize windows and select menu
1576     // items while a sheet is being displayed, so we can't just wait for the
1577     // first message to arrive and assume that is the setDialogReturn: call.
1579     while (nil == dialogReturn && !got_int && [connection isValid]
1580             && !isTerminating)
1581         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1582                                  beforeDate:[NSDate distantFuture]];
1584     // Search for any resize messages on the input queue.  All other messages
1585     // on the input queue are dropped.  The reason why we single out resize
1586     // messages is because the user may have resized the window while a sheet
1587     // was open.
1588     int i, count = [inputQueue count];
1589     if (count > 0) {
1590         id textDimData = nil;
1591         if (count%2 == 0) {
1592             for (i = count-2; i >= 0; i -= 2) {
1593                 int msgid = [[inputQueue objectAtIndex:i] intValue];
1594                 if (SetTextDimensionsMsgID == msgid) {
1595                     textDimData = [[inputQueue objectAtIndex:i+1] retain];
1596                     break;
1597                 }
1598             }
1599         }
1601         [inputQueue removeAllObjects];
1603         if (textDimData) {
1604             [inputQueue addObject:
1605                     [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1606             [inputQueue addObject:textDimData];
1607             [textDimData release];
1608         }
1609     }
1612 - (void)insertVimStateMessage
1614     // NOTE: This is the place to add Vim state that needs to be accessed from
1615     // MacVim.  Do not add state that could potentially require lots of memory
1616     // since this message gets sent each time the output queue is forcibly
1617     // flushed (e.g. storing the currently selected text would be a bad idea).
1618     // We take this approach of "pushing" the state to MacVim to avoid having
1619     // to make synchronous calls from MacVim to Vim in order to get state.
1621     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1622         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1623         [NSNumber numberWithInt:p_mh], @"p_mh",
1624         [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1625         nil];
1627     // Put the state before all other messages.
1628     int msgid = SetVimStateMsgID;
1629     [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1630     [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1631                       atIndex:0];
1634 - (void)processInputQueue
1636     if ([inputQueue count] == 0) return;
1638     // NOTE: One of the input events may cause this method to be called
1639     // recursively, so copy the input queue to a local variable and clear it
1640     // before starting to process input events (otherwise we could get stuck in
1641     // an endless loop).
1642     NSArray *q = [inputQueue copy];
1643     unsigned i, count = [q count];
1645     [inputQueue removeAllObjects];
1647     for (i = 0; i < count-1; i += 2) {
1648         int msgid = [[q objectAtIndex:i] intValue];
1649         id data = [q objectAtIndex:i+1];
1650         if ([data isEqual:[NSNull null]])
1651             data = nil;
1653         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1654         [self handleInputEvent:msgid data:data];
1655     }
1657     [q release];
1658     //NSLog(@"Clear input event queue");
1661 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1663     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1665     if (SelectTabMsgID == msgid) {
1666         if (!data) return;
1667         const void *bytes = [data bytes];
1668         int idx = *((int*)bytes) + 1;
1669         //NSLog(@"Selecting tab %d", idx);
1670         send_tabline_event(idx);
1671     } else if (CloseTabMsgID == msgid) {
1672         if (!data) return;
1673         const void *bytes = [data bytes];
1674         int idx = *((int*)bytes) + 1;
1675         //NSLog(@"Closing tab %d", idx);
1676         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1677     } else if (AddNewTabMsgID == msgid) {
1678         //NSLog(@"Adding new tab");
1679         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1680     } else if (DraggedTabMsgID == msgid) {
1681         if (!data) return;
1682         const void *bytes = [data bytes];
1683         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1684         // based.
1685         int idx = *((int*)bytes);
1687         tabpage_move(idx);
1688     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1689             || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1690         if (!data) return;
1691         const void *bytes = [data bytes];
1692         int rows = Rows;
1693         if (SetTextColumnsMsgID != msgid) {
1694             rows = *((int*)bytes);  bytes += sizeof(int);
1695         }
1696         int cols = Columns;
1697         if (SetTextRowsMsgID != msgid) {
1698             cols = *((int*)bytes);  bytes += sizeof(int);
1699         }
1701         NSData *d = data;
1702         if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1703             int dim[2] = { rows, cols };
1704             d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1705             msgid = SetTextDimensionsMsgID;
1706         }
1708         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1709         // gui_resize_shell(), so we have to manually set the rows and columns
1710         // here.  (MacVim doesn't change the rows and columns to avoid
1711         // inconsistent states between Vim and MacVim.)
1712         [self queueMessage:msgid data:d];
1714         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1715         gui_resize_shell(cols, rows);
1716     } else if (ExecuteMenuMsgID == msgid) {
1717         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1718         if (attrs) {
1719             NSArray *desc = [attrs objectForKey:@"descriptor"];
1720             vimmenu_T *menu = menu_for_descriptor(desc);
1721             if (menu)
1722                 gui_menu_cb(menu);
1723         }
1724     } else if (ToggleToolbarMsgID == msgid) {
1725         [self handleToggleToolbar];
1726     } else if (ScrollbarEventMsgID == msgid) {
1727         [self handleScrollbarEvent:data];
1728     } else if (SetFontMsgID == msgid) {
1729         [self handleSetFont:data];
1730     } else if (VimShouldCloseMsgID == msgid) {
1731         gui_shell_closed();
1732     } else if (DropFilesMsgID == msgid) {
1733         [self handleDropFiles:data];
1734     } else if (DropStringMsgID == msgid) {
1735         [self handleDropString:data];
1736     } else if (GotFocusMsgID == msgid) {
1737         if (!gui.in_focus)
1738             [self focusChange:YES];
1739     } else if (LostFocusMsgID == msgid) {
1740         if (gui.in_focus)
1741             [self focusChange:NO];
1742     } else if (SetMouseShapeMsgID == msgid) {
1743         const void *bytes = [data bytes];
1744         int shape = *((int*)bytes);  bytes += sizeof(int);
1745         update_mouseshape(shape);
1746     } else if (XcodeModMsgID == msgid) {
1747         [self handleXcodeMod:data];
1748     } else if (OpenWithArgumentsMsgID == msgid) {
1749         [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1750     } else {
1751         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1752     }
1755 + (NSDictionary *)specialKeys
1757     static NSDictionary *specialKeys = nil;
1759     if (!specialKeys) {
1760         NSBundle *mainBundle = [NSBundle mainBundle];
1761         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1762                                               ofType:@"plist"];
1763         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1764     }
1766     return specialKeys;
1769 - (void)handleInsertText:(NSData *)data
1771     if (!data) return;
1773     NSString *key = [[NSString alloc] initWithData:data
1774                                           encoding:NSUTF8StringEncoding];
1775     char_u *str = (char_u*)[key UTF8String];
1776     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1778 #ifdef FEAT_MBYTE
1779     char_u *conv_str = NULL;
1780     if (input_conv.vc_type != CONV_NONE) {
1781         conv_str = string_convert(&input_conv, str, &len);
1782         if (conv_str)
1783             str = conv_str;
1784     }
1785 #endif
1787     if (len == 1 && ((str[0] == Ctrl_C && ctrl_c_interrupts)
1788             || (str[0] == intr_char && intr_char != Ctrl_C))) {
1789         trash_input_buf();
1790         got_int = TRUE;
1791     }
1793     for (i = 0; i < len; ++i) {
1794         add_to_input_buf(str+i, 1);
1795         if (CSI == str[i]) {
1796             // NOTE: If the converted string contains the byte CSI, then it
1797             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1798             // won't work.
1799             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1800             add_to_input_buf(extra, 2);
1801         }
1802     }
1804 #ifdef FEAT_MBYTE
1805     if (conv_str)
1806         vim_free(conv_str);
1807 #endif
1808     [key release];
1811 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1813     char_u special[3];
1814     char_u modChars[3];
1815     char_u *chars = (char_u*)[key UTF8String];
1816 #ifdef FEAT_MBYTE
1817     char_u *conv_str = NULL;
1818 #endif
1819     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1821     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1822     // that new keys can easily be added.
1823     NSString *specialString = [[MMBackend specialKeys]
1824             objectForKey:key];
1825     if (specialString && [specialString length] > 1) {
1826         //NSLog(@"special key: %@", specialString);
1827         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1828                 [specialString characterAtIndex:1]);
1830         ikey = simplify_key(ikey, &mods);
1831         if (ikey == CSI)
1832             ikey = K_CSI;
1834         special[0] = CSI;
1835         special[1] = K_SECOND(ikey);
1836         special[2] = K_THIRD(ikey);
1838         chars = special;
1839         length = 3;
1840     } else if (1 == length && TAB == chars[0]) {
1841         // Tab is a trouble child:
1842         // - <Tab> is added to the input buffer as is
1843         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1844         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1845         //   to be converted to utf-8
1846         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1847         // - <C-Tab> is reserved by Mac OS X
1848         // - <D-Tab> is reserved by Mac OS X
1849         chars = special;
1850         special[0] = TAB;
1851         length = 1;
1853         if (mods & MOD_MASK_SHIFT) {
1854             mods &= ~MOD_MASK_SHIFT;
1855             special[0] = CSI;
1856             special[1] = K_SECOND(K_S_TAB);
1857             special[2] = K_THIRD(K_S_TAB);
1858             length = 3;
1859         } else if (mods & MOD_MASK_ALT) {
1860             int mtab = 0x80 | TAB;
1861 #ifdef FEAT_MBYTE
1862             if (enc_utf8) {
1863                 // Convert to utf-8
1864                 special[0] = (mtab >> 6) + 0xc0;
1865                 special[1] = mtab & 0xbf;
1866                 length = 2;
1867             } else
1868 #endif
1869             {
1870                 special[0] = mtab;
1871                 length = 1;
1872             }
1873             mods &= ~MOD_MASK_ALT;
1874         }
1875     } else if (length > 0) {
1876         unichar c = [key characterAtIndex:0];
1878         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1879         //        [key characterAtIndex:0], mods);
1881         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1882                 || (c == intr_char && intr_char != Ctrl_C))) {
1883             trash_input_buf();
1884             got_int = TRUE;
1885         }
1887         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1888         // cleared since they are already added to the key by the AppKit.
1889         // Unfortunately, the only way to deal with when to clear the modifiers
1890         // or not seems to be to have hard-wired rules like this.
1891         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1892                     || 0x9 == c || 0xd == c || ESC == c) ) {
1893             mods &= ~MOD_MASK_SHIFT;
1894             mods &= ~MOD_MASK_CTRL;
1895             //NSLog(@"clear shift ctrl");
1896         }
1898         // HACK!  All Option+key presses go via 'insert text' messages, except
1899         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1900         // not work to map to it.
1901         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1902             //NSLog(@"clear alt");
1903             mods &= ~MOD_MASK_ALT;
1904         }
1906 #ifdef FEAT_MBYTE
1907         if (input_conv.vc_type != CONV_NONE) {
1908             conv_str = string_convert(&input_conv, chars, &length);
1909             if (conv_str)
1910                 chars = conv_str;
1911         }
1912 #endif
1913     }
1915     if (chars && length > 0) {
1916         if (mods) {
1917             //NSLog(@"adding mods: %d", mods);
1918             modChars[0] = CSI;
1919             modChars[1] = KS_MODIFIER;
1920             modChars[2] = mods;
1921             add_to_input_buf(modChars, 3);
1922         }
1924         //NSLog(@"add to input buf: 0x%x", chars[0]);
1925         // TODO: Check for CSI bytes?
1926         add_to_input_buf(chars, length);
1927     }
1929 #ifdef FEAT_MBYTE
1930     if (conv_str)
1931         vim_free(conv_str);
1932 #endif
1935 - (void)queueMessage:(int)msgid data:(NSData *)data
1937     //if (msgid != EnableMenuItemMsgID)
1938     //    NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1940     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1941     if (data)
1942         [outputQueue addObject:data];
1943     else
1944         [outputQueue addObject:[NSData data]];
1947 - (void)connectionDidDie:(NSNotification *)notification
1949     // If the main connection to MacVim is lost this means that MacVim was
1950     // either quit (by the user chosing Quit on the MacVim menu), or it has
1951     // crashed.  In the former case the flag 'isTerminating' is set and we then
1952     // quit cleanly; in the latter case we make sure the swap files are left
1953     // for recovery.
1954     //
1955     // NOTE: This is not called if a Vim controller invalidates its connection.
1957     //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1958     if (isTerminating)
1959         getout(0);
1960     else
1961         getout_preserve_modified(1);
1964 - (void)blinkTimerFired:(NSTimer *)timer
1966     NSTimeInterval timeInterval = 0;
1968     [blinkTimer release];
1969     blinkTimer = nil;
1971     if (MMBlinkStateOn == blinkState) {
1972         gui_undraw_cursor();
1973         blinkState = MMBlinkStateOff;
1974         timeInterval = blinkOffInterval;
1975     } else if (MMBlinkStateOff == blinkState) {
1976         gui_update_cursor(TRUE, FALSE);
1977         blinkState = MMBlinkStateOn;
1978         timeInterval = blinkOnInterval;
1979     }
1981     if (timeInterval > 0) {
1982         blinkTimer = 
1983             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1984                                             selector:@selector(blinkTimerFired:)
1985                                             userInfo:nil repeats:NO] retain];
1986         [self flushQueue:YES];
1987     }
1990 - (void)focusChange:(BOOL)on
1992     gui_focus_change(on);
1995 - (void)handleToggleToolbar
1997     // If 'go' contains 'T', then remove it, else add it.
1999     char_u go[sizeof(GO_ALL)+2];
2000     char_u *p;
2001     int len;
2003     STRCPY(go, p_go);
2004     p = vim_strchr(go, GO_TOOLBAR);
2005     len = STRLEN(go);
2007     if (p != NULL) {
2008         char_u *end = go + len;
2009         while (p < end) {
2010             p[0] = p[1];
2011             ++p;
2012         }
2013     } else {
2014         go[len] = GO_TOOLBAR;
2015         go[len+1] = NUL;
2016     }
2018     set_option_value((char_u*)"guioptions", 0, go, 0);
2020     // Force screen redraw (does it have to be this complicated?).
2021     redraw_all_later(CLEAR);
2022     update_screen(NOT_VALID);
2023     setcursor();
2024     out_flush();
2025     gui_update_cursor(FALSE, FALSE);
2026     gui_mch_flush();
2029 - (void)handleScrollbarEvent:(NSData *)data
2031     if (!data) return;
2033     const void *bytes = [data bytes];
2034     long ident = *((long*)bytes);  bytes += sizeof(long);
2035     int hitPart = *((int*)bytes);  bytes += sizeof(int);
2036     float fval = *((float*)bytes);  bytes += sizeof(float);
2037     scrollbar_T *sb = gui_find_scrollbar(ident);
2039     if (sb) {
2040         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2041         long value = sb_info->value;
2042         long size = sb_info->size;
2043         long max = sb_info->max;
2044         BOOL isStillDragging = NO;
2045         BOOL updateKnob = YES;
2047         switch (hitPart) {
2048         case NSScrollerDecrementPage:
2049             value -= (size > 2 ? size - 2 : 1);
2050             break;
2051         case NSScrollerIncrementPage:
2052             value += (size > 2 ? size - 2 : 1);
2053             break;
2054         case NSScrollerDecrementLine:
2055             --value;
2056             break;
2057         case NSScrollerIncrementLine:
2058             ++value;
2059             break;
2060         case NSScrollerKnob:
2061             isStillDragging = YES;
2062             // fall through ...
2063         case NSScrollerKnobSlot:
2064             value = (long)(fval * (max - size + 1));
2065             // fall through ...
2066         default:
2067             updateKnob = NO;
2068             break;
2069         }
2071         //NSLog(@"value %d -> %d", sb_info->value, value);
2072         gui_drag_scrollbar(sb, value, isStillDragging);
2074         if (updateKnob) {
2075             // Dragging the knob or option+clicking automatically updates
2076             // the knob position (on the actual NSScroller), so we only
2077             // need to set the knob position in the other cases.
2078             if (sb->wp) {
2079                 // Update both the left&right vertical scrollbars.
2080                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2081                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2082                 [self setScrollbarThumbValue:value size:size max:max
2083                                   identifier:identLeft];
2084                 [self setScrollbarThumbValue:value size:size max:max
2085                                   identifier:identRight];
2086             } else {
2087                 // Update the horizontal scrollbar.
2088                 [self setScrollbarThumbValue:value size:size max:max
2089                                   identifier:ident];
2090             }
2091         }
2092     }
2095 - (void)handleSetFont:(NSData *)data
2097     if (!data) return;
2099     const void *bytes = [data bytes];
2100     float pointSize = *((float*)bytes);  bytes += sizeof(float);
2101     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2102     bytes += sizeof(unsigned);  // len not used
2104     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2105     [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
2106     char_u *s = (char_u*)[name UTF8String];
2108 #ifdef FEAT_MBYTE
2109     s = CONVERT_FROM_UTF8(s);
2110 #endif
2112     set_option_value((char_u*)"guifont", 0, s, 0);
2114 #ifdef FEAT_MBYTE
2115     CONVERT_FROM_UTF8_FREE(s);
2116 #endif
2118     // Force screen redraw (does it have to be this complicated?).
2119     redraw_all_later(CLEAR);
2120     update_screen(NOT_VALID);
2121     setcursor();
2122     out_flush();
2123     gui_update_cursor(FALSE, FALSE);
2124     gui_mch_flush();
2127 - (void)handleDropFiles:(NSData *)data
2129     // TODO: Get rid of this method; instead use Vim script directly.  At the
2130     // moment I know how to do this to open files in tabs, but I'm not sure how
2131     // to add the filenames to the command line when in command line mode.
2133     if (!data) return;
2135     NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2136     if (!args) return;
2138     id obj = [args objectForKey:@"forceOpen"];
2139     BOOL forceOpen = YES;
2140     if (obj)
2141         forceOpen = [obj boolValue];
2143     NSArray *filenames = [args objectForKey:@"filenames"];
2144     if (!(filenames && [filenames count] > 0)) return;
2146 #ifdef FEAT_DND
2147     if (!forceOpen && (State & CMDLINE)) {
2148         // HACK!  If Vim is in command line mode then the files names
2149         // should be added to the command line, instead of opening the
2150         // files in tabs (unless forceOpen is set).  This is taken care of by
2151         // gui_handle_drop().
2152         int n = [filenames count];
2153         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2154         if (fnames) {
2155             int i = 0;
2156             for (i = 0; i < n; ++i)
2157                 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2159             // NOTE!  This function will free 'fnames'.
2160             // HACK!  It is assumed that the 'x' and 'y' arguments are
2161             // unused when in command line mode.
2162             gui_handle_drop(0, 0, 0, fnames, n);
2163         }
2164     } else
2165 #endif // FEAT_DND
2166     {
2167         [self handleOpenWithArguments:args];
2168     }
2171 - (void)handleDropString:(NSData *)data
2173     if (!data) return;
2175 #ifdef FEAT_DND
2176     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2177     const void *bytes = [data bytes];
2178     int len = *((int*)bytes);  bytes += sizeof(int);
2179     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2181     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2182     NSRange range = { 0, [string length] };
2183     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2184                                          withString:@"\x0a" options:0
2185                                               range:range];
2186     if (0 == n) {
2187         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2188                                        options:0 range:range];
2189     }
2191     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2192     char_u *s = (char_u*)[string UTF8String];
2193 #ifdef FEAT_MBYTE
2194     if (input_conv.vc_type != CONV_NONE)
2195         s = string_convert(&input_conv, s, &len);
2196 #endif
2197     dnd_yank_drag_data(s, len);
2198 #ifdef FEAT_MBYTE
2199     if (input_conv.vc_type != CONV_NONE)
2200         vim_free(s);
2201 #endif
2202     add_to_input_buf(dropkey, sizeof(dropkey));
2203 #endif // FEAT_DND
2206 - (void)startOdbEditWithArguments:(NSDictionary *)args
2208 #ifdef FEAT_ODB_EDITOR
2209     id obj = [args objectForKey:@"remoteID"];
2210     if (!obj) return;
2212     OSType serverID = [obj unsignedIntValue];
2213     NSString *remotePath = [args objectForKey:@"remotePath"];
2215     NSAppleEventDescriptor *token = nil;
2216     NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2217     obj = [args objectForKey:@"remoteTokenDescType"];
2218     if (tokenData && obj) {
2219         DescType tokenType = [obj unsignedLongValue];
2220         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2221                                                                 data:tokenData];
2222     }
2224     NSArray *filenames = [args objectForKey:@"filenames"];
2225     unsigned i, numFiles = [filenames count];
2226     for (i = 0; i < numFiles; ++i) {
2227         NSString *filename = [filenames objectAtIndex:i];
2228         char_u *s = [filename vimStringSave];
2229         buf_T *buf = buflist_findname(s);
2230         vim_free(s);
2232         if (buf) {
2233             if (buf->b_odb_token) {
2234                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2235                 buf->b_odb_token = NULL;
2236             }
2238             if (buf->b_odb_fname) {
2239                 vim_free(buf->b_odb_fname);
2240                 buf->b_odb_fname = NULL;
2241             }
2243             buf->b_odb_server_id = serverID;
2245             if (token)
2246                 buf->b_odb_token = [token retain];
2247             if (remotePath)
2248                 buf->b_odb_fname = [remotePath vimStringSave];
2249         } else {
2250             NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2251                     filename);
2252         }
2253     }
2254 #endif // FEAT_ODB_EDITOR
2257 - (void)handleXcodeMod:(NSData *)data
2259 #if 0
2260     const void *bytes = [data bytes];
2261     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2262     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2263     if (0 == len)
2264         return;
2266     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2267             descriptorWithDescriptorType:type
2268                                    bytes:bytes
2269                                   length:len];
2270 #endif
2273 - (void)handleOpenWithArguments:(NSDictionary *)args
2275     // ARGUMENT:                DESCRIPTION:
2276     // -------------------------------------------------------------
2277     // filenames                list of filenames
2278     // dontOpen                 don't open files specified in above argument
2279     // layout                   which layout to use to open files
2280     // selectionRange           range of lines to select
2281     // searchText               string to search for
2282     // cursorLine               line to position the cursor on
2283     // cursorColumn             column to position the cursor on
2284     //                          (only valid when "cursorLine" is set)
2285     // remoteID                 ODB parameter
2286     // remotePath               ODB parameter
2287     // remoteTokenDescType      ODB parameter
2288     // remoteTokenData          ODB parameter
2290     //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2292     NSArray *filenames = [args objectForKey:@"filenames"];
2293     int i, numFiles = filenames ? [filenames count] : 0;
2294     BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2295     int layout = [[args objectForKey:@"layout"] intValue];
2297     // Change to directory of first file to open if this is an "unused" editor
2298     // (but do not do this if editing remotely).
2299     if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2300             && (starting || [self unusedEditor]) ) {
2301         char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2302         vim_chdirfile(s);
2303         vim_free(s);
2304     }
2306     if (starting > 0) {
2307         // When Vim is starting we simply add the files to be opened to the
2308         // global arglist and Vim will take care of opening them for us.
2309         if (openFiles && numFiles > 0) {
2310             for (i = 0; i < numFiles; i++) {
2311                 NSString *fname = [filenames objectAtIndex:i];
2312                 char_u *p = NULL;
2314                 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2315                         || (p = [fname vimStringSave]) == NULL)
2316                     exit(2); // See comment in -[MMBackend exit]
2317                 else
2318                     alist_add(&global_alist, p, 2);
2319             }
2321             // Vim will take care of arranging the files added to the arglist
2322             // in windows or tabs; all we must do is to specify which layout to
2323             // use.
2324             initialWindowLayout = layout;
2325         }
2326     } else {
2327         // When Vim is already open we resort to some trickery to open the
2328         // files with the specified layout.
2329         //
2330         // TODO: Figure out a better way to handle this?
2331         if (openFiles && numFiles > 0) {
2332             BOOL oneWindowInTab = topframe ? YES
2333                                            : (topframe->fr_layout == FR_LEAF);
2334             BOOL bufChanged = NO;
2335             BOOL bufHasFilename = NO;
2336             if (curbuf) {
2337                 bufChanged = curbufIsChanged();
2338                 bufHasFilename = curbuf->b_ffname != NULL;
2339             }
2341             // Temporarily disable flushing since the following code may
2342             // potentially cause multiple redraws.
2343             flushDisabled = YES;
2345             BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2346             if (WIN_TABS == layout && !onlyOneTab) {
2347                 // By going to the last tabpage we ensure that the new tabs
2348                 // will appear last (if this call is left out, the taborder
2349                 // becomes messy).
2350                 goto_tabpage(9999);
2351             }
2353             // Make sure we're in normal mode first.
2354             [self addInput:@"<C-\\><C-N>"];
2356             if (numFiles > 1) {
2357                 // With "split layout" we open a new tab before opening
2358                 // multiple files if the current tab has more than one window
2359                 // or if there is exactly one window but whose buffer has a
2360                 // filename.  (The :drop command ensures modified buffers get
2361                 // their own window.)
2362                 if ((WIN_HOR == layout || WIN_VER == layout) &&
2363                         (!oneWindowInTab || bufHasFilename))
2364                     [self addInput:@":tabnew<CR>"];
2366                 // The files are opened by constructing a ":drop ..." command
2367                 // and executing it.
2368                 NSMutableString *cmd = (WIN_TABS == layout)
2369                         ? [NSMutableString stringWithString:@":tab drop"]
2370                         : [NSMutableString stringWithString:@":drop"];
2372                 for (i = 0; i < numFiles; ++i) {
2373                     NSString *file = [filenames objectAtIndex:i];
2374                     file = [file stringByEscapingSpecialFilenameCharacters];
2375                     [cmd appendString:@" "];
2376                     [cmd appendString:file];
2377                 }
2379                 [self addInput:cmd];
2381                 // Split the view into multiple windows if requested.
2382                 if (WIN_HOR == layout)
2383                     [self addInput:@"|sall"];
2384                 else if (WIN_VER == layout)
2385                     [self addInput:@"|vert sall"];
2387                 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2388                 [self addInput:@"|redr|f<CR>"];
2389             } else {
2390                 // When opening one file we try to reuse the current window,
2391                 // but not if its buffer is modified or has a filename.
2392                 // However, the 'arglist' layout always opens the file in the
2393                 // current window.
2394                 NSString *file = [[filenames lastObject]
2395                         stringByEscapingSpecialFilenameCharacters];
2396                 NSString *cmd;
2397                 if (WIN_HOR == layout) {
2398                     if (!(bufHasFilename || bufChanged))
2399                         cmd = [NSString stringWithFormat:@":e %@", file];
2400                     else
2401                         cmd = [NSString stringWithFormat:@":sp %@", file];
2402                 } else if (WIN_VER == layout) {
2403                     if (!(bufHasFilename || bufChanged))
2404                         cmd = [NSString stringWithFormat:@":e %@", file];
2405                     else
2406                         cmd = [NSString stringWithFormat:@":vsp %@", file];
2407                 } else if (WIN_TABS == layout) {
2408                     if (oneWindowInTab && !(bufHasFilename || bufChanged))
2409                         cmd = [NSString stringWithFormat:@":e %@", file];
2410                     else
2411                         cmd = [NSString stringWithFormat:@":tabe %@", file];
2412                 } else {
2413                     // (The :drop command will split if there is a modified
2414                     // buffer.)
2415                     cmd = [NSString stringWithFormat:@":drop %@", file];
2416                 }
2418                 [self addInput:cmd];
2420                 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2421                 [self addInput:@"|redr|f<CR>"];
2422             }
2424             // Force screen redraw (does it have to be this complicated?).
2425             // (This code was taken from the end of gui_handle_drop().)
2426             update_screen(NOT_VALID);
2427             setcursor();
2428             out_flush();
2429             gui_update_cursor(FALSE, FALSE);
2430             maketitle();
2432             flushDisabled = NO;
2433             gui_mch_flush();
2434         }
2435     }
2437     if ([args objectForKey:@"remoteID"]) {
2438         // NOTE: We have to delay processing any ODB related arguments since
2439         // the file(s) may not be opened until the input buffer is processed.
2440         [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2441                                withObject:args
2442                             waitUntilDone:NO];
2443     }
2445     NSString *lineString = [args objectForKey:@"cursorLine"];
2446     if (lineString && [lineString intValue] > 0) {
2447         NSString *columnString = [args objectForKey:@"cursorColumn"];
2448         if (!(columnString && [columnString intValue] > 0))
2449             columnString = @"1";
2451         NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2452                 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2453         [self addInput:cmd];
2454     }
2456     NSString *rangeString = [args objectForKey:@"selectionRange"];
2457     if (rangeString) {
2458         // Build a command line string that will select the given range of
2459         // lines.  If range.length == 0, then position the cursor on the given
2460         // line but do not select.
2461         NSRange range = NSRangeFromString(rangeString);
2462         NSString *cmd;
2463         if (range.length > 0) {
2464             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2465                     NSMaxRange(range), range.location];
2466         } else {
2467             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2468                     range.location];
2469         }
2471         [self addInput:cmd];
2472     }
2474     NSString *searchText = [args objectForKey:@"searchText"];
2475     if (searchText) {
2476         // TODO: Searching is an exclusive motion, so if the pattern would
2477         // match on row 0 column 0 then this pattern will miss that match.
2478         [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@<CR>",
2479                 searchText]];
2480     }
2483 - (BOOL)checkForModifiedBuffers
2485     buf_T *buf;
2486     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2487         if (bufIsChanged(buf)) {
2488             return YES;
2489         }
2490     }
2492     return NO;
2495 - (void)addInput:(NSString *)input
2497     char_u *s = (char_u*)[input UTF8String];
2499 #ifdef FEAT_MBYTE
2500     s = CONVERT_FROM_UTF8(s);
2501 #endif
2503     server_to_input_buf(s);
2505 #ifdef FEAT_MBYTE
2506     CONVERT_FROM_UTF8_FREE(s);
2507 #endif
2510 - (BOOL)unusedEditor
2512     BOOL oneWindowInTab = topframe ? YES
2513                                    : (topframe->fr_layout == FR_LEAF);
2514     BOOL bufChanged = NO;
2515     BOOL bufHasFilename = NO;
2516     if (curbuf) {
2517         bufChanged = curbufIsChanged();
2518         bufHasFilename = curbuf->b_ffname != NULL;
2519     }
2521     BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2523     return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2526 @end // MMBackend (Private)
2531 @implementation MMBackend (ClientServer)
2533 - (NSString *)connectionNameFromServerName:(NSString *)name
2535     NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2537     return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2538         lowercaseString];
2541 - (NSConnection *)connectionForServerName:(NSString *)name
2543     // TODO: Try 'name%d' if 'name' fails.
2544     NSString *connName = [self connectionNameFromServerName:name];
2545     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2547     if (!svrConn) {
2548         svrConn = [NSConnection connectionWithRegisteredName:connName
2549                                                            host:nil];
2550         // Try alternate server...
2551         if (!svrConn && alternateServerName) {
2552             //NSLog(@"  trying to connect to alternate server: %@",
2553             //        alternateServerName);
2554             connName = [self connectionNameFromServerName:alternateServerName];
2555             svrConn = [NSConnection connectionWithRegisteredName:connName
2556                                                             host:nil];
2557         }
2559         // Try looking for alternate servers...
2560         if (!svrConn) {
2561             //NSLog(@"  looking for alternate servers...");
2562             NSString *alt = [self alternateServerNameForName:name];
2563             if (alt != alternateServerName) {
2564                 //NSLog(@"  found alternate server: %@", string);
2565                 [alternateServerName release];
2566                 alternateServerName = [alt copy];
2567             }
2568         }
2570         // Try alternate server again...
2571         if (!svrConn && alternateServerName) {
2572             //NSLog(@"  trying to connect to alternate server: %@",
2573             //        alternateServerName);
2574             connName = [self connectionNameFromServerName:alternateServerName];
2575             svrConn = [NSConnection connectionWithRegisteredName:connName
2576                                                             host:nil];
2577         }
2579         if (svrConn) {
2580             [connectionNameDict setObject:svrConn forKey:connName];
2582             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2583             [[NSNotificationCenter defaultCenter] addObserver:self
2584                     selector:@selector(serverConnectionDidDie:)
2585                         name:NSConnectionDidDieNotification object:svrConn];
2586         }
2587     }
2589     return svrConn;
2592 - (NSConnection *)connectionForServerPort:(int)port
2594     NSConnection *conn;
2595     NSEnumerator *e = [connectionNameDict objectEnumerator];
2597     while ((conn = [e nextObject])) {
2598         // HACK! Assume connection uses mach ports.
2599         if (port == [(NSMachPort*)[conn sendPort] machPort])
2600             return conn;
2601     }
2603     return nil;
2606 - (void)serverConnectionDidDie:(NSNotification *)notification
2608     //NSLog(@"%s%@", _cmd, notification);
2610     NSConnection *svrConn = [notification object];
2612     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2613     [[NSNotificationCenter defaultCenter]
2614             removeObserver:self
2615                       name:NSConnectionDidDieNotification
2616                     object:svrConn];
2618     [connectionNameDict removeObjectsForKeys:
2619         [connectionNameDict allKeysForObject:svrConn]];
2621     // HACK! Assume connection uses mach ports.
2622     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2623     NSNumber *key = [NSNumber numberWithInt:port];
2625     [clientProxyDict removeObjectForKey:key];
2626     [serverReplyDict removeObjectForKey:key];
2629 - (void)addClient:(NSDistantObject *)client
2631     NSConnection *conn = [client connectionForProxy];
2632     // HACK! Assume connection uses mach ports.
2633     int port = [(NSMachPort*)[conn sendPort] machPort];
2634     NSNumber *key = [NSNumber numberWithInt:port];
2636     if (![clientProxyDict objectForKey:key]) {
2637         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2638         [clientProxyDict setObject:client forKey:key];
2639     }
2641     // NOTE: 'clientWindow' is a global variable which is used by <client>
2642     clientWindow = port;
2645 - (NSString *)alternateServerNameForName:(NSString *)name
2647     if (!(name && [name length] > 0))
2648         return nil;
2650     // Only look for alternates if 'name' doesn't end in a digit.
2651     unichar lastChar = [name characterAtIndex:[name length]-1];
2652     if (lastChar >= '0' && lastChar <= '9')
2653         return nil;
2655     // Look for alternates among all current servers.
2656     NSArray *list = [self serverList];
2657     if (!(list && [list count] > 0))
2658         return nil;
2660     // Filter out servers starting with 'name' and ending with a number. The
2661     // (?i) pattern ensures that the match is case insensitive.
2662     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2663     NSPredicate *pred = [NSPredicate predicateWithFormat:
2664             @"SELF MATCHES %@", pat];
2665     list = [list filteredArrayUsingPredicate:pred];
2666     if ([list count] > 0) {
2667         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2668         return [list objectAtIndex:0];
2669     }
2671     return nil;
2674 @end // MMBackend (ClientServer)
2679 @implementation NSString (MMServerNameCompare)
2680 - (NSComparisonResult)serverNameCompare:(NSString *)string
2682     return [self compare:string
2683                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2685 @end
2690 static int eventModifierFlagsToVimModMask(int modifierFlags)
2692     int modMask = 0;
2694     if (modifierFlags & NSShiftKeyMask)
2695         modMask |= MOD_MASK_SHIFT;
2696     if (modifierFlags & NSControlKeyMask)
2697         modMask |= MOD_MASK_CTRL;
2698     if (modifierFlags & NSAlternateKeyMask)
2699         modMask |= MOD_MASK_ALT;
2700     if (modifierFlags & NSCommandKeyMask)
2701         modMask |= MOD_MASK_CMD;
2703     return modMask;
2706 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2708     int modMask = 0;
2710     if (modifierFlags & NSShiftKeyMask)
2711         modMask |= MOUSE_SHIFT;
2712     if (modifierFlags & NSControlKeyMask)
2713         modMask |= MOUSE_CTRL;
2714     if (modifierFlags & NSAlternateKeyMask)
2715         modMask |= MOUSE_ALT;
2717     return modMask;
2720 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2722     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2724     return (buttonNumber >= 0 && buttonNumber < 3)
2725             ? mouseButton[buttonNumber] : -1;
2730 // This function is modeled after the VimToPython function found in if_python.c
2731 // NB This does a deep copy by value, it does not lookup references like the
2732 // VimToPython function does.  This is because I didn't want to deal with the
2733 // retain cycles that this would create, and we can cover 99% of the use cases
2734 // by ignoring it.  If we ever switch to using GC in MacVim then this
2735 // functionality can be implemented easily.
2736 static id vimToCocoa(typval_T * tv, int depth)
2738     id result = nil;
2739     id newObj = nil;
2742     // Avoid infinite recursion
2743     if (depth > 100) {
2744         return nil;
2745     }
2747     if (tv->v_type == VAR_STRING) {
2748         char_u * val = tv->vval.v_string;
2749         // val can be NULL if the string is empty
2750         if (!val) {
2751             result = [NSString string];
2752         } else {
2753 #ifdef FEAT_MBYTE
2754             val = CONVERT_TO_UTF8(val);
2755 #endif
2756             result = [NSString stringWithUTF8String:(char*)val];
2757 #ifdef FEAT_MBYTE
2758             CONVERT_TO_UTF8_FREE(val);
2759 #endif
2760         }
2761     } else if (tv->v_type == VAR_NUMBER) {
2762         // looks like sizeof(varnumber_T) is always <= sizeof(long)
2763         result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2764     } else if (tv->v_type == VAR_LIST) {
2765         list_T * list = tv->vval.v_list;
2766         listitem_T * curr;
2768         NSMutableArray * arr = result = [NSMutableArray array];
2770         if (list != NULL) {
2771             for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2772                 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2773                 [arr addObject:newObj];
2774             }
2775         }
2776     } else if (tv->v_type == VAR_DICT) {
2777         NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2779         if (tv->vval.v_dict != NULL) {
2780             hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2781             int todo = ht->ht_used;
2782             hashitem_T * hi;
2783             dictitem_T * di;
2785             for (hi = ht->ht_array; todo > 0; ++hi) {
2786                 if (!HASHITEM_EMPTY(hi)) {
2787                     --todo;
2789                     di = dict_lookup(hi);
2790                     newObj = vimToCocoa(&di->di_tv, depth + 1);
2792                     char_u * keyval = hi->hi_key;
2793 #ifdef FEAT_MBYTE
2794                     keyval = CONVERT_TO_UTF8(keyval);
2795 #endif
2796                     NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2797 #ifdef FEAT_MBYTE
2798                     CONVERT_TO_UTF8_FREE(keyval);
2799 #endif
2800                     [dict setObject:newObj forKey:key];
2801                 }
2802             }
2803         }
2804     } else { // only func refs should fall into this category?
2805         result = nil;
2806     }
2808     return result;
2812 // This function is modeled after eval_client_expr_to_string found in main.c
2813 // Returns nil if there was an error evaluating the expression, and writes a
2814 // message to errorStr.
2815 // TODO Get the error that occurred while evaluating the expression in vim
2816 // somehow.
2817 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2820     char_u *s = (char_u*)[expr UTF8String];
2822 #ifdef FEAT_MBYTE
2823     s = CONVERT_FROM_UTF8(s);
2824 #endif
2826     int save_dbl = debug_break_level;
2827     int save_ro = redir_off;
2829     debug_break_level = -1;
2830     redir_off = 0;
2831     ++emsg_skip;
2833     typval_T * tvres = eval_expr(s, NULL);
2835     debug_break_level = save_dbl;
2836     redir_off = save_ro;
2837     --emsg_skip;
2839     setcursor();
2840     out_flush();
2842 #ifdef FEAT_MBYTE
2843     CONVERT_FROM_UTF8_FREE(s);
2844 #endif
2846 #ifdef FEAT_GUI
2847     if (gui.in_use)
2848         gui_update_cursor(FALSE, FALSE);
2849 #endif
2851     if (tvres == NULL) {
2852         free_tv(tvres);
2853         *errstr = @"Expression evaluation failed.";
2854     }
2856     id res = vimToCocoa(tvres, 1);
2858     free_tv(tvres);
2860     if (res == nil) {
2861         *errstr = @"Conversion to cocoa values failed.";
2862     }
2864     return res;
2870 @implementation NSString (VimStrings)
2872 + (id)stringWithVimString:(char_u *)s
2874     // This method ensures a non-nil string is returned.  If 's' cannot be
2875     // converted to a utf-8 string it is assumed to be latin-1.  If conversion
2876     // still fails an empty NSString is returned.
2877     NSString *string = nil;
2878     if (s) {
2879 #ifdef FEAT_MBYTE
2880         s = CONVERT_TO_UTF8(s);
2881 #endif
2882         string = [NSString stringWithUTF8String:(char*)s];
2883         if (!string) {
2884             // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2885             // latin-1?
2886             string = [NSString stringWithCString:(char*)s
2887                                         encoding:NSISOLatin1StringEncoding];
2888         }
2889 #ifdef FEAT_MBYTE
2890         CONVERT_TO_UTF8_FREE(s);
2891 #endif
2892     }
2894     return string != nil ? string : [NSString string];
2897 - (char_u *)vimStringSave
2899     char_u *s = (char_u*)[self UTF8String], *ret = NULL;
2901 #ifdef FEAT_MBYTE
2902     s = CONVERT_FROM_UTF8(s);
2903 #endif
2904     ret = vim_strsave(s);
2905 #ifdef FEAT_MBYTE
2906     CONVERT_FROM_UTF8_FREE(s);
2907 #endif
2909     return ret;
2912 @end // NSString (VimStrings)