Dialogs are always displayed in the default run loop mode
[MacVim.git] / src / MacVim / MMBackend.m
blob251ae289ebea098f5f9acffcb208bf4d36190d05
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) ))
43 // This constant controls how often the command queue may be flushed.  If it is
44 // too small the app might feel unresponsive; if it is too large there might be
45 // long periods without the screen updating (e.g. when sourcing a large session
46 // file).  (The unit is seconds.)
47 static float MMFlushTimeoutInterval = 0.1f;
48 static int MMFlushQueueLenHint = 80*40;
50 static unsigned MMServerMax = 1000;
52 // TODO: Move to separate file.
53 static int eventModifierFlagsToVimModMask(int modifierFlags);
54 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
55 static int eventButtonNumberToVimMouseButton(int buttonNumber);
57 // In gui_macvim.m
58 vimmenu_T *menu_for_descriptor(NSArray *desc);
61 enum {
62     MMBlinkStateNone = 0,
63     MMBlinkStateOn,
64     MMBlinkStateOff
67 static NSString *MMSymlinkWarningString =
68     @"\n\n\tMost likely this is because you have symlinked directly to\n"
69      "\tthe Vim binary, which Cocoa does not allow.  Please use an\n"
70      "\talias or the mvim shell script instead.  If you have not used\n"
71      "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
75 @interface NSString (MMServerNameCompare)
76 - (NSComparisonResult)serverNameCompare:(NSString *)string;
77 @end
82 @interface MMBackend (Private)
83 - (void)waitForDialogReturn;
84 - (void)queueVimStateMessage;
85 - (void)processInputQueue;
86 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
87 + (NSDictionary *)specialKeys;
88 - (void)handleInsertText:(NSData *)data;
89 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
90 - (void)queueMessage:(int)msgid data:(NSData *)data;
91 - (void)connectionDidDie:(NSNotification *)notification;
92 - (void)blinkTimerFired:(NSTimer *)timer;
93 - (void)focusChange:(BOOL)on;
94 - (void)handleToggleToolbar;
95 - (void)handleScrollbarEvent:(NSData *)data;
96 - (void)handleSetFont:(NSData *)data;
97 - (void)handleDropFiles:(NSData *)data;
98 - (void)handleDropString:(NSData *)data;
99 - (void)handleOdbEdit:(NSData *)data;
100 - (void)handleXcodeMod:(NSData *)data;
101 - (BOOL)checkForModifiedBuffers;
102 - (void)addInput:(NSString *)input;
103 @end
107 @interface MMBackend (ClientServer)
108 - (NSString *)connectionNameFromServerName:(NSString *)name;
109 - (NSConnection *)connectionForServerName:(NSString *)name;
110 - (NSConnection *)connectionForServerPort:(int)port;
111 - (void)serverConnectionDidDie:(NSNotification *)notification;
112 - (void)addClient:(NSDistantObject *)client;
113 - (NSString *)alternateServerNameForName:(NSString *)name;
114 @end
118 @implementation MMBackend
120 + (MMBackend *)sharedInstance
122     static MMBackend *singleton = nil;
123     return singleton ? singleton : (singleton = [MMBackend new]);
126 - (id)init
128     self = [super init];
129     if (!self) return nil;
131     fontContainerRef = loadFonts();
133     outputQueue = [[NSMutableArray alloc] init];
134     inputQueue = [[NSMutableArray alloc] init];
135     drawData = [[NSMutableData alloc] initWithCapacity:1024];
136     connectionNameDict = [[NSMutableDictionary alloc] init];
137     clientProxyDict = [[NSMutableDictionary alloc] init];
138     serverReplyDict = [[NSMutableDictionary alloc] init];
140     NSBundle *mainBundle = [NSBundle mainBundle];
141     NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
142     if (path)
143         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
145     path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
146     if (path)
147         sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
148             retain];
150     path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
151     if (path)
152         actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
154     if (!(colorDict && sysColorDict && actionDict))
155         NSLog(@"ERROR: Failed to load dictionaries.%@",
156                 MMSymlinkWarningString);
158     return self;
161 - (void)dealloc
163     //NSLog(@"%@ %s", [self className], _cmd);
164     [[NSNotificationCenter defaultCenter] removeObserver:self];
166     [oldWideFont release];  oldWideFont = nil;
167     [blinkTimer release];  blinkTimer = nil;
168     [alternateServerName release];  alternateServerName = nil;
169     [serverReplyDict release];  serverReplyDict = nil;
170     [clientProxyDict release];  clientProxyDict = nil;
171     [connectionNameDict release];  connectionNameDict = nil;
172     [inputQueue release];  inputQueue = nil;
173     [outputQueue release];  outputQueue = nil;
174     [drawData release];  drawData = nil;
175     [frontendProxy release];  frontendProxy = nil;
176     [connection release];  connection = nil;
177     [actionDict release];  actionDict = nil;
178     [sysColorDict release];  sysColorDict = nil;
179     [colorDict release];  colorDict = nil;
181     [super dealloc];
184 - (void)setBackgroundColor:(int)color
186     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
189 - (void)setForegroundColor:(int)color
191     foregroundColor = MM_COLOR(color);
194 - (void)setSpecialColor:(int)color
196     specialColor = MM_COLOR(color);
199 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
201     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
202     defaultForegroundColor = MM_COLOR(fg);
204     NSMutableData *data = [NSMutableData data];
206     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
207     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
209     [self queueMessage:SetDefaultColorsMsgID data:data];
212 - (NSConnection *)connection
214     if (!connection) {
215         // NOTE!  If the name of the connection changes here it must also be
216         // updated in MMAppController.m.
217         NSString *name = [NSString stringWithFormat:@"%@-connection",
218                [[NSBundle mainBundle] bundleIdentifier]];
220         connection = [NSConnection connectionWithRegisteredName:name host:nil];
221         [connection retain];
222     }
224     // NOTE: 'connection' may be nil here.
225     return connection;
228 - (NSDictionary *)actionDict
230     return actionDict;
233 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
235     [self queueMessage:msgid data:[props dictionaryAsData]];
238 - (BOOL)checkin
240     if (![self connection]) {
241         NSBundle *mainBundle = [NSBundle mainBundle];
242 #if 0
243         OSStatus status;
244         FSRef ref;
246         // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
247         // the API to pass Apple Event parameters is broken on 10.4).
248         NSString *path = [mainBundle bundlePath];
249         status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
250         if (noErr == status) {
251             // Pass parameter to the 'Open' Apple Event that tells MacVim not
252             // to open an untitled window.
253             NSAppleEventDescriptor *desc =
254                     [NSAppleEventDescriptor recordDescriptor];
255             [desc setParamDescriptor:
256                     [NSAppleEventDescriptor descriptorWithBoolean:NO]
257                           forKeyword:keyMMUntitledWindow];
259             LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
260                     kLSLaunchDefaults, NULL };
261             status = LSOpenFromRefSpec(&spec, NULL);
262         }
264         if (noErr != status) {
265         NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
266                 path, MMSymlinkWarningString);
267             return NO;
268         }
269 #else
270         // Launch MacVim using NSTask.  For some reason the above code using
271         // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
272         // fails, the dock icon starts bouncing and never stops).  It seems
273         // like rebuilding the Launch Services database takes care of this
274         // problem, but the NSTask way seems more stable so stick with it.
275         //
276         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
277         // that the GUI won't be activated (or raised) so there is a hack in
278         // MMAppController which raises the app when a new window is opened.
279         NSMutableArray *args = [NSMutableArray arrayWithObjects:
280             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
281         NSString *exeName = [[mainBundle infoDictionary]
282                 objectForKey:@"CFBundleExecutable"];
283         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
284         if (!path) {
285             NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
286                     MMSymlinkWarningString);
287             return NO;
288         }
290         [NSTask launchedTaskWithLaunchPath:path arguments:args];
291 #endif
293         // HACK!  Poll the mach bootstrap server until it returns a valid
294         // connection to detect that MacVim has finished launching.  Also set a
295         // time-out date so that we don't get stuck doing this forever.
296         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
297         while (![self connection] &&
298                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
299             [[NSRunLoop currentRunLoop]
300                     runMode:NSDefaultRunLoopMode
301                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
303         // NOTE: [self connection] will set 'connection' as a side-effect.
304         if (!connection) {
305             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
306             return NO;
307         }
308     }
310     BOOL ok = NO;
311     @try {
312         [[NSNotificationCenter defaultCenter] addObserver:self
313                 selector:@selector(connectionDidDie:)
314                     name:NSConnectionDidDieNotification object:connection];
316         id proxy = [connection rootProxy];
317         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
319         int pid = [[NSProcessInfo processInfo] processIdentifier];
321         frontendProxy = [proxy connectBackend:self pid:pid];
322         if (frontendProxy) {
323             [frontendProxy retain];
324             [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
325             ok = YES;
326         }
327     }
328     @catch (NSException *e) {
329         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
330     }
332     return ok;
335 - (BOOL)openVimWindow
337     [self queueMessage:OpenVimWindowMsgID data:nil];
338     return YES;
341 - (void)clearAll
343     int type = ClearAllDrawType;
345     // Any draw commands in queue are effectively obsolete since this clearAll
346     // will negate any effect they have, therefore we may as well clear the
347     // draw queue.
348     [drawData setLength:0];
350     [drawData appendBytes:&type length:sizeof(int)];
353 - (void)clearBlockFromRow:(int)row1 column:(int)col1
354                     toRow:(int)row2 column:(int)col2
356     int type = ClearBlockDrawType;
358     [drawData appendBytes:&type length:sizeof(int)];
360     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
361     [drawData appendBytes:&row1 length:sizeof(int)];
362     [drawData appendBytes:&col1 length:sizeof(int)];
363     [drawData appendBytes:&row2 length:sizeof(int)];
364     [drawData appendBytes:&col2 length:sizeof(int)];
367 - (void)deleteLinesFromRow:(int)row count:(int)count
368               scrollBottom:(int)bottom left:(int)left right:(int)right
370     int type = DeleteLinesDrawType;
372     [drawData appendBytes:&type length:sizeof(int)];
374     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
375     [drawData appendBytes:&row length:sizeof(int)];
376     [drawData appendBytes:&count length:sizeof(int)];
377     [drawData appendBytes:&bottom length:sizeof(int)];
378     [drawData appendBytes:&left length:sizeof(int)];
379     [drawData appendBytes:&right length:sizeof(int)];
382 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
383              cells:(int)cells flags:(int)flags
385     if (len <= 0 || cells <= 0) return;
387     int type = DrawStringDrawType;
389     [drawData appendBytes:&type length:sizeof(int)];
391     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
392     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
393     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
394     [drawData appendBytes:&row length:sizeof(int)];
395     [drawData appendBytes:&col length:sizeof(int)];
396     [drawData appendBytes:&cells length:sizeof(int)];
397     [drawData appendBytes:&flags length:sizeof(int)];
398     [drawData appendBytes:&len length:sizeof(int)];
399     [drawData appendBytes:s length:len];
402 - (void)insertLinesFromRow:(int)row count:(int)count
403               scrollBottom:(int)bottom left:(int)left right:(int)right
405     int type = InsertLinesDrawType;
407     [drawData appendBytes:&type length:sizeof(int)];
409     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
410     [drawData appendBytes:&row length:sizeof(int)];
411     [drawData appendBytes:&count length:sizeof(int)];
412     [drawData appendBytes:&bottom length:sizeof(int)];
413     [drawData appendBytes:&left length:sizeof(int)];
414     [drawData appendBytes:&right length:sizeof(int)];
417 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
418                fraction:(int)percent color:(int)color
420     int type = DrawCursorDrawType;
421     unsigned uc = MM_COLOR(color);
423     [drawData appendBytes:&type length:sizeof(int)];
425     [drawData appendBytes:&uc length:sizeof(unsigned)];
426     [drawData appendBytes:&row length:sizeof(int)];
427     [drawData appendBytes:&col length:sizeof(int)];
428     [drawData appendBytes:&shape length:sizeof(int)];
429     [drawData appendBytes:&percent length:sizeof(int)];
432 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
433                    numColumns:(int)nc invert:(int)invert
435     int type = DrawInvertedRectDrawType;
436     [drawData appendBytes:&type length:sizeof(int)];
438     [drawData appendBytes:&row length:sizeof(int)];
439     [drawData appendBytes:&col length:sizeof(int)];
440     [drawData appendBytes:&nr length:sizeof(int)];
441     [drawData appendBytes:&nc length:sizeof(int)];
442     [drawData appendBytes:&invert length:sizeof(int)];
445 - (void)update
447     // Tend to the run loop, returning immediately if there are no events
448     // waiting.
449     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
450                              beforeDate:[NSDate distantPast]];
452 #if 0
453     // Keyboard and mouse input is handled directly, other input is queued and
454     // processed here.  This call may enter a blocking loop.
455     if ([inputQueue count] > 0)
456         [self processInputQueue];
457 #endif
460 - (void)flushQueue:(BOOL)force
462     // NOTE! This method gets called a lot; if we were to flush every time it
463     // got called MacVim would feel unresponsive.  So there is a time out which
464     // ensures that the queue isn't flushed too often.
465     if (!force && lastFlushDate
466             && -[lastFlushDate timeIntervalSinceNow] < MMFlushTimeoutInterval
467             && [drawData length] < MMFlushQueueLenHint)
468         return;
470     if ([drawData length] > 0) {
471         // HACK!  Detect changes to 'guifontwide'.
472         if (gui.wide_font != (GuiFont)oldWideFont) {
473             [oldWideFont release];
474             oldWideFont = [(NSFont*)gui.wide_font retain];
475             [self setWideFont:oldWideFont];
476         }
478         int type = SetCursorPosDrawType;
479         [drawData appendBytes:&type length:sizeof(type)];
480         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
481         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
483         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
484         [drawData setLength:0];
485     }
487     if ([outputQueue count] > 0 || force) {
488         // When 'force' is set we always update the Vim state to ensure that
489         // MacVim has a copy of the latest state (since 'force' is typically
490         // set just before Vim takes a nap whilst waiting for input).
491         [self queueVimStateMessage];
493         @try {
494             [frontendProxy processCommandQueue:outputQueue];
495         }
496         @catch (NSException *e) {
497             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
498         }
500         [outputQueue removeAllObjects];
502         [lastFlushDate release];
503         lastFlushDate = [[NSDate date] retain];
504     }
507 - (BOOL)waitForInput:(int)milliseconds
509     //NSLog(@"|ENTER| %s%d",  _cmd, milliseconds);
511     // Only start the run loop if the input queue is empty, otherwise process
512     // the input first so that the input on queue isn't delayed.
513     if ([inputQueue count]) {
514         inputReceived = YES;
515     } else {
516         NSDate *date = milliseconds > 0 ?
517                 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
518                 [NSDate distantFuture];
520         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
521                                  beforeDate:date];
522     }
524     // I know of no way to figure out if the run loop exited because input was
525     // found or because of a time out, so I need to manually indicate when
526     // input was received in processInput:data: and then reset it every time
527     // here.
528     BOOL yn = inputReceived;
529     inputReceived = NO;
531     // Keyboard and mouse input is handled directly, other input is queued and
532     // processed here.  This call may enter a blocking loop.
533     if ([inputQueue count] > 0)
534         [self processInputQueue];
536     //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
537     return yn;
540 - (void)exit
542 #ifdef MAC_CLIENTSERVER
543     // The default connection is used for the client/server code.
544     [[NSConnection defaultConnection] setRootObject:nil];
545     [[NSConnection defaultConnection] invalidate];
546 #endif
548     // By invalidating the NSConnection the MMWindowController immediately
549     // finds out that the connection is down and as a result
550     // [MMWindowController connectionDidDie:] is invoked.
551     //NSLog(@"%@ %s", [self className], _cmd);
552     [[NSNotificationCenter defaultCenter] removeObserver:self];
553     [connection invalidate];
555     if (fontContainerRef) {
556         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
557         fontContainerRef = 0;
558     }
562 - (void)selectTab:(int)index
564     //NSLog(@"%s%d", _cmd, index);
566     index -= 1;
567     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
568     [self queueMessage:SelectTabMsgID data:data];
571 - (void)updateTabBar
573     //NSLog(@"%s", _cmd);
575     NSMutableData *data = [NSMutableData data];
577     int idx = tabpage_index(curtab) - 1;
578     [data appendBytes:&idx length:sizeof(int)];
580     tabpage_T *tp;
581     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
582         // This function puts the label of the tab in the global 'NameBuff'.
583         get_tabline_label(tp, FALSE);
584         char_u *s = NameBuff;
585         int len = STRLEN(s);
586         if (len <= 0) continue;
588 #ifdef FEAT_MBYTE
589         s = CONVERT_TO_UTF8(s);
590 #endif
592         // Count the number of windows in the tabpage.
593         //win_T *wp = tp->tp_firstwin;
594         //int wincount;
595         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
597         //[data appendBytes:&wincount length:sizeof(int)];
598         [data appendBytes:&len length:sizeof(int)];
599         [data appendBytes:s length:len];
601 #ifdef FEAT_MBYTE
602         CONVERT_TO_UTF8_FREE(s);
603 #endif
604     }
606     [self queueMessage:UpdateTabBarMsgID data:data];
609 - (BOOL)tabBarVisible
611     return tabBarVisible;
614 - (void)showTabBar:(BOOL)enable
616     tabBarVisible = enable;
618     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
619     [self queueMessage:msgid data:nil];
622 - (void)setRows:(int)rows columns:(int)cols
624     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
626     int dim[] = { rows, cols };
627     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
629     [self queueMessage:SetTextDimensionsMsgID data:data];
632 - (void)setWindowTitle:(char *)title
634     NSMutableData *data = [NSMutableData data];
635     int len = strlen(title);
636     if (len <= 0) return;
638     [data appendBytes:&len length:sizeof(int)];
639     [data appendBytes:title length:len];
641     [self queueMessage:SetWindowTitleMsgID data:data];
644 - (void)setDocumentFilename:(char *)filename
646     NSMutableData *data = [NSMutableData data];
647     int len = filename ? strlen(filename) : 0;
649     [data appendBytes:&len length:sizeof(int)];
650     if (len > 0)
651         [data appendBytes:filename length:len];
653     [self queueMessage:SetDocumentFilenameMsgID data:data];
656 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
658     char_u *s = NULL;
660     @try {
661         [frontendProxy showSavePanelWithAttributes:attr];
663         [self waitForDialogReturn];
665         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
666             char_u *ret = (char_u*)[dialogReturn UTF8String];
667 #ifdef FEAT_MBYTE
668             ret = CONVERT_FROM_UTF8(ret);
669 #endif
670             s = vim_strsave(ret);
671 #ifdef FEAT_MBYTE
672             CONVERT_FROM_UTF8_FREE(ret);
673 #endif
674         }
676         [dialogReturn release];  dialogReturn = nil;
677     }
678     @catch (NSException *e) {
679         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
680     }
682     return (char *)s;
685 - (oneway void)setDialogReturn:(in bycopy id)obj
687     // NOTE: This is called by
688     //   - [MMVimController panelDidEnd:::], and
689     //   - [MMVimController alertDidEnd:::],
690     // to indicate that a save/open panel or alert has finished.
692     // We want to distinguish between "no dialog return yet" and "dialog
693     // returned nothing".  The former can be tested with dialogReturn == nil,
694     // the latter with dialogReturn == [NSNull null].
695     if (!obj) obj = [NSNull null];
697     if (obj != dialogReturn) {
698         [dialogReturn release];
699         dialogReturn = [obj retain];
700     }
703 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
705     int retval = 0;
707     @try {
708         [frontendProxy presentDialogWithAttributes:attr];
710         [self waitForDialogReturn];
712         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
713                 && [dialogReturn count]) {
714             retval = [[dialogReturn objectAtIndex:0] intValue];
715             if (txtfield && [dialogReturn count] > 1) {
716                 NSString *retString = [dialogReturn objectAtIndex:1];
717                 char_u *ret = (char_u*)[retString UTF8String];
718 #ifdef FEAT_MBYTE
719                 ret = CONVERT_FROM_UTF8(ret);
720 #endif
721                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
722 #ifdef FEAT_MBYTE
723                 CONVERT_FROM_UTF8_FREE(ret);
724 #endif
725             }
726         }
728         [dialogReturn release]; dialogReturn = nil;
729     }
730     @catch (NSException *e) {
731         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
732     }
734     return retval;
737 - (void)showToolbar:(int)enable flags:(int)flags
739     NSMutableData *data = [NSMutableData data];
741     [data appendBytes:&enable length:sizeof(int)];
742     [data appendBytes:&flags length:sizeof(int)];
744     [self queueMessage:ShowToolbarMsgID data:data];
747 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
749     NSMutableData *data = [NSMutableData data];
751     [data appendBytes:&ident length:sizeof(long)];
752     [data appendBytes:&type length:sizeof(int)];
754     [self queueMessage:CreateScrollbarMsgID data:data];
757 - (void)destroyScrollbarWithIdentifier:(long)ident
759     NSMutableData *data = [NSMutableData data];
760     [data appendBytes:&ident length:sizeof(long)];
762     [self queueMessage:DestroyScrollbarMsgID data:data];
765 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
767     NSMutableData *data = [NSMutableData data];
769     [data appendBytes:&ident length:sizeof(long)];
770     [data appendBytes:&visible length:sizeof(int)];
772     [self queueMessage:ShowScrollbarMsgID data:data];
775 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
777     NSMutableData *data = [NSMutableData data];
779     [data appendBytes:&ident length:sizeof(long)];
780     [data appendBytes:&pos length:sizeof(int)];
781     [data appendBytes:&len length:sizeof(int)];
783     [self queueMessage:SetScrollbarPositionMsgID data:data];
786 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
787                     identifier:(long)ident
789     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
790     float prop = (float)size/(max+1);
791     if (fval < 0) fval = 0;
792     else if (fval > 1.0f) fval = 1.0f;
793     if (prop < 0) prop = 0;
794     else if (prop > 1.0f) prop = 1.0f;
796     NSMutableData *data = [NSMutableData data];
798     [data appendBytes:&ident length:sizeof(long)];
799     [data appendBytes:&fval length:sizeof(float)];
800     [data appendBytes:&prop length:sizeof(float)];
802     [self queueMessage:SetScrollbarThumbMsgID data:data];
805 - (void)setFont:(NSFont *)font
807     NSString *fontName = [font displayName];
808     float size = [font pointSize];
809     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
810     if (len > 0) {
811         NSMutableData *data = [NSMutableData data];
813         [data appendBytes:&size length:sizeof(float)];
814         [data appendBytes:&len length:sizeof(int)];
815         [data appendBytes:[fontName UTF8String] length:len];
817         [self queueMessage:SetFontMsgID data:data];
818     }
821 - (void)setWideFont:(NSFont *)font
823     NSString *fontName = [font displayName];
824     float size = [font pointSize];
825     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
826     NSMutableData *data = [NSMutableData data];
828     [data appendBytes:&size length:sizeof(float)];
829     [data appendBytes:&len length:sizeof(int)];
830     if (len > 0)
831         [data appendBytes:[fontName UTF8String] length:len];
833     [self queueMessage:SetWideFontMsgID data:data];
836 - (void)executeActionWithName:(NSString *)name
838     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
840     if (len > 0) {
841         NSMutableData *data = [NSMutableData data];
843         [data appendBytes:&len length:sizeof(int)];
844         [data appendBytes:[name UTF8String] length:len];
846         [self queueMessage:ExecuteActionMsgID data:data];
847     }
850 - (void)setMouseShape:(int)shape
852     NSMutableData *data = [NSMutableData data];
853     [data appendBytes:&shape length:sizeof(int)];
854     [self queueMessage:SetMouseShapeMsgID data:data];
857 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
859     // Vim specifies times in milliseconds, whereas Cocoa wants them in
860     // seconds.
861     blinkWaitInterval = .001f*wait;
862     blinkOnInterval = .001f*on;
863     blinkOffInterval = .001f*off;
866 - (void)startBlink
868     if (blinkTimer) {
869         [blinkTimer invalidate];
870         [blinkTimer release];
871         blinkTimer = nil;
872     }
874     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
875             && gui.in_focus) {
876         blinkState = MMBlinkStateOn;
877         blinkTimer =
878             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
879                                               target:self
880                                             selector:@selector(blinkTimerFired:)
881                                             userInfo:nil repeats:NO] retain];
882         gui_update_cursor(TRUE, FALSE);
883         [self flushQueue:YES];
884     }
887 - (void)stopBlink
889     if (MMBlinkStateOff == blinkState) {
890         gui_update_cursor(TRUE, FALSE);
891         [self flushQueue:YES];
892     }
894     blinkState = MMBlinkStateNone;
897 - (void)adjustLinespace:(int)linespace
899     NSMutableData *data = [NSMutableData data];
900     [data appendBytes:&linespace length:sizeof(int)];
901     [self queueMessage:AdjustLinespaceMsgID data:data];
904 - (void)activate
906     [self queueMessage:ActivateMsgID data:nil];
909 - (void)setPreEditRow:(int)row column:(int)col
911     NSMutableData *data = [NSMutableData data];
912     [data appendBytes:&row length:sizeof(int)];
913     [data appendBytes:&col length:sizeof(int)];
914     [self queueMessage:SetPreEditPositionMsgID data:data];
917 - (int)lookupColorWithKey:(NSString *)key
919     if (!(key && [key length] > 0))
920         return INVALCOLOR;
922     NSString *stripKey = [[[[key lowercaseString]
923         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
924             componentsSeparatedByString:@" "]
925                componentsJoinedByString:@""];
927     if (stripKey && [stripKey length] > 0) {
928         // First of all try to lookup key in the color dictionary; note that
929         // all keys in this dictionary are lowercase with no whitespace.
930         id obj = [colorDict objectForKey:stripKey];
931         if (obj) return [obj intValue];
933         // The key was not in the dictionary; is it perhaps of the form
934         // #rrggbb?
935         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
936             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
937             [scanner setScanLocation:1];
938             unsigned hex = 0;
939             if ([scanner scanHexInt:&hex]) {
940                 return (int)hex;
941             }
942         }
944         // As a last resort, check if it is one of the system defined colors.
945         // The keys in this dictionary are also lowercase with no whitespace.
946         obj = [sysColorDict objectForKey:stripKey];
947         if (obj) {
948             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
949             if (col) {
950                 float r, g, b, a;
951                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
952                 [col getRed:&r green:&g blue:&b alpha:&a];
953                 return (((int)(r*255+.5f) & 0xff) << 16)
954                      + (((int)(g*255+.5f) & 0xff) << 8)
955                      +  ((int)(b*255+.5f) & 0xff);
956             }
957         }
958     }
960     //NSLog(@"WARNING: No color with key %@ found.", stripKey);
961     return INVALCOLOR;
964 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
966     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
967     id obj;
969     while ((obj = [e nextObject])) {
970         if ([value isEqual:obj])
971             return YES;
972     }
974     return NO;
977 - (void)enterFullscreen:(int)fuoptions background:(int)bg
979     NSMutableData *data = [NSMutableData data];
980     [data appendBytes:&fuoptions length:sizeof(int)];
981     bg = MM_COLOR(bg);
982     [data appendBytes:&bg length:sizeof(int)];
983     [self queueMessage:EnterFullscreenMsgID data:data];
986 - (void)leaveFullscreen
988     [self queueMessage:LeaveFullscreenMsgID data:nil];
991 - (void)setAntialias:(BOOL)antialias
993     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
995     [self queueMessage:msgid data:nil];
998 - (void)updateModifiedFlag
1000     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1001     // vice versa.
1002     int msgid = [self checkForModifiedBuffers]
1003             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1005     [self queueMessage:msgid data:nil];
1008 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1010     // NOTE: This method might get called whenever the run loop is tended to.
1011     // Normal keyboard and mouse input is added to input buffers, so there is
1012     // no risk in handling these events directly (they return immediately, and
1013     // do not call any other Vim functions).  However, other events such
1014     // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
1015     // events which would cause this method to be called recursively.  This
1016     // in turn leads to various difficulties that we do not want to have to
1017     // deal with.  To avoid recursive calls here we add all events except
1018     // keyboard and mouse events to an input queue which is processed whenever
1019     // gui_mch_update() is called (see processInputQueue).
1021     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1023     // Don't flush too soon after receiving input or update speed will suffer.
1024     [lastFlushDate release];
1025     lastFlushDate = [[NSDate date] retain];
1027     // Handle keyboard and mouse input now.  All other events are queued.
1028     if (InsertTextMsgID == msgid) {
1029         [self handleInsertText:data];
1030     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1031         if (!data) return;
1032         const void *bytes = [data bytes];
1033         int mods = *((int*)bytes);  bytes += sizeof(int);
1034         int len = *((int*)bytes);  bytes += sizeof(int);
1035         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1036                                               encoding:NSUTF8StringEncoding];
1037         mods = eventModifierFlagsToVimModMask(mods);
1039         [self handleKeyDown:key modifiers:mods];
1041         [key release];
1042     } else if (ScrollWheelMsgID == msgid) {
1043         if (!data) return;
1044         const void *bytes = [data bytes];
1046         int row = *((int*)bytes);  bytes += sizeof(int);
1047         int col = *((int*)bytes);  bytes += sizeof(int);
1048         int flags = *((int*)bytes);  bytes += sizeof(int);
1049         float dy = *((float*)bytes);  bytes += sizeof(float);
1051         int button = MOUSE_5;
1052         if (dy > 0) button = MOUSE_4;
1054         flags = eventModifierFlagsToVimMouseModMask(flags);
1056         int numLines = (int)round(dy);
1057         if (numLines < 0) numLines = -numLines;
1058         if (numLines == 0) numLines = 1;
1060 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1061         gui.scroll_wheel_force = numLines;
1062 #endif
1064         gui_send_mouse_event(button, col, row, NO, flags);
1065     } else if (MouseDownMsgID == msgid) {
1066         if (!data) return;
1067         const void *bytes = [data bytes];
1069         int row = *((int*)bytes);  bytes += sizeof(int);
1070         int col = *((int*)bytes);  bytes += sizeof(int);
1071         int button = *((int*)bytes);  bytes += sizeof(int);
1072         int flags = *((int*)bytes);  bytes += sizeof(int);
1073         int count = *((int*)bytes);  bytes += sizeof(int);
1075         button = eventButtonNumberToVimMouseButton(button);
1076         if (button >= 0) {
1077             flags = eventModifierFlagsToVimMouseModMask(flags);
1078             gui_send_mouse_event(button, col, row, count>1, flags);
1079         }
1080     } else if (MouseUpMsgID == msgid) {
1081         if (!data) return;
1082         const void *bytes = [data bytes];
1084         int row = *((int*)bytes);  bytes += sizeof(int);
1085         int col = *((int*)bytes);  bytes += sizeof(int);
1086         int flags = *((int*)bytes);  bytes += sizeof(int);
1088         flags = eventModifierFlagsToVimMouseModMask(flags);
1090         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1091     } else if (MouseDraggedMsgID == msgid) {
1092         if (!data) return;
1093         const void *bytes = [data bytes];
1095         int row = *((int*)bytes);  bytes += sizeof(int);
1096         int col = *((int*)bytes);  bytes += sizeof(int);
1097         int flags = *((int*)bytes);  bytes += sizeof(int);
1099         flags = eventModifierFlagsToVimMouseModMask(flags);
1101         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1102     } else if (MouseMovedMsgID == msgid) {
1103         const void *bytes = [data bytes];
1104         int row = *((int*)bytes);  bytes += sizeof(int);
1105         int col = *((int*)bytes);  bytes += sizeof(int);
1107         gui_mouse_moved(col, row);
1108     } else if (AddInputMsgID == msgid) {
1109         NSString *string = [[NSString alloc] initWithData:data
1110                 encoding:NSUTF8StringEncoding];
1111         if (string) {
1112             [self addInput:string];
1113             [string release];
1114         }
1115     } else if (TerminateNowMsgID == msgid) {
1116         isTerminating = YES;
1117     } else {
1118         // Not keyboard or mouse event, queue it and handle later.
1119         //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
1120         [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1121         [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
1122     }
1124     // See waitForInput: for an explanation of this flag.
1125     inputReceived = YES;
1128 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1130     // TODO: Get rid of this method?
1131     //NSLog(@"%s%@", _cmd, messages);
1133     unsigned i, count = [messages count];
1134     for (i = 0; i < count; i += 2) {
1135         int msgid = [[messages objectAtIndex:i] intValue];
1136         id data = [messages objectAtIndex:i+1];
1137         if ([data isEqual:[NSNull null]])
1138             data = nil;
1140         [self processInput:msgid data:data];
1141     }
1144 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1146     NSString *eval = nil;
1147     char_u *s = (char_u*)[expr UTF8String];
1149 #ifdef FEAT_MBYTE
1150     s = CONVERT_FROM_UTF8(s);
1151 #endif
1153     char_u *res = eval_client_expr_to_string(s);
1155 #ifdef FEAT_MBYTE
1156     CONVERT_FROM_UTF8_FREE(s);
1157 #endif
1159     if (res != NULL) {
1160         s = res;
1161 #ifdef FEAT_MBYTE
1162         s = CONVERT_TO_UTF8(s);
1163 #endif
1164         eval = [NSString stringWithUTF8String:(char*)s];
1165 #ifdef FEAT_MBYTE
1166         CONVERT_TO_UTF8_FREE(s);
1167 #endif
1168         vim_free(res);
1169     }
1171     return eval;
1174 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1176     // TODO: This method should share code with clip_mch_request_selection().
1178     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1179         // If there is no pasteboard, return YES to indicate that there is text
1180         // to copy.
1181         if (!pboard)
1182             return YES;
1184         clip_copy_selection();
1186         // Get the text to put on the pasteboard.
1187         long_u llen = 0; char_u *str = 0;
1188         int type = clip_convert_selection(&str, &llen, &clip_star);
1189         if (type < 0)
1190             return NO;
1191         
1192         // TODO: Avoid overflow.
1193         int len = (int)llen;
1194 #ifdef FEAT_MBYTE
1195         if (output_conv.vc_type != CONV_NONE) {
1196             char_u *conv_str = string_convert(&output_conv, str, &len);
1197             if (conv_str) {
1198                 vim_free(str);
1199                 str = conv_str;
1200             }
1201         }
1202 #endif
1204         NSString *string = [[NSString alloc]
1205             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1207         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1208         [pboard declareTypes:types owner:nil];
1209         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1210     
1211         [string release];
1212         vim_free(str);
1214         return ok;
1215     }
1217     return NO;
1220 - (oneway void)addReply:(in bycopy NSString *)reply
1221                  server:(in byref id <MMVimServerProtocol>)server
1223     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1225     // Replies might come at any time and in any order so we keep them in an
1226     // array inside a dictionary with the send port used as key.
1228     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1229     // HACK! Assume connection uses mach ports.
1230     int port = [(NSMachPort*)[conn sendPort] machPort];
1231     NSNumber *key = [NSNumber numberWithInt:port];
1233     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1234     if (!replies) {
1235         replies = [NSMutableArray array];
1236         [serverReplyDict setObject:replies forKey:key];
1237     }
1239     [replies addObject:reply];
1242 - (void)addInput:(in bycopy NSString *)input
1243                  client:(in byref id <MMVimClientProtocol>)client
1245     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1247     [self addInput:input];
1248     [self addClient:(id)client];
1250     inputReceived = YES;
1253 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1254                  client:(in byref id <MMVimClientProtocol>)client
1256     [self addClient:(id)client];
1257     return [self evaluateExpression:expr];
1260 - (void)registerServerWithName:(NSString *)name
1262     NSString *svrName = name;
1263     NSConnection *svrConn = [NSConnection defaultConnection];
1264     unsigned i;
1266     for (i = 0; i < MMServerMax; ++i) {
1267         NSString *connName = [self connectionNameFromServerName:svrName];
1269         if ([svrConn registerName:connName]) {
1270             //NSLog(@"Registered server with name: %@", svrName);
1272             // TODO: Set request/reply time-outs to something else?
1273             //
1274             // Don't wait for requests (time-out means that the message is
1275             // dropped).
1276             [svrConn setRequestTimeout:0];
1277             //[svrConn setReplyTimeout:MMReplyTimeout];
1278             [svrConn setRootObject:self];
1280             char_u *s = (char_u*)[svrName UTF8String];
1281 #ifdef FEAT_MBYTE
1282             s = CONVERT_FROM_UTF8(s);
1283 #endif
1284             // NOTE: 'serverName' is a global variable
1285             serverName = vim_strsave(s);
1286 #ifdef FEAT_MBYTE
1287             CONVERT_FROM_UTF8_FREE(s);
1288 #endif
1289 #ifdef FEAT_EVAL
1290             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1291 #endif
1292 #ifdef FEAT_TITLE
1293             need_maketitle = TRUE;
1294 #endif
1295             [self queueMessage:SetServerNameMsgID data:
1296                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1297             break;
1298         }
1300         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1301     }
1304 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1305                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1306               silent:(BOOL)silent
1308     // NOTE: If 'name' equals 'serverName' then the request is local (client
1309     // and server are the same).  This case is not handled separately, so a
1310     // connection will be set up anyway (this simplifies the code).
1312     NSConnection *conn = [self connectionForServerName:name];
1313     if (!conn) {
1314         if (!silent) {
1315             char_u *s = (char_u*)[name UTF8String];
1316 #ifdef FEAT_MBYTE
1317             s = CONVERT_FROM_UTF8(s);
1318 #endif
1319             EMSG2(_(e_noserver), s);
1320 #ifdef FEAT_MBYTE
1321             CONVERT_FROM_UTF8_FREE(s);
1322 #endif
1323         }
1324         return NO;
1325     }
1327     if (port) {
1328         // HACK! Assume connection uses mach ports.
1329         *port = [(NSMachPort*)[conn sendPort] machPort];
1330     }
1332     id proxy = [conn rootProxy];
1333     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1335     @try {
1336         if (expr) {
1337             NSString *eval = [proxy evaluateExpression:string client:self];
1338             if (reply) {
1339                 if (eval) {
1340                     char_u *r = (char_u*)[eval UTF8String];
1341 #ifdef FEAT_MBYTE
1342                     r = CONVERT_FROM_UTF8(r);
1343 #endif
1344                     *reply = vim_strsave(r);
1345 #ifdef FEAT_MBYTE
1346                     CONVERT_FROM_UTF8_FREE(r);
1347 #endif
1348                 } else {
1349                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1350                 }
1351             }
1353             if (!eval)
1354                 return NO;
1355         } else {
1356             [proxy addInput:string client:self];
1357         }
1358     }
1359     @catch (NSException *e) {
1360         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1361         return NO;
1362     }
1364     return YES;
1367 - (NSArray *)serverList
1369     NSArray *list = nil;
1371     if ([self connection]) {
1372         id proxy = [connection rootProxy];
1373         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1375         @try {
1376             list = [proxy serverList];
1377         }
1378         @catch (NSException *e) {
1379             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1380         }
1381     } else {
1382         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1383     }
1385     return list;
1388 - (NSString *)peekForReplyOnPort:(int)port
1390     //NSLog(@"%s%d", _cmd, port);
1392     NSNumber *key = [NSNumber numberWithInt:port];
1393     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1394     if (replies && [replies count]) {
1395         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1396         //        [replies objectAtIndex:0]);
1397         return [replies objectAtIndex:0];
1398     }
1400     //NSLog(@"    No replies");
1401     return nil;
1404 - (NSString *)waitForReplyOnPort:(int)port
1406     //NSLog(@"%s%d", _cmd, port);
1407     
1408     NSConnection *conn = [self connectionForServerPort:port];
1409     if (!conn)
1410         return nil;
1412     NSNumber *key = [NSNumber numberWithInt:port];
1413     NSMutableArray *replies = nil;
1414     NSString *reply = nil;
1416     // Wait for reply as long as the connection to the server is valid (unless
1417     // user interrupts wait with Ctrl-C).
1418     while (!got_int && [conn isValid] &&
1419             !(replies = [serverReplyDict objectForKey:key])) {
1420         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1421                                  beforeDate:[NSDate distantFuture]];
1422     }
1424     if (replies) {
1425         if ([replies count] > 0) {
1426             reply = [[replies objectAtIndex:0] retain];
1427             //NSLog(@"    Got reply: %@", reply);
1428             [replies removeObjectAtIndex:0];
1429             [reply autorelease];
1430         }
1432         if ([replies count] == 0)
1433             [serverReplyDict removeObjectForKey:key];
1434     }
1436     return reply;
1439 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1441     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1442     if (client) {
1443         @try {
1444             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1445             [client addReply:reply server:self];
1446             return YES;
1447         }
1448         @catch (NSException *e) {
1449             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1450         }
1451     } else {
1452         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1453     }
1455     return NO;
1458 @end // MMBackend
1462 @implementation MMBackend (Private)
1464 - (void)waitForDialogReturn
1466     // Keep processing the run loop until a dialog returns.  To avoid getting
1467     // stuck in an endless loop (could happen if the setDialogReturn: message
1468     // was lost) we also do some paranoia checks.
1469     //
1470     // Note that in Cocoa the user can still resize windows and select menu
1471     // items while a sheet is being displayed, so we can't just wait for the
1472     // first message to arrive and assume that is the setDialogReturn: call.
1474     while (nil == dialogReturn && !got_int && [connection isValid]
1475             && !isTerminating)
1476         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1477                                  beforeDate:[NSDate distantFuture]];
1479     // Search for any resize messages on the input queue.  All other messages
1480     // on the input queue are dropped.  The reason why we single out resize
1481     // messages is because the user may have resized the window while a sheet
1482     // was open.
1483     int i, count = [inputQueue count];
1484     if (count > 0) {
1485         id textDimData = nil;
1486         if (count%2 == 0) {
1487             for (i = count-2; i >= 0; i -= 2) {
1488                 int msgid = [[inputQueue objectAtIndex:i] intValue];
1489                 if (SetTextDimensionsMsgID == msgid) {
1490                     textDimData = [[inputQueue objectAtIndex:i+1] retain];
1491                     break;
1492                 }
1493             }
1494         }
1496         [inputQueue removeAllObjects];
1498         if (textDimData) {
1499             [inputQueue addObject:
1500                     [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1501             [inputQueue addObject:textDimData];
1502             [textDimData release];
1503         }
1504     }
1507 - (void)queueVimStateMessage
1509     // NOTE: This is the place to add Vim state that needs to be accessed from
1510     // MacVim.  Do not add state that could potentially require lots of memory
1511     // since this message gets sent each time the output queue is forcibly
1512     // flushed (e.g. storing the currently selected text would be a bad idea).
1513     // We take this approach of "pushing" the state to MacVim to avoid having
1514     // to make synchronous calls from MacVim to Vim in order to get state.
1516     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1517         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1518         [NSNumber numberWithInt:p_mh], @"p_mh",
1519         nil];
1521     [self queueMessage:SetVimStateMsgID data:[vimState dictionaryAsData]];
1524 - (void)processInputQueue
1526     if ([inputQueue count] == 0) return;
1528     // NOTE: One of the input events may cause this method to be called
1529     // recursively, so copy the input queue to a local variable and clear it
1530     // before starting to process input events (otherwise we could get stuck in
1531     // an endless loop).
1532     NSArray *q = [inputQueue copy];
1533     unsigned i, count = [q count];
1535     [inputQueue removeAllObjects];
1537     for (i = 0; i < count-1; i += 2) {
1538         int msgid = [[q objectAtIndex:i] intValue];
1539         id data = [q objectAtIndex:i+1];
1540         if ([data isEqual:[NSNull null]])
1541             data = nil;
1543         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1544         [self handleInputEvent:msgid data:data];
1545     }
1547     [q release];
1548     //NSLog(@"Clear input event queue");
1551 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1553     // NOTE: Be careful with what you do in this method.  Ideally, a message
1554     // should be handled by adding something to the input buffer and returning
1555     // immediately.  If you call a Vim function then it should not enter a loop
1556     // waiting for key presses or in any other way block the process.  The
1557     // reason for this being that only one message can be processed at a time,
1558     // so if another message is received while processing, then the new message
1559     // is dropped.  See also the comment in processInput:data:.
1561     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1563     if (SelectTabMsgID == msgid) {
1564         if (!data) return;
1565         const void *bytes = [data bytes];
1566         int idx = *((int*)bytes) + 1;
1567         //NSLog(@"Selecting tab %d", idx);
1568         send_tabline_event(idx);
1569     } else if (CloseTabMsgID == msgid) {
1570         if (!data) return;
1571         const void *bytes = [data bytes];
1572         int idx = *((int*)bytes) + 1;
1573         //NSLog(@"Closing tab %d", idx);
1574         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1575     } else if (AddNewTabMsgID == msgid) {
1576         //NSLog(@"Adding new tab");
1577         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1578     } else if (DraggedTabMsgID == msgid) {
1579         if (!data) return;
1580         const void *bytes = [data bytes];
1581         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1582         // based.
1583         int idx = *((int*)bytes);
1585         tabpage_move(idx);
1586     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) {
1587         if (!data) return;
1588         const void *bytes = [data bytes];
1589         int rows = *((int*)bytes);  bytes += sizeof(int);
1590         int cols = *((int*)bytes);  bytes += sizeof(int);
1592         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1593         // gui_resize_shell(), so we have to manually set the rows and columns
1594         // here.  (MacVim doesn't change the rows and columns to avoid
1595         // inconsistent states between Vim and MacVim.)
1596         [self queueMessage:msgid data:data];
1598         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1599         gui_resize_shell(cols, rows);
1600     } else if (ExecuteMenuMsgID == msgid) {
1601         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1602         if (attrs) {
1603             NSArray *desc = [attrs objectForKey:@"descriptor"];
1604             vimmenu_T *menu = menu_for_descriptor(desc);
1605             if (menu)
1606                 gui_menu_cb(menu);
1607         }
1608     } else if (ToggleToolbarMsgID == msgid) {
1609         [self handleToggleToolbar];
1610     } else if (ScrollbarEventMsgID == msgid) {
1611         [self handleScrollbarEvent:data];
1612     } else if (SetFontMsgID == msgid) {
1613         [self handleSetFont:data];
1614     } else if (VimShouldCloseMsgID == msgid) {
1615         gui_shell_closed();
1616     } else if (DropFilesMsgID == msgid) {
1617         [self handleDropFiles:data];
1618     } else if (DropStringMsgID == msgid) {
1619         [self handleDropString:data];
1620     } else if (GotFocusMsgID == msgid) {
1621         if (!gui.in_focus)
1622             [self focusChange:YES];
1623     } else if (LostFocusMsgID == msgid) {
1624         if (gui.in_focus)
1625             [self focusChange:NO];
1626     } else if (SetMouseShapeMsgID == msgid) {
1627         const void *bytes = [data bytes];
1628         int shape = *((int*)bytes);  bytes += sizeof(int);
1629         update_mouseshape(shape);
1630     } else if (ODBEditMsgID == msgid) {
1631         [self handleOdbEdit:data];
1632     } else if (XcodeModMsgID == msgid) {
1633         [self handleXcodeMod:data];
1634     } else {
1635         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1636     }
1639 + (NSDictionary *)specialKeys
1641     static NSDictionary *specialKeys = nil;
1643     if (!specialKeys) {
1644         NSBundle *mainBundle = [NSBundle mainBundle];
1645         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1646                                               ofType:@"plist"];
1647         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1648     }
1650     return specialKeys;
1653 - (void)handleInsertText:(NSData *)data
1655     if (!data) return;
1657     NSString *key = [[NSString alloc] initWithData:data
1658                                           encoding:NSUTF8StringEncoding];
1659     char_u *str = (char_u*)[key UTF8String];
1660     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1662 #ifdef FEAT_MBYTE
1663     char_u *conv_str = NULL;
1664     if (input_conv.vc_type != CONV_NONE) {
1665         conv_str = string_convert(&input_conv, str, &len);
1666         if (conv_str)
1667             str = conv_str;
1668     }
1669 #endif
1671     if (len == 1 && ((str[0] == Ctrl_C && ctrl_c_interrupts)
1672             || (str[0] == intr_char && intr_char != Ctrl_C))) {
1673         trash_input_buf();
1674         got_int = TRUE;
1675     }
1677     for (i = 0; i < len; ++i) {
1678         add_to_input_buf(str+i, 1);
1679         if (CSI == str[i]) {
1680             // NOTE: If the converted string contains the byte CSI, then it
1681             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1682             // won't work.
1683             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1684             add_to_input_buf(extra, 2);
1685         }
1686     }
1688 #ifdef FEAT_MBYTE
1689     if (conv_str)
1690         vim_free(conv_str);
1691 #endif
1692     [key release];
1695 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1697     char_u special[3];
1698     char_u modChars[3];
1699     char_u *chars = (char_u*)[key UTF8String];
1700 #ifdef FEAT_MBYTE
1701     char_u *conv_str = NULL;
1702 #endif
1703     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1705     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1706     // that new keys can easily be added.
1707     NSString *specialString = [[MMBackend specialKeys]
1708             objectForKey:key];
1709     if (specialString && [specialString length] > 1) {
1710         //NSLog(@"special key: %@", specialString);
1711         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1712                 [specialString characterAtIndex:1]);
1714         ikey = simplify_key(ikey, &mods);
1715         if (ikey == CSI)
1716             ikey = K_CSI;
1718         special[0] = CSI;
1719         special[1] = K_SECOND(ikey);
1720         special[2] = K_THIRD(ikey);
1722         chars = special;
1723         length = 3;
1724     } else if (1 == length && TAB == chars[0]) {
1725         // Tab is a trouble child:
1726         // - <Tab> is added to the input buffer as is
1727         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1728         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1729         //   to be converted to utf-8
1730         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1731         // - <C-Tab> is reserved by Mac OS X
1732         // - <D-Tab> is reserved by Mac OS X
1733         chars = special;
1734         special[0] = TAB;
1735         length = 1;
1737         if (mods & MOD_MASK_SHIFT) {
1738             mods &= ~MOD_MASK_SHIFT;
1739             special[0] = CSI;
1740             special[1] = K_SECOND(K_S_TAB);
1741             special[2] = K_THIRD(K_S_TAB);
1742             length = 3;
1743         } else if (mods & MOD_MASK_ALT) {
1744             int mtab = 0x80 | TAB;
1745 #ifdef FEAT_MBYTE
1746             if (enc_utf8) {
1747                 // Convert to utf-8
1748                 special[0] = (mtab >> 6) + 0xc0;
1749                 special[1] = mtab & 0xbf;
1750                 length = 2;
1751             } else
1752 #endif
1753             {
1754                 special[0] = mtab;
1755                 length = 1;
1756             }
1757             mods &= ~MOD_MASK_ALT;
1758         }
1759     } else if (length > 0) {
1760         unichar c = [key characterAtIndex:0];
1762         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1763         //        [key characterAtIndex:0], mods);
1765         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1766                 || (c == intr_char && intr_char != Ctrl_C))) {
1767             trash_input_buf();
1768             got_int = TRUE;
1769         }
1771         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1772         // cleared since they are already added to the key by the AppKit.
1773         // Unfortunately, the only way to deal with when to clear the modifiers
1774         // or not seems to be to have hard-wired rules like this.
1775         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1776                     || 0x9 == c || 0xd == c || ESC == c) ) {
1777             mods &= ~MOD_MASK_SHIFT;
1778             mods &= ~MOD_MASK_CTRL;
1779             //NSLog(@"clear shift ctrl");
1780         }
1782         // HACK!  All Option+key presses go via 'insert text' messages, except
1783         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1784         // not work to map to it.
1785         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1786             //NSLog(@"clear alt");
1787             mods &= ~MOD_MASK_ALT;
1788         }
1790 #ifdef FEAT_MBYTE
1791         if (input_conv.vc_type != CONV_NONE) {
1792             conv_str = string_convert(&input_conv, chars, &length);
1793             if (conv_str)
1794                 chars = conv_str;
1795         }
1796 #endif
1797     }
1799     if (chars && length > 0) {
1800         if (mods) {
1801             //NSLog(@"adding mods: %d", mods);
1802             modChars[0] = CSI;
1803             modChars[1] = KS_MODIFIER;
1804             modChars[2] = mods;
1805             add_to_input_buf(modChars, 3);
1806         }
1808         //NSLog(@"add to input buf: 0x%x", chars[0]);
1809         // TODO: Check for CSI bytes?
1810         add_to_input_buf(chars, length);
1811     }
1813 #ifdef FEAT_MBYTE
1814     if (conv_str)
1815         vim_free(conv_str);
1816 #endif
1819 - (void)queueMessage:(int)msgid data:(NSData *)data
1821     //if (msgid != EnableMenuItemMsgID)
1822     //    NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1824     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1825     if (data)
1826         [outputQueue addObject:data];
1827     else
1828         [outputQueue addObject:[NSData data]];
1831 - (void)connectionDidDie:(NSNotification *)notification
1833     // If the main connection to MacVim is lost this means that MacVim was
1834     // either quit (by the user chosing Quit on the MacVim menu), or it has
1835     // crashed.  In the former case the flag 'isTerminating' is set and we then
1836     // quit cleanly; in the latter case we make sure the swap files are left
1837     // for recovery.
1839     //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1840     if (isTerminating)
1841         getout(0);
1842     else
1843         getout_preserve_modified(1);
1846 - (void)blinkTimerFired:(NSTimer *)timer
1848     NSTimeInterval timeInterval = 0;
1850     [blinkTimer release];
1851     blinkTimer = nil;
1853     if (MMBlinkStateOn == blinkState) {
1854         gui_undraw_cursor();
1855         blinkState = MMBlinkStateOff;
1856         timeInterval = blinkOffInterval;
1857     } else if (MMBlinkStateOff == blinkState) {
1858         gui_update_cursor(TRUE, FALSE);
1859         blinkState = MMBlinkStateOn;
1860         timeInterval = blinkOnInterval;
1861     }
1863     if (timeInterval > 0) {
1864         blinkTimer = 
1865             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1866                                             selector:@selector(blinkTimerFired:)
1867                                             userInfo:nil repeats:NO] retain];
1868         [self flushQueue:YES];
1869     }
1872 - (void)focusChange:(BOOL)on
1874     gui_focus_change(on);
1877 - (void)handleToggleToolbar
1879     // If 'go' contains 'T', then remove it, else add it.
1881     char_u go[sizeof(GO_ALL)+2];
1882     char_u *p;
1883     int len;
1885     STRCPY(go, p_go);
1886     p = vim_strchr(go, GO_TOOLBAR);
1887     len = STRLEN(go);
1889     if (p != NULL) {
1890         char_u *end = go + len;
1891         while (p < end) {
1892             p[0] = p[1];
1893             ++p;
1894         }
1895     } else {
1896         go[len] = GO_TOOLBAR;
1897         go[len+1] = NUL;
1898     }
1900     set_option_value((char_u*)"guioptions", 0, go, 0);
1902     // Force screen redraw (does it have to be this complicated?).
1903     redraw_all_later(CLEAR);
1904     update_screen(NOT_VALID);
1905     setcursor();
1906     out_flush();
1907     gui_update_cursor(FALSE, FALSE);
1908     gui_mch_flush();
1911 - (void)handleScrollbarEvent:(NSData *)data
1913     if (!data) return;
1915     const void *bytes = [data bytes];
1916     long ident = *((long*)bytes);  bytes += sizeof(long);
1917     int hitPart = *((int*)bytes);  bytes += sizeof(int);
1918     float fval = *((float*)bytes);  bytes += sizeof(float);
1919     scrollbar_T *sb = gui_find_scrollbar(ident);
1921     if (sb) {
1922         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1923         long value = sb_info->value;
1924         long size = sb_info->size;
1925         long max = sb_info->max;
1926         BOOL isStillDragging = NO;
1927         BOOL updateKnob = YES;
1929         switch (hitPart) {
1930         case NSScrollerDecrementPage:
1931             value -= (size > 2 ? size - 2 : 1);
1932             break;
1933         case NSScrollerIncrementPage:
1934             value += (size > 2 ? size - 2 : 1);
1935             break;
1936         case NSScrollerDecrementLine:
1937             --value;
1938             break;
1939         case NSScrollerIncrementLine:
1940             ++value;
1941             break;
1942         case NSScrollerKnob:
1943             isStillDragging = YES;
1944             // fall through ...
1945         case NSScrollerKnobSlot:
1946             value = (long)(fval * (max - size + 1));
1947             // fall through ...
1948         default:
1949             updateKnob = NO;
1950             break;
1951         }
1953         //NSLog(@"value %d -> %d", sb_info->value, value);
1954         gui_drag_scrollbar(sb, value, isStillDragging);
1956         if (updateKnob) {
1957             // Dragging the knob or option+clicking automatically updates
1958             // the knob position (on the actual NSScroller), so we only
1959             // need to set the knob position in the other cases.
1960             if (sb->wp) {
1961                 // Update both the left&right vertical scrollbars.
1962                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1963                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1964                 [self setScrollbarThumbValue:value size:size max:max
1965                                   identifier:identLeft];
1966                 [self setScrollbarThumbValue:value size:size max:max
1967                                   identifier:identRight];
1968             } else {
1969                 // Update the horizontal scrollbar.
1970                 [self setScrollbarThumbValue:value size:size max:max
1971                                   identifier:ident];
1972             }
1973         }
1974     }
1977 - (void)handleSetFont:(NSData *)data
1979     if (!data) return;
1981     const void *bytes = [data bytes];
1982     float pointSize = *((float*)bytes);  bytes += sizeof(float);
1983     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1984     bytes += sizeof(unsigned);  // len not used
1986     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1987     [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1988     char_u *s = (char_u*)[name UTF8String];
1990 #ifdef FEAT_MBYTE
1991     s = CONVERT_FROM_UTF8(s);
1992 #endif
1994     set_option_value((char_u*)"guifont", 0, s, 0);
1996 #ifdef FEAT_MBYTE
1997     CONVERT_FROM_UTF8_FREE(s);
1998 #endif
2000     // Force screen redraw (does it have to be this complicated?).
2001     redraw_all_later(CLEAR);
2002     update_screen(NOT_VALID);
2003     setcursor();
2004     out_flush();
2005     gui_update_cursor(FALSE, FALSE);
2006     gui_mch_flush();
2009 - (void)handleDropFiles:(NSData *)data
2011     // TODO: Get rid of this method; instead use Vim script directly.  At the
2012     // moment I know how to do this to open files in tabs, but I'm not sure how
2013     // to add the filenames to the command line when in command line mode.
2015     if (!data) return;
2017 #ifdef FEAT_DND
2018     const void *bytes = [data bytes];
2019     const void *end = [data bytes] + [data length];
2020     BOOL forceOpen = *((BOOL*)bytes);  bytes += sizeof(BOOL);
2021     int n = *((int*)bytes);  bytes += sizeof(int);
2023     if (!forceOpen && (State & CMDLINE)) {
2024         // HACK!  If Vim is in command line mode then the files names
2025         // should be added to the command line, instead of opening the
2026         // files in tabs (unless forceOpen is set).  This is taken care of by
2027         // gui_handle_drop().
2028         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2029         if (fnames) {
2030             int i = 0;
2031             while (bytes < end && i < n) {
2032                 int len = *((int*)bytes);  bytes += sizeof(int);
2033                 char_u *s = (char_u*)bytes;
2034 #ifdef FEAT_MBYTE
2035                 s = CONVERT_FROM_UTF8(s);
2036 #endif
2037                 fnames[i++] = vim_strsave(s);
2038 #ifdef FEAT_MBYTE
2039                 CONVERT_FROM_UTF8_FREE(s);
2040 #endif
2041                 bytes += len;
2042             }
2044             // NOTE!  This function will free 'fnames'.
2045             // HACK!  It is assumed that the 'x' and 'y' arguments are
2046             // unused when in command line mode.
2047             gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
2048         }
2049     } else {
2050         // HACK!  I'm not sure how to get Vim to open a list of files in
2051         // tabs, so instead I create a ':tab drop' command with all the
2052         // files to open and execute it.
2053         NSMutableString *cmd = [NSMutableString stringWithString:@":tab drop"];
2055         int i;
2056         for (i = 0; i < n && bytes < end; ++i) {
2057             int len = *((int*)bytes);  bytes += sizeof(int);
2058             NSString *file = [NSString stringWithUTF8String:bytes];
2059             file = [file stringByEscapingSpecialFilenameCharacters];
2060             bytes += len;
2062             [cmd appendString:@" "];
2063             [cmd appendString:file];
2064         }
2066         // By going to the last tabpage we ensure that the new tabs will
2067         // appear last (if this call is left out, the taborder becomes
2068         // messy).
2069         goto_tabpage(9999);
2071         char_u *s = (char_u*)[cmd UTF8String];
2072 #ifdef FEAT_MBYTE
2073         s = CONVERT_FROM_UTF8(s);
2074 #endif
2075         do_cmdline_cmd(s);
2076 #ifdef FEAT_MBYTE
2077         CONVERT_FROM_UTF8_FREE(s);
2078 #endif
2080         // Force screen redraw (does it have to be this complicated?).
2081         // (This code was taken from the end of gui_handle_drop().)
2082         update_screen(NOT_VALID);
2083         setcursor();
2084         out_flush();
2085         gui_update_cursor(FALSE, FALSE);
2086         maketitle();
2087         gui_mch_flush();
2088     }
2089 #endif // FEAT_DND
2092 - (void)handleDropString:(NSData *)data
2094     if (!data) return;
2096 #ifdef FEAT_DND
2097     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2098     const void *bytes = [data bytes];
2099     int len = *((int*)bytes);  bytes += sizeof(int);
2100     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2102     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2103     NSRange range = { 0, [string length] };
2104     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2105                                          withString:@"\x0a" options:0
2106                                               range:range];
2107     if (0 == n) {
2108         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2109                                        options:0 range:range];
2110     }
2112     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2113     char_u *s = (char_u*)[string UTF8String];
2114 #ifdef FEAT_MBYTE
2115     if (input_conv.vc_type != CONV_NONE)
2116         s = string_convert(&input_conv, s, &len);
2117 #endif
2118     dnd_yank_drag_data(s, len);
2119 #ifdef FEAT_MBYTE
2120     if (input_conv.vc_type != CONV_NONE)
2121         vim_free(s);
2122 #endif
2123     add_to_input_buf(dropkey, sizeof(dropkey));
2124 #endif // FEAT_DND
2127 - (void)handleOdbEdit:(NSData *)data
2129 #ifdef FEAT_ODB_EDITOR
2130     const void *bytes = [data bytes];
2132     OSType serverID = *((OSType*)bytes);  bytes += sizeof(OSType);
2134     char_u *path = NULL;
2135     int pathLen = *((int*)bytes);  bytes += sizeof(int);
2136     if (pathLen > 0) {
2137         path = (char_u*)bytes;
2138         bytes += pathLen;
2139 #ifdef FEAT_MBYTE
2140         path = CONVERT_FROM_UTF8(path);
2141 #endif
2142     }
2144     NSAppleEventDescriptor *token = nil;
2145     DescType tokenType = *((DescType*)bytes);  bytes += sizeof(DescType);
2146     int descLen = *((int*)bytes);  bytes += sizeof(int);
2147     if (descLen > 0) {
2148         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2149                                                                bytes:bytes
2150                                                               length:descLen];
2151         bytes += descLen;
2152     }
2154     unsigned i, numFiles = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2155     for (i = 0; i < numFiles; ++i) {
2156         int len = *((int*)bytes);  bytes += sizeof(int);
2157         char_u *filename = (char_u*)bytes;
2158 #ifdef FEAT_MBYTE
2159         filename = CONVERT_FROM_UTF8(filename);
2160 #endif
2161         buf_T *buf = buflist_findname(filename);
2162         if (buf) {
2163             if (buf->b_odb_token) {
2164                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2165                 buf->b_odb_token = NULL;
2166             }
2168             if (buf->b_odb_fname) {
2169                 vim_free(buf->b_odb_fname);
2170                 buf->b_odb_fname = NULL;
2171             }
2173             buf->b_odb_server_id = serverID;
2175             if (token)
2176                 buf->b_odb_token = [token retain];
2177             if (path)
2178                 buf->b_odb_fname = vim_strsave(path);
2179         } else {
2180             NSLog(@"WARNING: Could not find buffer '%s' for ODB editing.",
2181                     filename);
2182         }
2184 #ifdef FEAT_MBYTE
2185         CONVERT_FROM_UTF8_FREE(filename);
2186 #endif
2187         bytes += len;
2188     }
2189 #ifdef FEAT_MBYTE
2190     CONVERT_FROM_UTF8_FREE(path);
2191 #endif
2192 #endif // FEAT_ODB_EDITOR
2195 - (void)handleXcodeMod:(NSData *)data
2197 #if 0
2198     const void *bytes = [data bytes];
2199     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2200     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2201     if (0 == len)
2202         return;
2204     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2205             descriptorWithDescriptorType:type
2206                                    bytes:bytes
2207                                   length:len];
2208 #endif
2211 - (BOOL)checkForModifiedBuffers
2213     buf_T *buf;
2214     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2215         if (bufIsChanged(buf)) {
2216             return YES;
2217         }
2218     }
2220     return NO;
2223 - (void)addInput:(NSString *)input
2225     char_u *s = (char_u*)[input UTF8String];
2227 #ifdef FEAT_MBYTE
2228     s = CONVERT_FROM_UTF8(s);
2229 #endif
2231     server_to_input_buf(s);
2233 #ifdef FEAT_MBYTE
2234     CONVERT_FROM_UTF8_FREE(s);
2235 #endif
2238 @end // MMBackend (Private)
2243 @implementation MMBackend (ClientServer)
2245 - (NSString *)connectionNameFromServerName:(NSString *)name
2247     NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2249     return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2250         lowercaseString];
2253 - (NSConnection *)connectionForServerName:(NSString *)name
2255     // TODO: Try 'name%d' if 'name' fails.
2256     NSString *connName = [self connectionNameFromServerName:name];
2257     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2259     if (!svrConn) {
2260         svrConn = [NSConnection connectionWithRegisteredName:connName
2261                                                            host:nil];
2262         // Try alternate server...
2263         if (!svrConn && alternateServerName) {
2264             //NSLog(@"  trying to connect to alternate server: %@",
2265             //        alternateServerName);
2266             connName = [self connectionNameFromServerName:alternateServerName];
2267             svrConn = [NSConnection connectionWithRegisteredName:connName
2268                                                             host:nil];
2269         }
2271         // Try looking for alternate servers...
2272         if (!svrConn) {
2273             //NSLog(@"  looking for alternate servers...");
2274             NSString *alt = [self alternateServerNameForName:name];
2275             if (alt != alternateServerName) {
2276                 //NSLog(@"  found alternate server: %@", string);
2277                 [alternateServerName release];
2278                 alternateServerName = [alt copy];
2279             }
2280         }
2282         // Try alternate server again...
2283         if (!svrConn && alternateServerName) {
2284             //NSLog(@"  trying to connect to alternate server: %@",
2285             //        alternateServerName);
2286             connName = [self connectionNameFromServerName:alternateServerName];
2287             svrConn = [NSConnection connectionWithRegisteredName:connName
2288                                                             host:nil];
2289         }
2291         if (svrConn) {
2292             [connectionNameDict setObject:svrConn forKey:connName];
2294             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2295             [[NSNotificationCenter defaultCenter] addObserver:self
2296                     selector:@selector(serverConnectionDidDie:)
2297                         name:NSConnectionDidDieNotification object:svrConn];
2298         }
2299     }
2301     return svrConn;
2304 - (NSConnection *)connectionForServerPort:(int)port
2306     NSConnection *conn;
2307     NSEnumerator *e = [connectionNameDict objectEnumerator];
2309     while ((conn = [e nextObject])) {
2310         // HACK! Assume connection uses mach ports.
2311         if (port == [(NSMachPort*)[conn sendPort] machPort])
2312             return conn;
2313     }
2315     return nil;
2318 - (void)serverConnectionDidDie:(NSNotification *)notification
2320     //NSLog(@"%s%@", _cmd, notification);
2322     NSConnection *svrConn = [notification object];
2324     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2325     [[NSNotificationCenter defaultCenter]
2326             removeObserver:self
2327                       name:NSConnectionDidDieNotification
2328                     object:svrConn];
2330     [connectionNameDict removeObjectsForKeys:
2331         [connectionNameDict allKeysForObject:svrConn]];
2333     // HACK! Assume connection uses mach ports.
2334     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2335     NSNumber *key = [NSNumber numberWithInt:port];
2337     [clientProxyDict removeObjectForKey:key];
2338     [serverReplyDict removeObjectForKey:key];
2341 - (void)addClient:(NSDistantObject *)client
2343     NSConnection *conn = [client connectionForProxy];
2344     // HACK! Assume connection uses mach ports.
2345     int port = [(NSMachPort*)[conn sendPort] machPort];
2346     NSNumber *key = [NSNumber numberWithInt:port];
2348     if (![clientProxyDict objectForKey:key]) {
2349         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2350         [clientProxyDict setObject:client forKey:key];
2351     }
2353     // NOTE: 'clientWindow' is a global variable which is used by <client>
2354     clientWindow = port;
2357 - (NSString *)alternateServerNameForName:(NSString *)name
2359     if (!(name && [name length] > 0))
2360         return nil;
2362     // Only look for alternates if 'name' doesn't end in a digit.
2363     unichar lastChar = [name characterAtIndex:[name length]-1];
2364     if (lastChar >= '0' && lastChar <= '9')
2365         return nil;
2367     // Look for alternates among all current servers.
2368     NSArray *list = [self serverList];
2369     if (!(list && [list count] > 0))
2370         return nil;
2372     // Filter out servers starting with 'name' and ending with a number. The
2373     // (?i) pattern ensures that the match is case insensitive.
2374     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2375     NSPredicate *pred = [NSPredicate predicateWithFormat:
2376             @"SELF MATCHES %@", pat];
2377     list = [list filteredArrayUsingPredicate:pred];
2378     if ([list count] > 0) {
2379         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2380         return [list objectAtIndex:0];
2381     }
2383     return nil;
2386 @end // MMBackend (ClientServer)
2391 @implementation NSString (MMServerNameCompare)
2392 - (NSComparisonResult)serverNameCompare:(NSString *)string
2394     return [self compare:string
2395                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2397 @end
2402 static int eventModifierFlagsToVimModMask(int modifierFlags)
2404     int modMask = 0;
2406     if (modifierFlags & NSShiftKeyMask)
2407         modMask |= MOD_MASK_SHIFT;
2408     if (modifierFlags & NSControlKeyMask)
2409         modMask |= MOD_MASK_CTRL;
2410     if (modifierFlags & NSAlternateKeyMask)
2411         modMask |= MOD_MASK_ALT;
2412     if (modifierFlags & NSCommandKeyMask)
2413         modMask |= MOD_MASK_CMD;
2415     return modMask;
2418 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2420     int modMask = 0;
2422     if (modifierFlags & NSShiftKeyMask)
2423         modMask |= MOUSE_SHIFT;
2424     if (modifierFlags & NSControlKeyMask)
2425         modMask |= MOUSE_CTRL;
2426     if (modifierFlags & NSAlternateKeyMask)
2427         modMask |= MOUSE_ALT;
2429     return modMask;
2432 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2434     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2436     return (buttonNumber >= 0 && buttonNumber < 3)
2437             ? mouseButton[buttonNumber] : -1;