Cmd-. sends SIGINT
[MacVim.git] / src / MacVim / MMBackend.m
blob5316c9ad8891147d10566144f1d3276337278b92
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
11  * MMBackend
12  *
13  * MMBackend communicates with the frontend (MacVim).  It maintains a queue of
14  * output which is flushed to the frontend under controlled circumstances (so
15  * as to maintain a steady framerate).  Input from the frontend is also handled
16  * here.
17  *
18  * The frontend communicates with the backend via the MMBackendProtocol.  In
19  * particular, input is sent to the backend via processInput:data: and Vim
20  * state can be queried from the frontend with evaluateExpression:.
21  *
22  * It is very important to realize that all state is held by the backend, the
23  * frontend must either ask for state [MMBackend evaluateExpression:] or wait
24  * for the backend to update [MMVimController processCommandQueue:].
25  *
26  * The client/server functionality of Vim is handled by the backend.  It sets
27  * up a named NSConnection to which other Vim processes can connect.
28  */
30 #import "MMBackend.h"
34 // NOTE: Colors in MMBackend are stored as unsigned ints on the form 0xaarrggbb
35 // whereas colors in Vim are int without the alpha component.  Also note that
36 // 'transp' is assumed to be a value between 0 and 100.
37 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
38 #define MM_COLOR_WITH_TRANSP(col,transp) \
39     ((unsigned)( ((col)&0xffffff) \
40         | ((((unsigned)((((100-(transp))*255)/100)+.5f))&0xff)<<24) ))
42 // Values for window layout (must match values in main.c).
43 #define WIN_HOR     1       // "-o" horizontally split windows
44 #define WIN_VER     2       // "-O" vertically split windows
45 #define WIN_TABS    3       // "-p" windows on tab pages
47 static unsigned MMServerMax = 1000;
49 // TODO: Move to separate file.
50 static int eventModifierFlagsToVimModMask(int modifierFlags);
51 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
52 static int eventButtonNumberToVimMouseButton(int buttonNumber);
54 // 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)waitForDialogReturn;
86 - (void)insertVimStateMessage;
87 - (void)processInputQueue;
88 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
89 + (NSDictionary *)specialKeys;
90 - (void)handleInsertText:(NSString *)text;
91 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
92 - (void)queueMessage:(int)msgid data:(NSData *)data;
93 - (void)connectionDidDie:(NSNotification *)notification;
94 - (void)blinkTimerFired:(NSTimer *)timer;
95 - (void)focusChange:(BOOL)on;
96 - (void)handleToggleToolbar;
97 - (void)handleScrollbarEvent:(NSData *)data;
98 - (void)handleSetFont:(NSData *)data;
99 - (void)handleDropFiles:(NSData *)data;
100 - (void)handleDropString:(NSData *)data;
101 - (void)startOdbEditWithArguments:(NSDictionary *)args;
102 - (void)handleXcodeMod:(NSData *)data;
103 - (void)handleOpenWithArguments:(NSDictionary *)args;
104 - (BOOL)checkForModifiedBuffers;
105 - (void)addInput:(NSString *)input;
106 - (BOOL)unusedEditor;
107 - (void)redrawScreen;
108 - (void)handleFindReplace:(NSDictionary *)args;
109 @end
113 @interface MMBackend (ClientServer)
114 - (NSString *)connectionNameFromServerName:(NSString *)name;
115 - (NSConnection *)connectionForServerName:(NSString *)name;
116 - (NSConnection *)connectionForServerPort:(int)port;
117 - (void)serverConnectionDidDie:(NSNotification *)notification;
118 - (void)addClient:(NSDistantObject *)client;
119 - (NSString *)alternateServerNameForName:(NSString *)name;
120 @end
124 @implementation MMBackend
126 + (MMBackend *)sharedInstance
128     static MMBackend *singleton = nil;
129     return singleton ? singleton : (singleton = [MMBackend new]);
132 - (id)init
134     self = [super init];
135     if (!self) return nil;
137     outputQueue = [[NSMutableArray alloc] init];
138     inputQueue = [[NSMutableArray alloc] init];
139     drawData = [[NSMutableData alloc] initWithCapacity:1024];
140     connectionNameDict = [[NSMutableDictionary alloc] init];
141     clientProxyDict = [[NSMutableDictionary alloc] init];
142     serverReplyDict = [[NSMutableDictionary alloc] init];
144     NSBundle *mainBundle = [NSBundle mainBundle];
145     NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
146     if (path)
147         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
149     path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
150     if (path)
151         sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
152             retain];
154     path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
155     if (path)
156         actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
158     if (!(colorDict && sysColorDict && actionDict))
159         NSLog(@"ERROR: Failed to load dictionaries.%@",
160                 MMSymlinkWarningString);
162     return self;
165 - (void)dealloc
167     //NSLog(@"%@ %s", [self className], _cmd);
168     [[NSNotificationCenter defaultCenter] removeObserver:self];
170     gui_mch_free_font(oldWideFont);  oldWideFont = NOFONT;
171     [blinkTimer release];  blinkTimer = nil;
172     [alternateServerName release];  alternateServerName = nil;
173     [serverReplyDict release];  serverReplyDict = nil;
174     [clientProxyDict release];  clientProxyDict = nil;
175     [connectionNameDict release];  connectionNameDict = nil;
176     [inputQueue release];  inputQueue = nil;
177     [outputQueue release];  outputQueue = nil;
178     [drawData release];  drawData = nil;
179     [frontendProxy release];  frontendProxy = nil;
180     [connection release];  connection = nil;
181     [actionDict release];  actionDict = nil;
182     [sysColorDict release];  sysColorDict = nil;
183     [colorDict release];  colorDict = nil;
185     [super dealloc];
188 - (void)setBackgroundColor:(int)color
190     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
193 - (void)setForegroundColor:(int)color
195     foregroundColor = MM_COLOR(color);
198 - (void)setSpecialColor:(int)color
200     specialColor = MM_COLOR(color);
203 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
205     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
206     defaultForegroundColor = MM_COLOR(fg);
208     NSMutableData *data = [NSMutableData data];
210     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
211     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
213     [self queueMessage:SetDefaultColorsMsgID data:data];
216 - (NSConnection *)connection
218     if (!connection) {
219         // NOTE!  If the name of the connection changes here it must also be
220         // updated in MMAppController.m.
221         NSString *name = [NSString stringWithFormat:@"%@-connection",
222                [[NSBundle mainBundle] bundlePath]];
224         connection = [NSConnection connectionWithRegisteredName:name host:nil];
225         [connection retain];
226     }
228     // NOTE: 'connection' may be nil here.
229     return connection;
232 - (NSDictionary *)actionDict
234     return actionDict;
237 - (int)initialWindowLayout
239     return initialWindowLayout;
242 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
244     [self queueMessage:msgid data:[props dictionaryAsData]];
247 - (BOOL)checkin
249     if (![self connection]) {
250         if (waitForAck) {
251             // This is a preloaded process and as such should not cause the
252             // MacVim to be opened.  We probably got here as a result of the
253             // user quitting MacVim while the process was preloading, so exit
254             // this process too.
255             // (Don't use mch_exit() since it assumes the process has properly
256             // started.)
257             exit(0);
258         }
260         NSBundle *mainBundle = [NSBundle mainBundle];
261 #if 0
262         OSStatus status;
263         FSRef ref;
265         // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
266         // the API to pass Apple Event parameters is broken on 10.4).
267         NSString *path = [mainBundle bundlePath];
268         status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
269         if (noErr == status) {
270             // Pass parameter to the 'Open' Apple Event that tells MacVim not
271             // to open an untitled window.
272             NSAppleEventDescriptor *desc =
273                     [NSAppleEventDescriptor recordDescriptor];
274             [desc setParamDescriptor:
275                     [NSAppleEventDescriptor descriptorWithBoolean:NO]
276                           forKeyword:keyMMUntitledWindow];
278             LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
279                     kLSLaunchDefaults, NULL };
280             status = LSOpenFromRefSpec(&spec, NULL);
281         }
283         if (noErr != status) {
284         NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
285                 path, MMSymlinkWarningString);
286             return NO;
287         }
288 #else
289         // Launch MacVim using NSTask.  For some reason the above code using
290         // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
291         // fails, the dock icon starts bouncing and never stops).  It seems
292         // like rebuilding the Launch Services database takes care of this
293         // problem, but the NSTask way seems more stable so stick with it.
294         //
295         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
296         // that the GUI won't be activated (or raised) so there is a hack in
297         // MMAppController which raises the app when a new window is opened.
298         NSMutableArray *args = [NSMutableArray arrayWithObjects:
299             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
300         NSString *exeName = [[mainBundle infoDictionary]
301                 objectForKey:@"CFBundleExecutable"];
302         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
303         if (!path) {
304             NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
305                     MMSymlinkWarningString);
306             return NO;
307         }
309         [NSTask launchedTaskWithLaunchPath:path arguments:args];
310 #endif
312         // HACK!  Poll the mach bootstrap server until it returns a valid
313         // connection to detect that MacVim has finished launching.  Also set a
314         // time-out date so that we don't get stuck doing this forever.
315         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
316         while (![self connection] &&
317                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
318             [[NSRunLoop currentRunLoop]
319                     runMode:NSDefaultRunLoopMode
320                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
322         // NOTE: [self connection] will set 'connection' as a side-effect.
323         if (!connection) {
324             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
325             return NO;
326         }
327     }
329     BOOL ok = NO;
330     @try {
331         [[NSNotificationCenter defaultCenter] addObserver:self
332                 selector:@selector(connectionDidDie:)
333                     name:NSConnectionDidDieNotification object:connection];
335         id proxy = [connection rootProxy];
336         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
338         int pid = [[NSProcessInfo processInfo] processIdentifier];
340         frontendProxy = [proxy connectBackend:self pid:pid];
341         if (frontendProxy) {
342             [frontendProxy retain];
343             [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
344             ok = YES;
345         }
346     }
347     @catch (NSException *e) {
348         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
349     }
351     return ok;
354 - (BOOL)openGUIWindow
356     [self queueMessage:OpenWindowMsgID data:nil];
357     return YES;
360 - (void)clearAll
362     int type = ClearAllDrawType;
364     // Any draw commands in queue are effectively obsolete since this clearAll
365     // will negate any effect they have, therefore we may as well clear the
366     // draw queue.
367     [drawData setLength:0];
369     [drawData appendBytes:&type length:sizeof(int)];
372 - (void)clearBlockFromRow:(int)row1 column:(int)col1
373                     toRow:(int)row2 column:(int)col2
375     int type = ClearBlockDrawType;
377     [drawData appendBytes:&type length:sizeof(int)];
379     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
380     [drawData appendBytes:&row1 length:sizeof(int)];
381     [drawData appendBytes:&col1 length:sizeof(int)];
382     [drawData appendBytes:&row2 length:sizeof(int)];
383     [drawData appendBytes:&col2 length:sizeof(int)];
386 - (void)deleteLinesFromRow:(int)row count:(int)count
387               scrollBottom:(int)bottom left:(int)left right:(int)right
389     int type = DeleteLinesDrawType;
391     [drawData appendBytes:&type length:sizeof(int)];
393     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
394     [drawData appendBytes:&row length:sizeof(int)];
395     [drawData appendBytes:&count length:sizeof(int)];
396     [drawData appendBytes:&bottom length:sizeof(int)];
397     [drawData appendBytes:&left length:sizeof(int)];
398     [drawData appendBytes:&right length:sizeof(int)];
401 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
402              cells:(int)cells flags:(int)flags
404     if (len <= 0 || cells <= 0) return;
406     int type = DrawStringDrawType;
408     [drawData appendBytes:&type length:sizeof(int)];
410     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
411     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
412     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
413     [drawData appendBytes:&row length:sizeof(int)];
414     [drawData appendBytes:&col length:sizeof(int)];
415     [drawData appendBytes:&cells length:sizeof(int)];
416     [drawData appendBytes:&flags length:sizeof(int)];
417     [drawData appendBytes:&len length:sizeof(int)];
418     [drawData appendBytes:s length:len];
421 - (void)insertLinesFromRow:(int)row count:(int)count
422               scrollBottom:(int)bottom left:(int)left right:(int)right
424     int type = InsertLinesDrawType;
426     [drawData appendBytes:&type length:sizeof(int)];
428     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
429     [drawData appendBytes:&row length:sizeof(int)];
430     [drawData appendBytes:&count length:sizeof(int)];
431     [drawData appendBytes:&bottom length:sizeof(int)];
432     [drawData appendBytes:&left length:sizeof(int)];
433     [drawData appendBytes:&right length:sizeof(int)];
436 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
437                fraction:(int)percent color:(int)color
439     int type = DrawCursorDrawType;
440     unsigned uc = MM_COLOR(color);
442     [drawData appendBytes:&type length:sizeof(int)];
444     [drawData appendBytes:&uc length:sizeof(unsigned)];
445     [drawData appendBytes:&row length:sizeof(int)];
446     [drawData appendBytes:&col length:sizeof(int)];
447     [drawData appendBytes:&shape length:sizeof(int)];
448     [drawData appendBytes:&percent length:sizeof(int)];
451 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
452                    numColumns:(int)nc invert:(int)invert
454     int type = DrawInvertedRectDrawType;
455     [drawData appendBytes:&type length:sizeof(int)];
457     [drawData appendBytes:&row length:sizeof(int)];
458     [drawData appendBytes:&col length:sizeof(int)];
459     [drawData appendBytes:&nr length:sizeof(int)];
460     [drawData appendBytes:&nc length:sizeof(int)];
461     [drawData appendBytes:&invert length:sizeof(int)];
464 - (void)update
466     // Keep running the run-loop until there is no more input to process.
467     while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
468             == kCFRunLoopRunHandledSource)
469         ;   // do nothing
472 - (void)flushQueue:(BOOL)force
474     // NOTE: This variable allows for better control over when the queue is
475     // flushed.  It can be set to YES at the beginning of a sequence of calls
476     // that may potentially add items to the queue, and then restored back to
477     // NO.
478     if (flushDisabled) return;
480     if ([drawData length] > 0) {
481         // HACK!  Detect changes to 'guifontwide'.
482         if (gui.wide_font != oldWideFont) {
483             gui_mch_free_font(oldWideFont);
484             oldWideFont = gui_mch_retain_font(gui.wide_font);
485             [self setFont:oldWideFont wide:YES];
486         }
488         int type = SetCursorPosDrawType;
489         [drawData appendBytes:&type length:sizeof(type)];
490         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
491         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
493         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
494         [drawData setLength:0];
495     }
497     if ([outputQueue count] > 0) {
498         [self insertVimStateMessage];
500         @try {
501             [frontendProxy processCommandQueue:outputQueue];
502         }
503         @catch (NSException *e) {
504             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
505             NSLog(@"outputQueue(len:%d)=%@", [outputQueue count]/2,
506                     outputQueue);
507             if (![connection isValid]) {
508                 NSLog(@"WARNING! Connection is invalid, exit now!");
509                 NSLog(@"waitForAck=%d got_int=%d", waitForAck, got_int);
510                 mch_exit(-1);
511             }
512         }
514         [outputQueue removeAllObjects];
515     }
518 - (BOOL)waitForInput:(int)milliseconds
520     // Return NO if we timed out waiting for input, otherwise return YES.
521     BOOL inputReceived = NO;
523     // Only start the run loop if the input queue is empty, otherwise process
524     // the input first so that the input on queue isn't delayed.
525     if ([inputQueue count]) {
526         inputReceived = YES;
527     } else {
528         // Wait for the specified amount of time, unless 'milliseconds' is
529         // negative in which case we wait "forever" (1e6 seconds translates to
530         // approximately 11 days).
531         CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
533         while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
534                 == kCFRunLoopRunHandledSource) {
535             // In order to ensure that all input on the run-loop has been
536             // processed we set the timeout to 0 and keep processing until the
537             // run-loop times out.
538             dt = 0.0;
539             inputReceived = YES;
540         }
541     }
543     // The above calls may have placed messages on the input queue so process
544     // it now.  This call may enter a blocking loop.
545     if ([inputQueue count] > 0)
546         [self processInputQueue];
548     return inputReceived;
551 - (void)exit
553     // NOTE: This is called if mch_exit() is called.  Since we assume here that
554     // the process has started properly, be sure to use exit() instead of
555     // mch_exit() to prematurely terminate a process (or set 'isTerminating'
556     // first).
558     // Make sure no connectionDidDie: notification is received now that we are
559     // already exiting.
560     [[NSNotificationCenter defaultCenter] removeObserver:self];
562     // The 'isTerminating' flag indicates that the frontend is also exiting so
563     // there is no need to flush any more output since the frontend won't look
564     // at it anyway.
565     if (!isTerminating && [connection isValid]) {
566         @try {
567             // Flush the entire queue in case a VimLeave autocommand added
568             // something to the queue.
569             [self queueMessage:CloseWindowMsgID data:nil];
570             [frontendProxy processCommandQueue:outputQueue];
571         }
572         @catch (NSException *e) {
573             NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
574         }
576         // NOTE: If Cmd-w was pressed to close the window the menu is briefly
577         // highlighted and during this pause the frontend won't receive any DO
578         // messages.  If the Vim process exits before this highlighting has
579         // finished Cocoa will emit the following error message:
580         //   *** -[NSMachPort handlePortMessage:]: dropping incoming DO message
581         //   because the connection or ports are invalid
582         // To avoid this warning we delay here.  If the warning still appears
583         // this delay may need to be increased.
584         usleep(150000);
585     }
587 #ifdef MAC_CLIENTSERVER
588     // The default connection is used for the client/server code.
589     [[NSConnection defaultConnection] setRootObject:nil];
590     [[NSConnection defaultConnection] invalidate];
591 #endif
594 - (void)selectTab:(int)index
596     //NSLog(@"%s%d", _cmd, index);
598     index -= 1;
599     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
600     [self queueMessage:SelectTabMsgID data:data];
603 - (void)updateTabBar
605     //NSLog(@"%s", _cmd);
607     NSMutableData *data = [NSMutableData data];
609     int idx = tabpage_index(curtab) - 1;
610     [data appendBytes:&idx length:sizeof(int)];
612     tabpage_T *tp;
613     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
614         // Count the number of windows in the tabpage.
615         //win_T *wp = tp->tp_firstwin;
616         //int wincount;
617         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
618         //[data appendBytes:&wincount length:sizeof(int)];
620         int tabProp = MMTabInfoCount;
621         [data appendBytes:&tabProp length:sizeof(int)];
622         for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
623             // This function puts the label of the tab in the global 'NameBuff'.
624             get_tabline_label(tp, (tabProp == MMTabToolTip));
625             NSString *s = [NSString stringWithVimString:NameBuff];
626             int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
627             if (len < 0)
628                 len = 0;
630             [data appendBytes:&len length:sizeof(int)];
631             if (len > 0)
632                 [data appendBytes:[s UTF8String] length:len];
633         }
634     }
636     [self queueMessage:UpdateTabBarMsgID data:data];
639 - (BOOL)tabBarVisible
641     return tabBarVisible;
644 - (void)showTabBar:(BOOL)enable
646     tabBarVisible = enable;
648     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
649     [self queueMessage:msgid data:nil];
652 - (void)setRows:(int)rows columns:(int)cols
654     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
656     int dim[] = { rows, cols };
657     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
659     [self queueMessage:SetTextDimensionsMsgID data:data];
662 - (void)setWindowTitle:(char *)title
664     NSMutableData *data = [NSMutableData data];
665     int len = strlen(title);
666     if (len <= 0) return;
668     [data appendBytes:&len length:sizeof(int)];
669     [data appendBytes:title length:len];
671     [self queueMessage:SetWindowTitleMsgID data:data];
674 - (void)setDocumentFilename:(char *)filename
676     NSMutableData *data = [NSMutableData data];
677     int len = filename ? strlen(filename) : 0;
679     [data appendBytes:&len length:sizeof(int)];
680     if (len > 0)
681         [data appendBytes:filename length:len];
683     [self queueMessage:SetDocumentFilenameMsgID data:data];
686 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
688     char_u *s = NULL;
690     @try {
691         [frontendProxy showSavePanelWithAttributes:attr];
693         [self waitForDialogReturn];
695         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
696             s = [dialogReturn vimStringSave];
698         [dialogReturn release];  dialogReturn = nil;
699     }
700     @catch (NSException *e) {
701         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
702     }
704     return (char *)s;
707 - (oneway void)setDialogReturn:(in bycopy id)obj
709     // NOTE: This is called by
710     //   - [MMVimController panelDidEnd:::], and
711     //   - [MMVimController alertDidEnd:::],
712     // to indicate that a save/open panel or alert has finished.
714     // We want to distinguish between "no dialog return yet" and "dialog
715     // returned nothing".  The former can be tested with dialogReturn == nil,
716     // the latter with dialogReturn == [NSNull null].
717     if (!obj) obj = [NSNull null];
719     if (obj != dialogReturn) {
720         [dialogReturn release];
721         dialogReturn = [obj retain];
722     }
725 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
727     int retval = 0;
729     @try {
730         [frontendProxy presentDialogWithAttributes:attr];
732         [self waitForDialogReturn];
734         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
735                 && [dialogReturn count]) {
736             retval = [[dialogReturn objectAtIndex:0] intValue];
737             if (txtfield && [dialogReturn count] > 1) {
738                 NSString *retString = [dialogReturn objectAtIndex:1];
739                 char_u *ret = (char_u*)[retString UTF8String];
740 #ifdef FEAT_MBYTE
741                 ret = CONVERT_FROM_UTF8(ret);
742 #endif
743                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
744 #ifdef FEAT_MBYTE
745                 CONVERT_FROM_UTF8_FREE(ret);
746 #endif
747             }
748         }
750         [dialogReturn release]; dialogReturn = nil;
751     }
752     @catch (NSException *e) {
753         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
754     }
756     return retval;
759 - (void)showToolbar:(int)enable flags:(int)flags
761     NSMutableData *data = [NSMutableData data];
763     [data appendBytes:&enable length:sizeof(int)];
764     [data appendBytes:&flags length:sizeof(int)];
766     [self queueMessage:ShowToolbarMsgID data:data];
769 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
771     NSMutableData *data = [NSMutableData data];
773     [data appendBytes:&ident length:sizeof(long)];
774     [data appendBytes:&type length:sizeof(int)];
776     [self queueMessage:CreateScrollbarMsgID data:data];
779 - (void)destroyScrollbarWithIdentifier:(long)ident
781     NSMutableData *data = [NSMutableData data];
782     [data appendBytes:&ident length:sizeof(long)];
784     [self queueMessage:DestroyScrollbarMsgID data:data];
787 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
789     NSMutableData *data = [NSMutableData data];
791     [data appendBytes:&ident length:sizeof(long)];
792     [data appendBytes:&visible length:sizeof(int)];
794     [self queueMessage:ShowScrollbarMsgID data:data];
797 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
799     NSMutableData *data = [NSMutableData data];
801     [data appendBytes:&ident length:sizeof(long)];
802     [data appendBytes:&pos length:sizeof(int)];
803     [data appendBytes:&len length:sizeof(int)];
805     [self queueMessage:SetScrollbarPositionMsgID data:data];
808 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
809                     identifier:(long)ident
811     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
812     float prop = (float)size/(max+1);
813     if (fval < 0) fval = 0;
814     else if (fval > 1.0f) fval = 1.0f;
815     if (prop < 0) prop = 0;
816     else if (prop > 1.0f) prop = 1.0f;
818     NSMutableData *data = [NSMutableData data];
820     [data appendBytes:&ident length:sizeof(long)];
821     [data appendBytes:&fval length:sizeof(float)];
822     [data appendBytes:&prop length:sizeof(float)];
824     [self queueMessage:SetScrollbarThumbMsgID data:data];
827 - (void)setFont:(GuiFont)font wide:(BOOL)wide
829     NSString *fontName = (NSString *)font;
830     float size = 0;
831     NSArray *components = [fontName componentsSeparatedByString:@":"];
832     if ([components count] == 2) {
833         size = [[components lastObject] floatValue];
834         fontName = [components objectAtIndex:0];
835     }
837     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
838     NSMutableData *data = [NSMutableData data];
839     [data appendBytes:&size length:sizeof(float)];
840     [data appendBytes:&len length:sizeof(int)];
842     if (len > 0)
843         [data appendBytes:[fontName UTF8String] length:len];
844     else if (!wide)
845         return;     // Only the wide font can be set to nothing
847     [self queueMessage:(wide ? SetWideFontMsgID : SetFontMsgID) data:data];
850 - (void)executeActionWithName:(NSString *)name
852     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
854     if (len > 0) {
855         NSMutableData *data = [NSMutableData data];
857         [data appendBytes:&len length:sizeof(int)];
858         [data appendBytes:[name UTF8String] length:len];
860         [self queueMessage:ExecuteActionMsgID data:data];
861     }
864 - (void)setMouseShape:(int)shape
866     NSMutableData *data = [NSMutableData data];
867     [data appendBytes:&shape length:sizeof(int)];
868     [self queueMessage:SetMouseShapeMsgID data:data];
871 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
873     // Vim specifies times in milliseconds, whereas Cocoa wants them in
874     // seconds.
875     blinkWaitInterval = .001f*wait;
876     blinkOnInterval = .001f*on;
877     blinkOffInterval = .001f*off;
880 - (void)startBlink
882     if (blinkTimer) {
883         [blinkTimer invalidate];
884         [blinkTimer release];
885         blinkTimer = nil;
886     }
888     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
889             && gui.in_focus) {
890         blinkState = MMBlinkStateOn;
891         blinkTimer =
892             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
893                                               target:self
894                                             selector:@selector(blinkTimerFired:)
895                                             userInfo:nil repeats:NO] retain];
896         gui_update_cursor(TRUE, FALSE);
897         [self flushQueue:YES];
898     }
901 - (void)stopBlink
903     if (MMBlinkStateOff == blinkState) {
904         gui_update_cursor(TRUE, FALSE);
905         [self flushQueue:YES];
906     }
908     blinkState = MMBlinkStateNone;
911 - (void)adjustLinespace:(int)linespace
913     NSMutableData *data = [NSMutableData data];
914     [data appendBytes:&linespace length:sizeof(int)];
915     [self queueMessage:AdjustLinespaceMsgID data:data];
918 - (void)activate
920     [self queueMessage:ActivateMsgID data:nil];
923 - (void)setPreEditRow:(int)row column:(int)col
925     NSMutableData *data = [NSMutableData data];
926     [data appendBytes:&row length:sizeof(int)];
927     [data appendBytes:&col length:sizeof(int)];
928     [self queueMessage:SetPreEditPositionMsgID data:data];
931 - (int)lookupColorWithKey:(NSString *)key
933     if (!(key && [key length] > 0))
934         return INVALCOLOR;
936     NSString *stripKey = [[[[key lowercaseString]
937         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
938             componentsSeparatedByString:@" "]
939                componentsJoinedByString:@""];
941     if (stripKey && [stripKey length] > 0) {
942         // First of all try to lookup key in the color dictionary; note that
943         // all keys in this dictionary are lowercase with no whitespace.
944         id obj = [colorDict objectForKey:stripKey];
945         if (obj) return [obj intValue];
947         // The key was not in the dictionary; is it perhaps of the form
948         // #rrggbb?
949         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
950             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
951             [scanner setScanLocation:1];
952             unsigned hex = 0;
953             if ([scanner scanHexInt:&hex]) {
954                 return (int)hex;
955             }
956         }
958         // As a last resort, check if it is one of the system defined colors.
959         // The keys in this dictionary are also lowercase with no whitespace.
960         obj = [sysColorDict objectForKey:stripKey];
961         if (obj) {
962             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
963             if (col) {
964                 float r, g, b, a;
965                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
966                 [col getRed:&r green:&g blue:&b alpha:&a];
967                 return (((int)(r*255+.5f) & 0xff) << 16)
968                      + (((int)(g*255+.5f) & 0xff) << 8)
969                      +  ((int)(b*255+.5f) & 0xff);
970             }
971         }
972     }
974     //NSLog(@"WARNING: No color with key %@ found.", stripKey);
975     return INVALCOLOR;
978 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
980     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
981     id obj;
983     while ((obj = [e nextObject])) {
984         if ([value isEqual:obj])
985             return YES;
986     }
988     return NO;
991 - (void)enterFullscreen:(int)fuoptions background:(int)bg
993     NSMutableData *data = [NSMutableData data];
994     [data appendBytes:&fuoptions length:sizeof(int)];
995     bg = MM_COLOR(bg);
996     [data appendBytes:&bg length:sizeof(int)];
997     [self queueMessage:EnterFullscreenMsgID data:data];
1000 - (void)leaveFullscreen
1002     [self queueMessage:LeaveFullscreenMsgID data:nil];
1005 - (void)setFullscreenBackgroundColor:(int)color
1007     NSMutableData *data = [NSMutableData data];
1008     color = MM_COLOR(color);
1009     [data appendBytes:&color length:sizeof(int)];
1011     [self queueMessage:SetFullscreenColorMsgID data:data];
1014 - (void)setAntialias:(BOOL)antialias
1016     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1018     [self queueMessage:msgid data:nil];
1021 - (void)updateModifiedFlag
1023     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1024     // vice versa.
1025     int msgid = [self checkForModifiedBuffers]
1026             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1028     [self queueMessage:msgid data:nil];
1031 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1033     // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1034     // queue is processed since that only happens in waitForInput: (and Vim
1035     // regularly checks for Ctrl-C in between waiting for input).
1036     // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1037     // which waits on the run loop will fail to detect this message (e.g. in
1038     // waitForConnectionAcknowledgement).
1040     if (InsertTextMsgID == msgid && data != nil) {
1041         const void *bytes = [data bytes];
1042         bytes += sizeof(int);
1043         int len = *((int*)bytes);  bytes += sizeof(int);
1044         if (1 == len) {
1045             char_u *str = (char_u*)bytes;
1046             if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1047                     (str[0] == intr_char && intr_char != Ctrl_C)) {
1048                 got_int = TRUE;
1049                 [inputQueue removeAllObjects];
1050                 return;
1051             }
1052         }
1053     } else if (TerminateNowMsgID == msgid) {
1054         // Terminate immediately (the frontend is about to quit or this process
1055         // was aborted).
1056         isTerminating = YES;
1057         mch_exit(0);
1058         return;
1059     }
1061     // Remove all previous instances of this message from the input queue, else
1062     // the input queue may fill up as a result of Vim not being able to keep up
1063     // with the speed at which new messages are received.
1064     // Keyboard input is never dropped, unless the input represents and
1065     // auto-repeated key.
1067     BOOL isKeyRepeat = NO;
1068     BOOL isKeyboardInput = NO;
1070     if (data && (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1071             CmdKeyMsgID == msgid)) {
1072         isKeyboardInput = YES;
1074         // The lowest bit of the first int is set if this key is a repeat.
1075         int flags = *((int*)[data bytes]);
1076         if (flags & 1)
1077             isKeyRepeat = YES;
1078     }
1080     // Keyboard input is not removed from the queue; repeats are ignored if
1081     // there already is keyboard input on the input queue.
1082     if (isKeyRepeat || !isKeyboardInput) {
1083         int i, count = [inputQueue count];
1084         for (i = 1; i < count; i+=2) {
1085             if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1086                 if (isKeyRepeat)
1087                     return;
1089                 [inputQueue removeObjectAtIndex:i];
1090                 [inputQueue removeObjectAtIndex:i-1];
1091                 break;
1092             }
1093         }
1094     }
1096     [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1097     [inputQueue addObject:(data ? (id)data : [NSNull null])];
1100 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1102     // This is just a convenience method that allows the frontend to delay
1103     // sending messages.
1104     int i, count = [messages count];
1105     for (i = 1; i < count; i+=2)
1106         [self processInput:[[messages objectAtIndex:i-1] intValue]
1107                       data:[messages objectAtIndex:i]];
1110 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1111                   errorString:(out bycopy NSString **)errstr
1113     return evalExprCocoa(expr, errstr);
1117 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1119     NSString *eval = nil;
1120     char_u *s = (char_u*)[expr UTF8String];
1122 #ifdef FEAT_MBYTE
1123     s = CONVERT_FROM_UTF8(s);
1124 #endif
1126     char_u *res = eval_client_expr_to_string(s);
1128 #ifdef FEAT_MBYTE
1129     CONVERT_FROM_UTF8_FREE(s);
1130 #endif
1132     if (res != NULL) {
1133         s = res;
1134 #ifdef FEAT_MBYTE
1135         s = CONVERT_TO_UTF8(s);
1136 #endif
1137         eval = [NSString stringWithUTF8String:(char*)s];
1138 #ifdef FEAT_MBYTE
1139         CONVERT_TO_UTF8_FREE(s);
1140 #endif
1141         vim_free(res);
1142     }
1144     return eval;
1147 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1149     // TODO: This method should share code with clip_mch_request_selection().
1151     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1152         // If there is no pasteboard, return YES to indicate that there is text
1153         // to copy.
1154         if (!pboard)
1155             return YES;
1157         clip_copy_selection();
1159         // Get the text to put on the pasteboard.
1160         long_u llen = 0; char_u *str = 0;
1161         int type = clip_convert_selection(&str, &llen, &clip_star);
1162         if (type < 0)
1163             return NO;
1164         
1165         // TODO: Avoid overflow.
1166         int len = (int)llen;
1167 #ifdef FEAT_MBYTE
1168         if (output_conv.vc_type != CONV_NONE) {
1169             char_u *conv_str = string_convert(&output_conv, str, &len);
1170             if (conv_str) {
1171                 vim_free(str);
1172                 str = conv_str;
1173             }
1174         }
1175 #endif
1177         NSString *string = [[NSString alloc]
1178             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1180         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1181         [pboard declareTypes:types owner:nil];
1182         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1183     
1184         [string release];
1185         vim_free(str);
1187         return ok;
1188     }
1190     return NO;
1193 - (oneway void)addReply:(in bycopy NSString *)reply
1194                  server:(in byref id <MMVimServerProtocol>)server
1196     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1198     // Replies might come at any time and in any order so we keep them in an
1199     // array inside a dictionary with the send port used as key.
1201     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1202     // HACK! Assume connection uses mach ports.
1203     int port = [(NSMachPort*)[conn sendPort] machPort];
1204     NSNumber *key = [NSNumber numberWithInt:port];
1206     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1207     if (!replies) {
1208         replies = [NSMutableArray array];
1209         [serverReplyDict setObject:replies forKey:key];
1210     }
1212     [replies addObject:reply];
1215 - (void)addInput:(in bycopy NSString *)input
1216           client:(in byref id <MMVimClientProtocol>)client
1218     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1220     // NOTE: We don't call addInput: here because it differs from
1221     // server_to_input_buf() in that it always sets the 'silent' flag and we
1222     // don't want the MacVim client/server code to behave differently from
1223     // other platforms.
1224     char_u *s = [input vimStringSave];
1225     server_to_input_buf(s);
1226     vim_free(s);
1228     [self addClient:(id)client];
1231 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1232                  client:(in byref id <MMVimClientProtocol>)client
1234     [self addClient:(id)client];
1235     return [self evaluateExpression:expr];
1238 - (void)registerServerWithName:(NSString *)name
1240     NSString *svrName = name;
1241     NSConnection *svrConn = [NSConnection defaultConnection];
1242     unsigned i;
1244     for (i = 0; i < MMServerMax; ++i) {
1245         NSString *connName = [self connectionNameFromServerName:svrName];
1247         if ([svrConn registerName:connName]) {
1248             //NSLog(@"Registered server with name: %@", svrName);
1250             // TODO: Set request/reply time-outs to something else?
1251             //
1252             // Don't wait for requests (time-out means that the message is
1253             // dropped).
1254             [svrConn setRequestTimeout:0];
1255             //[svrConn setReplyTimeout:MMReplyTimeout];
1256             [svrConn setRootObject:self];
1258             // NOTE: 'serverName' is a global variable
1259             serverName = [svrName vimStringSave];
1260 #ifdef FEAT_EVAL
1261             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1262 #endif
1263 #ifdef FEAT_TITLE
1264             need_maketitle = TRUE;
1265 #endif
1266             [self queueMessage:SetServerNameMsgID data:
1267                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1268             break;
1269         }
1271         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1272     }
1275 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1276                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1277               silent:(BOOL)silent
1279     // NOTE: If 'name' equals 'serverName' then the request is local (client
1280     // and server are the same).  This case is not handled separately, so a
1281     // connection will be set up anyway (this simplifies the code).
1283     NSConnection *conn = [self connectionForServerName:name];
1284     if (!conn) {
1285         if (!silent) {
1286             char_u *s = (char_u*)[name UTF8String];
1287 #ifdef FEAT_MBYTE
1288             s = CONVERT_FROM_UTF8(s);
1289 #endif
1290             EMSG2(_(e_noserver), s);
1291 #ifdef FEAT_MBYTE
1292             CONVERT_FROM_UTF8_FREE(s);
1293 #endif
1294         }
1295         return NO;
1296     }
1298     if (port) {
1299         // HACK! Assume connection uses mach ports.
1300         *port = [(NSMachPort*)[conn sendPort] machPort];
1301     }
1303     id proxy = [conn rootProxy];
1304     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1306     @try {
1307         if (expr) {
1308             NSString *eval = [proxy evaluateExpression:string client:self];
1309             if (reply) {
1310                 if (eval) {
1311                     *reply = [eval vimStringSave];
1312                 } else {
1313                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1314                 }
1315             }
1317             if (!eval)
1318                 return NO;
1319         } else {
1320             [proxy addInput:string client:self];
1321         }
1322     }
1323     @catch (NSException *e) {
1324         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1325         return NO;
1326     }
1328     return YES;
1331 - (NSArray *)serverList
1333     NSArray *list = nil;
1335     if ([self connection]) {
1336         id proxy = [connection rootProxy];
1337         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1339         @try {
1340             list = [proxy serverList];
1341         }
1342         @catch (NSException *e) {
1343             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1344         }
1345     } else {
1346         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1347     }
1349     return list;
1352 - (NSString *)peekForReplyOnPort:(int)port
1354     //NSLog(@"%s%d", _cmd, port);
1356     NSNumber *key = [NSNumber numberWithInt:port];
1357     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1358     if (replies && [replies count]) {
1359         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1360         //        [replies objectAtIndex:0]);
1361         return [replies objectAtIndex:0];
1362     }
1364     //NSLog(@"    No replies");
1365     return nil;
1368 - (NSString *)waitForReplyOnPort:(int)port
1370     //NSLog(@"%s%d", _cmd, port);
1371     
1372     NSConnection *conn = [self connectionForServerPort:port];
1373     if (!conn)
1374         return nil;
1376     NSNumber *key = [NSNumber numberWithInt:port];
1377     NSMutableArray *replies = nil;
1378     NSString *reply = nil;
1380     // Wait for reply as long as the connection to the server is valid (unless
1381     // user interrupts wait with Ctrl-C).
1382     while (!got_int && [conn isValid] &&
1383             !(replies = [serverReplyDict objectForKey:key])) {
1384         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1385                                  beforeDate:[NSDate distantFuture]];
1386     }
1388     if (replies) {
1389         if ([replies count] > 0) {
1390             reply = [[replies objectAtIndex:0] retain];
1391             //NSLog(@"    Got reply: %@", reply);
1392             [replies removeObjectAtIndex:0];
1393             [reply autorelease];
1394         }
1396         if ([replies count] == 0)
1397             [serverReplyDict removeObjectForKey:key];
1398     }
1400     return reply;
1403 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1405     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1406     if (client) {
1407         @try {
1408             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1409             [client addReply:reply server:self];
1410             return YES;
1411         }
1412         @catch (NSException *e) {
1413             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1414         }
1415     } else {
1416         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1417     }
1419     return NO;
1422 - (BOOL)waitForAck
1424     return waitForAck;
1427 - (void)setWaitForAck:(BOOL)yn
1429     waitForAck = yn;
1432 - (void)waitForConnectionAcknowledgement
1434     if (!waitForAck) return;
1436     while (waitForAck && !got_int && [connection isValid]) {
1437         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1438                                  beforeDate:[NSDate distantFuture]];
1439         //NSLog(@"  waitForAck=%d got_int=%d isValid=%d",
1440         //        waitForAck, got_int, [connection isValid]);
1441     }
1443     if (waitForAck) {
1444         // Never received a connection acknowledgement, so die.
1445         [[NSNotificationCenter defaultCenter] removeObserver:self];
1446         [frontendProxy release];  frontendProxy = nil;
1448         // NOTE: We intentionally do not call mch_exit() since this in turn
1449         // will lead to -[MMBackend exit] getting called which we want to
1450         // avoid.
1451         exit(0);
1452     }
1454     [self processInputQueue];
1457 - (oneway void)acknowledgeConnection
1459     //NSLog(@"%s", _cmd);
1460     waitForAck = NO;
1463 @end // MMBackend
1467 @implementation MMBackend (Private)
1469 - (void)waitForDialogReturn
1471     // Keep processing the run loop until a dialog returns.  To avoid getting
1472     // stuck in an endless loop (could happen if the setDialogReturn: message
1473     // was lost) we also do some paranoia checks.
1474     //
1475     // Note that in Cocoa the user can still resize windows and select menu
1476     // items while a sheet is being displayed, so we can't just wait for the
1477     // first message to arrive and assume that is the setDialogReturn: call.
1479     while (nil == dialogReturn && !got_int && [connection isValid])
1480         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1481                                  beforeDate:[NSDate distantFuture]];
1483     // Search for any resize messages on the input queue.  All other messages
1484     // on the input queue are dropped.  The reason why we single out resize
1485     // messages is because the user may have resized the window while a sheet
1486     // was open.
1487     int i, count = [inputQueue count];
1488     if (count > 0) {
1489         id textDimData = nil;
1490         if (count%2 == 0) {
1491             for (i = count-2; i >= 0; i -= 2) {
1492                 int msgid = [[inputQueue objectAtIndex:i] intValue];
1493                 if (SetTextDimensionsMsgID == msgid) {
1494                     textDimData = [[inputQueue objectAtIndex:i+1] retain];
1495                     break;
1496                 }
1497             }
1498         }
1500         [inputQueue removeAllObjects];
1502         if (textDimData) {
1503             [inputQueue addObject:
1504                     [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1505             [inputQueue addObject:textDimData];
1506             [textDimData release];
1507         }
1508     }
1511 - (void)insertVimStateMessage
1513     // NOTE: This is the place to add Vim state that needs to be accessed from
1514     // MacVim.  Do not add state that could potentially require lots of memory
1515     // since this message gets sent each time the output queue is forcibly
1516     // flushed (e.g. storing the currently selected text would be a bad idea).
1517     // We take this approach of "pushing" the state to MacVim to avoid having
1518     // to make synchronous calls from MacVim to Vim in order to get state.
1520     BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1521     int numTabs = tabpage_index(NULL) - 1;
1522     if (numTabs < 0)
1523         numTabs = 0;
1525     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1526         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1527         [NSNumber numberWithInt:p_mh], @"p_mh",
1528         [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1529         [NSNumber numberWithBool:mmta], @"p_mmta",
1530         [NSNumber numberWithInt:numTabs], @"numTabs",
1531         nil];
1533     // Put the state before all other messages.
1534     int msgid = SetVimStateMsgID;
1535     [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1536     [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1537                       atIndex:0];
1540 - (void)processInputQueue
1542     if ([inputQueue count] == 0) return;
1544     // NOTE: One of the input events may cause this method to be called
1545     // recursively, so copy the input queue to a local variable and clear the
1546     // queue before starting to process input events (otherwise we could get
1547     // stuck in an endless loop).
1548     NSArray *q = [inputQueue copy];
1549     unsigned i, count = [q count];
1551     [inputQueue removeAllObjects];
1553     for (i = 1; i < count; i+=2) {
1554         int msgid = [[q objectAtIndex:i-1] intValue];
1555         id data = [q objectAtIndex:i];
1556         if ([data isEqual:[NSNull null]])
1557             data = nil;
1559         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1560         [self handleInputEvent:msgid data:data];
1561     }
1563     [q release];
1564     //NSLog(@"Clear input event queue");
1567 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1569     if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1570             CmdKeyMsgID == msgid) {
1571         if (!data) return;
1572         const void *bytes = [data bytes];
1573         int mods = *((int*)bytes);  bytes += sizeof(int);
1574         int len = *((int*)bytes);  bytes += sizeof(int);
1575         NSString *key = [[NSString alloc] initWithBytes:bytes
1576                                                  length:len
1577                                                encoding:NSUTF8StringEncoding];
1578         mods = eventModifierFlagsToVimModMask(mods);
1580         if (InsertTextMsgID == msgid)
1581             [self handleInsertText:key];
1582         else
1583             [self handleKeyDown:key modifiers:mods];
1585         [key release];
1586     } else if (ScrollWheelMsgID == msgid) {
1587         if (!data) return;
1588         const void *bytes = [data bytes];
1590         int row = *((int*)bytes);  bytes += sizeof(int);
1591         int col = *((int*)bytes);  bytes += sizeof(int);
1592         int flags = *((int*)bytes);  bytes += sizeof(int);
1593         float dy = *((float*)bytes);  bytes += sizeof(float);
1595         int button = MOUSE_5;
1596         if (dy > 0) button = MOUSE_4;
1598         flags = eventModifierFlagsToVimMouseModMask(flags);
1600         int numLines = (int)round(dy);
1601         if (numLines < 0) numLines = -numLines;
1602         if (numLines == 0) numLines = 1;
1604 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1605         gui.scroll_wheel_force = numLines;
1606 #endif
1608         gui_send_mouse_event(button, col, row, NO, flags);
1609     } else if (MouseDownMsgID == msgid) {
1610         if (!data) return;
1611         const void *bytes = [data bytes];
1613         int row = *((int*)bytes);  bytes += sizeof(int);
1614         int col = *((int*)bytes);  bytes += sizeof(int);
1615         int button = *((int*)bytes);  bytes += sizeof(int);
1616         int flags = *((int*)bytes);  bytes += sizeof(int);
1617         int count = *((int*)bytes);  bytes += sizeof(int);
1619         button = eventButtonNumberToVimMouseButton(button);
1620         if (button >= 0) {
1621             flags = eventModifierFlagsToVimMouseModMask(flags);
1622             gui_send_mouse_event(button, col, row, count>1, flags);
1623         }
1624     } else if (MouseUpMsgID == msgid) {
1625         if (!data) return;
1626         const void *bytes = [data bytes];
1628         int row = *((int*)bytes);  bytes += sizeof(int);
1629         int col = *((int*)bytes);  bytes += sizeof(int);
1630         int flags = *((int*)bytes);  bytes += sizeof(int);
1632         flags = eventModifierFlagsToVimMouseModMask(flags);
1634         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1635     } else if (MouseDraggedMsgID == msgid) {
1636         if (!data) return;
1637         const void *bytes = [data bytes];
1639         int row = *((int*)bytes);  bytes += sizeof(int);
1640         int col = *((int*)bytes);  bytes += sizeof(int);
1641         int flags = *((int*)bytes);  bytes += sizeof(int);
1643         flags = eventModifierFlagsToVimMouseModMask(flags);
1645         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1646     } else if (MouseMovedMsgID == msgid) {
1647         const void *bytes = [data bytes];
1648         int row = *((int*)bytes);  bytes += sizeof(int);
1649         int col = *((int*)bytes);  bytes += sizeof(int);
1651         gui_mouse_moved(col, row);
1652     } else if (AddInputMsgID == msgid) {
1653         NSString *string = [[NSString alloc] initWithData:data
1654                 encoding:NSUTF8StringEncoding];
1655         if (string) {
1656             [self addInput:string];
1657             [string release];
1658         }
1659     } else if (SelectTabMsgID == msgid) {
1660         if (!data) return;
1661         const void *bytes = [data bytes];
1662         int idx = *((int*)bytes) + 1;
1663         //NSLog(@"Selecting tab %d", idx);
1664         send_tabline_event(idx);
1665     } else if (CloseTabMsgID == msgid) {
1666         if (!data) return;
1667         const void *bytes = [data bytes];
1668         int idx = *((int*)bytes) + 1;
1669         //NSLog(@"Closing tab %d", idx);
1670         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1671     } else if (AddNewTabMsgID == msgid) {
1672         //NSLog(@"Adding new tab");
1673         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1674     } else if (DraggedTabMsgID == msgid) {
1675         if (!data) return;
1676         const void *bytes = [data bytes];
1677         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1678         // based.
1679         int idx = *((int*)bytes);
1681         tabpage_move(idx);
1682     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1683             || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1684         if (!data) return;
1685         const void *bytes = [data bytes];
1686         int rows = Rows;
1687         if (SetTextColumnsMsgID != msgid) {
1688             rows = *((int*)bytes);  bytes += sizeof(int);
1689         }
1690         int cols = Columns;
1691         if (SetTextRowsMsgID != msgid) {
1692             cols = *((int*)bytes);  bytes += sizeof(int);
1693         }
1695         NSData *d = data;
1696         if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1697             int dim[2] = { rows, cols };
1698             d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1699             msgid = SetTextDimensionsReplyMsgID;
1700         }
1702         if (SetTextDimensionsMsgID == msgid)
1703             msgid = SetTextDimensionsReplyMsgID;
1705         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1706         // gui_resize_shell(), so we have to manually set the rows and columns
1707         // here since MacVim doesn't change the rows and columns to avoid
1708         // inconsistent states between Vim and MacVim.  The message sent back
1709         // indicates that it is a reply to a message that originated in MacVim
1710         // since we need to be able to determine where a message originated.
1711         [self queueMessage:msgid data:d];
1713         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1714         gui_resize_shell(cols, rows);
1715     } else if (ExecuteMenuMsgID == msgid) {
1716         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1717         if (attrs) {
1718             NSArray *desc = [attrs objectForKey:@"descriptor"];
1719             vimmenu_T *menu = menu_for_descriptor(desc);
1720             if (menu)
1721                 gui_menu_cb(menu);
1722         }
1723     } else if (ToggleToolbarMsgID == msgid) {
1724         [self handleToggleToolbar];
1725     } else if (ScrollbarEventMsgID == msgid) {
1726         [self handleScrollbarEvent:data];
1727     } else if (SetFontMsgID == msgid) {
1728         [self handleSetFont:data];
1729     } else if (VimShouldCloseMsgID == msgid) {
1730         gui_shell_closed();
1731     } else if (DropFilesMsgID == msgid) {
1732         [self handleDropFiles:data];
1733     } else if (DropStringMsgID == msgid) {
1734         [self handleDropString:data];
1735     } else if (GotFocusMsgID == msgid) {
1736         if (!gui.in_focus)
1737             [self focusChange:YES];
1738     } else if (LostFocusMsgID == msgid) {
1739         if (gui.in_focus)
1740             [self focusChange:NO];
1741     } else if (SetMouseShapeMsgID == msgid) {
1742         const void *bytes = [data bytes];
1743         int shape = *((int*)bytes);  bytes += sizeof(int);
1744         update_mouseshape(shape);
1745     } else if (XcodeModMsgID == msgid) {
1746         [self handleXcodeMod:data];
1747     } else if (OpenWithArgumentsMsgID == msgid) {
1748         [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1749     } else if (FindReplaceMsgID == msgid) {
1750         [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1751     } else {
1752         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1753     }
1756 + (NSDictionary *)specialKeys
1758     static NSDictionary *specialKeys = nil;
1760     if (!specialKeys) {
1761         NSBundle *mainBundle = [NSBundle mainBundle];
1762         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1763                                               ofType:@"plist"];
1764         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1765     }
1767     return specialKeys;
1770 - (void)handleInsertText:(NSString *)text
1772     if (!text) return;
1774     char_u *str = (char_u*)[text UTF8String];
1775     int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1777 #ifdef FEAT_MBYTE
1778     char_u *conv_str = NULL;
1779     if (input_conv.vc_type != CONV_NONE) {
1780         conv_str = string_convert(&input_conv, str, &len);
1781         if (conv_str)
1782             str = conv_str;
1783     }
1784 #endif
1786     for (i = 0; i < len; ++i) {
1787         add_to_input_buf(str+i, 1);
1788         if (CSI == str[i]) {
1789             // NOTE: If the converted string contains the byte CSI, then it
1790             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1791             // won't work.
1792             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1793             add_to_input_buf(extra, 2);
1794         }
1795     }
1797 #ifdef FEAT_MBYTE
1798     if (conv_str)
1799         vim_free(conv_str);
1800 #endif
1803 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1805     // TODO: This code is a horrible mess -- clean up!
1806     char_u special[3];
1807     char_u modChars[3];
1808     char_u *chars = (char_u*)[key UTF8String];
1809 #ifdef FEAT_MBYTE
1810     char_u *conv_str = NULL;
1811 #endif
1812     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1814     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1815     // that new keys can easily be added.
1816     NSString *specialString = [[MMBackend specialKeys]
1817             objectForKey:key];
1818     if (specialString && [specialString length] > 1) {
1819         //NSLog(@"special key: %@", specialString);
1820         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1821                 [specialString characterAtIndex:1]);
1823         ikey = simplify_key(ikey, &mods);
1824         if (ikey == CSI)
1825             ikey = K_CSI;
1827         special[0] = CSI;
1828         special[1] = K_SECOND(ikey);
1829         special[2] = K_THIRD(ikey);
1831         chars = special;
1832         length = 3;
1833     } else if (1 == length && TAB == chars[0]) {
1834         // Tab is a trouble child:
1835         // - <Tab> is added to the input buffer as is
1836         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1837         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1838         //   to be converted to utf-8
1839         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1840         // - <C-Tab> is reserved by Mac OS X
1841         // - <D-Tab> is reserved by Mac OS X
1842         chars = special;
1843         special[0] = TAB;
1844         length = 1;
1846         if (mods & MOD_MASK_SHIFT) {
1847             mods &= ~MOD_MASK_SHIFT;
1848             special[0] = CSI;
1849             special[1] = K_SECOND(K_S_TAB);
1850             special[2] = K_THIRD(K_S_TAB);
1851             length = 3;
1852         } else if (mods & MOD_MASK_ALT) {
1853             int mtab = 0x80 | TAB;
1854 #ifdef FEAT_MBYTE
1855             if (enc_utf8) {
1856                 // Convert to utf-8
1857                 special[0] = (mtab >> 6) + 0xc0;
1858                 special[1] = mtab & 0xbf;
1859                 length = 2;
1860             } else
1861 #endif
1862             {
1863                 special[0] = mtab;
1864                 length = 1;
1865             }
1866             mods &= ~MOD_MASK_ALT;
1867         }
1868     } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1869         // META key is treated separately.  This code was taken from gui_w48.c
1870         // and gui_gtk_x11.c.
1871         char_u string[7];
1872         int ch = simplify_key(chars[0], &mods);
1874         // Remove the SHIFT modifier for keys where it's already included,
1875         // e.g., '(' and '*'
1876         if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1877             mods &= ~MOD_MASK_SHIFT;
1879         // Interpret the ALT key as making the key META, include SHIFT, etc.
1880         ch = extract_modifiers(ch, &mods);
1881         if (ch == CSI)
1882             ch = K_CSI;
1884         int len = 0;
1885         if (mods) {
1886             string[len++] = CSI;
1887             string[len++] = KS_MODIFIER;
1888             string[len++] = mods;
1889         }
1891         if (IS_SPECIAL(ch)) {
1892             string[len++] = CSI;
1893             string[len++] = K_SECOND(ch);
1894             string[len++] = K_THIRD(ch);
1895         } else {
1896             string[len++] = ch;
1897 #ifdef FEAT_MBYTE
1898             // TODO: What if 'enc' is not "utf-8"?
1899             if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1900                 string[len++] = ch & 0xbf;
1901                 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1902                 if (string[len-1] == CSI) {
1903                     string[len++] = KS_EXTRA;
1904                     string[len++] = (int)KE_CSI;
1905                 }
1906             }
1907 #endif
1908         }
1910         add_to_input_buf(string, len);
1911         return;
1912     } else if (length > 0) {
1913         unichar c = [key characterAtIndex:0];
1914         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1915         //        [key characterAtIndex:0], mods);
1917         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1918         // cleared since they are already added to the key by the AppKit.
1919         // Unfortunately, the only way to deal with when to clear the modifiers
1920         // or not seems to be to have hard-wired rules like this.
1921         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1922                     || 0x9 == c || 0xd == c || ESC == c) ) {
1923             mods &= ~MOD_MASK_SHIFT;
1924             mods &= ~MOD_MASK_CTRL;
1925             //NSLog(@"clear shift ctrl");
1926         }
1928 #ifdef FEAT_MBYTE
1929         if (input_conv.vc_type != CONV_NONE) {
1930             conv_str = string_convert(&input_conv, chars, &length);
1931             if (conv_str)
1932                 chars = conv_str;
1933         }
1934 #endif
1935     }
1937     if (chars && length > 0) {
1938         if (mods) {
1939             //NSLog(@"adding mods: %d", mods);
1940             modChars[0] = CSI;
1941             modChars[1] = KS_MODIFIER;
1942             modChars[2] = mods;
1943             add_to_input_buf(modChars, 3);
1944         }
1946         //NSLog(@"add to input buf: 0x%x", chars[0]);
1947         // TODO: Check for CSI bytes?
1948         add_to_input_buf(chars, length);
1949     }
1951 #ifdef FEAT_MBYTE
1952     if (conv_str)
1953         vim_free(conv_str);
1954 #endif
1957 - (void)queueMessage:(int)msgid data:(NSData *)data
1959     //if (msgid != EnableMenuItemMsgID)
1960     //    NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1962     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1963     if (data)
1964         [outputQueue addObject:data];
1965     else
1966         [outputQueue addObject:[NSData data]];
1969 - (void)connectionDidDie:(NSNotification *)notification
1971     // If the main connection to MacVim is lost this means that either MacVim
1972     // has crashed or this process did not receive its termination message
1973     // properly (e.g. if the TerminateNowMsgID was dropped).
1974     //
1975     // NOTE: This is not called if a Vim controller invalidates its connection.
1977     NSLog(@"WARNING[%s]: Main connection was lost before process had a chance "
1978             "to terminate; preserving swap files.", _cmd);
1979     getout_preserve_modified(1);
1982 - (void)blinkTimerFired:(NSTimer *)timer
1984     NSTimeInterval timeInterval = 0;
1986     [blinkTimer release];
1987     blinkTimer = nil;
1989     if (MMBlinkStateOn == blinkState) {
1990         gui_undraw_cursor();
1991         blinkState = MMBlinkStateOff;
1992         timeInterval = blinkOffInterval;
1993     } else if (MMBlinkStateOff == blinkState) {
1994         gui_update_cursor(TRUE, FALSE);
1995         blinkState = MMBlinkStateOn;
1996         timeInterval = blinkOnInterval;
1997     }
1999     if (timeInterval > 0) {
2000         blinkTimer = 
2001             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2002                                             selector:@selector(blinkTimerFired:)
2003                                             userInfo:nil repeats:NO] retain];
2004         [self flushQueue:YES];
2005     }
2008 - (void)focusChange:(BOOL)on
2010     gui_focus_change(on);
2013 - (void)handleToggleToolbar
2015     // If 'go' contains 'T', then remove it, else add it.
2017     char_u go[sizeof(GO_ALL)+2];
2018     char_u *p;
2019     int len;
2021     STRCPY(go, p_go);
2022     p = vim_strchr(go, GO_TOOLBAR);
2023     len = STRLEN(go);
2025     if (p != NULL) {
2026         char_u *end = go + len;
2027         while (p < end) {
2028             p[0] = p[1];
2029             ++p;
2030         }
2031     } else {
2032         go[len] = GO_TOOLBAR;
2033         go[len+1] = NUL;
2034     }
2036     set_option_value((char_u*)"guioptions", 0, go, 0);
2038     [self redrawScreen];
2041 - (void)handleScrollbarEvent:(NSData *)data
2043     if (!data) return;
2045     const void *bytes = [data bytes];
2046     long ident = *((long*)bytes);  bytes += sizeof(long);
2047     int hitPart = *((int*)bytes);  bytes += sizeof(int);
2048     float fval = *((float*)bytes);  bytes += sizeof(float);
2049     scrollbar_T *sb = gui_find_scrollbar(ident);
2051     if (sb) {
2052         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2053         long value = sb_info->value;
2054         long size = sb_info->size;
2055         long max = sb_info->max;
2056         BOOL isStillDragging = NO;
2057         BOOL updateKnob = YES;
2059         switch (hitPart) {
2060         case NSScrollerDecrementPage:
2061             value -= (size > 2 ? size - 2 : 1);
2062             break;
2063         case NSScrollerIncrementPage:
2064             value += (size > 2 ? size - 2 : 1);
2065             break;
2066         case NSScrollerDecrementLine:
2067             --value;
2068             break;
2069         case NSScrollerIncrementLine:
2070             ++value;
2071             break;
2072         case NSScrollerKnob:
2073             isStillDragging = YES;
2074             // fall through ...
2075         case NSScrollerKnobSlot:
2076             value = (long)(fval * (max - size + 1));
2077             // fall through ...
2078         default:
2079             updateKnob = NO;
2080             break;
2081         }
2083         //NSLog(@"value %d -> %d", sb_info->value, value);
2084         gui_drag_scrollbar(sb, value, isStillDragging);
2086         if (updateKnob) {
2087             // Dragging the knob or option+clicking automatically updates
2088             // the knob position (on the actual NSScroller), so we only
2089             // need to set the knob position in the other cases.
2090             if (sb->wp) {
2091                 // Update both the left&right vertical scrollbars.
2092                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2093                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2094                 [self setScrollbarThumbValue:value size:size max:max
2095                                   identifier:identLeft];
2096                 [self setScrollbarThumbValue:value size:size max:max
2097                                   identifier:identRight];
2098             } else {
2099                 // Update the horizontal scrollbar.
2100                 [self setScrollbarThumbValue:value size:size max:max
2101                                   identifier:ident];
2102             }
2103         }
2104     }
2107 - (void)handleSetFont:(NSData *)data
2109     if (!data) return;
2111     const void *bytes = [data bytes];
2112     float pointSize = *((float*)bytes);  bytes += sizeof(float);
2113     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2114     bytes += sizeof(unsigned);  // len not used
2116     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2117     [name appendString:[NSString stringWithFormat:@":h%d", (int)pointSize]];
2118     char_u *s = (char_u*)[name UTF8String];
2120 #ifdef FEAT_MBYTE
2121     s = CONVERT_FROM_UTF8(s);
2122 #endif
2124     set_option_value((char_u*)"guifont", 0, s, 0);
2126 #ifdef FEAT_MBYTE
2127     CONVERT_FROM_UTF8_FREE(s);
2128 #endif
2130     [self redrawScreen];
2133 - (void)handleDropFiles:(NSData *)data
2135     // TODO: Get rid of this method; instead use Vim script directly.  At the
2136     // moment I know how to do this to open files in tabs, but I'm not sure how
2137     // to add the filenames to the command line when in command line mode.
2139     if (!data) return;
2141     NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2142     if (!args) return;
2144     id obj = [args objectForKey:@"forceOpen"];
2145     BOOL forceOpen = YES;
2146     if (obj)
2147         forceOpen = [obj boolValue];
2149     NSArray *filenames = [args objectForKey:@"filenames"];
2150     if (!(filenames && [filenames count] > 0)) return;
2152 #ifdef FEAT_DND
2153     if (!forceOpen && (State & CMDLINE)) {
2154         // HACK!  If Vim is in command line mode then the files names
2155         // should be added to the command line, instead of opening the
2156         // files in tabs (unless forceOpen is set).  This is taken care of by
2157         // gui_handle_drop().
2158         int n = [filenames count];
2159         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2160         if (fnames) {
2161             int i = 0;
2162             for (i = 0; i < n; ++i)
2163                 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2165             // NOTE!  This function will free 'fnames'.
2166             // HACK!  It is assumed that the 'x' and 'y' arguments are
2167             // unused when in command line mode.
2168             gui_handle_drop(0, 0, 0, fnames, n);
2169         }
2170     } else
2171 #endif // FEAT_DND
2172     {
2173         [self handleOpenWithArguments:args];
2174     }
2177 - (void)handleDropString:(NSData *)data
2179     if (!data) return;
2181 #ifdef FEAT_DND
2182     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2183     const void *bytes = [data bytes];
2184     int len = *((int*)bytes);  bytes += sizeof(int);
2185     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2187     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2188     NSRange range = { 0, [string length] };
2189     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2190                                          withString:@"\x0a" options:0
2191                                               range:range];
2192     if (0 == n) {
2193         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2194                                        options:0 range:range];
2195     }
2197     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2198     char_u *s = (char_u*)[string UTF8String];
2199 #ifdef FEAT_MBYTE
2200     if (input_conv.vc_type != CONV_NONE)
2201         s = string_convert(&input_conv, s, &len);
2202 #endif
2203     dnd_yank_drag_data(s, len);
2204 #ifdef FEAT_MBYTE
2205     if (input_conv.vc_type != CONV_NONE)
2206         vim_free(s);
2207 #endif
2208     add_to_input_buf(dropkey, sizeof(dropkey));
2209 #endif // FEAT_DND
2212 - (void)startOdbEditWithArguments:(NSDictionary *)args
2214 #ifdef FEAT_ODB_EDITOR
2215     id obj = [args objectForKey:@"remoteID"];
2216     if (!obj) return;
2218     OSType serverID = [obj unsignedIntValue];
2219     NSString *remotePath = [args objectForKey:@"remotePath"];
2221     NSAppleEventDescriptor *token = nil;
2222     NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2223     obj = [args objectForKey:@"remoteTokenDescType"];
2224     if (tokenData && obj) {
2225         DescType tokenType = [obj unsignedLongValue];
2226         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2227                                                                 data:tokenData];
2228     }
2230     NSArray *filenames = [args objectForKey:@"filenames"];
2231     unsigned i, numFiles = [filenames count];
2232     for (i = 0; i < numFiles; ++i) {
2233         NSString *filename = [filenames objectAtIndex:i];
2234         char_u *s = [filename vimStringSave];
2235         buf_T *buf = buflist_findname(s);
2236         vim_free(s);
2238         if (buf) {
2239             if (buf->b_odb_token) {
2240                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2241                 buf->b_odb_token = NULL;
2242             }
2244             if (buf->b_odb_fname) {
2245                 vim_free(buf->b_odb_fname);
2246                 buf->b_odb_fname = NULL;
2247             }
2249             buf->b_odb_server_id = serverID;
2251             if (token)
2252                 buf->b_odb_token = [token retain];
2253             if (remotePath)
2254                 buf->b_odb_fname = [remotePath vimStringSave];
2255         } else {
2256             NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2257                     filename);
2258         }
2259     }
2260 #endif // FEAT_ODB_EDITOR
2263 - (void)handleXcodeMod:(NSData *)data
2265 #if 0
2266     const void *bytes = [data bytes];
2267     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2268     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2269     if (0 == len)
2270         return;
2272     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2273             descriptorWithDescriptorType:type
2274                                    bytes:bytes
2275                                   length:len];
2276 #endif
2279 - (void)handleOpenWithArguments:(NSDictionary *)args
2281     // ARGUMENT:                DESCRIPTION:
2282     // -------------------------------------------------------------
2283     // filenames                list of filenames
2284     // dontOpen                 don't open files specified in above argument
2285     // layout                   which layout to use to open files
2286     // selectionRange           range of lines to select
2287     // searchText               string to search for
2288     // cursorLine               line to position the cursor on
2289     // cursorColumn             column to position the cursor on
2290     //                          (only valid when "cursorLine" is set)
2291     // remoteID                 ODB parameter
2292     // remotePath               ODB parameter
2293     // remoteTokenDescType      ODB parameter
2294     // remoteTokenData          ODB parameter
2296     //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2298     NSArray *filenames = [args objectForKey:@"filenames"];
2299     int i, numFiles = filenames ? [filenames count] : 0;
2300     BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2301     int layout = [[args objectForKey:@"layout"] intValue];
2303     // Change to directory of first file to open if this is an "unused" editor
2304     // (but do not do this if editing remotely).
2305     if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2306             && (starting || [self unusedEditor]) ) {
2307         char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2308         vim_chdirfile(s);
2309         vim_free(s);
2310     }
2312     if (starting > 0) {
2313         // When Vim is starting we simply add the files to be opened to the
2314         // global arglist and Vim will take care of opening them for us.
2315         if (openFiles && numFiles > 0) {
2316             for (i = 0; i < numFiles; i++) {
2317                 NSString *fname = [filenames objectAtIndex:i];
2318                 char_u *p = NULL;
2320                 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2321                         || (p = [fname vimStringSave]) == NULL)
2322                     exit(2); // See comment in -[MMBackend exit]
2323                 else
2324                     alist_add(&global_alist, p, 2);
2325             }
2327             // Vim will take care of arranging the files added to the arglist
2328             // in windows or tabs; all we must do is to specify which layout to
2329             // use.
2330             initialWindowLayout = layout;
2331         }
2332     } else {
2333         // When Vim is already open we resort to some trickery to open the
2334         // files with the specified layout.
2335         //
2336         // TODO: Figure out a better way to handle this?
2337         if (openFiles && numFiles > 0) {
2338             BOOL oneWindowInTab = topframe ? YES
2339                                            : (topframe->fr_layout == FR_LEAF);
2340             BOOL bufChanged = NO;
2341             BOOL bufHasFilename = NO;
2342             if (curbuf) {
2343                 bufChanged = curbufIsChanged();
2344                 bufHasFilename = curbuf->b_ffname != NULL;
2345             }
2347             // Temporarily disable flushing since the following code may
2348             // potentially cause multiple redraws.
2349             flushDisabled = YES;
2351             BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2352             if (WIN_TABS == layout && !onlyOneTab) {
2353                 // By going to the last tabpage we ensure that the new tabs
2354                 // will appear last (if this call is left out, the taborder
2355                 // becomes messy).
2356                 goto_tabpage(9999);
2357             }
2359             // Make sure we're in normal mode first.
2360             [self addInput:@"<C-\\><C-N>"];
2362             if (numFiles > 1) {
2363                 // With "split layout" we open a new tab before opening
2364                 // multiple files if the current tab has more than one window
2365                 // or if there is exactly one window but whose buffer has a
2366                 // filename.  (The :drop command ensures modified buffers get
2367                 // their own window.)
2368                 if ((WIN_HOR == layout || WIN_VER == layout) &&
2369                         (!oneWindowInTab || bufHasFilename))
2370                     [self addInput:@":tabnew<CR>"];
2372                 // The files are opened by constructing a ":drop ..." command
2373                 // and executing it.
2374                 NSMutableString *cmd = (WIN_TABS == layout)
2375                         ? [NSMutableString stringWithString:@":tab drop"]
2376                         : [NSMutableString stringWithString:@":drop"];
2378                 for (i = 0; i < numFiles; ++i) {
2379                     NSString *file = [filenames objectAtIndex:i];
2380                     file = [file stringByEscapingSpecialFilenameCharacters];
2381                     [cmd appendString:@" "];
2382                     [cmd appendString:file];
2383                 }
2385                 // Temporarily clear 'suffixes' so that the files are opened in
2386                 // the same order as they appear in the "filenames" array.
2387                 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2389                 [self addInput:cmd];
2391                 // Split the view into multiple windows if requested.
2392                 if (WIN_HOR == layout)
2393                     [self addInput:@"|sall"];
2394                 else if (WIN_VER == layout)
2395                     [self addInput:@"|vert sall"];
2397                 // Restore the old value of 'suffixes'.
2398                 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2399             } else {
2400                 // When opening one file we try to reuse the current window,
2401                 // but not if its buffer is modified or has a filename.
2402                 // However, the 'arglist' layout always opens the file in the
2403                 // current window.
2404                 NSString *file = [[filenames lastObject]
2405                         stringByEscapingSpecialFilenameCharacters];
2406                 NSString *cmd;
2407                 if (WIN_HOR == layout) {
2408                     if (!(bufHasFilename || bufChanged))
2409                         cmd = [NSString stringWithFormat:@":e %@", file];
2410                     else
2411                         cmd = [NSString stringWithFormat:@":sp %@", file];
2412                 } else if (WIN_VER == layout) {
2413                     if (!(bufHasFilename || bufChanged))
2414                         cmd = [NSString stringWithFormat:@":e %@", file];
2415                     else
2416                         cmd = [NSString stringWithFormat:@":vsp %@", file];
2417                 } else if (WIN_TABS == layout) {
2418                     if (oneWindowInTab && !(bufHasFilename || bufChanged))
2419                         cmd = [NSString stringWithFormat:@":e %@", file];
2420                     else
2421                         cmd = [NSString stringWithFormat:@":tabe %@", file];
2422                 } else {
2423                     // (The :drop command will split if there is a modified
2424                     // buffer.)
2425                     cmd = [NSString stringWithFormat:@":drop %@", file];
2426                 }
2428                 [self addInput:cmd];
2429                 [self addInput:@"<CR>"];
2430             }
2432             // Force screen redraw (does it have to be this complicated?).
2433             // (This code was taken from the end of gui_handle_drop().)
2434             update_screen(NOT_VALID);
2435             setcursor();
2436             out_flush();
2437             gui_update_cursor(FALSE, FALSE);
2438             maketitle();
2440             flushDisabled = NO;
2441         }
2442     }
2444     if ([args objectForKey:@"remoteID"]) {
2445         // NOTE: We have to delay processing any ODB related arguments since
2446         // the file(s) may not be opened until the input buffer is processed.
2447         [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2448                                withObject:args
2449                             waitUntilDone:NO];
2450     }
2452     NSString *lineString = [args objectForKey:@"cursorLine"];
2453     if (lineString && [lineString intValue] > 0) {
2454         NSString *columnString = [args objectForKey:@"cursorColumn"];
2455         if (!(columnString && [columnString intValue] > 0))
2456             columnString = @"1";
2458         NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2459                 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2460         [self addInput:cmd];
2461     }
2463     NSString *rangeString = [args objectForKey:@"selectionRange"];
2464     if (rangeString) {
2465         // Build a command line string that will select the given range of
2466         // lines.  If range.length == 0, then position the cursor on the given
2467         // line but do not select.
2468         NSRange range = NSRangeFromString(rangeString);
2469         NSString *cmd;
2470         if (range.length > 0) {
2471             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2472                     NSMaxRange(range), range.location];
2473         } else {
2474             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2475                     range.location];
2476         }
2478         [self addInput:cmd];
2479     }
2481     NSString *searchText = [args objectForKey:@"searchText"];
2482     if (searchText) {
2483         // TODO: Searching is an exclusive motion, so if the pattern would
2484         // match on row 0 column 0 then this pattern will miss that match.
2485         [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@<CR>",
2486                 searchText]];
2487     }
2490 - (BOOL)checkForModifiedBuffers
2492     buf_T *buf;
2493     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2494         if (bufIsChanged(buf)) {
2495             return YES;
2496         }
2497     }
2499     return NO;
2502 - (void)addInput:(NSString *)input
2504     // NOTE: This code is essentially identical to server_to_input_buf(),
2505     // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2506     char_u *string = [input vimStringSave];
2507     if (!string) return;
2509     /* Set 'cpoptions' the way we want it.
2510      *    B set - backslashes are *not* treated specially
2511      *    k set - keycodes are *not* reverse-engineered
2512      *    < unset - <Key> sequences *are* interpreted
2513      *  The last but one parameter of replace_termcodes() is TRUE so that the
2514      *  <lt> sequence is recognised - needed for a real backslash.
2515      */
2516     char_u *ptr = NULL;
2517     char_u *cpo_save = p_cpo;
2518     p_cpo = (char_u *)"Bk";
2519     char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2520     p_cpo = cpo_save;
2522     if (*ptr != NUL)    /* trailing CTRL-V results in nothing */
2523     {
2524         /*
2525          * Add the string to the input stream.
2526          * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2527          *
2528          * First clear typed characters from the typeahead buffer, there could
2529          * be half a mapping there.  Then append to the existing string, so
2530          * that multiple commands from a client are concatenated.
2531          */
2532         if (typebuf.tb_maplen < typebuf.tb_len)
2533             del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2534         (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2536         /* Let input_available() know we inserted text in the typeahead
2537          * buffer. */
2538         typebuf_was_filled = TRUE;
2539     }
2540     vim_free(ptr);
2541     vim_free(string);
2544 - (BOOL)unusedEditor
2546     BOOL oneWindowInTab = topframe ? YES
2547                                    : (topframe->fr_layout == FR_LEAF);
2548     BOOL bufChanged = NO;
2549     BOOL bufHasFilename = NO;
2550     if (curbuf) {
2551         bufChanged = curbufIsChanged();
2552         bufHasFilename = curbuf->b_ffname != NULL;
2553     }
2555     BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2557     return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2560 - (void)redrawScreen
2562     // Force screen redraw (does it have to be this complicated?).
2563     redraw_all_later(CLEAR);
2564     update_screen(NOT_VALID);
2565     setcursor();
2566     out_flush();
2567     gui_update_cursor(FALSE, FALSE);
2569     // HACK! The cursor is not put back at the command line by the above
2570     // "redraw commands".  The following test seems to do the trick though.
2571     if (State & CMDLINE)
2572         redrawcmdline();
2575 - (void)handleFindReplace:(NSDictionary *)args
2577     if (!args) return;
2579     NSString *findString = [args objectForKey:@"find"];
2580     if (!findString) return;
2582     char_u *find = [findString vimStringSave];
2583     char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2584     int flags = [[args objectForKey:@"flags"] intValue];
2586     // NOTE: The flag 0x100 is used to indicate a backward search.
2587     gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2589     vim_free(find);
2590     vim_free(replace);
2593 @end // MMBackend (Private)
2598 @implementation MMBackend (ClientServer)
2600 - (NSString *)connectionNameFromServerName:(NSString *)name
2602     NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2604     return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2605         lowercaseString];
2608 - (NSConnection *)connectionForServerName:(NSString *)name
2610     // TODO: Try 'name%d' if 'name' fails.
2611     NSString *connName = [self connectionNameFromServerName:name];
2612     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2614     if (!svrConn) {
2615         svrConn = [NSConnection connectionWithRegisteredName:connName
2616                                                            host:nil];
2617         // Try alternate server...
2618         if (!svrConn && alternateServerName) {
2619             //NSLog(@"  trying to connect to alternate server: %@",
2620             //        alternateServerName);
2621             connName = [self connectionNameFromServerName:alternateServerName];
2622             svrConn = [NSConnection connectionWithRegisteredName:connName
2623                                                             host:nil];
2624         }
2626         // Try looking for alternate servers...
2627         if (!svrConn) {
2628             //NSLog(@"  looking for alternate servers...");
2629             NSString *alt = [self alternateServerNameForName:name];
2630             if (alt != alternateServerName) {
2631                 //NSLog(@"  found alternate server: %@", string);
2632                 [alternateServerName release];
2633                 alternateServerName = [alt copy];
2634             }
2635         }
2637         // Try alternate server again...
2638         if (!svrConn && alternateServerName) {
2639             //NSLog(@"  trying to connect to alternate server: %@",
2640             //        alternateServerName);
2641             connName = [self connectionNameFromServerName:alternateServerName];
2642             svrConn = [NSConnection connectionWithRegisteredName:connName
2643                                                             host:nil];
2644         }
2646         if (svrConn) {
2647             [connectionNameDict setObject:svrConn forKey:connName];
2649             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2650             [[NSNotificationCenter defaultCenter] addObserver:self
2651                     selector:@selector(serverConnectionDidDie:)
2652                         name:NSConnectionDidDieNotification object:svrConn];
2653         }
2654     }
2656     return svrConn;
2659 - (NSConnection *)connectionForServerPort:(int)port
2661     NSConnection *conn;
2662     NSEnumerator *e = [connectionNameDict objectEnumerator];
2664     while ((conn = [e nextObject])) {
2665         // HACK! Assume connection uses mach ports.
2666         if (port == [(NSMachPort*)[conn sendPort] machPort])
2667             return conn;
2668     }
2670     return nil;
2673 - (void)serverConnectionDidDie:(NSNotification *)notification
2675     //NSLog(@"%s%@", _cmd, notification);
2677     NSConnection *svrConn = [notification object];
2679     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2680     [[NSNotificationCenter defaultCenter]
2681             removeObserver:self
2682                       name:NSConnectionDidDieNotification
2683                     object:svrConn];
2685     [connectionNameDict removeObjectsForKeys:
2686         [connectionNameDict allKeysForObject:svrConn]];
2688     // HACK! Assume connection uses mach ports.
2689     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2690     NSNumber *key = [NSNumber numberWithInt:port];
2692     [clientProxyDict removeObjectForKey:key];
2693     [serverReplyDict removeObjectForKey:key];
2696 - (void)addClient:(NSDistantObject *)client
2698     NSConnection *conn = [client connectionForProxy];
2699     // HACK! Assume connection uses mach ports.
2700     int port = [(NSMachPort*)[conn sendPort] machPort];
2701     NSNumber *key = [NSNumber numberWithInt:port];
2703     if (![clientProxyDict objectForKey:key]) {
2704         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2705         [clientProxyDict setObject:client forKey:key];
2706     }
2708     // NOTE: 'clientWindow' is a global variable which is used by <client>
2709     clientWindow = port;
2712 - (NSString *)alternateServerNameForName:(NSString *)name
2714     if (!(name && [name length] > 0))
2715         return nil;
2717     // Only look for alternates if 'name' doesn't end in a digit.
2718     unichar lastChar = [name characterAtIndex:[name length]-1];
2719     if (lastChar >= '0' && lastChar <= '9')
2720         return nil;
2722     // Look for alternates among all current servers.
2723     NSArray *list = [self serverList];
2724     if (!(list && [list count] > 0))
2725         return nil;
2727     // Filter out servers starting with 'name' and ending with a number. The
2728     // (?i) pattern ensures that the match is case insensitive.
2729     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2730     NSPredicate *pred = [NSPredicate predicateWithFormat:
2731             @"SELF MATCHES %@", pat];
2732     list = [list filteredArrayUsingPredicate:pred];
2733     if ([list count] > 0) {
2734         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2735         return [list objectAtIndex:0];
2736     }
2738     return nil;
2741 @end // MMBackend (ClientServer)
2746 @implementation NSString (MMServerNameCompare)
2747 - (NSComparisonResult)serverNameCompare:(NSString *)string
2749     return [self compare:string
2750                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2752 @end
2757 static int eventModifierFlagsToVimModMask(int modifierFlags)
2759     int modMask = 0;
2761     if (modifierFlags & NSShiftKeyMask)
2762         modMask |= MOD_MASK_SHIFT;
2763     if (modifierFlags & NSControlKeyMask)
2764         modMask |= MOD_MASK_CTRL;
2765     if (modifierFlags & NSAlternateKeyMask)
2766         modMask |= MOD_MASK_ALT;
2767     if (modifierFlags & NSCommandKeyMask)
2768         modMask |= MOD_MASK_CMD;
2770     return modMask;
2773 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2775     int modMask = 0;
2777     if (modifierFlags & NSShiftKeyMask)
2778         modMask |= MOUSE_SHIFT;
2779     if (modifierFlags & NSControlKeyMask)
2780         modMask |= MOUSE_CTRL;
2781     if (modifierFlags & NSAlternateKeyMask)
2782         modMask |= MOUSE_ALT;
2784     return modMask;
2787 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2789     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2791     return (buttonNumber >= 0 && buttonNumber < 3)
2792             ? mouseButton[buttonNumber] : -1;
2797 // This function is modeled after the VimToPython function found in if_python.c
2798 // NB This does a deep copy by value, it does not lookup references like the
2799 // VimToPython function does.  This is because I didn't want to deal with the
2800 // retain cycles that this would create, and we can cover 99% of the use cases
2801 // by ignoring it.  If we ever switch to using GC in MacVim then this
2802 // functionality can be implemented easily.
2803 static id vimToCocoa(typval_T * tv, int depth)
2805     id result = nil;
2806     id newObj = nil;
2809     // Avoid infinite recursion
2810     if (depth > 100) {
2811         return nil;
2812     }
2814     if (tv->v_type == VAR_STRING) {
2815         char_u * val = tv->vval.v_string;
2816         // val can be NULL if the string is empty
2817         if (!val) {
2818             result = [NSString string];
2819         } else {
2820 #ifdef FEAT_MBYTE
2821             val = CONVERT_TO_UTF8(val);
2822 #endif
2823             result = [NSString stringWithUTF8String:(char*)val];
2824 #ifdef FEAT_MBYTE
2825             CONVERT_TO_UTF8_FREE(val);
2826 #endif
2827         }
2828     } else if (tv->v_type == VAR_NUMBER) {
2829         // looks like sizeof(varnumber_T) is always <= sizeof(long)
2830         result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2831     } else if (tv->v_type == VAR_LIST) {
2832         list_T * list = tv->vval.v_list;
2833         listitem_T * curr;
2835         NSMutableArray * arr = result = [NSMutableArray array];
2837         if (list != NULL) {
2838             for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2839                 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2840                 [arr addObject:newObj];
2841             }
2842         }
2843     } else if (tv->v_type == VAR_DICT) {
2844         NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2846         if (tv->vval.v_dict != NULL) {
2847             hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2848             int todo = ht->ht_used;
2849             hashitem_T * hi;
2850             dictitem_T * di;
2852             for (hi = ht->ht_array; todo > 0; ++hi) {
2853                 if (!HASHITEM_EMPTY(hi)) {
2854                     --todo;
2856                     di = dict_lookup(hi);
2857                     newObj = vimToCocoa(&di->di_tv, depth + 1);
2859                     char_u * keyval = hi->hi_key;
2860 #ifdef FEAT_MBYTE
2861                     keyval = CONVERT_TO_UTF8(keyval);
2862 #endif
2863                     NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2864 #ifdef FEAT_MBYTE
2865                     CONVERT_TO_UTF8_FREE(keyval);
2866 #endif
2867                     [dict setObject:newObj forKey:key];
2868                 }
2869             }
2870         }
2871     } else { // only func refs should fall into this category?
2872         result = nil;
2873     }
2875     return result;
2879 // This function is modeled after eval_client_expr_to_string found in main.c
2880 // Returns nil if there was an error evaluating the expression, and writes a
2881 // message to errorStr.
2882 // TODO Get the error that occurred while evaluating the expression in vim
2883 // somehow.
2884 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2887     char_u *s = (char_u*)[expr UTF8String];
2889 #ifdef FEAT_MBYTE
2890     s = CONVERT_FROM_UTF8(s);
2891 #endif
2893     int save_dbl = debug_break_level;
2894     int save_ro = redir_off;
2896     debug_break_level = -1;
2897     redir_off = 0;
2898     ++emsg_skip;
2900     typval_T * tvres = eval_expr(s, NULL);
2902     debug_break_level = save_dbl;
2903     redir_off = save_ro;
2904     --emsg_skip;
2906     setcursor();
2907     out_flush();
2909 #ifdef FEAT_MBYTE
2910     CONVERT_FROM_UTF8_FREE(s);
2911 #endif
2913 #ifdef FEAT_GUI
2914     if (gui.in_use)
2915         gui_update_cursor(FALSE, FALSE);
2916 #endif
2918     if (tvres == NULL) {
2919         free_tv(tvres);
2920         *errstr = @"Expression evaluation failed.";
2921     }
2923     id res = vimToCocoa(tvres, 1);
2925     free_tv(tvres);
2927     if (res == nil) {
2928         *errstr = @"Conversion to cocoa values failed.";
2929     }
2931     return res;
2936 @implementation NSString (VimStrings)
2938 + (id)stringWithVimString:(char_u *)s
2940     // This method ensures a non-nil string is returned.  If 's' cannot be
2941     // converted to a utf-8 string it is assumed to be latin-1.  If conversion
2942     // still fails an empty NSString is returned.
2943     NSString *string = nil;
2944     if (s) {
2945 #ifdef FEAT_MBYTE
2946         s = CONVERT_TO_UTF8(s);
2947 #endif
2948         string = [NSString stringWithUTF8String:(char*)s];
2949         if (!string) {
2950             // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2951             // latin-1?
2952             string = [NSString stringWithCString:(char*)s
2953                                         encoding:NSISOLatin1StringEncoding];
2954         }
2955 #ifdef FEAT_MBYTE
2956         CONVERT_TO_UTF8_FREE(s);
2957 #endif
2958     }
2960     return string != nil ? string : [NSString string];
2963 - (char_u *)vimStringSave
2965     char_u *s = (char_u*)[self UTF8String], *ret = NULL;
2967 #ifdef FEAT_MBYTE
2968     s = CONVERT_FROM_UTF8(s);
2969 #endif
2970     ret = vim_strsave(s);
2971 #ifdef FEAT_MBYTE
2972     CONVERT_FROM_UTF8_FREE(s);
2973 #endif
2975     return ret;
2978 @end // NSString (VimStrings)