Add command line completion to :macaction command
[MacVim.git] / src / MacVim / MMBackend.m
blob128f243dc7f4bf543f94e5f708959d74a91aea18
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     path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
146     if (path)
147         actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
149     if (!(colorDict && sysColorDict && actionDict))
150         NSLog(@"ERROR: Failed to load dictionaries.%@",
151                 MMSymlinkWarningString);
153     return self;
156 - (void)dealloc
158     //NSLog(@"%@ %s", [self className], _cmd);
159     [[NSNotificationCenter defaultCenter] removeObserver:self];
161     [oldWideFont release];  oldWideFont = nil;
162     [blinkTimer release];  blinkTimer = nil;
163     [alternateServerName release];  alternateServerName = nil;
164     [serverReplyDict release];  serverReplyDict = nil;
165     [clientProxyDict release];  clientProxyDict = nil;
166     [connectionNameDict release];  connectionNameDict = nil;
167     [inputQueue release];  inputQueue = nil;
168     [outputQueue release];  outputQueue = nil;
169     [drawData release];  drawData = nil;
170     [frontendProxy release];  frontendProxy = nil;
171     [connection release];  connection = nil;
172     [sysColorDict release];  sysColorDict = nil;
173     [colorDict release];  colorDict = nil;
175     [super dealloc];
178 - (void)setBackgroundColor:(int)color
180     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
183 - (void)setForegroundColor:(int)color
185     foregroundColor = MM_COLOR(color);
188 - (void)setSpecialColor:(int)color
190     specialColor = MM_COLOR(color);
193 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
195     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
196     defaultForegroundColor = MM_COLOR(fg);
198     NSMutableData *data = [NSMutableData data];
200     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
201     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
203     [self queueMessage:SetDefaultColorsMsgID data:data];
206 - (NSConnection *)connection
208     if (!connection) {
209         // NOTE!  If the name of the connection changes here it must also be
210         // updated in MMAppController.m.
211         NSString *name = [NSString stringWithFormat:@"%@-connection",
212                [[NSBundle mainBundle] bundleIdentifier]];
214         connection = [NSConnection connectionWithRegisteredName:name host:nil];
215         [connection retain];
216     }
218     // NOTE: 'connection' may be nil here.
219     return connection;
222 - (NSDictionary *)actionDict
224     return actionDict;
227 - (BOOL)checkin
229     if (![self connection]) {
230         NSBundle *mainBundle = [NSBundle mainBundle];
231 #if 0
232         OSStatus status;
233         FSRef ref;
235         // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
236         // the API to pass Apple Event parameters is broken on 10.4).
237         NSString *path = [mainBundle bundlePath];
238         status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
239         if (noErr == status) {
240             // Pass parameter to the 'Open' Apple Event that tells MacVim not
241             // to open an untitled window.
242             NSAppleEventDescriptor *desc =
243                     [NSAppleEventDescriptor recordDescriptor];
244             [desc setParamDescriptor:
245                     [NSAppleEventDescriptor descriptorWithBoolean:NO]
246                           forKeyword:keyMMUntitledWindow];
248             LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
249                     kLSLaunchDefaults, NULL };
250             status = LSOpenFromRefSpec(&spec, NULL);
251         }
253         if (noErr != status) {
254         NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
255                 path, MMSymlinkWarningString);
256             return NO;
257         }
258 #else
259         // Launch MacVim using NSTask.  For some reason the above code using
260         // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
261         // fails, the dock icon starts bouncing and never stops).  It seems
262         // like rebuilding the Launch Services database takes care of this
263         // problem, but the NSTask way seems more stable so stick with it.
264         //
265         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
266         // that the GUI won't be activated (or raised) so there is a hack in
267         // MMAppController which raises the app when a new window is opened.
268         NSMutableArray *args = [NSMutableArray arrayWithObjects:
269             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
270         NSString *exeName = [[mainBundle infoDictionary]
271                 objectForKey:@"CFBundleExecutable"];
272         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
273         if (!path) {
274             NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
275                     MMSymlinkWarningString);
276             return NO;
277         }
279         [NSTask launchedTaskWithLaunchPath:path arguments:args];
280 #endif
282         // HACK!  Poll the mach bootstrap server until it returns a valid
283         // connection to detect that MacVim has finished launching.  Also set a
284         // time-out date so that we don't get stuck doing this forever.
285         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
286         while (![self connection] &&
287                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
288             [[NSRunLoop currentRunLoop]
289                     runMode:NSDefaultRunLoopMode
290                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
292         // NOTE: [self connection] will set 'connection' as a side-effect.
293         if (!connection) {
294             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
295             return NO;
296         }
297     }
299     BOOL ok = NO;
300     @try {
301         [[NSNotificationCenter defaultCenter] addObserver:self
302                 selector:@selector(connectionDidDie:)
303                     name:NSConnectionDidDieNotification object:connection];
305         id proxy = [connection rootProxy];
306         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
308         int pid = [[NSProcessInfo processInfo] processIdentifier];
310         frontendProxy = [proxy connectBackend:self pid:pid];
311         if (frontendProxy) {
312             [frontendProxy retain];
313             [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
314             ok = YES;
315         }
316     }
317     @catch (NSException *e) {
318         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
319     }
321     return ok;
324 - (BOOL)openVimWindow
326     [self queueMessage:OpenVimWindowMsgID data:nil];
327     return YES;
330 - (void)clearAll
332     int type = ClearAllDrawType;
334     // Any draw commands in queue are effectively obsolete since this clearAll
335     // will negate any effect they have, therefore we may as well clear the
336     // draw queue.
337     [drawData setLength:0];
339     [drawData appendBytes:&type length:sizeof(int)];
342 - (void)clearBlockFromRow:(int)row1 column:(int)col1
343                     toRow:(int)row2 column:(int)col2
345     int type = ClearBlockDrawType;
347     [drawData appendBytes:&type length:sizeof(int)];
349     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
350     [drawData appendBytes:&row1 length:sizeof(int)];
351     [drawData appendBytes:&col1 length:sizeof(int)];
352     [drawData appendBytes:&row2 length:sizeof(int)];
353     [drawData appendBytes:&col2 length:sizeof(int)];
356 - (void)deleteLinesFromRow:(int)row count:(int)count
357               scrollBottom:(int)bottom left:(int)left right:(int)right
359     int type = DeleteLinesDrawType;
361     [drawData appendBytes:&type length:sizeof(int)];
363     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
364     [drawData appendBytes:&row length:sizeof(int)];
365     [drawData appendBytes:&count length:sizeof(int)];
366     [drawData appendBytes:&bottom length:sizeof(int)];
367     [drawData appendBytes:&left length:sizeof(int)];
368     [drawData appendBytes:&right length:sizeof(int)];
371 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
372              cells:(int)cells flags:(int)flags
374     if (len <= 0 || cells <= 0) return;
376     int type = DrawStringDrawType;
378     [drawData appendBytes:&type length:sizeof(int)];
380     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
381     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
382     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
383     [drawData appendBytes:&row length:sizeof(int)];
384     [drawData appendBytes:&col length:sizeof(int)];
385     [drawData appendBytes:&cells length:sizeof(int)];
386     [drawData appendBytes:&flags length:sizeof(int)];
387     [drawData appendBytes:&len length:sizeof(int)];
388     [drawData appendBytes:s length:len];
391 - (void)insertLinesFromRow:(int)row count:(int)count
392               scrollBottom:(int)bottom left:(int)left right:(int)right
394     int type = InsertLinesDrawType;
396     [drawData appendBytes:&type length:sizeof(int)];
398     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
399     [drawData appendBytes:&row length:sizeof(int)];
400     [drawData appendBytes:&count length:sizeof(int)];
401     [drawData appendBytes:&bottom length:sizeof(int)];
402     [drawData appendBytes:&left length:sizeof(int)];
403     [drawData appendBytes:&right length:sizeof(int)];
406 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
407                fraction:(int)percent color:(int)color
409     int type = DrawCursorDrawType;
410     unsigned uc = MM_COLOR(color);
412     [drawData appendBytes:&type length:sizeof(int)];
414     [drawData appendBytes:&uc length:sizeof(unsigned)];
415     [drawData appendBytes:&row length:sizeof(int)];
416     [drawData appendBytes:&col length:sizeof(int)];
417     [drawData appendBytes:&shape length:sizeof(int)];
418     [drawData appendBytes:&percent length:sizeof(int)];
421 - (void)update
423     // Tend to the run loop, returning immediately if there are no events
424     // waiting.
425     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
426                              beforeDate:[NSDate distantPast]];
428 #if 0
429     // Keyboard and mouse input is handled directly, other input is queued and
430     // processed here.  This call may enter a blocking loop.
431     if ([inputQueue count] > 0)
432         [self processInputQueue];
433 #endif
436 - (void)flushQueue:(BOOL)force
438     // NOTE! This method gets called a lot; if we were to flush every time it
439     // got called MacVim would feel unresponsive.  So there is a time out which
440     // ensures that the queue isn't flushed too often.
441     if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
442             < MMFlushTimeoutInterval
443             && [drawData length] < MMFlushQueueLenHint)
444         return;
446     if ([drawData length] > 0) {
447         // HACK!  Detect changes to 'guifontwide'.
448         if (gui.wide_font != (GuiFont)oldWideFont) {
449             [oldWideFont release];
450             oldWideFont = [(NSFont*)gui.wide_font retain];
451             [self setWideFont:oldWideFont];
452         }
454         int type = SetCursorPosDrawType;
455         [drawData appendBytes:&type length:sizeof(type)];
456         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
457         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
459         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
460         [drawData setLength:0];
461     }
463     if ([outputQueue count] > 0) {
464         @try {
465             [frontendProxy processCommandQueue:outputQueue];
466         }
467         @catch (NSException *e) {
468             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
469         }
471         [outputQueue removeAllObjects];
473         [lastFlushDate release];
474         lastFlushDate = [[NSDate date] retain];
475     }
478 - (BOOL)waitForInput:(int)milliseconds
480     //NSLog(@"|ENTER| %s%d",  _cmd, milliseconds);
482     // Only start the run loop if the input queue is empty, otherwise process
483     // the input first so that the input on queue isn't delayed.
484     if ([inputQueue count]) {
485         inputReceived = YES;
486     } else {
487         NSDate *date = milliseconds > 0 ?
488                 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
489                 [NSDate distantFuture];
491         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
492                                  beforeDate:date];
493     }
495     // I know of no way to figure out if the run loop exited because input was
496     // found or because of a time out, so I need to manually indicate when
497     // input was received in processInput:data: and then reset it every time
498     // here.
499     BOOL yn = inputReceived;
500     inputReceived = NO;
502     // Keyboard and mouse input is handled directly, other input is queued and
503     // processed here.  This call may enter a blocking loop.
504     if ([inputQueue count] > 0)
505         [self processInputQueue];
507     //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
508     return yn;
511 - (void)exit
513 #ifdef MAC_CLIENTSERVER
514     // The default connection is used for the client/server code.
515     [[NSConnection defaultConnection] setRootObject:nil];
516     [[NSConnection defaultConnection] invalidate];
517 #endif
519     // By invalidating the NSConnection the MMWindowController immediately
520     // finds out that the connection is down and as a result
521     // [MMWindowController connectionDidDie:] is invoked.
522     //NSLog(@"%@ %s", [self className], _cmd);
523     [[NSNotificationCenter defaultCenter] removeObserver:self];
524     [connection invalidate];
526     if (fontContainerRef) {
527         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
528         fontContainerRef = 0;
529     }
533 - (void)selectTab:(int)index
535     //NSLog(@"%s%d", _cmd, index);
537     index -= 1;
538     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
539     [self queueMessage:SelectTabMsgID data:data];
542 - (void)updateTabBar
544     //NSLog(@"%s", _cmd);
546     NSMutableData *data = [NSMutableData data];
548     int idx = tabpage_index(curtab) - 1;
549     [data appendBytes:&idx length:sizeof(int)];
551     tabpage_T *tp;
552     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
553         // This function puts the label of the tab in the global 'NameBuff'.
554         get_tabline_label(tp, FALSE);
555         char_u *s = NameBuff;
556         int len = STRLEN(s);
557         if (len <= 0) continue;
559 #ifdef FEAT_MBYTE
560         s = CONVERT_TO_UTF8(s);
561 #endif
563         // Count the number of windows in the tabpage.
564         //win_T *wp = tp->tp_firstwin;
565         //int wincount;
566         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
568         //[data appendBytes:&wincount length:sizeof(int)];
569         [data appendBytes:&len length:sizeof(int)];
570         [data appendBytes:s length:len];
572 #ifdef FEAT_MBYTE
573         CONVERT_TO_UTF8_FREE(s);
574 #endif
575     }
577     [self queueMessage:UpdateTabBarMsgID data:data];
580 - (BOOL)tabBarVisible
582     return tabBarVisible;
585 - (void)showTabBar:(BOOL)enable
587     tabBarVisible = enable;
589     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
590     [self queueMessage:msgid data:nil];
593 - (void)setRows:(int)rows columns:(int)cols
595     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
597     int dim[] = { rows, cols };
598     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
600     [self queueMessage:SetTextDimensionsMsgID data:data];
603 - (void)setWindowTitle:(char *)title
605     NSMutableData *data = [NSMutableData data];
606     int len = strlen(title);
607     if (len <= 0) return;
609     [data appendBytes:&len length:sizeof(int)];
610     [data appendBytes:title length:len];
612     [self queueMessage:SetWindowTitleMsgID data:data];
615 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
616                             saving:(int)saving
618     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
619     //        saving);
621     char_u *s = NULL;
622     NSString *ds = dir ? [NSString stringWithUTF8String:dir] : nil;
623     NSString *ts = title ? [NSString stringWithUTF8String:title] : nil;
624     @try {
625         [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
627         // Wait until a reply is sent from MMVimController.
628         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
629                                  beforeDate:[NSDate distantFuture]];
631         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
632             char_u *ret = (char_u*)[dialogReturn UTF8String];
633 #ifdef FEAT_MBYTE
634             ret = CONVERT_FROM_UTF8(ret);
635 #endif
636             s = vim_strsave(ret);
637 #ifdef FEAT_MBYTE
638             CONVERT_FROM_UTF8_FREE(ret);
639 #endif
640         }
642         [dialogReturn release];  dialogReturn = nil;
643     }
644     @catch (NSException *e) {
645         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
646     }
648     return (char *)s;
651 - (oneway void)setDialogReturn:(in bycopy id)obj
653     // NOTE: This is called by
654     //   - [MMVimController panelDidEnd:::], and
655     //   - [MMVimController alertDidEnd:::],
656     // to indicate that a save/open panel or alert has finished.
658     if (obj != dialogReturn) {
659         [dialogReturn release];
660         dialogReturn = [obj retain];
661     }
664 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
665                      buttons:(char *)btns textField:(char *)txtfield
667     int retval = 0;
668     NSString *message = nil, *text = nil, *textFieldString = nil;
669     NSArray *buttons = nil;
670     int style = NSInformationalAlertStyle;
672     if (VIM_WARNING == type) style = NSWarningAlertStyle;
673     else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
675     if (btns) {
676         NSString *btnString = [NSString stringWithUTF8String:btns];
677         buttons = [btnString componentsSeparatedByString:@"\n"];
678     }
679     if (title)
680         message = [NSString stringWithUTF8String:title];
681     if (msg) {
682         text = [NSString stringWithUTF8String:msg];
683         if (!message) {
684             // HACK! If there is a '\n\n' or '\n' sequence in the message, then
685             // make the part up to there into the title.  We only do this
686             // because Vim has lots of dialogs without a title and they look
687             // ugly that way.
688             // TODO: Fix the actual dialog texts.
689             NSRange eolRange = [text rangeOfString:@"\n\n"];
690             if (NSNotFound == eolRange.location)
691                 eolRange = [text rangeOfString:@"\n"];
692             if (NSNotFound != eolRange.location) {
693                 message = [text substringToIndex:eolRange.location];
694                 text = [text substringFromIndex:NSMaxRange(eolRange)];
695             }
696         }
697     }
698     if (txtfield)
699         textFieldString = [NSString stringWithUTF8String:txtfield];
701     @try {
702         [frontendProxy presentDialogWithStyle:style message:message
703                               informativeText:text buttonTitles:buttons
704                               textFieldString:textFieldString];
706         // Wait until a reply is sent from MMVimController.
707         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
708                                  beforeDate:[NSDate distantFuture]];
710         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
711                 && [dialogReturn count]) {
712             retval = [[dialogReturn objectAtIndex:0] intValue];
713             if (txtfield && [dialogReturn count] > 1) {
714                 NSString *retString = [dialogReturn objectAtIndex:1];
715                 char_u *ret = (char_u*)[retString UTF8String];
716 #ifdef FEAT_MBYTE
717                 ret = CONVERT_FROM_UTF8(ret);
718 #endif
719                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
720 #ifdef FEAT_MBYTE
721                 CONVERT_FROM_UTF8_FREE(ret);
722 #endif
723             }
724         }
726         [dialogReturn release]; dialogReturn = nil;
727     }
728     @catch (NSException *e) {
729         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
730     }
732     return retval;
735 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
736                atIndex:(int)index
738     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
739     //        name, index);
741     int namelen = name ? strlen(name) : 0;
742     NSMutableData *data = [NSMutableData data];
744     [data appendBytes:&tag length:sizeof(int)];
745     [data appendBytes:&parentTag length:sizeof(int)];
746     [data appendBytes:&namelen length:sizeof(int)];
747     if (namelen > 0) [data appendBytes:name length:namelen];
748     [data appendBytes:&index length:sizeof(int)];
750     [self queueMessage:AddMenuMsgID data:data];
753 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
754                        tip:(char *)tip icon:(char *)icon
755              keyEquivalent:(int)key modifiers:(int)mods
756                     action:(NSString *)action atIndex:(int)index
758     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
759     //        parentTag, name, tip, index);
761     int namelen = name ? strlen(name) : 0;
762     int tiplen = tip ? strlen(tip) : 0;
763     int iconlen = icon ? strlen(icon) : 0;
764     int eventFlags = vimModMaskToEventModifierFlags(mods);
765     int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
766     NSMutableData *data = [NSMutableData data];
768     key = specialKeyToNSKey(key);
770     [data appendBytes:&tag length:sizeof(int)];
771     [data appendBytes:&parentTag length:sizeof(int)];
772     [data appendBytes:&namelen length:sizeof(int)];
773     if (namelen > 0) [data appendBytes:name length:namelen];
774     [data appendBytes:&tiplen length:sizeof(int)];
775     if (tiplen > 0) [data appendBytes:tip length:tiplen];
776     [data appendBytes:&iconlen length:sizeof(int)];
777     if (iconlen > 0) [data appendBytes:icon length:iconlen];
778     [data appendBytes:&actionlen length:sizeof(int)];
779     if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
780     [data appendBytes:&index length:sizeof(int)];
781     [data appendBytes:&key length:sizeof(int)];
782     [data appendBytes:&eventFlags length:sizeof(int)];
784     [self queueMessage:AddMenuItemMsgID data:data];
787 - (void)removeMenuItemWithTag:(int)tag
789     NSMutableData *data = [NSMutableData data];
790     [data appendBytes:&tag length:sizeof(int)];
792     [self queueMessage:RemoveMenuItemMsgID data:data];
795 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
797     NSMutableData *data = [NSMutableData data];
799     [data appendBytes:&tag length:sizeof(int)];
800     [data appendBytes:&enabled length:sizeof(int)];
802     [self queueMessage:EnableMenuItemMsgID data:data];
805 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
807     int len = strlen(name);
808     int row = -1, col = -1;
810     if (len <= 0) return;
812     if (!mouse && curwin) {
813         row = curwin->w_wrow;
814         col = curwin->w_wcol;
815     }
817     NSMutableData *data = [NSMutableData data];
819     [data appendBytes:&row length:sizeof(int)];
820     [data appendBytes:&col length:sizeof(int)];
821     [data appendBytes:&len length:sizeof(int)];
822     [data appendBytes:name length:len];
824     [self queueMessage:ShowPopupMenuMsgID data:data];
827 - (void)showToolbar:(int)enable flags:(int)flags
829     NSMutableData *data = [NSMutableData data];
831     [data appendBytes:&enable length:sizeof(int)];
832     [data appendBytes:&flags length:sizeof(int)];
834     [self queueMessage:ShowToolbarMsgID data:data];
837 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
839     NSMutableData *data = [NSMutableData data];
841     [data appendBytes:&ident length:sizeof(long)];
842     [data appendBytes:&type length:sizeof(int)];
844     [self queueMessage:CreateScrollbarMsgID data:data];
847 - (void)destroyScrollbarWithIdentifier:(long)ident
849     NSMutableData *data = [NSMutableData data];
850     [data appendBytes:&ident length:sizeof(long)];
852     [self queueMessage:DestroyScrollbarMsgID data:data];
855 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
857     NSMutableData *data = [NSMutableData data];
859     [data appendBytes:&ident length:sizeof(long)];
860     [data appendBytes:&visible length:sizeof(int)];
862     [self queueMessage:ShowScrollbarMsgID data:data];
865 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
867     NSMutableData *data = [NSMutableData data];
869     [data appendBytes:&ident length:sizeof(long)];
870     [data appendBytes:&pos length:sizeof(int)];
871     [data appendBytes:&len length:sizeof(int)];
873     [self queueMessage:SetScrollbarPositionMsgID data:data];
876 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
877                     identifier:(long)ident
879     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
880     float prop = (float)size/(max+1);
881     if (fval < 0) fval = 0;
882     else if (fval > 1.0f) fval = 1.0f;
883     if (prop < 0) prop = 0;
884     else if (prop > 1.0f) prop = 1.0f;
886     NSMutableData *data = [NSMutableData data];
888     [data appendBytes:&ident length:sizeof(long)];
889     [data appendBytes:&fval length:sizeof(float)];
890     [data appendBytes:&prop length:sizeof(float)];
892     [self queueMessage:SetScrollbarThumbMsgID data:data];
895 - (void)setFont:(NSFont *)font
897     NSString *fontName = [font displayName];
898     float size = [font pointSize];
899     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
900     if (len > 0) {
901         NSMutableData *data = [NSMutableData data];
903         [data appendBytes:&size length:sizeof(float)];
904         [data appendBytes:&len length:sizeof(int)];
905         [data appendBytes:[fontName UTF8String] length:len];
907         [self queueMessage:SetFontMsgID data:data];
908     }
911 - (void)setWideFont:(NSFont *)font
913     NSString *fontName = [font displayName];
914     float size = [font pointSize];
915     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
916     NSMutableData *data = [NSMutableData data];
918     [data appendBytes:&size length:sizeof(float)];
919     [data appendBytes:&len length:sizeof(int)];
920     if (len > 0)
921         [data appendBytes:[fontName UTF8String] length:len];
923     [self queueMessage:SetWideFontMsgID data:data];
926 - (void)executeActionWithName:(NSString *)name
928     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
930     if (len > 0) {
931         NSMutableData *data = [NSMutableData data];
933         [data appendBytes:&len length:sizeof(int)];
934         [data appendBytes:[name UTF8String] length:len];
936         [self queueMessage:ExecuteActionMsgID data:data];
937     }
940 - (void)setMouseShape:(int)shape
942     NSMutableData *data = [NSMutableData data];
943     [data appendBytes:&shape length:sizeof(int)];
944     [self queueMessage:SetMouseShapeMsgID data:data];
947 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
949     // Vim specifies times in milliseconds, whereas Cocoa wants them in
950     // seconds.
951     blinkWaitInterval = .001f*wait;
952     blinkOnInterval = .001f*on;
953     blinkOffInterval = .001f*off;
956 - (void)startBlink
958     if (blinkTimer) {
959         [blinkTimer invalidate];
960         [blinkTimer release];
961         blinkTimer = nil;
962     }
964     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
965             && gui.in_focus) {
966         blinkState = MMBlinkStateOn;
967         blinkTimer =
968             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
969                                               target:self
970                                             selector:@selector(blinkTimerFired:)
971                                             userInfo:nil repeats:NO] retain];
972         gui_update_cursor(TRUE, FALSE);
973         [self flushQueue:YES];
974     }
977 - (void)stopBlink
979     if (MMBlinkStateOff == blinkState) {
980         gui_update_cursor(TRUE, FALSE);
981         [self flushQueue:YES];
982     }
984     blinkState = MMBlinkStateNone;
987 - (void)adjustLinespace:(int)linespace
989     NSMutableData *data = [NSMutableData data];
990     [data appendBytes:&linespace length:sizeof(int)];
991     [self queueMessage:AdjustLinespaceMsgID data:data];
994 - (void)activate
996     [self queueMessage:ActivateMsgID data:nil];
999 - (void)setPreEditRow:(int)row column:(int)col
1001     NSMutableData *data = [NSMutableData data];
1002     [data appendBytes:&row length:sizeof(int)];
1003     [data appendBytes:&col length:sizeof(int)];
1004     [self queueMessage:SetPreEditPositionMsgID data:data];
1007 - (int)lookupColorWithKey:(NSString *)key
1009     if (!(key && [key length] > 0))
1010         return INVALCOLOR;
1012     NSString *stripKey = [[[[key lowercaseString]
1013         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
1014             componentsSeparatedByString:@" "]
1015                componentsJoinedByString:@""];
1017     if (stripKey && [stripKey length] > 0) {
1018         // First of all try to lookup key in the color dictionary; note that
1019         // all keys in this dictionary are lowercase with no whitespace.
1020         id obj = [colorDict objectForKey:stripKey];
1021         if (obj) return [obj intValue];
1023         // The key was not in the dictionary; is it perhaps of the form
1024         // #rrggbb?
1025         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
1026             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
1027             [scanner setScanLocation:1];
1028             unsigned hex = 0;
1029             if ([scanner scanHexInt:&hex]) {
1030                 return (int)hex;
1031             }
1032         }
1034         // As a last resort, check if it is one of the system defined colors.
1035         // The keys in this dictionary are also lowercase with no whitespace.
1036         obj = [sysColorDict objectForKey:stripKey];
1037         if (obj) {
1038             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
1039             if (col) {
1040                 float r, g, b, a;
1041                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
1042                 [col getRed:&r green:&g blue:&b alpha:&a];
1043                 return (((int)(r*255+.5f) & 0xff) << 16)
1044                      + (((int)(g*255+.5f) & 0xff) << 8)
1045                      +  ((int)(b*255+.5f) & 0xff);
1046             }
1047         }
1048     }
1050     //NSLog(@"WARNING: No color with key %@ found.", stripKey);
1051     return INVALCOLOR;
1054 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
1056     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1057     id obj;
1059     while ((obj = [e nextObject])) {
1060         if ([value isEqual:obj])
1061             return YES;
1062     }
1064     return NO;
1067 - (void)enterFullscreen:(int)fuoptions
1069     NSMutableData *data = [NSMutableData data];
1070     [data appendBytes:&fuoptions length:sizeof(int)];
1071     [self queueMessage:EnterFullscreenMsgID data:data];
1074 - (void)leaveFullscreen
1076     [self queueMessage:LeaveFullscreenMsgID data:nil];
1079 - (void)setAntialias:(BOOL)antialias
1081     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1083     [self queueMessage:msgid data:nil];
1086 - (void)updateModifiedFlag
1088     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1089     // vice versa.
1090     int msgid = [self checkForModifiedBuffers]
1091             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1093     [self queueMessage:msgid data:nil];
1096 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1098     // NOTE: This method might get called whenever the run loop is tended to.
1099     // Normal keyboard and mouse input is added to input buffers, so there is
1100     // no risk in handling these events directly (they return immediately, and
1101     // do not call any other Vim functions).  However, other events such
1102     // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
1103     // events which would cause this method to be called recursively.  This
1104     // in turn leads to various difficulties that we do not want to have to
1105     // deal with.  To avoid recursive calls here we add all events except
1106     // keyboard and mouse events to an input queue which is processed whenever
1107     // gui_mch_update() is called (see processInputQueue).
1109     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1111     // Don't flush too soon after receiving input or update speed will suffer.
1112     [lastFlushDate release];
1113     lastFlushDate = [[NSDate date] retain];
1115     // Handle keyboard and mouse input now.  All other events are queued.
1116     if (InsertTextMsgID == msgid) {
1117         [self handleInsertText:data];
1118     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1119         if (!data) return;
1120         const void *bytes = [data bytes];
1121         int mods = *((int*)bytes);  bytes += sizeof(int);
1122         int len = *((int*)bytes);  bytes += sizeof(int);
1123         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1124                                               encoding:NSUTF8StringEncoding];
1125         mods = eventModifierFlagsToVimModMask(mods);
1127         [self handleKeyDown:key modifiers:mods];
1129         [key release];
1130     } else if (ScrollWheelMsgID == msgid) {
1131         if (!data) return;
1132         const void *bytes = [data bytes];
1134         int row = *((int*)bytes);  bytes += sizeof(int);
1135         int col = *((int*)bytes);  bytes += sizeof(int);
1136         int flags = *((int*)bytes);  bytes += sizeof(int);
1137         float dy = *((float*)bytes);  bytes += sizeof(float);
1139         int button = MOUSE_5;
1140         if (dy > 0) button = MOUSE_4;
1142         flags = eventModifierFlagsToVimMouseModMask(flags);
1144         int numLines = (int)round(dy);
1145         if (numLines < 0) numLines = -numLines;
1146         if (numLines == 0) numLines = 1;
1148 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1149         gui.scroll_wheel_force = numLines;
1150 #endif
1152         gui_send_mouse_event(button, col, row, NO, flags);
1153     } else if (MouseDownMsgID == msgid) {
1154         if (!data) return;
1155         const void *bytes = [data bytes];
1157         int row = *((int*)bytes);  bytes += sizeof(int);
1158         int col = *((int*)bytes);  bytes += sizeof(int);
1159         int button = *((int*)bytes);  bytes += sizeof(int);
1160         int flags = *((int*)bytes);  bytes += sizeof(int);
1161         int count = *((int*)bytes);  bytes += sizeof(int);
1163         button = eventButtonNumberToVimMouseButton(button);
1164         if (button >= 0) {
1165             flags = eventModifierFlagsToVimMouseModMask(flags);
1166             gui_send_mouse_event(button, col, row, count>1, flags);
1167         }
1168     } else if (MouseUpMsgID == msgid) {
1169         if (!data) return;
1170         const void *bytes = [data bytes];
1172         int row = *((int*)bytes);  bytes += sizeof(int);
1173         int col = *((int*)bytes);  bytes += sizeof(int);
1174         int flags = *((int*)bytes);  bytes += sizeof(int);
1176         flags = eventModifierFlagsToVimMouseModMask(flags);
1178         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1179     } else if (MouseDraggedMsgID == msgid) {
1180         if (!data) return;
1181         const void *bytes = [data bytes];
1183         int row = *((int*)bytes);  bytes += sizeof(int);
1184         int col = *((int*)bytes);  bytes += sizeof(int);
1185         int flags = *((int*)bytes);  bytes += sizeof(int);
1187         flags = eventModifierFlagsToVimMouseModMask(flags);
1189         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1190     } else if (MouseMovedMsgID == msgid) {
1191         const void *bytes = [data bytes];
1192         int row = *((int*)bytes);  bytes += sizeof(int);
1193         int col = *((int*)bytes);  bytes += sizeof(int);
1195         gui_mouse_moved(col, row);
1196     } else if (AddInputMsgID == msgid) {
1197         NSString *string = [[NSString alloc] initWithData:data
1198                 encoding:NSUTF8StringEncoding];
1199         if (string) {
1200             [self addInput:string];
1201             [string release];
1202         }
1203     } else if (TerminateNowMsgID == msgid) {
1204         isTerminating = YES;
1205     } else {
1206         // Not keyboard or mouse event, queue it and handle later.
1207         //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
1208         [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1209         [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
1210     }
1212     // See waitForInput: for an explanation of this flag.
1213     inputReceived = YES;
1216 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1218     // TODO: Get rid of this method?
1219     //NSLog(@"%s%@", _cmd, messages);
1221     unsigned i, count = [messages count];
1222     for (i = 0; i < count; i += 2) {
1223         int msgid = [[messages objectAtIndex:i] intValue];
1224         id data = [messages objectAtIndex:i+1];
1225         if ([data isEqual:[NSNull null]])
1226             data = nil;
1228         [self processInput:msgid data:data];
1229     }
1232 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1234     NSString *eval = nil;
1235     char_u *s = (char_u*)[expr UTF8String];
1237 #ifdef FEAT_MBYTE
1238     s = CONVERT_FROM_UTF8(s);
1239 #endif
1241     char_u *res = eval_client_expr_to_string(s);
1243 #ifdef FEAT_MBYTE
1244     CONVERT_FROM_UTF8_FREE(s);
1245 #endif
1247     if (res != NULL) {
1248         s = res;
1249 #ifdef FEAT_MBYTE
1250         s = CONVERT_TO_UTF8(s);
1251 #endif
1252         eval = [NSString stringWithUTF8String:(char*)s];
1253 #ifdef FEAT_MBYTE
1254         CONVERT_TO_UTF8_FREE(s);
1255 #endif
1256         vim_free(res);
1257     }
1259     return eval;
1262 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1264     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1265         // If there is no pasteboard, return YES to indicate that there is text
1266         // to copy.
1267         if (!pboard)
1268             return YES;
1270         clip_copy_selection();
1272         // Get the text to put on the pasteboard.
1273         long_u llen = 0; char_u *str = 0;
1274         int type = clip_convert_selection(&str, &llen, &clip_star);
1275         if (type < 0)
1276             return NO;
1277         
1278         // TODO: Avoid overflow.
1279         int len = (int)llen;
1280 #ifdef FEAT_MBYTE
1281         if (output_conv.vc_type != CONV_NONE) {
1282             char_u *conv_str = string_convert(&output_conv, str, &len);
1283             if (conv_str) {
1284                 vim_free(str);
1285                 str = conv_str;
1286             }
1287         }
1288 #endif
1290         NSString *string = [[NSString alloc]
1291             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1293         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1294         [pboard declareTypes:types owner:nil];
1295         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1296     
1297         [string release];
1298         vim_free(str);
1300         return ok;
1301     }
1303     return NO;
1306 - (oneway void)addReply:(in bycopy NSString *)reply
1307                  server:(in byref id <MMVimServerProtocol>)server
1309     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1311     // Replies might come at any time and in any order so we keep them in an
1312     // array inside a dictionary with the send port used as key.
1314     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1315     // HACK! Assume connection uses mach ports.
1316     int port = [(NSMachPort*)[conn sendPort] machPort];
1317     NSNumber *key = [NSNumber numberWithInt:port];
1319     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1320     if (!replies) {
1321         replies = [NSMutableArray array];
1322         [serverReplyDict setObject:replies forKey:key];
1323     }
1325     [replies addObject:reply];
1328 - (void)addInput:(in bycopy NSString *)input
1329                  client:(in byref id <MMVimClientProtocol>)client
1331     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1333     [self addInput:input];
1334     [self addClient:(id)client];
1336     inputReceived = YES;
1339 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1340                  client:(in byref id <MMVimClientProtocol>)client
1342     [self addClient:(id)client];
1343     return [self evaluateExpression:expr];
1346 - (void)registerServerWithName:(NSString *)name
1348     NSString *svrName = name;
1349     NSConnection *svrConn = [NSConnection defaultConnection];
1350     unsigned i;
1352     for (i = 0; i < MMServerMax; ++i) {
1353         NSString *connName = [self connectionNameFromServerName:svrName];
1355         if ([svrConn registerName:connName]) {
1356             //NSLog(@"Registered server with name: %@", svrName);
1358             // TODO: Set request/reply time-outs to something else?
1359             //
1360             // Don't wait for requests (time-out means that the message is
1361             // dropped).
1362             [svrConn setRequestTimeout:0];
1363             //[svrConn setReplyTimeout:MMReplyTimeout];
1364             [svrConn setRootObject:self];
1366             char_u *s = (char_u*)[svrName UTF8String];
1367 #ifdef FEAT_MBYTE
1368             s = CONVERT_FROM_UTF8(s);
1369 #endif
1370             // NOTE: 'serverName' is a global variable
1371             serverName = vim_strsave(s);
1372 #ifdef FEAT_MBYTE
1373             CONVERT_FROM_UTF8_FREE(s);
1374 #endif
1375 #ifdef FEAT_EVAL
1376             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1377 #endif
1378 #ifdef FEAT_TITLE
1379             need_maketitle = TRUE;
1380 #endif
1381             [self queueMessage:SetServerNameMsgID data:
1382                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1383             break;
1384         }
1386         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1387     }
1390 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1391                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1392               silent:(BOOL)silent
1394     // NOTE: If 'name' equals 'serverName' then the request is local (client
1395     // and server are the same).  This case is not handled separately, so a
1396     // connection will be set up anyway (this simplifies the code).
1398     NSConnection *conn = [self connectionForServerName:name];
1399     if (!conn) {
1400         if (!silent) {
1401             char_u *s = (char_u*)[name UTF8String];
1402 #ifdef FEAT_MBYTE
1403             s = CONVERT_FROM_UTF8(s);
1404 #endif
1405             EMSG2(_(e_noserver), s);
1406 #ifdef FEAT_MBYTE
1407             CONVERT_FROM_UTF8_FREE(s);
1408 #endif
1409         }
1410         return NO;
1411     }
1413     if (port) {
1414         // HACK! Assume connection uses mach ports.
1415         *port = [(NSMachPort*)[conn sendPort] machPort];
1416     }
1418     id proxy = [conn rootProxy];
1419     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1421     @try {
1422         if (expr) {
1423             NSString *eval = [proxy evaluateExpression:string client:self];
1424             if (reply) {
1425                 if (eval) {
1426                     char_u *r = (char_u*)[eval UTF8String];
1427 #ifdef FEAT_MBYTE
1428                     r = CONVERT_FROM_UTF8(r);
1429 #endif
1430                     *reply = vim_strsave(r);
1431 #ifdef FEAT_MBYTE
1432                     CONVERT_FROM_UTF8_FREE(r);
1433 #endif
1434                 } else {
1435                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1436                 }
1437             }
1439             if (!eval)
1440                 return NO;
1441         } else {
1442             [proxy addInput:string client:self];
1443         }
1444     }
1445     @catch (NSException *e) {
1446         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1447         return NO;
1448     }
1450     return YES;
1453 - (NSArray *)serverList
1455     NSArray *list = nil;
1457     if ([self connection]) {
1458         id proxy = [connection rootProxy];
1459         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1461         @try {
1462             list = [proxy serverList];
1463         }
1464         @catch (NSException *e) {
1465             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1466         }
1467     } else {
1468         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1469     }
1471     return list;
1474 - (NSString *)peekForReplyOnPort:(int)port
1476     //NSLog(@"%s%d", _cmd, port);
1478     NSNumber *key = [NSNumber numberWithInt:port];
1479     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1480     if (replies && [replies count]) {
1481         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1482         //        [replies objectAtIndex:0]);
1483         return [replies objectAtIndex:0];
1484     }
1486     //NSLog(@"    No replies");
1487     return nil;
1490 - (NSString *)waitForReplyOnPort:(int)port
1492     //NSLog(@"%s%d", _cmd, port);
1493     
1494     NSConnection *conn = [self connectionForServerPort:port];
1495     if (!conn)
1496         return nil;
1498     NSNumber *key = [NSNumber numberWithInt:port];
1499     NSMutableArray *replies = nil;
1500     NSString *reply = nil;
1502     // Wait for reply as long as the connection to the server is valid (unless
1503     // user interrupts wait with Ctrl-C).
1504     while (!got_int && [conn isValid] &&
1505             !(replies = [serverReplyDict objectForKey:key])) {
1506         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1507                                  beforeDate:[NSDate distantFuture]];
1508     }
1510     if (replies) {
1511         if ([replies count] > 0) {
1512             reply = [[replies objectAtIndex:0] retain];
1513             //NSLog(@"    Got reply: %@", reply);
1514             [replies removeObjectAtIndex:0];
1515             [reply autorelease];
1516         }
1518         if ([replies count] == 0)
1519             [serverReplyDict removeObjectForKey:key];
1520     }
1522     return reply;
1525 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1527     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1528     if (client) {
1529         @try {
1530             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1531             [client addReply:reply server:self];
1532             return YES;
1533         }
1534         @catch (NSException *e) {
1535             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1536         }
1537     } else {
1538         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1539     }
1541     return NO;
1544 @end // MMBackend
1548 @implementation MMBackend (Private)
1550 - (void)processInputQueue
1552     // NOTE: One of the input events may cause this method to be called
1553     // recursively, so copy the input queue to a local variable and clear it
1554     // before starting to process input events (otherwise we could get stuck in
1555     // an endless loop).
1556     NSArray *q = [inputQueue copy];
1557     unsigned i, count = [q count];
1559     [inputQueue removeAllObjects];
1561     for (i = 0; i < count-1; i += 2) {
1562         int msgid = [[q objectAtIndex:i] intValue];
1563         id data = [q objectAtIndex:i+1];
1564         if ([data isEqual:[NSNull null]])
1565             data = nil;
1567         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1568         [self handleInputEvent:msgid data:data];
1569     }
1571     [q release];
1572     //NSLog(@"Clear input event queue");
1575 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1577     // NOTE: Be careful with what you do in this method.  Ideally, a message
1578     // should be handled by adding something to the input buffer and returning
1579     // immediately.  If you call a Vim function then it should not enter a loop
1580     // waiting for key presses or in any other way block the process.  The
1581     // reason for this being that only one message can be processed at a time,
1582     // so if another message is received while processing, then the new message
1583     // is dropped.  See also the comment in processInput:data:.
1585     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1587     if (SelectTabMsgID == msgid) {
1588         if (!data) return;
1589         const void *bytes = [data bytes];
1590         int idx = *((int*)bytes) + 1;
1591         //NSLog(@"Selecting tab %d", idx);
1592         send_tabline_event(idx);
1593     } else if (CloseTabMsgID == msgid) {
1594         if (!data) return;
1595         const void *bytes = [data bytes];
1596         int idx = *((int*)bytes) + 1;
1597         //NSLog(@"Closing tab %d", idx);
1598         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1599     } else if (AddNewTabMsgID == msgid) {
1600         //NSLog(@"Adding new tab");
1601         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1602     } else if (DraggedTabMsgID == msgid) {
1603         if (!data) return;
1604         const void *bytes = [data bytes];
1605         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1606         // based.
1607         int idx = *((int*)bytes);
1609         tabpage_move(idx);
1610     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) {
1611         if (!data) return;
1612         const void *bytes = [data bytes];
1613         int rows = *((int*)bytes);  bytes += sizeof(int);
1614         int cols = *((int*)bytes);  bytes += sizeof(int);
1616         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1617         // gui_resize_shell(), so we have to manually set the rows and columns
1618         // here.  (MacVim doesn't change the rows and columns to avoid
1619         // inconsistent states between Vim and MacVim.)
1620         [self queueMessage:msgid data:data];
1622         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1623         gui_resize_shell(cols, rows);
1624     } else if (ExecuteMenuMsgID == msgid) {
1625         if (!data) return;
1626         const void *bytes = [data bytes];
1627         int tag = *((int*)bytes);  bytes += sizeof(int);
1629         vimmenu_T *menu = (vimmenu_T*)tag;
1630         // TODO!  Make sure 'menu' is a valid menu pointer!
1631         if (menu) {
1632             gui_menu_cb(menu);
1633         }
1634     } else if (ToggleToolbarMsgID == msgid) {
1635         [self handleToggleToolbar];
1636     } else if (ScrollbarEventMsgID == msgid) {
1637         [self handleScrollbarEvent:data];
1638     } else if (SetFontMsgID == msgid) {
1639         [self handleSetFont:data];
1640     } else if (VimShouldCloseMsgID == msgid) {
1641         gui_shell_closed();
1642     } else if (DropFilesMsgID == msgid) {
1643         [self handleDropFiles:data];
1644     } else if (DropStringMsgID == msgid) {
1645         [self handleDropString:data];
1646     } else if (GotFocusMsgID == msgid) {
1647         if (!gui.in_focus)
1648             [self focusChange:YES];
1649     } else if (LostFocusMsgID == msgid) {
1650         if (gui.in_focus)
1651             [self focusChange:NO];
1652     } else if (SetMouseShapeMsgID == msgid) {
1653         const void *bytes = [data bytes];
1654         int shape = *((int*)bytes);  bytes += sizeof(int);
1655         update_mouseshape(shape);
1656     } else if (ODBEditMsgID == msgid) {
1657         [self handleOdbEdit:data];
1658     } else if (XcodeModMsgID == msgid) {
1659         [self handleXcodeMod:data];
1660     } else if (CloseMsgID == msgid) {
1661         // If in Ex mode, then simply exit Ex mode (^U:vi<CR>).  Otherwise
1662         // try to close one (Vim-)window by going to Normal mode first
1663         // (CTRL-\_CTRL-N) and then sending ":q<CR>", but only if the
1664         // command-line window is not open.  If the command-line window is open
1665         // then we just go back to normal mode (since CTRL-\_CTRL-N closes the
1666         // command-line window).
1667         if (exmode_active) {
1668             // Exit Ex mode
1669             add_to_input_buf((char_u*)"\x15:vi\n", 5);
1670         } else {
1671             // Go to normal mode
1672             add_to_input_buf((char_u*)"\x1c\xe", 2);
1673             if (0 == cmdwin_type) {
1674                 // Command-line window was not open, so :q
1675                 add_to_input_buf((char_u*)":q\n", 3);
1676             }
1677         }
1678     } else {
1679         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1680     }
1683 + (NSDictionary *)specialKeys
1685     static NSDictionary *specialKeys = nil;
1687     if (!specialKeys) {
1688         NSBundle *mainBundle = [NSBundle mainBundle];
1689         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1690                                               ofType:@"plist"];
1691         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1692     }
1694     return specialKeys;
1697 - (void)handleInsertText:(NSData *)data
1699     if (!data) return;
1701     NSString *key = [[NSString alloc] initWithData:data
1702                                           encoding:NSUTF8StringEncoding];
1703     char_u *str = (char_u*)[key UTF8String];
1704     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1706 #ifdef FEAT_MBYTE
1707     char_u *conv_str = NULL;
1708     if (input_conv.vc_type != CONV_NONE) {
1709         conv_str = string_convert(&input_conv, str, &len);
1710         if (conv_str)
1711             str = conv_str;
1712     }
1713 #endif
1715     if (len == 1 && ((str[0] == Ctrl_C && ctrl_c_interrupts)
1716             || (str[0] == intr_char && intr_char != Ctrl_C))) {
1717         trash_input_buf();
1718         got_int = TRUE;
1719     }
1721     for (i = 0; i < len; ++i) {
1722         add_to_input_buf(str+i, 1);
1723         if (CSI == str[i]) {
1724             // NOTE: If the converted string contains the byte CSI, then it
1725             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1726             // won't work.
1727             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1728             add_to_input_buf(extra, 2);
1729         }
1730     }
1732 #ifdef FEAT_MBYTE
1733     if (conv_str)
1734         vim_free(conv_str);
1735 #endif
1736     [key release];
1739 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1741     char_u special[3];
1742     char_u modChars[3];
1743     char_u *chars = (char_u*)[key UTF8String];
1744 #ifdef FEAT_MBYTE
1745     char_u *conv_str = NULL;
1746 #endif
1747     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1749     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1750     // that new keys can easily be added.
1751     NSString *specialString = [[MMBackend specialKeys]
1752             objectForKey:key];
1753     if (specialString && [specialString length] > 1) {
1754         //NSLog(@"special key: %@", specialString);
1755         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1756                 [specialString characterAtIndex:1]);
1758         ikey = simplify_key(ikey, &mods);
1759         if (ikey == CSI)
1760             ikey = K_CSI;
1762         special[0] = CSI;
1763         special[1] = K_SECOND(ikey);
1764         special[2] = K_THIRD(ikey);
1766         chars = special;
1767         length = 3;
1768     } else if (1 == length && TAB == chars[0]) {
1769         // Tab is a trouble child:
1770         // - <Tab> is added to the input buffer as is
1771         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1772         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1773         //   to be converted to utf-8
1774         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1775         // - <C-Tab> is reserved by Mac OS X
1776         // - <D-Tab> is reserved by Mac OS X
1777         chars = special;
1778         special[0] = TAB;
1779         length = 1;
1781         if (mods & MOD_MASK_SHIFT) {
1782             mods &= ~MOD_MASK_SHIFT;
1783             special[0] = CSI;
1784             special[1] = K_SECOND(K_S_TAB);
1785             special[2] = K_THIRD(K_S_TAB);
1786             length = 3;
1787         } else if (mods & MOD_MASK_ALT) {
1788             int mtab = 0x80 | TAB;
1789 #ifdef FEAT_MBYTE
1790             if (enc_utf8) {
1791                 // Convert to utf-8
1792                 special[0] = (mtab >> 6) + 0xc0;
1793                 special[1] = mtab & 0xbf;
1794                 length = 2;
1795             } else
1796 #endif
1797             {
1798                 special[0] = mtab;
1799                 length = 1;
1800             }
1801             mods &= ~MOD_MASK_ALT;
1802         }
1803     } else if (length > 0) {
1804         unichar c = [key characterAtIndex:0];
1806         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1807         //        [key characterAtIndex:0], mods);
1809         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1810                 || (c == intr_char && intr_char != Ctrl_C))) {
1811             trash_input_buf();
1812             got_int = TRUE;
1813         }
1815         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1816         // cleared since they are already added to the key by the AppKit.
1817         // Unfortunately, the only way to deal with when to clear the modifiers
1818         // or not seems to be to have hard-wired rules like this.
1819         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1820                     || 0x9 == c || 0xd == c || ESC == c) ) {
1821             mods &= ~MOD_MASK_SHIFT;
1822             mods &= ~MOD_MASK_CTRL;
1823             //NSLog(@"clear shift ctrl");
1824         }
1826         // HACK!  All Option+key presses go via 'insert text' messages, except
1827         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1828         // not work to map to it.
1829         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1830             //NSLog(@"clear alt");
1831             mods &= ~MOD_MASK_ALT;
1832         }
1834 #ifdef FEAT_MBYTE
1835         if (input_conv.vc_type != CONV_NONE) {
1836             conv_str = string_convert(&input_conv, chars, &length);
1837             if (conv_str)
1838                 chars = conv_str;
1839         }
1840 #endif
1841     }
1843     if (chars && length > 0) {
1844         if (mods) {
1845             //NSLog(@"adding mods: %d", mods);
1846             modChars[0] = CSI;
1847             modChars[1] = KS_MODIFIER;
1848             modChars[2] = mods;
1849             add_to_input_buf(modChars, 3);
1850         }
1852         //NSLog(@"add to input buf: 0x%x", chars[0]);
1853         // TODO: Check for CSI bytes?
1854         add_to_input_buf(chars, length);
1855     }
1857 #ifdef FEAT_MBYTE
1858     if (conv_str)
1859         vim_free(conv_str);
1860 #endif
1863 - (void)queueMessage:(int)msgid data:(NSData *)data
1865     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1866     if (data)
1867         [outputQueue addObject:data];
1868     else
1869         [outputQueue addObject:[NSData data]];
1872 - (void)connectionDidDie:(NSNotification *)notification
1874     // If the main connection to MacVim is lost this means that MacVim was
1875     // either quit (by the user chosing Quit on the MacVim menu), or it has
1876     // crashed.  In the former case the flag 'isTerminating' is set and we then
1877     // quit cleanly; in the latter case we make sure the swap files are left
1878     // for recovery.
1880     //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1881     if (isTerminating)
1882         getout(0);
1883     else
1884         getout_preserve_modified(1);
1887 - (void)blinkTimerFired:(NSTimer *)timer
1889     NSTimeInterval timeInterval = 0;
1891     [blinkTimer release];
1892     blinkTimer = nil;
1894     if (MMBlinkStateOn == blinkState) {
1895         gui_undraw_cursor();
1896         blinkState = MMBlinkStateOff;
1897         timeInterval = blinkOffInterval;
1898     } else if (MMBlinkStateOff == blinkState) {
1899         gui_update_cursor(TRUE, FALSE);
1900         blinkState = MMBlinkStateOn;
1901         timeInterval = blinkOnInterval;
1902     }
1904     if (timeInterval > 0) {
1905         blinkTimer = 
1906             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1907                                             selector:@selector(blinkTimerFired:)
1908                                             userInfo:nil repeats:NO] retain];
1909         [self flushQueue:YES];
1910     }
1913 - (void)focusChange:(BOOL)on
1915     gui_focus_change(on);
1918 - (void)handleToggleToolbar
1920     // If 'go' contains 'T', then remove it, else add it.
1922     char_u go[sizeof(GO_ALL)+2];
1923     char_u *p;
1924     int len;
1926     STRCPY(go, p_go);
1927     p = vim_strchr(go, GO_TOOLBAR);
1928     len = STRLEN(go);
1930     if (p != NULL) {
1931         char_u *end = go + len;
1932         while (p < end) {
1933             p[0] = p[1];
1934             ++p;
1935         }
1936     } else {
1937         go[len] = GO_TOOLBAR;
1938         go[len+1] = NUL;
1939     }
1941     set_option_value((char_u*)"guioptions", 0, go, 0);
1943     // Force screen redraw (does it have to be this complicated?).
1944     redraw_all_later(CLEAR);
1945     update_screen(NOT_VALID);
1946     setcursor();
1947     out_flush();
1948     gui_update_cursor(FALSE, FALSE);
1949     gui_mch_flush();
1952 - (void)handleScrollbarEvent:(NSData *)data
1954     if (!data) return;
1956     const void *bytes = [data bytes];
1957     long ident = *((long*)bytes);  bytes += sizeof(long);
1958     int hitPart = *((int*)bytes);  bytes += sizeof(int);
1959     float fval = *((float*)bytes);  bytes += sizeof(float);
1960     scrollbar_T *sb = gui_find_scrollbar(ident);
1962     if (sb) {
1963         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1964         long value = sb_info->value;
1965         long size = sb_info->size;
1966         long max = sb_info->max;
1967         BOOL isStillDragging = NO;
1968         BOOL updateKnob = YES;
1970         switch (hitPart) {
1971         case NSScrollerDecrementPage:
1972             value -= (size > 2 ? size - 2 : 1);
1973             break;
1974         case NSScrollerIncrementPage:
1975             value += (size > 2 ? size - 2 : 1);
1976             break;
1977         case NSScrollerDecrementLine:
1978             --value;
1979             break;
1980         case NSScrollerIncrementLine:
1981             ++value;
1982             break;
1983         case NSScrollerKnob:
1984             isStillDragging = YES;
1985             // fall through ...
1986         case NSScrollerKnobSlot:
1987             value = (long)(fval * (max - size + 1));
1988             // fall through ...
1989         default:
1990             updateKnob = NO;
1991             break;
1992         }
1994         //NSLog(@"value %d -> %d", sb_info->value, value);
1995         gui_drag_scrollbar(sb, value, isStillDragging);
1997         if (updateKnob) {
1998             // Dragging the knob or option+clicking automatically updates
1999             // the knob position (on the actual NSScroller), so we only
2000             // need to set the knob position in the other cases.
2001             if (sb->wp) {
2002                 // Update both the left&right vertical scrollbars.
2003                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2004                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2005                 [self setScrollbarThumbValue:value size:size max:max
2006                                   identifier:identLeft];
2007                 [self setScrollbarThumbValue:value size:size max:max
2008                                   identifier:identRight];
2009             } else {
2010                 // Update the horizontal scrollbar.
2011                 [self setScrollbarThumbValue:value size:size max:max
2012                                   identifier:ident];
2013             }
2014         }
2015     }
2018 - (void)handleSetFont:(NSData *)data
2020     if (!data) return;
2022     const void *bytes = [data bytes];
2023     float pointSize = *((float*)bytes);  bytes += sizeof(float);
2024     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2025     bytes += sizeof(unsigned);  // len not used
2027     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2028     [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
2029     char_u *s = (char_u*)[name UTF8String];
2031 #ifdef FEAT_MBYTE
2032     s = CONVERT_FROM_UTF8(s);
2033 #endif
2035     set_option_value((char_u*)"guifont", 0, s, 0);
2037 #ifdef FEAT_MBYTE
2038     CONVERT_FROM_UTF8_FREE(s);
2039 #endif
2041     // Force screen redraw (does it have to be this complicated?).
2042     redraw_all_later(CLEAR);
2043     update_screen(NOT_VALID);
2044     setcursor();
2045     out_flush();
2046     gui_update_cursor(FALSE, FALSE);
2047     gui_mch_flush();
2050 - (void)handleDropFiles:(NSData *)data
2052     // TODO: Get rid of this method; instead use Vim script directly.  At the
2053     // moment I know how to do this to open files in tabs, but I'm not sure how
2054     // to add the filenames to the command line when in command line mode.
2056     if (!data) return;
2058 #ifdef FEAT_DND
2059     const void *bytes = [data bytes];
2060     const void *end = [data bytes] + [data length];
2061     BOOL forceOpen = *((BOOL*)bytes);  bytes += sizeof(BOOL);
2062     int n = *((int*)bytes);  bytes += sizeof(int);
2064     if (!forceOpen && (State & CMDLINE)) {
2065         // HACK!  If Vim is in command line mode then the files names
2066         // should be added to the command line, instead of opening the
2067         // files in tabs (unless forceOpen is set).  This is taken care of by
2068         // gui_handle_drop().
2069         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2070         if (fnames) {
2071             int i = 0;
2072             while (bytes < end && i < n) {
2073                 int len = *((int*)bytes);  bytes += sizeof(int);
2074                 char_u *s = (char_u*)bytes;
2075 #ifdef FEAT_MBYTE
2076                 s = CONVERT_FROM_UTF8(s);
2077 #endif
2078                 fnames[i++] = vim_strsave(s);
2079 #ifdef FEAT_MBYTE
2080                 CONVERT_FROM_UTF8_FREE(s);
2081 #endif
2082                 bytes += len;
2083             }
2085             // NOTE!  This function will free 'fnames'.
2086             // HACK!  It is assumed that the 'x' and 'y' arguments are
2087             // unused when in command line mode.
2088             gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
2089         }
2090     } else {
2091         // HACK!  I'm not sure how to get Vim to open a list of files in
2092         // tabs, so instead I create a ':tab drop' command with all the
2093         // files to open and execute it.
2094         NSMutableString *cmd = [NSMutableString stringWithString:@":tab drop"];
2096         int i;
2097         for (i = 0; i < n && bytes < end; ++i) {
2098             int len = *((int*)bytes);  bytes += sizeof(int);
2099             NSString *file = [NSString stringWithUTF8String:bytes];
2100             file = [file stringByEscapingSpecialFilenameCharacters];
2101             bytes += len;
2103             [cmd appendString:@" "];
2104             [cmd appendString:file];
2105         }
2107         // By going to the last tabpage we ensure that the new tabs will
2108         // appear last (if this call is left out, the taborder becomes
2109         // messy).
2110         goto_tabpage(9999);
2112         char_u *s = (char_u*)[cmd UTF8String];
2113 #ifdef FEAT_MBYTE
2114         s = CONVERT_FROM_UTF8(s);
2115 #endif
2116         do_cmdline_cmd(s);
2117 #ifdef FEAT_MBYTE
2118         CONVERT_FROM_UTF8_FREE(s);
2119 #endif
2121         // Force screen redraw (does it have to be this complicated?).
2122         // (This code was taken from the end of gui_handle_drop().)
2123         update_screen(NOT_VALID);
2124         setcursor();
2125         out_flush();
2126         gui_update_cursor(FALSE, FALSE);
2127         maketitle();
2128         gui_mch_flush();
2129     }
2130 #endif // FEAT_DND
2133 - (void)handleDropString:(NSData *)data
2135     if (!data) return;
2137 #ifdef FEAT_DND
2138     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2139     const void *bytes = [data bytes];
2140     int len = *((int*)bytes);  bytes += sizeof(int);
2141     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2143     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2144     NSRange range = { 0, [string length] };
2145     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2146                                          withString:@"\x0a" options:0
2147                                               range:range];
2148     if (0 == n) {
2149         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2150                                        options:0 range:range];
2151     }
2153     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2154     char_u *s = (char_u*)[string UTF8String];
2155 #ifdef FEAT_MBYTE
2156     if (input_conv.vc_type != CONV_NONE)
2157         s = string_convert(&input_conv, s, &len);
2158 #endif
2159     dnd_yank_drag_data(s, len);
2160 #ifdef FEAT_MBYTE
2161     if (input_conv.vc_type != CONV_NONE)
2162         vim_free(s);
2163 #endif
2164     add_to_input_buf(dropkey, sizeof(dropkey));
2165 #endif // FEAT_DND
2168 - (void)handleOdbEdit:(NSData *)data
2170 #ifdef FEAT_ODB_EDITOR
2171     const void *bytes = [data bytes];
2173     OSType serverID = *((OSType*)bytes);  bytes += sizeof(OSType);
2175     char_u *path = NULL;
2176     int pathLen = *((int*)bytes);  bytes += sizeof(int);
2177     if (pathLen > 0) {
2178         path = (char_u*)bytes;
2179         bytes += pathLen;
2180 #ifdef FEAT_MBYTE
2181         path = CONVERT_FROM_UTF8(path);
2182 #endif
2183     }
2185     NSAppleEventDescriptor *token = nil;
2186     DescType tokenType = *((DescType*)bytes);  bytes += sizeof(DescType);
2187     int descLen = *((int*)bytes);  bytes += sizeof(int);
2188     if (descLen > 0) {
2189         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2190                                                                bytes:bytes
2191                                                               length:descLen];
2192         bytes += descLen;
2193     }
2195     unsigned i, numFiles = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2196     for (i = 0; i < numFiles; ++i) {
2197         int len = *((int*)bytes);  bytes += sizeof(int);
2198         char_u *filename = (char_u*)bytes;
2199 #ifdef FEAT_MBYTE
2200         filename = CONVERT_FROM_UTF8(filename);
2201 #endif
2202         buf_T *buf = buflist_findname(filename);
2203         if (buf) {
2204             if (buf->b_odb_token) {
2205                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2206                 buf->b_odb_token = NULL;
2207             }
2209             if (buf->b_odb_fname) {
2210                 vim_free(buf->b_odb_fname);
2211                 buf->b_odb_fname = NULL;
2212             }
2214             buf->b_odb_server_id = serverID;
2216             if (token)
2217                 buf->b_odb_token = [token retain];
2218             if (path)
2219                 buf->b_odb_fname = vim_strsave(path);
2220         } else {
2221             NSLog(@"WARNING: Could not find buffer '%s' for ODB editing.",
2222                     filename);
2223         }
2225 #ifdef FEAT_MBYTE
2226         CONVERT_FROM_UTF8_FREE(filename);
2227 #endif
2228         bytes += len;
2229     }
2230 #ifdef FEAT_MBYTE
2231     CONVERT_FROM_UTF8_FREE(path);
2232 #endif
2233 #endif // FEAT_ODB_EDITOR
2236 - (void)handleXcodeMod:(NSData *)data
2238 #if 0
2239     const void *bytes = [data bytes];
2240     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2241     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2242     if (0 == len)
2243         return;
2245     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2246             descriptorWithDescriptorType:type
2247                                    bytes:bytes
2248                                   length:len];
2249 #endif
2252 - (BOOL)checkForModifiedBuffers
2254     buf_T *buf;
2255     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2256         if (bufIsChanged(buf)) {
2257             return YES;
2258         }
2259     }
2261     return NO;
2264 - (void)addInput:(NSString *)input
2266     char_u *s = (char_u*)[input UTF8String];
2268 #ifdef FEAT_MBYTE
2269     s = CONVERT_FROM_UTF8(s);
2270 #endif
2272     server_to_input_buf(s);
2274 #ifdef FEAT_MBYTE
2275     CONVERT_FROM_UTF8_FREE(s);
2276 #endif
2279 @end // MMBackend (Private)
2284 @implementation MMBackend (ClientServer)
2286 - (NSString *)connectionNameFromServerName:(NSString *)name
2288     NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2290     return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2291         lowercaseString];
2294 - (NSConnection *)connectionForServerName:(NSString *)name
2296     // TODO: Try 'name%d' if 'name' fails.
2297     NSString *connName = [self connectionNameFromServerName:name];
2298     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2300     if (!svrConn) {
2301         svrConn = [NSConnection connectionWithRegisteredName:connName
2302                                                            host:nil];
2303         // Try alternate server...
2304         if (!svrConn && alternateServerName) {
2305             //NSLog(@"  trying to connect to alternate server: %@",
2306             //        alternateServerName);
2307             connName = [self connectionNameFromServerName:alternateServerName];
2308             svrConn = [NSConnection connectionWithRegisteredName:connName
2309                                                             host:nil];
2310         }
2312         // Try looking for alternate servers...
2313         if (!svrConn) {
2314             //NSLog(@"  looking for alternate servers...");
2315             NSString *alt = [self alternateServerNameForName:name];
2316             if (alt != alternateServerName) {
2317                 //NSLog(@"  found alternate server: %@", string);
2318                 [alternateServerName release];
2319                 alternateServerName = [alt copy];
2320             }
2321         }
2323         // Try alternate server again...
2324         if (!svrConn && alternateServerName) {
2325             //NSLog(@"  trying to connect to alternate server: %@",
2326             //        alternateServerName);
2327             connName = [self connectionNameFromServerName:alternateServerName];
2328             svrConn = [NSConnection connectionWithRegisteredName:connName
2329                                                             host:nil];
2330         }
2332         if (svrConn) {
2333             [connectionNameDict setObject:svrConn forKey:connName];
2335             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2336             [[NSNotificationCenter defaultCenter] addObserver:self
2337                     selector:@selector(serverConnectionDidDie:)
2338                         name:NSConnectionDidDieNotification object:svrConn];
2339         }
2340     }
2342     return svrConn;
2345 - (NSConnection *)connectionForServerPort:(int)port
2347     NSConnection *conn;
2348     NSEnumerator *e = [connectionNameDict objectEnumerator];
2350     while ((conn = [e nextObject])) {
2351         // HACK! Assume connection uses mach ports.
2352         if (port == [(NSMachPort*)[conn sendPort] machPort])
2353             return conn;
2354     }
2356     return nil;
2359 - (void)serverConnectionDidDie:(NSNotification *)notification
2361     //NSLog(@"%s%@", _cmd, notification);
2363     NSConnection *svrConn = [notification object];
2365     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2366     [[NSNotificationCenter defaultCenter]
2367             removeObserver:self
2368                       name:NSConnectionDidDieNotification
2369                     object:svrConn];
2371     [connectionNameDict removeObjectsForKeys:
2372         [connectionNameDict allKeysForObject:svrConn]];
2374     // HACK! Assume connection uses mach ports.
2375     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2376     NSNumber *key = [NSNumber numberWithInt:port];
2378     [clientProxyDict removeObjectForKey:key];
2379     [serverReplyDict removeObjectForKey:key];
2382 - (void)addClient:(NSDistantObject *)client
2384     NSConnection *conn = [client connectionForProxy];
2385     // HACK! Assume connection uses mach ports.
2386     int port = [(NSMachPort*)[conn sendPort] machPort];
2387     NSNumber *key = [NSNumber numberWithInt:port];
2389     if (![clientProxyDict objectForKey:key]) {
2390         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2391         [clientProxyDict setObject:client forKey:key];
2392     }
2394     // NOTE: 'clientWindow' is a global variable which is used by <client>
2395     clientWindow = port;
2398 - (NSString *)alternateServerNameForName:(NSString *)name
2400     if (!(name && [name length] > 0))
2401         return nil;
2403     // Only look for alternates if 'name' doesn't end in a digit.
2404     unichar lastChar = [name characterAtIndex:[name length]-1];
2405     if (lastChar >= '0' && lastChar <= '9')
2406         return nil;
2408     // Look for alternates among all current servers.
2409     NSArray *list = [self serverList];
2410     if (!(list && [list count] > 0))
2411         return nil;
2413     // Filter out servers starting with 'name' and ending with a number. The
2414     // (?i) pattern ensures that the match is case insensitive.
2415     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2416     NSPredicate *pred = [NSPredicate predicateWithFormat:
2417             @"SELF MATCHES %@", pat];
2418     list = [list filteredArrayUsingPredicate:pred];
2419     if ([list count] > 0) {
2420         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2421         return [list objectAtIndex:0];
2422     }
2424     return nil;
2427 @end // MMBackend (ClientServer)
2432 @implementation NSString (MMServerNameCompare)
2433 - (NSComparisonResult)serverNameCompare:(NSString *)string
2435     return [self compare:string
2436                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2438 @end
2443 static int eventModifierFlagsToVimModMask(int modifierFlags)
2445     int modMask = 0;
2447     if (modifierFlags & NSShiftKeyMask)
2448         modMask |= MOD_MASK_SHIFT;
2449     if (modifierFlags & NSControlKeyMask)
2450         modMask |= MOD_MASK_CTRL;
2451     if (modifierFlags & NSAlternateKeyMask)
2452         modMask |= MOD_MASK_ALT;
2453     if (modifierFlags & NSCommandKeyMask)
2454         modMask |= MOD_MASK_CMD;
2456     return modMask;
2459 static int vimModMaskToEventModifierFlags(int mods)
2461     int flags = 0;
2463     if (mods & MOD_MASK_SHIFT)
2464         flags |= NSShiftKeyMask;
2465     if (mods & MOD_MASK_CTRL)
2466         flags |= NSControlKeyMask;
2467     if (mods & MOD_MASK_ALT)
2468         flags |= NSAlternateKeyMask;
2469     if (mods & MOD_MASK_CMD)
2470         flags |= NSCommandKeyMask;
2472     return flags;
2475 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2477     int modMask = 0;
2479     if (modifierFlags & NSShiftKeyMask)
2480         modMask |= MOUSE_SHIFT;
2481     if (modifierFlags & NSControlKeyMask)
2482         modMask |= MOUSE_CTRL;
2483     if (modifierFlags & NSAlternateKeyMask)
2484         modMask |= MOUSE_ALT;
2486     return modMask;
2489 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2491     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2493     return (buttonNumber >= 0 && buttonNumber < 3)
2494             ? mouseButton[buttonNumber] : -1;
2497 static int specialKeyToNSKey(int key)
2499     if (!IS_SPECIAL(key))
2500         return key;
2502     static struct {
2503         int special;
2504         int nskey;
2505     } sp2ns[] = {
2506         { K_UP, NSUpArrowFunctionKey },
2507         { K_DOWN, NSDownArrowFunctionKey },
2508         { K_LEFT, NSLeftArrowFunctionKey },
2509         { K_RIGHT, NSRightArrowFunctionKey },
2510         { K_F1, NSF1FunctionKey },
2511         { K_F2, NSF2FunctionKey },
2512         { K_F3, NSF3FunctionKey },
2513         { K_F4, NSF4FunctionKey },
2514         { K_F5, NSF5FunctionKey },
2515         { K_F6, NSF6FunctionKey },
2516         { K_F7, NSF7FunctionKey },
2517         { K_F8, NSF8FunctionKey },
2518         { K_F9, NSF9FunctionKey },
2519         { K_F10, NSF10FunctionKey },
2520         { K_F11, NSF11FunctionKey },
2521         { K_F12, NSF12FunctionKey },
2522         { K_F13, NSF13FunctionKey },
2523         { K_F14, NSF14FunctionKey },
2524         { K_F15, NSF15FunctionKey },
2525         { K_F16, NSF16FunctionKey },
2526         { K_F17, NSF17FunctionKey },
2527         { K_F18, NSF18FunctionKey },
2528         { K_F19, NSF19FunctionKey },
2529         { K_F20, NSF20FunctionKey },
2530         { K_F21, NSF21FunctionKey },
2531         { K_F22, NSF22FunctionKey },
2532         { K_F23, NSF23FunctionKey },
2533         { K_F24, NSF24FunctionKey },
2534         { K_F25, NSF25FunctionKey },
2535         { K_F26, NSF26FunctionKey },
2536         { K_F27, NSF27FunctionKey },
2537         { K_F28, NSF28FunctionKey },
2538         { K_F29, NSF29FunctionKey },
2539         { K_F30, NSF30FunctionKey },
2540         { K_F31, NSF31FunctionKey },
2541         { K_F32, NSF32FunctionKey },
2542         { K_F33, NSF33FunctionKey },
2543         { K_F34, NSF34FunctionKey },
2544         { K_F35, NSF35FunctionKey },
2545         { K_DEL, NSBackspaceCharacter },
2546         { K_BS, NSDeleteCharacter },
2547         { K_HOME, NSHomeFunctionKey },
2548         { K_END, NSEndFunctionKey },
2549         { K_PAGEUP, NSPageUpFunctionKey },
2550         { K_PAGEDOWN, NSPageDownFunctionKey }
2551     };
2553     int i;
2554     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2555         if (sp2ns[i].special == key)
2556             return sp2ns[i].nskey;
2557     }
2559     return 0;