Send cursor position on each batch draw
[MacVim.git] / src / MacVim / MMBackend.m
blob5fea7e73d356624d3cc31dc87d270fe24b4dcecc
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
11  * MMBackend
12  *
13  * MMBackend communicates with the frontend (MacVim).  It maintains a queue of
14  * output which is flushed to the frontend under controlled circumstances (so
15  * as to maintain a steady framerate).  Input from the frontend is also handled
16  * here.
17  *
18  * The frontend communicates with the backend via the MMBackendProtocol.  In
19  * particular, input is sent to the backend via processInput:data: and Vim
20  * state can be queried from the frontend with evaluateExpression:.
21  *
22  * It is very important to realize that all state is held by the backend, the
23  * frontend must either ask for state [MMBackend evaluateExpression:] or wait
24  * for the backend to update [MMVimController processCommandQueue:].
25  *
26  * The client/server functionality of Vim is handled by the backend.  It sets
27  * up a named NSConnection to which other Vim processes can connect.
28  */
30 #import "MMBackend.h"
34 // NOTE: Colors in MMBackend are stored as unsigned ints on the form 0xaarrggbb
35 // whereas colors in Vim are int without the alpha component.  Also note that
36 // 'transp' is assumed to be a value between 0 and 100.
37 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
38 #define MM_COLOR_WITH_TRANSP(col,transp) \
39     ((unsigned)( ((col)&0xffffff) \
40         | ((((unsigned)((((100-(transp))*255)/100)+.5f))&0xff)<<24) ))
43 // This constant controls how often the command queue may be flushed.  If it is
44 // too small the app might feel unresponsive; if it is too large there might be
45 // long periods without the screen updating (e.g. when sourcing a large session
46 // file).  (The unit is seconds.)
47 static float MMFlushTimeoutInterval = 0.1f;
48 static int MMFlushQueueLenHint = 80*40;
50 static unsigned MMServerMax = 1000;
52 // TODO: Move to separate file.
53 static int eventModifierFlagsToVimModMask(int modifierFlags);
54 static int vimModMaskToEventModifierFlags(int mods);
55 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
56 static int eventButtonNumberToVimMouseButton(int buttonNumber);
57 static int specialKeyToNSKey(int key);
59 enum {
60     MMBlinkStateNone = 0,
61     MMBlinkStateOn,
62     MMBlinkStateOff
65 static NSString *MMSymlinkWarningString =
66     @"\n\n\tMost likely this is because you have symlinked directly to\n"
67      "\tthe Vim binary, which Cocoa does not allow.  Please use an\n"
68      "\talias or the mvim shell script instead.  If you have not used\n"
69      "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
73 @interface NSString (MMServerNameCompare)
74 - (NSComparisonResult)serverNameCompare:(NSString *)string;
75 @end
79 @interface MMBackend (Private)
80 - (void)processInputQueue;
81 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
82 + (NSDictionary *)specialKeys;
83 - (void)handleInsertText:(NSData *)data;
84 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
85 - (void)queueMessage:(int)msgid data:(NSData *)data;
86 - (void)connectionDidDie:(NSNotification *)notification;
87 - (void)blinkTimerFired:(NSTimer *)timer;
88 - (void)focusChange:(BOOL)on;
89 - (void)handleToggleToolbar;
90 - (void)handleScrollbarEvent:(NSData *)data;
91 - (void)handleSetFont:(NSData *)data;
92 - (void)handleDropFiles:(NSData *)data;
93 - (void)handleDropString:(NSData *)data;
94 - (void)handleOdbEdit:(NSData *)data;
95 - (void)handleXcodeMod:(NSData *)data;
96 - (BOOL)checkForModifiedBuffers;
97 - (void)addInput:(NSString *)input;
98 @end
102 @interface MMBackend (ClientServer)
103 - (NSString *)connectionNameFromServerName:(NSString *)name;
104 - (NSConnection *)connectionForServerName:(NSString *)name;
105 - (NSConnection *)connectionForServerPort:(int)port;
106 - (void)serverConnectionDidDie:(NSNotification *)notification;
107 - (void)addClient:(NSDistantObject *)client;
108 - (NSString *)alternateServerNameForName:(NSString *)name;
109 @end
113 @implementation MMBackend
115 + (MMBackend *)sharedInstance
117     static MMBackend *singleton = nil;
118     return singleton ? singleton : (singleton = [MMBackend new]);
121 - (id)init
123     self = [super init];
124     if (!self) return nil;
126     fontContainerRef = loadFonts();
128     outputQueue = [[NSMutableArray alloc] init];
129     inputQueue = [[NSMutableArray alloc] init];
130     drawData = [[NSMutableData alloc] initWithCapacity:1024];
131     connectionNameDict = [[NSMutableDictionary alloc] init];
132     clientProxyDict = [[NSMutableDictionary alloc] init];
133     serverReplyDict = [[NSMutableDictionary alloc] init];
135     NSBundle *mainBundle = [NSBundle mainBundle];
136     NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
137     if (path)
138         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
140     path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
141     if (path)
142         sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
143             retain];
145     if (!(colorDict && sysColorDict))
146         NSLog(@"ERROR: Failed to load color dictionaries.%@",
147                 MMSymlinkWarningString);
149     return self;
152 - (void)dealloc
154     //NSLog(@"%@ %s", [self className], _cmd);
155     [[NSNotificationCenter defaultCenter] removeObserver:self];
157     [oldWideFont release];  oldWideFont = nil;
158     [blinkTimer release];  blinkTimer = nil;
159     [alternateServerName release];  alternateServerName = nil;
160     [serverReplyDict release];  serverReplyDict = nil;
161     [clientProxyDict release];  clientProxyDict = nil;
162     [connectionNameDict release];  connectionNameDict = nil;
163     [inputQueue release];  inputQueue = nil;
164     [outputQueue release];  outputQueue = nil;
165     [drawData release];  drawData = nil;
166     [frontendProxy release];  frontendProxy = nil;
167     [connection release];  connection = nil;
168     [sysColorDict release];  sysColorDict = nil;
169     [colorDict release];  colorDict = nil;
171     [super dealloc];
174 - (void)setBackgroundColor:(int)color
176     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
179 - (void)setForegroundColor:(int)color
181     foregroundColor = MM_COLOR(color);
184 - (void)setSpecialColor:(int)color
186     specialColor = MM_COLOR(color);
189 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
191     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
192     defaultForegroundColor = MM_COLOR(fg);
194     NSMutableData *data = [NSMutableData data];
196     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
197     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
199     [self queueMessage:SetDefaultColorsMsgID data:data];
202 - (NSConnection *)connection
204     if (!connection) {
205         // NOTE!  If the name of the connection changes here it must also be
206         // updated in MMAppController.m.
207         NSString *name = [NSString stringWithFormat:@"%@-connection",
208                [[NSBundle mainBundle] bundleIdentifier]];
210         connection = [NSConnection connectionWithRegisteredName:name host:nil];
211         [connection retain];
212     }
214     // NOTE: 'connection' may be nil here.
215     return connection;
218 - (BOOL)checkin
220     if (![self connection]) {
221         NSBundle *mainBundle = [NSBundle mainBundle];
222 #if 0
223         OSStatus status;
224         FSRef ref;
226         // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
227         // the API to pass Apple Event parameters is broken on 10.4).
228         NSString *path = [mainBundle bundlePath];
229         status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
230         if (noErr == status) {
231             // Pass parameter to the 'Open' Apple Event that tells MacVim not
232             // to open an untitled window.
233             NSAppleEventDescriptor *desc =
234                     [NSAppleEventDescriptor recordDescriptor];
235             [desc setParamDescriptor:
236                     [NSAppleEventDescriptor descriptorWithBoolean:NO]
237                           forKeyword:keyMMUntitledWindow];
239             LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
240                     kLSLaunchDefaults, NULL };
241             status = LSOpenFromRefSpec(&spec, NULL);
242         }
244         if (noErr != status) {
245         NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
246                 path, MMSymlinkWarningString);
247             return NO;
248         }
249 #else
250         // Launch MacVim using NSTask.  For some reason the above code using
251         // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
252         // fails, the dock icon starts bouncing and never stops).  It seems
253         // like rebuilding the Launch Services database takes care of this
254         // problem, but the NSTask way seems more stable so stick with it.
255         //
256         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
257         // that the GUI won't be activated (or raised) so there is a hack in
258         // MMAppController which raises the app when a new window is opened.
259         NSMutableArray *args = [NSMutableArray arrayWithObjects:
260             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
261         NSString *exeName = [[mainBundle infoDictionary]
262                 objectForKey:@"CFBundleExecutable"];
263         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
264         if (!path) {
265             NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
266                     MMSymlinkWarningString);
267             return NO;
268         }
270         [NSTask launchedTaskWithLaunchPath:path arguments:args];
271 #endif
273         // HACK!  Poll the mach bootstrap server until it returns a valid
274         // connection to detect that MacVim has finished launching.  Also set a
275         // time-out date so that we don't get stuck doing this forever.
276         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
277         while (![self connection] &&
278                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
279             [[NSRunLoop currentRunLoop]
280                     runMode:NSDefaultRunLoopMode
281                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
283         // NOTE: [self connection] will set 'connection' as a side-effect.
284         if (!connection) {
285             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
286             return NO;
287         }
288     }
290     id proxy = [connection rootProxy];
291     [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
293     [[NSNotificationCenter defaultCenter] addObserver:self
294             selector:@selector(connectionDidDie:)
295                 name:NSConnectionDidDieNotification object:connection];
297     int pid = [[NSProcessInfo processInfo] processIdentifier];
299     @try {
300         frontendProxy = [proxy connectBackend:self pid:pid];
301     }
302     @catch (NSException *e) {
303         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
304     }
306     if (frontendProxy) {
307         [frontendProxy retain];
308         [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
309     }
311     return connection && frontendProxy;
314 - (BOOL)openVimWindow
316     [self queueMessage:OpenVimWindowMsgID data:nil];
317     return YES;
320 - (void)clearAll
322     int type = ClearAllDrawType;
324     // Any draw commands in queue are effectively obsolete since this clearAll
325     // will negate any effect they have, therefore we may as well clear the
326     // draw queue.
327     [drawData setLength:0];
329     [drawData appendBytes:&type length:sizeof(int)];
332 - (void)clearBlockFromRow:(int)row1 column:(int)col1
333                     toRow:(int)row2 column:(int)col2
335     int type = ClearBlockDrawType;
337     [drawData appendBytes:&type length:sizeof(int)];
339     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
340     [drawData appendBytes:&row1 length:sizeof(int)];
341     [drawData appendBytes:&col1 length:sizeof(int)];
342     [drawData appendBytes:&row2 length:sizeof(int)];
343     [drawData appendBytes:&col2 length:sizeof(int)];
346 - (void)deleteLinesFromRow:(int)row count:(int)count
347               scrollBottom:(int)bottom left:(int)left right:(int)right
349     int type = DeleteLinesDrawType;
351     [drawData appendBytes:&type length:sizeof(int)];
353     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
354     [drawData appendBytes:&row length:sizeof(int)];
355     [drawData appendBytes:&count length:sizeof(int)];
356     [drawData appendBytes:&bottom length:sizeof(int)];
357     [drawData appendBytes:&left length:sizeof(int)];
358     [drawData appendBytes:&right length:sizeof(int)];
361 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
362              cells:(int)cells flags:(int)flags
364     if (len <= 0 || cells <= 0) return;
366     int type = DrawStringDrawType;
368     [drawData appendBytes:&type length:sizeof(int)];
370     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
371     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
372     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
373     [drawData appendBytes:&row length:sizeof(int)];
374     [drawData appendBytes:&col length:sizeof(int)];
375     [drawData appendBytes:&cells length:sizeof(int)];
376     [drawData appendBytes:&flags length:sizeof(int)];
377     [drawData appendBytes:&len length:sizeof(int)];
378     [drawData appendBytes:s length:len];
381 - (void)insertLinesFromRow:(int)row count:(int)count
382               scrollBottom:(int)bottom left:(int)left right:(int)right
384     int type = InsertLinesDrawType;
386     [drawData appendBytes:&type length:sizeof(int)];
388     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
389     [drawData appendBytes:&row length:sizeof(int)];
390     [drawData appendBytes:&count length:sizeof(int)];
391     [drawData appendBytes:&bottom length:sizeof(int)];
392     [drawData appendBytes:&left length:sizeof(int)];
393     [drawData appendBytes:&right length:sizeof(int)];
396 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
397                fraction:(int)percent color:(int)color
399     int type = DrawCursorDrawType;
400     unsigned uc = MM_COLOR(color);
402     [drawData appendBytes:&type length:sizeof(int)];
404     [drawData appendBytes:&uc length:sizeof(unsigned)];
405     [drawData appendBytes:&row length:sizeof(int)];
406     [drawData appendBytes:&col length:sizeof(int)];
407     [drawData appendBytes:&shape length:sizeof(int)];
408     [drawData appendBytes:&percent length:sizeof(int)];
411 - (void)update
413     // Tend to the run loop, returning immediately if there are no events
414     // waiting.
415     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
416                              beforeDate:[NSDate distantPast]];
418 #if 0
419     // Keyboard and mouse input is handled directly, other input is queued and
420     // processed here.  This call may enter a blocking loop.
421     if ([inputQueue count] > 0)
422         [self processInputQueue];
423 #endif
426 - (void)flushQueue:(BOOL)force
428     // NOTE! This method gets called a lot; if we were to flush every time it
429     // got called MacVim would feel unresponsive.  So there is a time out which
430     // ensures that the queue isn't flushed too often.
431     if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
432             < MMFlushTimeoutInterval
433             && [drawData length] < MMFlushQueueLenHint)
434         return;
436     if ([drawData length] > 0) {
437         // HACK!  Detect changes to 'guifontwide'.
438         if (gui.wide_font != (GuiFont)oldWideFont) {
439             [oldWideFont release];
440             oldWideFont = [(NSFont*)gui.wide_font retain];
441             [self setWideFont:oldWideFont];
442         }
444         int type = SetCursorPosDrawType;
445         [drawData appendBytes:&type length:sizeof(type)];
446         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
447         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
449         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
450         [drawData setLength:0];
451     }
453     if ([outputQueue count] > 0) {
454         @try {
455             [frontendProxy processCommandQueue:outputQueue];
456         }
457         @catch (NSException *e) {
458             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
459         }
461         [outputQueue removeAllObjects];
463         [lastFlushDate release];
464         lastFlushDate = [[NSDate date] retain];
465     }
468 - (BOOL)waitForInput:(int)milliseconds
470     //NSLog(@"|ENTER| %s%d",  _cmd, milliseconds);
472     // Only start the run loop if the input queue is empty, otherwise process
473     // the input first so that the input on queue isn't delayed.
474     if ([inputQueue count]) {
475         inputReceived = YES;
476     } else {
477         NSDate *date = milliseconds > 0 ?
478                 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
479                 [NSDate distantFuture];
481         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
482                                  beforeDate:date];
483     }
485     // I know of no way to figure out if the run loop exited because input was
486     // found or because of a time out, so I need to manually indicate when
487     // input was received in processInput:data: and then reset it every time
488     // here.
489     BOOL yn = inputReceived;
490     inputReceived = NO;
492     // Keyboard and mouse input is handled directly, other input is queued and
493     // processed here.  This call may enter a blocking loop.
494     if ([inputQueue count] > 0)
495         [self processInputQueue];
497     //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
498     return yn;
501 - (void)exit
503 #ifdef MAC_CLIENTSERVER
504     // The default connection is used for the client/server code.
505     [[NSConnection defaultConnection] setRootObject:nil];
506     [[NSConnection defaultConnection] invalidate];
507 #endif
509     // By invalidating the NSConnection the MMWindowController immediately
510     // finds out that the connection is down and as a result
511     // [MMWindowController connectionDidDie:] is invoked.
512     //NSLog(@"%@ %s", [self className], _cmd);
513     [[NSNotificationCenter defaultCenter] removeObserver:self];
514     [connection invalidate];
516     if (fontContainerRef) {
517         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
518         fontContainerRef = 0;
519     }
523 - (void)selectTab:(int)index
525     //NSLog(@"%s%d", _cmd, index);
527     index -= 1;
528     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
529     [self queueMessage:SelectTabMsgID data:data];
532 - (void)updateTabBar
534     //NSLog(@"%s", _cmd);
536     NSMutableData *data = [NSMutableData data];
538     int idx = tabpage_index(curtab) - 1;
539     [data appendBytes:&idx length:sizeof(int)];
541     tabpage_T *tp;
542     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
543         // This function puts the label of the tab in the global 'NameBuff'.
544         get_tabline_label(tp, FALSE);
545         char_u *s = NameBuff;
546         int len = STRLEN(s);
547         if (len <= 0) continue;
549 #ifdef FEAT_MBYTE
550         s = CONVERT_TO_UTF8(s);
551 #endif
553         // Count the number of windows in the tabpage.
554         //win_T *wp = tp->tp_firstwin;
555         //int wincount;
556         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
558         //[data appendBytes:&wincount length:sizeof(int)];
559         [data appendBytes:&len length:sizeof(int)];
560         [data appendBytes:s length:len];
562 #ifdef FEAT_MBYTE
563         CONVERT_TO_UTF8_FREE(s);
564 #endif
565     }
567     [self queueMessage:UpdateTabBarMsgID data:data];
570 - (BOOL)tabBarVisible
572     return tabBarVisible;
575 - (void)showTabBar:(BOOL)enable
577     tabBarVisible = enable;
579     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
580     [self queueMessage:msgid data:nil];
583 - (void)setRows:(int)rows columns:(int)cols
585     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
587     int dim[] = { rows, cols };
588     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
590     [self queueMessage:SetTextDimensionsMsgID data:data];
593 - (void)setWindowTitle:(char *)title
595     NSMutableData *data = [NSMutableData data];
596     int len = strlen(title);
597     if (len <= 0) return;
599     [data appendBytes:&len length:sizeof(int)];
600     [data appendBytes:title length:len];
602     [self queueMessage:SetWindowTitleMsgID data:data];
605 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
606                             saving:(int)saving
608     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
609     //        saving);
611     char_u *s = NULL;
612     NSString *ds = dir ? [NSString stringWithUTF8String:dir] : nil;
613     NSString *ts = title ? [NSString stringWithUTF8String:title] : nil;
614     @try {
615         [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
617         // Wait until a reply is sent from MMVimController.
618         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
619                                  beforeDate:[NSDate distantFuture]];
621         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
622             char_u *ret = (char_u*)[dialogReturn UTF8String];
623 #ifdef FEAT_MBYTE
624             ret = CONVERT_FROM_UTF8(ret);
625 #endif
626             s = vim_strsave(ret);
627 #ifdef FEAT_MBYTE
628             CONVERT_FROM_UTF8_FREE(ret);
629 #endif
630         }
632         [dialogReturn release];  dialogReturn = nil;
633     }
634     @catch (NSException *e) {
635         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
636     }
638     return (char *)s;
641 - (oneway void)setDialogReturn:(in bycopy id)obj
643     // NOTE: This is called by
644     //   - [MMVimController panelDidEnd:::], and
645     //   - [MMVimController alertDidEnd:::],
646     // to indicate that a save/open panel or alert has finished.
648     if (obj != dialogReturn) {
649         [dialogReturn release];
650         dialogReturn = [obj retain];
651     }
654 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
655                      buttons:(char *)btns textField:(char *)txtfield
657     int retval = 0;
658     NSString *message = nil, *text = nil, *textFieldString = nil;
659     NSArray *buttons = nil;
660     int style = NSInformationalAlertStyle;
662     if (VIM_WARNING == type) style = NSWarningAlertStyle;
663     else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
665     if (btns) {
666         NSString *btnString = [NSString stringWithUTF8String:btns];
667         buttons = [btnString componentsSeparatedByString:@"\n"];
668     }
669     if (title)
670         message = [NSString stringWithUTF8String:title];
671     if (msg) {
672         text = [NSString stringWithUTF8String:msg];
673         if (!message) {
674             // HACK! If there is a '\n\n' or '\n' sequence in the message, then
675             // make the part up to there into the title.  We only do this
676             // because Vim has lots of dialogs without a title and they look
677             // ugly that way.
678             // TODO: Fix the actual dialog texts.
679             NSRange eolRange = [text rangeOfString:@"\n\n"];
680             if (NSNotFound == eolRange.location)
681                 eolRange = [text rangeOfString:@"\n"];
682             if (NSNotFound != eolRange.location) {
683                 message = [text substringToIndex:eolRange.location];
684                 text = [text substringFromIndex:NSMaxRange(eolRange)];
685             }
686         }
687     }
688     if (txtfield)
689         textFieldString = [NSString stringWithUTF8String:txtfield];
691     @try {
692         [frontendProxy presentDialogWithStyle:style message:message
693                               informativeText:text buttonTitles:buttons
694                               textFieldString:textFieldString];
696         // Wait until a reply is sent from MMVimController.
697         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
698                                  beforeDate:[NSDate distantFuture]];
700         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
701                 && [dialogReturn count]) {
702             retval = [[dialogReturn objectAtIndex:0] intValue];
703             if (txtfield && [dialogReturn count] > 1) {
704                 NSString *retString = [dialogReturn objectAtIndex:1];
705                 char_u *ret = (char_u*)[retString UTF8String];
706 #ifdef FEAT_MBYTE
707                 ret = CONVERT_FROM_UTF8(ret);
708 #endif
709                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
710 #ifdef FEAT_MBYTE
711                 CONVERT_FROM_UTF8_FREE(ret);
712 #endif
713             }
714         }
716         [dialogReturn release]; dialogReturn = nil;
717     }
718     @catch (NSException *e) {
719         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
720     }
722     return retval;
725 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
726                atIndex:(int)index
728     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
729     //        name, index);
731     int namelen = name ? strlen(name) : 0;
732     NSMutableData *data = [NSMutableData data];
734     [data appendBytes:&tag length:sizeof(int)];
735     [data appendBytes:&parentTag length:sizeof(int)];
736     [data appendBytes:&namelen length:sizeof(int)];
737     if (namelen > 0) [data appendBytes:name length:namelen];
738     [data appendBytes:&index length:sizeof(int)];
740     [self queueMessage:AddMenuMsgID data:data];
743 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
744                        tip:(char *)tip icon:(char *)icon
745              keyEquivalent:(int)key modifiers:(int)mods
746                     action:(NSString *)action atIndex:(int)index
748     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
749     //        parentTag, name, tip, index);
751     int namelen = name ? strlen(name) : 0;
752     int tiplen = tip ? strlen(tip) : 0;
753     int iconlen = icon ? strlen(icon) : 0;
754     int eventFlags = vimModMaskToEventModifierFlags(mods);
755     int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
756     NSMutableData *data = [NSMutableData data];
758     key = specialKeyToNSKey(key);
760     [data appendBytes:&tag length:sizeof(int)];
761     [data appendBytes:&parentTag length:sizeof(int)];
762     [data appendBytes:&namelen length:sizeof(int)];
763     if (namelen > 0) [data appendBytes:name length:namelen];
764     [data appendBytes:&tiplen length:sizeof(int)];
765     if (tiplen > 0) [data appendBytes:tip length:tiplen];
766     [data appendBytes:&iconlen length:sizeof(int)];
767     if (iconlen > 0) [data appendBytes:icon length:iconlen];
768     [data appendBytes:&actionlen length:sizeof(int)];
769     if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
770     [data appendBytes:&index length:sizeof(int)];
771     [data appendBytes:&key length:sizeof(int)];
772     [data appendBytes:&eventFlags length:sizeof(int)];
774     [self queueMessage:AddMenuItemMsgID data:data];
777 - (void)removeMenuItemWithTag:(int)tag
779     NSMutableData *data = [NSMutableData data];
780     [data appendBytes:&tag length:sizeof(int)];
782     [self queueMessage:RemoveMenuItemMsgID data:data];
785 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
787     NSMutableData *data = [NSMutableData data];
789     [data appendBytes:&tag length:sizeof(int)];
790     [data appendBytes:&enabled length:sizeof(int)];
792     [self queueMessage:EnableMenuItemMsgID data:data];
795 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
797     int len = strlen(name);
798     int row = -1, col = -1;
800     if (len <= 0) return;
802     if (!mouse && curwin) {
803         row = curwin->w_wrow;
804         col = curwin->w_wcol;
805     }
807     NSMutableData *data = [NSMutableData data];
809     [data appendBytes:&row length:sizeof(int)];
810     [data appendBytes:&col length:sizeof(int)];
811     [data appendBytes:&len length:sizeof(int)];
812     [data appendBytes:name length:len];
814     [self queueMessage:ShowPopupMenuMsgID data:data];
817 - (void)showToolbar:(int)enable flags:(int)flags
819     NSMutableData *data = [NSMutableData data];
821     [data appendBytes:&enable length:sizeof(int)];
822     [data appendBytes:&flags length:sizeof(int)];
824     [self queueMessage:ShowToolbarMsgID data:data];
827 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
829     NSMutableData *data = [NSMutableData data];
831     [data appendBytes:&ident length:sizeof(long)];
832     [data appendBytes:&type length:sizeof(int)];
834     [self queueMessage:CreateScrollbarMsgID data:data];
837 - (void)destroyScrollbarWithIdentifier:(long)ident
839     NSMutableData *data = [NSMutableData data];
840     [data appendBytes:&ident length:sizeof(long)];
842     [self queueMessage:DestroyScrollbarMsgID data:data];
845 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
847     NSMutableData *data = [NSMutableData data];
849     [data appendBytes:&ident length:sizeof(long)];
850     [data appendBytes:&visible length:sizeof(int)];
852     [self queueMessage:ShowScrollbarMsgID data:data];
855 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
857     NSMutableData *data = [NSMutableData data];
859     [data appendBytes:&ident length:sizeof(long)];
860     [data appendBytes:&pos length:sizeof(int)];
861     [data appendBytes:&len length:sizeof(int)];
863     [self queueMessage:SetScrollbarPositionMsgID data:data];
866 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
867                     identifier:(long)ident
869     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
870     float prop = (float)size/(max+1);
871     if (fval < 0) fval = 0;
872     else if (fval > 1.0f) fval = 1.0f;
873     if (prop < 0) prop = 0;
874     else if (prop > 1.0f) prop = 1.0f;
876     NSMutableData *data = [NSMutableData data];
878     [data appendBytes:&ident length:sizeof(long)];
879     [data appendBytes:&fval length:sizeof(float)];
880     [data appendBytes:&prop length:sizeof(float)];
882     [self queueMessage:SetScrollbarThumbMsgID data:data];
885 - (void)setFont:(NSFont *)font
887     NSString *fontName = [font displayName];
888     float size = [font pointSize];
889     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
890     if (len > 0) {
891         NSMutableData *data = [NSMutableData data];
893         [data appendBytes:&size length:sizeof(float)];
894         [data appendBytes:&len length:sizeof(int)];
895         [data appendBytes:[fontName UTF8String] length:len];
897         [self queueMessage:SetFontMsgID data:data];
898     }
901 - (void)setWideFont:(NSFont *)font
903     NSString *fontName = [font displayName];
904     float size = [font pointSize];
905     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
906     NSMutableData *data = [NSMutableData data];
908     [data appendBytes:&size length:sizeof(float)];
909     [data appendBytes:&len length:sizeof(int)];
910     if (len > 0)
911         [data appendBytes:[fontName UTF8String] length:len];
913     [self queueMessage:SetWideFontMsgID data:data];
916 - (void)executeActionWithName:(NSString *)name
918     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
920     if (len > 0) {
921         NSMutableData *data = [NSMutableData data];
923         [data appendBytes:&len length:sizeof(int)];
924         [data appendBytes:[name UTF8String] length:len];
926         [self queueMessage:ExecuteActionMsgID data:data];
927     }
930 - (void)setMouseShape:(int)shape
932     NSMutableData *data = [NSMutableData data];
933     [data appendBytes:&shape length:sizeof(int)];
934     [self queueMessage:SetMouseShapeMsgID data:data];
937 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
939     // Vim specifies times in milliseconds, whereas Cocoa wants them in
940     // seconds.
941     blinkWaitInterval = .001f*wait;
942     blinkOnInterval = .001f*on;
943     blinkOffInterval = .001f*off;
946 - (void)startBlink
948     if (blinkTimer) {
949         [blinkTimer invalidate];
950         [blinkTimer release];
951         blinkTimer = nil;
952     }
954     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
955             && gui.in_focus) {
956         blinkState = MMBlinkStateOn;
957         blinkTimer =
958             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
959                                               target:self
960                                             selector:@selector(blinkTimerFired:)
961                                             userInfo:nil repeats:NO] retain];
962         gui_update_cursor(TRUE, FALSE);
963         [self flushQueue:YES];
964     }
967 - (void)stopBlink
969     if (MMBlinkStateOff == blinkState) {
970         gui_update_cursor(TRUE, FALSE);
971         [self flushQueue:YES];
972     }
974     blinkState = MMBlinkStateNone;
977 - (void)adjustLinespace:(int)linespace
979     NSMutableData *data = [NSMutableData data];
980     [data appendBytes:&linespace length:sizeof(int)];
981     [self queueMessage:AdjustLinespaceMsgID data:data];
984 - (void)activate
986     [self queueMessage:ActivateMsgID data:nil];
989 - (void)setPreEditRow:(int)row column:(int)col
991     NSMutableData *data = [NSMutableData data];
992     [data appendBytes:&row length:sizeof(int)];
993     [data appendBytes:&col length:sizeof(int)];
994     [self queueMessage:SetPreEditPositionMsgID data:data];
997 - (int)lookupColorWithKey:(NSString *)key
999     if (!(key && [key length] > 0))
1000         return INVALCOLOR;
1002     NSString *stripKey = [[[[key lowercaseString]
1003         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
1004             componentsSeparatedByString:@" "]
1005                componentsJoinedByString:@""];
1007     if (stripKey && [stripKey length] > 0) {
1008         // First of all try to lookup key in the color dictionary; note that
1009         // all keys in this dictionary are lowercase with no whitespace.
1010         id obj = [colorDict objectForKey:stripKey];
1011         if (obj) return [obj intValue];
1013         // The key was not in the dictionary; is it perhaps of the form
1014         // #rrggbb?
1015         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
1016             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
1017             [scanner setScanLocation:1];
1018             unsigned hex = 0;
1019             if ([scanner scanHexInt:&hex]) {
1020                 return (int)hex;
1021             }
1022         }
1024         // As a last resort, check if it is one of the system defined colors.
1025         // The keys in this dictionary are also lowercase with no whitespace.
1026         obj = [sysColorDict objectForKey:stripKey];
1027         if (obj) {
1028             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
1029             if (col) {
1030                 float r, g, b, a;
1031                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
1032                 [col getRed:&r green:&g blue:&b alpha:&a];
1033                 return (((int)(r*255+.5f) & 0xff) << 16)
1034                      + (((int)(g*255+.5f) & 0xff) << 8)
1035                      +  ((int)(b*255+.5f) & 0xff);
1036             }
1037         }
1038     }
1040     //NSLog(@"WARNING: No color with key %@ found.", stripKey);
1041     return INVALCOLOR;
1044 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
1046     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1047     id obj;
1049     while ((obj = [e nextObject])) {
1050         if ([value isEqual:obj])
1051             return YES;
1052     }
1054     return NO;
1057 - (void)enterFullscreen
1059     [self queueMessage:EnterFullscreenMsgID data:nil];
1062 - (void)leaveFullscreen
1064     [self queueMessage:LeaveFullscreenMsgID data:nil];
1067 - (void)updateModifiedFlag
1069     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1070     // vice versa.
1071     int msgid = [self checkForModifiedBuffers]
1072             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1074     [self queueMessage:msgid data:nil];
1077 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1079     // NOTE: This method might get called whenever the run loop is tended to.
1080     // Normal keyboard and mouse input is added to input buffers, so there is
1081     // no risk in handling these events directly (they return immediately, and
1082     // do not call any other Vim functions).  However, other events such
1083     // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
1084     // events which would cause this method to be called recursively.  This
1085     // in turn leads to various difficulties that we do not want to have to
1086     // deal with.  To avoid recursive calls here we add all events except
1087     // keyboard and mouse events to an input queue which is processed whenever
1088     // gui_mch_update() is called (see processInputQueue).
1090     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1092     // Don't flush too soon after receiving input or update speed will suffer.
1093     [lastFlushDate release];
1094     lastFlushDate = [[NSDate date] retain];
1096     // Handle keyboard and mouse input now.  All other events are queued.
1097     if (InsertTextMsgID == msgid) {
1098         [self handleInsertText:data];
1099     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1100         if (!data) return;
1101         const void *bytes = [data bytes];
1102         int mods = *((int*)bytes);  bytes += sizeof(int);
1103         int len = *((int*)bytes);  bytes += sizeof(int);
1104         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1105                                               encoding:NSUTF8StringEncoding];
1106         mods = eventModifierFlagsToVimModMask(mods);
1108         [self handleKeyDown:key modifiers:mods];
1110         [key release];
1111     } else if (ScrollWheelMsgID == msgid) {
1112         if (!data) return;
1113         const void *bytes = [data bytes];
1115         int row = *((int*)bytes);  bytes += sizeof(int);
1116         int col = *((int*)bytes);  bytes += sizeof(int);
1117         int flags = *((int*)bytes);  bytes += sizeof(int);
1118         float dy = *((float*)bytes);  bytes += sizeof(float);
1120         int button = MOUSE_5;
1121         if (dy > 0) button = MOUSE_4;
1123         flags = eventModifierFlagsToVimMouseModMask(flags);
1125         int numLines = (int)round(dy);
1126         if (numLines < 0) numLines = -numLines;
1127         if (numLines == 0) numLines = 1;
1129 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1130         gui.scroll_wheel_force = numLines;
1131 #endif
1133         gui_send_mouse_event(button, col, row, NO, flags);
1134     } else if (MouseDownMsgID == msgid) {
1135         if (!data) return;
1136         const void *bytes = [data bytes];
1138         int row = *((int*)bytes);  bytes += sizeof(int);
1139         int col = *((int*)bytes);  bytes += sizeof(int);
1140         int button = *((int*)bytes);  bytes += sizeof(int);
1141         int flags = *((int*)bytes);  bytes += sizeof(int);
1142         int count = *((int*)bytes);  bytes += sizeof(int);
1144         button = eventButtonNumberToVimMouseButton(button);
1145         if (button >= 0) {
1146             flags = eventModifierFlagsToVimMouseModMask(flags);
1147             gui_send_mouse_event(button, col, row, count>1, flags);
1148         }
1149     } else if (MouseUpMsgID == msgid) {
1150         if (!data) return;
1151         const void *bytes = [data bytes];
1153         int row = *((int*)bytes);  bytes += sizeof(int);
1154         int col = *((int*)bytes);  bytes += sizeof(int);
1155         int flags = *((int*)bytes);  bytes += sizeof(int);
1157         flags = eventModifierFlagsToVimMouseModMask(flags);
1159         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1160     } else if (MouseDraggedMsgID == msgid) {
1161         if (!data) return;
1162         const void *bytes = [data bytes];
1164         int row = *((int*)bytes);  bytes += sizeof(int);
1165         int col = *((int*)bytes);  bytes += sizeof(int);
1166         int flags = *((int*)bytes);  bytes += sizeof(int);
1168         flags = eventModifierFlagsToVimMouseModMask(flags);
1170         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1171     } else if (MouseMovedMsgID == msgid) {
1172         const void *bytes = [data bytes];
1173         int row = *((int*)bytes);  bytes += sizeof(int);
1174         int col = *((int*)bytes);  bytes += sizeof(int);
1176         gui_mouse_moved(col, row);
1177     } else if (AddInputMsgID == msgid) {
1178         NSString *string = [[NSString alloc] initWithData:data
1179                 encoding:NSUTF8StringEncoding];
1180         if (string) {
1181             [self addInput:string];
1182             [string release];
1183         }
1184     } else if (TerminateNowMsgID == msgid) {
1185         isTerminating = YES;
1186     } else {
1187         // Not keyboard or mouse event, queue it and handle later.
1188         //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
1189         [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1190         [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
1191     }
1193     // See waitForInput: for an explanation of this flag.
1194     inputReceived = YES;
1197 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1199     // TODO: Get rid of this method?
1200     //NSLog(@"%s%@", _cmd, messages);
1202     unsigned i, count = [messages count];
1203     for (i = 0; i < count; i += 2) {
1204         int msgid = [[messages objectAtIndex:i] intValue];
1205         id data = [messages objectAtIndex:i+1];
1206         if ([data isEqual:[NSNull null]])
1207             data = nil;
1209         [self processInput:msgid data:data];
1210     }
1213 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1215     NSString *eval = nil;
1216     char_u *s = (char_u*)[expr UTF8String];
1218 #ifdef FEAT_MBYTE
1219     s = CONVERT_FROM_UTF8(s);
1220 #endif
1222     char_u *res = eval_client_expr_to_string(s);
1224 #ifdef FEAT_MBYTE
1225     CONVERT_FROM_UTF8_FREE(s);
1226 #endif
1228     if (res != NULL) {
1229         s = res;
1230 #ifdef FEAT_MBYTE
1231         s = CONVERT_TO_UTF8(s);
1232 #endif
1233         eval = [NSString stringWithUTF8String:(char*)s];
1234 #ifdef FEAT_MBYTE
1235         CONVERT_TO_UTF8_FREE(s);
1236 #endif
1237         vim_free(res);
1238     }
1240     return eval;
1243 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1245     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1246         // If there is no pasteboard, return YES to indicate that there is text
1247         // to copy.
1248         if (!pboard)
1249             return YES;
1251         clip_copy_selection();
1253         // Get the text to put on the pasteboard.
1254         long_u llen = 0; char_u *str = 0;
1255         int type = clip_convert_selection(&str, &llen, &clip_star);
1256         if (type < 0)
1257             return NO;
1258         
1259         // TODO: Avoid overflow.
1260         int len = (int)llen;
1261 #ifdef FEAT_MBYTE
1262         if (output_conv.vc_type != CONV_NONE) {
1263             char_u *conv_str = string_convert(&output_conv, str, &len);
1264             if (conv_str) {
1265                 vim_free(str);
1266                 str = conv_str;
1267             }
1268         }
1269 #endif
1271         NSString *string = [[NSString alloc]
1272             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1274         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1275         [pboard declareTypes:types owner:nil];
1276         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1277     
1278         [string release];
1279         vim_free(str);
1281         return ok;
1282     }
1284     return NO;
1287 - (oneway void)addReply:(in bycopy NSString *)reply
1288                  server:(in byref id <MMVimServerProtocol>)server
1290     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1292     // Replies might come at any time and in any order so we keep them in an
1293     // array inside a dictionary with the send port used as key.
1295     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1296     // HACK! Assume connection uses mach ports.
1297     int port = [(NSMachPort*)[conn sendPort] machPort];
1298     NSNumber *key = [NSNumber numberWithInt:port];
1300     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1301     if (!replies) {
1302         replies = [NSMutableArray array];
1303         [serverReplyDict setObject:replies forKey:key];
1304     }
1306     [replies addObject:reply];
1309 - (void)addInput:(in bycopy NSString *)input
1310                  client:(in byref id <MMVimClientProtocol>)client
1312     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1314     [self addInput:input];
1315     [self addClient:(id)client];
1317     inputReceived = YES;
1320 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1321                  client:(in byref id <MMVimClientProtocol>)client
1323     [self addClient:(id)client];
1324     return [self evaluateExpression:expr];
1327 - (void)registerServerWithName:(NSString *)name
1329     NSString *svrName = name;
1330     NSConnection *svrConn = [NSConnection defaultConnection];
1331     unsigned i;
1333     for (i = 0; i < MMServerMax; ++i) {
1334         NSString *connName = [self connectionNameFromServerName:svrName];
1336         if ([svrConn registerName:connName]) {
1337             //NSLog(@"Registered server with name: %@", svrName);
1339             // TODO: Set request/reply time-outs to something else?
1340             //
1341             // Don't wait for requests (time-out means that the message is
1342             // dropped).
1343             [svrConn setRequestTimeout:0];
1344             //[svrConn setReplyTimeout:MMReplyTimeout];
1345             [svrConn setRootObject:self];
1347             char_u *s = (char_u*)[svrName UTF8String];
1348 #ifdef FEAT_MBYTE
1349             s = CONVERT_FROM_UTF8(s);
1350 #endif
1351             // NOTE: 'serverName' is a global variable
1352             serverName = vim_strsave(s);
1353 #ifdef FEAT_MBYTE
1354             CONVERT_FROM_UTF8_FREE(s);
1355 #endif
1356 #ifdef FEAT_EVAL
1357             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1358 #endif
1359 #ifdef FEAT_TITLE
1360             need_maketitle = TRUE;
1361 #endif
1362             [self queueMessage:SetServerNameMsgID data:
1363                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1364             break;
1365         }
1367         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1368     }
1371 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1372                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1373               silent:(BOOL)silent
1375     // NOTE: If 'name' equals 'serverName' then the request is local (client
1376     // and server are the same).  This case is not handled separately, so a
1377     // connection will be set up anyway (this simplifies the code).
1379     NSConnection *conn = [self connectionForServerName:name];
1380     if (!conn) {
1381         if (!silent) {
1382             char_u *s = (char_u*)[name UTF8String];
1383 #ifdef FEAT_MBYTE
1384             s = CONVERT_FROM_UTF8(s);
1385 #endif
1386             EMSG2(_(e_noserver), s);
1387 #ifdef FEAT_MBYTE
1388             CONVERT_FROM_UTF8_FREE(s);
1389 #endif
1390         }
1391         return NO;
1392     }
1394     if (port) {
1395         // HACK! Assume connection uses mach ports.
1396         *port = [(NSMachPort*)[conn sendPort] machPort];
1397     }
1399     id proxy = [conn rootProxy];
1400     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1402     @try {
1403         if (expr) {
1404             NSString *eval = [proxy evaluateExpression:string client:self];
1405             if (reply) {
1406                 if (eval) {
1407                     char_u *r = (char_u*)[eval UTF8String];
1408 #ifdef FEAT_MBYTE
1409                     r = CONVERT_FROM_UTF8(r);
1410 #endif
1411                     *reply = vim_strsave(r);
1412 #ifdef FEAT_MBYTE
1413                     CONVERT_FROM_UTF8_FREE(r);
1414 #endif
1415                 } else {
1416                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1417                 }
1418             }
1420             if (!eval)
1421                 return NO;
1422         } else {
1423             [proxy addInput:string client:self];
1424         }
1425     }
1426     @catch (NSException *e) {
1427         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1428         return NO;
1429     }
1431     return YES;
1434 - (NSArray *)serverList
1436     NSArray *list = nil;
1438     if ([self connection]) {
1439         id proxy = [connection rootProxy];
1440         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1442         @try {
1443             list = [proxy serverList];
1444         }
1445         @catch (NSException *e) {
1446             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1447         }
1448     } else {
1449         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1450     }
1452     return list;
1455 - (NSString *)peekForReplyOnPort:(int)port
1457     //NSLog(@"%s%d", _cmd, port);
1459     NSNumber *key = [NSNumber numberWithInt:port];
1460     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1461     if (replies && [replies count]) {
1462         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1463         //        [replies objectAtIndex:0]);
1464         return [replies objectAtIndex:0];
1465     }
1467     //NSLog(@"    No replies");
1468     return nil;
1471 - (NSString *)waitForReplyOnPort:(int)port
1473     //NSLog(@"%s%d", _cmd, port);
1474     
1475     NSConnection *conn = [self connectionForServerPort:port];
1476     if (!conn)
1477         return nil;
1479     NSNumber *key = [NSNumber numberWithInt:port];
1480     NSMutableArray *replies = nil;
1481     NSString *reply = nil;
1483     // Wait for reply as long as the connection to the server is valid (unless
1484     // user interrupts wait with Ctrl-C).
1485     while (!got_int && [conn isValid] &&
1486             !(replies = [serverReplyDict objectForKey:key])) {
1487         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1488                                  beforeDate:[NSDate distantFuture]];
1489     }
1491     if (replies) {
1492         if ([replies count] > 0) {
1493             reply = [[replies objectAtIndex:0] retain];
1494             //NSLog(@"    Got reply: %@", reply);
1495             [replies removeObjectAtIndex:0];
1496             [reply autorelease];
1497         }
1499         if ([replies count] == 0)
1500             [serverReplyDict removeObjectForKey:key];
1501     }
1503     return reply;
1506 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1508     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1509     if (client) {
1510         @try {
1511             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1512             [client addReply:reply server:self];
1513             return YES;
1514         }
1515         @catch (NSException *e) {
1516             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1517         }
1518     } else {
1519         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1520     }
1522     return NO;
1525 @end // MMBackend
1529 @implementation MMBackend (Private)
1531 - (void)processInputQueue
1533     // NOTE: One of the input events may cause this method to be called
1534     // recursively, so copy the input queue to a local variable and clear it
1535     // before starting to process input events (otherwise we could get stuck in
1536     // an endless loop).
1537     NSArray *q = [inputQueue copy];
1538     unsigned i, count = [q count];
1540     [inputQueue removeAllObjects];
1542     for (i = 0; i < count-1; i += 2) {
1543         int msgid = [[q objectAtIndex:i] intValue];
1544         id data = [q objectAtIndex:i+1];
1545         if ([data isEqual:[NSNull null]])
1546             data = nil;
1548         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1549         [self handleInputEvent:msgid data:data];
1550     }
1552     [q release];
1553     //NSLog(@"Clear input event queue");
1556 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1558     // NOTE: Be careful with what you do in this method.  Ideally, a message
1559     // should be handled by adding something to the input buffer and returning
1560     // immediately.  If you call a Vim function then it should not enter a loop
1561     // waiting for key presses or in any other way block the process.  The
1562     // reason for this being that only one message can be processed at a time,
1563     // so if another message is received while processing, then the new message
1564     // is dropped.  See also the comment in processInput:data:.
1566     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1568     if (SelectTabMsgID == msgid) {
1569         if (!data) return;
1570         const void *bytes = [data bytes];
1571         int idx = *((int*)bytes) + 1;
1572         //NSLog(@"Selecting tab %d", idx);
1573         send_tabline_event(idx);
1574     } else if (CloseTabMsgID == msgid) {
1575         if (!data) return;
1576         const void *bytes = [data bytes];
1577         int idx = *((int*)bytes) + 1;
1578         //NSLog(@"Closing tab %d", idx);
1579         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1580     } else if (AddNewTabMsgID == msgid) {
1581         //NSLog(@"Adding new tab");
1582         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1583     } else if (DraggedTabMsgID == msgid) {
1584         if (!data) return;
1585         const void *bytes = [data bytes];
1586         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1587         // based.
1588         int idx = *((int*)bytes);
1590         tabpage_move(idx);
1591     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) {
1592         if (!data) return;
1593         const void *bytes = [data bytes];
1594         int rows = *((int*)bytes);  bytes += sizeof(int);
1595         int cols = *((int*)bytes);  bytes += sizeof(int);
1597         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1598         // gui_resize_shell(), so we have to manually set the rows and columns
1599         // here.  (MacVim doesn't change the rows and columns to avoid
1600         // inconsistent states between Vim and MacVim.)
1601         [self queueMessage:msgid data:data];
1603         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1604         gui_resize_shell(cols, rows);
1605     } else if (ExecuteMenuMsgID == msgid) {
1606         if (!data) return;
1607         const void *bytes = [data bytes];
1608         int tag = *((int*)bytes);  bytes += sizeof(int);
1610         vimmenu_T *menu = (vimmenu_T*)tag;
1611         // TODO!  Make sure 'menu' is a valid menu pointer!
1612         if (menu) {
1613             gui_menu_cb(menu);
1614         }
1615     } else if (ToggleToolbarMsgID == msgid) {
1616         [self handleToggleToolbar];
1617     } else if (ScrollbarEventMsgID == msgid) {
1618         [self handleScrollbarEvent:data];
1619     } else if (SetFontMsgID == msgid) {
1620         [self handleSetFont:data];
1621     } else if (VimShouldCloseMsgID == msgid) {
1622         gui_shell_closed();
1623     } else if (DropFilesMsgID == msgid) {
1624         [self handleDropFiles:data];
1625     } else if (DropStringMsgID == msgid) {
1626         [self handleDropString:data];
1627     } else if (GotFocusMsgID == msgid) {
1628         if (!gui.in_focus)
1629             [self focusChange:YES];
1630     } else if (LostFocusMsgID == msgid) {
1631         if (gui.in_focus)
1632             [self focusChange:NO];
1633     } else if (SetMouseShapeMsgID == msgid) {
1634         const void *bytes = [data bytes];
1635         int shape = *((int*)bytes);  bytes += sizeof(int);
1636         update_mouseshape(shape);
1637     } else if (ODBEditMsgID == msgid) {
1638         [self handleOdbEdit:data];
1639     } else if (XcodeModMsgID == msgid) {
1640         [self handleXcodeMod:data];
1641     } else {
1642         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1643     }
1646 + (NSDictionary *)specialKeys
1648     static NSDictionary *specialKeys = nil;
1650     if (!specialKeys) {
1651         NSBundle *mainBundle = [NSBundle mainBundle];
1652         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1653                                               ofType:@"plist"];
1654         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1655     }
1657     return specialKeys;
1660 - (void)handleInsertText:(NSData *)data
1662     if (!data) return;
1664     NSString *key = [[NSString alloc] initWithData:data
1665                                           encoding:NSUTF8StringEncoding];
1666     char_u *str = (char_u*)[key UTF8String];
1667     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1669 #ifdef FEAT_MBYTE
1670     char_u *conv_str = NULL;
1671     if (input_conv.vc_type != CONV_NONE) {
1672         conv_str = string_convert(&input_conv, str, &len);
1673         if (conv_str)
1674             str = conv_str;
1675     }
1676 #endif
1678     if (len == 1 && ((str[0] == Ctrl_C && ctrl_c_interrupts)
1679             || (str[0] == intr_char && intr_char != Ctrl_C))) {
1680         trash_input_buf();
1681         got_int = TRUE;
1682     }
1684     for (i = 0; i < len; ++i) {
1685         add_to_input_buf(str+i, 1);
1686         if (CSI == str[i]) {
1687             // NOTE: If the converted string contains the byte CSI, then it
1688             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1689             // won't work.
1690             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1691             add_to_input_buf(extra, 2);
1692         }
1693     }
1695 #ifdef FEAT_MBYTE
1696     if (conv_str)
1697         vim_free(conv_str);
1698 #endif
1699     [key release];
1702 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1704     char_u special[3];
1705     char_u modChars[3];
1706     char_u *chars = (char_u*)[key UTF8String];
1707 #ifdef FEAT_MBYTE
1708     char_u *conv_str = NULL;
1709 #endif
1710     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1712     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1713     // that new keys can easily be added.
1714     NSString *specialString = [[MMBackend specialKeys]
1715             objectForKey:key];
1716     if (specialString && [specialString length] > 1) {
1717         //NSLog(@"special key: %@", specialString);
1718         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1719                 [specialString characterAtIndex:1]);
1721         ikey = simplify_key(ikey, &mods);
1722         if (ikey == CSI)
1723             ikey = K_CSI;
1725         special[0] = CSI;
1726         special[1] = K_SECOND(ikey);
1727         special[2] = K_THIRD(ikey);
1729         chars = special;
1730         length = 3;
1731     } else if (1 == length && TAB == chars[0]) {
1732         // Tab is a trouble child:
1733         // - <Tab> is added to the input buffer as is
1734         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1735         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1736         //   to be converted to utf-8
1737         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1738         // - <C-Tab> is reserved by Mac OS X
1739         // - <D-Tab> is reserved by Mac OS X
1740         chars = special;
1741         special[0] = TAB;
1742         length = 1;
1744         if (mods & MOD_MASK_SHIFT) {
1745             mods &= ~MOD_MASK_SHIFT;
1746             special[0] = CSI;
1747             special[1] = K_SECOND(K_S_TAB);
1748             special[2] = K_THIRD(K_S_TAB);
1749             length = 3;
1750         } else if (mods & MOD_MASK_ALT) {
1751             int mtab = 0x80 | TAB;
1752 #ifdef FEAT_MBYTE
1753             if (enc_utf8) {
1754                 // Convert to utf-8
1755                 special[0] = (mtab >> 6) + 0xc0;
1756                 special[1] = mtab & 0xbf;
1757                 length = 2;
1758             } else
1759 #endif
1760             {
1761                 special[0] = mtab;
1762                 length = 1;
1763             }
1764             mods &= ~MOD_MASK_ALT;
1765         }
1766     } else if (length > 0) {
1767         unichar c = [key characterAtIndex:0];
1769         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1770         //        [key characterAtIndex:0], mods);
1772         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1773                 || (c == intr_char && intr_char != Ctrl_C))) {
1774             trash_input_buf();
1775             got_int = TRUE;
1776         }
1778         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1779         // cleared since they are already added to the key by the AppKit.
1780         // Unfortunately, the only way to deal with when to clear the modifiers
1781         // or not seems to be to have hard-wired rules like this.
1782         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1783                     || 0x9 == c || 0xd == c || ESC == c) ) {
1784             mods &= ~MOD_MASK_SHIFT;
1785             mods &= ~MOD_MASK_CTRL;
1786             //NSLog(@"clear shift ctrl");
1787         }
1789         // HACK!  All Option+key presses go via 'insert text' messages, except
1790         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1791         // not work to map to it.
1792         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1793             //NSLog(@"clear alt");
1794             mods &= ~MOD_MASK_ALT;
1795         }
1797 #ifdef FEAT_MBYTE
1798         if (input_conv.vc_type != CONV_NONE) {
1799             conv_str = string_convert(&input_conv, chars, &length);
1800             if (conv_str)
1801                 chars = conv_str;
1802         }
1803 #endif
1804     }
1806     if (chars && length > 0) {
1807         if (mods) {
1808             //NSLog(@"adding mods: %d", mods);
1809             modChars[0] = CSI;
1810             modChars[1] = KS_MODIFIER;
1811             modChars[2] = mods;
1812             add_to_input_buf(modChars, 3);
1813         }
1815         //NSLog(@"add to input buf: 0x%x", chars[0]);
1816         // TODO: Check for CSI bytes?
1817         add_to_input_buf(chars, length);
1818     }
1820 #ifdef FEAT_MBYTE
1821     if (conv_str)
1822         vim_free(conv_str);
1823 #endif
1826 - (void)queueMessage:(int)msgid data:(NSData *)data
1828     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1829     if (data)
1830         [outputQueue addObject:data];
1831     else
1832         [outputQueue addObject:[NSData data]];
1835 - (void)connectionDidDie:(NSNotification *)notification
1837     // If the main connection to MacVim is lost this means that MacVim was
1838     // either quit (by the user chosing Quit on the MacVim menu), or it has
1839     // crashed.  In the former case the flag 'isTerminating' is set and we then
1840     // quit cleanly; in the latter case we make sure the swap files are left
1841     // for recovery.
1843     //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1844     if (isTerminating)
1845         getout(0);
1846     else
1847         getout_preserve_modified(1);
1850 - (void)blinkTimerFired:(NSTimer *)timer
1852     NSTimeInterval timeInterval = 0;
1854     [blinkTimer release];
1855     blinkTimer = nil;
1857     if (MMBlinkStateOn == blinkState) {
1858         gui_undraw_cursor();
1859         blinkState = MMBlinkStateOff;
1860         timeInterval = blinkOffInterval;
1861     } else if (MMBlinkStateOff == blinkState) {
1862         gui_update_cursor(TRUE, FALSE);
1863         blinkState = MMBlinkStateOn;
1864         timeInterval = blinkOnInterval;
1865     }
1867     if (timeInterval > 0) {
1868         blinkTimer = 
1869             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1870                                             selector:@selector(blinkTimerFired:)
1871                                             userInfo:nil repeats:NO] retain];
1872         [self flushQueue:YES];
1873     }
1876 - (void)focusChange:(BOOL)on
1878     gui_focus_change(on);
1881 - (void)handleToggleToolbar
1883     // If 'go' contains 'T', then remove it, else add it.
1885     char_u go[sizeof(GO_ALL)+2];
1886     char_u *p;
1887     int len;
1889     STRCPY(go, p_go);
1890     p = vim_strchr(go, GO_TOOLBAR);
1891     len = STRLEN(go);
1893     if (p != NULL) {
1894         char_u *end = go + len;
1895         while (p < end) {
1896             p[0] = p[1];
1897             ++p;
1898         }
1899     } else {
1900         go[len] = GO_TOOLBAR;
1901         go[len+1] = NUL;
1902     }
1904     set_option_value((char_u*)"guioptions", 0, go, 0);
1906     // Force screen redraw (does it have to be this complicated?).
1907     redraw_all_later(CLEAR);
1908     update_screen(NOT_VALID);
1909     setcursor();
1910     out_flush();
1911     gui_update_cursor(FALSE, FALSE);
1912     gui_mch_flush();
1915 - (void)handleScrollbarEvent:(NSData *)data
1917     if (!data) return;
1919     const void *bytes = [data bytes];
1920     long ident = *((long*)bytes);  bytes += sizeof(long);
1921     int hitPart = *((int*)bytes);  bytes += sizeof(int);
1922     float fval = *((float*)bytes);  bytes += sizeof(float);
1923     scrollbar_T *sb = gui_find_scrollbar(ident);
1925     if (sb) {
1926         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1927         long value = sb_info->value;
1928         long size = sb_info->size;
1929         long max = sb_info->max;
1930         BOOL isStillDragging = NO;
1931         BOOL updateKnob = YES;
1933         switch (hitPart) {
1934         case NSScrollerDecrementPage:
1935             value -= (size > 2 ? size - 2 : 1);
1936             break;
1937         case NSScrollerIncrementPage:
1938             value += (size > 2 ? size - 2 : 1);
1939             break;
1940         case NSScrollerDecrementLine:
1941             --value;
1942             break;
1943         case NSScrollerIncrementLine:
1944             ++value;
1945             break;
1946         case NSScrollerKnob:
1947             isStillDragging = YES;
1948             // fall through ...
1949         case NSScrollerKnobSlot:
1950             value = (long)(fval * (max - size + 1));
1951             // fall through ...
1952         default:
1953             updateKnob = NO;
1954             break;
1955         }
1957         //NSLog(@"value %d -> %d", sb_info->value, value);
1958         gui_drag_scrollbar(sb, value, isStillDragging);
1960         if (updateKnob) {
1961             // Dragging the knob or option+clicking automatically updates
1962             // the knob position (on the actual NSScroller), so we only
1963             // need to set the knob position in the other cases.
1964             if (sb->wp) {
1965                 // Update both the left&right vertical scrollbars.
1966                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1967                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1968                 [self setScrollbarThumbValue:value size:size max:max
1969                                   identifier:identLeft];
1970                 [self setScrollbarThumbValue:value size:size max:max
1971                                   identifier:identRight];
1972             } else {
1973                 // Update the horizontal scrollbar.
1974                 [self setScrollbarThumbValue:value size:size max:max
1975                                   identifier:ident];
1976             }
1977         }
1978     }
1981 - (void)handleSetFont:(NSData *)data
1983     if (!data) return;
1985     const void *bytes = [data bytes];
1986     float pointSize = *((float*)bytes);  bytes += sizeof(float);
1987     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1988     bytes += sizeof(unsigned);  // len not used
1990     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1991     [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1992     char_u *s = (char_u*)[name UTF8String];
1994 #ifdef FEAT_MBYTE
1995     s = CONVERT_FROM_UTF8(s);
1996 #endif
1998     set_option_value((char_u*)"guifont", 0, s, 0);
2000 #ifdef FEAT_MBYTE
2001     CONVERT_FROM_UTF8_FREE(s);
2002 #endif
2004     // Force screen redraw (does it have to be this complicated?).
2005     redraw_all_later(CLEAR);
2006     update_screen(NOT_VALID);
2007     setcursor();
2008     out_flush();
2009     gui_update_cursor(FALSE, FALSE);
2010     gui_mch_flush();
2013 - (void)handleDropFiles:(NSData *)data
2015     // TODO: Get rid of this method; instead use Vim script directly.  At the
2016     // moment I know how to do this to open files in tabs, but I'm not sure how
2017     // to add the filenames to the command line when in command line mode.
2019     if (!data) return;
2021 #ifdef FEAT_DND
2022     const void *bytes = [data bytes];
2023     const void *end = [data bytes] + [data length];
2024     BOOL forceOpen = *((BOOL*)bytes);  bytes += sizeof(BOOL);
2025     int n = *((int*)bytes);  bytes += sizeof(int);
2027     if (!forceOpen && (State & CMDLINE)) {
2028         // HACK!  If Vim is in command line mode then the files names
2029         // should be added to the command line, instead of opening the
2030         // files in tabs (unless forceOpen is set).  This is taken care of by
2031         // gui_handle_drop().
2032         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2033         if (fnames) {
2034             int i = 0;
2035             while (bytes < end && i < n) {
2036                 int len = *((int*)bytes);  bytes += sizeof(int);
2037                 char_u *s = (char_u*)bytes;
2038 #ifdef FEAT_MBYTE
2039                 s = CONVERT_FROM_UTF8(s);
2040 #endif
2041                 fnames[i++] = vim_strsave(s);
2042 #ifdef FEAT_MBYTE
2043                 CONVERT_FROM_UTF8_FREE(s);
2044 #endif
2045                 bytes += len;
2046             }
2048             // NOTE!  This function will free 'fnames'.
2049             // HACK!  It is assumed that the 'x' and 'y' arguments are
2050             // unused when in command line mode.
2051             gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
2052         }
2053     } else {
2054         // HACK!  I'm not sure how to get Vim to open a list of files in
2055         // tabs, so instead I create a ':tab drop' command with all the
2056         // files to open and execute it.
2057         NSMutableString *cmd = [NSMutableString stringWithString:@":tab drop"];
2059         int i;
2060         for (i = 0; i < n && bytes < end; ++i) {
2061             int len = *((int*)bytes);  bytes += sizeof(int);
2062             NSString *file = [NSString stringWithUTF8String:bytes];
2063             file = [file stringByEscapingSpecialFilenameCharacters];
2064             bytes += len;
2066             [cmd appendString:@" "];
2067             [cmd appendString:file];
2068         }
2070         // By going to the last tabpage we ensure that the new tabs will
2071         // appear last (if this call is left out, the taborder becomes
2072         // messy).
2073         goto_tabpage(9999);
2075         char_u *s = (char_u*)[cmd UTF8String];
2076 #ifdef FEAT_MBYTE
2077         s = CONVERT_FROM_UTF8(s);
2078 #endif
2079         do_cmdline_cmd(s);
2080 #ifdef FEAT_MBYTE
2081         CONVERT_FROM_UTF8_FREE(s);
2082 #endif
2084         // Force screen redraw (does it have to be this complicated?).
2085         // (This code was taken from the end of gui_handle_drop().)
2086         update_screen(NOT_VALID);
2087         setcursor();
2088         out_flush();
2089         gui_update_cursor(FALSE, FALSE);
2090         maketitle();
2091         gui_mch_flush();
2092     }
2093 #endif // FEAT_DND
2096 - (void)handleDropString:(NSData *)data
2098     if (!data) return;
2100 #ifdef FEAT_DND
2101     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2102     const void *bytes = [data bytes];
2103     int len = *((int*)bytes);  bytes += sizeof(int);
2104     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2106     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2107     NSRange range = { 0, [string length] };
2108     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2109                                          withString:@"\x0a" options:0
2110                                               range:range];
2111     if (0 == n) {
2112         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2113                                        options:0 range:range];
2114     }
2116     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2117     char_u *s = (char_u*)[string UTF8String];
2118 #ifdef FEAT_MBYTE
2119     if (input_conv.vc_type != CONV_NONE)
2120         s = string_convert(&input_conv, s, &len);
2121 #endif
2122     dnd_yank_drag_data(s, len);
2123 #ifdef FEAT_MBYTE
2124     if (input_conv.vc_type != CONV_NONE)
2125         vim_free(s);
2126 #endif
2127     add_to_input_buf(dropkey, sizeof(dropkey));
2128 #endif // FEAT_DND
2131 - (void)handleOdbEdit:(NSData *)data
2133 #ifdef FEAT_ODB_EDITOR
2134     const void *bytes = [data bytes];
2136     OSType serverID = *((OSType*)bytes);  bytes += sizeof(OSType);
2138     char_u *path = NULL;
2139     int pathLen = *((int*)bytes);  bytes += sizeof(int);
2140     if (pathLen > 0) {
2141         path = (char_u*)bytes;
2142         bytes += pathLen;
2143 #ifdef FEAT_MBYTE
2144         path = CONVERT_FROM_UTF8(path);
2145 #endif
2146     }
2148     NSAppleEventDescriptor *token = nil;
2149     DescType tokenType = *((DescType*)bytes);  bytes += sizeof(DescType);
2150     int descLen = *((int*)bytes);  bytes += sizeof(int);
2151     if (descLen > 0) {
2152         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2153                                                                bytes:bytes
2154                                                               length:descLen];
2155         bytes += descLen;
2156     }
2158     unsigned i, numFiles = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2159     for (i = 0; i < numFiles; ++i) {
2160         int len = *((int*)bytes);  bytes += sizeof(int);
2161         char_u *filename = (char_u*)bytes;
2162 #ifdef FEAT_MBYTE
2163         filename = CONVERT_FROM_UTF8(filename);
2164 #endif
2165         buf_T *buf = buflist_findname(filename);
2166         if (buf) {
2167             if (buf->b_odb_token) {
2168                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2169                 buf->b_odb_token = NULL;
2170             }
2172             if (buf->b_odb_fname) {
2173                 vim_free(buf->b_odb_fname);
2174                 buf->b_odb_fname = NULL;
2175             }
2177             buf->b_odb_server_id = serverID;
2179             if (token)
2180                 buf->b_odb_token = [token retain];
2181             if (path)
2182                 buf->b_odb_fname = vim_strsave(path);
2183         } else {
2184             NSLog(@"WARNING: Could not find buffer '%s' for ODB editing.",
2185                     filename);
2186         }
2188 #ifdef FEAT_MBYTE
2189         CONVERT_FROM_UTF8_FREE(filename);
2190 #endif
2191         bytes += len;
2192     }
2193 #ifdef FEAT_MBYTE
2194     CONVERT_FROM_UTF8_FREE(path);
2195 #endif
2196 #endif // FEAT_ODB_EDITOR
2199 - (void)handleXcodeMod:(NSData *)data
2201 #if 0
2202     const void *bytes = [data bytes];
2203     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2204     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2205     if (0 == len)
2206         return;
2208     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2209             descriptorWithDescriptorType:type
2210                                    bytes:bytes
2211                                   length:len];
2212 #endif
2215 - (BOOL)checkForModifiedBuffers
2217     buf_T *buf;
2218     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2219         if (bufIsChanged(buf)) {
2220             return YES;
2221         }
2222     }
2224     return NO;
2227 - (void)addInput:(NSString *)input
2229     char_u *s = (char_u*)[input UTF8String];
2231 #ifdef FEAT_MBYTE
2232     s = CONVERT_FROM_UTF8(s);
2233 #endif
2235     server_to_input_buf(s);
2237 #ifdef FEAT_MBYTE
2238     CONVERT_FROM_UTF8_FREE(s);
2239 #endif
2242 @end // MMBackend (Private)
2247 @implementation MMBackend (ClientServer)
2249 - (NSString *)connectionNameFromServerName:(NSString *)name
2251     NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2253     return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2254         lowercaseString];
2257 - (NSConnection *)connectionForServerName:(NSString *)name
2259     // TODO: Try 'name%d' if 'name' fails.
2260     NSString *connName = [self connectionNameFromServerName:name];
2261     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2263     if (!svrConn) {
2264         svrConn = [NSConnection connectionWithRegisteredName:connName
2265                                                            host:nil];
2266         // Try alternate server...
2267         if (!svrConn && alternateServerName) {
2268             //NSLog(@"  trying to connect to alternate server: %@",
2269             //        alternateServerName);
2270             connName = [self connectionNameFromServerName:alternateServerName];
2271             svrConn = [NSConnection connectionWithRegisteredName:connName
2272                                                             host:nil];
2273         }
2275         // Try looking for alternate servers...
2276         if (!svrConn) {
2277             //NSLog(@"  looking for alternate servers...");
2278             NSString *alt = [self alternateServerNameForName:name];
2279             if (alt != alternateServerName) {
2280                 //NSLog(@"  found alternate server: %@", string);
2281                 [alternateServerName release];
2282                 alternateServerName = [alt copy];
2283             }
2284         }
2286         // Try alternate server again...
2287         if (!svrConn && alternateServerName) {
2288             //NSLog(@"  trying to connect to alternate server: %@",
2289             //        alternateServerName);
2290             connName = [self connectionNameFromServerName:alternateServerName];
2291             svrConn = [NSConnection connectionWithRegisteredName:connName
2292                                                             host:nil];
2293         }
2295         if (svrConn) {
2296             [connectionNameDict setObject:svrConn forKey:connName];
2298             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2299             [[NSNotificationCenter defaultCenter] addObserver:self
2300                     selector:@selector(serverConnectionDidDie:)
2301                         name:NSConnectionDidDieNotification object:svrConn];
2302         }
2303     }
2305     return svrConn;
2308 - (NSConnection *)connectionForServerPort:(int)port
2310     NSConnection *conn;
2311     NSEnumerator *e = [connectionNameDict objectEnumerator];
2313     while ((conn = [e nextObject])) {
2314         // HACK! Assume connection uses mach ports.
2315         if (port == [(NSMachPort*)[conn sendPort] machPort])
2316             return conn;
2317     }
2319     return nil;
2322 - (void)serverConnectionDidDie:(NSNotification *)notification
2324     //NSLog(@"%s%@", _cmd, notification);
2326     NSConnection *svrConn = [notification object];
2328     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2329     [[NSNotificationCenter defaultCenter]
2330             removeObserver:self
2331                       name:NSConnectionDidDieNotification
2332                     object:svrConn];
2334     [connectionNameDict removeObjectsForKeys:
2335         [connectionNameDict allKeysForObject:svrConn]];
2337     // HACK! Assume connection uses mach ports.
2338     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2339     NSNumber *key = [NSNumber numberWithInt:port];
2341     [clientProxyDict removeObjectForKey:key];
2342     [serverReplyDict removeObjectForKey:key];
2345 - (void)addClient:(NSDistantObject *)client
2347     NSConnection *conn = [client connectionForProxy];
2348     // HACK! Assume connection uses mach ports.
2349     int port = [(NSMachPort*)[conn sendPort] machPort];
2350     NSNumber *key = [NSNumber numberWithInt:port];
2352     if (![clientProxyDict objectForKey:key]) {
2353         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2354         [clientProxyDict setObject:client forKey:key];
2355     }
2357     // NOTE: 'clientWindow' is a global variable which is used by <client>
2358     clientWindow = port;
2361 - (NSString *)alternateServerNameForName:(NSString *)name
2363     if (!(name && [name length] > 0))
2364         return nil;
2366     // Only look for alternates if 'name' doesn't end in a digit.
2367     unichar lastChar = [name characterAtIndex:[name length]-1];
2368     if (lastChar >= '0' && lastChar <= '9')
2369         return nil;
2371     // Look for alternates among all current servers.
2372     NSArray *list = [self serverList];
2373     if (!(list && [list count] > 0))
2374         return nil;
2376     // Filter out servers starting with 'name' and ending with a number. The
2377     // (?i) pattern ensures that the match is case insensitive.
2378     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2379     NSPredicate *pred = [NSPredicate predicateWithFormat:
2380             @"SELF MATCHES %@", pat];
2381     list = [list filteredArrayUsingPredicate:pred];
2382     if ([list count] > 0) {
2383         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2384         return [list objectAtIndex:0];
2385     }
2387     return nil;
2390 @end // MMBackend (ClientServer)
2395 @implementation NSString (MMServerNameCompare)
2396 - (NSComparisonResult)serverNameCompare:(NSString *)string
2398     return [self compare:string
2399                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2401 @end
2406 static int eventModifierFlagsToVimModMask(int modifierFlags)
2408     int modMask = 0;
2410     if (modifierFlags & NSShiftKeyMask)
2411         modMask |= MOD_MASK_SHIFT;
2412     if (modifierFlags & NSControlKeyMask)
2413         modMask |= MOD_MASK_CTRL;
2414     if (modifierFlags & NSAlternateKeyMask)
2415         modMask |= MOD_MASK_ALT;
2416     if (modifierFlags & NSCommandKeyMask)
2417         modMask |= MOD_MASK_CMD;
2419     return modMask;
2422 static int vimModMaskToEventModifierFlags(int mods)
2424     int flags = 0;
2426     if (mods & MOD_MASK_SHIFT)
2427         flags |= NSShiftKeyMask;
2428     if (mods & MOD_MASK_CTRL)
2429         flags |= NSControlKeyMask;
2430     if (mods & MOD_MASK_ALT)
2431         flags |= NSAlternateKeyMask;
2432     if (mods & MOD_MASK_CMD)
2433         flags |= NSCommandKeyMask;
2435     return flags;
2438 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2440     int modMask = 0;
2442     if (modifierFlags & NSShiftKeyMask)
2443         modMask |= MOUSE_SHIFT;
2444     if (modifierFlags & NSControlKeyMask)
2445         modMask |= MOUSE_CTRL;
2446     if (modifierFlags & NSAlternateKeyMask)
2447         modMask |= MOUSE_ALT;
2449     return modMask;
2452 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2454     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2456     return (buttonNumber >= 0 && buttonNumber < 3)
2457             ? mouseButton[buttonNumber] : -1;
2460 static int specialKeyToNSKey(int key)
2462     if (!IS_SPECIAL(key))
2463         return key;
2465     static struct {
2466         int special;
2467         int nskey;
2468     } sp2ns[] = {
2469         { K_UP, NSUpArrowFunctionKey },
2470         { K_DOWN, NSDownArrowFunctionKey },
2471         { K_LEFT, NSLeftArrowFunctionKey },
2472         { K_RIGHT, NSRightArrowFunctionKey },
2473         { K_F1, NSF1FunctionKey },
2474         { K_F2, NSF2FunctionKey },
2475         { K_F3, NSF3FunctionKey },
2476         { K_F4, NSF4FunctionKey },
2477         { K_F5, NSF5FunctionKey },
2478         { K_F6, NSF6FunctionKey },
2479         { K_F7, NSF7FunctionKey },
2480         { K_F8, NSF8FunctionKey },
2481         { K_F9, NSF9FunctionKey },
2482         { K_F10, NSF10FunctionKey },
2483         { K_F11, NSF11FunctionKey },
2484         { K_F12, NSF12FunctionKey },
2485         { K_F13, NSF13FunctionKey },
2486         { K_F14, NSF14FunctionKey },
2487         { K_F15, NSF15FunctionKey },
2488         { K_F16, NSF16FunctionKey },
2489         { K_F17, NSF17FunctionKey },
2490         { K_F18, NSF18FunctionKey },
2491         { K_F19, NSF19FunctionKey },
2492         { K_F20, NSF20FunctionKey },
2493         { K_F21, NSF21FunctionKey },
2494         { K_F22, NSF22FunctionKey },
2495         { K_F23, NSF23FunctionKey },
2496         { K_F24, NSF24FunctionKey },
2497         { K_F25, NSF25FunctionKey },
2498         { K_F26, NSF26FunctionKey },
2499         { K_F27, NSF27FunctionKey },
2500         { K_F28, NSF28FunctionKey },
2501         { K_F29, NSF29FunctionKey },
2502         { K_F30, NSF30FunctionKey },
2503         { K_F31, NSF31FunctionKey },
2504         { K_F32, NSF32FunctionKey },
2505         { K_F33, NSF33FunctionKey },
2506         { K_F34, NSF34FunctionKey },
2507         { K_F35, NSF35FunctionKey },
2508         { K_DEL, NSBackspaceCharacter },
2509         { K_BS, NSDeleteCharacter },
2510         { K_HOME, NSHomeFunctionKey },
2511         { K_END, NSEndFunctionKey },
2512         { K_PAGEUP, NSPageUpFunctionKey },
2513         { K_PAGEDOWN, NSPageDownFunctionKey }
2514     };
2516     int i;
2517     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2518         if (sp2ns[i].special == key)
2519             return sp2ns[i].nskey;
2520     }
2522     return 0;