Improved method to start Vim processes in a login shell
[MacVim.git] / src / MacVim / MMBackend.m
blob55f09f08fc42144bfbb7b3b889bb6ac01ba46c84
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     BOOL ok = NO;
291     @try {
292         [[NSNotificationCenter defaultCenter] addObserver:self
293                 selector:@selector(connectionDidDie:)
294                     name:NSConnectionDidDieNotification object:connection];
296         id proxy = [connection rootProxy];
297         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
299         int pid = [[NSProcessInfo processInfo] processIdentifier];
301         frontendProxy = [proxy connectBackend:self pid:pid];
302         if (frontendProxy) {
303             [frontendProxy retain];
304             [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
305             ok = YES;
306         }
307     }
308     @catch (NSException *e) {
309         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
310     }
312     return ok;
315 - (BOOL)openVimWindow
317     [self queueMessage:OpenVimWindowMsgID data:nil];
318     return YES;
321 - (void)clearAll
323     int type = ClearAllDrawType;
325     // Any draw commands in queue are effectively obsolete since this clearAll
326     // will negate any effect they have, therefore we may as well clear the
327     // draw queue.
328     [drawData setLength:0];
330     [drawData appendBytes:&type length:sizeof(int)];
333 - (void)clearBlockFromRow:(int)row1 column:(int)col1
334                     toRow:(int)row2 column:(int)col2
336     int type = ClearBlockDrawType;
338     [drawData appendBytes:&type length:sizeof(int)];
340     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
341     [drawData appendBytes:&row1 length:sizeof(int)];
342     [drawData appendBytes:&col1 length:sizeof(int)];
343     [drawData appendBytes:&row2 length:sizeof(int)];
344     [drawData appendBytes:&col2 length:sizeof(int)];
347 - (void)deleteLinesFromRow:(int)row count:(int)count
348               scrollBottom:(int)bottom left:(int)left right:(int)right
350     int type = DeleteLinesDrawType;
352     [drawData appendBytes:&type length:sizeof(int)];
354     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
355     [drawData appendBytes:&row length:sizeof(int)];
356     [drawData appendBytes:&count length:sizeof(int)];
357     [drawData appendBytes:&bottom length:sizeof(int)];
358     [drawData appendBytes:&left length:sizeof(int)];
359     [drawData appendBytes:&right length:sizeof(int)];
362 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
363              cells:(int)cells flags:(int)flags
365     if (len <= 0 || cells <= 0) return;
367     int type = DrawStringDrawType;
369     [drawData appendBytes:&type length:sizeof(int)];
371     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
372     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
373     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
374     [drawData appendBytes:&row length:sizeof(int)];
375     [drawData appendBytes:&col length:sizeof(int)];
376     [drawData appendBytes:&cells length:sizeof(int)];
377     [drawData appendBytes:&flags length:sizeof(int)];
378     [drawData appendBytes:&len length:sizeof(int)];
379     [drawData appendBytes:s length:len];
382 - (void)insertLinesFromRow:(int)row count:(int)count
383               scrollBottom:(int)bottom left:(int)left right:(int)right
385     int type = InsertLinesDrawType;
387     [drawData appendBytes:&type length:sizeof(int)];
389     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
390     [drawData appendBytes:&row length:sizeof(int)];
391     [drawData appendBytes:&count length:sizeof(int)];
392     [drawData appendBytes:&bottom length:sizeof(int)];
393     [drawData appendBytes:&left length:sizeof(int)];
394     [drawData appendBytes:&right length:sizeof(int)];
397 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
398                fraction:(int)percent color:(int)color
400     int type = DrawCursorDrawType;
401     unsigned uc = MM_COLOR(color);
403     [drawData appendBytes:&type length:sizeof(int)];
405     [drawData appendBytes:&uc length:sizeof(unsigned)];
406     [drawData appendBytes:&row length:sizeof(int)];
407     [drawData appendBytes:&col length:sizeof(int)];
408     [drawData appendBytes:&shape length:sizeof(int)];
409     [drawData appendBytes:&percent length:sizeof(int)];
412 - (void)update
414     // Tend to the run loop, returning immediately if there are no events
415     // waiting.
416     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
417                              beforeDate:[NSDate distantPast]];
419 #if 0
420     // Keyboard and mouse input is handled directly, other input is queued and
421     // processed here.  This call may enter a blocking loop.
422     if ([inputQueue count] > 0)
423         [self processInputQueue];
424 #endif
427 - (void)flushQueue:(BOOL)force
429     // NOTE! This method gets called a lot; if we were to flush every time it
430     // got called MacVim would feel unresponsive.  So there is a time out which
431     // ensures that the queue isn't flushed too often.
432     if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
433             < MMFlushTimeoutInterval
434             && [drawData length] < MMFlushQueueLenHint)
435         return;
437     if ([drawData length] > 0) {
438         // HACK!  Detect changes to 'guifontwide'.
439         if (gui.wide_font != (GuiFont)oldWideFont) {
440             [oldWideFont release];
441             oldWideFont = [(NSFont*)gui.wide_font retain];
442             [self setWideFont:oldWideFont];
443         }
445         int type = SetCursorPosDrawType;
446         [drawData appendBytes:&type length:sizeof(type)];
447         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
448         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
450         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
451         [drawData setLength:0];
452     }
454     if ([outputQueue count] > 0) {
455         @try {
456             [frontendProxy processCommandQueue:outputQueue];
457         }
458         @catch (NSException *e) {
459             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
460         }
462         [outputQueue removeAllObjects];
464         [lastFlushDate release];
465         lastFlushDate = [[NSDate date] retain];
466     }
469 - (BOOL)waitForInput:(int)milliseconds
471     //NSLog(@"|ENTER| %s%d",  _cmd, milliseconds);
473     // Only start the run loop if the input queue is empty, otherwise process
474     // the input first so that the input on queue isn't delayed.
475     if ([inputQueue count]) {
476         inputReceived = YES;
477     } else {
478         NSDate *date = milliseconds > 0 ?
479                 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
480                 [NSDate distantFuture];
482         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
483                                  beforeDate:date];
484     }
486     // I know of no way to figure out if the run loop exited because input was
487     // found or because of a time out, so I need to manually indicate when
488     // input was received in processInput:data: and then reset it every time
489     // here.
490     BOOL yn = inputReceived;
491     inputReceived = NO;
493     // Keyboard and mouse input is handled directly, other input is queued and
494     // processed here.  This call may enter a blocking loop.
495     if ([inputQueue count] > 0)
496         [self processInputQueue];
498     //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
499     return yn;
502 - (void)exit
504 #ifdef MAC_CLIENTSERVER
505     // The default connection is used for the client/server code.
506     [[NSConnection defaultConnection] setRootObject:nil];
507     [[NSConnection defaultConnection] invalidate];
508 #endif
510     // By invalidating the NSConnection the MMWindowController immediately
511     // finds out that the connection is down and as a result
512     // [MMWindowController connectionDidDie:] is invoked.
513     //NSLog(@"%@ %s", [self className], _cmd);
514     [[NSNotificationCenter defaultCenter] removeObserver:self];
515     [connection invalidate];
517     if (fontContainerRef) {
518         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
519         fontContainerRef = 0;
520     }
524 - (void)selectTab:(int)index
526     //NSLog(@"%s%d", _cmd, index);
528     index -= 1;
529     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
530     [self queueMessage:SelectTabMsgID data:data];
533 - (void)updateTabBar
535     //NSLog(@"%s", _cmd);
537     NSMutableData *data = [NSMutableData data];
539     int idx = tabpage_index(curtab) - 1;
540     [data appendBytes:&idx length:sizeof(int)];
542     tabpage_T *tp;
543     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
544         // This function puts the label of the tab in the global 'NameBuff'.
545         get_tabline_label(tp, FALSE);
546         char_u *s = NameBuff;
547         int len = STRLEN(s);
548         if (len <= 0) continue;
550 #ifdef FEAT_MBYTE
551         s = CONVERT_TO_UTF8(s);
552 #endif
554         // Count the number of windows in the tabpage.
555         //win_T *wp = tp->tp_firstwin;
556         //int wincount;
557         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
559         //[data appendBytes:&wincount length:sizeof(int)];
560         [data appendBytes:&len length:sizeof(int)];
561         [data appendBytes:s length:len];
563 #ifdef FEAT_MBYTE
564         CONVERT_TO_UTF8_FREE(s);
565 #endif
566     }
568     [self queueMessage:UpdateTabBarMsgID data:data];
571 - (BOOL)tabBarVisible
573     return tabBarVisible;
576 - (void)showTabBar:(BOOL)enable
578     tabBarVisible = enable;
580     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
581     [self queueMessage:msgid data:nil];
584 - (void)setRows:(int)rows columns:(int)cols
586     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
588     int dim[] = { rows, cols };
589     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
591     [self queueMessage:SetTextDimensionsMsgID data:data];
594 - (void)setWindowTitle:(char *)title
596     NSMutableData *data = [NSMutableData data];
597     int len = strlen(title);
598     if (len <= 0) return;
600     [data appendBytes:&len length:sizeof(int)];
601     [data appendBytes:title length:len];
603     [self queueMessage:SetWindowTitleMsgID data:data];
606 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
607                             saving:(int)saving
609     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
610     //        saving);
612     char_u *s = NULL;
613     NSString *ds = dir ? [NSString stringWithUTF8String:dir] : nil;
614     NSString *ts = title ? [NSString stringWithUTF8String:title] : nil;
615     @try {
616         [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
618         // Wait until a reply is sent from MMVimController.
619         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
620                                  beforeDate:[NSDate distantFuture]];
622         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
623             char_u *ret = (char_u*)[dialogReturn UTF8String];
624 #ifdef FEAT_MBYTE
625             ret = CONVERT_FROM_UTF8(ret);
626 #endif
627             s = vim_strsave(ret);
628 #ifdef FEAT_MBYTE
629             CONVERT_FROM_UTF8_FREE(ret);
630 #endif
631         }
633         [dialogReturn release];  dialogReturn = nil;
634     }
635     @catch (NSException *e) {
636         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
637     }
639     return (char *)s;
642 - (oneway void)setDialogReturn:(in bycopy id)obj
644     // NOTE: This is called by
645     //   - [MMVimController panelDidEnd:::], and
646     //   - [MMVimController alertDidEnd:::],
647     // to indicate that a save/open panel or alert has finished.
649     if (obj != dialogReturn) {
650         [dialogReturn release];
651         dialogReturn = [obj retain];
652     }
655 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
656                      buttons:(char *)btns textField:(char *)txtfield
658     int retval = 0;
659     NSString *message = nil, *text = nil, *textFieldString = nil;
660     NSArray *buttons = nil;
661     int style = NSInformationalAlertStyle;
663     if (VIM_WARNING == type) style = NSWarningAlertStyle;
664     else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
666     if (btns) {
667         NSString *btnString = [NSString stringWithUTF8String:btns];
668         buttons = [btnString componentsSeparatedByString:@"\n"];
669     }
670     if (title)
671         message = [NSString stringWithUTF8String:title];
672     if (msg) {
673         text = [NSString stringWithUTF8String:msg];
674         if (!message) {
675             // HACK! If there is a '\n\n' or '\n' sequence in the message, then
676             // make the part up to there into the title.  We only do this
677             // because Vim has lots of dialogs without a title and they look
678             // ugly that way.
679             // TODO: Fix the actual dialog texts.
680             NSRange eolRange = [text rangeOfString:@"\n\n"];
681             if (NSNotFound == eolRange.location)
682                 eolRange = [text rangeOfString:@"\n"];
683             if (NSNotFound != eolRange.location) {
684                 message = [text substringToIndex:eolRange.location];
685                 text = [text substringFromIndex:NSMaxRange(eolRange)];
686             }
687         }
688     }
689     if (txtfield)
690         textFieldString = [NSString stringWithUTF8String:txtfield];
692     @try {
693         [frontendProxy presentDialogWithStyle:style message:message
694                               informativeText:text buttonTitles:buttons
695                               textFieldString:textFieldString];
697         // Wait until a reply is sent from MMVimController.
698         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
699                                  beforeDate:[NSDate distantFuture]];
701         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
702                 && [dialogReturn count]) {
703             retval = [[dialogReturn objectAtIndex:0] intValue];
704             if (txtfield && [dialogReturn count] > 1) {
705                 NSString *retString = [dialogReturn objectAtIndex:1];
706                 char_u *ret = (char_u*)[retString UTF8String];
707 #ifdef FEAT_MBYTE
708                 ret = CONVERT_FROM_UTF8(ret);
709 #endif
710                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
711 #ifdef FEAT_MBYTE
712                 CONVERT_FROM_UTF8_FREE(ret);
713 #endif
714             }
715         }
717         [dialogReturn release]; dialogReturn = nil;
718     }
719     @catch (NSException *e) {
720         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
721     }
723     return retval;
726 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
727                atIndex:(int)index
729     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
730     //        name, index);
732     int namelen = name ? strlen(name) : 0;
733     NSMutableData *data = [NSMutableData data];
735     [data appendBytes:&tag length:sizeof(int)];
736     [data appendBytes:&parentTag length:sizeof(int)];
737     [data appendBytes:&namelen length:sizeof(int)];
738     if (namelen > 0) [data appendBytes:name length:namelen];
739     [data appendBytes:&index length:sizeof(int)];
741     [self queueMessage:AddMenuMsgID data:data];
744 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
745                        tip:(char *)tip icon:(char *)icon
746              keyEquivalent:(int)key modifiers:(int)mods
747                     action:(NSString *)action atIndex:(int)index
749     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
750     //        parentTag, name, tip, index);
752     int namelen = name ? strlen(name) : 0;
753     int tiplen = tip ? strlen(tip) : 0;
754     int iconlen = icon ? strlen(icon) : 0;
755     int eventFlags = vimModMaskToEventModifierFlags(mods);
756     int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
757     NSMutableData *data = [NSMutableData data];
759     key = specialKeyToNSKey(key);
761     [data appendBytes:&tag length:sizeof(int)];
762     [data appendBytes:&parentTag length:sizeof(int)];
763     [data appendBytes:&namelen length:sizeof(int)];
764     if (namelen > 0) [data appendBytes:name length:namelen];
765     [data appendBytes:&tiplen length:sizeof(int)];
766     if (tiplen > 0) [data appendBytes:tip length:tiplen];
767     [data appendBytes:&iconlen length:sizeof(int)];
768     if (iconlen > 0) [data appendBytes:icon length:iconlen];
769     [data appendBytes:&actionlen length:sizeof(int)];
770     if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
771     [data appendBytes:&index length:sizeof(int)];
772     [data appendBytes:&key length:sizeof(int)];
773     [data appendBytes:&eventFlags length:sizeof(int)];
775     [self queueMessage:AddMenuItemMsgID data:data];
778 - (void)removeMenuItemWithTag:(int)tag
780     NSMutableData *data = [NSMutableData data];
781     [data appendBytes:&tag length:sizeof(int)];
783     [self queueMessage:RemoveMenuItemMsgID data:data];
786 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
788     NSMutableData *data = [NSMutableData data];
790     [data appendBytes:&tag length:sizeof(int)];
791     [data appendBytes:&enabled length:sizeof(int)];
793     [self queueMessage:EnableMenuItemMsgID data:data];
796 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
798     int len = strlen(name);
799     int row = -1, col = -1;
801     if (len <= 0) return;
803     if (!mouse && curwin) {
804         row = curwin->w_wrow;
805         col = curwin->w_wcol;
806     }
808     NSMutableData *data = [NSMutableData data];
810     [data appendBytes:&row length:sizeof(int)];
811     [data appendBytes:&col length:sizeof(int)];
812     [data appendBytes:&len length:sizeof(int)];
813     [data appendBytes:name length:len];
815     [self queueMessage:ShowPopupMenuMsgID data:data];
818 - (void)showToolbar:(int)enable flags:(int)flags
820     NSMutableData *data = [NSMutableData data];
822     [data appendBytes:&enable length:sizeof(int)];
823     [data appendBytes:&flags length:sizeof(int)];
825     [self queueMessage:ShowToolbarMsgID data:data];
828 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
830     NSMutableData *data = [NSMutableData data];
832     [data appendBytes:&ident length:sizeof(long)];
833     [data appendBytes:&type length:sizeof(int)];
835     [self queueMessage:CreateScrollbarMsgID data:data];
838 - (void)destroyScrollbarWithIdentifier:(long)ident
840     NSMutableData *data = [NSMutableData data];
841     [data appendBytes:&ident length:sizeof(long)];
843     [self queueMessage:DestroyScrollbarMsgID data:data];
846 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
848     NSMutableData *data = [NSMutableData data];
850     [data appendBytes:&ident length:sizeof(long)];
851     [data appendBytes:&visible length:sizeof(int)];
853     [self queueMessage:ShowScrollbarMsgID data:data];
856 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
858     NSMutableData *data = [NSMutableData data];
860     [data appendBytes:&ident length:sizeof(long)];
861     [data appendBytes:&pos length:sizeof(int)];
862     [data appendBytes:&len length:sizeof(int)];
864     [self queueMessage:SetScrollbarPositionMsgID data:data];
867 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
868                     identifier:(long)ident
870     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
871     float prop = (float)size/(max+1);
872     if (fval < 0) fval = 0;
873     else if (fval > 1.0f) fval = 1.0f;
874     if (prop < 0) prop = 0;
875     else if (prop > 1.0f) prop = 1.0f;
877     NSMutableData *data = [NSMutableData data];
879     [data appendBytes:&ident length:sizeof(long)];
880     [data appendBytes:&fval length:sizeof(float)];
881     [data appendBytes:&prop length:sizeof(float)];
883     [self queueMessage:SetScrollbarThumbMsgID data:data];
886 - (void)setFont:(NSFont *)font
888     NSString *fontName = [font displayName];
889     float size = [font pointSize];
890     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
891     if (len > 0) {
892         NSMutableData *data = [NSMutableData data];
894         [data appendBytes:&size length:sizeof(float)];
895         [data appendBytes:&len length:sizeof(int)];
896         [data appendBytes:[fontName UTF8String] length:len];
898         [self queueMessage:SetFontMsgID data:data];
899     }
902 - (void)setWideFont:(NSFont *)font
904     NSString *fontName = [font displayName];
905     float size = [font pointSize];
906     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
907     NSMutableData *data = [NSMutableData data];
909     [data appendBytes:&size length:sizeof(float)];
910     [data appendBytes:&len length:sizeof(int)];
911     if (len > 0)
912         [data appendBytes:[fontName UTF8String] length:len];
914     [self queueMessage:SetWideFontMsgID data:data];
917 - (void)executeActionWithName:(NSString *)name
919     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
921     if (len > 0) {
922         NSMutableData *data = [NSMutableData data];
924         [data appendBytes:&len length:sizeof(int)];
925         [data appendBytes:[name UTF8String] length:len];
927         [self queueMessage:ExecuteActionMsgID data:data];
928     }
931 - (void)setMouseShape:(int)shape
933     NSMutableData *data = [NSMutableData data];
934     [data appendBytes:&shape length:sizeof(int)];
935     [self queueMessage:SetMouseShapeMsgID data:data];
938 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
940     // Vim specifies times in milliseconds, whereas Cocoa wants them in
941     // seconds.
942     blinkWaitInterval = .001f*wait;
943     blinkOnInterval = .001f*on;
944     blinkOffInterval = .001f*off;
947 - (void)startBlink
949     if (blinkTimer) {
950         [blinkTimer invalidate];
951         [blinkTimer release];
952         blinkTimer = nil;
953     }
955     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
956             && gui.in_focus) {
957         blinkState = MMBlinkStateOn;
958         blinkTimer =
959             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
960                                               target:self
961                                             selector:@selector(blinkTimerFired:)
962                                             userInfo:nil repeats:NO] retain];
963         gui_update_cursor(TRUE, FALSE);
964         [self flushQueue:YES];
965     }
968 - (void)stopBlink
970     if (MMBlinkStateOff == blinkState) {
971         gui_update_cursor(TRUE, FALSE);
972         [self flushQueue:YES];
973     }
975     blinkState = MMBlinkStateNone;
978 - (void)adjustLinespace:(int)linespace
980     NSMutableData *data = [NSMutableData data];
981     [data appendBytes:&linespace length:sizeof(int)];
982     [self queueMessage:AdjustLinespaceMsgID data:data];
985 - (void)activate
987     [self queueMessage:ActivateMsgID data:nil];
990 - (void)setPreEditRow:(int)row column:(int)col
992     NSMutableData *data = [NSMutableData data];
993     [data appendBytes:&row length:sizeof(int)];
994     [data appendBytes:&col length:sizeof(int)];
995     [self queueMessage:SetPreEditPositionMsgID data:data];
998 - (int)lookupColorWithKey:(NSString *)key
1000     if (!(key && [key length] > 0))
1001         return INVALCOLOR;
1003     NSString *stripKey = [[[[key lowercaseString]
1004         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
1005             componentsSeparatedByString:@" "]
1006                componentsJoinedByString:@""];
1008     if (stripKey && [stripKey length] > 0) {
1009         // First of all try to lookup key in the color dictionary; note that
1010         // all keys in this dictionary are lowercase with no whitespace.
1011         id obj = [colorDict objectForKey:stripKey];
1012         if (obj) return [obj intValue];
1014         // The key was not in the dictionary; is it perhaps of the form
1015         // #rrggbb?
1016         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
1017             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
1018             [scanner setScanLocation:1];
1019             unsigned hex = 0;
1020             if ([scanner scanHexInt:&hex]) {
1021                 return (int)hex;
1022             }
1023         }
1025         // As a last resort, check if it is one of the system defined colors.
1026         // The keys in this dictionary are also lowercase with no whitespace.
1027         obj = [sysColorDict objectForKey:stripKey];
1028         if (obj) {
1029             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
1030             if (col) {
1031                 float r, g, b, a;
1032                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
1033                 [col getRed:&r green:&g blue:&b alpha:&a];
1034                 return (((int)(r*255+.5f) & 0xff) << 16)
1035                      + (((int)(g*255+.5f) & 0xff) << 8)
1036                      +  ((int)(b*255+.5f) & 0xff);
1037             }
1038         }
1039     }
1041     //NSLog(@"WARNING: No color with key %@ found.", stripKey);
1042     return INVALCOLOR;
1045 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
1047     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1048     id obj;
1050     while ((obj = [e nextObject])) {
1051         if ([value isEqual:obj])
1052             return YES;
1053     }
1055     return NO;
1058 - (void)enterFullscreen
1060     [self queueMessage:EnterFullscreenMsgID data:nil];
1063 - (void)leaveFullscreen
1065     [self queueMessage:LeaveFullscreenMsgID data:nil];
1068 - (void)updateModifiedFlag
1070     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1071     // vice versa.
1072     int msgid = [self checkForModifiedBuffers]
1073             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1075     [self queueMessage:msgid data:nil];
1078 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1080     // NOTE: This method might get called whenever the run loop is tended to.
1081     // Normal keyboard and mouse input is added to input buffers, so there is
1082     // no risk in handling these events directly (they return immediately, and
1083     // do not call any other Vim functions).  However, other events such
1084     // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
1085     // events which would cause this method to be called recursively.  This
1086     // in turn leads to various difficulties that we do not want to have to
1087     // deal with.  To avoid recursive calls here we add all events except
1088     // keyboard and mouse events to an input queue which is processed whenever
1089     // gui_mch_update() is called (see processInputQueue).
1091     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1093     // Don't flush too soon after receiving input or update speed will suffer.
1094     [lastFlushDate release];
1095     lastFlushDate = [[NSDate date] retain];
1097     // Handle keyboard and mouse input now.  All other events are queued.
1098     if (InsertTextMsgID == msgid) {
1099         [self handleInsertText:data];
1100     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1101         if (!data) return;
1102         const void *bytes = [data bytes];
1103         int mods = *((int*)bytes);  bytes += sizeof(int);
1104         int len = *((int*)bytes);  bytes += sizeof(int);
1105         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1106                                               encoding:NSUTF8StringEncoding];
1107         mods = eventModifierFlagsToVimModMask(mods);
1109         [self handleKeyDown:key modifiers:mods];
1111         [key release];
1112     } else if (ScrollWheelMsgID == msgid) {
1113         if (!data) return;
1114         const void *bytes = [data bytes];
1116         int row = *((int*)bytes);  bytes += sizeof(int);
1117         int col = *((int*)bytes);  bytes += sizeof(int);
1118         int flags = *((int*)bytes);  bytes += sizeof(int);
1119         float dy = *((float*)bytes);  bytes += sizeof(float);
1121         int button = MOUSE_5;
1122         if (dy > 0) button = MOUSE_4;
1124         flags = eventModifierFlagsToVimMouseModMask(flags);
1126         int numLines = (int)round(dy);
1127         if (numLines < 0) numLines = -numLines;
1128         if (numLines == 0) numLines = 1;
1130 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1131         gui.scroll_wheel_force = numLines;
1132 #endif
1134         gui_send_mouse_event(button, col, row, NO, flags);
1135     } else if (MouseDownMsgID == msgid) {
1136         if (!data) return;
1137         const void *bytes = [data bytes];
1139         int row = *((int*)bytes);  bytes += sizeof(int);
1140         int col = *((int*)bytes);  bytes += sizeof(int);
1141         int button = *((int*)bytes);  bytes += sizeof(int);
1142         int flags = *((int*)bytes);  bytes += sizeof(int);
1143         int count = *((int*)bytes);  bytes += sizeof(int);
1145         button = eventButtonNumberToVimMouseButton(button);
1146         if (button >= 0) {
1147             flags = eventModifierFlagsToVimMouseModMask(flags);
1148             gui_send_mouse_event(button, col, row, count>1, flags);
1149         }
1150     } else if (MouseUpMsgID == msgid) {
1151         if (!data) return;
1152         const void *bytes = [data bytes];
1154         int row = *((int*)bytes);  bytes += sizeof(int);
1155         int col = *((int*)bytes);  bytes += sizeof(int);
1156         int flags = *((int*)bytes);  bytes += sizeof(int);
1158         flags = eventModifierFlagsToVimMouseModMask(flags);
1160         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1161     } else if (MouseDraggedMsgID == msgid) {
1162         if (!data) return;
1163         const void *bytes = [data bytes];
1165         int row = *((int*)bytes);  bytes += sizeof(int);
1166         int col = *((int*)bytes);  bytes += sizeof(int);
1167         int flags = *((int*)bytes);  bytes += sizeof(int);
1169         flags = eventModifierFlagsToVimMouseModMask(flags);
1171         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1172     } else if (MouseMovedMsgID == msgid) {
1173         const void *bytes = [data bytes];
1174         int row = *((int*)bytes);  bytes += sizeof(int);
1175         int col = *((int*)bytes);  bytes += sizeof(int);
1177         gui_mouse_moved(col, row);
1178     } else if (AddInputMsgID == msgid) {
1179         NSString *string = [[NSString alloc] initWithData:data
1180                 encoding:NSUTF8StringEncoding];
1181         if (string) {
1182             [self addInput:string];
1183             [string release];
1184         }
1185     } else if (TerminateNowMsgID == msgid) {
1186         isTerminating = YES;
1187     } else {
1188         // Not keyboard or mouse event, queue it and handle later.
1189         //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
1190         [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1191         [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
1192     }
1194     // See waitForInput: for an explanation of this flag.
1195     inputReceived = YES;
1198 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1200     // TODO: Get rid of this method?
1201     //NSLog(@"%s%@", _cmd, messages);
1203     unsigned i, count = [messages count];
1204     for (i = 0; i < count; i += 2) {
1205         int msgid = [[messages objectAtIndex:i] intValue];
1206         id data = [messages objectAtIndex:i+1];
1207         if ([data isEqual:[NSNull null]])
1208             data = nil;
1210         [self processInput:msgid data:data];
1211     }
1214 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1216     NSString *eval = nil;
1217     char_u *s = (char_u*)[expr UTF8String];
1219 #ifdef FEAT_MBYTE
1220     s = CONVERT_FROM_UTF8(s);
1221 #endif
1223     char_u *res = eval_client_expr_to_string(s);
1225 #ifdef FEAT_MBYTE
1226     CONVERT_FROM_UTF8_FREE(s);
1227 #endif
1229     if (res != NULL) {
1230         s = res;
1231 #ifdef FEAT_MBYTE
1232         s = CONVERT_TO_UTF8(s);
1233 #endif
1234         eval = [NSString stringWithUTF8String:(char*)s];
1235 #ifdef FEAT_MBYTE
1236         CONVERT_TO_UTF8_FREE(s);
1237 #endif
1238         vim_free(res);
1239     }
1241     return eval;
1244 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1246     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1247         // If there is no pasteboard, return YES to indicate that there is text
1248         // to copy.
1249         if (!pboard)
1250             return YES;
1252         clip_copy_selection();
1254         // Get the text to put on the pasteboard.
1255         long_u llen = 0; char_u *str = 0;
1256         int type = clip_convert_selection(&str, &llen, &clip_star);
1257         if (type < 0)
1258             return NO;
1259         
1260         // TODO: Avoid overflow.
1261         int len = (int)llen;
1262 #ifdef FEAT_MBYTE
1263         if (output_conv.vc_type != CONV_NONE) {
1264             char_u *conv_str = string_convert(&output_conv, str, &len);
1265             if (conv_str) {
1266                 vim_free(str);
1267                 str = conv_str;
1268             }
1269         }
1270 #endif
1272         NSString *string = [[NSString alloc]
1273             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1275         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1276         [pboard declareTypes:types owner:nil];
1277         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1278     
1279         [string release];
1280         vim_free(str);
1282         return ok;
1283     }
1285     return NO;
1288 - (oneway void)addReply:(in bycopy NSString *)reply
1289                  server:(in byref id <MMVimServerProtocol>)server
1291     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1293     // Replies might come at any time and in any order so we keep them in an
1294     // array inside a dictionary with the send port used as key.
1296     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1297     // HACK! Assume connection uses mach ports.
1298     int port = [(NSMachPort*)[conn sendPort] machPort];
1299     NSNumber *key = [NSNumber numberWithInt:port];
1301     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1302     if (!replies) {
1303         replies = [NSMutableArray array];
1304         [serverReplyDict setObject:replies forKey:key];
1305     }
1307     [replies addObject:reply];
1310 - (void)addInput:(in bycopy NSString *)input
1311                  client:(in byref id <MMVimClientProtocol>)client
1313     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1315     [self addInput:input];
1316     [self addClient:(id)client];
1318     inputReceived = YES;
1321 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1322                  client:(in byref id <MMVimClientProtocol>)client
1324     [self addClient:(id)client];
1325     return [self evaluateExpression:expr];
1328 - (void)registerServerWithName:(NSString *)name
1330     NSString *svrName = name;
1331     NSConnection *svrConn = [NSConnection defaultConnection];
1332     unsigned i;
1334     for (i = 0; i < MMServerMax; ++i) {
1335         NSString *connName = [self connectionNameFromServerName:svrName];
1337         if ([svrConn registerName:connName]) {
1338             //NSLog(@"Registered server with name: %@", svrName);
1340             // TODO: Set request/reply time-outs to something else?
1341             //
1342             // Don't wait for requests (time-out means that the message is
1343             // dropped).
1344             [svrConn setRequestTimeout:0];
1345             //[svrConn setReplyTimeout:MMReplyTimeout];
1346             [svrConn setRootObject:self];
1348             char_u *s = (char_u*)[svrName UTF8String];
1349 #ifdef FEAT_MBYTE
1350             s = CONVERT_FROM_UTF8(s);
1351 #endif
1352             // NOTE: 'serverName' is a global variable
1353             serverName = vim_strsave(s);
1354 #ifdef FEAT_MBYTE
1355             CONVERT_FROM_UTF8_FREE(s);
1356 #endif
1357 #ifdef FEAT_EVAL
1358             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1359 #endif
1360 #ifdef FEAT_TITLE
1361             need_maketitle = TRUE;
1362 #endif
1363             [self queueMessage:SetServerNameMsgID data:
1364                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1365             break;
1366         }
1368         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1369     }
1372 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1373                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1374               silent:(BOOL)silent
1376     // NOTE: If 'name' equals 'serverName' then the request is local (client
1377     // and server are the same).  This case is not handled separately, so a
1378     // connection will be set up anyway (this simplifies the code).
1380     NSConnection *conn = [self connectionForServerName:name];
1381     if (!conn) {
1382         if (!silent) {
1383             char_u *s = (char_u*)[name UTF8String];
1384 #ifdef FEAT_MBYTE
1385             s = CONVERT_FROM_UTF8(s);
1386 #endif
1387             EMSG2(_(e_noserver), s);
1388 #ifdef FEAT_MBYTE
1389             CONVERT_FROM_UTF8_FREE(s);
1390 #endif
1391         }
1392         return NO;
1393     }
1395     if (port) {
1396         // HACK! Assume connection uses mach ports.
1397         *port = [(NSMachPort*)[conn sendPort] machPort];
1398     }
1400     id proxy = [conn rootProxy];
1401     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1403     @try {
1404         if (expr) {
1405             NSString *eval = [proxy evaluateExpression:string client:self];
1406             if (reply) {
1407                 if (eval) {
1408                     char_u *r = (char_u*)[eval UTF8String];
1409 #ifdef FEAT_MBYTE
1410                     r = CONVERT_FROM_UTF8(r);
1411 #endif
1412                     *reply = vim_strsave(r);
1413 #ifdef FEAT_MBYTE
1414                     CONVERT_FROM_UTF8_FREE(r);
1415 #endif
1416                 } else {
1417                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1418                 }
1419             }
1421             if (!eval)
1422                 return NO;
1423         } else {
1424             [proxy addInput:string client:self];
1425         }
1426     }
1427     @catch (NSException *e) {
1428         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1429         return NO;
1430     }
1432     return YES;
1435 - (NSArray *)serverList
1437     NSArray *list = nil;
1439     if ([self connection]) {
1440         id proxy = [connection rootProxy];
1441         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1443         @try {
1444             list = [proxy serverList];
1445         }
1446         @catch (NSException *e) {
1447             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1448         }
1449     } else {
1450         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1451     }
1453     return list;
1456 - (NSString *)peekForReplyOnPort:(int)port
1458     //NSLog(@"%s%d", _cmd, port);
1460     NSNumber *key = [NSNumber numberWithInt:port];
1461     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1462     if (replies && [replies count]) {
1463         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1464         //        [replies objectAtIndex:0]);
1465         return [replies objectAtIndex:0];
1466     }
1468     //NSLog(@"    No replies");
1469     return nil;
1472 - (NSString *)waitForReplyOnPort:(int)port
1474     //NSLog(@"%s%d", _cmd, port);
1475     
1476     NSConnection *conn = [self connectionForServerPort:port];
1477     if (!conn)
1478         return nil;
1480     NSNumber *key = [NSNumber numberWithInt:port];
1481     NSMutableArray *replies = nil;
1482     NSString *reply = nil;
1484     // Wait for reply as long as the connection to the server is valid (unless
1485     // user interrupts wait with Ctrl-C).
1486     while (!got_int && [conn isValid] &&
1487             !(replies = [serverReplyDict objectForKey:key])) {
1488         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1489                                  beforeDate:[NSDate distantFuture]];
1490     }
1492     if (replies) {
1493         if ([replies count] > 0) {
1494             reply = [[replies objectAtIndex:0] retain];
1495             //NSLog(@"    Got reply: %@", reply);
1496             [replies removeObjectAtIndex:0];
1497             [reply autorelease];
1498         }
1500         if ([replies count] == 0)
1501             [serverReplyDict removeObjectForKey:key];
1502     }
1504     return reply;
1507 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1509     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1510     if (client) {
1511         @try {
1512             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1513             [client addReply:reply server:self];
1514             return YES;
1515         }
1516         @catch (NSException *e) {
1517             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1518         }
1519     } else {
1520         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1521     }
1523     return NO;
1526 @end // MMBackend
1530 @implementation MMBackend (Private)
1532 - (void)processInputQueue
1534     // NOTE: One of the input events may cause this method to be called
1535     // recursively, so copy the input queue to a local variable and clear it
1536     // before starting to process input events (otherwise we could get stuck in
1537     // an endless loop).
1538     NSArray *q = [inputQueue copy];
1539     unsigned i, count = [q count];
1541     [inputQueue removeAllObjects];
1543     for (i = 0; i < count-1; i += 2) {
1544         int msgid = [[q objectAtIndex:i] intValue];
1545         id data = [q objectAtIndex:i+1];
1546         if ([data isEqual:[NSNull null]])
1547             data = nil;
1549         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1550         [self handleInputEvent:msgid data:data];
1551     }
1553     [q release];
1554     //NSLog(@"Clear input event queue");
1557 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1559     // NOTE: Be careful with what you do in this method.  Ideally, a message
1560     // should be handled by adding something to the input buffer and returning
1561     // immediately.  If you call a Vim function then it should not enter a loop
1562     // waiting for key presses or in any other way block the process.  The
1563     // reason for this being that only one message can be processed at a time,
1564     // so if another message is received while processing, then the new message
1565     // is dropped.  See also the comment in processInput:data:.
1567     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1569     if (SelectTabMsgID == msgid) {
1570         if (!data) return;
1571         const void *bytes = [data bytes];
1572         int idx = *((int*)bytes) + 1;
1573         //NSLog(@"Selecting tab %d", idx);
1574         send_tabline_event(idx);
1575     } else if (CloseTabMsgID == msgid) {
1576         if (!data) return;
1577         const void *bytes = [data bytes];
1578         int idx = *((int*)bytes) + 1;
1579         //NSLog(@"Closing tab %d", idx);
1580         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1581     } else if (AddNewTabMsgID == msgid) {
1582         //NSLog(@"Adding new tab");
1583         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1584     } else if (DraggedTabMsgID == msgid) {
1585         if (!data) return;
1586         const void *bytes = [data bytes];
1587         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1588         // based.
1589         int idx = *((int*)bytes);
1591         tabpage_move(idx);
1592     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) {
1593         if (!data) return;
1594         const void *bytes = [data bytes];
1595         int rows = *((int*)bytes);  bytes += sizeof(int);
1596         int cols = *((int*)bytes);  bytes += sizeof(int);
1598         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1599         // gui_resize_shell(), so we have to manually set the rows and columns
1600         // here.  (MacVim doesn't change the rows and columns to avoid
1601         // inconsistent states between Vim and MacVim.)
1602         [self queueMessage:msgid data:data];
1604         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1605         gui_resize_shell(cols, rows);
1606     } else if (ExecuteMenuMsgID == msgid) {
1607         if (!data) return;
1608         const void *bytes = [data bytes];
1609         int tag = *((int*)bytes);  bytes += sizeof(int);
1611         vimmenu_T *menu = (vimmenu_T*)tag;
1612         // TODO!  Make sure 'menu' is a valid menu pointer!
1613         if (menu) {
1614             gui_menu_cb(menu);
1615         }
1616     } else if (ToggleToolbarMsgID == msgid) {
1617         [self handleToggleToolbar];
1618     } else if (ScrollbarEventMsgID == msgid) {
1619         [self handleScrollbarEvent:data];
1620     } else if (SetFontMsgID == msgid) {
1621         [self handleSetFont:data];
1622     } else if (VimShouldCloseMsgID == msgid) {
1623         gui_shell_closed();
1624     } else if (DropFilesMsgID == msgid) {
1625         [self handleDropFiles:data];
1626     } else if (DropStringMsgID == msgid) {
1627         [self handleDropString:data];
1628     } else if (GotFocusMsgID == msgid) {
1629         if (!gui.in_focus)
1630             [self focusChange:YES];
1631     } else if (LostFocusMsgID == msgid) {
1632         if (gui.in_focus)
1633             [self focusChange:NO];
1634     } else if (SetMouseShapeMsgID == msgid) {
1635         const void *bytes = [data bytes];
1636         int shape = *((int*)bytes);  bytes += sizeof(int);
1637         update_mouseshape(shape);
1638     } else if (ODBEditMsgID == msgid) {
1639         [self handleOdbEdit:data];
1640     } else if (XcodeModMsgID == msgid) {
1641         [self handleXcodeMod:data];
1642     } else {
1643         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1644     }
1647 + (NSDictionary *)specialKeys
1649     static NSDictionary *specialKeys = nil;
1651     if (!specialKeys) {
1652         NSBundle *mainBundle = [NSBundle mainBundle];
1653         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1654                                               ofType:@"plist"];
1655         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1656     }
1658     return specialKeys;
1661 - (void)handleInsertText:(NSData *)data
1663     if (!data) return;
1665     NSString *key = [[NSString alloc] initWithData:data
1666                                           encoding:NSUTF8StringEncoding];
1667     char_u *str = (char_u*)[key UTF8String];
1668     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1670 #ifdef FEAT_MBYTE
1671     char_u *conv_str = NULL;
1672     if (input_conv.vc_type != CONV_NONE) {
1673         conv_str = string_convert(&input_conv, str, &len);
1674         if (conv_str)
1675             str = conv_str;
1676     }
1677 #endif
1679     if (len == 1 && ((str[0] == Ctrl_C && ctrl_c_interrupts)
1680             || (str[0] == intr_char && intr_char != Ctrl_C))) {
1681         trash_input_buf();
1682         got_int = TRUE;
1683     }
1685     for (i = 0; i < len; ++i) {
1686         add_to_input_buf(str+i, 1);
1687         if (CSI == str[i]) {
1688             // NOTE: If the converted string contains the byte CSI, then it
1689             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1690             // won't work.
1691             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1692             add_to_input_buf(extra, 2);
1693         }
1694     }
1696 #ifdef FEAT_MBYTE
1697     if (conv_str)
1698         vim_free(conv_str);
1699 #endif
1700     [key release];
1703 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1705     char_u special[3];
1706     char_u modChars[3];
1707     char_u *chars = (char_u*)[key UTF8String];
1708 #ifdef FEAT_MBYTE
1709     char_u *conv_str = NULL;
1710 #endif
1711     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1713     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1714     // that new keys can easily be added.
1715     NSString *specialString = [[MMBackend specialKeys]
1716             objectForKey:key];
1717     if (specialString && [specialString length] > 1) {
1718         //NSLog(@"special key: %@", specialString);
1719         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1720                 [specialString characterAtIndex:1]);
1722         ikey = simplify_key(ikey, &mods);
1723         if (ikey == CSI)
1724             ikey = K_CSI;
1726         special[0] = CSI;
1727         special[1] = K_SECOND(ikey);
1728         special[2] = K_THIRD(ikey);
1730         chars = special;
1731         length = 3;
1732     } else if (1 == length && TAB == chars[0]) {
1733         // Tab is a trouble child:
1734         // - <Tab> is added to the input buffer as is
1735         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1736         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1737         //   to be converted to utf-8
1738         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1739         // - <C-Tab> is reserved by Mac OS X
1740         // - <D-Tab> is reserved by Mac OS X
1741         chars = special;
1742         special[0] = TAB;
1743         length = 1;
1745         if (mods & MOD_MASK_SHIFT) {
1746             mods &= ~MOD_MASK_SHIFT;
1747             special[0] = CSI;
1748             special[1] = K_SECOND(K_S_TAB);
1749             special[2] = K_THIRD(K_S_TAB);
1750             length = 3;
1751         } else if (mods & MOD_MASK_ALT) {
1752             int mtab = 0x80 | TAB;
1753 #ifdef FEAT_MBYTE
1754             if (enc_utf8) {
1755                 // Convert to utf-8
1756                 special[0] = (mtab >> 6) + 0xc0;
1757                 special[1] = mtab & 0xbf;
1758                 length = 2;
1759             } else
1760 #endif
1761             {
1762                 special[0] = mtab;
1763                 length = 1;
1764             }
1765             mods &= ~MOD_MASK_ALT;
1766         }
1767     } else if (length > 0) {
1768         unichar c = [key characterAtIndex:0];
1770         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1771         //        [key characterAtIndex:0], mods);
1773         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1774                 || (c == intr_char && intr_char != Ctrl_C))) {
1775             trash_input_buf();
1776             got_int = TRUE;
1777         }
1779         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1780         // cleared since they are already added to the key by the AppKit.
1781         // Unfortunately, the only way to deal with when to clear the modifiers
1782         // or not seems to be to have hard-wired rules like this.
1783         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1784                     || 0x9 == c || 0xd == c || ESC == c) ) {
1785             mods &= ~MOD_MASK_SHIFT;
1786             mods &= ~MOD_MASK_CTRL;
1787             //NSLog(@"clear shift ctrl");
1788         }
1790         // HACK!  All Option+key presses go via 'insert text' messages, except
1791         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1792         // not work to map to it.
1793         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1794             //NSLog(@"clear alt");
1795             mods &= ~MOD_MASK_ALT;
1796         }
1798 #ifdef FEAT_MBYTE
1799         if (input_conv.vc_type != CONV_NONE) {
1800             conv_str = string_convert(&input_conv, chars, &length);
1801             if (conv_str)
1802                 chars = conv_str;
1803         }
1804 #endif
1805     }
1807     if (chars && length > 0) {
1808         if (mods) {
1809             //NSLog(@"adding mods: %d", mods);
1810             modChars[0] = CSI;
1811             modChars[1] = KS_MODIFIER;
1812             modChars[2] = mods;
1813             add_to_input_buf(modChars, 3);
1814         }
1816         //NSLog(@"add to input buf: 0x%x", chars[0]);
1817         // TODO: Check for CSI bytes?
1818         add_to_input_buf(chars, length);
1819     }
1821 #ifdef FEAT_MBYTE
1822     if (conv_str)
1823         vim_free(conv_str);
1824 #endif
1827 - (void)queueMessage:(int)msgid data:(NSData *)data
1829     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1830     if (data)
1831         [outputQueue addObject:data];
1832     else
1833         [outputQueue addObject:[NSData data]];
1836 - (void)connectionDidDie:(NSNotification *)notification
1838     // If the main connection to MacVim is lost this means that MacVim was
1839     // either quit (by the user chosing Quit on the MacVim menu), or it has
1840     // crashed.  In the former case the flag 'isTerminating' is set and we then
1841     // quit cleanly; in the latter case we make sure the swap files are left
1842     // for recovery.
1844     //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1845     if (isTerminating)
1846         getout(0);
1847     else
1848         getout_preserve_modified(1);
1851 - (void)blinkTimerFired:(NSTimer *)timer
1853     NSTimeInterval timeInterval = 0;
1855     [blinkTimer release];
1856     blinkTimer = nil;
1858     if (MMBlinkStateOn == blinkState) {
1859         gui_undraw_cursor();
1860         blinkState = MMBlinkStateOff;
1861         timeInterval = blinkOffInterval;
1862     } else if (MMBlinkStateOff == blinkState) {
1863         gui_update_cursor(TRUE, FALSE);
1864         blinkState = MMBlinkStateOn;
1865         timeInterval = blinkOnInterval;
1866     }
1868     if (timeInterval > 0) {
1869         blinkTimer = 
1870             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1871                                             selector:@selector(blinkTimerFired:)
1872                                             userInfo:nil repeats:NO] retain];
1873         [self flushQueue:YES];
1874     }
1877 - (void)focusChange:(BOOL)on
1879     gui_focus_change(on);
1882 - (void)handleToggleToolbar
1884     // If 'go' contains 'T', then remove it, else add it.
1886     char_u go[sizeof(GO_ALL)+2];
1887     char_u *p;
1888     int len;
1890     STRCPY(go, p_go);
1891     p = vim_strchr(go, GO_TOOLBAR);
1892     len = STRLEN(go);
1894     if (p != NULL) {
1895         char_u *end = go + len;
1896         while (p < end) {
1897             p[0] = p[1];
1898             ++p;
1899         }
1900     } else {
1901         go[len] = GO_TOOLBAR;
1902         go[len+1] = NUL;
1903     }
1905     set_option_value((char_u*)"guioptions", 0, go, 0);
1907     // Force screen redraw (does it have to be this complicated?).
1908     redraw_all_later(CLEAR);
1909     update_screen(NOT_VALID);
1910     setcursor();
1911     out_flush();
1912     gui_update_cursor(FALSE, FALSE);
1913     gui_mch_flush();
1916 - (void)handleScrollbarEvent:(NSData *)data
1918     if (!data) return;
1920     const void *bytes = [data bytes];
1921     long ident = *((long*)bytes);  bytes += sizeof(long);
1922     int hitPart = *((int*)bytes);  bytes += sizeof(int);
1923     float fval = *((float*)bytes);  bytes += sizeof(float);
1924     scrollbar_T *sb = gui_find_scrollbar(ident);
1926     if (sb) {
1927         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1928         long value = sb_info->value;
1929         long size = sb_info->size;
1930         long max = sb_info->max;
1931         BOOL isStillDragging = NO;
1932         BOOL updateKnob = YES;
1934         switch (hitPart) {
1935         case NSScrollerDecrementPage:
1936             value -= (size > 2 ? size - 2 : 1);
1937             break;
1938         case NSScrollerIncrementPage:
1939             value += (size > 2 ? size - 2 : 1);
1940             break;
1941         case NSScrollerDecrementLine:
1942             --value;
1943             break;
1944         case NSScrollerIncrementLine:
1945             ++value;
1946             break;
1947         case NSScrollerKnob:
1948             isStillDragging = YES;
1949             // fall through ...
1950         case NSScrollerKnobSlot:
1951             value = (long)(fval * (max - size + 1));
1952             // fall through ...
1953         default:
1954             updateKnob = NO;
1955             break;
1956         }
1958         //NSLog(@"value %d -> %d", sb_info->value, value);
1959         gui_drag_scrollbar(sb, value, isStillDragging);
1961         if (updateKnob) {
1962             // Dragging the knob or option+clicking automatically updates
1963             // the knob position (on the actual NSScroller), so we only
1964             // need to set the knob position in the other cases.
1965             if (sb->wp) {
1966                 // Update both the left&right vertical scrollbars.
1967                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1968                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1969                 [self setScrollbarThumbValue:value size:size max:max
1970                                   identifier:identLeft];
1971                 [self setScrollbarThumbValue:value size:size max:max
1972                                   identifier:identRight];
1973             } else {
1974                 // Update the horizontal scrollbar.
1975                 [self setScrollbarThumbValue:value size:size max:max
1976                                   identifier:ident];
1977             }
1978         }
1979     }
1982 - (void)handleSetFont:(NSData *)data
1984     if (!data) return;
1986     const void *bytes = [data bytes];
1987     float pointSize = *((float*)bytes);  bytes += sizeof(float);
1988     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1989     bytes += sizeof(unsigned);  // len not used
1991     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1992     [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1993     char_u *s = (char_u*)[name UTF8String];
1995 #ifdef FEAT_MBYTE
1996     s = CONVERT_FROM_UTF8(s);
1997 #endif
1999     set_option_value((char_u*)"guifont", 0, s, 0);
2001 #ifdef FEAT_MBYTE
2002     CONVERT_FROM_UTF8_FREE(s);
2003 #endif
2005     // Force screen redraw (does it have to be this complicated?).
2006     redraw_all_later(CLEAR);
2007     update_screen(NOT_VALID);
2008     setcursor();
2009     out_flush();
2010     gui_update_cursor(FALSE, FALSE);
2011     gui_mch_flush();
2014 - (void)handleDropFiles:(NSData *)data
2016     // TODO: Get rid of this method; instead use Vim script directly.  At the
2017     // moment I know how to do this to open files in tabs, but I'm not sure how
2018     // to add the filenames to the command line when in command line mode.
2020     if (!data) return;
2022 #ifdef FEAT_DND
2023     const void *bytes = [data bytes];
2024     const void *end = [data bytes] + [data length];
2025     BOOL forceOpen = *((BOOL*)bytes);  bytes += sizeof(BOOL);
2026     int n = *((int*)bytes);  bytes += sizeof(int);
2028     if (!forceOpen && (State & CMDLINE)) {
2029         // HACK!  If Vim is in command line mode then the files names
2030         // should be added to the command line, instead of opening the
2031         // files in tabs (unless forceOpen is set).  This is taken care of by
2032         // gui_handle_drop().
2033         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2034         if (fnames) {
2035             int i = 0;
2036             while (bytes < end && i < n) {
2037                 int len = *((int*)bytes);  bytes += sizeof(int);
2038                 char_u *s = (char_u*)bytes;
2039 #ifdef FEAT_MBYTE
2040                 s = CONVERT_FROM_UTF8(s);
2041 #endif
2042                 fnames[i++] = vim_strsave(s);
2043 #ifdef FEAT_MBYTE
2044                 CONVERT_FROM_UTF8_FREE(s);
2045 #endif
2046                 bytes += len;
2047             }
2049             // NOTE!  This function will free 'fnames'.
2050             // HACK!  It is assumed that the 'x' and 'y' arguments are
2051             // unused when in command line mode.
2052             gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
2053         }
2054     } else {
2055         // HACK!  I'm not sure how to get Vim to open a list of files in
2056         // tabs, so instead I create a ':tab drop' command with all the
2057         // files to open and execute it.
2058         NSMutableString *cmd = [NSMutableString stringWithString:@":tab drop"];
2060         int i;
2061         for (i = 0; i < n && bytes < end; ++i) {
2062             int len = *((int*)bytes);  bytes += sizeof(int);
2063             NSString *file = [NSString stringWithUTF8String:bytes];
2064             file = [file stringByEscapingSpecialFilenameCharacters];
2065             bytes += len;
2067             [cmd appendString:@" "];
2068             [cmd appendString:file];
2069         }
2071         // By going to the last tabpage we ensure that the new tabs will
2072         // appear last (if this call is left out, the taborder becomes
2073         // messy).
2074         goto_tabpage(9999);
2076         char_u *s = (char_u*)[cmd UTF8String];
2077 #ifdef FEAT_MBYTE
2078         s = CONVERT_FROM_UTF8(s);
2079 #endif
2080         do_cmdline_cmd(s);
2081 #ifdef FEAT_MBYTE
2082         CONVERT_FROM_UTF8_FREE(s);
2083 #endif
2085         // Force screen redraw (does it have to be this complicated?).
2086         // (This code was taken from the end of gui_handle_drop().)
2087         update_screen(NOT_VALID);
2088         setcursor();
2089         out_flush();
2090         gui_update_cursor(FALSE, FALSE);
2091         maketitle();
2092         gui_mch_flush();
2093     }
2094 #endif // FEAT_DND
2097 - (void)handleDropString:(NSData *)data
2099     if (!data) return;
2101 #ifdef FEAT_DND
2102     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2103     const void *bytes = [data bytes];
2104     int len = *((int*)bytes);  bytes += sizeof(int);
2105     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2107     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2108     NSRange range = { 0, [string length] };
2109     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2110                                          withString:@"\x0a" options:0
2111                                               range:range];
2112     if (0 == n) {
2113         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2114                                        options:0 range:range];
2115     }
2117     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2118     char_u *s = (char_u*)[string UTF8String];
2119 #ifdef FEAT_MBYTE
2120     if (input_conv.vc_type != CONV_NONE)
2121         s = string_convert(&input_conv, s, &len);
2122 #endif
2123     dnd_yank_drag_data(s, len);
2124 #ifdef FEAT_MBYTE
2125     if (input_conv.vc_type != CONV_NONE)
2126         vim_free(s);
2127 #endif
2128     add_to_input_buf(dropkey, sizeof(dropkey));
2129 #endif // FEAT_DND
2132 - (void)handleOdbEdit:(NSData *)data
2134 #ifdef FEAT_ODB_EDITOR
2135     const void *bytes = [data bytes];
2137     OSType serverID = *((OSType*)bytes);  bytes += sizeof(OSType);
2139     char_u *path = NULL;
2140     int pathLen = *((int*)bytes);  bytes += sizeof(int);
2141     if (pathLen > 0) {
2142         path = (char_u*)bytes;
2143         bytes += pathLen;
2144 #ifdef FEAT_MBYTE
2145         path = CONVERT_FROM_UTF8(path);
2146 #endif
2147     }
2149     NSAppleEventDescriptor *token = nil;
2150     DescType tokenType = *((DescType*)bytes);  bytes += sizeof(DescType);
2151     int descLen = *((int*)bytes);  bytes += sizeof(int);
2152     if (descLen > 0) {
2153         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2154                                                                bytes:bytes
2155                                                               length:descLen];
2156         bytes += descLen;
2157     }
2159     unsigned i, numFiles = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2160     for (i = 0; i < numFiles; ++i) {
2161         int len = *((int*)bytes);  bytes += sizeof(int);
2162         char_u *filename = (char_u*)bytes;
2163 #ifdef FEAT_MBYTE
2164         filename = CONVERT_FROM_UTF8(filename);
2165 #endif
2166         buf_T *buf = buflist_findname(filename);
2167         if (buf) {
2168             if (buf->b_odb_token) {
2169                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2170                 buf->b_odb_token = NULL;
2171             }
2173             if (buf->b_odb_fname) {
2174                 vim_free(buf->b_odb_fname);
2175                 buf->b_odb_fname = NULL;
2176             }
2178             buf->b_odb_server_id = serverID;
2180             if (token)
2181                 buf->b_odb_token = [token retain];
2182             if (path)
2183                 buf->b_odb_fname = vim_strsave(path);
2184         } else {
2185             NSLog(@"WARNING: Could not find buffer '%s' for ODB editing.",
2186                     filename);
2187         }
2189 #ifdef FEAT_MBYTE
2190         CONVERT_FROM_UTF8_FREE(filename);
2191 #endif
2192         bytes += len;
2193     }
2194 #ifdef FEAT_MBYTE
2195     CONVERT_FROM_UTF8_FREE(path);
2196 #endif
2197 #endif // FEAT_ODB_EDITOR
2200 - (void)handleXcodeMod:(NSData *)data
2202 #if 0
2203     const void *bytes = [data bytes];
2204     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2205     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2206     if (0 == len)
2207         return;
2209     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2210             descriptorWithDescriptorType:type
2211                                    bytes:bytes
2212                                   length:len];
2213 #endif
2216 - (BOOL)checkForModifiedBuffers
2218     buf_T *buf;
2219     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2220         if (bufIsChanged(buf)) {
2221             return YES;
2222         }
2223     }
2225     return NO;
2228 - (void)addInput:(NSString *)input
2230     char_u *s = (char_u*)[input UTF8String];
2232 #ifdef FEAT_MBYTE
2233     s = CONVERT_FROM_UTF8(s);
2234 #endif
2236     server_to_input_buf(s);
2238 #ifdef FEAT_MBYTE
2239     CONVERT_FROM_UTF8_FREE(s);
2240 #endif
2243 @end // MMBackend (Private)
2248 @implementation MMBackend (ClientServer)
2250 - (NSString *)connectionNameFromServerName:(NSString *)name
2252     NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2254     return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2255         lowercaseString];
2258 - (NSConnection *)connectionForServerName:(NSString *)name
2260     // TODO: Try 'name%d' if 'name' fails.
2261     NSString *connName = [self connectionNameFromServerName:name];
2262     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2264     if (!svrConn) {
2265         svrConn = [NSConnection connectionWithRegisteredName:connName
2266                                                            host:nil];
2267         // Try alternate server...
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         // Try looking for alternate servers...
2277         if (!svrConn) {
2278             //NSLog(@"  looking for alternate servers...");
2279             NSString *alt = [self alternateServerNameForName:name];
2280             if (alt != alternateServerName) {
2281                 //NSLog(@"  found alternate server: %@", string);
2282                 [alternateServerName release];
2283                 alternateServerName = [alt copy];
2284             }
2285         }
2287         // Try alternate server again...
2288         if (!svrConn && alternateServerName) {
2289             //NSLog(@"  trying to connect to alternate server: %@",
2290             //        alternateServerName);
2291             connName = [self connectionNameFromServerName:alternateServerName];
2292             svrConn = [NSConnection connectionWithRegisteredName:connName
2293                                                             host:nil];
2294         }
2296         if (svrConn) {
2297             [connectionNameDict setObject:svrConn forKey:connName];
2299             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2300             [[NSNotificationCenter defaultCenter] addObserver:self
2301                     selector:@selector(serverConnectionDidDie:)
2302                         name:NSConnectionDidDieNotification object:svrConn];
2303         }
2304     }
2306     return svrConn;
2309 - (NSConnection *)connectionForServerPort:(int)port
2311     NSConnection *conn;
2312     NSEnumerator *e = [connectionNameDict objectEnumerator];
2314     while ((conn = [e nextObject])) {
2315         // HACK! Assume connection uses mach ports.
2316         if (port == [(NSMachPort*)[conn sendPort] machPort])
2317             return conn;
2318     }
2320     return nil;
2323 - (void)serverConnectionDidDie:(NSNotification *)notification
2325     //NSLog(@"%s%@", _cmd, notification);
2327     NSConnection *svrConn = [notification object];
2329     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2330     [[NSNotificationCenter defaultCenter]
2331             removeObserver:self
2332                       name:NSConnectionDidDieNotification
2333                     object:svrConn];
2335     [connectionNameDict removeObjectsForKeys:
2336         [connectionNameDict allKeysForObject:svrConn]];
2338     // HACK! Assume connection uses mach ports.
2339     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2340     NSNumber *key = [NSNumber numberWithInt:port];
2342     [clientProxyDict removeObjectForKey:key];
2343     [serverReplyDict removeObjectForKey:key];
2346 - (void)addClient:(NSDistantObject *)client
2348     NSConnection *conn = [client connectionForProxy];
2349     // HACK! Assume connection uses mach ports.
2350     int port = [(NSMachPort*)[conn sendPort] machPort];
2351     NSNumber *key = [NSNumber numberWithInt:port];
2353     if (![clientProxyDict objectForKey:key]) {
2354         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2355         [clientProxyDict setObject:client forKey:key];
2356     }
2358     // NOTE: 'clientWindow' is a global variable which is used by <client>
2359     clientWindow = port;
2362 - (NSString *)alternateServerNameForName:(NSString *)name
2364     if (!(name && [name length] > 0))
2365         return nil;
2367     // Only look for alternates if 'name' doesn't end in a digit.
2368     unichar lastChar = [name characterAtIndex:[name length]-1];
2369     if (lastChar >= '0' && lastChar <= '9')
2370         return nil;
2372     // Look for alternates among all current servers.
2373     NSArray *list = [self serverList];
2374     if (!(list && [list count] > 0))
2375         return nil;
2377     // Filter out servers starting with 'name' and ending with a number. The
2378     // (?i) pattern ensures that the match is case insensitive.
2379     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2380     NSPredicate *pred = [NSPredicate predicateWithFormat:
2381             @"SELF MATCHES %@", pat];
2382     list = [list filteredArrayUsingPredicate:pred];
2383     if ([list count] > 0) {
2384         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2385         return [list objectAtIndex:0];
2386     }
2388     return nil;
2391 @end // MMBackend (ClientServer)
2396 @implementation NSString (MMServerNameCompare)
2397 - (NSComparisonResult)serverNameCompare:(NSString *)string
2399     return [self compare:string
2400                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2402 @end
2407 static int eventModifierFlagsToVimModMask(int modifierFlags)
2409     int modMask = 0;
2411     if (modifierFlags & NSShiftKeyMask)
2412         modMask |= MOD_MASK_SHIFT;
2413     if (modifierFlags & NSControlKeyMask)
2414         modMask |= MOD_MASK_CTRL;
2415     if (modifierFlags & NSAlternateKeyMask)
2416         modMask |= MOD_MASK_ALT;
2417     if (modifierFlags & NSCommandKeyMask)
2418         modMask |= MOD_MASK_CMD;
2420     return modMask;
2423 static int vimModMaskToEventModifierFlags(int mods)
2425     int flags = 0;
2427     if (mods & MOD_MASK_SHIFT)
2428         flags |= NSShiftKeyMask;
2429     if (mods & MOD_MASK_CTRL)
2430         flags |= NSControlKeyMask;
2431     if (mods & MOD_MASK_ALT)
2432         flags |= NSAlternateKeyMask;
2433     if (mods & MOD_MASK_CMD)
2434         flags |= NSCommandKeyMask;
2436     return flags;
2439 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2441     int modMask = 0;
2443     if (modifierFlags & NSShiftKeyMask)
2444         modMask |= MOUSE_SHIFT;
2445     if (modifierFlags & NSControlKeyMask)
2446         modMask |= MOUSE_CTRL;
2447     if (modifierFlags & NSAlternateKeyMask)
2448         modMask |= MOUSE_ALT;
2450     return modMask;
2453 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2455     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2457     return (buttonNumber >= 0 && buttonNumber < 3)
2458             ? mouseButton[buttonNumber] : -1;
2461 static int specialKeyToNSKey(int key)
2463     if (!IS_SPECIAL(key))
2464         return key;
2466     static struct {
2467         int special;
2468         int nskey;
2469     } sp2ns[] = {
2470         { K_UP, NSUpArrowFunctionKey },
2471         { K_DOWN, NSDownArrowFunctionKey },
2472         { K_LEFT, NSLeftArrowFunctionKey },
2473         { K_RIGHT, NSRightArrowFunctionKey },
2474         { K_F1, NSF1FunctionKey },
2475         { K_F2, NSF2FunctionKey },
2476         { K_F3, NSF3FunctionKey },
2477         { K_F4, NSF4FunctionKey },
2478         { K_F5, NSF5FunctionKey },
2479         { K_F6, NSF6FunctionKey },
2480         { K_F7, NSF7FunctionKey },
2481         { K_F8, NSF8FunctionKey },
2482         { K_F9, NSF9FunctionKey },
2483         { K_F10, NSF10FunctionKey },
2484         { K_F11, NSF11FunctionKey },
2485         { K_F12, NSF12FunctionKey },
2486         { K_F13, NSF13FunctionKey },
2487         { K_F14, NSF14FunctionKey },
2488         { K_F15, NSF15FunctionKey },
2489         { K_F16, NSF16FunctionKey },
2490         { K_F17, NSF17FunctionKey },
2491         { K_F18, NSF18FunctionKey },
2492         { K_F19, NSF19FunctionKey },
2493         { K_F20, NSF20FunctionKey },
2494         { K_F21, NSF21FunctionKey },
2495         { K_F22, NSF22FunctionKey },
2496         { K_F23, NSF23FunctionKey },
2497         { K_F24, NSF24FunctionKey },
2498         { K_F25, NSF25FunctionKey },
2499         { K_F26, NSF26FunctionKey },
2500         { K_F27, NSF27FunctionKey },
2501         { K_F28, NSF28FunctionKey },
2502         { K_F29, NSF29FunctionKey },
2503         { K_F30, NSF30FunctionKey },
2504         { K_F31, NSF31FunctionKey },
2505         { K_F32, NSF32FunctionKey },
2506         { K_F33, NSF33FunctionKey },
2507         { K_F34, NSF34FunctionKey },
2508         { K_F35, NSF35FunctionKey },
2509         { K_DEL, NSBackspaceCharacter },
2510         { K_BS, NSDeleteCharacter },
2511         { K_HOME, NSHomeFunctionKey },
2512         { K_END, NSEndFunctionKey },
2513         { K_PAGEUP, NSPageUpFunctionKey },
2514         { K_PAGEDOWN, NSPageDownFunctionKey }
2515     };
2517     int i;
2518     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2519         if (sp2ns[i].special == key)
2520             return sp2ns[i].nskey;
2521     }
2523     return 0;