Update 'gfw' on Cmd--/Cmd-+
[MacVim.git] / src / MacVim / MMBackend.m
blobd3b3fea8a08081e8a8e6dcaed40db93c0ad6d8a2
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 [MMAppController processInput:forIdentifier:].
25  *
26  * The client/server functionality of Vim is handled by the backend.  It sets
27  * up a named NSConnection to which other Vim processes can connect.
28  */
30 #import "MMBackend.h"
34 // NOTE: Colors in MMBackend are stored as unsigned ints on the form 0xaarrggbb
35 // whereas colors in Vim are int without the alpha component.  Also note that
36 // 'transp' is assumed to be a value between 0 and 100.
37 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
38 #define MM_COLOR_WITH_TRANSP(col,transp) \
39     ((unsigned)( ((col)&0xffffff) \
40         | ((((unsigned)((((100-(transp))*255)/100)+.5f))&0xff)<<24) ))
42 // Values for window layout (must match values in main.c).
43 #define WIN_HOR     1       // "-o" horizontally split windows
44 #define WIN_VER     2       // "-O" vertically split windows
45 #define WIN_TABS    3       // "-p" windows on tab pages
47 static unsigned MMServerMax = 1000;
49 // TODO: Move to separate file.
50 static int eventModifierFlagsToVimModMask(int modifierFlags);
51 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
52 static int eventButtonNumberToVimMouseButton(int buttonNumber);
54 // In gui_macvim.m
55 vimmenu_T *menu_for_descriptor(NSArray *desc);
57 static id evalExprCocoa(NSString * expr, NSString ** errstr);
60 enum {
61     MMBlinkStateNone = 0,
62     MMBlinkStateOn,
63     MMBlinkStateOff
66 static NSString *MMSymlinkWarningString =
67     @"\n\n\tMost likely this is because you have symlinked directly to\n"
68      "\tthe Vim binary, which Cocoa does not allow.  Please use an\n"
69      "\talias or the mvim shell script instead.  If you have not used\n"
70      "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
73 extern GuiFont gui_mch_retain_font(GuiFont font);
77 @interface NSString (MMServerNameCompare)
78 - (NSComparisonResult)serverNameCompare:(NSString *)string;
79 @end
84 @interface MMBackend (Private)
85 - (void)clearDrawData;
86 - (void)didChangeWholeLine;
87 - (void)waitForDialogReturn;
88 - (void)insertVimStateMessage;
89 - (void)processInputQueue;
90 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
91 + (NSDictionary *)specialKeys;
92 - (void)handleInsertText:(NSString *)text;
93 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
94 - (void)queueMessage:(int)msgid data:(NSData *)data;
95 - (void)connectionDidDie:(NSNotification *)notification;
96 - (void)blinkTimerFired:(NSTimer *)timer;
97 - (void)focusChange:(BOOL)on;
98 - (void)handleToggleToolbar;
99 - (void)handleScrollbarEvent:(NSData *)data;
100 - (void)handleSetFont:(NSData *)data;
101 - (void)handleDropFiles:(NSData *)data;
102 - (void)handleDropString:(NSData *)data;
103 - (void)startOdbEditWithArguments:(NSDictionary *)args;
104 - (void)handleXcodeMod:(NSData *)data;
105 - (void)handleOpenWithArguments:(NSDictionary *)args;
106 - (BOOL)checkForModifiedBuffers;
107 - (void)addInput:(NSString *)input;
108 - (BOOL)unusedEditor;
109 - (void)redrawScreen;
110 - (void)handleFindReplace:(NSDictionary *)args;
111 @end
115 @interface MMBackend (ClientServer)
116 - (NSString *)connectionNameFromServerName:(NSString *)name;
117 - (NSConnection *)connectionForServerName:(NSString *)name;
118 - (NSConnection *)connectionForServerPort:(int)port;
119 - (void)serverConnectionDidDie:(NSNotification *)notification;
120 - (void)addClient:(NSDistantObject *)client;
121 - (NSString *)alternateServerNameForName:(NSString *)name;
122 @end
126 @implementation MMBackend
128 + (MMBackend *)sharedInstance
130     static MMBackend *singleton = nil;
131     return singleton ? singleton : (singleton = [MMBackend new]);
134 - (id)init
136     self = [super init];
137     if (!self) return nil;
139     outputQueue = [[NSMutableArray alloc] init];
140     inputQueue = [[NSMutableArray alloc] init];
141     drawData = [[NSMutableData alloc] initWithCapacity:1024];
142     connectionNameDict = [[NSMutableDictionary alloc] init];
143     clientProxyDict = [[NSMutableDictionary alloc] init];
144     serverReplyDict = [[NSMutableDictionary alloc] init];
146     NSBundle *mainBundle = [NSBundle mainBundle];
147     NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
148     if (path)
149         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
151     path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
152     if (path)
153         sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
154             retain];
156     path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
157     if (path)
158         actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
160     if (!(colorDict && sysColorDict && actionDict))
161         NSLog(@"ERROR: Failed to load dictionaries.%@",
162                 MMSymlinkWarningString);
164     return self;
167 - (void)dealloc
169     //NSLog(@"%@ %s", [self className], _cmd);
170     [[NSNotificationCenter defaultCenter] removeObserver:self];
172     gui_mch_free_font(oldWideFont);  oldWideFont = NOFONT;
173     [blinkTimer release];  blinkTimer = nil;
174     [alternateServerName release];  alternateServerName = nil;
175     [serverReplyDict release];  serverReplyDict = nil;
176     [clientProxyDict release];  clientProxyDict = nil;
177     [connectionNameDict release];  connectionNameDict = nil;
178     [inputQueue release];  inputQueue = nil;
179     [outputQueue release];  outputQueue = nil;
180     [drawData release];  drawData = nil;
181     [connection release];  connection = nil;
182     [actionDict release];  actionDict = nil;
183     [sysColorDict release];  sysColorDict = nil;
184     [colorDict release];  colorDict = nil;
186     [super dealloc];
189 - (void)setBackgroundColor:(int)color
191     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
194 - (void)setForegroundColor:(int)color
196     foregroundColor = MM_COLOR(color);
199 - (void)setSpecialColor:(int)color
201     specialColor = MM_COLOR(color);
204 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
206     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
207     defaultForegroundColor = MM_COLOR(fg);
209     NSMutableData *data = [NSMutableData data];
211     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
212     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
214     [self queueMessage:SetDefaultColorsMsgID data:data];
217 - (NSConnection *)connection
219     if (!connection) {
220         // NOTE!  If the name of the connection changes here it must also be
221         // updated in MMAppController.m.
222         NSString *name = [NSString stringWithFormat:@"%@-connection",
223                [[NSBundle mainBundle] bundlePath]];
225         connection = [NSConnection connectionWithRegisteredName:name host:nil];
226         [connection retain];
227     }
229     // NOTE: 'connection' may be nil here.
230     return connection;
233 - (NSDictionary *)actionDict
235     return actionDict;
238 - (int)initialWindowLayout
240     return initialWindowLayout;
243 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
245     [self queueMessage:msgid data:[props dictionaryAsData]];
248 - (BOOL)checkin
250     if (![self connection]) {
251         if (waitForAck) {
252             // This is a preloaded process and as such should not cause the
253             // MacVim to be opened.  We probably got here as a result of the
254             // user quitting MacVim while the process was preloading, so exit
255             // this process too.
256             // (Don't use mch_exit() since it assumes the process has properly
257             // started.)
258             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     @try {
331         [[NSNotificationCenter defaultCenter] addObserver:self
332                 selector:@selector(connectionDidDie:)
333                     name:NSConnectionDidDieNotification object:connection];
335         appProxy = [[connection rootProxy] retain];
336         [appProxy setProtocolForProxy:@protocol(MMAppProtocol)];
338         // NOTE: We do not set any new timeout values for the connection to the
339         // frontend.  This means that if the frontend is "stuck" (e.g. in a
340         // modal loop) then any calls to the frontend will block indefinitely
341         // (the default timeouts are huge).
343         int pid = [[NSProcessInfo processInfo] processIdentifier];
345         identifier = [appProxy connectBackend:self pid:pid];
346         return YES;
347     }
348     @catch (NSException *e) {
349         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
350     }
352     return NO;
355 - (BOOL)openGUIWindow
357     [self queueMessage:OpenWindowMsgID 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     [self clearDrawData];
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)];
401     if (left == 0 && right == gui.num_cols-1)
402         [self didChangeWholeLine];
405 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
406              cells:(int)cells flags:(int)flags
408     if (len <= 0 || cells <= 0) return;
410     int type = DrawStringDrawType;
412     [drawData appendBytes:&type length:sizeof(int)];
414     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
415     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
416     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
417     [drawData appendBytes:&row length:sizeof(int)];
418     [drawData appendBytes:&col length:sizeof(int)];
419     [drawData appendBytes:&cells length:sizeof(int)];
420     [drawData appendBytes:&flags length:sizeof(int)];
421     [drawData appendBytes:&len length:sizeof(int)];
422     [drawData appendBytes:s length:len];
425 - (void)insertLinesFromRow:(int)row count:(int)count
426               scrollBottom:(int)bottom left:(int)left right:(int)right
428     int type = InsertLinesDrawType;
430     [drawData appendBytes:&type length:sizeof(int)];
432     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
433     [drawData appendBytes:&row length:sizeof(int)];
434     [drawData appendBytes:&count length:sizeof(int)];
435     [drawData appendBytes:&bottom length:sizeof(int)];
436     [drawData appendBytes:&left length:sizeof(int)];
437     [drawData appendBytes:&right length:sizeof(int)];
439     if (left == 0 && right == gui.num_cols-1)
440         [self didChangeWholeLine];
443 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
444                fraction:(int)percent color:(int)color
446     int type = DrawCursorDrawType;
447     unsigned uc = MM_COLOR(color);
449     [drawData appendBytes:&type length:sizeof(int)];
451     [drawData appendBytes:&uc length:sizeof(unsigned)];
452     [drawData appendBytes:&row length:sizeof(int)];
453     [drawData appendBytes:&col length:sizeof(int)];
454     [drawData appendBytes:&shape length:sizeof(int)];
455     [drawData appendBytes:&percent length:sizeof(int)];
458 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
459                    numColumns:(int)nc invert:(int)invert
461     int type = DrawInvertedRectDrawType;
462     [drawData appendBytes:&type length:sizeof(int)];
464     [drawData appendBytes:&row length:sizeof(int)];
465     [drawData appendBytes:&col length:sizeof(int)];
466     [drawData appendBytes:&nr length:sizeof(int)];
467     [drawData appendBytes:&nc length:sizeof(int)];
468     [drawData appendBytes:&invert length:sizeof(int)];
471 - (void)update
473     // Keep running the run-loop until there is no more input to process.
474     while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
475             == kCFRunLoopRunHandledSource)
476         ;   // do nothing
479 - (void)flushQueue:(BOOL)force
481     // NOTE: This variable allows for better control over when the queue is
482     // flushed.  It can be set to YES at the beginning of a sequence of calls
483     // that may potentially add items to the queue, and then restored back to
484     // NO.
485     if (flushDisabled) return;
487     if ([drawData length] > 0) {
488         // HACK!  Detect changes to 'guifontwide'.
489         if (gui.wide_font != oldWideFont) {
490             gui_mch_free_font(oldWideFont);
491             oldWideFont = gui_mch_retain_font(gui.wide_font);
492             [self setFont:oldWideFont wide:YES];
493         }
495         int type = SetCursorPosDrawType;
496         [drawData appendBytes:&type length:sizeof(type)];
497         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
498         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
500         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
501         [self clearDrawData];
502     }
504     if ([outputQueue count] > 0) {
505         [self insertVimStateMessage];
507         @try {
508             //NSLog(@"[%s] Flushing (count=%d)", _cmd, [outputQueue count]);
509             [appProxy processInput:outputQueue forIdentifier:identifier];
510         }
511         @catch (NSException *e) {
512             NSLog(@"[%s] Exception caught: \"%@\"", _cmd, e);
513             NSLog(@"outputQueue(len:%d)=%@", [outputQueue count]/2,
514                     outputQueue);
515             if (![connection isValid]) {
516                 NSLog(@"WARNING! Connection is invalid, exit now!");
517                 NSLog(@"waitForAck=%d got_int=%d", waitForAck, got_int);
518                 mch_exit(-1);
519             }
520         }
522         [outputQueue removeAllObjects];
523     }
526 - (BOOL)waitForInput:(int)milliseconds
528     // Return NO if we timed out waiting for input, otherwise return YES.
529     BOOL inputReceived = NO;
531     // Only start the run loop if the input queue is empty, otherwise process
532     // the input first so that the input on queue isn't delayed.
533     if ([inputQueue count]) {
534         inputReceived = YES;
535     } else {
536         // Wait for the specified amount of time, unless 'milliseconds' is
537         // negative in which case we wait "forever" (1e6 seconds translates to
538         // approximately 11 days).
539         CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
541         while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
542                 == kCFRunLoopRunHandledSource) {
543             // In order to ensure that all input on the run-loop has been
544             // processed we set the timeout to 0 and keep processing until the
545             // run-loop times out.
546             dt = 0.0;
547             inputReceived = YES;
548         }
549     }
551     // The above calls may have placed messages on the input queue so process
552     // it now.  This call may enter a blocking loop.
553     if ([inputQueue count] > 0)
554         [self processInputQueue];
556     return inputReceived;
559 - (void)exit
561     // NOTE: This is called if mch_exit() is called.  Since we assume here that
562     // the process has started properly, be sure to use exit() instead of
563     // mch_exit() to prematurely terminate a process (or set 'isTerminating'
564     // first).
566     // Make sure no connectionDidDie: notification is received now that we are
567     // already exiting.
568     [[NSNotificationCenter defaultCenter] removeObserver:self];
570     // The 'isTerminating' flag indicates that the frontend is also exiting so
571     // there is no need to flush any more output since the frontend won't look
572     // at it anyway.
573     if (!isTerminating && [connection isValid]) {
574         @try {
575             // Flush the entire queue in case a VimLeave autocommand added
576             // something to the queue.
577             [self queueMessage:CloseWindowMsgID data:nil];
578             [appProxy processInput:outputQueue forIdentifier:identifier];
579         }
580         @catch (NSException *e) {
581             NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
582         }
584         // NOTE: If Cmd-w was pressed to close the window the menu is briefly
585         // highlighted and during this pause the frontend won't receive any DO
586         // messages.  If the Vim process exits before this highlighting has
587         // finished Cocoa will emit the following error message:
588         //   *** -[NSMachPort handlePortMessage:]: dropping incoming DO message
589         //   because the connection or ports are invalid
590         // To avoid this warning we delay here.  If the warning still appears
591         // this delay may need to be increased.
592         usleep(150000);
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
602 - (void)selectTab:(int)index
604     //NSLog(@"%s%d", _cmd, index);
606     index -= 1;
607     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
608     [self queueMessage:SelectTabMsgID data:data];
611 - (void)updateTabBar
613     //NSLog(@"%s", _cmd);
615     NSMutableData *data = [NSMutableData data];
617     int idx = tabpage_index(curtab) - 1;
618     [data appendBytes:&idx length:sizeof(int)];
620     tabpage_T *tp;
621     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
622         // Count the number of windows in the tabpage.
623         //win_T *wp = tp->tp_firstwin;
624         //int wincount;
625         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
626         //[data appendBytes:&wincount length:sizeof(int)];
628         int tabProp = MMTabInfoCount;
629         [data appendBytes:&tabProp length:sizeof(int)];
630         for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
631             // This function puts the label of the tab in the global 'NameBuff'.
632             get_tabline_label(tp, (tabProp == MMTabToolTip));
633             NSString *s = [NSString stringWithVimString:NameBuff];
634             int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
635             if (len < 0)
636                 len = 0;
638             [data appendBytes:&len length:sizeof(int)];
639             if (len > 0)
640                 [data appendBytes:[s UTF8String] length:len];
641         }
642     }
644     [self queueMessage:UpdateTabBarMsgID data:data];
647 - (BOOL)tabBarVisible
649     return tabBarVisible;
652 - (void)showTabBar:(BOOL)enable
654     tabBarVisible = enable;
656     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
657     [self queueMessage:msgid data:nil];
660 - (void)setRows:(int)rows columns:(int)cols
662     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
664     int dim[] = { rows, cols };
665     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
667     [self queueMessage:SetTextDimensionsMsgID data:data];
670 - (void)setWindowTitle:(char *)title
672     NSMutableData *data = [NSMutableData data];
673     int len = strlen(title);
674     if (len <= 0) return;
676     [data appendBytes:&len length:sizeof(int)];
677     [data appendBytes:title length:len];
679     [self queueMessage:SetWindowTitleMsgID data:data];
682 - (void)setDocumentFilename:(char *)filename
684     NSMutableData *data = [NSMutableData data];
685     int len = filename ? strlen(filename) : 0;
687     [data appendBytes:&len length:sizeof(int)];
688     if (len > 0)
689         [data appendBytes:filename length:len];
691     [self queueMessage:SetDocumentFilenameMsgID data:data];
694 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
696     char_u *s = NULL;
698     [self queueMessage:BrowseForFileMsgID properties:attr];
699     [self flushQueue:YES];
701     @try {
702         [self waitForDialogReturn];
704         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
705             s = [dialogReturn vimStringSave];
707         [dialogReturn release];  dialogReturn = nil;
708     }
709     @catch (NSException *e) {
710         NSLog(@"[%s] Exception caught: \"%@\"", _cmd, e);
711     }
713     return (char *)s;
716 - (oneway void)setDialogReturn:(in bycopy id)obj
718     // NOTE: This is called by
719     //   - [MMVimController panelDidEnd:::], and
720     //   - [MMVimController alertDidEnd:::],
721     // to indicate that a save/open panel or alert has finished.
723     // We want to distinguish between "no dialog return yet" and "dialog
724     // returned nothing".  The former can be tested with dialogReturn == nil,
725     // the latter with dialogReturn == [NSNull null].
726     if (!obj) obj = [NSNull null];
728     if (obj != dialogReturn) {
729         [dialogReturn release];
730         dialogReturn = [obj retain];
731     }
734 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
736     int retval = 0;
738     [self queueMessage:ShowDialogMsgID properties:attr];
739     [self flushQueue:YES];
741     @try {
742         [self waitForDialogReturn];
744         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
745                 && [dialogReturn count]) {
746             retval = [[dialogReturn objectAtIndex:0] intValue];
747             if (txtfield && [dialogReturn count] > 1) {
748                 NSString *retString = [dialogReturn objectAtIndex:1];
749                 char_u *ret = (char_u*)[retString UTF8String];
750 #ifdef FEAT_MBYTE
751                 ret = CONVERT_FROM_UTF8(ret);
752 #endif
753                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
754 #ifdef FEAT_MBYTE
755                 CONVERT_FROM_UTF8_FREE(ret);
756 #endif
757             }
758         }
760         [dialogReturn release]; dialogReturn = nil;
761     }
762     @catch (NSException *e) {
763         NSLog(@"[%s] Exception caught: \"%@\"", _cmd, e);
764     }
766     return retval;
769 - (void)showToolbar:(int)enable flags:(int)flags
771     NSMutableData *data = [NSMutableData data];
773     [data appendBytes:&enable length:sizeof(int)];
774     [data appendBytes:&flags length:sizeof(int)];
776     [self queueMessage:ShowToolbarMsgID data:data];
779 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
781     NSMutableData *data = [NSMutableData data];
783     [data appendBytes:&ident length:sizeof(long)];
784     [data appendBytes:&type length:sizeof(int)];
786     [self queueMessage:CreateScrollbarMsgID data:data];
789 - (void)destroyScrollbarWithIdentifier:(long)ident
791     NSMutableData *data = [NSMutableData data];
792     [data appendBytes:&ident length:sizeof(long)];
794     [self queueMessage:DestroyScrollbarMsgID data:data];
797 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
799     NSMutableData *data = [NSMutableData data];
801     [data appendBytes:&ident length:sizeof(long)];
802     [data appendBytes:&visible length:sizeof(int)];
804     [self queueMessage:ShowScrollbarMsgID data:data];
807 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
809     NSMutableData *data = [NSMutableData data];
811     [data appendBytes:&ident length:sizeof(long)];
812     [data appendBytes:&pos length:sizeof(int)];
813     [data appendBytes:&len length:sizeof(int)];
815     [self queueMessage:SetScrollbarPositionMsgID data:data];
818 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
819                     identifier:(long)ident
821     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
822     float prop = (float)size/(max+1);
823     if (fval < 0) fval = 0;
824     else if (fval > 1.0f) fval = 1.0f;
825     if (prop < 0) prop = 0;
826     else if (prop > 1.0f) prop = 1.0f;
828     NSMutableData *data = [NSMutableData data];
830     [data appendBytes:&ident length:sizeof(long)];
831     [data appendBytes:&fval length:sizeof(float)];
832     [data appendBytes:&prop length:sizeof(float)];
834     [self queueMessage:SetScrollbarThumbMsgID data:data];
837 - (void)setFont:(GuiFont)font wide:(BOOL)wide
839     NSString *fontName = (NSString *)font;
840     float size = 0;
841     NSArray *components = [fontName componentsSeparatedByString:@":"];
842     if ([components count] == 2) {
843         size = [[components lastObject] floatValue];
844         fontName = [components objectAtIndex:0];
845     }
847     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
848     NSMutableData *data = [NSMutableData data];
849     [data appendBytes:&size length:sizeof(float)];
850     [data appendBytes:&len length:sizeof(int)];
852     if (len > 0)
853         [data appendBytes:[fontName UTF8String] length:len];
854     else if (!wide)
855         return;     // Only the wide font can be set to nothing
857     [self queueMessage:(wide ? SetWideFontMsgID : SetFontMsgID) data:data];
860 - (void)executeActionWithName:(NSString *)name
862     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
864     if (len > 0) {
865         NSMutableData *data = [NSMutableData data];
867         [data appendBytes:&len length:sizeof(int)];
868         [data appendBytes:[name UTF8String] length:len];
870         [self queueMessage:ExecuteActionMsgID data:data];
871     }
874 - (void)setMouseShape:(int)shape
876     NSMutableData *data = [NSMutableData data];
877     [data appendBytes:&shape length:sizeof(int)];
878     [self queueMessage:SetMouseShapeMsgID data:data];
881 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
883     // Vim specifies times in milliseconds, whereas Cocoa wants them in
884     // seconds.
885     blinkWaitInterval = .001f*wait;
886     blinkOnInterval = .001f*on;
887     blinkOffInterval = .001f*off;
890 - (void)startBlink
892     if (blinkTimer) {
893         [blinkTimer invalidate];
894         [blinkTimer release];
895         blinkTimer = nil;
896     }
898     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
899             && gui.in_focus) {
900         blinkState = MMBlinkStateOn;
901         blinkTimer =
902             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
903                                               target:self
904                                             selector:@selector(blinkTimerFired:)
905                                             userInfo:nil repeats:NO] retain];
906         gui_update_cursor(TRUE, FALSE);
907         [self flushQueue:YES];
908     }
911 - (void)stopBlink
913     if (MMBlinkStateOff == blinkState) {
914         gui_update_cursor(TRUE, FALSE);
915         [self flushQueue:YES];
916     }
918     blinkState = MMBlinkStateNone;
921 - (void)adjustLinespace:(int)linespace
923     NSMutableData *data = [NSMutableData data];
924     [data appendBytes:&linespace length:sizeof(int)];
925     [self queueMessage:AdjustLinespaceMsgID data:data];
928 - (void)activate
930     [self queueMessage:ActivateMsgID data:nil];
933 - (void)setPreEditRow:(int)row column:(int)col
935     NSMutableData *data = [NSMutableData data];
936     [data appendBytes:&row length:sizeof(int)];
937     [data appendBytes:&col length:sizeof(int)];
938     [self queueMessage:SetPreEditPositionMsgID data:data];
941 - (int)lookupColorWithKey:(NSString *)key
943     if (!(key && [key length] > 0))
944         return INVALCOLOR;
946     NSString *stripKey = [[[[key lowercaseString]
947         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
948             componentsSeparatedByString:@" "]
949                componentsJoinedByString:@""];
951     if (stripKey && [stripKey length] > 0) {
952         // First of all try to lookup key in the color dictionary; note that
953         // all keys in this dictionary are lowercase with no whitespace.
954         id obj = [colorDict objectForKey:stripKey];
955         if (obj) return [obj intValue];
957         // The key was not in the dictionary; is it perhaps of the form
958         // #rrggbb?
959         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
960             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
961             [scanner setScanLocation:1];
962             unsigned hex = 0;
963             if ([scanner scanHexInt:&hex]) {
964                 return (int)hex;
965             }
966         }
968         // As a last resort, check if it is one of the system defined colors.
969         // The keys in this dictionary are also lowercase with no whitespace.
970         obj = [sysColorDict objectForKey:stripKey];
971         if (obj) {
972             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
973             if (col) {
974                 float r, g, b, a;
975                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
976                 [col getRed:&r green:&g blue:&b alpha:&a];
977                 return (((int)(r*255+.5f) & 0xff) << 16)
978                      + (((int)(g*255+.5f) & 0xff) << 8)
979                      +  ((int)(b*255+.5f) & 0xff);
980             }
981         }
982     }
984     //NSLog(@"WARNING: No color with key %@ found.", stripKey);
985     return INVALCOLOR;
988 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
990     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
991     id obj;
993     while ((obj = [e nextObject])) {
994         if ([value isEqual:obj])
995             return YES;
996     }
998     return NO;
1001 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1003     NSMutableData *data = [NSMutableData data];
1004     [data appendBytes:&fuoptions length:sizeof(int)];
1005     bg = MM_COLOR(bg);
1006     [data appendBytes:&bg length:sizeof(int)];
1007     [self queueMessage:EnterFullscreenMsgID data:data];
1010 - (void)leaveFullscreen
1012     [self queueMessage:LeaveFullscreenMsgID data:nil];
1015 - (void)setFullscreenBackgroundColor:(int)color
1017     NSMutableData *data = [NSMutableData data];
1018     color = MM_COLOR(color);
1019     [data appendBytes:&color length:sizeof(int)];
1021     [self queueMessage:SetFullscreenColorMsgID data:data];
1024 - (void)setAntialias:(BOOL)antialias
1026     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1028     [self queueMessage:msgid data:nil];
1031 - (void)updateModifiedFlag
1033     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1034     // vice versa.
1035     int msgid = [self checkForModifiedBuffers]
1036             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1038     [self queueMessage:msgid data:nil];
1041 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1043     // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1044     // queue is processed since that only happens in waitForInput: (and Vim
1045     // regularly checks for Ctrl-C in between waiting for input).
1046     // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1047     // which waits on the run loop will fail to detect this message (e.g. in
1048     // waitForConnectionAcknowledgement).
1050     if (InsertTextMsgID == msgid && data != nil) {
1051         const void *bytes = [data bytes];
1052         bytes += sizeof(int);
1053         int len = *((int*)bytes);  bytes += sizeof(int);
1054         if (1 == len) {
1055             char_u *str = (char_u*)bytes;
1056             if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1057                     (str[0] == intr_char && intr_char != Ctrl_C)) {
1058                 got_int = TRUE;
1059                 [inputQueue removeAllObjects];
1060                 return;
1061             }
1062         }
1063     } else if (TerminateNowMsgID == msgid) {
1064         // Terminate immediately (the frontend is about to quit or this process
1065         // was aborted).  Don't preserve modified files since the user would
1066         // already have been presented with a dialog warning if there were any
1067         // modified files when we get here.
1068         isTerminating = YES;
1069         getout(0);
1070         return;
1071     }
1073     // Remove all previous instances of this message from the input queue, else
1074     // the input queue may fill up as a result of Vim not being able to keep up
1075     // with the speed at which new messages are received.
1076     // Keyboard input is never dropped, unless the input represents and
1077     // auto-repeated key.
1079     BOOL isKeyRepeat = NO;
1080     BOOL isKeyboardInput = NO;
1082     if (data && (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1083             CmdKeyMsgID == msgid)) {
1084         isKeyboardInput = YES;
1086         // The lowest bit of the first int is set if this key is a repeat.
1087         int flags = *((int*)[data bytes]);
1088         if (flags & 1)
1089             isKeyRepeat = YES;
1090     }
1092     // Keyboard input is not removed from the queue; repeats are ignored if
1093     // there already is keyboard input on the input queue.
1094     if (isKeyRepeat || !isKeyboardInput) {
1095         int i, count = [inputQueue count];
1096         for (i = 1; i < count; i+=2) {
1097             if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1098                 if (isKeyRepeat)
1099                     return;
1101                 [inputQueue removeObjectAtIndex:i];
1102                 [inputQueue removeObjectAtIndex:i-1];
1103                 break;
1104             }
1105         }
1106     }
1108     [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1109     [inputQueue addObject:(data ? (id)data : [NSNull null])];
1112 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1113                   errorString:(out bycopy NSString **)errstr
1115     return evalExprCocoa(expr, errstr);
1119 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1121     NSString *eval = nil;
1122     char_u *s = (char_u*)[expr UTF8String];
1124 #ifdef FEAT_MBYTE
1125     s = CONVERT_FROM_UTF8(s);
1126 #endif
1128     char_u *res = eval_client_expr_to_string(s);
1130 #ifdef FEAT_MBYTE
1131     CONVERT_FROM_UTF8_FREE(s);
1132 #endif
1134     if (res != NULL) {
1135         s = res;
1136 #ifdef FEAT_MBYTE
1137         s = CONVERT_TO_UTF8(s);
1138 #endif
1139         eval = [NSString stringWithUTF8String:(char*)s];
1140 #ifdef FEAT_MBYTE
1141         CONVERT_TO_UTF8_FREE(s);
1142 #endif
1143         vim_free(res);
1144     }
1146     return eval;
1149 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1151     // TODO: This method should share code with clip_mch_request_selection().
1153     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1154         // If there is no pasteboard, return YES to indicate that there is text
1155         // to copy.
1156         if (!pboard)
1157             return YES;
1159         clip_copy_selection();
1161         // Get the text to put on the pasteboard.
1162         long_u llen = 0; char_u *str = 0;
1163         int type = clip_convert_selection(&str, &llen, &clip_star);
1164         if (type < 0)
1165             return NO;
1166         
1167         // TODO: Avoid overflow.
1168         int len = (int)llen;
1169 #ifdef FEAT_MBYTE
1170         if (output_conv.vc_type != CONV_NONE) {
1171             char_u *conv_str = string_convert(&output_conv, str, &len);
1172             if (conv_str) {
1173                 vim_free(str);
1174                 str = conv_str;
1175             }
1176         }
1177 #endif
1179         NSString *string = [[NSString alloc]
1180             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1182         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1183         [pboard declareTypes:types owner:nil];
1184         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1185     
1186         [string release];
1187         vim_free(str);
1189         return ok;
1190     }
1192     return NO;
1195 - (oneway void)addReply:(in bycopy NSString *)reply
1196                  server:(in byref id <MMVimServerProtocol>)server
1198     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1200     // Replies might come at any time and in any order so we keep them in an
1201     // array inside a dictionary with the send port used as key.
1203     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1204     // HACK! Assume connection uses mach ports.
1205     int port = [(NSMachPort*)[conn sendPort] machPort];
1206     NSNumber *key = [NSNumber numberWithInt:port];
1208     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1209     if (!replies) {
1210         replies = [NSMutableArray array];
1211         [serverReplyDict setObject:replies forKey:key];
1212     }
1214     [replies addObject:reply];
1217 - (void)addInput:(in bycopy NSString *)input
1218           client:(in byref id <MMVimClientProtocol>)client
1220     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1222     // NOTE: We don't call addInput: here because it differs from
1223     // server_to_input_buf() in that it always sets the 'silent' flag and we
1224     // don't want the MacVim client/server code to behave differently from
1225     // other platforms.
1226     char_u *s = [input vimStringSave];
1227     server_to_input_buf(s);
1228     vim_free(s);
1230     [self addClient:(id)client];
1233 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1234                  client:(in byref id <MMVimClientProtocol>)client
1236     [self addClient:(id)client];
1237     return [self evaluateExpression:expr];
1240 - (void)registerServerWithName:(NSString *)name
1242     NSString *svrName = name;
1243     NSConnection *svrConn = [NSConnection defaultConnection];
1244     unsigned i;
1246     for (i = 0; i < MMServerMax; ++i) {
1247         NSString *connName = [self connectionNameFromServerName:svrName];
1249         if ([svrConn registerName:connName]) {
1250             //NSLog(@"Registered server with name: %@", svrName);
1252             // TODO: Set request/reply time-outs to something else?
1253             //
1254             // Don't wait for requests (time-out means that the message is
1255             // dropped).
1256             [svrConn setRequestTimeout:0];
1257             //[svrConn setReplyTimeout:MMReplyTimeout];
1258             [svrConn setRootObject:self];
1260             // NOTE: 'serverName' is a global variable
1261             serverName = [svrName vimStringSave];
1262 #ifdef FEAT_EVAL
1263             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1264 #endif
1265 #ifdef FEAT_TITLE
1266             need_maketitle = TRUE;
1267 #endif
1268             [self queueMessage:SetServerNameMsgID data:
1269                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1270             break;
1271         }
1273         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1274     }
1277 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1278                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1279               silent:(BOOL)silent
1281     // NOTE: If 'name' equals 'serverName' then the request is local (client
1282     // and server are the same).  This case is not handled separately, so a
1283     // connection will be set up anyway (this simplifies the code).
1285     NSConnection *conn = [self connectionForServerName:name];
1286     if (!conn) {
1287         if (!silent) {
1288             char_u *s = (char_u*)[name UTF8String];
1289 #ifdef FEAT_MBYTE
1290             s = CONVERT_FROM_UTF8(s);
1291 #endif
1292             EMSG2(_(e_noserver), s);
1293 #ifdef FEAT_MBYTE
1294             CONVERT_FROM_UTF8_FREE(s);
1295 #endif
1296         }
1297         return NO;
1298     }
1300     if (port) {
1301         // HACK! Assume connection uses mach ports.
1302         *port = [(NSMachPort*)[conn sendPort] machPort];
1303     }
1305     id proxy = [conn rootProxy];
1306     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1308     @try {
1309         if (expr) {
1310             NSString *eval = [proxy evaluateExpression:string client:self];
1311             if (reply) {
1312                 if (eval) {
1313                     *reply = [eval vimStringSave];
1314                 } else {
1315                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1316                 }
1317             }
1319             if (!eval)
1320                 return NO;
1321         } else {
1322             [proxy addInput:string client:self];
1323         }
1324     }
1325     @catch (NSException *e) {
1326         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1327         return NO;
1328     }
1330     return YES;
1333 - (NSArray *)serverList
1335     NSArray *list = nil;
1337     if ([self connection]) {
1338         id proxy = [connection rootProxy];
1339         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1341         @try {
1342             list = [proxy serverList];
1343         }
1344         @catch (NSException *e) {
1345             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1346         }
1347     } else {
1348         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1349     }
1351     return list;
1354 - (NSString *)peekForReplyOnPort:(int)port
1356     //NSLog(@"%s%d", _cmd, port);
1358     NSNumber *key = [NSNumber numberWithInt:port];
1359     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1360     if (replies && [replies count]) {
1361         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1362         //        [replies objectAtIndex:0]);
1363         return [replies objectAtIndex:0];
1364     }
1366     //NSLog(@"    No replies");
1367     return nil;
1370 - (NSString *)waitForReplyOnPort:(int)port
1372     //NSLog(@"%s%d", _cmd, port);
1373     
1374     NSConnection *conn = [self connectionForServerPort:port];
1375     if (!conn)
1376         return nil;
1378     NSNumber *key = [NSNumber numberWithInt:port];
1379     NSMutableArray *replies = nil;
1380     NSString *reply = nil;
1382     // Wait for reply as long as the connection to the server is valid (unless
1383     // user interrupts wait with Ctrl-C).
1384     while (!got_int && [conn isValid] &&
1385             !(replies = [serverReplyDict objectForKey:key])) {
1386         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1387                                  beforeDate:[NSDate distantFuture]];
1388     }
1390     if (replies) {
1391         if ([replies count] > 0) {
1392             reply = [[replies objectAtIndex:0] retain];
1393             //NSLog(@"    Got reply: %@", reply);
1394             [replies removeObjectAtIndex:0];
1395             [reply autorelease];
1396         }
1398         if ([replies count] == 0)
1399             [serverReplyDict removeObjectForKey:key];
1400     }
1402     return reply;
1405 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1407     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1408     if (client) {
1409         @try {
1410             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1411             [client addReply:reply server:self];
1412             return YES;
1413         }
1414         @catch (NSException *e) {
1415             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1416         }
1417     } else {
1418         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1419     }
1421     return NO;
1424 - (BOOL)waitForAck
1426     return waitForAck;
1429 - (void)setWaitForAck:(BOOL)yn
1431     waitForAck = yn;
1434 - (void)waitForConnectionAcknowledgement
1436     if (!waitForAck) return;
1438     while (waitForAck && !got_int && [connection isValid]) {
1439         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1440                                  beforeDate:[NSDate distantFuture]];
1441         //NSLog(@"  waitForAck=%d got_int=%d isValid=%d",
1442         //        waitForAck, got_int, [connection isValid]);
1443     }
1445     if (waitForAck) {
1446         // Never received a connection acknowledgement, so die.
1447         [[NSNotificationCenter defaultCenter] removeObserver:self];
1448         [appProxy release];  appProxy = nil;
1450         // NOTE: We intentionally do not call mch_exit() since this in turn
1451         // will lead to -[MMBackend exit] getting called which we want to
1452         // avoid.
1453         exit(0);
1454     }
1456     [self processInputQueue];
1459 - (oneway void)acknowledgeConnection
1461     //NSLog(@"%s", _cmd);
1462     waitForAck = NO;
1465 - (BOOL)imState
1467     return imState;
1470 - (void)setImState:(BOOL)activated
1472     imState = activated;
1475 @end // MMBackend
1479 @implementation MMBackend (Private)
1481 - (void)clearDrawData
1483     [drawData setLength:0];
1484     numWholeLineChanges = offsetForDrawDataPrune = 0;
1487 - (void)didChangeWholeLine
1489     // It may happen that draw queue is filled up with lots of changes that
1490     // affect a whole row.  If the number of such changes equals twice the
1491     // number of visible rows then we can prune some commands off the queue.
1492     //
1493     // NOTE: If we don't perform this pruning the draw queue may grow
1494     // indefinitely if Vim were to repeatedly send draw commands without ever
1495     // waiting for new input (that's when the draw queue is flushed).  The one
1496     // instance I know where this can happen is when a command is executed in
1497     // the shell (think ":grep" with thousands of matches).
1499     ++numWholeLineChanges;
1500     if (numWholeLineChanges == gui.num_rows) {
1501         // Remember the offset to prune up to.
1502         offsetForDrawDataPrune = [drawData length];
1503     } else if (numWholeLineChanges == 2*gui.num_rows) {
1504         // Delete all the unnecessary draw commands.
1505         NSMutableData *d = [[NSMutableData alloc]
1506                     initWithBytes:[drawData bytes] + offsetForDrawDataPrune
1507                            length:[drawData length] - offsetForDrawDataPrune];
1508         offsetForDrawDataPrune = [d length];
1509         numWholeLineChanges -= gui.num_rows;
1510         [drawData release];
1511         drawData = d;
1512     }
1515 - (void)waitForDialogReturn
1517     // Keep processing the run loop until a dialog returns.  To avoid getting
1518     // stuck in an endless loop (could happen if the setDialogReturn: message
1519     // was lost) we also do some paranoia checks.
1520     //
1521     // Note that in Cocoa the user can still resize windows and select menu
1522     // items while a sheet is being displayed, so we can't just wait for the
1523     // first message to arrive and assume that is the setDialogReturn: call.
1525     while (nil == dialogReturn && !got_int && [connection isValid])
1526         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1527                                  beforeDate:[NSDate distantFuture]];
1529     // Search for any resize messages on the input queue.  All other messages
1530     // on the input queue are dropped.  The reason why we single out resize
1531     // messages is because the user may have resized the window while a sheet
1532     // was open.
1533     int i, count = [inputQueue count];
1534     if (count > 0) {
1535         id textDimData = nil;
1536         if (count%2 == 0) {
1537             for (i = count-2; i >= 0; i -= 2) {
1538                 int msgid = [[inputQueue objectAtIndex:i] intValue];
1539                 if (SetTextDimensionsMsgID == msgid) {
1540                     textDimData = [[inputQueue objectAtIndex:i+1] retain];
1541                     break;
1542                 }
1543             }
1544         }
1546         [inputQueue removeAllObjects];
1548         if (textDimData) {
1549             [inputQueue addObject:
1550                     [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1551             [inputQueue addObject:textDimData];
1552             [textDimData release];
1553         }
1554     }
1557 - (void)insertVimStateMessage
1559     // NOTE: This is the place to add Vim state that needs to be accessed from
1560     // MacVim.  Do not add state that could potentially require lots of memory
1561     // since this message gets sent each time the output queue is forcibly
1562     // flushed (e.g. storing the currently selected text would be a bad idea).
1563     // We take this approach of "pushing" the state to MacVim to avoid having
1564     // to make synchronous calls from MacVim to Vim in order to get state.
1566     BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1567     int numTabs = tabpage_index(NULL) - 1;
1568     if (numTabs < 0)
1569         numTabs = 0;
1571     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1572         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1573         [NSNumber numberWithInt:p_mh], @"p_mh",
1574         [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1575         [NSNumber numberWithBool:mmta], @"p_mmta",
1576         [NSNumber numberWithInt:numTabs], @"numTabs",
1577         nil];
1579     // Put the state before all other messages.
1580     int msgid = SetVimStateMsgID;
1581     [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1582     [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1583                       atIndex:0];
1586 - (void)processInputQueue
1588     if ([inputQueue count] == 0) return;
1590     // NOTE: One of the input events may cause this method to be called
1591     // recursively, so copy the input queue to a local variable and clear the
1592     // queue before starting to process input events (otherwise we could get
1593     // stuck in an endless loop).
1594     NSArray *q = [inputQueue copy];
1595     unsigned i, count = [q count];
1597     [inputQueue removeAllObjects];
1599     for (i = 1; i < count; i+=2) {
1600         int msgid = [[q objectAtIndex:i-1] intValue];
1601         id data = [q objectAtIndex:i];
1602         if ([data isEqual:[NSNull null]])
1603             data = nil;
1605         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1606         [self handleInputEvent:msgid data:data];
1607     }
1609     [q release];
1610     //NSLog(@"Clear input event queue");
1613 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1615     if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1616             CmdKeyMsgID == msgid) {
1617         if (!data) return;
1618         const void *bytes = [data bytes];
1619         int mods = *((int*)bytes);  bytes += sizeof(int);
1620         int len = *((int*)bytes);  bytes += sizeof(int);
1621         NSString *key = [[NSString alloc] initWithBytes:bytes
1622                                                  length:len
1623                                                encoding:NSUTF8StringEncoding];
1624         mods = eventModifierFlagsToVimModMask(mods);
1626         if (InsertTextMsgID == msgid)
1627             [self handleInsertText:key];
1628         else
1629             [self handleKeyDown:key modifiers:mods];
1631         [key release];
1632     } else if (ScrollWheelMsgID == msgid) {
1633         if (!data) return;
1634         const void *bytes = [data bytes];
1636         int row = *((int*)bytes);  bytes += sizeof(int);
1637         int col = *((int*)bytes);  bytes += sizeof(int);
1638         int flags = *((int*)bytes);  bytes += sizeof(int);
1639         float dy = *((float*)bytes);  bytes += sizeof(float);
1641         int button = MOUSE_5;
1642         if (dy > 0) button = MOUSE_4;
1644         flags = eventModifierFlagsToVimMouseModMask(flags);
1646         int numLines = (int)round(dy);
1647         if (numLines < 0) numLines = -numLines;
1648         if (numLines == 0) numLines = 1;
1650 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1651         gui.scroll_wheel_force = numLines;
1652 #endif
1654         gui_send_mouse_event(button, col, row, NO, flags);
1655     } else if (MouseDownMsgID == msgid) {
1656         if (!data) return;
1657         const void *bytes = [data bytes];
1659         int row = *((int*)bytes);  bytes += sizeof(int);
1660         int col = *((int*)bytes);  bytes += sizeof(int);
1661         int button = *((int*)bytes);  bytes += sizeof(int);
1662         int flags = *((int*)bytes);  bytes += sizeof(int);
1663         int count = *((int*)bytes);  bytes += sizeof(int);
1665         button = eventButtonNumberToVimMouseButton(button);
1666         if (button >= 0) {
1667             flags = eventModifierFlagsToVimMouseModMask(flags);
1668             gui_send_mouse_event(button, col, row, count>1, flags);
1669         }
1670     } else if (MouseUpMsgID == msgid) {
1671         if (!data) return;
1672         const void *bytes = [data bytes];
1674         int row = *((int*)bytes);  bytes += sizeof(int);
1675         int col = *((int*)bytes);  bytes += sizeof(int);
1676         int flags = *((int*)bytes);  bytes += sizeof(int);
1678         flags = eventModifierFlagsToVimMouseModMask(flags);
1680         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1681     } else if (MouseDraggedMsgID == msgid) {
1682         if (!data) return;
1683         const void *bytes = [data bytes];
1685         int row = *((int*)bytes);  bytes += sizeof(int);
1686         int col = *((int*)bytes);  bytes += sizeof(int);
1687         int flags = *((int*)bytes);  bytes += sizeof(int);
1689         flags = eventModifierFlagsToVimMouseModMask(flags);
1691         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1692     } else if (MouseMovedMsgID == msgid) {
1693         const void *bytes = [data bytes];
1694         int row = *((int*)bytes);  bytes += sizeof(int);
1695         int col = *((int*)bytes);  bytes += sizeof(int);
1697         gui_mouse_moved(col, row);
1698     } else if (AddInputMsgID == msgid) {
1699         NSString *string = [[NSString alloc] initWithData:data
1700                 encoding:NSUTF8StringEncoding];
1701         if (string) {
1702             [self addInput:string];
1703             [string release];
1704         }
1705     } else if (SelectTabMsgID == msgid) {
1706         if (!data) return;
1707         const void *bytes = [data bytes];
1708         int idx = *((int*)bytes) + 1;
1709         //NSLog(@"Selecting tab %d", idx);
1710         send_tabline_event(idx);
1711     } else if (CloseTabMsgID == msgid) {
1712         if (!data) return;
1713         const void *bytes = [data bytes];
1714         int idx = *((int*)bytes) + 1;
1715         //NSLog(@"Closing tab %d", idx);
1716         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1717     } else if (AddNewTabMsgID == msgid) {
1718         //NSLog(@"Adding new tab");
1719         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1720     } else if (DraggedTabMsgID == msgid) {
1721         if (!data) return;
1722         const void *bytes = [data bytes];
1723         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1724         // based.
1725         int idx = *((int*)bytes);
1727         tabpage_move(idx);
1728     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1729             || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1730         if (!data) return;
1731         const void *bytes = [data bytes];
1732         int rows = Rows;
1733         if (SetTextColumnsMsgID != msgid) {
1734             rows = *((int*)bytes);  bytes += sizeof(int);
1735         }
1736         int cols = Columns;
1737         if (SetTextRowsMsgID != msgid) {
1738             cols = *((int*)bytes);  bytes += sizeof(int);
1739         }
1741         NSData *d = data;
1742         if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1743             int dim[2] = { rows, cols };
1744             d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1745             msgid = SetTextDimensionsReplyMsgID;
1746         }
1748         if (SetTextDimensionsMsgID == msgid)
1749             msgid = SetTextDimensionsReplyMsgID;
1751         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1752         // gui_resize_shell(), so we have to manually set the rows and columns
1753         // here since MacVim doesn't change the rows and columns to avoid
1754         // inconsistent states between Vim and MacVim.  The message sent back
1755         // indicates that it is a reply to a message that originated in MacVim
1756         // since we need to be able to determine where a message originated.
1757         [self queueMessage:msgid data:d];
1759         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1760         gui_resize_shell(cols, rows);
1761     } else if (ExecuteMenuMsgID == msgid) {
1762         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1763         if (attrs) {
1764             NSArray *desc = [attrs objectForKey:@"descriptor"];
1765             vimmenu_T *menu = menu_for_descriptor(desc);
1766             if (menu)
1767                 gui_menu_cb(menu);
1768         }
1769     } else if (ToggleToolbarMsgID == msgid) {
1770         [self handleToggleToolbar];
1771     } else if (ScrollbarEventMsgID == msgid) {
1772         [self handleScrollbarEvent:data];
1773     } else if (SetFontMsgID == msgid) {
1774         [self handleSetFont:data];
1775     } else if (VimShouldCloseMsgID == msgid) {
1776         gui_shell_closed();
1777     } else if (DropFilesMsgID == msgid) {
1778         [self handleDropFiles:data];
1779     } else if (DropStringMsgID == msgid) {
1780         [self handleDropString:data];
1781     } else if (GotFocusMsgID == msgid) {
1782         if (!gui.in_focus)
1783             [self focusChange:YES];
1784     } else if (LostFocusMsgID == msgid) {
1785         if (gui.in_focus)
1786             [self focusChange:NO];
1787     } else if (SetMouseShapeMsgID == msgid) {
1788         const void *bytes = [data bytes];
1789         int shape = *((int*)bytes);  bytes += sizeof(int);
1790         update_mouseshape(shape);
1791     } else if (XcodeModMsgID == msgid) {
1792         [self handleXcodeMod:data];
1793     } else if (OpenWithArgumentsMsgID == msgid) {
1794         [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1795     } else if (FindReplaceMsgID == msgid) {
1796         [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1797     } else if (ActivatedImMsgID == msgid) {
1798         [self setImState:YES];
1799     } else if (DeactivatedImMsgID == msgid) {
1800         [self setImState:NO];
1801     } else {
1802         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1803     }
1806 + (NSDictionary *)specialKeys
1808     static NSDictionary *specialKeys = nil;
1810     if (!specialKeys) {
1811         NSBundle *mainBundle = [NSBundle mainBundle];
1812         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1813                                               ofType:@"plist"];
1814         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1815     }
1817     return specialKeys;
1820 - (void)handleInsertText:(NSString *)text
1822     if (!text) return;
1824     char_u *str = (char_u*)[text UTF8String];
1825     int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1827 #ifdef FEAT_MBYTE
1828     char_u *conv_str = NULL;
1829     if (input_conv.vc_type != CONV_NONE) {
1830         conv_str = string_convert(&input_conv, str, &len);
1831         if (conv_str)
1832             str = conv_str;
1833     }
1834 #endif
1836     for (i = 0; i < len; ++i) {
1837         add_to_input_buf(str+i, 1);
1838         if (CSI == str[i]) {
1839             // NOTE: If the converted string contains the byte CSI, then it
1840             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1841             // won't work.
1842             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1843             add_to_input_buf(extra, 2);
1844         }
1845     }
1847 #ifdef FEAT_MBYTE
1848     if (conv_str)
1849         vim_free(conv_str);
1850 #endif
1853 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1855     // TODO: This code is a horrible mess -- clean up!
1856     char_u special[3];
1857     char_u modChars[3];
1858     char_u *chars = (char_u*)[key UTF8String];
1859 #ifdef FEAT_MBYTE
1860     char_u *conv_str = NULL;
1861 #endif
1862     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1864     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1865     // that new keys can easily be added.
1866     NSString *specialString = [[MMBackend specialKeys]
1867             objectForKey:key];
1868     if (specialString && [specialString length] > 1) {
1869         //NSLog(@"special key: %@", specialString);
1870         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1871                 [specialString characterAtIndex:1]);
1873         ikey = simplify_key(ikey, &mods);
1874         if (ikey == CSI)
1875             ikey = K_CSI;
1877         special[0] = CSI;
1878         special[1] = K_SECOND(ikey);
1879         special[2] = K_THIRD(ikey);
1881         chars = special;
1882         length = 3;
1883     } else if (1 == length && TAB == chars[0]) {
1884         // Tab is a trouble child:
1885         // - <Tab> is added to the input buffer as is
1886         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1887         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1888         //   to be converted to utf-8
1889         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1890         // - <C-Tab> is reserved by Mac OS X
1891         // - <D-Tab> is reserved by Mac OS X
1892         chars = special;
1893         special[0] = TAB;
1894         length = 1;
1896         if (mods & MOD_MASK_SHIFT) {
1897             mods &= ~MOD_MASK_SHIFT;
1898             special[0] = CSI;
1899             special[1] = K_SECOND(K_S_TAB);
1900             special[2] = K_THIRD(K_S_TAB);
1901             length = 3;
1902         } else if (mods & MOD_MASK_ALT) {
1903             int mtab = 0x80 | TAB;
1904 #ifdef FEAT_MBYTE
1905             if (enc_utf8) {
1906                 // Convert to utf-8
1907                 special[0] = (mtab >> 6) + 0xc0;
1908                 special[1] = mtab & 0xbf;
1909                 length = 2;
1910             } else
1911 #endif
1912             {
1913                 special[0] = mtab;
1914                 length = 1;
1915             }
1916             mods &= ~MOD_MASK_ALT;
1917         }
1918     } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1919         // META key is treated separately.  This code was taken from gui_w48.c
1920         // and gui_gtk_x11.c.
1921         char_u string[7];
1922         int ch = simplify_key(chars[0], &mods);
1924         // Remove the SHIFT modifier for keys where it's already included,
1925         // e.g., '(' and '*'
1926         if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1927             mods &= ~MOD_MASK_SHIFT;
1929         // Interpret the ALT key as making the key META, include SHIFT, etc.
1930         ch = extract_modifiers(ch, &mods);
1931         if (ch == CSI)
1932             ch = K_CSI;
1934         int len = 0;
1935         if (mods) {
1936             string[len++] = CSI;
1937             string[len++] = KS_MODIFIER;
1938             string[len++] = mods;
1939         }
1941         if (IS_SPECIAL(ch)) {
1942             string[len++] = CSI;
1943             string[len++] = K_SECOND(ch);
1944             string[len++] = K_THIRD(ch);
1945         } else {
1946             string[len++] = ch;
1947 #ifdef FEAT_MBYTE
1948             // TODO: What if 'enc' is not "utf-8"?
1949             if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1950                 string[len++] = ch & 0xbf;
1951                 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1952                 if (string[len-1] == CSI) {
1953                     string[len++] = KS_EXTRA;
1954                     string[len++] = (int)KE_CSI;
1955                 }
1956             }
1957 #endif
1958         }
1960         add_to_input_buf(string, len);
1961         return;
1962     } else if (length > 0) {
1963         unichar c = [key characterAtIndex:0];
1964         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1965         //        [key characterAtIndex:0], mods);
1967         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1968         // cleared since they are already added to the key by the AppKit.
1969         // Unfortunately, the only way to deal with when to clear the modifiers
1970         // or not seems to be to have hard-wired rules like this.
1971         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1972                     || 0x9 == c || 0xd == c || ESC == c) ) {
1973             mods &= ~MOD_MASK_SHIFT;
1974             mods &= ~MOD_MASK_CTRL;
1975             //NSLog(@"clear shift ctrl");
1976         }
1978 #ifdef FEAT_MBYTE
1979         if (input_conv.vc_type != CONV_NONE) {
1980             conv_str = string_convert(&input_conv, chars, &length);
1981             if (conv_str)
1982                 chars = conv_str;
1983         }
1984 #endif
1985     }
1987     if (chars && length > 0) {
1988         if (mods) {
1989             //NSLog(@"adding mods: %d", mods);
1990             modChars[0] = CSI;
1991             modChars[1] = KS_MODIFIER;
1992             modChars[2] = mods;
1993             add_to_input_buf(modChars, 3);
1994         }
1996         //NSLog(@"add to input buf: 0x%x", chars[0]);
1997         // TODO: Check for CSI bytes?
1998         add_to_input_buf(chars, length);
1999     }
2001 #ifdef FEAT_MBYTE
2002     if (conv_str)
2003         vim_free(conv_str);
2004 #endif
2007 - (void)queueMessage:(int)msgid data:(NSData *)data
2009     //if (msgid != EnableMenuItemMsgID)
2010     //    NSLog(@"queueMessage:%s", MessageStrings[msgid]);
2012     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
2013     if (data)
2014         [outputQueue addObject:data];
2015     else
2016         [outputQueue addObject:[NSData data]];
2019 - (void)connectionDidDie:(NSNotification *)notification
2021     // If the main connection to MacVim is lost this means that either MacVim
2022     // has crashed or this process did not receive its termination message
2023     // properly (e.g. if the TerminateNowMsgID was dropped).
2024     //
2025     // NOTE: This is not called if a Vim controller invalidates its connection.
2027     NSLog(@"WARNING[%s]: Main connection was lost before process had a chance "
2028             "to terminate; preserving swap files.", _cmd);
2029     getout_preserve_modified(1);
2032 - (void)blinkTimerFired:(NSTimer *)timer
2034     NSTimeInterval timeInterval = 0;
2036     [blinkTimer release];
2037     blinkTimer = nil;
2039     if (MMBlinkStateOn == blinkState) {
2040         gui_undraw_cursor();
2041         blinkState = MMBlinkStateOff;
2042         timeInterval = blinkOffInterval;
2043     } else if (MMBlinkStateOff == blinkState) {
2044         gui_update_cursor(TRUE, FALSE);
2045         blinkState = MMBlinkStateOn;
2046         timeInterval = blinkOnInterval;
2047     }
2049     if (timeInterval > 0) {
2050         blinkTimer = 
2051             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2052                                             selector:@selector(blinkTimerFired:)
2053                                             userInfo:nil repeats:NO] retain];
2054         [self flushQueue:YES];
2055     }
2058 - (void)focusChange:(BOOL)on
2060     gui_focus_change(on);
2063 - (void)handleToggleToolbar
2065     // If 'go' contains 'T', then remove it, else add it.
2067     char_u go[sizeof(GO_ALL)+2];
2068     char_u *p;
2069     int len;
2071     STRCPY(go, p_go);
2072     p = vim_strchr(go, GO_TOOLBAR);
2073     len = STRLEN(go);
2075     if (p != NULL) {
2076         char_u *end = go + len;
2077         while (p < end) {
2078             p[0] = p[1];
2079             ++p;
2080         }
2081     } else {
2082         go[len] = GO_TOOLBAR;
2083         go[len+1] = NUL;
2084     }
2086     set_option_value((char_u*)"guioptions", 0, go, 0);
2088     [self redrawScreen];
2091 - (void)handleScrollbarEvent:(NSData *)data
2093     if (!data) return;
2095     const void *bytes = [data bytes];
2096     long ident = *((long*)bytes);  bytes += sizeof(long);
2097     int hitPart = *((int*)bytes);  bytes += sizeof(int);
2098     float fval = *((float*)bytes);  bytes += sizeof(float);
2099     scrollbar_T *sb = gui_find_scrollbar(ident);
2101     if (sb) {
2102         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2103         long value = sb_info->value;
2104         long size = sb_info->size;
2105         long max = sb_info->max;
2106         BOOL isStillDragging = NO;
2107         BOOL updateKnob = YES;
2109         switch (hitPart) {
2110         case NSScrollerDecrementPage:
2111             value -= (size > 2 ? size - 2 : 1);
2112             break;
2113         case NSScrollerIncrementPage:
2114             value += (size > 2 ? size - 2 : 1);
2115             break;
2116         case NSScrollerDecrementLine:
2117             --value;
2118             break;
2119         case NSScrollerIncrementLine:
2120             ++value;
2121             break;
2122         case NSScrollerKnob:
2123             isStillDragging = YES;
2124             // fall through ...
2125         case NSScrollerKnobSlot:
2126             value = (long)(fval * (max - size + 1));
2127             // fall through ...
2128         default:
2129             updateKnob = NO;
2130             break;
2131         }
2133         //NSLog(@"value %d -> %d", sb_info->value, value);
2134         gui_drag_scrollbar(sb, value, isStillDragging);
2136         if (updateKnob) {
2137             // Dragging the knob or option+clicking automatically updates
2138             // the knob position (on the actual NSScroller), so we only
2139             // need to set the knob position in the other cases.
2140             if (sb->wp) {
2141                 // Update both the left&right vertical scrollbars.
2142                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2143                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2144                 [self setScrollbarThumbValue:value size:size max:max
2145                                   identifier:identLeft];
2146                 [self setScrollbarThumbValue:value size:size max:max
2147                                   identifier:identRight];
2148             } else {
2149                 // Update the horizontal scrollbar.
2150                 [self setScrollbarThumbValue:value size:size max:max
2151                                   identifier:ident];
2152             }
2153         }
2154     }
2157 - (void)handleSetFont:(NSData *)data
2159     if (!data) return;
2161     const void *bytes = [data bytes];
2162     int pointSize = (int)*((float*)bytes);  bytes += sizeof(float);
2164     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2165     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2166     bytes += len;
2168     [name appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2169     char_u *s = (char_u*)[name UTF8String];
2171     unsigned wlen = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2172     char_u *ws = NULL;
2173     if (wlen > 0) {
2174         NSMutableString *wname = [NSMutableString stringWithUTF8String:bytes];
2175         bytes += wlen;
2177         [wname appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2178         ws = (char_u*)[wname UTF8String];
2179     }
2181 #ifdef FEAT_MBYTE
2182     s = CONVERT_FROM_UTF8(s);
2183     if (ws) {
2184         ws = CONVERT_FROM_UTF8(ws);
2185     }
2186 #endif
2188     set_option_value((char_u*)"guifont", 0, s, 0);
2190     if (ws && gui.wide_font != NOFONT) {
2191         // NOTE: This message is sent on Cmd-+/Cmd-- and as such should only
2192         // change the wide font if 'gfw' is non-empty (the frontend always has
2193         // some wide font set, even if 'gfw' is empty).
2194         set_option_value((char_u*)"guifontwide", 0, ws, 0);
2195     }
2197 #ifdef FEAT_MBYTE
2198     if (ws) {
2199         CONVERT_FROM_UTF8_FREE(ws);
2200     }
2201     CONVERT_FROM_UTF8_FREE(s);
2202 #endif
2204     [self redrawScreen];
2207 - (void)handleDropFiles:(NSData *)data
2209     // TODO: Get rid of this method; instead use Vim script directly.  At the
2210     // moment I know how to do this to open files in tabs, but I'm not sure how
2211     // to add the filenames to the command line when in command line mode.
2213     if (!data) return;
2215     NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2216     if (!args) return;
2218     id obj = [args objectForKey:@"forceOpen"];
2219     BOOL forceOpen = YES;
2220     if (obj)
2221         forceOpen = [obj boolValue];
2223     NSArray *filenames = [args objectForKey:@"filenames"];
2224     if (!(filenames && [filenames count] > 0)) return;
2226 #ifdef FEAT_DND
2227     if (!forceOpen && (State & CMDLINE)) {
2228         // HACK!  If Vim is in command line mode then the files names
2229         // should be added to the command line, instead of opening the
2230         // files in tabs (unless forceOpen is set).  This is taken care of by
2231         // gui_handle_drop().
2232         int n = [filenames count];
2233         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2234         if (fnames) {
2235             int i = 0;
2236             for (i = 0; i < n; ++i)
2237                 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2239             // NOTE!  This function will free 'fnames'.
2240             // HACK!  It is assumed that the 'x' and 'y' arguments are
2241             // unused when in command line mode.
2242             gui_handle_drop(0, 0, 0, fnames, n);
2243         }
2244     } else
2245 #endif // FEAT_DND
2246     {
2247         [self handleOpenWithArguments:args];
2248     }
2251 - (void)handleDropString:(NSData *)data
2253     if (!data) return;
2255 #ifdef FEAT_DND
2256     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2257     const void *bytes = [data bytes];
2258     int len = *((int*)bytes);  bytes += sizeof(int);
2259     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2261     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2262     NSRange range = { 0, [string length] };
2263     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2264                                          withString:@"\x0a" options:0
2265                                               range:range];
2266     if (0 == n) {
2267         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2268                                        options:0 range:range];
2269     }
2271     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2272     char_u *s = (char_u*)[string UTF8String];
2273 #ifdef FEAT_MBYTE
2274     if (input_conv.vc_type != CONV_NONE)
2275         s = string_convert(&input_conv, s, &len);
2276 #endif
2277     dnd_yank_drag_data(s, len);
2278 #ifdef FEAT_MBYTE
2279     if (input_conv.vc_type != CONV_NONE)
2280         vim_free(s);
2281 #endif
2282     add_to_input_buf(dropkey, sizeof(dropkey));
2283 #endif // FEAT_DND
2286 - (void)startOdbEditWithArguments:(NSDictionary *)args
2288 #ifdef FEAT_ODB_EDITOR
2289     id obj = [args objectForKey:@"remoteID"];
2290     if (!obj) return;
2292     OSType serverID = [obj unsignedIntValue];
2293     NSString *remotePath = [args objectForKey:@"remotePath"];
2295     NSAppleEventDescriptor *token = nil;
2296     NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2297     obj = [args objectForKey:@"remoteTokenDescType"];
2298     if (tokenData && obj) {
2299         DescType tokenType = [obj unsignedLongValue];
2300         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2301                                                                 data:tokenData];
2302     }
2304     NSArray *filenames = [args objectForKey:@"filenames"];
2305     unsigned i, numFiles = [filenames count];
2306     for (i = 0; i < numFiles; ++i) {
2307         NSString *filename = [filenames objectAtIndex:i];
2308         char_u *s = [filename vimStringSave];
2309         buf_T *buf = buflist_findname(s);
2310         vim_free(s);
2312         if (buf) {
2313             if (buf->b_odb_token) {
2314                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2315                 buf->b_odb_token = NULL;
2316             }
2318             if (buf->b_odb_fname) {
2319                 vim_free(buf->b_odb_fname);
2320                 buf->b_odb_fname = NULL;
2321             }
2323             buf->b_odb_server_id = serverID;
2325             if (token)
2326                 buf->b_odb_token = [token retain];
2327             if (remotePath)
2328                 buf->b_odb_fname = [remotePath vimStringSave];
2329         } else {
2330             NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2331                     filename);
2332         }
2333     }
2334 #endif // FEAT_ODB_EDITOR
2337 - (void)handleXcodeMod:(NSData *)data
2339 #if 0
2340     const void *bytes = [data bytes];
2341     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2342     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2343     if (0 == len)
2344         return;
2346     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2347             descriptorWithDescriptorType:type
2348                                    bytes:bytes
2349                                   length:len];
2350 #endif
2353 - (void)handleOpenWithArguments:(NSDictionary *)args
2355     // ARGUMENT:                DESCRIPTION:
2356     // -------------------------------------------------------------
2357     // filenames                list of filenames
2358     // dontOpen                 don't open files specified in above argument
2359     // layout                   which layout to use to open files
2360     // selectionRange           range of lines to select
2361     // searchText               string to search for
2362     // cursorLine               line to position the cursor on
2363     // cursorColumn             column to position the cursor on
2364     //                          (only valid when "cursorLine" is set)
2365     // remoteID                 ODB parameter
2366     // remotePath               ODB parameter
2367     // remoteTokenDescType      ODB parameter
2368     // remoteTokenData          ODB parameter
2370     //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2372     NSArray *filenames = [args objectForKey:@"filenames"];
2373     int i, numFiles = filenames ? [filenames count] : 0;
2374     BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2375     int layout = [[args objectForKey:@"layout"] intValue];
2377     // Change to directory of first file to open if this is an "unused" editor
2378     // (but do not do this if editing remotely).
2379     if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2380             && (starting || [self unusedEditor]) ) {
2381         char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2382         vim_chdirfile(s);
2383         vim_free(s);
2384     }
2386     if (starting > 0) {
2387         // When Vim is starting we simply add the files to be opened to the
2388         // global arglist and Vim will take care of opening them for us.
2389         if (openFiles && numFiles > 0) {
2390             for (i = 0; i < numFiles; i++) {
2391                 NSString *fname = [filenames objectAtIndex:i];
2392                 char_u *p = NULL;
2394                 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2395                         || (p = [fname vimStringSave]) == NULL)
2396                     exit(2); // See comment in -[MMBackend exit]
2397                 else
2398                     alist_add(&global_alist, p, 2);
2399             }
2401             // Vim will take care of arranging the files added to the arglist
2402             // in windows or tabs; all we must do is to specify which layout to
2403             // use.
2404             initialWindowLayout = layout;
2405         }
2406     } else {
2407         // When Vim is already open we resort to some trickery to open the
2408         // files with the specified layout.
2409         //
2410         // TODO: Figure out a better way to handle this?
2411         if (openFiles && numFiles > 0) {
2412             BOOL oneWindowInTab = topframe ? YES
2413                                            : (topframe->fr_layout == FR_LEAF);
2414             BOOL bufChanged = NO;
2415             BOOL bufHasFilename = NO;
2416             if (curbuf) {
2417                 bufChanged = curbufIsChanged();
2418                 bufHasFilename = curbuf->b_ffname != NULL;
2419             }
2421             // Temporarily disable flushing since the following code may
2422             // potentially cause multiple redraws.
2423             flushDisabled = YES;
2425             BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2426             if (WIN_TABS == layout && !onlyOneTab) {
2427                 // By going to the last tabpage we ensure that the new tabs
2428                 // will appear last (if this call is left out, the taborder
2429                 // becomes messy).
2430                 goto_tabpage(9999);
2431             }
2433             // Make sure we're in normal mode first.
2434             [self addInput:@"<C-\\><C-N>"];
2436             if (numFiles > 1) {
2437                 // With "split layout" we open a new tab before opening
2438                 // multiple files if the current tab has more than one window
2439                 // or if there is exactly one window but whose buffer has a
2440                 // filename.  (The :drop command ensures modified buffers get
2441                 // their own window.)
2442                 if ((WIN_HOR == layout || WIN_VER == layout) &&
2443                         (!oneWindowInTab || bufHasFilename))
2444                     [self addInput:@":tabnew<CR>"];
2446                 // The files are opened by constructing a ":drop ..." command
2447                 // and executing it.
2448                 NSMutableString *cmd = (WIN_TABS == layout)
2449                         ? [NSMutableString stringWithString:@":tab drop"]
2450                         : [NSMutableString stringWithString:@":drop"];
2452                 for (i = 0; i < numFiles; ++i) {
2453                     NSString *file = [filenames objectAtIndex:i];
2454                     file = [file stringByEscapingSpecialFilenameCharacters];
2455                     [cmd appendString:@" "];
2456                     [cmd appendString:file];
2457                 }
2459                 // Temporarily clear 'suffixes' so that the files are opened in
2460                 // the same order as they appear in the "filenames" array.
2461                 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2463                 [self addInput:cmd];
2465                 // Split the view into multiple windows if requested.
2466                 if (WIN_HOR == layout)
2467                     [self addInput:@"|sall"];
2468                 else if (WIN_VER == layout)
2469                     [self addInput:@"|vert sall"];
2471                 // Restore the old value of 'suffixes'.
2472                 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2473             } else {
2474                 // When opening one file we try to reuse the current window,
2475                 // but not if its buffer is modified or has a filename.
2476                 // However, the 'arglist' layout always opens the file in the
2477                 // current window.
2478                 NSString *file = [[filenames lastObject]
2479                         stringByEscapingSpecialFilenameCharacters];
2480                 NSString *cmd;
2481                 if (WIN_HOR == layout) {
2482                     if (!(bufHasFilename || bufChanged))
2483                         cmd = [NSString stringWithFormat:@":e %@", file];
2484                     else
2485                         cmd = [NSString stringWithFormat:@":sp %@", file];
2486                 } else if (WIN_VER == layout) {
2487                     if (!(bufHasFilename || bufChanged))
2488                         cmd = [NSString stringWithFormat:@":e %@", file];
2489                     else
2490                         cmd = [NSString stringWithFormat:@":vsp %@", file];
2491                 } else if (WIN_TABS == layout) {
2492                     if (oneWindowInTab && !(bufHasFilename || bufChanged))
2493                         cmd = [NSString stringWithFormat:@":e %@", file];
2494                     else
2495                         cmd = [NSString stringWithFormat:@":tabe %@", file];
2496                 } else {
2497                     // (The :drop command will split if there is a modified
2498                     // buffer.)
2499                     cmd = [NSString stringWithFormat:@":drop %@", file];
2500                 }
2502                 [self addInput:cmd];
2503                 [self addInput:@"<CR>"];
2504             }
2506             // Force screen redraw (does it have to be this complicated?).
2507             // (This code was taken from the end of gui_handle_drop().)
2508             update_screen(NOT_VALID);
2509             setcursor();
2510             out_flush();
2511             gui_update_cursor(FALSE, FALSE);
2512             maketitle();
2514             flushDisabled = NO;
2515         }
2516     }
2518     if ([args objectForKey:@"remoteID"]) {
2519         // NOTE: We have to delay processing any ODB related arguments since
2520         // the file(s) may not be opened until the input buffer is processed.
2521         [self performSelector:@selector(startOdbEditWithArguments:)
2522                    withObject:args
2523                    afterDelay:0];
2524     }
2526     NSString *lineString = [args objectForKey:@"cursorLine"];
2527     if (lineString && [lineString intValue] > 0) {
2528         NSString *columnString = [args objectForKey:@"cursorColumn"];
2529         if (!(columnString && [columnString intValue] > 0))
2530             columnString = @"1";
2532         NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2533                 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2534         [self addInput:cmd];
2535     }
2537     NSString *rangeString = [args objectForKey:@"selectionRange"];
2538     if (rangeString) {
2539         // Build a command line string that will select the given range of
2540         // lines.  If range.length == 0, then position the cursor on the given
2541         // line but do not select.
2542         NSRange range = NSRangeFromString(rangeString);
2543         NSString *cmd;
2544         if (range.length > 0) {
2545             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2546                     NSMaxRange(range), range.location];
2547         } else {
2548             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2549                     range.location];
2550         }
2552         [self addInput:cmd];
2553     }
2555     NSString *searchText = [args objectForKey:@"searchText"];
2556     if (searchText) {
2557         [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2558                 searchText]];
2559     }
2562 - (BOOL)checkForModifiedBuffers
2564     buf_T *buf;
2565     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2566         if (bufIsChanged(buf)) {
2567             return YES;
2568         }
2569     }
2571     return NO;
2574 - (void)addInput:(NSString *)input
2576     // NOTE: This code is essentially identical to server_to_input_buf(),
2577     // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2578     char_u *string = [input vimStringSave];
2579     if (!string) return;
2581     /* Set 'cpoptions' the way we want it.
2582      *    B set - backslashes are *not* treated specially
2583      *    k set - keycodes are *not* reverse-engineered
2584      *    < unset - <Key> sequences *are* interpreted
2585      *  The last but one parameter of replace_termcodes() is TRUE so that the
2586      *  <lt> sequence is recognised - needed for a real backslash.
2587      */
2588     char_u *ptr = NULL;
2589     char_u *cpo_save = p_cpo;
2590     p_cpo = (char_u *)"Bk";
2591     char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2592     p_cpo = cpo_save;
2594     if (*ptr != NUL)    /* trailing CTRL-V results in nothing */
2595     {
2596         /*
2597          * Add the string to the input stream.
2598          * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2599          *
2600          * First clear typed characters from the typeahead buffer, there could
2601          * be half a mapping there.  Then append to the existing string, so
2602          * that multiple commands from a client are concatenated.
2603          */
2604         if (typebuf.tb_maplen < typebuf.tb_len)
2605             del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2606         (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2608         /* Let input_available() know we inserted text in the typeahead
2609          * buffer. */
2610         typebuf_was_filled = TRUE;
2611     }
2612     vim_free(ptr);
2613     vim_free(string);
2616 - (BOOL)unusedEditor
2618     BOOL oneWindowInTab = topframe ? YES
2619                                    : (topframe->fr_layout == FR_LEAF);
2620     BOOL bufChanged = NO;
2621     BOOL bufHasFilename = NO;
2622     if (curbuf) {
2623         bufChanged = curbufIsChanged();
2624         bufHasFilename = curbuf->b_ffname != NULL;
2625     }
2627     BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2629     return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2632 - (void)redrawScreen
2634     // Force screen redraw (does it have to be this complicated?).
2635     redraw_all_later(CLEAR);
2636     update_screen(NOT_VALID);
2637     setcursor();
2638     out_flush();
2639     gui_update_cursor(FALSE, FALSE);
2641     // HACK! The cursor is not put back at the command line by the above
2642     // "redraw commands".  The following test seems to do the trick though.
2643     if (State & CMDLINE)
2644         redrawcmdline();
2647 - (void)handleFindReplace:(NSDictionary *)args
2649     if (!args) return;
2651     NSString *findString = [args objectForKey:@"find"];
2652     if (!findString) return;
2654     char_u *find = [findString vimStringSave];
2655     char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2656     int flags = [[args objectForKey:@"flags"] intValue];
2658     // NOTE: The flag 0x100 is used to indicate a backward search.
2659     gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2661     vim_free(find);
2662     vim_free(replace);
2665 @end // MMBackend (Private)
2670 @implementation MMBackend (ClientServer)
2672 - (NSString *)connectionNameFromServerName:(NSString *)name
2674     NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2676     return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2677         lowercaseString];
2680 - (NSConnection *)connectionForServerName:(NSString *)name
2682     // TODO: Try 'name%d' if 'name' fails.
2683     NSString *connName = [self connectionNameFromServerName:name];
2684     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2686     if (!svrConn) {
2687         svrConn = [NSConnection connectionWithRegisteredName:connName
2688                                                            host:nil];
2689         // Try alternate server...
2690         if (!svrConn && alternateServerName) {
2691             //NSLog(@"  trying to connect to alternate server: %@",
2692             //        alternateServerName);
2693             connName = [self connectionNameFromServerName:alternateServerName];
2694             svrConn = [NSConnection connectionWithRegisteredName:connName
2695                                                             host:nil];
2696         }
2698         // Try looking for alternate servers...
2699         if (!svrConn) {
2700             //NSLog(@"  looking for alternate servers...");
2701             NSString *alt = [self alternateServerNameForName:name];
2702             if (alt != alternateServerName) {
2703                 //NSLog(@"  found alternate server: %@", string);
2704                 [alternateServerName release];
2705                 alternateServerName = [alt copy];
2706             }
2707         }
2709         // Try alternate server again...
2710         if (!svrConn && alternateServerName) {
2711             //NSLog(@"  trying to connect to alternate server: %@",
2712             //        alternateServerName);
2713             connName = [self connectionNameFromServerName:alternateServerName];
2714             svrConn = [NSConnection connectionWithRegisteredName:connName
2715                                                             host:nil];
2716         }
2718         if (svrConn) {
2719             [connectionNameDict setObject:svrConn forKey:connName];
2721             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2722             [[NSNotificationCenter defaultCenter] addObserver:self
2723                     selector:@selector(serverConnectionDidDie:)
2724                         name:NSConnectionDidDieNotification object:svrConn];
2725         }
2726     }
2728     return svrConn;
2731 - (NSConnection *)connectionForServerPort:(int)port
2733     NSConnection *conn;
2734     NSEnumerator *e = [connectionNameDict objectEnumerator];
2736     while ((conn = [e nextObject])) {
2737         // HACK! Assume connection uses mach ports.
2738         if (port == [(NSMachPort*)[conn sendPort] machPort])
2739             return conn;
2740     }
2742     return nil;
2745 - (void)serverConnectionDidDie:(NSNotification *)notification
2747     //NSLog(@"%s%@", _cmd, notification);
2749     NSConnection *svrConn = [notification object];
2751     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2752     [[NSNotificationCenter defaultCenter]
2753             removeObserver:self
2754                       name:NSConnectionDidDieNotification
2755                     object:svrConn];
2757     [connectionNameDict removeObjectsForKeys:
2758         [connectionNameDict allKeysForObject:svrConn]];
2760     // HACK! Assume connection uses mach ports.
2761     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2762     NSNumber *key = [NSNumber numberWithInt:port];
2764     [clientProxyDict removeObjectForKey:key];
2765     [serverReplyDict removeObjectForKey:key];
2768 - (void)addClient:(NSDistantObject *)client
2770     NSConnection *conn = [client connectionForProxy];
2771     // HACK! Assume connection uses mach ports.
2772     int port = [(NSMachPort*)[conn sendPort] machPort];
2773     NSNumber *key = [NSNumber numberWithInt:port];
2775     if (![clientProxyDict objectForKey:key]) {
2776         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2777         [clientProxyDict setObject:client forKey:key];
2778     }
2780     // NOTE: 'clientWindow' is a global variable which is used by <client>
2781     clientWindow = port;
2784 - (NSString *)alternateServerNameForName:(NSString *)name
2786     if (!(name && [name length] > 0))
2787         return nil;
2789     // Only look for alternates if 'name' doesn't end in a digit.
2790     unichar lastChar = [name characterAtIndex:[name length]-1];
2791     if (lastChar >= '0' && lastChar <= '9')
2792         return nil;
2794     // Look for alternates among all current servers.
2795     NSArray *list = [self serverList];
2796     if (!(list && [list count] > 0))
2797         return nil;
2799     // Filter out servers starting with 'name' and ending with a number. The
2800     // (?i) pattern ensures that the match is case insensitive.
2801     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2802     NSPredicate *pred = [NSPredicate predicateWithFormat:
2803             @"SELF MATCHES %@", pat];
2804     list = [list filteredArrayUsingPredicate:pred];
2805     if ([list count] > 0) {
2806         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2807         return [list objectAtIndex:0];
2808     }
2810     return nil;
2813 @end // MMBackend (ClientServer)
2818 @implementation NSString (MMServerNameCompare)
2819 - (NSComparisonResult)serverNameCompare:(NSString *)string
2821     return [self compare:string
2822                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2824 @end
2829 static int eventModifierFlagsToVimModMask(int modifierFlags)
2831     int modMask = 0;
2833     if (modifierFlags & NSShiftKeyMask)
2834         modMask |= MOD_MASK_SHIFT;
2835     if (modifierFlags & NSControlKeyMask)
2836         modMask |= MOD_MASK_CTRL;
2837     if (modifierFlags & NSAlternateKeyMask)
2838         modMask |= MOD_MASK_ALT;
2839     if (modifierFlags & NSCommandKeyMask)
2840         modMask |= MOD_MASK_CMD;
2842     return modMask;
2845 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2847     int modMask = 0;
2849     if (modifierFlags & NSShiftKeyMask)
2850         modMask |= MOUSE_SHIFT;
2851     if (modifierFlags & NSControlKeyMask)
2852         modMask |= MOUSE_CTRL;
2853     if (modifierFlags & NSAlternateKeyMask)
2854         modMask |= MOUSE_ALT;
2856     return modMask;
2859 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2861     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2863     return (buttonNumber >= 0 && buttonNumber < 3)
2864             ? mouseButton[buttonNumber] : -1;
2869 // This function is modeled after the VimToPython function found in if_python.c
2870 // NB This does a deep copy by value, it does not lookup references like the
2871 // VimToPython function does.  This is because I didn't want to deal with the
2872 // retain cycles that this would create, and we can cover 99% of the use cases
2873 // by ignoring it.  If we ever switch to using GC in MacVim then this
2874 // functionality can be implemented easily.
2875 static id vimToCocoa(typval_T * tv, int depth)
2877     id result = nil;
2878     id newObj = nil;
2881     // Avoid infinite recursion
2882     if (depth > 100) {
2883         return nil;
2884     }
2886     if (tv->v_type == VAR_STRING) {
2887         char_u * val = tv->vval.v_string;
2888         // val can be NULL if the string is empty
2889         if (!val) {
2890             result = [NSString string];
2891         } else {
2892 #ifdef FEAT_MBYTE
2893             val = CONVERT_TO_UTF8(val);
2894 #endif
2895             result = [NSString stringWithUTF8String:(char*)val];
2896 #ifdef FEAT_MBYTE
2897             CONVERT_TO_UTF8_FREE(val);
2898 #endif
2899         }
2900     } else if (tv->v_type == VAR_NUMBER) {
2901         // looks like sizeof(varnumber_T) is always <= sizeof(long)
2902         result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2903     } else if (tv->v_type == VAR_LIST) {
2904         list_T * list = tv->vval.v_list;
2905         listitem_T * curr;
2907         NSMutableArray * arr = result = [NSMutableArray array];
2909         if (list != NULL) {
2910             for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2911                 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2912                 [arr addObject:newObj];
2913             }
2914         }
2915     } else if (tv->v_type == VAR_DICT) {
2916         NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2918         if (tv->vval.v_dict != NULL) {
2919             hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2920             int todo = ht->ht_used;
2921             hashitem_T * hi;
2922             dictitem_T * di;
2924             for (hi = ht->ht_array; todo > 0; ++hi) {
2925                 if (!HASHITEM_EMPTY(hi)) {
2926                     --todo;
2928                     di = dict_lookup(hi);
2929                     newObj = vimToCocoa(&di->di_tv, depth + 1);
2931                     char_u * keyval = hi->hi_key;
2932 #ifdef FEAT_MBYTE
2933                     keyval = CONVERT_TO_UTF8(keyval);
2934 #endif
2935                     NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2936 #ifdef FEAT_MBYTE
2937                     CONVERT_TO_UTF8_FREE(keyval);
2938 #endif
2939                     [dict setObject:newObj forKey:key];
2940                 }
2941             }
2942         }
2943     } else { // only func refs should fall into this category?
2944         result = nil;
2945     }
2947     return result;
2951 // This function is modeled after eval_client_expr_to_string found in main.c
2952 // Returns nil if there was an error evaluating the expression, and writes a
2953 // message to errorStr.
2954 // TODO Get the error that occurred while evaluating the expression in vim
2955 // somehow.
2956 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2959     char_u *s = (char_u*)[expr UTF8String];
2961 #ifdef FEAT_MBYTE
2962     s = CONVERT_FROM_UTF8(s);
2963 #endif
2965     int save_dbl = debug_break_level;
2966     int save_ro = redir_off;
2968     debug_break_level = -1;
2969     redir_off = 0;
2970     ++emsg_skip;
2972     typval_T * tvres = eval_expr(s, NULL);
2974     debug_break_level = save_dbl;
2975     redir_off = save_ro;
2976     --emsg_skip;
2978     setcursor();
2979     out_flush();
2981 #ifdef FEAT_MBYTE
2982     CONVERT_FROM_UTF8_FREE(s);
2983 #endif
2985 #ifdef FEAT_GUI
2986     if (gui.in_use)
2987         gui_update_cursor(FALSE, FALSE);
2988 #endif
2990     if (tvres == NULL) {
2991         free_tv(tvres);
2992         *errstr = @"Expression evaluation failed.";
2993     }
2995     id res = vimToCocoa(tvres, 1);
2997     free_tv(tvres);
2999     if (res == nil) {
3000         *errstr = @"Conversion to cocoa values failed.";
3001     }
3003     return res;
3008 @implementation NSString (VimStrings)
3010 + (id)stringWithVimString:(char_u *)s
3012     // This method ensures a non-nil string is returned.  If 's' cannot be
3013     // converted to a utf-8 string it is assumed to be latin-1.  If conversion
3014     // still fails an empty NSString is returned.
3015     NSString *string = nil;
3016     if (s) {
3017 #ifdef FEAT_MBYTE
3018         s = CONVERT_TO_UTF8(s);
3019 #endif
3020         string = [NSString stringWithUTF8String:(char*)s];
3021         if (!string) {
3022             // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
3023             // latin-1?
3024             string = [NSString stringWithCString:(char*)s
3025                                         encoding:NSISOLatin1StringEncoding];
3026         }
3027 #ifdef FEAT_MBYTE
3028         CONVERT_TO_UTF8_FREE(s);
3029 #endif
3030     }
3032     return string != nil ? string : [NSString string];
3035 - (char_u *)vimStringSave
3037     char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3039 #ifdef FEAT_MBYTE
3040     s = CONVERT_FROM_UTF8(s);
3041 #endif
3042     ret = vim_strsave(s);
3043 #ifdef FEAT_MBYTE
3044     CONVERT_FROM_UTF8_FREE(s);
3045 #endif
3047     return ret;
3050 @end // NSString (VimStrings)