File->Close sends performClose:
[MacVim.git] / src / MacVim / MMBackend.m
blob598272ab943ce907a23a6074a1ed8f3e18f4324f
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 vimModMaskToEventModifierFlags(int mods);
55 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
56 static int eventButtonNumberToVimMouseButton(int buttonNumber);
57 static int specialKeyToNSKey(int key);
59 enum {
60     MMBlinkStateNone = 0,
61     MMBlinkStateOn,
62     MMBlinkStateOff
65 static NSString *MMSymlinkWarningString =
66     @"\n\n\tMost likely this is because you have symlinked directly to\n"
67      "\tthe Vim binary, which Cocoa does not allow.  Please use an\n"
68      "\talias or the mvim shell script instead.  If you have not used\n"
69      "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
73 @interface NSString (MMServerNameCompare)
74 - (NSComparisonResult)serverNameCompare:(NSString *)string;
75 @end
79 @interface MMBackend (Private)
80 - (void)processInputQueue;
81 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
82 + (NSDictionary *)specialKeys;
83 - (void)handleInsertText:(NSData *)data;
84 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
85 - (void)queueMessage:(int)msgid data:(NSData *)data;
86 - (void)connectionDidDie:(NSNotification *)notification;
87 - (void)blinkTimerFired:(NSTimer *)timer;
88 - (void)focusChange:(BOOL)on;
89 - (void)handleToggleToolbar;
90 - (void)handleScrollbarEvent:(NSData *)data;
91 - (void)handleSetFont:(NSData *)data;
92 - (void)handleDropFiles:(NSData *)data;
93 - (void)handleDropString:(NSData *)data;
94 - (void)handleOdbEdit:(NSData *)data;
95 - (void)handleXcodeMod:(NSData *)data;
96 - (BOOL)checkForModifiedBuffers;
97 - (void)addInput:(NSString *)input;
98 @end
102 @interface MMBackend (ClientServer)
103 - (NSString *)connectionNameFromServerName:(NSString *)name;
104 - (NSConnection *)connectionForServerName:(NSString *)name;
105 - (NSConnection *)connectionForServerPort:(int)port;
106 - (void)serverConnectionDidDie:(NSNotification *)notification;
107 - (void)addClient:(NSDistantObject *)client;
108 - (NSString *)alternateServerNameForName:(NSString *)name;
109 @end
113 @implementation MMBackend
115 + (MMBackend *)sharedInstance
117     static MMBackend *singleton = nil;
118     return singleton ? singleton : (singleton = [MMBackend new]);
121 - (id)init
123     self = [super init];
124     if (!self) return nil;
126     fontContainerRef = loadFonts();
128     outputQueue = [[NSMutableArray alloc] init];
129     inputQueue = [[NSMutableArray alloc] init];
130     drawData = [[NSMutableData alloc] initWithCapacity:1024];
131     connectionNameDict = [[NSMutableDictionary alloc] init];
132     clientProxyDict = [[NSMutableDictionary alloc] init];
133     serverReplyDict = [[NSMutableDictionary alloc] init];
135     NSBundle *mainBundle = [NSBundle mainBundle];
136     NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
137     if (path)
138         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
140     path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
141     if (path)
142         sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
143             retain];
145     if (!(colorDict && sysColorDict))
146         NSLog(@"ERROR: Failed to load color dictionaries.%@",
147                 MMSymlinkWarningString);
149     return self;
152 - (void)dealloc
154     //NSLog(@"%@ %s", [self className], _cmd);
155     [[NSNotificationCenter defaultCenter] removeObserver:self];
157     [oldWideFont release];  oldWideFont = nil;
158     [blinkTimer release];  blinkTimer = nil;
159     [alternateServerName release];  alternateServerName = nil;
160     [serverReplyDict release];  serverReplyDict = nil;
161     [clientProxyDict release];  clientProxyDict = nil;
162     [connectionNameDict release];  connectionNameDict = nil;
163     [inputQueue release];  inputQueue = nil;
164     [outputQueue release];  outputQueue = nil;
165     [drawData release];  drawData = nil;
166     [frontendProxy release];  frontendProxy = nil;
167     [connection release];  connection = nil;
168     [sysColorDict release];  sysColorDict = nil;
169     [colorDict release];  colorDict = nil;
171     [super dealloc];
174 - (void)setBackgroundColor:(int)color
176     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
179 - (void)setForegroundColor:(int)color
181     foregroundColor = MM_COLOR(color);
184 - (void)setSpecialColor:(int)color
186     specialColor = MM_COLOR(color);
189 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
191     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
192     defaultForegroundColor = MM_COLOR(fg);
194     NSMutableData *data = [NSMutableData data];
196     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
197     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
199     [self queueMessage:SetDefaultColorsMsgID data:data];
202 - (NSConnection *)connection
204     if (!connection) {
205         // NOTE!  If the name of the connection changes here it must also be
206         // updated in MMAppController.m.
207         NSString *name = [NSString stringWithFormat:@"%@-connection",
208                [[NSBundle mainBundle] bundleIdentifier]];
210         connection = [NSConnection connectionWithRegisteredName:name host:nil];
211         [connection retain];
212     }
214     // NOTE: 'connection' may be nil here.
215     return connection;
218 - (BOOL)checkin
220     if (![self connection]) {
221         NSBundle *mainBundle = [NSBundle mainBundle];
222 #if 0
223         OSStatus status;
224         FSRef ref;
226         // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
227         // the API to pass Apple Event parameters is broken on 10.4).
228         NSString *path = [mainBundle bundlePath];
229         status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
230         if (noErr == status) {
231             // Pass parameter to the 'Open' Apple Event that tells MacVim not
232             // to open an untitled window.
233             NSAppleEventDescriptor *desc =
234                     [NSAppleEventDescriptor recordDescriptor];
235             [desc setParamDescriptor:
236                     [NSAppleEventDescriptor descriptorWithBoolean:NO]
237                           forKeyword:keyMMUntitledWindow];
239             LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
240                     kLSLaunchDefaults, NULL };
241             status = LSOpenFromRefSpec(&spec, NULL);
242         }
244         if (noErr != status) {
245         NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
246                 path, MMSymlinkWarningString);
247             return NO;
248         }
249 #else
250         // Launch MacVim using NSTask.  For some reason the above code using
251         // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
252         // fails, the dock icon starts bouncing and never stops).  It seems
253         // like rebuilding the Launch Services database takes care of this
254         // problem, but the NSTask way seems more stable so stick with it.
255         //
256         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
257         // that the GUI won't be activated (or raised) so there is a hack in
258         // MMAppController which raises the app when a new window is opened.
259         NSMutableArray *args = [NSMutableArray arrayWithObjects:
260             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
261         NSString *exeName = [[mainBundle infoDictionary]
262                 objectForKey:@"CFBundleExecutable"];
263         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
264         if (!path) {
265             NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
266                     MMSymlinkWarningString);
267             return NO;
268         }
270         [NSTask launchedTaskWithLaunchPath:path arguments:args];
271 #endif
273         // HACK!  Poll the mach bootstrap server until it returns a valid
274         // connection to detect that MacVim has finished launching.  Also set a
275         // time-out date so that we don't get stuck doing this forever.
276         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
277         while (![self connection] &&
278                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
279             [[NSRunLoop currentRunLoop]
280                     runMode:NSDefaultRunLoopMode
281                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
283         // NOTE: [self connection] will set 'connection' as a side-effect.
284         if (!connection) {
285             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
286             return NO;
287         }
288     }
290     id proxy = [connection rootProxy];
291     [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
293     [[NSNotificationCenter defaultCenter] addObserver:self
294             selector:@selector(connectionDidDie:)
295                 name:NSConnectionDidDieNotification object:connection];
297     int pid = [[NSProcessInfo processInfo] processIdentifier];
299     @try {
300         frontendProxy = [proxy connectBackend:self pid:pid];
301     }
302     @catch (NSException *e) {
303         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
304     }
306     if (frontendProxy) {
307         [frontendProxy retain];
308         [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
309     }
311     return connection && frontendProxy;
314 - (BOOL)openVimWindow
316     [self queueMessage:OpenVimWindowMsgID data:nil];
317     return YES;
320 - (void)clearAll
322     int type = ClearAllDrawType;
324     // Any draw commands in queue are effectively obsolete since this clearAll
325     // will negate any effect they have, therefore we may as well clear the
326     // draw queue.
327     [drawData setLength:0];
329     [drawData appendBytes:&type length:sizeof(int)];
332 - (void)clearBlockFromRow:(int)row1 column:(int)col1
333                     toRow:(int)row2 column:(int)col2
335     int type = ClearBlockDrawType;
337     [drawData appendBytes:&type length:sizeof(int)];
339     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
340     [drawData appendBytes:&row1 length:sizeof(int)];
341     [drawData appendBytes:&col1 length:sizeof(int)];
342     [drawData appendBytes:&row2 length:sizeof(int)];
343     [drawData appendBytes:&col2 length:sizeof(int)];
346 - (void)deleteLinesFromRow:(int)row count:(int)count
347               scrollBottom:(int)bottom left:(int)left right:(int)right
349     int type = DeleteLinesDrawType;
351     [drawData appendBytes:&type length:sizeof(int)];
353     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
354     [drawData appendBytes:&row length:sizeof(int)];
355     [drawData appendBytes:&count length:sizeof(int)];
356     [drawData appendBytes:&bottom length:sizeof(int)];
357     [drawData appendBytes:&left length:sizeof(int)];
358     [drawData appendBytes:&right length:sizeof(int)];
361 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
362              cells:(int)cells flags:(int)flags
364     if (len <= 0 || cells <= 0) return;
366     int type = DrawStringDrawType;
368     [drawData appendBytes:&type length:sizeof(int)];
370     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
371     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
372     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
373     [drawData appendBytes:&row length:sizeof(int)];
374     [drawData appendBytes:&col length:sizeof(int)];
375     [drawData appendBytes:&cells length:sizeof(int)];
376     [drawData appendBytes:&flags length:sizeof(int)];
377     [drawData appendBytes:&len length:sizeof(int)];
378     [drawData appendBytes:s length:len];
381 - (void)insertLinesFromRow:(int)row count:(int)count
382               scrollBottom:(int)bottom left:(int)left right:(int)right
384     int type = InsertLinesDrawType;
386     [drawData appendBytes:&type length:sizeof(int)];
388     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
389     [drawData appendBytes:&row length:sizeof(int)];
390     [drawData appendBytes:&count length:sizeof(int)];
391     [drawData appendBytes:&bottom length:sizeof(int)];
392     [drawData appendBytes:&left length:sizeof(int)];
393     [drawData appendBytes:&right length:sizeof(int)];
396 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
397                fraction:(int)percent color:(int)color
399     int type = DrawCursorDrawType;
400     unsigned uc = MM_COLOR(color);
402     [drawData appendBytes:&type length:sizeof(int)];
404     [drawData appendBytes:&uc length:sizeof(unsigned)];
405     [drawData appendBytes:&row length:sizeof(int)];
406     [drawData appendBytes:&col length:sizeof(int)];
407     [drawData appendBytes:&shape length:sizeof(int)];
408     [drawData appendBytes:&percent length:sizeof(int)];
411 - (void)update
413     // Tend to the run loop, returning immediately if there are no events
414     // waiting.
415     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
416                              beforeDate:[NSDate distantPast]];
418 #if 0
419     // Keyboard and mouse input is handled directly, other input is queued and
420     // processed here.  This call may enter a blocking loop.
421     if ([inputQueue count] > 0)
422         [self processInputQueue];
423 #endif
426 - (void)flushQueue:(BOOL)force
428     // NOTE! This method gets called a lot; if we were to flush every time it
429     // got called MacVim would feel unresponsive.  So there is a time out which
430     // ensures that the queue isn't flushed too often.
431     if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
432             < MMFlushTimeoutInterval
433             && [drawData length] < MMFlushQueueLenHint)
434         return;
436     if ([drawData length] > 0) {
437         // HACK!  Detect changes to 'guifontwide'.
438         if (gui.wide_font != (GuiFont)oldWideFont) {
439             [oldWideFont release];
440             oldWideFont = [(NSFont*)gui.wide_font retain];
441             [self setWideFont:oldWideFont];
442         }
444         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
445         [drawData setLength:0];
446     }
448     if ([outputQueue count] > 0) {
449         @try {
450             [frontendProxy processCommandQueue:outputQueue];
451         }
452         @catch (NSException *e) {
453             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
454         }
456         [outputQueue removeAllObjects];
458         [lastFlushDate release];
459         lastFlushDate = [[NSDate date] retain];
460     }
463 - (BOOL)waitForInput:(int)milliseconds
465     //NSLog(@"|ENTER| %s%d",  _cmd, milliseconds);
467     // Only start the run loop if the input queue is empty, otherwise process
468     // the input first so that the input on queue isn't delayed.
469     if ([inputQueue count]) {
470         inputReceived = YES;
471     } else {
472         NSDate *date = milliseconds > 0 ?
473                 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
474                 [NSDate distantFuture];
476         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
477                                  beforeDate:date];
478     }
480     // I know of no way to figure out if the run loop exited because input was
481     // found or because of a time out, so I need to manually indicate when
482     // input was received in processInput:data: and then reset it every time
483     // here.
484     BOOL yn = inputReceived;
485     inputReceived = NO;
487     // Keyboard and mouse input is handled directly, other input is queued and
488     // processed here.  This call may enter a blocking loop.
489     if ([inputQueue count] > 0)
490         [self processInputQueue];
492     //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
493     return yn;
496 - (void)exit
498 #ifdef MAC_CLIENTSERVER
499     // The default connection is used for the client/server code.
500     [[NSConnection defaultConnection] setRootObject:nil];
501     [[NSConnection defaultConnection] invalidate];
502 #endif
504     // By invalidating the NSConnection the MMWindowController immediately
505     // finds out that the connection is down and as a result
506     // [MMWindowController connectionDidDie:] is invoked.
507     //NSLog(@"%@ %s", [self className], _cmd);
508     [[NSNotificationCenter defaultCenter] removeObserver:self];
509     [connection invalidate];
511     if (fontContainerRef) {
512         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
513         fontContainerRef = 0;
514     }
518 - (void)selectTab:(int)index
520     //NSLog(@"%s%d", _cmd, index);
522     index -= 1;
523     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
524     [self queueMessage:SelectTabMsgID data:data];
527 - (void)updateTabBar
529     //NSLog(@"%s", _cmd);
531     NSMutableData *data = [NSMutableData data];
533     int idx = tabpage_index(curtab) - 1;
534     [data appendBytes:&idx length:sizeof(int)];
536     tabpage_T *tp;
537     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
538         // This function puts the label of the tab in the global 'NameBuff'.
539         get_tabline_label(tp, FALSE);
540         char_u *s = NameBuff;
541         int len = STRLEN(s);
542         if (len <= 0) continue;
544 #ifdef FEAT_MBYTE
545         s = CONVERT_TO_UTF8(s);
546 #endif
548         // Count the number of windows in the tabpage.
549         //win_T *wp = tp->tp_firstwin;
550         //int wincount;
551         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
553         //[data appendBytes:&wincount length:sizeof(int)];
554         [data appendBytes:&len length:sizeof(int)];
555         [data appendBytes:s length:len];
557 #ifdef FEAT_MBYTE
558         CONVERT_TO_UTF8_FREE(s);
559 #endif
560     }
562     [self queueMessage:UpdateTabBarMsgID data:data];
565 - (BOOL)tabBarVisible
567     return tabBarVisible;
570 - (void)showTabBar:(BOOL)enable
572     tabBarVisible = enable;
574     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
575     [self queueMessage:msgid data:nil];
578 - (void)setRows:(int)rows columns:(int)cols
580     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
582     int dim[] = { rows, cols };
583     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
585     [self queueMessage:SetTextDimensionsMsgID data:data];
588 - (void)setWindowTitle:(char *)title
590     NSMutableData *data = [NSMutableData data];
591     int len = strlen(title);
592     if (len <= 0) return;
594     [data appendBytes:&len length:sizeof(int)];
595     [data appendBytes:title length:len];
597     [self queueMessage:SetWindowTitleMsgID data:data];
600 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
601                             saving:(int)saving
603     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
604     //        saving);
606     char_u *s = NULL;
607     NSString *ds = dir ? [NSString stringWithUTF8String:dir] : nil;
608     NSString *ts = title ? [NSString stringWithUTF8String:title] : nil;
609     @try {
610         [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
612         // Wait until a reply is sent from MMVimController.
613         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
614                                  beforeDate:[NSDate distantFuture]];
616         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
617             char_u *ret = (char_u*)[dialogReturn UTF8String];
618 #ifdef FEAT_MBYTE
619             ret = CONVERT_FROM_UTF8(ret);
620 #endif
621             s = vim_strsave(ret);
622 #ifdef FEAT_MBYTE
623             CONVERT_FROM_UTF8_FREE(ret);
624 #endif
625         }
627         [dialogReturn release];  dialogReturn = nil;
628     }
629     @catch (NSException *e) {
630         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
631     }
633     return (char *)s;
636 - (oneway void)setDialogReturn:(in bycopy id)obj
638     // NOTE: This is called by
639     //   - [MMVimController panelDidEnd:::], and
640     //   - [MMVimController alertDidEnd:::],
641     // to indicate that a save/open panel or alert has finished.
643     if (obj != dialogReturn) {
644         [dialogReturn release];
645         dialogReturn = [obj retain];
646     }
649 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
650                      buttons:(char *)btns textField:(char *)txtfield
652     int retval = 0;
653     NSString *message = nil, *text = nil, *textFieldString = nil;
654     NSArray *buttons = nil;
655     int style = NSInformationalAlertStyle;
657     if (VIM_WARNING == type) style = NSWarningAlertStyle;
658     else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
660     if (btns) {
661         NSString *btnString = [NSString stringWithUTF8String:btns];
662         buttons = [btnString componentsSeparatedByString:@"\n"];
663     }
664     if (title)
665         message = [NSString stringWithUTF8String:title];
666     if (msg) {
667         text = [NSString stringWithUTF8String:msg];
668         if (!message) {
669             // HACK! If there is a '\n\n' or '\n' sequence in the message, then
670             // make the part up to there into the title.  We only do this
671             // because Vim has lots of dialogs without a title and they look
672             // ugly that way.
673             // TODO: Fix the actual dialog texts.
674             NSRange eolRange = [text rangeOfString:@"\n\n"];
675             if (NSNotFound == eolRange.location)
676                 eolRange = [text rangeOfString:@"\n"];
677             if (NSNotFound != eolRange.location) {
678                 message = [text substringToIndex:eolRange.location];
679                 text = [text substringFromIndex:NSMaxRange(eolRange)];
680             }
681         }
682     }
683     if (txtfield)
684         textFieldString = [NSString stringWithUTF8String:txtfield];
686     @try {
687         [frontendProxy presentDialogWithStyle:style message:message
688                               informativeText:text buttonTitles:buttons
689                               textFieldString:textFieldString];
691         // Wait until a reply is sent from MMVimController.
692         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
693                                  beforeDate:[NSDate distantFuture]];
695         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
696                 && [dialogReturn count]) {
697             retval = [[dialogReturn objectAtIndex:0] intValue];
698             if (txtfield && [dialogReturn count] > 1) {
699                 NSString *retString = [dialogReturn objectAtIndex:1];
700                 char_u *ret = (char_u*)[retString UTF8String];
701 #ifdef FEAT_MBYTE
702                 ret = CONVERT_FROM_UTF8(ret);
703 #endif
704                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
705 #ifdef FEAT_MBYTE
706                 CONVERT_FROM_UTF8_FREE(ret);
707 #endif
708             }
709         }
711         [dialogReturn release]; dialogReturn = nil;
712     }
713     @catch (NSException *e) {
714         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
715     }
717     return retval;
720 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
721                atIndex:(int)index
723     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
724     //        name, index);
726     int namelen = name ? strlen(name) : 0;
727     NSMutableData *data = [NSMutableData data];
729     [data appendBytes:&tag length:sizeof(int)];
730     [data appendBytes:&parentTag length:sizeof(int)];
731     [data appendBytes:&namelen length:sizeof(int)];
732     if (namelen > 0) [data appendBytes:name length:namelen];
733     [data appendBytes:&index length:sizeof(int)];
735     [self queueMessage:AddMenuMsgID data:data];
738 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
739                        tip:(char *)tip icon:(char *)icon
740              keyEquivalent:(int)key modifiers:(int)mods
741                     action:(NSString *)action atIndex:(int)index
743     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
744     //        parentTag, name, tip, index);
746     int namelen = name ? strlen(name) : 0;
747     int tiplen = tip ? strlen(tip) : 0;
748     int iconlen = icon ? strlen(icon) : 0;
749     int eventFlags = vimModMaskToEventModifierFlags(mods);
750     int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
751     NSMutableData *data = [NSMutableData data];
753     key = specialKeyToNSKey(key);
755     [data appendBytes:&tag length:sizeof(int)];
756     [data appendBytes:&parentTag length:sizeof(int)];
757     [data appendBytes:&namelen length:sizeof(int)];
758     if (namelen > 0) [data appendBytes:name length:namelen];
759     [data appendBytes:&tiplen length:sizeof(int)];
760     if (tiplen > 0) [data appendBytes:tip length:tiplen];
761     [data appendBytes:&iconlen length:sizeof(int)];
762     if (iconlen > 0) [data appendBytes:icon length:iconlen];
763     [data appendBytes:&actionlen length:sizeof(int)];
764     if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
765     [data appendBytes:&index length:sizeof(int)];
766     [data appendBytes:&key length:sizeof(int)];
767     [data appendBytes:&eventFlags length:sizeof(int)];
769     [self queueMessage:AddMenuItemMsgID data:data];
772 - (void)removeMenuItemWithTag:(int)tag
774     NSMutableData *data = [NSMutableData data];
775     [data appendBytes:&tag length:sizeof(int)];
777     [self queueMessage:RemoveMenuItemMsgID data:data];
780 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
782     NSMutableData *data = [NSMutableData data];
784     [data appendBytes:&tag length:sizeof(int)];
785     [data appendBytes:&enabled length:sizeof(int)];
787     [self queueMessage:EnableMenuItemMsgID data:data];
790 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
792     int len = strlen(name);
793     int row = -1, col = -1;
795     if (len <= 0) return;
797     if (!mouse && curwin) {
798         row = curwin->w_wrow;
799         col = curwin->w_wcol;
800     }
802     NSMutableData *data = [NSMutableData data];
804     [data appendBytes:&row length:sizeof(int)];
805     [data appendBytes:&col length:sizeof(int)];
806     [data appendBytes:&len length:sizeof(int)];
807     [data appendBytes:name length:len];
809     [self queueMessage:ShowPopupMenuMsgID data:data];
812 - (void)showToolbar:(int)enable flags:(int)flags
814     NSMutableData *data = [NSMutableData data];
816     [data appendBytes:&enable length:sizeof(int)];
817     [data appendBytes:&flags length:sizeof(int)];
819     [self queueMessage:ShowToolbarMsgID data:data];
822 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
824     NSMutableData *data = [NSMutableData data];
826     [data appendBytes:&ident length:sizeof(long)];
827     [data appendBytes:&type length:sizeof(int)];
829     [self queueMessage:CreateScrollbarMsgID data:data];
832 - (void)destroyScrollbarWithIdentifier:(long)ident
834     NSMutableData *data = [NSMutableData data];
835     [data appendBytes:&ident length:sizeof(long)];
837     [self queueMessage:DestroyScrollbarMsgID data:data];
840 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
842     NSMutableData *data = [NSMutableData data];
844     [data appendBytes:&ident length:sizeof(long)];
845     [data appendBytes:&visible length:sizeof(int)];
847     [self queueMessage:ShowScrollbarMsgID data:data];
850 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
852     NSMutableData *data = [NSMutableData data];
854     [data appendBytes:&ident length:sizeof(long)];
855     [data appendBytes:&pos length:sizeof(int)];
856     [data appendBytes:&len length:sizeof(int)];
858     [self queueMessage:SetScrollbarPositionMsgID data:data];
861 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
862                     identifier:(long)ident
864     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
865     float prop = (float)size/(max+1);
866     if (fval < 0) fval = 0;
867     else if (fval > 1.0f) fval = 1.0f;
868     if (prop < 0) prop = 0;
869     else if (prop > 1.0f) prop = 1.0f;
871     NSMutableData *data = [NSMutableData data];
873     [data appendBytes:&ident length:sizeof(long)];
874     [data appendBytes:&fval length:sizeof(float)];
875     [data appendBytes:&prop length:sizeof(float)];
877     [self queueMessage:SetScrollbarThumbMsgID data:data];
880 - (void)setFont:(NSFont *)font
882     NSString *fontName = [font displayName];
883     float size = [font pointSize];
884     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
885     if (len > 0) {
886         NSMutableData *data = [NSMutableData data];
888         [data appendBytes:&size length:sizeof(float)];
889         [data appendBytes:&len length:sizeof(int)];
890         [data appendBytes:[fontName UTF8String] length:len];
892         [self queueMessage:SetFontMsgID data:data];
893     }
896 - (void)setWideFont:(NSFont *)font
898     NSString *fontName = [font displayName];
899     float size = [font pointSize];
900     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
901     NSMutableData *data = [NSMutableData data];
903     [data appendBytes:&size length:sizeof(float)];
904     [data appendBytes:&len length:sizeof(int)];
905     if (len > 0)
906         [data appendBytes:[fontName UTF8String] length:len];
908     [self queueMessage:SetWideFontMsgID data:data];
911 - (void)executeActionWithName:(NSString *)name
913     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
915     if (len > 0) {
916         NSMutableData *data = [NSMutableData data];
918         [data appendBytes:&len length:sizeof(int)];
919         [data appendBytes:[name UTF8String] length:len];
921         [self queueMessage:ExecuteActionMsgID data:data];
922     }
925 - (void)setMouseShape:(int)shape
927     NSMutableData *data = [NSMutableData data];
928     [data appendBytes:&shape length:sizeof(int)];
929     [self queueMessage:SetMouseShapeMsgID data:data];
932 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
934     // Vim specifies times in milliseconds, whereas Cocoa wants them in
935     // seconds.
936     blinkWaitInterval = .001f*wait;
937     blinkOnInterval = .001f*on;
938     blinkOffInterval = .001f*off;
941 - (void)startBlink
943     if (blinkTimer) {
944         [blinkTimer invalidate];
945         [blinkTimer release];
946         blinkTimer = nil;
947     }
949     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
950             && gui.in_focus) {
951         blinkState = MMBlinkStateOn;
952         blinkTimer =
953             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
954                                               target:self
955                                             selector:@selector(blinkTimerFired:)
956                                             userInfo:nil repeats:NO] retain];
957         gui_update_cursor(TRUE, FALSE);
958         [self flushQueue:YES];
959     }
962 - (void)stopBlink
964     if (MMBlinkStateOff == blinkState) {
965         gui_update_cursor(TRUE, FALSE);
966         [self flushQueue:YES];
967     }
969     blinkState = MMBlinkStateNone;
972 - (void)adjustLinespace:(int)linespace
974     NSMutableData *data = [NSMutableData data];
975     [data appendBytes:&linespace length:sizeof(int)];
976     [self queueMessage:AdjustLinespaceMsgID data:data];
979 - (void)activate
981     [self queueMessage:ActivateMsgID data:nil];
984 - (void)setPreEditRow:(int)row column:(int)col
986     NSMutableData *data = [NSMutableData data];
987     [data appendBytes:&row length:sizeof(int)];
988     [data appendBytes:&col length:sizeof(int)];
989     [self queueMessage:SetPreEditPositionMsgID data:data];
992 - (int)lookupColorWithKey:(NSString *)key
994     if (!(key && [key length] > 0))
995         return INVALCOLOR;
997     NSString *stripKey = [[[[key lowercaseString]
998         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
999             componentsSeparatedByString:@" "]
1000                componentsJoinedByString:@""];
1002     if (stripKey && [stripKey length] > 0) {
1003         // First of all try to lookup key in the color dictionary; note that
1004         // all keys in this dictionary are lowercase with no whitespace.
1005         id obj = [colorDict objectForKey:stripKey];
1006         if (obj) return [obj intValue];
1008         // The key was not in the dictionary; is it perhaps of the form
1009         // #rrggbb?
1010         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
1011             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
1012             [scanner setScanLocation:1];
1013             unsigned hex = 0;
1014             if ([scanner scanHexInt:&hex]) {
1015                 return (int)hex;
1016             }
1017         }
1019         // As a last resort, check if it is one of the system defined colors.
1020         // The keys in this dictionary are also lowercase with no whitespace.
1021         obj = [sysColorDict objectForKey:stripKey];
1022         if (obj) {
1023             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
1024             if (col) {
1025                 float r, g, b, a;
1026                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
1027                 [col getRed:&r green:&g blue:&b alpha:&a];
1028                 return (((int)(r*255+.5f) & 0xff) << 16)
1029                      + (((int)(g*255+.5f) & 0xff) << 8)
1030                      +  ((int)(b*255+.5f) & 0xff);
1031             }
1032         }
1033     }
1035     //NSLog(@"WARNING: No color with key %@ found.", stripKey);
1036     return INVALCOLOR;
1039 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
1041     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1042     id obj;
1044     while ((obj = [e nextObject])) {
1045         if ([value isEqual:obj])
1046             return YES;
1047     }
1049     return NO;
1052 - (void)enterFullscreen
1054     [self queueMessage:EnterFullscreenMsgID data:nil];
1057 - (void)leaveFullscreen
1059     [self queueMessage:LeaveFullscreenMsgID data:nil];
1062 - (void)updateModifiedFlag
1064     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1065     // vice versa.
1066     int msgid = [self checkForModifiedBuffers]
1067             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1069     [self queueMessage:msgid data:nil];
1072 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1074     // NOTE: This method might get called whenever the run loop is tended to.
1075     // Normal keyboard and mouse input is added to input buffers, so there is
1076     // no risk in handling these events directly (they return immediately, and
1077     // do not call any other Vim functions).  However, other events such
1078     // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
1079     // events which would cause this method to be called recursively.  This
1080     // in turn leads to various difficulties that we do not want to have to
1081     // deal with.  To avoid recursive calls here we add all events except
1082     // keyboard and mouse events to an input queue which is processed whenever
1083     // gui_mch_update() is called (see processInputQueue).
1085     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1087     // Don't flush too soon after receiving input or update speed will suffer.
1088     [lastFlushDate release];
1089     lastFlushDate = [[NSDate date] retain];
1091     // Handle keyboard and mouse input now.  All other events are queued.
1092     if (InsertTextMsgID == msgid) {
1093         [self handleInsertText:data];
1094     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1095         if (!data) return;
1096         const void *bytes = [data bytes];
1097         int mods = *((int*)bytes);  bytes += sizeof(int);
1098         int len = *((int*)bytes);  bytes += sizeof(int);
1099         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1100                                               encoding:NSUTF8StringEncoding];
1101         mods = eventModifierFlagsToVimModMask(mods);
1103         [self handleKeyDown:key modifiers:mods];
1105         [key release];
1106     } else if (ScrollWheelMsgID == msgid) {
1107         if (!data) return;
1108         const void *bytes = [data bytes];
1110         int row = *((int*)bytes);  bytes += sizeof(int);
1111         int col = *((int*)bytes);  bytes += sizeof(int);
1112         int flags = *((int*)bytes);  bytes += sizeof(int);
1113         float dy = *((float*)bytes);  bytes += sizeof(float);
1115         int button = MOUSE_5;
1116         if (dy > 0) button = MOUSE_4;
1118         flags = eventModifierFlagsToVimMouseModMask(flags);
1120         gui_send_mouse_event(button, col, row, NO, flags);
1121     } else if (MouseDownMsgID == msgid) {
1122         if (!data) return;
1123         const void *bytes = [data bytes];
1125         int row = *((int*)bytes);  bytes += sizeof(int);
1126         int col = *((int*)bytes);  bytes += sizeof(int);
1127         int button = *((int*)bytes);  bytes += sizeof(int);
1128         int flags = *((int*)bytes);  bytes += sizeof(int);
1129         int count = *((int*)bytes);  bytes += sizeof(int);
1131         button = eventButtonNumberToVimMouseButton(button);
1132         if (button >= 0) {
1133             flags = eventModifierFlagsToVimMouseModMask(flags);
1134             gui_send_mouse_event(button, col, row, count>1, flags);
1135         }
1136     } else if (MouseUpMsgID == msgid) {
1137         if (!data) return;
1138         const void *bytes = [data bytes];
1140         int row = *((int*)bytes);  bytes += sizeof(int);
1141         int col = *((int*)bytes);  bytes += sizeof(int);
1142         int flags = *((int*)bytes);  bytes += sizeof(int);
1144         flags = eventModifierFlagsToVimMouseModMask(flags);
1146         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1147     } else if (MouseDraggedMsgID == msgid) {
1148         if (!data) return;
1149         const void *bytes = [data bytes];
1151         int row = *((int*)bytes);  bytes += sizeof(int);
1152         int col = *((int*)bytes);  bytes += sizeof(int);
1153         int flags = *((int*)bytes);  bytes += sizeof(int);
1155         flags = eventModifierFlagsToVimMouseModMask(flags);
1157         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1158     } else if (MouseMovedMsgID == msgid) {
1159         const void *bytes = [data bytes];
1160         int row = *((int*)bytes);  bytes += sizeof(int);
1161         int col = *((int*)bytes);  bytes += sizeof(int);
1163         gui_mouse_moved(col, row);
1164     } else if (AddInputMsgID == msgid) {
1165         NSString *string = [[NSString alloc] initWithData:data
1166                 encoding:NSUTF8StringEncoding];
1167         if (string) {
1168             [self addInput:string];
1169             [string release];
1170         }
1171     } else if (TerminateNowMsgID == msgid) {
1172         isTerminating = YES;
1173     } else {
1174         // Not keyboard or mouse event, queue it and handle later.
1175         //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
1176         [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1177         [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
1178     }
1180     // See waitForInput: for an explanation of this flag.
1181     inputReceived = YES;
1184 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1186     // TODO: Get rid of this method?
1187     //NSLog(@"%s%@", _cmd, messages);
1189     unsigned i, count = [messages count];
1190     for (i = 0; i < count; i += 2) {
1191         int msgid = [[messages objectAtIndex:i] intValue];
1192         id data = [messages objectAtIndex:i+1];
1193         if ([data isEqual:[NSNull null]])
1194             data = nil;
1196         [self processInput:msgid data:data];
1197     }
1200 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1202     NSString *eval = nil;
1203     char_u *s = (char_u*)[expr UTF8String];
1205 #ifdef FEAT_MBYTE
1206     s = CONVERT_FROM_UTF8(s);
1207 #endif
1209     char_u *res = eval_client_expr_to_string(s);
1211 #ifdef FEAT_MBYTE
1212     CONVERT_FROM_UTF8_FREE(s);
1213 #endif
1215     if (res != NULL) {
1216         s = res;
1217 #ifdef FEAT_MBYTE
1218         s = CONVERT_TO_UTF8(s);
1219 #endif
1220         eval = [NSString stringWithUTF8String:(char*)s];
1221 #ifdef FEAT_MBYTE
1222         CONVERT_TO_UTF8_FREE(s);
1223 #endif
1224         vim_free(res);
1225     }
1227     return eval;
1230 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1232     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1233         // If there is no pasteboard, return YES to indicate that there is text
1234         // to copy.
1235         if (!pboard)
1236             return YES;
1238         clip_copy_selection();
1240         // Get the text to put on the pasteboard.
1241         long_u llen = 0; char_u *str = 0;
1242         int type = clip_convert_selection(&str, &llen, &clip_star);
1243         if (type < 0)
1244             return NO;
1245         
1246         // TODO: Avoid overflow.
1247         int len = (int)llen;
1248 #ifdef FEAT_MBYTE
1249         if (output_conv.vc_type != CONV_NONE) {
1250             char_u *conv_str = string_convert(&output_conv, str, &len);
1251             if (conv_str) {
1252                 vim_free(str);
1253                 str = conv_str;
1254             }
1255         }
1256 #endif
1258         NSString *string = [[NSString alloc]
1259             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1261         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1262         [pboard declareTypes:types owner:nil];
1263         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1264     
1265         [string release];
1266         vim_free(str);
1268         return ok;
1269     }
1271     return NO;
1274 - (oneway void)addReply:(in bycopy NSString *)reply
1275                  server:(in byref id <MMVimServerProtocol>)server
1277     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1279     // Replies might come at any time and in any order so we keep them in an
1280     // array inside a dictionary with the send port used as key.
1282     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1283     // HACK! Assume connection uses mach ports.
1284     int port = [(NSMachPort*)[conn sendPort] machPort];
1285     NSNumber *key = [NSNumber numberWithInt:port];
1287     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1288     if (!replies) {
1289         replies = [NSMutableArray array];
1290         [serverReplyDict setObject:replies forKey:key];
1291     }
1293     [replies addObject:reply];
1296 - (void)addInput:(in bycopy NSString *)input
1297                  client:(in byref id <MMVimClientProtocol>)client
1299     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1301     [self addInput:input];
1302     [self addClient:(id)client];
1304     inputReceived = YES;
1307 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1308                  client:(in byref id <MMVimClientProtocol>)client
1310     [self addClient:(id)client];
1311     return [self evaluateExpression:expr];
1314 - (void)registerServerWithName:(NSString *)name
1316     NSString *svrName = name;
1317     NSConnection *svrConn = [NSConnection defaultConnection];
1318     unsigned i;
1320     for (i = 0; i < MMServerMax; ++i) {
1321         NSString *connName = [self connectionNameFromServerName:svrName];
1323         if ([svrConn registerName:connName]) {
1324             //NSLog(@"Registered server with name: %@", svrName);
1326             // TODO: Set request/reply time-outs to something else?
1327             //
1328             // Don't wait for requests (time-out means that the message is
1329             // dropped).
1330             [svrConn setRequestTimeout:0];
1331             //[svrConn setReplyTimeout:MMReplyTimeout];
1332             [svrConn setRootObject:self];
1334             char_u *s = (char_u*)[svrName UTF8String];
1335 #ifdef FEAT_MBYTE
1336             s = CONVERT_FROM_UTF8(s);
1337 #endif
1338             // NOTE: 'serverName' is a global variable
1339             serverName = vim_strsave(s);
1340 #ifdef FEAT_MBYTE
1341             CONVERT_FROM_UTF8_FREE(s);
1342 #endif
1343 #ifdef FEAT_EVAL
1344             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1345 #endif
1346 #ifdef FEAT_TITLE
1347             need_maketitle = TRUE;
1348 #endif
1349             [self queueMessage:SetServerNameMsgID data:
1350                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1351             break;
1352         }
1354         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1355     }
1358 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1359                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1360               silent:(BOOL)silent
1362     // NOTE: If 'name' equals 'serverName' then the request is local (client
1363     // and server are the same).  This case is not handled separately, so a
1364     // connection will be set up anyway (this simplifies the code).
1366     NSConnection *conn = [self connectionForServerName:name];
1367     if (!conn) {
1368         if (!silent) {
1369             char_u *s = (char_u*)[name UTF8String];
1370 #ifdef FEAT_MBYTE
1371             s = CONVERT_FROM_UTF8(s);
1372 #endif
1373             EMSG2(_(e_noserver), s);
1374 #ifdef FEAT_MBYTE
1375             CONVERT_FROM_UTF8_FREE(s);
1376 #endif
1377         }
1378         return NO;
1379     }
1381     if (port) {
1382         // HACK! Assume connection uses mach ports.
1383         *port = [(NSMachPort*)[conn sendPort] machPort];
1384     }
1386     id proxy = [conn rootProxy];
1387     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1389     @try {
1390         if (expr) {
1391             NSString *eval = [proxy evaluateExpression:string client:self];
1392             if (reply) {
1393                 if (eval) {
1394                     char_u *r = (char_u*)[eval UTF8String];
1395 #ifdef FEAT_MBYTE
1396                     r = CONVERT_FROM_UTF8(r);
1397 #endif
1398                     *reply = vim_strsave(r);
1399 #ifdef FEAT_MBYTE
1400                     CONVERT_FROM_UTF8_FREE(r);
1401 #endif
1402                 } else {
1403                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1404                 }
1405             }
1407             if (!eval)
1408                 return NO;
1409         } else {
1410             [proxy addInput:string client:self];
1411         }
1412     }
1413     @catch (NSException *e) {
1414         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1415         return NO;
1416     }
1418     return YES;
1421 - (NSArray *)serverList
1423     NSArray *list = nil;
1425     if ([self connection]) {
1426         id proxy = [connection rootProxy];
1427         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1429         @try {
1430             list = [proxy serverList];
1431         }
1432         @catch (NSException *e) {
1433             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1434         }
1435     } else {
1436         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1437     }
1439     return list;
1442 - (NSString *)peekForReplyOnPort:(int)port
1444     //NSLog(@"%s%d", _cmd, port);
1446     NSNumber *key = [NSNumber numberWithInt:port];
1447     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1448     if (replies && [replies count]) {
1449         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1450         //        [replies objectAtIndex:0]);
1451         return [replies objectAtIndex:0];
1452     }
1454     //NSLog(@"    No replies");
1455     return nil;
1458 - (NSString *)waitForReplyOnPort:(int)port
1460     //NSLog(@"%s%d", _cmd, port);
1461     
1462     NSConnection *conn = [self connectionForServerPort:port];
1463     if (!conn)
1464         return nil;
1466     NSNumber *key = [NSNumber numberWithInt:port];
1467     NSMutableArray *replies = nil;
1468     NSString *reply = nil;
1470     // Wait for reply as long as the connection to the server is valid (unless
1471     // user interrupts wait with Ctrl-C).
1472     while (!got_int && [conn isValid] &&
1473             !(replies = [serverReplyDict objectForKey:key])) {
1474         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1475                                  beforeDate:[NSDate distantFuture]];
1476     }
1478     if (replies) {
1479         if ([replies count] > 0) {
1480             reply = [[replies objectAtIndex:0] retain];
1481             //NSLog(@"    Got reply: %@", reply);
1482             [replies removeObjectAtIndex:0];
1483             [reply autorelease];
1484         }
1486         if ([replies count] == 0)
1487             [serverReplyDict removeObjectForKey:key];
1488     }
1490     return reply;
1493 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1495     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1496     if (client) {
1497         @try {
1498             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1499             [client addReply:reply server:self];
1500             return YES;
1501         }
1502         @catch (NSException *e) {
1503             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1504         }
1505     } else {
1506         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1507     }
1509     return NO;
1512 @end // MMBackend
1516 @implementation MMBackend (Private)
1518 - (void)processInputQueue
1520     // NOTE: One of the input events may cause this method to be called
1521     // recursively, so copy the input queue to a local variable and clear it
1522     // before starting to process input events (otherwise we could get stuck in
1523     // an endless loop).
1524     NSArray *q = [inputQueue copy];
1525     unsigned i, count = [q count];
1527     [inputQueue removeAllObjects];
1529     for (i = 0; i < count-1; i += 2) {
1530         int msgid = [[q objectAtIndex:i] intValue];
1531         id data = [q objectAtIndex:i+1];
1532         if ([data isEqual:[NSNull null]])
1533             data = nil;
1535         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1536         [self handleInputEvent:msgid data:data];
1537     }
1539     [q release];
1540     //NSLog(@"Clear input event queue");
1543 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1545     // NOTE: Be careful with what you do in this method.  Ideally, a message
1546     // should be handled by adding something to the input buffer and returning
1547     // immediately.  If you call a Vim function then it should not enter a loop
1548     // waiting for key presses or in any other way block the process.  The
1549     // reason for this being that only one message can be processed at a time,
1550     // so if another message is received while processing, then the new message
1551     // is dropped.  See also the comment in processInput:data:.
1553     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1555     if (SelectTabMsgID == msgid) {
1556         if (!data) return;
1557         const void *bytes = [data bytes];
1558         int idx = *((int*)bytes) + 1;
1559         //NSLog(@"Selecting tab %d", idx);
1560         send_tabline_event(idx);
1561     } else if (CloseTabMsgID == msgid) {
1562         if (!data) return;
1563         const void *bytes = [data bytes];
1564         int idx = *((int*)bytes) + 1;
1565         //NSLog(@"Closing tab %d", idx);
1566         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1567     } else if (AddNewTabMsgID == msgid) {
1568         //NSLog(@"Adding new tab");
1569         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1570     } else if (DraggedTabMsgID == msgid) {
1571         if (!data) return;
1572         const void *bytes = [data bytes];
1573         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1574         // based.
1575         int idx = *((int*)bytes);
1577         tabpage_move(idx);
1578     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) {
1579         if (!data) return;
1580         const void *bytes = [data bytes];
1581         int rows = *((int*)bytes);  bytes += sizeof(int);
1582         int cols = *((int*)bytes);  bytes += sizeof(int);
1584         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1585         // gui_resize_shell(), so we have to manually set the rows and columns
1586         // here.  (MacVim doesn't change the rows and columns to avoid
1587         // inconsistent states between Vim and MacVim.)
1588         [self queueMessage:msgid data:data];
1590         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1591         gui_resize_shell(cols, rows);
1592     } else if (ExecuteMenuMsgID == msgid) {
1593         if (!data) return;
1594         const void *bytes = [data bytes];
1595         int tag = *((int*)bytes);  bytes += sizeof(int);
1597         vimmenu_T *menu = (vimmenu_T*)tag;
1598         // TODO!  Make sure 'menu' is a valid menu pointer!
1599         if (menu) {
1600             gui_menu_cb(menu);
1601         }
1602     } else if (ToggleToolbarMsgID == msgid) {
1603         [self handleToggleToolbar];
1604     } else if (ScrollbarEventMsgID == msgid) {
1605         [self handleScrollbarEvent:data];
1606     } else if (SetFontMsgID == msgid) {
1607         [self handleSetFont:data];
1608     } else if (VimShouldCloseMsgID == msgid) {
1609         gui_shell_closed();
1610     } else if (DropFilesMsgID == msgid) {
1611         [self handleDropFiles:data];
1612     } else if (DropStringMsgID == msgid) {
1613         [self handleDropString:data];
1614     } else if (GotFocusMsgID == msgid) {
1615         if (!gui.in_focus)
1616             [self focusChange:YES];
1617     } else if (LostFocusMsgID == msgid) {
1618         if (gui.in_focus)
1619             [self focusChange:NO];
1620     } else if (SetMouseShapeMsgID == msgid) {
1621         const void *bytes = [data bytes];
1622         int shape = *((int*)bytes);  bytes += sizeof(int);
1623         update_mouseshape(shape);
1624     } else if (ODBEditMsgID == msgid) {
1625         [self handleOdbEdit:data];
1626     } else if (XcodeModMsgID == msgid) {
1627         [self handleXcodeMod:data];
1628     } else {
1629         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1630     }
1633 + (NSDictionary *)specialKeys
1635     static NSDictionary *specialKeys = nil;
1637     if (!specialKeys) {
1638         NSBundle *mainBundle = [NSBundle mainBundle];
1639         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1640                                               ofType:@"plist"];
1641         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1642     }
1644     return specialKeys;
1647 - (void)handleInsertText:(NSData *)data
1649     if (!data) return;
1651     NSString *key = [[NSString alloc] initWithData:data
1652                                           encoding:NSUTF8StringEncoding];
1653     char_u *str = (char_u*)[key UTF8String];
1654     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1656 #ifdef FEAT_MBYTE
1657     char_u *conv_str = NULL;
1658     if (input_conv.vc_type != CONV_NONE) {
1659         conv_str = string_convert(&input_conv, str, &len);
1660         if (conv_str)
1661             str = conv_str;
1662     }
1663 #endif
1665     for (i = 0; i < len; ++i) {
1666         add_to_input_buf(str+i, 1);
1667         if (CSI == str[i]) {
1668             // NOTE: If the converted string contains the byte CSI, then it
1669             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1670             // won't work.
1671             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1672             add_to_input_buf(extra, 2);
1673         }
1674     }
1676 #ifdef FEAT_MBYTE
1677     if (conv_str)
1678         vim_free(conv_str);
1679 #endif
1680     [key release];
1683 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1685     char_u special[3];
1686     char_u modChars[3];
1687     char_u *chars = (char_u*)[key UTF8String];
1688 #ifdef FEAT_MBYTE
1689     char_u *conv_str = NULL;
1690 #endif
1691     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1693     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1694     // that new keys can easily be added.
1695     NSString *specialString = [[MMBackend specialKeys]
1696             objectForKey:key];
1697     if (specialString && [specialString length] > 1) {
1698         //NSLog(@"special key: %@", specialString);
1699         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1700                 [specialString characterAtIndex:1]);
1702         ikey = simplify_key(ikey, &mods);
1703         if (ikey == CSI)
1704             ikey = K_CSI;
1706         special[0] = CSI;
1707         special[1] = K_SECOND(ikey);
1708         special[2] = K_THIRD(ikey);
1710         chars = special;
1711         length = 3;
1712     } else if (1 == length && TAB == chars[0]) {
1713         // Tab is a trouble child:
1714         // - <Tab> is added to the input buffer as is
1715         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1716         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1717         //   to be converted to utf-8
1718         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1719         // - <C-Tab> is reserved by Mac OS X
1720         // - <D-Tab> is reserved by Mac OS X
1721         chars = special;
1722         special[0] = TAB;
1723         length = 1;
1725         if (mods & MOD_MASK_SHIFT) {
1726             mods &= ~MOD_MASK_SHIFT;
1727             special[0] = CSI;
1728             special[1] = K_SECOND(K_S_TAB);
1729             special[2] = K_THIRD(K_S_TAB);
1730             length = 3;
1731         } else if (mods & MOD_MASK_ALT) {
1732             int mtab = 0x80 | TAB;
1733 #ifdef FEAT_MBYTE
1734             if (enc_utf8) {
1735                 // Convert to utf-8
1736                 special[0] = (mtab >> 6) + 0xc0;
1737                 special[1] = mtab & 0xbf;
1738                 length = 2;
1739             } else
1740 #endif
1741             {
1742                 special[0] = mtab;
1743                 length = 1;
1744             }
1745             mods &= ~MOD_MASK_ALT;
1746         }
1747     } else if (length > 0) {
1748         unichar c = [key characterAtIndex:0];
1750         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1751         //        [key characterAtIndex:0], mods);
1753         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1754                 || (c == intr_char && intr_char != Ctrl_C))) {
1755             trash_input_buf();
1756             got_int = TRUE;
1757         }
1759         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1760         // cleared since they are already added to the key by the AppKit.
1761         // Unfortunately, the only way to deal with when to clear the modifiers
1762         // or not seems to be to have hard-wired rules like this.
1763         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1764                     || 0x9 == c || 0xd == c || ESC == c) ) {
1765             mods &= ~MOD_MASK_SHIFT;
1766             mods &= ~MOD_MASK_CTRL;
1767             //NSLog(@"clear shift ctrl");
1768         }
1770         // HACK!  All Option+key presses go via 'insert text' messages, except
1771         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1772         // not work to map to it.
1773         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1774             //NSLog(@"clear alt");
1775             mods &= ~MOD_MASK_ALT;
1776         }
1778 #ifdef FEAT_MBYTE
1779         if (input_conv.vc_type != CONV_NONE) {
1780             conv_str = string_convert(&input_conv, chars, &length);
1781             if (conv_str)
1782                 chars = conv_str;
1783         }
1784 #endif
1785     }
1787     if (chars && length > 0) {
1788         if (mods) {
1789             //NSLog(@"adding mods: %d", mods);
1790             modChars[0] = CSI;
1791             modChars[1] = KS_MODIFIER;
1792             modChars[2] = mods;
1793             add_to_input_buf(modChars, 3);
1794         }
1796         //NSLog(@"add to input buf: 0x%x", chars[0]);
1797         // TODO: Check for CSI bytes?
1798         add_to_input_buf(chars, length);
1799     }
1801 #ifdef FEAT_MBYTE
1802     if (conv_str)
1803         vim_free(conv_str);
1804 #endif
1807 - (void)queueMessage:(int)msgid data:(NSData *)data
1809     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1810     if (data)
1811         [outputQueue addObject:data];
1812     else
1813         [outputQueue addObject:[NSData data]];
1816 - (void)connectionDidDie:(NSNotification *)notification
1818     // If the main connection to MacVim is lost this means that MacVim was
1819     // either quit (by the user chosing Quit on the MacVim menu), or it has
1820     // crashed.  In the former case the flag 'isTerminating' is set and we then
1821     // quit cleanly; in the latter case we make sure the swap files are left
1822     // for recovery.
1824     //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1825     if (isTerminating)
1826         getout(0);
1827     else
1828         getout_preserve_modified(1);
1831 - (void)blinkTimerFired:(NSTimer *)timer
1833     NSTimeInterval timeInterval = 0;
1835     [blinkTimer release];
1836     blinkTimer = nil;
1838     if (MMBlinkStateOn == blinkState) {
1839         gui_undraw_cursor();
1840         blinkState = MMBlinkStateOff;
1841         timeInterval = blinkOffInterval;
1842     } else if (MMBlinkStateOff == blinkState) {
1843         gui_update_cursor(TRUE, FALSE);
1844         blinkState = MMBlinkStateOn;
1845         timeInterval = blinkOnInterval;
1846     }
1848     if (timeInterval > 0) {
1849         blinkTimer = 
1850             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1851                                             selector:@selector(blinkTimerFired:)
1852                                             userInfo:nil repeats:NO] retain];
1853         [self flushQueue:YES];
1854     }
1857 - (void)focusChange:(BOOL)on
1859     gui_focus_change(on);
1862 - (void)handleToggleToolbar
1864     // If 'go' contains 'T', then remove it, else add it.
1866     char_u go[sizeof(GO_ALL)+2];
1867     char_u *p;
1868     int len;
1870     STRCPY(go, p_go);
1871     p = vim_strchr(go, GO_TOOLBAR);
1872     len = STRLEN(go);
1874     if (p != NULL) {
1875         char_u *end = go + len;
1876         while (p < end) {
1877             p[0] = p[1];
1878             ++p;
1879         }
1880     } else {
1881         go[len] = GO_TOOLBAR;
1882         go[len+1] = NUL;
1883     }
1885     set_option_value((char_u*)"guioptions", 0, go, 0);
1887     // Force screen redraw (does it have to be this complicated?).
1888     redraw_all_later(CLEAR);
1889     update_screen(NOT_VALID);
1890     setcursor();
1891     out_flush();
1892     gui_update_cursor(FALSE, FALSE);
1893     gui_mch_flush();
1896 - (void)handleScrollbarEvent:(NSData *)data
1898     if (!data) return;
1900     const void *bytes = [data bytes];
1901     long ident = *((long*)bytes);  bytes += sizeof(long);
1902     int hitPart = *((int*)bytes);  bytes += sizeof(int);
1903     float fval = *((float*)bytes);  bytes += sizeof(float);
1904     scrollbar_T *sb = gui_find_scrollbar(ident);
1906     if (sb) {
1907         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1908         long value = sb_info->value;
1909         long size = sb_info->size;
1910         long max = sb_info->max;
1911         BOOL isStillDragging = NO;
1912         BOOL updateKnob = YES;
1914         switch (hitPart) {
1915         case NSScrollerDecrementPage:
1916             value -= (size > 2 ? size - 2 : 1);
1917             break;
1918         case NSScrollerIncrementPage:
1919             value += (size > 2 ? size - 2 : 1);
1920             break;
1921         case NSScrollerDecrementLine:
1922             --value;
1923             break;
1924         case NSScrollerIncrementLine:
1925             ++value;
1926             break;
1927         case NSScrollerKnob:
1928             isStillDragging = YES;
1929             // fall through ...
1930         case NSScrollerKnobSlot:
1931             value = (long)(fval * (max - size + 1));
1932             // fall through ...
1933         default:
1934             updateKnob = NO;
1935             break;
1936         }
1938         //NSLog(@"value %d -> %d", sb_info->value, value);
1939         gui_drag_scrollbar(sb, value, isStillDragging);
1941         if (updateKnob) {
1942             // Dragging the knob or option+clicking automatically updates
1943             // the knob position (on the actual NSScroller), so we only
1944             // need to set the knob position in the other cases.
1945             if (sb->wp) {
1946                 // Update both the left&right vertical scrollbars.
1947                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1948                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1949                 [self setScrollbarThumbValue:value size:size max:max
1950                                   identifier:identLeft];
1951                 [self setScrollbarThumbValue:value size:size max:max
1952                                   identifier:identRight];
1953             } else {
1954                 // Update the horizontal scrollbar.
1955                 [self setScrollbarThumbValue:value size:size max:max
1956                                   identifier:ident];
1957             }
1958         }
1959     }
1962 - (void)handleSetFont:(NSData *)data
1964     if (!data) return;
1966     const void *bytes = [data bytes];
1967     float pointSize = *((float*)bytes);  bytes += sizeof(float);
1968     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1969     bytes += sizeof(unsigned);  // len not used
1971     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1972     [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1973     char_u *s = (char_u*)[name UTF8String];
1975 #ifdef FEAT_MBYTE
1976     s = CONVERT_FROM_UTF8(s);
1977 #endif
1979     set_option_value((char_u*)"guifont", 0, s, 0);
1981 #ifdef FEAT_MBYTE
1982     CONVERT_FROM_UTF8_FREE(s);
1983 #endif
1985     // Force screen redraw (does it have to be this complicated?).
1986     redraw_all_later(CLEAR);
1987     update_screen(NOT_VALID);
1988     setcursor();
1989     out_flush();
1990     gui_update_cursor(FALSE, FALSE);
1991     gui_mch_flush();
1994 - (void)handleDropFiles:(NSData *)data
1996     // TODO: Get rid of this method; instead use Vim script directly.  At the
1997     // moment I know how to do this to open files in tabs, but I'm not sure how
1998     // to add the filenames to the command line when in command line mode.
2000     if (!data) return;
2002 #ifdef FEAT_DND
2003     const void *bytes = [data bytes];
2004     const void *end = [data bytes] + [data length];
2005     BOOL forceOpen = *((BOOL*)bytes);  bytes += sizeof(BOOL);
2006     int n = *((int*)bytes);  bytes += sizeof(int);
2008     if (!forceOpen && (State & CMDLINE)) {
2009         // HACK!  If Vim is in command line mode then the files names
2010         // should be added to the command line, instead of opening the
2011         // files in tabs (unless forceOpen is set).  This is taken care of by
2012         // gui_handle_drop().
2013         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2014         if (fnames) {
2015             int i = 0;
2016             while (bytes < end && i < n) {
2017                 int len = *((int*)bytes);  bytes += sizeof(int);
2018                 char_u *s = (char_u*)bytes;
2019 #ifdef FEAT_MBYTE
2020                 s = CONVERT_FROM_UTF8(s);
2021 #endif
2022                 fnames[i++] = vim_strsave(s);
2023 #ifdef FEAT_MBYTE
2024                 CONVERT_FROM_UTF8_FREE(s);
2025 #endif
2026                 bytes += len;
2027             }
2029             // NOTE!  This function will free 'fnames'.
2030             // HACK!  It is assumed that the 'x' and 'y' arguments are
2031             // unused when in command line mode.
2032             gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
2033         }
2034     } else {
2035         // HACK!  I'm not sure how to get Vim to open a list of files in
2036         // tabs, so instead I create a ':tab drop' command with all the
2037         // files to open and execute it.
2038         NSMutableString *cmd = [NSMutableString stringWithString:@":tab drop"];
2040         int i;
2041         for (i = 0; i < n && bytes < end; ++i) {
2042             int len = *((int*)bytes);  bytes += sizeof(int);
2043             NSString *file = [NSString stringWithUTF8String:bytes];
2044             file = [file stringByEscapingSpecialFilenameCharacters];
2045             bytes += len;
2047             [cmd appendString:@" "];
2048             [cmd appendString:file];
2049         }
2051         // By going to the last tabpage we ensure that the new tabs will
2052         // appear last (if this call is left out, the taborder becomes
2053         // messy).
2054         goto_tabpage(9999);
2056         char_u *s = (char_u*)[cmd UTF8String];
2057 #ifdef FEAT_MBYTE
2058         s = CONVERT_FROM_UTF8(s);
2059 #endif
2060         do_cmdline_cmd(s);
2061 #ifdef FEAT_MBYTE
2062         CONVERT_FROM_UTF8_FREE(s);
2063 #endif
2065         // Force screen redraw (does it have to be this complicated?).
2066         // (This code was taken from the end of gui_handle_drop().)
2067         update_screen(NOT_VALID);
2068         setcursor();
2069         out_flush();
2070         gui_update_cursor(FALSE, FALSE);
2071         maketitle();
2072         gui_mch_flush();
2073     }
2074 #endif // FEAT_DND
2077 - (void)handleDropString:(NSData *)data
2079     if (!data) return;
2081 #ifdef FEAT_DND
2082     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2083     const void *bytes = [data bytes];
2084     int len = *((int*)bytes);  bytes += sizeof(int);
2085     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2087     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2088     NSRange range = { 0, [string length] };
2089     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2090                                          withString:@"\x0a" options:0
2091                                               range:range];
2092     if (0 == n) {
2093         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2094                                        options:0 range:range];
2095     }
2097     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2098     char_u *s = (char_u*)[string UTF8String];
2099 #ifdef FEAT_MBYTE
2100     if (input_conv.vc_type != CONV_NONE)
2101         s = string_convert(&input_conv, s, &len);
2102 #endif
2103     dnd_yank_drag_data(s, len);
2104 #ifdef FEAT_MBYTE
2105     if (input_conv.vc_type != CONV_NONE)
2106         vim_free(s);
2107 #endif
2108     add_to_input_buf(dropkey, sizeof(dropkey));
2109 #endif // FEAT_DND
2112 - (void)handleOdbEdit:(NSData *)data
2114 #ifdef FEAT_ODB_EDITOR
2115     const void *bytes = [data bytes];
2117     OSType serverID = *((OSType*)bytes);  bytes += sizeof(OSType);
2119     char_u *path = NULL;
2120     int pathLen = *((int*)bytes);  bytes += sizeof(int);
2121     if (pathLen > 0) {
2122         path = (char_u*)bytes;
2123         bytes += pathLen;
2124 #ifdef FEAT_MBYTE
2125         path = CONVERT_FROM_UTF8(path);
2126 #endif
2127     }
2129     NSAppleEventDescriptor *token = nil;
2130     DescType tokenType = *((DescType*)bytes);  bytes += sizeof(DescType);
2131     int descLen = *((int*)bytes);  bytes += sizeof(int);
2132     if (descLen > 0) {
2133         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2134                                                                bytes:bytes
2135                                                               length:descLen];
2136         bytes += descLen;
2137     }
2139     unsigned i, numFiles = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2140     for (i = 0; i < numFiles; ++i) {
2141         int len = *((int*)bytes);  bytes += sizeof(int);
2142         char_u *filename = (char_u*)bytes;
2143 #ifdef FEAT_MBYTE
2144         filename = CONVERT_FROM_UTF8(filename);
2145 #endif
2146         buf_T *buf = buflist_findname(filename);
2147         if (buf) {
2148             if (buf->b_odb_token) {
2149                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2150                 buf->b_odb_token = NULL;
2151             }
2153             if (buf->b_odb_fname) {
2154                 vim_free(buf->b_odb_fname);
2155                 buf->b_odb_fname = NULL;
2156             }
2158             buf->b_odb_server_id = serverID;
2160             if (token)
2161                 buf->b_odb_token = [token retain];
2162             if (path)
2163                 buf->b_odb_fname = vim_strsave(path);
2164         } else {
2165             NSLog(@"WARNING: Could not find buffer '%s' for ODB editing.",
2166                     filename);
2167         }
2169 #ifdef FEAT_MBYTE
2170         CONVERT_FROM_UTF8_FREE(filename);
2171 #endif
2172         bytes += len;
2173     }
2174 #ifdef FEAT_MBYTE
2175     CONVERT_FROM_UTF8_FREE(path);
2176 #endif
2177 #endif // FEAT_ODB_EDITOR
2180 - (void)handleXcodeMod:(NSData *)data
2182 #if 0
2183     const void *bytes = [data bytes];
2184     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2185     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2186     if (0 == len)
2187         return;
2189     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2190             descriptorWithDescriptorType:type
2191                                    bytes:bytes
2192                                   length:len];
2193 #endif
2196 - (BOOL)checkForModifiedBuffers
2198     buf_T *buf;
2199     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2200         if (bufIsChanged(buf)) {
2201             return YES;
2202         }
2203     }
2205     return NO;
2208 - (void)addInput:(NSString *)input
2210     char_u *s = (char_u*)[input UTF8String];
2212 #ifdef FEAT_MBYTE
2213     s = CONVERT_FROM_UTF8(s);
2214 #endif
2216     server_to_input_buf(s);
2218 #ifdef FEAT_MBYTE
2219     CONVERT_FROM_UTF8_FREE(s);
2220 #endif
2223 @end // MMBackend (Private)
2228 @implementation MMBackend (ClientServer)
2230 - (NSString *)connectionNameFromServerName:(NSString *)name
2232     NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2234     return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2235         lowercaseString];
2238 - (NSConnection *)connectionForServerName:(NSString *)name
2240     // TODO: Try 'name%d' if 'name' fails.
2241     NSString *connName = [self connectionNameFromServerName:name];
2242     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2244     if (!svrConn) {
2245         svrConn = [NSConnection connectionWithRegisteredName:connName
2246                                                            host:nil];
2247         // Try alternate server...
2248         if (!svrConn && alternateServerName) {
2249             //NSLog(@"  trying to connect to alternate server: %@",
2250             //        alternateServerName);
2251             connName = [self connectionNameFromServerName:alternateServerName];
2252             svrConn = [NSConnection connectionWithRegisteredName:connName
2253                                                             host:nil];
2254         }
2256         // Try looking for alternate servers...
2257         if (!svrConn) {
2258             //NSLog(@"  looking for alternate servers...");
2259             NSString *alt = [self alternateServerNameForName:name];
2260             if (alt != alternateServerName) {
2261                 //NSLog(@"  found alternate server: %@", string);
2262                 [alternateServerName release];
2263                 alternateServerName = [alt copy];
2264             }
2265         }
2267         // Try alternate server again...
2268         if (!svrConn && alternateServerName) {
2269             //NSLog(@"  trying to connect to alternate server: %@",
2270             //        alternateServerName);
2271             connName = [self connectionNameFromServerName:alternateServerName];
2272             svrConn = [NSConnection connectionWithRegisteredName:connName
2273                                                             host:nil];
2274         }
2276         if (svrConn) {
2277             [connectionNameDict setObject:svrConn forKey:connName];
2279             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2280             [[NSNotificationCenter defaultCenter] addObserver:self
2281                     selector:@selector(serverConnectionDidDie:)
2282                         name:NSConnectionDidDieNotification object:svrConn];
2283         }
2284     }
2286     return svrConn;
2289 - (NSConnection *)connectionForServerPort:(int)port
2291     NSConnection *conn;
2292     NSEnumerator *e = [connectionNameDict objectEnumerator];
2294     while ((conn = [e nextObject])) {
2295         // HACK! Assume connection uses mach ports.
2296         if (port == [(NSMachPort*)[conn sendPort] machPort])
2297             return conn;
2298     }
2300     return nil;
2303 - (void)serverConnectionDidDie:(NSNotification *)notification
2305     //NSLog(@"%s%@", _cmd, notification);
2307     NSConnection *svrConn = [notification object];
2309     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2310     [[NSNotificationCenter defaultCenter]
2311             removeObserver:self
2312                       name:NSConnectionDidDieNotification
2313                     object:svrConn];
2315     [connectionNameDict removeObjectsForKeys:
2316         [connectionNameDict allKeysForObject:svrConn]];
2318     // HACK! Assume connection uses mach ports.
2319     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2320     NSNumber *key = [NSNumber numberWithInt:port];
2322     [clientProxyDict removeObjectForKey:key];
2323     [serverReplyDict removeObjectForKey:key];
2326 - (void)addClient:(NSDistantObject *)client
2328     NSConnection *conn = [client connectionForProxy];
2329     // HACK! Assume connection uses mach ports.
2330     int port = [(NSMachPort*)[conn sendPort] machPort];
2331     NSNumber *key = [NSNumber numberWithInt:port];
2333     if (![clientProxyDict objectForKey:key]) {
2334         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2335         [clientProxyDict setObject:client forKey:key];
2336     }
2338     // NOTE: 'clientWindow' is a global variable which is used by <client>
2339     clientWindow = port;
2342 - (NSString *)alternateServerNameForName:(NSString *)name
2344     if (!(name && [name length] > 0))
2345         return nil;
2347     // Only look for alternates if 'name' doesn't end in a digit.
2348     unichar lastChar = [name characterAtIndex:[name length]-1];
2349     if (lastChar >= '0' && lastChar <= '9')
2350         return nil;
2352     // Look for alternates among all current servers.
2353     NSArray *list = [self serverList];
2354     if (!(list && [list count] > 0))
2355         return nil;
2357     // Filter out servers starting with 'name' and ending with a number. The
2358     // (?i) pattern ensures that the match is case insensitive.
2359     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2360     NSPredicate *pred = [NSPredicate predicateWithFormat:
2361             @"SELF MATCHES %@", pat];
2362     list = [list filteredArrayUsingPredicate:pred];
2363     if ([list count] > 0) {
2364         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2365         return [list objectAtIndex:0];
2366     }
2368     return nil;
2371 @end // MMBackend (ClientServer)
2376 @implementation NSString (MMServerNameCompare)
2377 - (NSComparisonResult)serverNameCompare:(NSString *)string
2379     return [self compare:string
2380                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2382 @end
2387 static int eventModifierFlagsToVimModMask(int modifierFlags)
2389     int modMask = 0;
2391     if (modifierFlags & NSShiftKeyMask)
2392         modMask |= MOD_MASK_SHIFT;
2393     if (modifierFlags & NSControlKeyMask)
2394         modMask |= MOD_MASK_CTRL;
2395     if (modifierFlags & NSAlternateKeyMask)
2396         modMask |= MOD_MASK_ALT;
2397     if (modifierFlags & NSCommandKeyMask)
2398         modMask |= MOD_MASK_CMD;
2400     return modMask;
2403 static int vimModMaskToEventModifierFlags(int mods)
2405     int flags = 0;
2407     if (mods & MOD_MASK_SHIFT)
2408         flags |= NSShiftKeyMask;
2409     if (mods & MOD_MASK_CTRL)
2410         flags |= NSControlKeyMask;
2411     if (mods & MOD_MASK_ALT)
2412         flags |= NSAlternateKeyMask;
2413     if (mods & MOD_MASK_CMD)
2414         flags |= NSCommandKeyMask;
2416     return flags;
2419 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2421     int modMask = 0;
2423     if (modifierFlags & NSShiftKeyMask)
2424         modMask |= MOUSE_SHIFT;
2425     if (modifierFlags & NSControlKeyMask)
2426         modMask |= MOUSE_CTRL;
2427     if (modifierFlags & NSAlternateKeyMask)
2428         modMask |= MOUSE_ALT;
2430     return modMask;
2433 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2435     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2437     return (buttonNumber >= 0 && buttonNumber < 3)
2438             ? mouseButton[buttonNumber] : -1;
2441 static int specialKeyToNSKey(int key)
2443     if (!IS_SPECIAL(key))
2444         return key;
2446     static struct {
2447         int special;
2448         int nskey;
2449     } sp2ns[] = {
2450         { K_UP, NSUpArrowFunctionKey },
2451         { K_DOWN, NSDownArrowFunctionKey },
2452         { K_LEFT, NSLeftArrowFunctionKey },
2453         { K_RIGHT, NSRightArrowFunctionKey },
2454         { K_F1, NSF1FunctionKey },
2455         { K_F2, NSF2FunctionKey },
2456         { K_F3, NSF3FunctionKey },
2457         { K_F4, NSF4FunctionKey },
2458         { K_F5, NSF5FunctionKey },
2459         { K_F6, NSF6FunctionKey },
2460         { K_F7, NSF7FunctionKey },
2461         { K_F8, NSF8FunctionKey },
2462         { K_F9, NSF9FunctionKey },
2463         { K_F10, NSF10FunctionKey },
2464         { K_F11, NSF11FunctionKey },
2465         { K_F12, NSF12FunctionKey },
2466         { K_F13, NSF13FunctionKey },
2467         { K_F14, NSF14FunctionKey },
2468         { K_F15, NSF15FunctionKey },
2469         { K_F16, NSF16FunctionKey },
2470         { K_F17, NSF17FunctionKey },
2471         { K_F18, NSF18FunctionKey },
2472         { K_F19, NSF19FunctionKey },
2473         { K_F20, NSF20FunctionKey },
2474         { K_F21, NSF21FunctionKey },
2475         { K_F22, NSF22FunctionKey },
2476         { K_F23, NSF23FunctionKey },
2477         { K_F24, NSF24FunctionKey },
2478         { K_F25, NSF25FunctionKey },
2479         { K_F26, NSF26FunctionKey },
2480         { K_F27, NSF27FunctionKey },
2481         { K_F28, NSF28FunctionKey },
2482         { K_F29, NSF29FunctionKey },
2483         { K_F30, NSF30FunctionKey },
2484         { K_F31, NSF31FunctionKey },
2485         { K_F32, NSF32FunctionKey },
2486         { K_F33, NSF33FunctionKey },
2487         { K_F34, NSF34FunctionKey },
2488         { K_F35, NSF35FunctionKey },
2489         { K_DEL, NSBackspaceCharacter },
2490         { K_BS, NSDeleteCharacter },
2491         { K_HOME, NSHomeFunctionKey },
2492         { K_END, NSEndFunctionKey },
2493         { K_PAGEUP, NSPageUpFunctionKey },
2494         { K_PAGEDOWN, NSPageDownFunctionKey }
2495     };
2497     int i;
2498     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2499         if (sp2ns[i].special == key)
2500             return sp2ns[i].nskey;
2501     }
2503     return 0;