More file opening options (plus quickstart feature)
[MacVim.git] / src / MacVim / MMBackend.m
blob4418bbf2e63270002208d004ad38d73323c4eedb
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);
62 // In gui_macvim.m
63 vimmenu_T *menu_for_descriptor(NSArray *desc);
65 static id evalExprCocoa(NSString * expr, NSString ** errstr);
67 enum {
68     MMBlinkStateNone = 0,
69     MMBlinkStateOn,
70     MMBlinkStateOff
73 static NSString *MMSymlinkWarningString =
74     @"\n\n\tMost likely this is because you have symlinked directly to\n"
75      "\tthe Vim binary, which Cocoa does not allow.  Please use an\n"
76      "\talias or the mvim shell script instead.  If you have not used\n"
77      "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
81 @interface NSString (MMServerNameCompare)
82 - (NSComparisonResult)serverNameCompare:(NSString *)string;
83 @end
88 @interface MMBackend (Private)
89 - (void)waitForDialogReturn;
90 - (void)queueVimStateMessage;
91 - (void)processInputQueue;
92 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
93 + (NSDictionary *)specialKeys;
94 - (void)handleInsertText:(NSData *)data;
95 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
96 - (void)queueMessage:(int)msgid data:(NSData *)data;
97 - (void)connectionDidDie:(NSNotification *)notification;
98 - (void)blinkTimerFired:(NSTimer *)timer;
99 - (void)focusChange:(BOOL)on;
100 - (void)handleToggleToolbar;
101 - (void)handleScrollbarEvent:(NSData *)data;
102 - (void)handleSetFont:(NSData *)data;
103 - (void)handleDropFiles:(NSData *)data;
104 - (void)handleDropString:(NSData *)data;
105 - (void)startOdbEditWithArguments:(NSDictionary *)args;
106 - (void)handleXcodeMod:(NSData *)data;
107 - (void)handleOpenWithArguments:(NSDictionary *)args;
108 - (BOOL)checkForModifiedBuffers;
109 - (void)addInput:(NSString *)input;
110 @end
114 @interface MMBackend (ClientServer)
115 - (NSString *)connectionNameFromServerName:(NSString *)name;
116 - (NSConnection *)connectionForServerName:(NSString *)name;
117 - (NSConnection *)connectionForServerPort:(int)port;
118 - (void)serverConnectionDidDie:(NSNotification *)notification;
119 - (void)addClient:(NSDistantObject *)client;
120 - (NSString *)alternateServerNameForName:(NSString *)name;
121 @end
125 @implementation MMBackend
127 + (MMBackend *)sharedInstance
129     static MMBackend *singleton = nil;
130     return singleton ? singleton : (singleton = [MMBackend new]);
133 - (id)init
135     self = [super init];
136     if (!self) return nil;
138     fontContainerRef = loadFonts();
140     outputQueue = [[NSMutableArray alloc] init];
141     inputQueue = [[NSMutableArray alloc] init];
142     drawData = [[NSMutableData alloc] initWithCapacity:1024];
143     connectionNameDict = [[NSMutableDictionary alloc] init];
144     clientProxyDict = [[NSMutableDictionary alloc] init];
145     serverReplyDict = [[NSMutableDictionary alloc] init];
147     NSBundle *mainBundle = [NSBundle mainBundle];
148     NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
149     if (path)
150         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
152     path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
153     if (path)
154         sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
155             retain];
157     path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
158     if (path)
159         actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
161     if (!(colorDict && sysColorDict && actionDict))
162         NSLog(@"ERROR: Failed to load dictionaries.%@",
163                 MMSymlinkWarningString);
165     return self;
168 - (void)dealloc
170     //NSLog(@"%@ %s", [self className], _cmd);
171     [[NSNotificationCenter defaultCenter] removeObserver:self];
173     [oldWideFont release];  oldWideFont = nil;
174     [blinkTimer release];  blinkTimer = nil;
175     [alternateServerName release];  alternateServerName = nil;
176     [serverReplyDict release];  serverReplyDict = nil;
177     [clientProxyDict release];  clientProxyDict = nil;
178     [connectionNameDict release];  connectionNameDict = nil;
179     [inputQueue release];  inputQueue = nil;
180     [outputQueue release];  outputQueue = nil;
181     [drawData release];  drawData = nil;
182     [frontendProxy release];  frontendProxy = nil;
183     [connection release];  connection = nil;
184     [actionDict release];  actionDict = nil;
185     [sysColorDict release];  sysColorDict = nil;
186     [colorDict release];  colorDict = nil;
188     [super dealloc];
191 - (void)setBackgroundColor:(int)color
193     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
196 - (void)setForegroundColor:(int)color
198     foregroundColor = MM_COLOR(color);
201 - (void)setSpecialColor:(int)color
203     specialColor = MM_COLOR(color);
206 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
208     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
209     defaultForegroundColor = MM_COLOR(fg);
211     NSMutableData *data = [NSMutableData data];
213     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
214     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
216     [self queueMessage:SetDefaultColorsMsgID data:data];
219 - (NSConnection *)connection
221     if (!connection) {
222         // NOTE!  If the name of the connection changes here it must also be
223         // updated in MMAppController.m.
224         NSString *name = [NSString stringWithFormat:@"%@-connection",
225                [[NSBundle mainBundle] bundleIdentifier]];
227         connection = [NSConnection connectionWithRegisteredName:name host:nil];
228         [connection retain];
229     }
231     // NOTE: 'connection' may be nil here.
232     return connection;
235 - (NSDictionary *)actionDict
237     return actionDict;
240 - (int)initialWindowLayout
242     return initialWindowLayout;
245 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
247     [self queueMessage:msgid data:[props dictionaryAsData]];
250 - (BOOL)checkin
252     if (![self connection]) {
253         if (waitForAck) {
254             // This is a preloaded process and as such should not cause the
255             // MacVim to be opened.  We probably got here as a result of the
256             // user quitting MacVim while the process was preloading, so exit
257             // this process too.
258             mch_exit(0);
259         }
261         NSBundle *mainBundle = [NSBundle mainBundle];
262 #if 0
263         OSStatus status;
264         FSRef ref;
266         // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
267         // the API to pass Apple Event parameters is broken on 10.4).
268         NSString *path = [mainBundle bundlePath];
269         status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
270         if (noErr == status) {
271             // Pass parameter to the 'Open' Apple Event that tells MacVim not
272             // to open an untitled window.
273             NSAppleEventDescriptor *desc =
274                     [NSAppleEventDescriptor recordDescriptor];
275             [desc setParamDescriptor:
276                     [NSAppleEventDescriptor descriptorWithBoolean:NO]
277                           forKeyword:keyMMUntitledWindow];
279             LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
280                     kLSLaunchDefaults, NULL };
281             status = LSOpenFromRefSpec(&spec, NULL);
282         }
284         if (noErr != status) {
285         NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
286                 path, MMSymlinkWarningString);
287             return NO;
288         }
289 #else
290         // Launch MacVim using NSTask.  For some reason the above code using
291         // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
292         // fails, the dock icon starts bouncing and never stops).  It seems
293         // like rebuilding the Launch Services database takes care of this
294         // problem, but the NSTask way seems more stable so stick with it.
295         //
296         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
297         // that the GUI won't be activated (or raised) so there is a hack in
298         // MMAppController which raises the app when a new window is opened.
299         NSMutableArray *args = [NSMutableArray arrayWithObjects:
300             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
301         NSString *exeName = [[mainBundle infoDictionary]
302                 objectForKey:@"CFBundleExecutable"];
303         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
304         if (!path) {
305             NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
306                     MMSymlinkWarningString);
307             return NO;
308         }
310         [NSTask launchedTaskWithLaunchPath:path arguments:args];
311 #endif
313         // HACK!  Poll the mach bootstrap server until it returns a valid
314         // connection to detect that MacVim has finished launching.  Also set a
315         // time-out date so that we don't get stuck doing this forever.
316         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
317         while (![self connection] &&
318                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
319             [[NSRunLoop currentRunLoop]
320                     runMode:NSDefaultRunLoopMode
321                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
323         // NOTE: [self connection] will set 'connection' as a side-effect.
324         if (!connection) {
325             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
326             return NO;
327         }
328     }
330     BOOL ok = NO;
331     @try {
332         [[NSNotificationCenter defaultCenter] addObserver:self
333                 selector:@selector(connectionDidDie:)
334                     name:NSConnectionDidDieNotification object:connection];
336         id proxy = [connection rootProxy];
337         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
339         int pid = [[NSProcessInfo processInfo] processIdentifier];
341         frontendProxy = [proxy connectBackend:self pid:pid];
342         if (frontendProxy) {
343             [frontendProxy retain];
344             [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
345             ok = YES;
346         }
347     }
348     @catch (NSException *e) {
349         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
350     }
352     return ok;
355 - (BOOL)openVimWindow
357     [self queueMessage:OpenVimWindowMsgID data:nil];
358     return YES;
361 - (void)clearAll
363     int type = ClearAllDrawType;
365     // Any draw commands in queue are effectively obsolete since this clearAll
366     // will negate any effect they have, therefore we may as well clear the
367     // draw queue.
368     [drawData setLength:0];
370     [drawData appendBytes:&type length:sizeof(int)];
373 - (void)clearBlockFromRow:(int)row1 column:(int)col1
374                     toRow:(int)row2 column:(int)col2
376     int type = ClearBlockDrawType;
378     [drawData appendBytes:&type length:sizeof(int)];
380     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
381     [drawData appendBytes:&row1 length:sizeof(int)];
382     [drawData appendBytes:&col1 length:sizeof(int)];
383     [drawData appendBytes:&row2 length:sizeof(int)];
384     [drawData appendBytes:&col2 length:sizeof(int)];
387 - (void)deleteLinesFromRow:(int)row count:(int)count
388               scrollBottom:(int)bottom left:(int)left right:(int)right
390     int type = DeleteLinesDrawType;
392     [drawData appendBytes:&type length:sizeof(int)];
394     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
395     [drawData appendBytes:&row length:sizeof(int)];
396     [drawData appendBytes:&count length:sizeof(int)];
397     [drawData appendBytes:&bottom length:sizeof(int)];
398     [drawData appendBytes:&left length:sizeof(int)];
399     [drawData appendBytes:&right length:sizeof(int)];
402 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
403              cells:(int)cells flags:(int)flags
405     if (len <= 0 || cells <= 0) return;
407     int type = DrawStringDrawType;
409     [drawData appendBytes:&type length:sizeof(int)];
411     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
412     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
413     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
414     [drawData appendBytes:&row length:sizeof(int)];
415     [drawData appendBytes:&col length:sizeof(int)];
416     [drawData appendBytes:&cells length:sizeof(int)];
417     [drawData appendBytes:&flags length:sizeof(int)];
418     [drawData appendBytes:&len length:sizeof(int)];
419     [drawData appendBytes:s length:len];
422 - (void)insertLinesFromRow:(int)row count:(int)count
423               scrollBottom:(int)bottom left:(int)left right:(int)right
425     int type = InsertLinesDrawType;
427     [drawData appendBytes:&type length:sizeof(int)];
429     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
430     [drawData appendBytes:&row length:sizeof(int)];
431     [drawData appendBytes:&count length:sizeof(int)];
432     [drawData appendBytes:&bottom length:sizeof(int)];
433     [drawData appendBytes:&left length:sizeof(int)];
434     [drawData appendBytes:&right length:sizeof(int)];
437 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
438                fraction:(int)percent color:(int)color
440     int type = DrawCursorDrawType;
441     unsigned uc = MM_COLOR(color);
443     [drawData appendBytes:&type length:sizeof(int)];
445     [drawData appendBytes:&uc length:sizeof(unsigned)];
446     [drawData appendBytes:&row length:sizeof(int)];
447     [drawData appendBytes:&col length:sizeof(int)];
448     [drawData appendBytes:&shape length:sizeof(int)];
449     [drawData appendBytes:&percent length:sizeof(int)];
452 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
453                    numColumns:(int)nc invert:(int)invert
455     int type = DrawInvertedRectDrawType;
456     [drawData appendBytes:&type length:sizeof(int)];
458     [drawData appendBytes:&row length:sizeof(int)];
459     [drawData appendBytes:&col length:sizeof(int)];
460     [drawData appendBytes:&nr length:sizeof(int)];
461     [drawData appendBytes:&nc length:sizeof(int)];
462     [drawData appendBytes:&invert length:sizeof(int)];
465 - (void)update
467     // Tend to the run loop, returning immediately if there are no events
468     // waiting.
469     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
470                              beforeDate:[NSDate distantPast]];
472 #if 0
473     // Keyboard and mouse input is handled directly, other input is queued and
474     // processed here.  This call may enter a blocking loop.
475     if ([inputQueue count] > 0)
476         [self processInputQueue];
477 #endif
480 - (void)flushQueue:(BOOL)force
482     // NOTE: This variable allows for better control over when the queue is
483     // flushed.  It can be set to YES at the beginning of a sequence of calls
484     // that may potentially add items to the queue, and then restored back to
485     // NO.
486     if (flushDisabled) return;
488     // NOTE! This method gets called a lot; if we were to flush every time it
489     // got called MacVim would feel unresponsive.  So there is a time out which
490     // ensures that the queue isn't flushed too often.
491     if (!force && lastFlushDate
492             && -[lastFlushDate timeIntervalSinceNow] < MMFlushTimeoutInterval
493             && [drawData length] < MMFlushQueueLenHint)
494         return;
496     if ([drawData length] > 0) {
497         // HACK!  Detect changes to 'guifontwide'.
498         if (gui.wide_font != (GuiFont)oldWideFont) {
499             [oldWideFont release];
500             oldWideFont = [(NSFont*)gui.wide_font retain];
501             [self setWideFont:oldWideFont];
502         }
504         int type = SetCursorPosDrawType;
505         [drawData appendBytes:&type length:sizeof(type)];
506         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
507         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
509         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
510         [drawData setLength:0];
511     }
513     if ([outputQueue count] > 0 || force) {
514         // When 'force' is set we always update the Vim state to ensure that
515         // MacVim has a copy of the latest state (since 'force' is typically
516         // set just before Vim takes a nap whilst waiting for input).
517         [self queueVimStateMessage];
519         @try {
520             [frontendProxy processCommandQueue:outputQueue];
521         }
522         @catch (NSException *e) {
523             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
524         }
526         [outputQueue removeAllObjects];
528         [lastFlushDate release];
529         lastFlushDate = [[NSDate date] retain];
530     }
533 - (BOOL)waitForInput:(int)milliseconds
535     //NSLog(@"|ENTER| %s%d",  _cmd, milliseconds);
537     // Only start the run loop if the input queue is empty, otherwise process
538     // the input first so that the input on queue isn't delayed.
539     if ([inputQueue count]) {
540         inputReceived = YES;
541     } else {
542         NSDate *date = milliseconds > 0 ?
543                 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
544                 [NSDate distantFuture];
546         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
547                                  beforeDate:date];
548     }
550     // I know of no way to figure out if the run loop exited because input was
551     // found or because of a time out, so I need to manually indicate when
552     // input was received in processInput:data: and then reset it every time
553     // here.
554     BOOL yn = inputReceived;
555     inputReceived = NO;
557     // Keyboard and mouse input is handled directly, other input is queued and
558     // processed here.  This call may enter a blocking loop.
559     if ([inputQueue count] > 0)
560         [self processInputQueue];
562     //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
563     return yn;
566 - (void)exit
568     // To notify MacVim that this Vim process is exiting we could simply
569     // invalidate the connection and it would automatically receive a
570     // connectionDidDie: notification.  However, this notification seems to
571     // take up to 300 ms to arrive which is quite a noticeable delay.  Instead
572     // we immediately send a message to MacVim asking it to close the window
573     // belonging to this process, and then we invalidate the connection (in
574     // case the message got lost).
576     // Make sure no connectionDidDie: notification is received now that we are
577     // already exiting.
578     [[NSNotificationCenter defaultCenter] removeObserver:self];
580     if ([connection isValid]) {
581         @try {
582             int msgid = CloseWindowMsgID;
583             NSData *data = [NSData dataWithBytes:&msgid length:sizeof(int)];
584             NSArray *q = [NSArray arrayWithObjects:data, [NSData data], nil];
585             [frontendProxy processCommandQueue:q];
586             //usleep(10000);
587         }
588         @catch (NSException *e) {
589             NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
590         }
592         [connection invalidate];
593     }
595 #ifdef MAC_CLIENTSERVER
596     // The default connection is used for the client/server code.
597     [[NSConnection defaultConnection] setRootObject:nil];
598     [[NSConnection defaultConnection] invalidate];
599 #endif
601     if (fontContainerRef) {
602         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
603         fontContainerRef = 0;
604     }
608 - (void)selectTab:(int)index
610     //NSLog(@"%s%d", _cmd, index);
612     index -= 1;
613     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
614     [self queueMessage:SelectTabMsgID data:data];
617 - (void)updateTabBar
619     //NSLog(@"%s", _cmd);
621     NSMutableData *data = [NSMutableData data];
623     int idx = tabpage_index(curtab) - 1;
624     [data appendBytes:&idx length:sizeof(int)];
626     tabpage_T *tp;
627     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
628         // This function puts the label of the tab in the global 'NameBuff'.
629         get_tabline_label(tp, FALSE);
630         char_u *s = NameBuff;
631         int len = STRLEN(s);
632         if (len <= 0) continue;
634 #ifdef FEAT_MBYTE
635         s = CONVERT_TO_UTF8(s);
636 #endif
638         // Count the number of windows in the tabpage.
639         //win_T *wp = tp->tp_firstwin;
640         //int wincount;
641         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
643         //[data appendBytes:&wincount length:sizeof(int)];
644         [data appendBytes:&len length:sizeof(int)];
645         [data appendBytes:s length:len];
647 #ifdef FEAT_MBYTE
648         CONVERT_TO_UTF8_FREE(s);
649 #endif
650     }
652     [self queueMessage:UpdateTabBarMsgID data:data];
655 - (BOOL)tabBarVisible
657     return tabBarVisible;
660 - (void)showTabBar:(BOOL)enable
662     tabBarVisible = enable;
664     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
665     [self queueMessage:msgid data:nil];
668 - (void)setRows:(int)rows columns:(int)cols
670     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
672     int dim[] = { rows, cols };
673     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
675     [self queueMessage:SetTextDimensionsMsgID data:data];
678 - (void)setWindowTitle:(char *)title
680     NSMutableData *data = [NSMutableData data];
681     int len = strlen(title);
682     if (len <= 0) return;
684     [data appendBytes:&len length:sizeof(int)];
685     [data appendBytes:title length:len];
687     [self queueMessage:SetWindowTitleMsgID data:data];
690 - (void)setDocumentFilename:(char *)filename
692     NSMutableData *data = [NSMutableData data];
693     int len = filename ? strlen(filename) : 0;
695     [data appendBytes:&len length:sizeof(int)];
696     if (len > 0)
697         [data appendBytes:filename length:len];
699     [self queueMessage:SetDocumentFilenameMsgID data:data];
702 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
704     char_u *s = NULL;
706     @try {
707         [frontendProxy showSavePanelWithAttributes:attr];
709         [self waitForDialogReturn];
711         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
712             char_u *ret = (char_u*)[dialogReturn UTF8String];
713 #ifdef FEAT_MBYTE
714             ret = CONVERT_FROM_UTF8(ret);
715 #endif
716             s = vim_strsave(ret);
717 #ifdef FEAT_MBYTE
718             CONVERT_FROM_UTF8_FREE(ret);
719 #endif
720         }
722         [dialogReturn release];  dialogReturn = nil;
723     }
724     @catch (NSException *e) {
725         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
726     }
728     return (char *)s;
731 - (oneway void)setDialogReturn:(in bycopy id)obj
733     // NOTE: This is called by
734     //   - [MMVimController panelDidEnd:::], and
735     //   - [MMVimController alertDidEnd:::],
736     // to indicate that a save/open panel or alert has finished.
738     // We want to distinguish between "no dialog return yet" and "dialog
739     // returned nothing".  The former can be tested with dialogReturn == nil,
740     // the latter with dialogReturn == [NSNull null].
741     if (!obj) obj = [NSNull null];
743     if (obj != dialogReturn) {
744         [dialogReturn release];
745         dialogReturn = [obj retain];
746     }
749 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
751     int retval = 0;
753     @try {
754         [frontendProxy presentDialogWithAttributes:attr];
756         [self waitForDialogReturn];
758         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
759                 && [dialogReturn count]) {
760             retval = [[dialogReturn objectAtIndex:0] intValue];
761             if (txtfield && [dialogReturn count] > 1) {
762                 NSString *retString = [dialogReturn objectAtIndex:1];
763                 char_u *ret = (char_u*)[retString UTF8String];
764 #ifdef FEAT_MBYTE
765                 ret = CONVERT_FROM_UTF8(ret);
766 #endif
767                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
768 #ifdef FEAT_MBYTE
769                 CONVERT_FROM_UTF8_FREE(ret);
770 #endif
771             }
772         }
774         [dialogReturn release]; dialogReturn = nil;
775     }
776     @catch (NSException *e) {
777         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
778     }
780     return retval;
783 - (void)showToolbar:(int)enable flags:(int)flags
785     NSMutableData *data = [NSMutableData data];
787     [data appendBytes:&enable length:sizeof(int)];
788     [data appendBytes:&flags length:sizeof(int)];
790     [self queueMessage:ShowToolbarMsgID data:data];
793 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
795     NSMutableData *data = [NSMutableData data];
797     [data appendBytes:&ident length:sizeof(long)];
798     [data appendBytes:&type length:sizeof(int)];
800     [self queueMessage:CreateScrollbarMsgID data:data];
803 - (void)destroyScrollbarWithIdentifier:(long)ident
805     NSMutableData *data = [NSMutableData data];
806     [data appendBytes:&ident length:sizeof(long)];
808     [self queueMessage:DestroyScrollbarMsgID data:data];
811 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
813     NSMutableData *data = [NSMutableData data];
815     [data appendBytes:&ident length:sizeof(long)];
816     [data appendBytes:&visible length:sizeof(int)];
818     [self queueMessage:ShowScrollbarMsgID data:data];
821 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
823     NSMutableData *data = [NSMutableData data];
825     [data appendBytes:&ident length:sizeof(long)];
826     [data appendBytes:&pos length:sizeof(int)];
827     [data appendBytes:&len length:sizeof(int)];
829     [self queueMessage:SetScrollbarPositionMsgID data:data];
832 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
833                     identifier:(long)ident
835     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
836     float prop = (float)size/(max+1);
837     if (fval < 0) fval = 0;
838     else if (fval > 1.0f) fval = 1.0f;
839     if (prop < 0) prop = 0;
840     else if (prop > 1.0f) prop = 1.0f;
842     NSMutableData *data = [NSMutableData data];
844     [data appendBytes:&ident length:sizeof(long)];
845     [data appendBytes:&fval length:sizeof(float)];
846     [data appendBytes:&prop length:sizeof(float)];
848     [self queueMessage:SetScrollbarThumbMsgID data:data];
851 - (void)setFont:(NSFont *)font
853     NSString *fontName = [font displayName];
854     float size = [font pointSize];
855     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
856     if (len > 0) {
857         NSMutableData *data = [NSMutableData data];
859         [data appendBytes:&size length:sizeof(float)];
860         [data appendBytes:&len length:sizeof(int)];
861         [data appendBytes:[fontName UTF8String] length:len];
863         [self queueMessage:SetFontMsgID data:data];
864     }
867 - (void)setWideFont:(NSFont *)font
869     NSString *fontName = [font displayName];
870     float size = [font pointSize];
871     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
872     NSMutableData *data = [NSMutableData data];
874     [data appendBytes:&size length:sizeof(float)];
875     [data appendBytes:&len length:sizeof(int)];
876     if (len > 0)
877         [data appendBytes:[fontName UTF8String] length:len];
879     [self queueMessage:SetWideFontMsgID data:data];
882 - (void)executeActionWithName:(NSString *)name
884     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
886     if (len > 0) {
887         NSMutableData *data = [NSMutableData data];
889         [data appendBytes:&len length:sizeof(int)];
890         [data appendBytes:[name UTF8String] length:len];
892         [self queueMessage:ExecuteActionMsgID data:data];
893     }
896 - (void)setMouseShape:(int)shape
898     NSMutableData *data = [NSMutableData data];
899     [data appendBytes:&shape length:sizeof(int)];
900     [self queueMessage:SetMouseShapeMsgID data:data];
903 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
905     // Vim specifies times in milliseconds, whereas Cocoa wants them in
906     // seconds.
907     blinkWaitInterval = .001f*wait;
908     blinkOnInterval = .001f*on;
909     blinkOffInterval = .001f*off;
912 - (void)startBlink
914     if (blinkTimer) {
915         [blinkTimer invalidate];
916         [blinkTimer release];
917         blinkTimer = nil;
918     }
920     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
921             && gui.in_focus) {
922         blinkState = MMBlinkStateOn;
923         blinkTimer =
924             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
925                                               target:self
926                                             selector:@selector(blinkTimerFired:)
927                                             userInfo:nil repeats:NO] retain];
928         gui_update_cursor(TRUE, FALSE);
929         [self flushQueue:YES];
930     }
933 - (void)stopBlink
935     if (MMBlinkStateOff == blinkState) {
936         gui_update_cursor(TRUE, FALSE);
937         [self flushQueue:YES];
938     }
940     blinkState = MMBlinkStateNone;
943 - (void)adjustLinespace:(int)linespace
945     NSMutableData *data = [NSMutableData data];
946     [data appendBytes:&linespace length:sizeof(int)];
947     [self queueMessage:AdjustLinespaceMsgID data:data];
950 - (void)activate
952     [self queueMessage:ActivateMsgID data:nil];
955 - (void)setPreEditRow:(int)row column:(int)col
957     NSMutableData *data = [NSMutableData data];
958     [data appendBytes:&row length:sizeof(int)];
959     [data appendBytes:&col length:sizeof(int)];
960     [self queueMessage:SetPreEditPositionMsgID data:data];
963 - (int)lookupColorWithKey:(NSString *)key
965     if (!(key && [key length] > 0))
966         return INVALCOLOR;
968     NSString *stripKey = [[[[key lowercaseString]
969         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
970             componentsSeparatedByString:@" "]
971                componentsJoinedByString:@""];
973     if (stripKey && [stripKey length] > 0) {
974         // First of all try to lookup key in the color dictionary; note that
975         // all keys in this dictionary are lowercase with no whitespace.
976         id obj = [colorDict objectForKey:stripKey];
977         if (obj) return [obj intValue];
979         // The key was not in the dictionary; is it perhaps of the form
980         // #rrggbb?
981         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
982             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
983             [scanner setScanLocation:1];
984             unsigned hex = 0;
985             if ([scanner scanHexInt:&hex]) {
986                 return (int)hex;
987             }
988         }
990         // As a last resort, check if it is one of the system defined colors.
991         // The keys in this dictionary are also lowercase with no whitespace.
992         obj = [sysColorDict objectForKey:stripKey];
993         if (obj) {
994             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
995             if (col) {
996                 float r, g, b, a;
997                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
998                 [col getRed:&r green:&g blue:&b alpha:&a];
999                 return (((int)(r*255+.5f) & 0xff) << 16)
1000                      + (((int)(g*255+.5f) & 0xff) << 8)
1001                      +  ((int)(b*255+.5f) & 0xff);
1002             }
1003         }
1004     }
1006     //NSLog(@"WARNING: No color with key %@ found.", stripKey);
1007     return INVALCOLOR;
1010 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
1012     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1013     id obj;
1015     while ((obj = [e nextObject])) {
1016         if ([value isEqual:obj])
1017             return YES;
1018     }
1020     return NO;
1023 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1025     NSMutableData *data = [NSMutableData data];
1026     [data appendBytes:&fuoptions length:sizeof(int)];
1027     bg = MM_COLOR(bg);
1028     [data appendBytes:&bg length:sizeof(int)];
1029     [self queueMessage:EnterFullscreenMsgID data:data];
1032 - (void)leaveFullscreen
1034     [self queueMessage:LeaveFullscreenMsgID data:nil];
1037 - (void)setAntialias:(BOOL)antialias
1039     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1041     [self queueMessage:msgid data:nil];
1044 - (void)updateModifiedFlag
1046     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1047     // vice versa.
1048     int msgid = [self checkForModifiedBuffers]
1049             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1051     [self queueMessage:msgid data:nil];
1054 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1056     // NOTE: This method might get called whenever the run loop is tended to.
1057     // Normal keyboard and mouse input is added to input buffers, so there is
1058     // no risk in handling these events directly (they return immediately, and
1059     // do not call any other Vim functions).  However, other events such
1060     // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
1061     // events which would cause this method to be called recursively.  This
1062     // in turn leads to various difficulties that we do not want to have to
1063     // deal with.  To avoid recursive calls here we add all events except
1064     // keyboard and mouse events to an input queue which is processed whenever
1065     // gui_mch_update() is called (see processInputQueue).
1067     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1069     // Don't flush too soon after receiving input or update speed will suffer.
1070     [lastFlushDate release];
1071     lastFlushDate = [[NSDate date] retain];
1073     // Handle keyboard and mouse input now.  All other events are queued.
1074     if (InsertTextMsgID == msgid) {
1075         [self handleInsertText:data];
1076     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1077         if (!data) return;
1078         const void *bytes = [data bytes];
1079         int mods = *((int*)bytes);  bytes += sizeof(int);
1080         int len = *((int*)bytes);  bytes += sizeof(int);
1081         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1082                                               encoding:NSUTF8StringEncoding];
1083         mods = eventModifierFlagsToVimModMask(mods);
1085         [self handleKeyDown:key modifiers:mods];
1087         [key release];
1088     } else if (ScrollWheelMsgID == msgid) {
1089         if (!data) return;
1090         const void *bytes = [data bytes];
1092         int row = *((int*)bytes);  bytes += sizeof(int);
1093         int col = *((int*)bytes);  bytes += sizeof(int);
1094         int flags = *((int*)bytes);  bytes += sizeof(int);
1095         float dy = *((float*)bytes);  bytes += sizeof(float);
1097         int button = MOUSE_5;
1098         if (dy > 0) button = MOUSE_4;
1100         flags = eventModifierFlagsToVimMouseModMask(flags);
1102         int numLines = (int)round(dy);
1103         if (numLines < 0) numLines = -numLines;
1104         if (numLines == 0) numLines = 1;
1106 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1107         gui.scroll_wheel_force = numLines;
1108 #endif
1110         gui_send_mouse_event(button, col, row, NO, flags);
1111     } else if (MouseDownMsgID == msgid) {
1112         if (!data) return;
1113         const void *bytes = [data bytes];
1115         int row = *((int*)bytes);  bytes += sizeof(int);
1116         int col = *((int*)bytes);  bytes += sizeof(int);
1117         int button = *((int*)bytes);  bytes += sizeof(int);
1118         int flags = *((int*)bytes);  bytes += sizeof(int);
1119         int count = *((int*)bytes);  bytes += sizeof(int);
1121         button = eventButtonNumberToVimMouseButton(button);
1122         if (button >= 0) {
1123             flags = eventModifierFlagsToVimMouseModMask(flags);
1124             gui_send_mouse_event(button, col, row, count>1, flags);
1125         }
1126     } else if (MouseUpMsgID == msgid) {
1127         if (!data) return;
1128         const void *bytes = [data bytes];
1130         int row = *((int*)bytes);  bytes += sizeof(int);
1131         int col = *((int*)bytes);  bytes += sizeof(int);
1132         int flags = *((int*)bytes);  bytes += sizeof(int);
1134         flags = eventModifierFlagsToVimMouseModMask(flags);
1136         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1137     } else if (MouseDraggedMsgID == msgid) {
1138         if (!data) return;
1139         const void *bytes = [data bytes];
1141         int row = *((int*)bytes);  bytes += sizeof(int);
1142         int col = *((int*)bytes);  bytes += sizeof(int);
1143         int flags = *((int*)bytes);  bytes += sizeof(int);
1145         flags = eventModifierFlagsToVimMouseModMask(flags);
1147         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1148     } else if (MouseMovedMsgID == msgid) {
1149         const void *bytes = [data bytes];
1150         int row = *((int*)bytes);  bytes += sizeof(int);
1151         int col = *((int*)bytes);  bytes += sizeof(int);
1153         gui_mouse_moved(col, row);
1154     } else if (AddInputMsgID == msgid) {
1155         NSString *string = [[NSString alloc] initWithData:data
1156                 encoding:NSUTF8StringEncoding];
1157         if (string) {
1158             [self addInput:string];
1159             [string release];
1160         }
1161     } else if (TerminateNowMsgID == msgid) {
1162         isTerminating = YES;
1163     } else {
1164         // Not keyboard or mouse event, queue it and handle later.
1165         //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
1166         [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1167         [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
1168     }
1170     // See waitForInput: for an explanation of this flag.
1171     inputReceived = YES;
1174 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1176     // TODO: Get rid of this method?
1177     //NSLog(@"%s%@", _cmd, messages);
1179     unsigned i, count = [messages count];
1180     for (i = 0; i < count; i += 2) {
1181         int msgid = [[messages objectAtIndex:i] intValue];
1182         id data = [messages objectAtIndex:i+1];
1183         if ([data isEqual:[NSNull null]])
1184             data = nil;
1186         [self processInput:msgid data:data];
1187     }
1190 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1191                   errorString:(out bycopy NSString **)errstr
1193     return evalExprCocoa(expr, errstr);
1197 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1199     NSString *eval = nil;
1200     char_u *s = (char_u*)[expr UTF8String];
1202 #ifdef FEAT_MBYTE
1203     s = CONVERT_FROM_UTF8(s);
1204 #endif
1206     char_u *res = eval_client_expr_to_string(s);
1208 #ifdef FEAT_MBYTE
1209     CONVERT_FROM_UTF8_FREE(s);
1210 #endif
1212     if (res != NULL) {
1213         s = res;
1214 #ifdef FEAT_MBYTE
1215         s = CONVERT_TO_UTF8(s);
1216 #endif
1217         eval = [NSString stringWithUTF8String:(char*)s];
1218 #ifdef FEAT_MBYTE
1219         CONVERT_TO_UTF8_FREE(s);
1220 #endif
1221         vim_free(res);
1222     }
1224     return eval;
1227 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1229     // TODO: This method should share code with clip_mch_request_selection().
1231     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1232         // If there is no pasteboard, return YES to indicate that there is text
1233         // to copy.
1234         if (!pboard)
1235             return YES;
1237         clip_copy_selection();
1239         // Get the text to put on the pasteboard.
1240         long_u llen = 0; char_u *str = 0;
1241         int type = clip_convert_selection(&str, &llen, &clip_star);
1242         if (type < 0)
1243             return NO;
1244         
1245         // TODO: Avoid overflow.
1246         int len = (int)llen;
1247 #ifdef FEAT_MBYTE
1248         if (output_conv.vc_type != CONV_NONE) {
1249             char_u *conv_str = string_convert(&output_conv, str, &len);
1250             if (conv_str) {
1251                 vim_free(str);
1252                 str = conv_str;
1253             }
1254         }
1255 #endif
1257         NSString *string = [[NSString alloc]
1258             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1260         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1261         [pboard declareTypes:types owner:nil];
1262         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1263     
1264         [string release];
1265         vim_free(str);
1267         return ok;
1268     }
1270     return NO;
1273 - (oneway void)addReply:(in bycopy NSString *)reply
1274                  server:(in byref id <MMVimServerProtocol>)server
1276     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1278     // Replies might come at any time and in any order so we keep them in an
1279     // array inside a dictionary with the send port used as key.
1281     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1282     // HACK! Assume connection uses mach ports.
1283     int port = [(NSMachPort*)[conn sendPort] machPort];
1284     NSNumber *key = [NSNumber numberWithInt:port];
1286     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1287     if (!replies) {
1288         replies = [NSMutableArray array];
1289         [serverReplyDict setObject:replies forKey:key];
1290     }
1292     [replies addObject:reply];
1295 - (void)addInput:(in bycopy NSString *)input
1296                  client:(in byref id <MMVimClientProtocol>)client
1298     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1300     [self addInput:input];
1301     [self addClient:(id)client];
1303     inputReceived = YES;
1306 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1307                  client:(in byref id <MMVimClientProtocol>)client
1309     [self addClient:(id)client];
1310     return [self evaluateExpression:expr];
1313 - (void)registerServerWithName:(NSString *)name
1315     NSString *svrName = name;
1316     NSConnection *svrConn = [NSConnection defaultConnection];
1317     unsigned i;
1319     for (i = 0; i < MMServerMax; ++i) {
1320         NSString *connName = [self connectionNameFromServerName:svrName];
1322         if ([svrConn registerName:connName]) {
1323             //NSLog(@"Registered server with name: %@", svrName);
1325             // TODO: Set request/reply time-outs to something else?
1326             //
1327             // Don't wait for requests (time-out means that the message is
1328             // dropped).
1329             [svrConn setRequestTimeout:0];
1330             //[svrConn setReplyTimeout:MMReplyTimeout];
1331             [svrConn setRootObject:self];
1333             char_u *s = (char_u*)[svrName UTF8String];
1334 #ifdef FEAT_MBYTE
1335             s = CONVERT_FROM_UTF8(s);
1336 #endif
1337             // NOTE: 'serverName' is a global variable
1338             serverName = vim_strsave(s);
1339 #ifdef FEAT_MBYTE
1340             CONVERT_FROM_UTF8_FREE(s);
1341 #endif
1342 #ifdef FEAT_EVAL
1343             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1344 #endif
1345 #ifdef FEAT_TITLE
1346             need_maketitle = TRUE;
1347 #endif
1348             [self queueMessage:SetServerNameMsgID data:
1349                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1350             break;
1351         }
1353         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1354     }
1357 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1358                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1359               silent:(BOOL)silent
1361     // NOTE: If 'name' equals 'serverName' then the request is local (client
1362     // and server are the same).  This case is not handled separately, so a
1363     // connection will be set up anyway (this simplifies the code).
1365     NSConnection *conn = [self connectionForServerName:name];
1366     if (!conn) {
1367         if (!silent) {
1368             char_u *s = (char_u*)[name UTF8String];
1369 #ifdef FEAT_MBYTE
1370             s = CONVERT_FROM_UTF8(s);
1371 #endif
1372             EMSG2(_(e_noserver), s);
1373 #ifdef FEAT_MBYTE
1374             CONVERT_FROM_UTF8_FREE(s);
1375 #endif
1376         }
1377         return NO;
1378     }
1380     if (port) {
1381         // HACK! Assume connection uses mach ports.
1382         *port = [(NSMachPort*)[conn sendPort] machPort];
1383     }
1385     id proxy = [conn rootProxy];
1386     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1388     @try {
1389         if (expr) {
1390             NSString *eval = [proxy evaluateExpression:string client:self];
1391             if (reply) {
1392                 if (eval) {
1393                     char_u *r = (char_u*)[eval UTF8String];
1394 #ifdef FEAT_MBYTE
1395                     r = CONVERT_FROM_UTF8(r);
1396 #endif
1397                     *reply = vim_strsave(r);
1398 #ifdef FEAT_MBYTE
1399                     CONVERT_FROM_UTF8_FREE(r);
1400 #endif
1401                 } else {
1402                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1403                 }
1404             }
1406             if (!eval)
1407                 return NO;
1408         } else {
1409             [proxy addInput:string client:self];
1410         }
1411     }
1412     @catch (NSException *e) {
1413         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1414         return NO;
1415     }
1417     return YES;
1420 - (NSArray *)serverList
1422     NSArray *list = nil;
1424     if ([self connection]) {
1425         id proxy = [connection rootProxy];
1426         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1428         @try {
1429             list = [proxy serverList];
1430         }
1431         @catch (NSException *e) {
1432             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1433         }
1434     } else {
1435         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1436     }
1438     return list;
1441 - (NSString *)peekForReplyOnPort:(int)port
1443     //NSLog(@"%s%d", _cmd, port);
1445     NSNumber *key = [NSNumber numberWithInt:port];
1446     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1447     if (replies && [replies count]) {
1448         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1449         //        [replies objectAtIndex:0]);
1450         return [replies objectAtIndex:0];
1451     }
1453     //NSLog(@"    No replies");
1454     return nil;
1457 - (NSString *)waitForReplyOnPort:(int)port
1459     //NSLog(@"%s%d", _cmd, port);
1460     
1461     NSConnection *conn = [self connectionForServerPort:port];
1462     if (!conn)
1463         return nil;
1465     NSNumber *key = [NSNumber numberWithInt:port];
1466     NSMutableArray *replies = nil;
1467     NSString *reply = nil;
1469     // Wait for reply as long as the connection to the server is valid (unless
1470     // user interrupts wait with Ctrl-C).
1471     while (!got_int && [conn isValid] &&
1472             !(replies = [serverReplyDict objectForKey:key])) {
1473         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1474                                  beforeDate:[NSDate distantFuture]];
1475     }
1477     if (replies) {
1478         if ([replies count] > 0) {
1479             reply = [[replies objectAtIndex:0] retain];
1480             //NSLog(@"    Got reply: %@", reply);
1481             [replies removeObjectAtIndex:0];
1482             [reply autorelease];
1483         }
1485         if ([replies count] == 0)
1486             [serverReplyDict removeObjectForKey:key];
1487     }
1489     return reply;
1492 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1494     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1495     if (client) {
1496         @try {
1497             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1498             [client addReply:reply server:self];
1499             return YES;
1500         }
1501         @catch (NSException *e) {
1502             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1503         }
1504     } else {
1505         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1506     }
1508     return NO;
1511 - (BOOL)waitForAck
1513     return waitForAck;
1516 - (void)setWaitForAck:(BOOL)yn
1518     waitForAck = yn;
1521 - (void)waitForConnectionAcknowledgement
1523     if (!waitForAck) return;
1525     while (waitForAck && !got_int && [connection isValid] && !isTerminating) {
1526         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1527                                  beforeDate:[NSDate distantFuture]];
1528         //NSLog(@"  waitForAck=%d got_int=%d isTerminating=%d isValid=%d",
1529         //        waitForAck, got_int, isTerminating, [connection isValid]);
1530     }
1532     if (waitForAck) {
1533         // Never received a connection acknowledgement, so die.
1534         [[NSNotificationCenter defaultCenter] removeObserver:self];
1535         [frontendProxy release];  frontendProxy = nil;
1537         // NOTE: We intentionally do not call mch_exit() since this in turn
1538         // will lead to -[MMBackend exit] getting called which we want to
1539         // avoid.
1540         exit(0);
1541     }
1543     [self processInputQueue];
1544     [self openVimWindow];
1547 - (oneway void)acknowledgeConnection
1549     //NSLog(@"%s", _cmd);
1550     waitForAck = NO;
1553 @end // MMBackend
1557 @implementation MMBackend (Private)
1559 - (void)waitForDialogReturn
1561     // Keep processing the run loop until a dialog returns.  To avoid getting
1562     // stuck in an endless loop (could happen if the setDialogReturn: message
1563     // was lost) we also do some paranoia checks.
1564     //
1565     // Note that in Cocoa the user can still resize windows and select menu
1566     // items while a sheet is being displayed, so we can't just wait for the
1567     // first message to arrive and assume that is the setDialogReturn: call.
1569     while (nil == dialogReturn && !got_int && [connection isValid]
1570             && !isTerminating)
1571         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1572                                  beforeDate:[NSDate distantFuture]];
1574     // Search for any resize messages on the input queue.  All other messages
1575     // on the input queue are dropped.  The reason why we single out resize
1576     // messages is because the user may have resized the window while a sheet
1577     // was open.
1578     int i, count = [inputQueue count];
1579     if (count > 0) {
1580         id textDimData = nil;
1581         if (count%2 == 0) {
1582             for (i = count-2; i >= 0; i -= 2) {
1583                 int msgid = [[inputQueue objectAtIndex:i] intValue];
1584                 if (SetTextDimensionsMsgID == msgid) {
1585                     textDimData = [[inputQueue objectAtIndex:i+1] retain];
1586                     break;
1587                 }
1588             }
1589         }
1591         [inputQueue removeAllObjects];
1593         if (textDimData) {
1594             [inputQueue addObject:
1595                     [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1596             [inputQueue addObject:textDimData];
1597             [textDimData release];
1598         }
1599     }
1602 - (void)queueVimStateMessage
1604     // NOTE: This is the place to add Vim state that needs to be accessed from
1605     // MacVim.  Do not add state that could potentially require lots of memory
1606     // since this message gets sent each time the output queue is forcibly
1607     // flushed (e.g. storing the currently selected text would be a bad idea).
1608     // We take this approach of "pushing" the state to MacVim to avoid having
1609     // to make synchronous calls from MacVim to Vim in order to get state.
1611     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1612         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1613         [NSNumber numberWithInt:p_mh], @"p_mh",
1614         nil];
1616     [self queueMessage:SetVimStateMsgID data:[vimState dictionaryAsData]];
1619 - (void)processInputQueue
1621     if ([inputQueue count] == 0) return;
1623     // NOTE: One of the input events may cause this method to be called
1624     // recursively, so copy the input queue to a local variable and clear it
1625     // before starting to process input events (otherwise we could get stuck in
1626     // an endless loop).
1627     NSArray *q = [inputQueue copy];
1628     unsigned i, count = [q count];
1630     [inputQueue removeAllObjects];
1632     for (i = 0; i < count-1; i += 2) {
1633         int msgid = [[q objectAtIndex:i] intValue];
1634         id data = [q objectAtIndex:i+1];
1635         if ([data isEqual:[NSNull null]])
1636             data = nil;
1638         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1639         [self handleInputEvent:msgid data:data];
1640     }
1642     [q release];
1643     //NSLog(@"Clear input event queue");
1646 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1648     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1650     if (SelectTabMsgID == msgid) {
1651         if (!data) return;
1652         const void *bytes = [data bytes];
1653         int idx = *((int*)bytes) + 1;
1654         //NSLog(@"Selecting tab %d", idx);
1655         send_tabline_event(idx);
1656     } else if (CloseTabMsgID == msgid) {
1657         if (!data) return;
1658         const void *bytes = [data bytes];
1659         int idx = *((int*)bytes) + 1;
1660         //NSLog(@"Closing tab %d", idx);
1661         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1662     } else if (AddNewTabMsgID == msgid) {
1663         //NSLog(@"Adding new tab");
1664         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1665     } else if (DraggedTabMsgID == msgid) {
1666         if (!data) return;
1667         const void *bytes = [data bytes];
1668         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1669         // based.
1670         int idx = *((int*)bytes);
1672         tabpage_move(idx);
1673     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) {
1674         if (!data) return;
1675         const void *bytes = [data bytes];
1676         int rows = *((int*)bytes);  bytes += sizeof(int);
1677         int cols = *((int*)bytes);  bytes += sizeof(int);
1679         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1680         // gui_resize_shell(), so we have to manually set the rows and columns
1681         // here.  (MacVim doesn't change the rows and columns to avoid
1682         // inconsistent states between Vim and MacVim.)
1683         [self queueMessage:msgid data:data];
1685         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1686         gui_resize_shell(cols, rows);
1687     } else if (ExecuteMenuMsgID == msgid) {
1688         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1689         if (attrs) {
1690             NSArray *desc = [attrs objectForKey:@"descriptor"];
1691             vimmenu_T *menu = menu_for_descriptor(desc);
1692             if (menu)
1693                 gui_menu_cb(menu);
1694         }
1695     } else if (ToggleToolbarMsgID == msgid) {
1696         [self handleToggleToolbar];
1697     } else if (ScrollbarEventMsgID == msgid) {
1698         [self handleScrollbarEvent:data];
1699     } else if (SetFontMsgID == msgid) {
1700         [self handleSetFont:data];
1701     } else if (VimShouldCloseMsgID == msgid) {
1702         gui_shell_closed();
1703     } else if (DropFilesMsgID == msgid) {
1704         [self handleDropFiles:data];
1705     } else if (DropStringMsgID == msgid) {
1706         [self handleDropString:data];
1707     } else if (GotFocusMsgID == msgid) {
1708         if (!gui.in_focus)
1709             [self focusChange:YES];
1710     } else if (LostFocusMsgID == msgid) {
1711         if (gui.in_focus)
1712             [self focusChange:NO];
1713     } else if (SetMouseShapeMsgID == msgid) {
1714         const void *bytes = [data bytes];
1715         int shape = *((int*)bytes);  bytes += sizeof(int);
1716         update_mouseshape(shape);
1717     } else if (XcodeModMsgID == msgid) {
1718         [self handleXcodeMod:data];
1719     } else if (OpenWithArgumentsMsgID == msgid) {
1720         [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1721     } else {
1722         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1723     }
1726 + (NSDictionary *)specialKeys
1728     static NSDictionary *specialKeys = nil;
1730     if (!specialKeys) {
1731         NSBundle *mainBundle = [NSBundle mainBundle];
1732         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1733                                               ofType:@"plist"];
1734         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1735     }
1737     return specialKeys;
1740 - (void)handleInsertText:(NSData *)data
1742     if (!data) return;
1744     NSString *key = [[NSString alloc] initWithData:data
1745                                           encoding:NSUTF8StringEncoding];
1746     char_u *str = (char_u*)[key UTF8String];
1747     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1749 #ifdef FEAT_MBYTE
1750     char_u *conv_str = NULL;
1751     if (input_conv.vc_type != CONV_NONE) {
1752         conv_str = string_convert(&input_conv, str, &len);
1753         if (conv_str)
1754             str = conv_str;
1755     }
1756 #endif
1758     if (len == 1 && ((str[0] == Ctrl_C && ctrl_c_interrupts)
1759             || (str[0] == intr_char && intr_char != Ctrl_C))) {
1760         trash_input_buf();
1761         got_int = TRUE;
1762     }
1764     for (i = 0; i < len; ++i) {
1765         add_to_input_buf(str+i, 1);
1766         if (CSI == str[i]) {
1767             // NOTE: If the converted string contains the byte CSI, then it
1768             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1769             // won't work.
1770             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1771             add_to_input_buf(extra, 2);
1772         }
1773     }
1775 #ifdef FEAT_MBYTE
1776     if (conv_str)
1777         vim_free(conv_str);
1778 #endif
1779     [key release];
1782 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1784     char_u special[3];
1785     char_u modChars[3];
1786     char_u *chars = (char_u*)[key UTF8String];
1787 #ifdef FEAT_MBYTE
1788     char_u *conv_str = NULL;
1789 #endif
1790     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1792     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1793     // that new keys can easily be added.
1794     NSString *specialString = [[MMBackend specialKeys]
1795             objectForKey:key];
1796     if (specialString && [specialString length] > 1) {
1797         //NSLog(@"special key: %@", specialString);
1798         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1799                 [specialString characterAtIndex:1]);
1801         ikey = simplify_key(ikey, &mods);
1802         if (ikey == CSI)
1803             ikey = K_CSI;
1805         special[0] = CSI;
1806         special[1] = K_SECOND(ikey);
1807         special[2] = K_THIRD(ikey);
1809         chars = special;
1810         length = 3;
1811     } else if (1 == length && TAB == chars[0]) {
1812         // Tab is a trouble child:
1813         // - <Tab> is added to the input buffer as is
1814         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1815         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1816         //   to be converted to utf-8
1817         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1818         // - <C-Tab> is reserved by Mac OS X
1819         // - <D-Tab> is reserved by Mac OS X
1820         chars = special;
1821         special[0] = TAB;
1822         length = 1;
1824         if (mods & MOD_MASK_SHIFT) {
1825             mods &= ~MOD_MASK_SHIFT;
1826             special[0] = CSI;
1827             special[1] = K_SECOND(K_S_TAB);
1828             special[2] = K_THIRD(K_S_TAB);
1829             length = 3;
1830         } else if (mods & MOD_MASK_ALT) {
1831             int mtab = 0x80 | TAB;
1832 #ifdef FEAT_MBYTE
1833             if (enc_utf8) {
1834                 // Convert to utf-8
1835                 special[0] = (mtab >> 6) + 0xc0;
1836                 special[1] = mtab & 0xbf;
1837                 length = 2;
1838             } else
1839 #endif
1840             {
1841                 special[0] = mtab;
1842                 length = 1;
1843             }
1844             mods &= ~MOD_MASK_ALT;
1845         }
1846     } else if (length > 0) {
1847         unichar c = [key characterAtIndex:0];
1849         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1850         //        [key characterAtIndex:0], mods);
1852         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1853                 || (c == intr_char && intr_char != Ctrl_C))) {
1854             trash_input_buf();
1855             got_int = TRUE;
1856         }
1858         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1859         // cleared since they are already added to the key by the AppKit.
1860         // Unfortunately, the only way to deal with when to clear the modifiers
1861         // or not seems to be to have hard-wired rules like this.
1862         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1863                     || 0x9 == c || 0xd == c || ESC == c) ) {
1864             mods &= ~MOD_MASK_SHIFT;
1865             mods &= ~MOD_MASK_CTRL;
1866             //NSLog(@"clear shift ctrl");
1867         }
1869         // HACK!  All Option+key presses go via 'insert text' messages, except
1870         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1871         // not work to map to it.
1872         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1873             //NSLog(@"clear alt");
1874             mods &= ~MOD_MASK_ALT;
1875         }
1877 #ifdef FEAT_MBYTE
1878         if (input_conv.vc_type != CONV_NONE) {
1879             conv_str = string_convert(&input_conv, chars, &length);
1880             if (conv_str)
1881                 chars = conv_str;
1882         }
1883 #endif
1884     }
1886     if (chars && length > 0) {
1887         if (mods) {
1888             //NSLog(@"adding mods: %d", mods);
1889             modChars[0] = CSI;
1890             modChars[1] = KS_MODIFIER;
1891             modChars[2] = mods;
1892             add_to_input_buf(modChars, 3);
1893         }
1895         //NSLog(@"add to input buf: 0x%x", chars[0]);
1896         // TODO: Check for CSI bytes?
1897         add_to_input_buf(chars, length);
1898     }
1900 #ifdef FEAT_MBYTE
1901     if (conv_str)
1902         vim_free(conv_str);
1903 #endif
1906 - (void)queueMessage:(int)msgid data:(NSData *)data
1908     //if (msgid != EnableMenuItemMsgID)
1909     //    NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1911     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1912     if (data)
1913         [outputQueue addObject:data];
1914     else
1915         [outputQueue addObject:[NSData data]];
1918 - (void)connectionDidDie:(NSNotification *)notification
1920     // If the main connection to MacVim is lost this means that MacVim was
1921     // either quit (by the user chosing Quit on the MacVim menu), or it has
1922     // crashed.  In the former case the flag 'isTerminating' is set and we then
1923     // quit cleanly; in the latter case we make sure the swap files are left
1924     // for recovery.
1925     //
1926     // NOTE: This is not called if a Vim controller invalidates its connection.
1928     //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1929     if (isTerminating)
1930         getout(0);
1931     else
1932         getout_preserve_modified(1);
1935 - (void)blinkTimerFired:(NSTimer *)timer
1937     NSTimeInterval timeInterval = 0;
1939     [blinkTimer release];
1940     blinkTimer = nil;
1942     if (MMBlinkStateOn == blinkState) {
1943         gui_undraw_cursor();
1944         blinkState = MMBlinkStateOff;
1945         timeInterval = blinkOffInterval;
1946     } else if (MMBlinkStateOff == blinkState) {
1947         gui_update_cursor(TRUE, FALSE);
1948         blinkState = MMBlinkStateOn;
1949         timeInterval = blinkOnInterval;
1950     }
1952     if (timeInterval > 0) {
1953         blinkTimer = 
1954             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1955                                             selector:@selector(blinkTimerFired:)
1956                                             userInfo:nil repeats:NO] retain];
1957         [self flushQueue:YES];
1958     }
1961 - (void)focusChange:(BOOL)on
1963     gui_focus_change(on);
1966 - (void)handleToggleToolbar
1968     // If 'go' contains 'T', then remove it, else add it.
1970     char_u go[sizeof(GO_ALL)+2];
1971     char_u *p;
1972     int len;
1974     STRCPY(go, p_go);
1975     p = vim_strchr(go, GO_TOOLBAR);
1976     len = STRLEN(go);
1978     if (p != NULL) {
1979         char_u *end = go + len;
1980         while (p < end) {
1981             p[0] = p[1];
1982             ++p;
1983         }
1984     } else {
1985         go[len] = GO_TOOLBAR;
1986         go[len+1] = NUL;
1987     }
1989     set_option_value((char_u*)"guioptions", 0, go, 0);
1991     // Force screen redraw (does it have to be this complicated?).
1992     redraw_all_later(CLEAR);
1993     update_screen(NOT_VALID);
1994     setcursor();
1995     out_flush();
1996     gui_update_cursor(FALSE, FALSE);
1997     gui_mch_flush();
2000 - (void)handleScrollbarEvent:(NSData *)data
2002     if (!data) return;
2004     const void *bytes = [data bytes];
2005     long ident = *((long*)bytes);  bytes += sizeof(long);
2006     int hitPart = *((int*)bytes);  bytes += sizeof(int);
2007     float fval = *((float*)bytes);  bytes += sizeof(float);
2008     scrollbar_T *sb = gui_find_scrollbar(ident);
2010     if (sb) {
2011         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2012         long value = sb_info->value;
2013         long size = sb_info->size;
2014         long max = sb_info->max;
2015         BOOL isStillDragging = NO;
2016         BOOL updateKnob = YES;
2018         switch (hitPart) {
2019         case NSScrollerDecrementPage:
2020             value -= (size > 2 ? size - 2 : 1);
2021             break;
2022         case NSScrollerIncrementPage:
2023             value += (size > 2 ? size - 2 : 1);
2024             break;
2025         case NSScrollerDecrementLine:
2026             --value;
2027             break;
2028         case NSScrollerIncrementLine:
2029             ++value;
2030             break;
2031         case NSScrollerKnob:
2032             isStillDragging = YES;
2033             // fall through ...
2034         case NSScrollerKnobSlot:
2035             value = (long)(fval * (max - size + 1));
2036             // fall through ...
2037         default:
2038             updateKnob = NO;
2039             break;
2040         }
2042         //NSLog(@"value %d -> %d", sb_info->value, value);
2043         gui_drag_scrollbar(sb, value, isStillDragging);
2045         if (updateKnob) {
2046             // Dragging the knob or option+clicking automatically updates
2047             // the knob position (on the actual NSScroller), so we only
2048             // need to set the knob position in the other cases.
2049             if (sb->wp) {
2050                 // Update both the left&right vertical scrollbars.
2051                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2052                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2053                 [self setScrollbarThumbValue:value size:size max:max
2054                                   identifier:identLeft];
2055                 [self setScrollbarThumbValue:value size:size max:max
2056                                   identifier:identRight];
2057             } else {
2058                 // Update the horizontal scrollbar.
2059                 [self setScrollbarThumbValue:value size:size max:max
2060                                   identifier:ident];
2061             }
2062         }
2063     }
2066 - (void)handleSetFont:(NSData *)data
2068     if (!data) return;
2070     const void *bytes = [data bytes];
2071     float pointSize = *((float*)bytes);  bytes += sizeof(float);
2072     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2073     bytes += sizeof(unsigned);  // len not used
2075     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2076     [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
2077     char_u *s = (char_u*)[name UTF8String];
2079 #ifdef FEAT_MBYTE
2080     s = CONVERT_FROM_UTF8(s);
2081 #endif
2083     set_option_value((char_u*)"guifont", 0, s, 0);
2085 #ifdef FEAT_MBYTE
2086     CONVERT_FROM_UTF8_FREE(s);
2087 #endif
2089     // Force screen redraw (does it have to be this complicated?).
2090     redraw_all_later(CLEAR);
2091     update_screen(NOT_VALID);
2092     setcursor();
2093     out_flush();
2094     gui_update_cursor(FALSE, FALSE);
2095     gui_mch_flush();
2098 - (void)handleDropFiles:(NSData *)data
2100     // TODO: Get rid of this method; instead use Vim script directly.  At the
2101     // moment I know how to do this to open files in tabs, but I'm not sure how
2102     // to add the filenames to the command line when in command line mode.
2104     if (!data) return;
2106     NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2107     if (!args) return;
2109     id obj = [args objectForKey:@"forceOpen"];
2110     BOOL forceOpen = YES;
2111     if (obj)
2112         forceOpen = [obj boolValue];
2114     NSArray *filenames = [args objectForKey:@"filenames"];
2115     if (!(filenames && [filenames count] > 0)) return;
2117 #ifdef FEAT_DND
2118     if (!forceOpen && (State & CMDLINE)) {
2119         // HACK!  If Vim is in command line mode then the files names
2120         // should be added to the command line, instead of opening the
2121         // files in tabs (unless forceOpen is set).  This is taken care of by
2122         // gui_handle_drop().
2123         int n = [filenames count];
2124         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2125         if (fnames) {
2126             int i = 0;
2127             for (i = 0; i < n; ++i)
2128                 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2130             // NOTE!  This function will free 'fnames'.
2131             // HACK!  It is assumed that the 'x' and 'y' arguments are
2132             // unused when in command line mode.
2133             gui_handle_drop(0, 0, 0, fnames, n);
2134         }
2135     } else
2136 #endif // FEAT_DND
2137     {
2138         [self handleOpenWithArguments:args];
2139     }
2142 - (void)handleDropString:(NSData *)data
2144     if (!data) return;
2146 #ifdef FEAT_DND
2147     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2148     const void *bytes = [data bytes];
2149     int len = *((int*)bytes);  bytes += sizeof(int);
2150     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2152     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2153     NSRange range = { 0, [string length] };
2154     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2155                                          withString:@"\x0a" options:0
2156                                               range:range];
2157     if (0 == n) {
2158         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2159                                        options:0 range:range];
2160     }
2162     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2163     char_u *s = (char_u*)[string UTF8String];
2164 #ifdef FEAT_MBYTE
2165     if (input_conv.vc_type != CONV_NONE)
2166         s = string_convert(&input_conv, s, &len);
2167 #endif
2168     dnd_yank_drag_data(s, len);
2169 #ifdef FEAT_MBYTE
2170     if (input_conv.vc_type != CONV_NONE)
2171         vim_free(s);
2172 #endif
2173     add_to_input_buf(dropkey, sizeof(dropkey));
2174 #endif // FEAT_DND
2177 - (void)startOdbEditWithArguments:(NSDictionary *)args
2179 #ifdef FEAT_ODB_EDITOR
2180     id obj = [args objectForKey:@"remoteID"];
2181     if (!obj) return;
2183     OSType serverID = [obj unsignedIntValue];
2184     NSString *remotePath = [args objectForKey:@"remotePath"];
2186     NSAppleEventDescriptor *token = nil;
2187     NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2188     obj = [args objectForKey:@"remoteTokenDescType"];
2189     if (tokenData && obj) {
2190         DescType tokenType = [obj unsignedLongValue];
2191         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2192                                                                 data:tokenData];
2193     }
2195     NSArray *filenames = [args objectForKey:@"filenames"];
2196     unsigned i, numFiles = [filenames count];
2197     for (i = 0; i < numFiles; ++i) {
2198         NSString *filename = [filenames objectAtIndex:i];
2199         char_u *s = [filename vimStringSave];
2200         buf_T *buf = buflist_findname(s);
2201         vim_free(s);
2203         if (buf) {
2204             if (buf->b_odb_token) {
2205                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2206                 buf->b_odb_token = NULL;
2207             }
2209             if (buf->b_odb_fname) {
2210                 vim_free(buf->b_odb_fname);
2211                 buf->b_odb_fname = NULL;
2212             }
2214             buf->b_odb_server_id = serverID;
2216             if (token)
2217                 buf->b_odb_token = [token retain];
2218             if (remotePath)
2219                 buf->b_odb_fname = [remotePath vimStringSave];
2220         } else {
2221             NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2222                     filename);
2223         }
2224     }
2225 #endif // FEAT_ODB_EDITOR
2228 - (void)handleXcodeMod:(NSData *)data
2230 #if 0
2231     const void *bytes = [data bytes];
2232     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2233     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2234     if (0 == len)
2235         return;
2237     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2238             descriptorWithDescriptorType:type
2239                                    bytes:bytes
2240                                   length:len];
2241 #endif
2244 - (void)handleOpenWithArguments:(NSDictionary *)args
2246     //   ARGUMENT:              DESCRIPTION:
2247     //   -------------------------------------------------------------
2248     //   filenames              list of filenames
2249     //   dontOpen               don't open files specified in above argument
2250     //   layout                 which layout to use to open files
2251     //   selectionRange         range to select
2252     //   searchText             string to search for
2253     //   remoteID               ODB parameter
2254     //   remotePath             ODB parameter
2255     //   remoteTokenDescType    ODB parameter
2256     //   remoteTokenData        ODB parameter
2258     //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2260     NSArray *filenames = [args objectForKey:@"filenames"];
2261     int i, numFiles = filenames ? [filenames count] : 0;
2262     BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2263     int layout = [[args objectForKey:@"layout"] intValue];
2265     if (starting > 0) {
2266         // When Vim is starting we simply add the files to be opened to the
2267         // global arglist and Vim will take care of opening them for us.
2268         if (openFiles && numFiles > 0) {
2269             for (i = 0; i < numFiles; i++) {
2270                 NSString *fname = [filenames objectAtIndex:i];
2271                 char_u *p = NULL;
2273                 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2274                         || (p = [fname vimStringSave]) == NULL)
2275                     mch_exit(2);
2276                 else
2277                     alist_add(&global_alist, p, 2);
2278             }
2280             // Vim will take care of arranging the files added to the arglist
2281             // in windows or tabs; all we must do is to specify which layout to
2282             // use.
2283             initialWindowLayout = layout;
2284         }
2285     } else {
2286         // When Vim is already open we resort to some trickery to open the
2287         // files with the specified layout.
2288         //
2289         // TODO: Figure out a better way to handle this?
2290         if (openFiles && numFiles > 0) {
2291             BOOL oneWindowInTab = topframe ? YES
2292                                            : (topframe->fr_layout == FR_LEAF);
2293             BOOL bufChanged = NO;
2294             BOOL bufHasFilename = NO;
2295             if (curbuf) {
2296                 bufChanged = check_changed(curbuf, TRUE, FALSE, FALSE, FALSE);
2297                 bufHasFilename = curbuf->b_ffname != NULL;
2298             }
2300             // Temporarily disable flushing since the following code may
2301             // potentially cause multiple redraws.
2302             flushDisabled = YES;
2304             BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2305             if (WIN_TABS == layout && !onlyOneTab) {
2306                 // By going to the last tabpage we ensure that the new tabs
2307                 // will appear last (if this call is left out, the taborder
2308                 // becomes messy).
2309                 goto_tabpage(9999);
2310             }
2312             // Make sure we're in normal mode first.
2313             [self addInput:@"<C-\\><C-N>"];
2315             if (numFiles > 1) {
2316                 // With "split layout" we open a new tab before opening
2317                 // multiple files if the current tab has more than one window
2318                 // or if there is exactly one window but whose buffer has a
2319                 // filename.  (The :drop command ensures modified buffers get
2320                 // their own window.)
2321                 if ((WIN_HOR == layout || WIN_VER == layout) &&
2322                         (!oneWindowInTab || bufHasFilename))
2323                     [self addInput:@":tabnew<CR>"];
2325                 // The files are opened by constructing a ":drop ..." command
2326                 // and executing it.
2327                 NSMutableString *cmd = (WIN_TABS == layout)
2328                         ? [NSMutableString stringWithString:@":tab drop"]
2329                         : [NSMutableString stringWithString:@":drop"];
2331                 for (i = 0; i < numFiles; ++i) {
2332                     NSString *file = [filenames objectAtIndex:i];
2333                     file = [file stringByEscapingSpecialFilenameCharacters];
2334                     [cmd appendString:@" "];
2335                     [cmd appendString:file];
2336                 }
2338                 [self addInput:cmd];
2340                 // Split the view into multiple windows if requested.
2341                 if (WIN_HOR == layout)
2342                     [self addInput:@"|sall"];
2343                 else if (WIN_VER == layout)
2344                     [self addInput:@"|vert sall"];
2346                 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2347                 [self addInput:@"|redr|f<CR>"];
2348             } else {
2349                 // When opening one file we try to reuse the current window,
2350                 // but not if its buffer is modified or has a filename.
2351                 // However, the 'arglist' layout always opens the file in the
2352                 // current window.
2353                 NSString *file = [[filenames lastObject]
2354                         stringByEscapingSpecialFilenameCharacters];
2355                 NSString *cmd;
2356                 if (WIN_HOR == layout) {
2357                     if (!(bufHasFilename || bufChanged))
2358                         cmd = [NSString stringWithFormat:@":e %@", file];
2359                     else
2360                         cmd = [NSString stringWithFormat:@":sp %@", file];
2361                 } else if (WIN_VER == layout) {
2362                     if (!(bufHasFilename || bufChanged))
2363                         cmd = [NSString stringWithFormat:@":e %@", file];
2364                     else
2365                         cmd = [NSString stringWithFormat:@":vsp %@", file];
2366                 } else if (WIN_TABS == layout) {
2367                     if (oneWindowInTab && !(bufHasFilename || bufChanged))
2368                         cmd = [NSString stringWithFormat:@":e %@", file];
2369                     else
2370                         cmd = [NSString stringWithFormat:@":tabe %@", file];
2371                 } else {
2372                     // (The :drop command will split if there is a modified
2373                     // buffer.)
2374                     cmd = [NSString stringWithFormat:@":drop %@", file];
2375                 }
2377                 [self addInput:cmd];
2379                 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2380                 [self addInput:@"|redr|f<CR>"];
2381             }
2383             // Force screen redraw (does it have to be this complicated?).
2384             // (This code was taken from the end of gui_handle_drop().)
2385             update_screen(NOT_VALID);
2386             setcursor();
2387             out_flush();
2388             gui_update_cursor(FALSE, FALSE);
2389             maketitle();
2391             flushDisabled = NO;
2392             gui_mch_flush();
2393         }
2394     }
2396     if ([args objectForKey:@"remoteID"]) {
2397         // NOTE: We have to delay processing any ODB related arguments since
2398         // the file(s) may not be opened until the input buffer is processed.
2399         [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2400                                withObject:args
2401                             waitUntilDone:NO];
2402     }
2404     NSString *rangeString = [args objectForKey:@"selectionRange"];
2405     if (rangeString) {
2406         // Build a command line string that will select the given range of
2407         // lines.  If range.length == 0, then position the cursor on the given
2408         // line but do not select.
2409         NSRange range = NSRangeFromString(rangeString);
2410         NSString *cmd;
2411         if (range.length > 0) {
2412             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2413                     NSMaxRange(range), range.location];
2414         } else {
2415             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2416                     range.location];
2417         }
2419         [self addInput:cmd];
2420     }
2422     NSString *searchText = [args objectForKey:@"searchText"];
2423     if (searchText) {
2424         // TODO: Searching is an exclusive motion, so if the pattern would
2425         // match on row 0 column 0 then this pattern will miss that match.
2426         [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@<CR>",
2427                 searchText]];
2428     }
2431 - (BOOL)checkForModifiedBuffers
2433     buf_T *buf;
2434     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2435         if (bufIsChanged(buf)) {
2436             return YES;
2437         }
2438     }
2440     return NO;
2443 - (void)addInput:(NSString *)input
2445     char_u *s = (char_u*)[input UTF8String];
2447 #ifdef FEAT_MBYTE
2448     s = CONVERT_FROM_UTF8(s);
2449 #endif
2451     server_to_input_buf(s);
2453 #ifdef FEAT_MBYTE
2454     CONVERT_FROM_UTF8_FREE(s);
2455 #endif
2458 @end // MMBackend (Private)
2463 @implementation MMBackend (ClientServer)
2465 - (NSString *)connectionNameFromServerName:(NSString *)name
2467     NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2469     return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2470         lowercaseString];
2473 - (NSConnection *)connectionForServerName:(NSString *)name
2475     // TODO: Try 'name%d' if 'name' fails.
2476     NSString *connName = [self connectionNameFromServerName:name];
2477     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2479     if (!svrConn) {
2480         svrConn = [NSConnection connectionWithRegisteredName:connName
2481                                                            host:nil];
2482         // Try alternate server...
2483         if (!svrConn && alternateServerName) {
2484             //NSLog(@"  trying to connect to alternate server: %@",
2485             //        alternateServerName);
2486             connName = [self connectionNameFromServerName:alternateServerName];
2487             svrConn = [NSConnection connectionWithRegisteredName:connName
2488                                                             host:nil];
2489         }
2491         // Try looking for alternate servers...
2492         if (!svrConn) {
2493             //NSLog(@"  looking for alternate servers...");
2494             NSString *alt = [self alternateServerNameForName:name];
2495             if (alt != alternateServerName) {
2496                 //NSLog(@"  found alternate server: %@", string);
2497                 [alternateServerName release];
2498                 alternateServerName = [alt copy];
2499             }
2500         }
2502         // Try alternate server again...
2503         if (!svrConn && alternateServerName) {
2504             //NSLog(@"  trying to connect to alternate server: %@",
2505             //        alternateServerName);
2506             connName = [self connectionNameFromServerName:alternateServerName];
2507             svrConn = [NSConnection connectionWithRegisteredName:connName
2508                                                             host:nil];
2509         }
2511         if (svrConn) {
2512             [connectionNameDict setObject:svrConn forKey:connName];
2514             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2515             [[NSNotificationCenter defaultCenter] addObserver:self
2516                     selector:@selector(serverConnectionDidDie:)
2517                         name:NSConnectionDidDieNotification object:svrConn];
2518         }
2519     }
2521     return svrConn;
2524 - (NSConnection *)connectionForServerPort:(int)port
2526     NSConnection *conn;
2527     NSEnumerator *e = [connectionNameDict objectEnumerator];
2529     while ((conn = [e nextObject])) {
2530         // HACK! Assume connection uses mach ports.
2531         if (port == [(NSMachPort*)[conn sendPort] machPort])
2532             return conn;
2533     }
2535     return nil;
2538 - (void)serverConnectionDidDie:(NSNotification *)notification
2540     //NSLog(@"%s%@", _cmd, notification);
2542     NSConnection *svrConn = [notification object];
2544     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2545     [[NSNotificationCenter defaultCenter]
2546             removeObserver:self
2547                       name:NSConnectionDidDieNotification
2548                     object:svrConn];
2550     [connectionNameDict removeObjectsForKeys:
2551         [connectionNameDict allKeysForObject:svrConn]];
2553     // HACK! Assume connection uses mach ports.
2554     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2555     NSNumber *key = [NSNumber numberWithInt:port];
2557     [clientProxyDict removeObjectForKey:key];
2558     [serverReplyDict removeObjectForKey:key];
2561 - (void)addClient:(NSDistantObject *)client
2563     NSConnection *conn = [client connectionForProxy];
2564     // HACK! Assume connection uses mach ports.
2565     int port = [(NSMachPort*)[conn sendPort] machPort];
2566     NSNumber *key = [NSNumber numberWithInt:port];
2568     if (![clientProxyDict objectForKey:key]) {
2569         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2570         [clientProxyDict setObject:client forKey:key];
2571     }
2573     // NOTE: 'clientWindow' is a global variable which is used by <client>
2574     clientWindow = port;
2577 - (NSString *)alternateServerNameForName:(NSString *)name
2579     if (!(name && [name length] > 0))
2580         return nil;
2582     // Only look for alternates if 'name' doesn't end in a digit.
2583     unichar lastChar = [name characterAtIndex:[name length]-1];
2584     if (lastChar >= '0' && lastChar <= '9')
2585         return nil;
2587     // Look for alternates among all current servers.
2588     NSArray *list = [self serverList];
2589     if (!(list && [list count] > 0))
2590         return nil;
2592     // Filter out servers starting with 'name' and ending with a number. The
2593     // (?i) pattern ensures that the match is case insensitive.
2594     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2595     NSPredicate *pred = [NSPredicate predicateWithFormat:
2596             @"SELF MATCHES %@", pat];
2597     list = [list filteredArrayUsingPredicate:pred];
2598     if ([list count] > 0) {
2599         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2600         return [list objectAtIndex:0];
2601     }
2603     return nil;
2606 @end // MMBackend (ClientServer)
2611 @implementation NSString (MMServerNameCompare)
2612 - (NSComparisonResult)serverNameCompare:(NSString *)string
2614     return [self compare:string
2615                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2617 @end
2622 static int eventModifierFlagsToVimModMask(int modifierFlags)
2624     int modMask = 0;
2626     if (modifierFlags & NSShiftKeyMask)
2627         modMask |= MOD_MASK_SHIFT;
2628     if (modifierFlags & NSControlKeyMask)
2629         modMask |= MOD_MASK_CTRL;
2630     if (modifierFlags & NSAlternateKeyMask)
2631         modMask |= MOD_MASK_ALT;
2632     if (modifierFlags & NSCommandKeyMask)
2633         modMask |= MOD_MASK_CMD;
2635     return modMask;
2638 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2640     int modMask = 0;
2642     if (modifierFlags & NSShiftKeyMask)
2643         modMask |= MOUSE_SHIFT;
2644     if (modifierFlags & NSControlKeyMask)
2645         modMask |= MOUSE_CTRL;
2646     if (modifierFlags & NSAlternateKeyMask)
2647         modMask |= MOUSE_ALT;
2649     return modMask;
2652 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2654     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2656     return (buttonNumber >= 0 && buttonNumber < 3)
2657             ? mouseButton[buttonNumber] : -1;
2662 // This function is modeled after the VimToPython function found in if_python.c
2663 // NB This does a deep copy by value, it does not lookup references like the
2664 // VimToPython function does.  This is because I didn't want to deal with the
2665 // retain cycles that this would create, and we can cover 99% of the use cases
2666 // by ignoring it.  If we ever switch to using GC in MacVim then this
2667 // functionality can be implemented easily.
2668 static id vimToCocoa(typval_T * tv, int depth)
2670     id result = nil;
2671     id newObj = nil;
2674     // Avoid infinite recursion
2675     if (depth > 100) {
2676         return nil;
2677     }
2679     if (tv->v_type == VAR_STRING) {
2680         char_u * val = tv->vval.v_string;
2681         // val can be NULL if the string is empty
2682         if (!val) {
2683             result = [NSString string];
2684         } else {
2685 #ifdef FEAT_MBYTE
2686             val = CONVERT_TO_UTF8(val);
2687 #endif
2688             result = [NSString stringWithUTF8String:(char*)val];
2689 #ifdef FEAT_MBYTE
2690             CONVERT_TO_UTF8_FREE(val);
2691 #endif
2692         }
2693     } else if (tv->v_type == VAR_NUMBER) {
2694         // looks like sizeof(varnumber_T) is always <= sizeof(long)
2695         result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2696     } else if (tv->v_type == VAR_LIST) {
2697         list_T * list = tv->vval.v_list;
2698         listitem_T * curr;
2700         NSMutableArray * arr = result = [NSMutableArray array];
2702         if (list != NULL) {
2703             for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2704                 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2705                 [arr addObject:newObj];
2706             }
2707         }
2708     } else if (tv->v_type == VAR_DICT) {
2709         NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2711         if (tv->vval.v_dict != NULL) {
2712             hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2713             int todo = ht->ht_used;
2714             hashitem_T * hi;
2715             dictitem_T * di;
2717             for (hi = ht->ht_array; todo > 0; ++hi) {
2718                 if (!HASHITEM_EMPTY(hi)) {
2719                     --todo;
2721                     di = dict_lookup(hi);
2722                     newObj = vimToCocoa(&di->di_tv, depth + 1);
2724                     char_u * keyval = hi->hi_key;
2725 #ifdef FEAT_MBYTE
2726                     keyval = CONVERT_TO_UTF8(keyval);
2727 #endif
2728                     NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2729 #ifdef FEAT_MBYTE
2730                     CONVERT_TO_UTF8_FREE(keyval);
2731 #endif
2732                     [dict setObject:newObj forKey:key];
2733                 }
2734             }
2735         }
2736     } else { // only func refs should fall into this category?
2737         result = nil;
2738     }
2740     return result;
2744 // This function is modeled after eval_client_expr_to_string found in main.c
2745 // Returns nil if there was an error evaluating the expression, and writes a
2746 // message to errorStr.
2747 // TODO Get the error that occurred while evaluating the expression in vim
2748 // somehow.
2749 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2752     char_u *s = (char_u*)[expr UTF8String];
2754 #ifdef FEAT_MBYTE
2755     s = CONVERT_FROM_UTF8(s);
2756 #endif
2758     int save_dbl = debug_break_level;
2759     int save_ro = redir_off;
2761     debug_break_level = -1;
2762     redir_off = 0;
2763     ++emsg_skip;
2765     typval_T * tvres = eval_expr(s, NULL);
2767     debug_break_level = save_dbl;
2768     redir_off = save_ro;
2769     --emsg_skip;
2771     setcursor();
2772     out_flush();
2774 #ifdef FEAT_MBYTE
2775     CONVERT_FROM_UTF8_FREE(s);
2776 #endif
2778 #ifdef FEAT_GUI
2779     if (gui.in_use)
2780         gui_update_cursor(FALSE, FALSE);
2781 #endif
2783     if (tvres == NULL) {
2784         free_tv(tvres);
2785         *errstr = @"Expression evaluation failed.";
2786     }
2788     id res = vimToCocoa(tvres, 1);
2790     free_tv(tvres);
2792     if (res == nil) {
2793         *errstr = @"Conversion to cocoa values failed.";
2794     }
2796     return res;
2802 @implementation NSString (VimStrings)
2804 + (id)stringWithVimString:(char_u *)s
2806     // This method ensures a non-nil string is returned.  If 's' cannot be
2807     // converted to a utf-8 string it is assumed to be latin-1.  If conversion
2808     // still fails an empty NSString is returned.
2809     NSString *string = nil;
2810     if (s) {
2811 #ifdef FEAT_MBYTE
2812         s = CONVERT_TO_UTF8(s);
2813 #endif
2814         string = [NSString stringWithUTF8String:(char*)s];
2815         if (!string) {
2816             // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2817             // latin-1?
2818             string = [NSString stringWithCString:(char*)s
2819                                         encoding:NSISOLatin1StringEncoding];
2820         }
2821 #ifdef FEAT_MBYTE
2822         CONVERT_TO_UTF8_FREE(s);
2823 #endif
2824     }
2826     return string != nil ? string : [NSString string];
2829 - (char_u *)vimStringSave
2831     char_u *s = (char_u*)[self UTF8String], *ret = NULL;
2833 #ifdef FEAT_MBYTE
2834     s = CONVERT_FROM_UTF8(s);
2835 #endif
2836     ret = vim_strsave(s);
2837 #ifdef FEAT_MBYTE
2838     CONVERT_FROM_UTF8_FREE(s);
2839 #endif
2841     return ret;
2844 @end // NSString (VimStrings)