Open and save dialogs track the Vim pwd
[MacVim.git] / src / MacVim / MMBackend.m
blob608388bd6f299e0a81f7725e5d526a4dda674f35
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)queueVimStateMessage;
81 - (void)processInputQueue;
82 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
83 + (NSDictionary *)specialKeys;
84 - (void)handleInsertText:(NSData *)data;
85 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
86 - (void)queueMessage:(int)msgid data:(NSData *)data;
87 - (void)connectionDidDie:(NSNotification *)notification;
88 - (void)blinkTimerFired:(NSTimer *)timer;
89 - (void)focusChange:(BOOL)on;
90 - (void)handleToggleToolbar;
91 - (void)handleScrollbarEvent:(NSData *)data;
92 - (void)handleSetFont:(NSData *)data;
93 - (void)handleDropFiles:(NSData *)data;
94 - (void)handleDropString:(NSData *)data;
95 - (void)handleOdbEdit:(NSData *)data;
96 - (void)handleXcodeMod:(NSData *)data;
97 - (BOOL)checkForModifiedBuffers;
98 - (void)addInput:(NSString *)input;
99 @end
103 @interface MMBackend (ClientServer)
104 - (NSString *)connectionNameFromServerName:(NSString *)name;
105 - (NSConnection *)connectionForServerName:(NSString *)name;
106 - (NSConnection *)connectionForServerPort:(int)port;
107 - (void)serverConnectionDidDie:(NSNotification *)notification;
108 - (void)addClient:(NSDistantObject *)client;
109 - (NSString *)alternateServerNameForName:(NSString *)name;
110 @end
114 @implementation MMBackend
116 + (MMBackend *)sharedInstance
118     static MMBackend *singleton = nil;
119     return singleton ? singleton : (singleton = [MMBackend new]);
122 - (id)init
124     self = [super init];
125     if (!self) return nil;
127     fontContainerRef = loadFonts();
129     outputQueue = [[NSMutableArray alloc] init];
130     inputQueue = [[NSMutableArray alloc] init];
131     drawData = [[NSMutableData alloc] initWithCapacity:1024];
132     connectionNameDict = [[NSMutableDictionary alloc] init];
133     clientProxyDict = [[NSMutableDictionary alloc] init];
134     serverReplyDict = [[NSMutableDictionary alloc] init];
136     NSBundle *mainBundle = [NSBundle mainBundle];
137     NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
138     if (path)
139         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
141     path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
142     if (path)
143         sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
144             retain];
146     path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
147     if (path)
148         actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
150     if (!(colorDict && sysColorDict && actionDict))
151         NSLog(@"ERROR: Failed to load dictionaries.%@",
152                 MMSymlinkWarningString);
154     return self;
157 - (void)dealloc
159     //NSLog(@"%@ %s", [self className], _cmd);
160     [[NSNotificationCenter defaultCenter] removeObserver:self];
162     [oldWideFont release];  oldWideFont = nil;
163     [blinkTimer release];  blinkTimer = nil;
164     [alternateServerName release];  alternateServerName = nil;
165     [serverReplyDict release];  serverReplyDict = nil;
166     [clientProxyDict release];  clientProxyDict = nil;
167     [connectionNameDict release];  connectionNameDict = nil;
168     [inputQueue release];  inputQueue = nil;
169     [outputQueue release];  outputQueue = nil;
170     [drawData release];  drawData = nil;
171     [frontendProxy release];  frontendProxy = nil;
172     [connection release];  connection = nil;
173     [actionDict release];  actionDict = nil;
174     [sysColorDict release];  sysColorDict = nil;
175     [colorDict release];  colorDict = nil;
177     [super dealloc];
180 - (void)setBackgroundColor:(int)color
182     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
185 - (void)setForegroundColor:(int)color
187     foregroundColor = MM_COLOR(color);
190 - (void)setSpecialColor:(int)color
192     specialColor = MM_COLOR(color);
195 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
197     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
198     defaultForegroundColor = MM_COLOR(fg);
200     NSMutableData *data = [NSMutableData data];
202     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
203     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
205     [self queueMessage:SetDefaultColorsMsgID data:data];
208 - (NSConnection *)connection
210     if (!connection) {
211         // NOTE!  If the name of the connection changes here it must also be
212         // updated in MMAppController.m.
213         NSString *name = [NSString stringWithFormat:@"%@-connection",
214                [[NSBundle mainBundle] bundleIdentifier]];
216         connection = [NSConnection connectionWithRegisteredName:name host:nil];
217         [connection retain];
218     }
220     // NOTE: 'connection' may be nil here.
221     return connection;
224 - (NSDictionary *)actionDict
226     return actionDict;
229 - (BOOL)checkin
231     if (![self connection]) {
232         NSBundle *mainBundle = [NSBundle mainBundle];
233 #if 0
234         OSStatus status;
235         FSRef ref;
237         // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
238         // the API to pass Apple Event parameters is broken on 10.4).
239         NSString *path = [mainBundle bundlePath];
240         status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
241         if (noErr == status) {
242             // Pass parameter to the 'Open' Apple Event that tells MacVim not
243             // to open an untitled window.
244             NSAppleEventDescriptor *desc =
245                     [NSAppleEventDescriptor recordDescriptor];
246             [desc setParamDescriptor:
247                     [NSAppleEventDescriptor descriptorWithBoolean:NO]
248                           forKeyword:keyMMUntitledWindow];
250             LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
251                     kLSLaunchDefaults, NULL };
252             status = LSOpenFromRefSpec(&spec, NULL);
253         }
255         if (noErr != status) {
256         NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
257                 path, MMSymlinkWarningString);
258             return NO;
259         }
260 #else
261         // Launch MacVim using NSTask.  For some reason the above code using
262         // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
263         // fails, the dock icon starts bouncing and never stops).  It seems
264         // like rebuilding the Launch Services database takes care of this
265         // problem, but the NSTask way seems more stable so stick with it.
266         //
267         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
268         // that the GUI won't be activated (or raised) so there is a hack in
269         // MMAppController which raises the app when a new window is opened.
270         NSMutableArray *args = [NSMutableArray arrayWithObjects:
271             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
272         NSString *exeName = [[mainBundle infoDictionary]
273                 objectForKey:@"CFBundleExecutable"];
274         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
275         if (!path) {
276             NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
277                     MMSymlinkWarningString);
278             return NO;
279         }
281         [NSTask launchedTaskWithLaunchPath:path arguments:args];
282 #endif
284         // HACK!  Poll the mach bootstrap server until it returns a valid
285         // connection to detect that MacVim has finished launching.  Also set a
286         // time-out date so that we don't get stuck doing this forever.
287         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
288         while (![self connection] &&
289                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
290             [[NSRunLoop currentRunLoop]
291                     runMode:NSDefaultRunLoopMode
292                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
294         // NOTE: [self connection] will set 'connection' as a side-effect.
295         if (!connection) {
296             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
297             return NO;
298         }
299     }
301     BOOL ok = NO;
302     @try {
303         [[NSNotificationCenter defaultCenter] addObserver:self
304                 selector:@selector(connectionDidDie:)
305                     name:NSConnectionDidDieNotification object:connection];
307         id proxy = [connection rootProxy];
308         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
310         int pid = [[NSProcessInfo processInfo] processIdentifier];
312         frontendProxy = [proxy connectBackend:self pid:pid];
313         if (frontendProxy) {
314             [frontendProxy retain];
315             [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
316             ok = YES;
317         }
318     }
319     @catch (NSException *e) {
320         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
321     }
323     return ok;
326 - (BOOL)openVimWindow
328     [self queueMessage:OpenVimWindowMsgID data:nil];
329     return YES;
332 - (void)clearAll
334     int type = ClearAllDrawType;
336     // Any draw commands in queue are effectively obsolete since this clearAll
337     // will negate any effect they have, therefore we may as well clear the
338     // draw queue.
339     [drawData setLength:0];
341     [drawData appendBytes:&type length:sizeof(int)];
344 - (void)clearBlockFromRow:(int)row1 column:(int)col1
345                     toRow:(int)row2 column:(int)col2
347     int type = ClearBlockDrawType;
349     [drawData appendBytes:&type length:sizeof(int)];
351     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
352     [drawData appendBytes:&row1 length:sizeof(int)];
353     [drawData appendBytes:&col1 length:sizeof(int)];
354     [drawData appendBytes:&row2 length:sizeof(int)];
355     [drawData appendBytes:&col2 length:sizeof(int)];
358 - (void)deleteLinesFromRow:(int)row count:(int)count
359               scrollBottom:(int)bottom left:(int)left right:(int)right
361     int type = DeleteLinesDrawType;
363     [drawData appendBytes:&type length:sizeof(int)];
365     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
366     [drawData appendBytes:&row length:sizeof(int)];
367     [drawData appendBytes:&count length:sizeof(int)];
368     [drawData appendBytes:&bottom length:sizeof(int)];
369     [drawData appendBytes:&left length:sizeof(int)];
370     [drawData appendBytes:&right length:sizeof(int)];
373 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
374              cells:(int)cells flags:(int)flags
376     if (len <= 0 || cells <= 0) return;
378     int type = DrawStringDrawType;
380     [drawData appendBytes:&type length:sizeof(int)];
382     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
383     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
384     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
385     [drawData appendBytes:&row length:sizeof(int)];
386     [drawData appendBytes:&col length:sizeof(int)];
387     [drawData appendBytes:&cells length:sizeof(int)];
388     [drawData appendBytes:&flags length:sizeof(int)];
389     [drawData appendBytes:&len length:sizeof(int)];
390     [drawData appendBytes:s length:len];
393 - (void)insertLinesFromRow:(int)row count:(int)count
394               scrollBottom:(int)bottom left:(int)left right:(int)right
396     int type = InsertLinesDrawType;
398     [drawData appendBytes:&type length:sizeof(int)];
400     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
401     [drawData appendBytes:&row length:sizeof(int)];
402     [drawData appendBytes:&count length:sizeof(int)];
403     [drawData appendBytes:&bottom length:sizeof(int)];
404     [drawData appendBytes:&left length:sizeof(int)];
405     [drawData appendBytes:&right length:sizeof(int)];
408 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
409                fraction:(int)percent color:(int)color
411     int type = DrawCursorDrawType;
412     unsigned uc = MM_COLOR(color);
414     [drawData appendBytes:&type length:sizeof(int)];
416     [drawData appendBytes:&uc length:sizeof(unsigned)];
417     [drawData appendBytes:&row length:sizeof(int)];
418     [drawData appendBytes:&col length:sizeof(int)];
419     [drawData appendBytes:&shape length:sizeof(int)];
420     [drawData appendBytes:&percent length:sizeof(int)];
423 - (void)update
425     // Tend to the run loop, returning immediately if there are no events
426     // waiting.
427     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
428                              beforeDate:[NSDate distantPast]];
430 #if 0
431     // Keyboard and mouse input is handled directly, other input is queued and
432     // processed here.  This call may enter a blocking loop.
433     if ([inputQueue count] > 0)
434         [self processInputQueue];
435 #endif
438 - (void)flushQueue:(BOOL)force
440     // NOTE! This method gets called a lot; if we were to flush every time it
441     // got called MacVim would feel unresponsive.  So there is a time out which
442     // ensures that the queue isn't flushed too often.
443     if (exiting ||
444             (!force && lastFlushDate &&
445              -[lastFlushDate timeIntervalSinceNow] < MMFlushTimeoutInterval &&
446              [drawData length] < MMFlushQueueLenHint))
447         return;
449     if ([drawData length] > 0) {
450         // HACK!  Detect changes to 'guifontwide'.
451         if (gui.wide_font != (GuiFont)oldWideFont) {
452             [oldWideFont release];
453             oldWideFont = [(NSFont*)gui.wide_font retain];
454             [self setWideFont:oldWideFont];
455         }
457         int type = SetCursorPosDrawType;
458         [drawData appendBytes:&type length:sizeof(type)];
459         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
460         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
462         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
463         [drawData setLength:0];
464     }
466     if ([outputQueue count] > 0 || force) {
467         // When 'force' is set we always update the Vim state to ensure that
468         // MacVim has a copy of the latest state (since 'force' is typically
469         // set just before Vim takes a nap whilst waiting for input).
470         [self queueVimStateMessage];
472         @try {
473             [frontendProxy processCommandQueue:outputQueue];
474         }
475         @catch (NSException *e) {
476             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
477         }
479         [outputQueue removeAllObjects];
481         [lastFlushDate release];
482         lastFlushDate = [[NSDate date] retain];
483     }
486 - (BOOL)waitForInput:(int)milliseconds
488     //NSLog(@"|ENTER| %s%d",  _cmd, milliseconds);
490     // Only start the run loop if the input queue is empty, otherwise process
491     // the input first so that the input on queue isn't delayed.
492     if ([inputQueue count]) {
493         inputReceived = YES;
494     } else {
495         NSDate *date = milliseconds > 0 ?
496                 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
497                 [NSDate distantFuture];
499         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
500                                  beforeDate:date];
501     }
503     // I know of no way to figure out if the run loop exited because input was
504     // found or because of a time out, so I need to manually indicate when
505     // input was received in processInput:data: and then reset it every time
506     // here.
507     BOOL yn = inputReceived;
508     inputReceived = NO;
510     // Keyboard and mouse input is handled directly, other input is queued and
511     // processed here.  This call may enter a blocking loop.
512     if ([inputQueue count] > 0)
513         [self processInputQueue];
515     //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
516     return yn;
519 - (void)exit
521 #ifdef MAC_CLIENTSERVER
522     // The default connection is used for the client/server code.
523     [[NSConnection defaultConnection] setRootObject:nil];
524     [[NSConnection defaultConnection] invalidate];
525 #endif
527     // By invalidating the NSConnection the MMWindowController immediately
528     // finds out that the connection is down and as a result
529     // [MMWindowController connectionDidDie:] is invoked.
530     //NSLog(@"%@ %s", [self className], _cmd);
531     [[NSNotificationCenter defaultCenter] removeObserver:self];
532     [connection invalidate];
534     if (fontContainerRef) {
535         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
536         fontContainerRef = 0;
537     }
541 - (void)selectTab:(int)index
543     //NSLog(@"%s%d", _cmd, index);
545     index -= 1;
546     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
547     [self queueMessage:SelectTabMsgID data:data];
550 - (void)updateTabBar
552     //NSLog(@"%s", _cmd);
554     NSMutableData *data = [NSMutableData data];
556     int idx = tabpage_index(curtab) - 1;
557     [data appendBytes:&idx length:sizeof(int)];
559     tabpage_T *tp;
560     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
561         // This function puts the label of the tab in the global 'NameBuff'.
562         get_tabline_label(tp, FALSE);
563         char_u *s = NameBuff;
564         int len = STRLEN(s);
565         if (len <= 0) continue;
567 #ifdef FEAT_MBYTE
568         s = CONVERT_TO_UTF8(s);
569 #endif
571         // Count the number of windows in the tabpage.
572         //win_T *wp = tp->tp_firstwin;
573         //int wincount;
574         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
576         //[data appendBytes:&wincount length:sizeof(int)];
577         [data appendBytes:&len length:sizeof(int)];
578         [data appendBytes:s length:len];
580 #ifdef FEAT_MBYTE
581         CONVERT_TO_UTF8_FREE(s);
582 #endif
583     }
585     [self queueMessage:UpdateTabBarMsgID data:data];
588 - (BOOL)tabBarVisible
590     return tabBarVisible;
593 - (void)showTabBar:(BOOL)enable
595     tabBarVisible = enable;
597     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
598     [self queueMessage:msgid data:nil];
601 - (void)setRows:(int)rows columns:(int)cols
603     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
605     int dim[] = { rows, cols };
606     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
608     [self queueMessage:SetTextDimensionsMsgID data:data];
611 - (void)setWindowTitle:(char *)title
613     NSMutableData *data = [NSMutableData data];
614     int len = strlen(title);
615     if (len <= 0) return;
617     [data appendBytes:&len length:sizeof(int)];
618     [data appendBytes:title length:len];
620     [self queueMessage:SetWindowTitleMsgID data:data];
623 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
624                             saving:(int)saving
626     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
627     //        saving);
629     char_u *s = NULL;
630     NSString *ds = dir ? [NSString stringWithUTF8String:dir] : nil;
631     NSString *ts = title ? [NSString stringWithUTF8String:title] : nil;
632     @try {
633         [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
635         // Wait until a reply is sent from MMVimController.
636         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
637                                  beforeDate:[NSDate distantFuture]];
639         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
640             char_u *ret = (char_u*)[dialogReturn UTF8String];
641 #ifdef FEAT_MBYTE
642             ret = CONVERT_FROM_UTF8(ret);
643 #endif
644             s = vim_strsave(ret);
645 #ifdef FEAT_MBYTE
646             CONVERT_FROM_UTF8_FREE(ret);
647 #endif
648         }
650         [dialogReturn release];  dialogReturn = nil;
651     }
652     @catch (NSException *e) {
653         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
654     }
656     return (char *)s;
659 - (oneway void)setDialogReturn:(in bycopy id)obj
661     // NOTE: This is called by
662     //   - [MMVimController panelDidEnd:::], and
663     //   - [MMVimController alertDidEnd:::],
664     // to indicate that a save/open panel or alert has finished.
666     if (obj != dialogReturn) {
667         [dialogReturn release];
668         dialogReturn = [obj retain];
669     }
672 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
673                      buttons:(char *)btns textField:(char *)txtfield
675     int retval = 0;
676     NSString *message = nil, *text = nil, *textFieldString = nil;
677     NSArray *buttons = nil;
678     int style = NSInformationalAlertStyle;
680     if (VIM_WARNING == type) style = NSWarningAlertStyle;
681     else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
683     if (btns) {
684         NSString *btnString = [NSString stringWithUTF8String:btns];
685         buttons = [btnString componentsSeparatedByString:@"\n"];
686     }
687     if (title)
688         message = [NSString stringWithUTF8String:title];
689     if (msg) {
690         text = [NSString stringWithUTF8String:msg];
691         if (!message) {
692             // HACK! If there is a '\n\n' or '\n' sequence in the message, then
693             // make the part up to there into the title.  We only do this
694             // because Vim has lots of dialogs without a title and they look
695             // ugly that way.
696             // TODO: Fix the actual dialog texts.
697             NSRange eolRange = [text rangeOfString:@"\n\n"];
698             if (NSNotFound == eolRange.location)
699                 eolRange = [text rangeOfString:@"\n"];
700             if (NSNotFound != eolRange.location) {
701                 message = [text substringToIndex:eolRange.location];
702                 text = [text substringFromIndex:NSMaxRange(eolRange)];
703             }
704         }
705     }
706     if (txtfield)
707         textFieldString = [NSString stringWithUTF8String:txtfield];
709     @try {
710         [frontendProxy presentDialogWithStyle:style message:message
711                               informativeText:text buttonTitles:buttons
712                               textFieldString:textFieldString];
714         // Wait until a reply is sent from MMVimController.
715         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
716                                  beforeDate:[NSDate distantFuture]];
718         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
719                 && [dialogReturn count]) {
720             retval = [[dialogReturn objectAtIndex:0] intValue];
721             if (txtfield && [dialogReturn count] > 1) {
722                 NSString *retString = [dialogReturn objectAtIndex:1];
723                 char_u *ret = (char_u*)[retString UTF8String];
724 #ifdef FEAT_MBYTE
725                 ret = CONVERT_FROM_UTF8(ret);
726 #endif
727                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
728 #ifdef FEAT_MBYTE
729                 CONVERT_FROM_UTF8_FREE(ret);
730 #endif
731             }
732         }
734         [dialogReturn release]; dialogReturn = nil;
735     }
736     @catch (NSException *e) {
737         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
738     }
740     return retval;
743 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
744                atIndex:(int)index
746     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
747     //        name, index);
749     int namelen = name ? strlen(name) : 0;
750     NSMutableData *data = [NSMutableData data];
752     [data appendBytes:&tag length:sizeof(int)];
753     [data appendBytes:&parentTag length:sizeof(int)];
754     [data appendBytes:&namelen length:sizeof(int)];
755     if (namelen > 0) [data appendBytes:name length:namelen];
756     [data appendBytes:&index length:sizeof(int)];
758     [self queueMessage:AddMenuMsgID data:data];
761 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
762                        tip:(char *)tip icon:(char *)icon
763              keyEquivalent:(int)key modifiers:(int)mods
764                     action:(char *)action isAlternate:(int)isAlt
765                    atIndex:(int)index
767     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
768     //        parentTag, name, tip, index);
770     int namelen = name ? strlen(name) : 0;
771     int tiplen = tip ? strlen(tip) : 0;
772     int iconlen = icon ? strlen(icon) : 0;
773     int eventFlags = vimModMaskToEventModifierFlags(mods);
774     int actionlen = action ? strlen(action) : 0;
775     NSMutableData *data = [NSMutableData data];
777     key = specialKeyToNSKey(key);
779     [data appendBytes:&tag length:sizeof(int)];
780     [data appendBytes:&parentTag length:sizeof(int)];
781     [data appendBytes:&namelen length:sizeof(int)];
782     if (namelen > 0) [data appendBytes:name length:namelen];
783     [data appendBytes:&tiplen length:sizeof(int)];
784     if (tiplen > 0) [data appendBytes:tip length:tiplen];
785     [data appendBytes:&iconlen length:sizeof(int)];
786     if (iconlen > 0) [data appendBytes:icon length:iconlen];
787     [data appendBytes:&actionlen length:sizeof(int)];
788     if (actionlen > 0) [data appendBytes:action length:actionlen];
789     [data appendBytes:&index length:sizeof(int)];
790     [data appendBytes:&key length:sizeof(int)];
791     [data appendBytes:&eventFlags length:sizeof(int)];
792     [data appendBytes:&isAlt length:sizeof(int)];
794     [self queueMessage:AddMenuItemMsgID data:data];
797 - (void)removeMenuItemWithTag:(int)tag
799     NSMutableData *data = [NSMutableData data];
800     [data appendBytes:&tag length:sizeof(int)];
802     [self queueMessage:RemoveMenuItemMsgID data:data];
805 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
807     NSMutableData *data = [NSMutableData data];
809     [data appendBytes:&tag length:sizeof(int)];
810     [data appendBytes:&enabled length:sizeof(int)];
812     [self queueMessage:EnableMenuItemMsgID data:data];
815 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
817     int len = strlen(name);
818     int row = -1, col = -1;
820     if (len <= 0) return;
822     if (!mouse && curwin) {
823         row = curwin->w_wrow;
824         col = curwin->w_wcol;
825     }
827     NSMutableData *data = [NSMutableData data];
829     [data appendBytes:&row length:sizeof(int)];
830     [data appendBytes:&col length:sizeof(int)];
831     [data appendBytes:&len length:sizeof(int)];
832     [data appendBytes:name length:len];
834     [self queueMessage:ShowPopupMenuMsgID data:data];
837 - (void)showToolbar:(int)enable flags:(int)flags
839     NSMutableData *data = [NSMutableData data];
841     [data appendBytes:&enable length:sizeof(int)];
842     [data appendBytes:&flags length:sizeof(int)];
844     [self queueMessage:ShowToolbarMsgID data:data];
847 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
849     NSMutableData *data = [NSMutableData data];
851     [data appendBytes:&ident length:sizeof(long)];
852     [data appendBytes:&type length:sizeof(int)];
854     [self queueMessage:CreateScrollbarMsgID data:data];
857 - (void)destroyScrollbarWithIdentifier:(long)ident
859     NSMutableData *data = [NSMutableData data];
860     [data appendBytes:&ident length:sizeof(long)];
862     [self queueMessage:DestroyScrollbarMsgID data:data];
865 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
867     NSMutableData *data = [NSMutableData data];
869     [data appendBytes:&ident length:sizeof(long)];
870     [data appendBytes:&visible length:sizeof(int)];
872     [self queueMessage:ShowScrollbarMsgID data:data];
875 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
877     NSMutableData *data = [NSMutableData data];
879     [data appendBytes:&ident length:sizeof(long)];
880     [data appendBytes:&pos length:sizeof(int)];
881     [data appendBytes:&len length:sizeof(int)];
883     [self queueMessage:SetScrollbarPositionMsgID data:data];
886 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
887                     identifier:(long)ident
889     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
890     float prop = (float)size/(max+1);
891     if (fval < 0) fval = 0;
892     else if (fval > 1.0f) fval = 1.0f;
893     if (prop < 0) prop = 0;
894     else if (prop > 1.0f) prop = 1.0f;
896     NSMutableData *data = [NSMutableData data];
898     [data appendBytes:&ident length:sizeof(long)];
899     [data appendBytes:&fval length:sizeof(float)];
900     [data appendBytes:&prop length:sizeof(float)];
902     [self queueMessage:SetScrollbarThumbMsgID data:data];
905 - (void)setFont:(NSFont *)font
907     NSString *fontName = [font displayName];
908     float size = [font pointSize];
909     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
910     if (len > 0) {
911         NSMutableData *data = [NSMutableData data];
913         [data appendBytes:&size length:sizeof(float)];
914         [data appendBytes:&len length:sizeof(int)];
915         [data appendBytes:[fontName UTF8String] length:len];
917         [self queueMessage:SetFontMsgID data:data];
918     }
921 - (void)setWideFont:(NSFont *)font
923     NSString *fontName = [font displayName];
924     float size = [font pointSize];
925     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
926     NSMutableData *data = [NSMutableData data];
928     [data appendBytes:&size length:sizeof(float)];
929     [data appendBytes:&len length:sizeof(int)];
930     if (len > 0)
931         [data appendBytes:[fontName UTF8String] length:len];
933     [self queueMessage:SetWideFontMsgID data:data];
936 - (void)executeActionWithName:(NSString *)name
938     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
940     if (len > 0) {
941         NSMutableData *data = [NSMutableData data];
943         [data appendBytes:&len length:sizeof(int)];
944         [data appendBytes:[name UTF8String] length:len];
946         [self queueMessage:ExecuteActionMsgID data:data];
947     }
950 - (void)setMouseShape:(int)shape
952     NSMutableData *data = [NSMutableData data];
953     [data appendBytes:&shape length:sizeof(int)];
954     [self queueMessage:SetMouseShapeMsgID data:data];
957 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
959     // Vim specifies times in milliseconds, whereas Cocoa wants them in
960     // seconds.
961     blinkWaitInterval = .001f*wait;
962     blinkOnInterval = .001f*on;
963     blinkOffInterval = .001f*off;
966 - (void)startBlink
968     if (blinkTimer) {
969         [blinkTimer invalidate];
970         [blinkTimer release];
971         blinkTimer = nil;
972     }
974     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
975             && gui.in_focus) {
976         blinkState = MMBlinkStateOn;
977         blinkTimer =
978             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
979                                               target:self
980                                             selector:@selector(blinkTimerFired:)
981                                             userInfo:nil repeats:NO] retain];
982         gui_update_cursor(TRUE, FALSE);
983         [self flushQueue:YES];
984     }
987 - (void)stopBlink
989     if (MMBlinkStateOff == blinkState) {
990         gui_update_cursor(TRUE, FALSE);
991         [self flushQueue:YES];
992     }
994     blinkState = MMBlinkStateNone;
997 - (void)adjustLinespace:(int)linespace
999     NSMutableData *data = [NSMutableData data];
1000     [data appendBytes:&linespace length:sizeof(int)];
1001     [self queueMessage:AdjustLinespaceMsgID data:data];
1004 - (void)activate
1006     [self queueMessage:ActivateMsgID data:nil];
1009 - (void)setPreEditRow:(int)row column:(int)col
1011     NSMutableData *data = [NSMutableData data];
1012     [data appendBytes:&row length:sizeof(int)];
1013     [data appendBytes:&col length:sizeof(int)];
1014     [self queueMessage:SetPreEditPositionMsgID data:data];
1017 - (int)lookupColorWithKey:(NSString *)key
1019     if (!(key && [key length] > 0))
1020         return INVALCOLOR;
1022     NSString *stripKey = [[[[key lowercaseString]
1023         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
1024             componentsSeparatedByString:@" "]
1025                componentsJoinedByString:@""];
1027     if (stripKey && [stripKey length] > 0) {
1028         // First of all try to lookup key in the color dictionary; note that
1029         // all keys in this dictionary are lowercase with no whitespace.
1030         id obj = [colorDict objectForKey:stripKey];
1031         if (obj) return [obj intValue];
1033         // The key was not in the dictionary; is it perhaps of the form
1034         // #rrggbb?
1035         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
1036             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
1037             [scanner setScanLocation:1];
1038             unsigned hex = 0;
1039             if ([scanner scanHexInt:&hex]) {
1040                 return (int)hex;
1041             }
1042         }
1044         // As a last resort, check if it is one of the system defined colors.
1045         // The keys in this dictionary are also lowercase with no whitespace.
1046         obj = [sysColorDict objectForKey:stripKey];
1047         if (obj) {
1048             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
1049             if (col) {
1050                 float r, g, b, a;
1051                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
1052                 [col getRed:&r green:&g blue:&b alpha:&a];
1053                 return (((int)(r*255+.5f) & 0xff) << 16)
1054                      + (((int)(g*255+.5f) & 0xff) << 8)
1055                      +  ((int)(b*255+.5f) & 0xff);
1056             }
1057         }
1058     }
1060     //NSLog(@"WARNING: No color with key %@ found.", stripKey);
1061     return INVALCOLOR;
1064 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
1066     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1067     id obj;
1069     while ((obj = [e nextObject])) {
1070         if ([value isEqual:obj])
1071             return YES;
1072     }
1074     return NO;
1077 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1079     NSMutableData *data = [NSMutableData data];
1080     [data appendBytes:&fuoptions length:sizeof(int)];
1081     bg = MM_COLOR(bg);
1082     [data appendBytes:&bg length:sizeof(int)];
1083     [self queueMessage:EnterFullscreenMsgID data:data];
1086 - (void)leaveFullscreen
1088     [self queueMessage:LeaveFullscreenMsgID data:nil];
1091 - (void)setAntialias:(BOOL)antialias
1093     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1095     [self queueMessage:msgid data:nil];
1098 - (void)updateModifiedFlag
1100     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1101     // vice versa.
1102     int msgid = [self checkForModifiedBuffers]
1103             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1105     [self queueMessage:msgid data:nil];
1108 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1110     // NOTE: This method might get called whenever the run loop is tended to.
1111     // Normal keyboard and mouse input is added to input buffers, so there is
1112     // no risk in handling these events directly (they return immediately, and
1113     // do not call any other Vim functions).  However, other events such
1114     // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
1115     // events which would cause this method to be called recursively.  This
1116     // in turn leads to various difficulties that we do not want to have to
1117     // deal with.  To avoid recursive calls here we add all events except
1118     // keyboard and mouse events to an input queue which is processed whenever
1119     // gui_mch_update() is called (see processInputQueue).
1121     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1123     // Don't flush too soon after receiving input or update speed will suffer.
1124     [lastFlushDate release];
1125     lastFlushDate = [[NSDate date] retain];
1127     // Handle keyboard and mouse input now.  All other events are queued.
1128     if (InsertTextMsgID == msgid) {
1129         [self handleInsertText:data];
1130     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1131         if (!data) return;
1132         const void *bytes = [data bytes];
1133         int mods = *((int*)bytes);  bytes += sizeof(int);
1134         int len = *((int*)bytes);  bytes += sizeof(int);
1135         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1136                                               encoding:NSUTF8StringEncoding];
1137         mods = eventModifierFlagsToVimModMask(mods);
1139         [self handleKeyDown:key modifiers:mods];
1141         [key release];
1142     } else if (ScrollWheelMsgID == msgid) {
1143         if (!data) return;
1144         const void *bytes = [data bytes];
1146         int row = *((int*)bytes);  bytes += sizeof(int);
1147         int col = *((int*)bytes);  bytes += sizeof(int);
1148         int flags = *((int*)bytes);  bytes += sizeof(int);
1149         float dy = *((float*)bytes);  bytes += sizeof(float);
1151         int button = MOUSE_5;
1152         if (dy > 0) button = MOUSE_4;
1154         flags = eventModifierFlagsToVimMouseModMask(flags);
1156         int numLines = (int)round(dy);
1157         if (numLines < 0) numLines = -numLines;
1158         if (numLines == 0) numLines = 1;
1160 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1161         gui.scroll_wheel_force = numLines;
1162 #endif
1164         gui_send_mouse_event(button, col, row, NO, flags);
1165     } else if (MouseDownMsgID == msgid) {
1166         if (!data) return;
1167         const void *bytes = [data bytes];
1169         int row = *((int*)bytes);  bytes += sizeof(int);
1170         int col = *((int*)bytes);  bytes += sizeof(int);
1171         int button = *((int*)bytes);  bytes += sizeof(int);
1172         int flags = *((int*)bytes);  bytes += sizeof(int);
1173         int count = *((int*)bytes);  bytes += sizeof(int);
1175         button = eventButtonNumberToVimMouseButton(button);
1176         if (button >= 0) {
1177             flags = eventModifierFlagsToVimMouseModMask(flags);
1178             gui_send_mouse_event(button, col, row, count>1, flags);
1179         }
1180     } else if (MouseUpMsgID == msgid) {
1181         if (!data) return;
1182         const void *bytes = [data bytes];
1184         int row = *((int*)bytes);  bytes += sizeof(int);
1185         int col = *((int*)bytes);  bytes += sizeof(int);
1186         int flags = *((int*)bytes);  bytes += sizeof(int);
1188         flags = eventModifierFlagsToVimMouseModMask(flags);
1190         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1191     } else if (MouseDraggedMsgID == msgid) {
1192         if (!data) return;
1193         const void *bytes = [data bytes];
1195         int row = *((int*)bytes);  bytes += sizeof(int);
1196         int col = *((int*)bytes);  bytes += sizeof(int);
1197         int flags = *((int*)bytes);  bytes += sizeof(int);
1199         flags = eventModifierFlagsToVimMouseModMask(flags);
1201         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1202     } else if (MouseMovedMsgID == msgid) {
1203         const void *bytes = [data bytes];
1204         int row = *((int*)bytes);  bytes += sizeof(int);
1205         int col = *((int*)bytes);  bytes += sizeof(int);
1207         gui_mouse_moved(col, row);
1208     } else if (AddInputMsgID == msgid) {
1209         NSString *string = [[NSString alloc] initWithData:data
1210                 encoding:NSUTF8StringEncoding];
1211         if (string) {
1212             [self addInput:string];
1213             [string release];
1214         }
1215     } else if (TerminateNowMsgID == msgid) {
1216         isTerminating = YES;
1217     } else {
1218         // Not keyboard or mouse event, queue it and handle later.
1219         //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
1220         [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1221         [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
1222     }
1224     // See waitForInput: for an explanation of this flag.
1225     inputReceived = YES;
1228 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1230     // TODO: Get rid of this method?
1231     //NSLog(@"%s%@", _cmd, messages);
1233     unsigned i, count = [messages count];
1234     for (i = 0; i < count; i += 2) {
1235         int msgid = [[messages objectAtIndex:i] intValue];
1236         id data = [messages objectAtIndex:i+1];
1237         if ([data isEqual:[NSNull null]])
1238             data = nil;
1240         [self processInput:msgid data:data];
1241     }
1244 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1246     NSString *eval = nil;
1247     char_u *s = (char_u*)[expr UTF8String];
1249 #ifdef FEAT_MBYTE
1250     s = CONVERT_FROM_UTF8(s);
1251 #endif
1253     char_u *res = eval_client_expr_to_string(s);
1255 #ifdef FEAT_MBYTE
1256     CONVERT_FROM_UTF8_FREE(s);
1257 #endif
1259     if (res != NULL) {
1260         s = res;
1261 #ifdef FEAT_MBYTE
1262         s = CONVERT_TO_UTF8(s);
1263 #endif
1264         eval = [NSString stringWithUTF8String:(char*)s];
1265 #ifdef FEAT_MBYTE
1266         CONVERT_TO_UTF8_FREE(s);
1267 #endif
1268         vim_free(res);
1269     }
1271     return eval;
1274 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1276     // TODO: This method should share code with clip_mch_request_selection().
1278     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1279         // If there is no pasteboard, return YES to indicate that there is text
1280         // to copy.
1281         if (!pboard)
1282             return YES;
1284         clip_copy_selection();
1286         // Get the text to put on the pasteboard.
1287         long_u llen = 0; char_u *str = 0;
1288         int type = clip_convert_selection(&str, &llen, &clip_star);
1289         if (type < 0)
1290             return NO;
1291         
1292         // TODO: Avoid overflow.
1293         int len = (int)llen;
1294 #ifdef FEAT_MBYTE
1295         if (output_conv.vc_type != CONV_NONE) {
1296             char_u *conv_str = string_convert(&output_conv, str, &len);
1297             if (conv_str) {
1298                 vim_free(str);
1299                 str = conv_str;
1300             }
1301         }
1302 #endif
1304         NSString *string = [[NSString alloc]
1305             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1307         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1308         [pboard declareTypes:types owner:nil];
1309         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1310     
1311         [string release];
1312         vim_free(str);
1314         return ok;
1315     }
1317     return NO;
1320 - (oneway void)addReply:(in bycopy NSString *)reply
1321                  server:(in byref id <MMVimServerProtocol>)server
1323     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1325     // Replies might come at any time and in any order so we keep them in an
1326     // array inside a dictionary with the send port used as key.
1328     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1329     // HACK! Assume connection uses mach ports.
1330     int port = [(NSMachPort*)[conn sendPort] machPort];
1331     NSNumber *key = [NSNumber numberWithInt:port];
1333     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1334     if (!replies) {
1335         replies = [NSMutableArray array];
1336         [serverReplyDict setObject:replies forKey:key];
1337     }
1339     [replies addObject:reply];
1342 - (void)addInput:(in bycopy NSString *)input
1343                  client:(in byref id <MMVimClientProtocol>)client
1345     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1347     [self addInput:input];
1348     [self addClient:(id)client];
1350     inputReceived = YES;
1353 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1354                  client:(in byref id <MMVimClientProtocol>)client
1356     [self addClient:(id)client];
1357     return [self evaluateExpression:expr];
1360 - (void)registerServerWithName:(NSString *)name
1362     NSString *svrName = name;
1363     NSConnection *svrConn = [NSConnection defaultConnection];
1364     unsigned i;
1366     for (i = 0; i < MMServerMax; ++i) {
1367         NSString *connName = [self connectionNameFromServerName:svrName];
1369         if ([svrConn registerName:connName]) {
1370             //NSLog(@"Registered server with name: %@", svrName);
1372             // TODO: Set request/reply time-outs to something else?
1373             //
1374             // Don't wait for requests (time-out means that the message is
1375             // dropped).
1376             [svrConn setRequestTimeout:0];
1377             //[svrConn setReplyTimeout:MMReplyTimeout];
1378             [svrConn setRootObject:self];
1380             char_u *s = (char_u*)[svrName UTF8String];
1381 #ifdef FEAT_MBYTE
1382             s = CONVERT_FROM_UTF8(s);
1383 #endif
1384             // NOTE: 'serverName' is a global variable
1385             serverName = vim_strsave(s);
1386 #ifdef FEAT_MBYTE
1387             CONVERT_FROM_UTF8_FREE(s);
1388 #endif
1389 #ifdef FEAT_EVAL
1390             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1391 #endif
1392 #ifdef FEAT_TITLE
1393             need_maketitle = TRUE;
1394 #endif
1395             [self queueMessage:SetServerNameMsgID data:
1396                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1397             break;
1398         }
1400         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1401     }
1404 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1405                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1406               silent:(BOOL)silent
1408     // NOTE: If 'name' equals 'serverName' then the request is local (client
1409     // and server are the same).  This case is not handled separately, so a
1410     // connection will be set up anyway (this simplifies the code).
1412     NSConnection *conn = [self connectionForServerName:name];
1413     if (!conn) {
1414         if (!silent) {
1415             char_u *s = (char_u*)[name UTF8String];
1416 #ifdef FEAT_MBYTE
1417             s = CONVERT_FROM_UTF8(s);
1418 #endif
1419             EMSG2(_(e_noserver), s);
1420 #ifdef FEAT_MBYTE
1421             CONVERT_FROM_UTF8_FREE(s);
1422 #endif
1423         }
1424         return NO;
1425     }
1427     if (port) {
1428         // HACK! Assume connection uses mach ports.
1429         *port = [(NSMachPort*)[conn sendPort] machPort];
1430     }
1432     id proxy = [conn rootProxy];
1433     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1435     @try {
1436         if (expr) {
1437             NSString *eval = [proxy evaluateExpression:string client:self];
1438             if (reply) {
1439                 if (eval) {
1440                     char_u *r = (char_u*)[eval UTF8String];
1441 #ifdef FEAT_MBYTE
1442                     r = CONVERT_FROM_UTF8(r);
1443 #endif
1444                     *reply = vim_strsave(r);
1445 #ifdef FEAT_MBYTE
1446                     CONVERT_FROM_UTF8_FREE(r);
1447 #endif
1448                 } else {
1449                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1450                 }
1451             }
1453             if (!eval)
1454                 return NO;
1455         } else {
1456             [proxy addInput:string client:self];
1457         }
1458     }
1459     @catch (NSException *e) {
1460         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1461         return NO;
1462     }
1464     return YES;
1467 - (NSArray *)serverList
1469     NSArray *list = nil;
1471     if ([self connection]) {
1472         id proxy = [connection rootProxy];
1473         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1475         @try {
1476             list = [proxy serverList];
1477         }
1478         @catch (NSException *e) {
1479             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1480         }
1481     } else {
1482         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1483     }
1485     return list;
1488 - (NSString *)peekForReplyOnPort:(int)port
1490     //NSLog(@"%s%d", _cmd, port);
1492     NSNumber *key = [NSNumber numberWithInt:port];
1493     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1494     if (replies && [replies count]) {
1495         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1496         //        [replies objectAtIndex:0]);
1497         return [replies objectAtIndex:0];
1498     }
1500     //NSLog(@"    No replies");
1501     return nil;
1504 - (NSString *)waitForReplyOnPort:(int)port
1506     //NSLog(@"%s%d", _cmd, port);
1507     
1508     NSConnection *conn = [self connectionForServerPort:port];
1509     if (!conn)
1510         return nil;
1512     NSNumber *key = [NSNumber numberWithInt:port];
1513     NSMutableArray *replies = nil;
1514     NSString *reply = nil;
1516     // Wait for reply as long as the connection to the server is valid (unless
1517     // user interrupts wait with Ctrl-C).
1518     while (!got_int && [conn isValid] &&
1519             !(replies = [serverReplyDict objectForKey:key])) {
1520         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1521                                  beforeDate:[NSDate distantFuture]];
1522     }
1524     if (replies) {
1525         if ([replies count] > 0) {
1526             reply = [[replies objectAtIndex:0] retain];
1527             //NSLog(@"    Got reply: %@", reply);
1528             [replies removeObjectAtIndex:0];
1529             [reply autorelease];
1530         }
1532         if ([replies count] == 0)
1533             [serverReplyDict removeObjectForKey:key];
1534     }
1536     return reply;
1539 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1541     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1542     if (client) {
1543         @try {
1544             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1545             [client addReply:reply server:self];
1546             return YES;
1547         }
1548         @catch (NSException *e) {
1549             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1550         }
1551     } else {
1552         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1553     }
1555     return NO;
1558 @end // MMBackend
1562 @implementation MMBackend (Private)
1564 - (void)queueVimStateMessage
1566     // NOTE: This is the place to add Vim state that needs to be accessed from
1567     // MacVim.  Do not add state that could potentially require lots of memory
1568     // since this message gets sent each time the output queue is forcibly
1569     // flushed (e.g. storing the currently selected text would be a bad idea).
1570     // We take this approach of "pushing" the state to MacVim to avoid having
1571     // to make synchronous calls from MacVim to Vim in order to get state.
1572     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1573         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1574         nil];
1576     [self queueMessage:SetVimStateMsgID data:[vimState dictionaryAsData]];
1579 - (void)processInputQueue
1581     // NOTE: One of the input events may cause this method to be called
1582     // recursively, so copy the input queue to a local variable and clear it
1583     // before starting to process input events (otherwise we could get stuck in
1584     // an endless loop).
1585     NSArray *q = [inputQueue copy];
1586     unsigned i, count = [q count];
1588     [inputQueue removeAllObjects];
1590     for (i = 0; i < count-1; i += 2) {
1591         int msgid = [[q objectAtIndex:i] intValue];
1592         id data = [q objectAtIndex:i+1];
1593         if ([data isEqual:[NSNull null]])
1594             data = nil;
1596         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1597         [self handleInputEvent:msgid data:data];
1598     }
1600     [q release];
1601     //NSLog(@"Clear input event queue");
1604 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1606     // NOTE: Be careful with what you do in this method.  Ideally, a message
1607     // should be handled by adding something to the input buffer and returning
1608     // immediately.  If you call a Vim function then it should not enter a loop
1609     // waiting for key presses or in any other way block the process.  The
1610     // reason for this being that only one message can be processed at a time,
1611     // so if another message is received while processing, then the new message
1612     // is dropped.  See also the comment in processInput:data:.
1614     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1616     if (SelectTabMsgID == msgid) {
1617         if (!data) return;
1618         const void *bytes = [data bytes];
1619         int idx = *((int*)bytes) + 1;
1620         //NSLog(@"Selecting tab %d", idx);
1621         send_tabline_event(idx);
1622     } else if (CloseTabMsgID == msgid) {
1623         if (!data) return;
1624         const void *bytes = [data bytes];
1625         int idx = *((int*)bytes) + 1;
1626         //NSLog(@"Closing tab %d", idx);
1627         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1628     } else if (AddNewTabMsgID == msgid) {
1629         //NSLog(@"Adding new tab");
1630         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1631     } else if (DraggedTabMsgID == msgid) {
1632         if (!data) return;
1633         const void *bytes = [data bytes];
1634         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1635         // based.
1636         int idx = *((int*)bytes);
1638         tabpage_move(idx);
1639     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) {
1640         if (!data) return;
1641         const void *bytes = [data bytes];
1642         int rows = *((int*)bytes);  bytes += sizeof(int);
1643         int cols = *((int*)bytes);  bytes += sizeof(int);
1645         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1646         // gui_resize_shell(), so we have to manually set the rows and columns
1647         // here.  (MacVim doesn't change the rows and columns to avoid
1648         // inconsistent states between Vim and MacVim.)
1649         [self queueMessage:msgid data:data];
1651         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1652         gui_resize_shell(cols, rows);
1653     } else if (ExecuteMenuMsgID == msgid) {
1654         if (!data) return;
1655         const void *bytes = [data bytes];
1656         int tag = *((int*)bytes);  bytes += sizeof(int);
1658         vimmenu_T *menu = (vimmenu_T*)tag;
1659         // TODO!  Make sure 'menu' is a valid menu pointer!
1660         if (menu) {
1661             gui_menu_cb(menu);
1662         }
1663     } else if (ToggleToolbarMsgID == msgid) {
1664         [self handleToggleToolbar];
1665     } else if (ScrollbarEventMsgID == msgid) {
1666         [self handleScrollbarEvent:data];
1667     } else if (SetFontMsgID == msgid) {
1668         [self handleSetFont:data];
1669     } else if (VimShouldCloseMsgID == msgid) {
1670         gui_shell_closed();
1671     } else if (DropFilesMsgID == msgid) {
1672         [self handleDropFiles:data];
1673     } else if (DropStringMsgID == msgid) {
1674         [self handleDropString:data];
1675     } else if (GotFocusMsgID == msgid) {
1676         if (!gui.in_focus)
1677             [self focusChange:YES];
1678     } else if (LostFocusMsgID == msgid) {
1679         if (gui.in_focus)
1680             [self focusChange:NO];
1681     } else if (SetMouseShapeMsgID == msgid) {
1682         const void *bytes = [data bytes];
1683         int shape = *((int*)bytes);  bytes += sizeof(int);
1684         update_mouseshape(shape);
1685     } else if (ODBEditMsgID == msgid) {
1686         [self handleOdbEdit:data];
1687     } else if (XcodeModMsgID == msgid) {
1688         [self handleXcodeMod:data];
1689     } else {
1690         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1691     }
1694 + (NSDictionary *)specialKeys
1696     static NSDictionary *specialKeys = nil;
1698     if (!specialKeys) {
1699         NSBundle *mainBundle = [NSBundle mainBundle];
1700         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1701                                               ofType:@"plist"];
1702         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1703     }
1705     return specialKeys;
1708 - (void)handleInsertText:(NSData *)data
1710     if (!data) return;
1712     NSString *key = [[NSString alloc] initWithData:data
1713                                           encoding:NSUTF8StringEncoding];
1714     char_u *str = (char_u*)[key UTF8String];
1715     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1717 #ifdef FEAT_MBYTE
1718     char_u *conv_str = NULL;
1719     if (input_conv.vc_type != CONV_NONE) {
1720         conv_str = string_convert(&input_conv, str, &len);
1721         if (conv_str)
1722             str = conv_str;
1723     }
1724 #endif
1726     if (len == 1 && ((str[0] == Ctrl_C && ctrl_c_interrupts)
1727             || (str[0] == intr_char && intr_char != Ctrl_C))) {
1728         trash_input_buf();
1729         got_int = TRUE;
1730     }
1732     for (i = 0; i < len; ++i) {
1733         add_to_input_buf(str+i, 1);
1734         if (CSI == str[i]) {
1735             // NOTE: If the converted string contains the byte CSI, then it
1736             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1737             // won't work.
1738             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1739             add_to_input_buf(extra, 2);
1740         }
1741     }
1743 #ifdef FEAT_MBYTE
1744     if (conv_str)
1745         vim_free(conv_str);
1746 #endif
1747     [key release];
1750 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1752     char_u special[3];
1753     char_u modChars[3];
1754     char_u *chars = (char_u*)[key UTF8String];
1755 #ifdef FEAT_MBYTE
1756     char_u *conv_str = NULL;
1757 #endif
1758     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1760     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1761     // that new keys can easily be added.
1762     NSString *specialString = [[MMBackend specialKeys]
1763             objectForKey:key];
1764     if (specialString && [specialString length] > 1) {
1765         //NSLog(@"special key: %@", specialString);
1766         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1767                 [specialString characterAtIndex:1]);
1769         ikey = simplify_key(ikey, &mods);
1770         if (ikey == CSI)
1771             ikey = K_CSI;
1773         special[0] = CSI;
1774         special[1] = K_SECOND(ikey);
1775         special[2] = K_THIRD(ikey);
1777         chars = special;
1778         length = 3;
1779     } else if (1 == length && TAB == chars[0]) {
1780         // Tab is a trouble child:
1781         // - <Tab> is added to the input buffer as is
1782         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1783         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1784         //   to be converted to utf-8
1785         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1786         // - <C-Tab> is reserved by Mac OS X
1787         // - <D-Tab> is reserved by Mac OS X
1788         chars = special;
1789         special[0] = TAB;
1790         length = 1;
1792         if (mods & MOD_MASK_SHIFT) {
1793             mods &= ~MOD_MASK_SHIFT;
1794             special[0] = CSI;
1795             special[1] = K_SECOND(K_S_TAB);
1796             special[2] = K_THIRD(K_S_TAB);
1797             length = 3;
1798         } else if (mods & MOD_MASK_ALT) {
1799             int mtab = 0x80 | TAB;
1800 #ifdef FEAT_MBYTE
1801             if (enc_utf8) {
1802                 // Convert to utf-8
1803                 special[0] = (mtab >> 6) + 0xc0;
1804                 special[1] = mtab & 0xbf;
1805                 length = 2;
1806             } else
1807 #endif
1808             {
1809                 special[0] = mtab;
1810                 length = 1;
1811             }
1812             mods &= ~MOD_MASK_ALT;
1813         }
1814     } else if (length > 0) {
1815         unichar c = [key characterAtIndex:0];
1817         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1818         //        [key characterAtIndex:0], mods);
1820         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1821                 || (c == intr_char && intr_char != Ctrl_C))) {
1822             trash_input_buf();
1823             got_int = TRUE;
1824         }
1826         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1827         // cleared since they are already added to the key by the AppKit.
1828         // Unfortunately, the only way to deal with when to clear the modifiers
1829         // or not seems to be to have hard-wired rules like this.
1830         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1831                     || 0x9 == c || 0xd == c || ESC == c) ) {
1832             mods &= ~MOD_MASK_SHIFT;
1833             mods &= ~MOD_MASK_CTRL;
1834             //NSLog(@"clear shift ctrl");
1835         }
1837         // HACK!  All Option+key presses go via 'insert text' messages, except
1838         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1839         // not work to map to it.
1840         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1841             //NSLog(@"clear alt");
1842             mods &= ~MOD_MASK_ALT;
1843         }
1845 #ifdef FEAT_MBYTE
1846         if (input_conv.vc_type != CONV_NONE) {
1847             conv_str = string_convert(&input_conv, chars, &length);
1848             if (conv_str)
1849                 chars = conv_str;
1850         }
1851 #endif
1852     }
1854     if (chars && length > 0) {
1855         if (mods) {
1856             //NSLog(@"adding mods: %d", mods);
1857             modChars[0] = CSI;
1858             modChars[1] = KS_MODIFIER;
1859             modChars[2] = mods;
1860             add_to_input_buf(modChars, 3);
1861         }
1863         //NSLog(@"add to input buf: 0x%x", chars[0]);
1864         // TODO: Check for CSI bytes?
1865         add_to_input_buf(chars, length);
1866     }
1868 #ifdef FEAT_MBYTE
1869     if (conv_str)
1870         vim_free(conv_str);
1871 #endif
1874 - (void)queueMessage:(int)msgid data:(NSData *)data
1876     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1877     if (data)
1878         [outputQueue addObject:data];
1879     else
1880         [outputQueue addObject:[NSData data]];
1883 - (void)connectionDidDie:(NSNotification *)notification
1885     // If the main connection to MacVim is lost this means that MacVim was
1886     // either quit (by the user chosing Quit on the MacVim menu), or it has
1887     // crashed.  In the former case the flag 'isTerminating' is set and we then
1888     // quit cleanly; in the latter case we make sure the swap files are left
1889     // for recovery.
1891     //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1892     if (isTerminating)
1893         getout(0);
1894     else
1895         getout_preserve_modified(1);
1898 - (void)blinkTimerFired:(NSTimer *)timer
1900     NSTimeInterval timeInterval = 0;
1902     [blinkTimer release];
1903     blinkTimer = nil;
1905     if (MMBlinkStateOn == blinkState) {
1906         gui_undraw_cursor();
1907         blinkState = MMBlinkStateOff;
1908         timeInterval = blinkOffInterval;
1909     } else if (MMBlinkStateOff == blinkState) {
1910         gui_update_cursor(TRUE, FALSE);
1911         blinkState = MMBlinkStateOn;
1912         timeInterval = blinkOnInterval;
1913     }
1915     if (timeInterval > 0) {
1916         blinkTimer = 
1917             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1918                                             selector:@selector(blinkTimerFired:)
1919                                             userInfo:nil repeats:NO] retain];
1920         [self flushQueue:YES];
1921     }
1924 - (void)focusChange:(BOOL)on
1926     gui_focus_change(on);
1929 - (void)handleToggleToolbar
1931     // If 'go' contains 'T', then remove it, else add it.
1933     char_u go[sizeof(GO_ALL)+2];
1934     char_u *p;
1935     int len;
1937     STRCPY(go, p_go);
1938     p = vim_strchr(go, GO_TOOLBAR);
1939     len = STRLEN(go);
1941     if (p != NULL) {
1942         char_u *end = go + len;
1943         while (p < end) {
1944             p[0] = p[1];
1945             ++p;
1946         }
1947     } else {
1948         go[len] = GO_TOOLBAR;
1949         go[len+1] = NUL;
1950     }
1952     set_option_value((char_u*)"guioptions", 0, go, 0);
1954     // Force screen redraw (does it have to be this complicated?).
1955     redraw_all_later(CLEAR);
1956     update_screen(NOT_VALID);
1957     setcursor();
1958     out_flush();
1959     gui_update_cursor(FALSE, FALSE);
1960     gui_mch_flush();
1963 - (void)handleScrollbarEvent:(NSData *)data
1965     if (!data) return;
1967     const void *bytes = [data bytes];
1968     long ident = *((long*)bytes);  bytes += sizeof(long);
1969     int hitPart = *((int*)bytes);  bytes += sizeof(int);
1970     float fval = *((float*)bytes);  bytes += sizeof(float);
1971     scrollbar_T *sb = gui_find_scrollbar(ident);
1973     if (sb) {
1974         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1975         long value = sb_info->value;
1976         long size = sb_info->size;
1977         long max = sb_info->max;
1978         BOOL isStillDragging = NO;
1979         BOOL updateKnob = YES;
1981         switch (hitPart) {
1982         case NSScrollerDecrementPage:
1983             value -= (size > 2 ? size - 2 : 1);
1984             break;
1985         case NSScrollerIncrementPage:
1986             value += (size > 2 ? size - 2 : 1);
1987             break;
1988         case NSScrollerDecrementLine:
1989             --value;
1990             break;
1991         case NSScrollerIncrementLine:
1992             ++value;
1993             break;
1994         case NSScrollerKnob:
1995             isStillDragging = YES;
1996             // fall through ...
1997         case NSScrollerKnobSlot:
1998             value = (long)(fval * (max - size + 1));
1999             // fall through ...
2000         default:
2001             updateKnob = NO;
2002             break;
2003         }
2005         //NSLog(@"value %d -> %d", sb_info->value, value);
2006         gui_drag_scrollbar(sb, value, isStillDragging);
2008         if (updateKnob) {
2009             // Dragging the knob or option+clicking automatically updates
2010             // the knob position (on the actual NSScroller), so we only
2011             // need to set the knob position in the other cases.
2012             if (sb->wp) {
2013                 // Update both the left&right vertical scrollbars.
2014                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2015                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2016                 [self setScrollbarThumbValue:value size:size max:max
2017                                   identifier:identLeft];
2018                 [self setScrollbarThumbValue:value size:size max:max
2019                                   identifier:identRight];
2020             } else {
2021                 // Update the horizontal scrollbar.
2022                 [self setScrollbarThumbValue:value size:size max:max
2023                                   identifier:ident];
2024             }
2025         }
2026     }
2029 - (void)handleSetFont:(NSData *)data
2031     if (!data) return;
2033     const void *bytes = [data bytes];
2034     float pointSize = *((float*)bytes);  bytes += sizeof(float);
2035     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2036     bytes += sizeof(unsigned);  // len not used
2038     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2039     [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
2040     char_u *s = (char_u*)[name UTF8String];
2042 #ifdef FEAT_MBYTE
2043     s = CONVERT_FROM_UTF8(s);
2044 #endif
2046     set_option_value((char_u*)"guifont", 0, s, 0);
2048 #ifdef FEAT_MBYTE
2049     CONVERT_FROM_UTF8_FREE(s);
2050 #endif
2052     // Force screen redraw (does it have to be this complicated?).
2053     redraw_all_later(CLEAR);
2054     update_screen(NOT_VALID);
2055     setcursor();
2056     out_flush();
2057     gui_update_cursor(FALSE, FALSE);
2058     gui_mch_flush();
2061 - (void)handleDropFiles:(NSData *)data
2063     // TODO: Get rid of this method; instead use Vim script directly.  At the
2064     // moment I know how to do this to open files in tabs, but I'm not sure how
2065     // to add the filenames to the command line when in command line mode.
2067     if (!data) return;
2069 #ifdef FEAT_DND
2070     const void *bytes = [data bytes];
2071     const void *end = [data bytes] + [data length];
2072     BOOL forceOpen = *((BOOL*)bytes);  bytes += sizeof(BOOL);
2073     int n = *((int*)bytes);  bytes += sizeof(int);
2075     if (!forceOpen && (State & CMDLINE)) {
2076         // HACK!  If Vim is in command line mode then the files names
2077         // should be added to the command line, instead of opening the
2078         // files in tabs (unless forceOpen is set).  This is taken care of by
2079         // gui_handle_drop().
2080         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2081         if (fnames) {
2082             int i = 0;
2083             while (bytes < end && i < n) {
2084                 int len = *((int*)bytes);  bytes += sizeof(int);
2085                 char_u *s = (char_u*)bytes;
2086 #ifdef FEAT_MBYTE
2087                 s = CONVERT_FROM_UTF8(s);
2088 #endif
2089                 fnames[i++] = vim_strsave(s);
2090 #ifdef FEAT_MBYTE
2091                 CONVERT_FROM_UTF8_FREE(s);
2092 #endif
2093                 bytes += len;
2094             }
2096             // NOTE!  This function will free 'fnames'.
2097             // HACK!  It is assumed that the 'x' and 'y' arguments are
2098             // unused when in command line mode.
2099             gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
2100         }
2101     } else {
2102         // HACK!  I'm not sure how to get Vim to open a list of files in
2103         // tabs, so instead I create a ':tab drop' command with all the
2104         // files to open and execute it.
2105         NSMutableString *cmd = [NSMutableString stringWithString:@":tab drop"];
2107         int i;
2108         for (i = 0; i < n && bytes < end; ++i) {
2109             int len = *((int*)bytes);  bytes += sizeof(int);
2110             NSString *file = [NSString stringWithUTF8String:bytes];
2111             file = [file stringByEscapingSpecialFilenameCharacters];
2112             bytes += len;
2114             [cmd appendString:@" "];
2115             [cmd appendString:file];
2116         }
2118         // By going to the last tabpage we ensure that the new tabs will
2119         // appear last (if this call is left out, the taborder becomes
2120         // messy).
2121         goto_tabpage(9999);
2123         char_u *s = (char_u*)[cmd UTF8String];
2124 #ifdef FEAT_MBYTE
2125         s = CONVERT_FROM_UTF8(s);
2126 #endif
2127         do_cmdline_cmd(s);
2128 #ifdef FEAT_MBYTE
2129         CONVERT_FROM_UTF8_FREE(s);
2130 #endif
2132         // Force screen redraw (does it have to be this complicated?).
2133         // (This code was taken from the end of gui_handle_drop().)
2134         update_screen(NOT_VALID);
2135         setcursor();
2136         out_flush();
2137         gui_update_cursor(FALSE, FALSE);
2138         maketitle();
2139         gui_mch_flush();
2140     }
2141 #endif // FEAT_DND
2144 - (void)handleDropString:(NSData *)data
2146     if (!data) return;
2148 #ifdef FEAT_DND
2149     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2150     const void *bytes = [data bytes];
2151     int len = *((int*)bytes);  bytes += sizeof(int);
2152     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2154     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2155     NSRange range = { 0, [string length] };
2156     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2157                                          withString:@"\x0a" options:0
2158                                               range:range];
2159     if (0 == n) {
2160         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2161                                        options:0 range:range];
2162     }
2164     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2165     char_u *s = (char_u*)[string UTF8String];
2166 #ifdef FEAT_MBYTE
2167     if (input_conv.vc_type != CONV_NONE)
2168         s = string_convert(&input_conv, s, &len);
2169 #endif
2170     dnd_yank_drag_data(s, len);
2171 #ifdef FEAT_MBYTE
2172     if (input_conv.vc_type != CONV_NONE)
2173         vim_free(s);
2174 #endif
2175     add_to_input_buf(dropkey, sizeof(dropkey));
2176 #endif // FEAT_DND
2179 - (void)handleOdbEdit:(NSData *)data
2181 #ifdef FEAT_ODB_EDITOR
2182     const void *bytes = [data bytes];
2184     OSType serverID = *((OSType*)bytes);  bytes += sizeof(OSType);
2186     char_u *path = NULL;
2187     int pathLen = *((int*)bytes);  bytes += sizeof(int);
2188     if (pathLen > 0) {
2189         path = (char_u*)bytes;
2190         bytes += pathLen;
2191 #ifdef FEAT_MBYTE
2192         path = CONVERT_FROM_UTF8(path);
2193 #endif
2194     }
2196     NSAppleEventDescriptor *token = nil;
2197     DescType tokenType = *((DescType*)bytes);  bytes += sizeof(DescType);
2198     int descLen = *((int*)bytes);  bytes += sizeof(int);
2199     if (descLen > 0) {
2200         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2201                                                                bytes:bytes
2202                                                               length:descLen];
2203         bytes += descLen;
2204     }
2206     unsigned i, numFiles = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2207     for (i = 0; i < numFiles; ++i) {
2208         int len = *((int*)bytes);  bytes += sizeof(int);
2209         char_u *filename = (char_u*)bytes;
2210 #ifdef FEAT_MBYTE
2211         filename = CONVERT_FROM_UTF8(filename);
2212 #endif
2213         buf_T *buf = buflist_findname(filename);
2214         if (buf) {
2215             if (buf->b_odb_token) {
2216                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2217                 buf->b_odb_token = NULL;
2218             }
2220             if (buf->b_odb_fname) {
2221                 vim_free(buf->b_odb_fname);
2222                 buf->b_odb_fname = NULL;
2223             }
2225             buf->b_odb_server_id = serverID;
2227             if (token)
2228                 buf->b_odb_token = [token retain];
2229             if (path)
2230                 buf->b_odb_fname = vim_strsave(path);
2231         } else {
2232             NSLog(@"WARNING: Could not find buffer '%s' for ODB editing.",
2233                     filename);
2234         }
2236 #ifdef FEAT_MBYTE
2237         CONVERT_FROM_UTF8_FREE(filename);
2238 #endif
2239         bytes += len;
2240     }
2241 #ifdef FEAT_MBYTE
2242     CONVERT_FROM_UTF8_FREE(path);
2243 #endif
2244 #endif // FEAT_ODB_EDITOR
2247 - (void)handleXcodeMod:(NSData *)data
2249 #if 0
2250     const void *bytes = [data bytes];
2251     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2252     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2253     if (0 == len)
2254         return;
2256     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2257             descriptorWithDescriptorType:type
2258                                    bytes:bytes
2259                                   length:len];
2260 #endif
2263 - (BOOL)checkForModifiedBuffers
2265     buf_T *buf;
2266     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2267         if (bufIsChanged(buf)) {
2268             return YES;
2269         }
2270     }
2272     return NO;
2275 - (void)addInput:(NSString *)input
2277     char_u *s = (char_u*)[input UTF8String];
2279 #ifdef FEAT_MBYTE
2280     s = CONVERT_FROM_UTF8(s);
2281 #endif
2283     server_to_input_buf(s);
2285 #ifdef FEAT_MBYTE
2286     CONVERT_FROM_UTF8_FREE(s);
2287 #endif
2290 @end // MMBackend (Private)
2295 @implementation MMBackend (ClientServer)
2297 - (NSString *)connectionNameFromServerName:(NSString *)name
2299     NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2301     return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2302         lowercaseString];
2305 - (NSConnection *)connectionForServerName:(NSString *)name
2307     // TODO: Try 'name%d' if 'name' fails.
2308     NSString *connName = [self connectionNameFromServerName:name];
2309     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2311     if (!svrConn) {
2312         svrConn = [NSConnection connectionWithRegisteredName:connName
2313                                                            host:nil];
2314         // Try alternate server...
2315         if (!svrConn && alternateServerName) {
2316             //NSLog(@"  trying to connect to alternate server: %@",
2317             //        alternateServerName);
2318             connName = [self connectionNameFromServerName:alternateServerName];
2319             svrConn = [NSConnection connectionWithRegisteredName:connName
2320                                                             host:nil];
2321         }
2323         // Try looking for alternate servers...
2324         if (!svrConn) {
2325             //NSLog(@"  looking for alternate servers...");
2326             NSString *alt = [self alternateServerNameForName:name];
2327             if (alt != alternateServerName) {
2328                 //NSLog(@"  found alternate server: %@", string);
2329                 [alternateServerName release];
2330                 alternateServerName = [alt copy];
2331             }
2332         }
2334         // Try alternate server again...
2335         if (!svrConn && alternateServerName) {
2336             //NSLog(@"  trying to connect to alternate server: %@",
2337             //        alternateServerName);
2338             connName = [self connectionNameFromServerName:alternateServerName];
2339             svrConn = [NSConnection connectionWithRegisteredName:connName
2340                                                             host:nil];
2341         }
2343         if (svrConn) {
2344             [connectionNameDict setObject:svrConn forKey:connName];
2346             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2347             [[NSNotificationCenter defaultCenter] addObserver:self
2348                     selector:@selector(serverConnectionDidDie:)
2349                         name:NSConnectionDidDieNotification object:svrConn];
2350         }
2351     }
2353     return svrConn;
2356 - (NSConnection *)connectionForServerPort:(int)port
2358     NSConnection *conn;
2359     NSEnumerator *e = [connectionNameDict objectEnumerator];
2361     while ((conn = [e nextObject])) {
2362         // HACK! Assume connection uses mach ports.
2363         if (port == [(NSMachPort*)[conn sendPort] machPort])
2364             return conn;
2365     }
2367     return nil;
2370 - (void)serverConnectionDidDie:(NSNotification *)notification
2372     //NSLog(@"%s%@", _cmd, notification);
2374     NSConnection *svrConn = [notification object];
2376     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2377     [[NSNotificationCenter defaultCenter]
2378             removeObserver:self
2379                       name:NSConnectionDidDieNotification
2380                     object:svrConn];
2382     [connectionNameDict removeObjectsForKeys:
2383         [connectionNameDict allKeysForObject:svrConn]];
2385     // HACK! Assume connection uses mach ports.
2386     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2387     NSNumber *key = [NSNumber numberWithInt:port];
2389     [clientProxyDict removeObjectForKey:key];
2390     [serverReplyDict removeObjectForKey:key];
2393 - (void)addClient:(NSDistantObject *)client
2395     NSConnection *conn = [client connectionForProxy];
2396     // HACK! Assume connection uses mach ports.
2397     int port = [(NSMachPort*)[conn sendPort] machPort];
2398     NSNumber *key = [NSNumber numberWithInt:port];
2400     if (![clientProxyDict objectForKey:key]) {
2401         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2402         [clientProxyDict setObject:client forKey:key];
2403     }
2405     // NOTE: 'clientWindow' is a global variable which is used by <client>
2406     clientWindow = port;
2409 - (NSString *)alternateServerNameForName:(NSString *)name
2411     if (!(name && [name length] > 0))
2412         return nil;
2414     // Only look for alternates if 'name' doesn't end in a digit.
2415     unichar lastChar = [name characterAtIndex:[name length]-1];
2416     if (lastChar >= '0' && lastChar <= '9')
2417         return nil;
2419     // Look for alternates among all current servers.
2420     NSArray *list = [self serverList];
2421     if (!(list && [list count] > 0))
2422         return nil;
2424     // Filter out servers starting with 'name' and ending with a number. The
2425     // (?i) pattern ensures that the match is case insensitive.
2426     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2427     NSPredicate *pred = [NSPredicate predicateWithFormat:
2428             @"SELF MATCHES %@", pat];
2429     list = [list filteredArrayUsingPredicate:pred];
2430     if ([list count] > 0) {
2431         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2432         return [list objectAtIndex:0];
2433     }
2435     return nil;
2438 @end // MMBackend (ClientServer)
2443 @implementation NSString (MMServerNameCompare)
2444 - (NSComparisonResult)serverNameCompare:(NSString *)string
2446     return [self compare:string
2447                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2449 @end
2454 static int eventModifierFlagsToVimModMask(int modifierFlags)
2456     int modMask = 0;
2458     if (modifierFlags & NSShiftKeyMask)
2459         modMask |= MOD_MASK_SHIFT;
2460     if (modifierFlags & NSControlKeyMask)
2461         modMask |= MOD_MASK_CTRL;
2462     if (modifierFlags & NSAlternateKeyMask)
2463         modMask |= MOD_MASK_ALT;
2464     if (modifierFlags & NSCommandKeyMask)
2465         modMask |= MOD_MASK_CMD;
2467     return modMask;
2470 static int vimModMaskToEventModifierFlags(int mods)
2472     int flags = 0;
2474     if (mods & MOD_MASK_SHIFT)
2475         flags |= NSShiftKeyMask;
2476     if (mods & MOD_MASK_CTRL)
2477         flags |= NSControlKeyMask;
2478     if (mods & MOD_MASK_ALT)
2479         flags |= NSAlternateKeyMask;
2480     if (mods & MOD_MASK_CMD)
2481         flags |= NSCommandKeyMask;
2483     return flags;
2486 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2488     int modMask = 0;
2490     if (modifierFlags & NSShiftKeyMask)
2491         modMask |= MOUSE_SHIFT;
2492     if (modifierFlags & NSControlKeyMask)
2493         modMask |= MOUSE_CTRL;
2494     if (modifierFlags & NSAlternateKeyMask)
2495         modMask |= MOUSE_ALT;
2497     return modMask;
2500 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2502     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2504     return (buttonNumber >= 0 && buttonNumber < 3)
2505             ? mouseButton[buttonNumber] : -1;
2508 static int specialKeyToNSKey(int key)
2510     if (!IS_SPECIAL(key))
2511         return key;
2513     static struct {
2514         int special;
2515         int nskey;
2516     } sp2ns[] = {
2517         { K_UP, NSUpArrowFunctionKey },
2518         { K_DOWN, NSDownArrowFunctionKey },
2519         { K_LEFT, NSLeftArrowFunctionKey },
2520         { K_RIGHT, NSRightArrowFunctionKey },
2521         { K_F1, NSF1FunctionKey },
2522         { K_F2, NSF2FunctionKey },
2523         { K_F3, NSF3FunctionKey },
2524         { K_F4, NSF4FunctionKey },
2525         { K_F5, NSF5FunctionKey },
2526         { K_F6, NSF6FunctionKey },
2527         { K_F7, NSF7FunctionKey },
2528         { K_F8, NSF8FunctionKey },
2529         { K_F9, NSF9FunctionKey },
2530         { K_F10, NSF10FunctionKey },
2531         { K_F11, NSF11FunctionKey },
2532         { K_F12, NSF12FunctionKey },
2533         { K_F13, NSF13FunctionKey },
2534         { K_F14, NSF14FunctionKey },
2535         { K_F15, NSF15FunctionKey },
2536         { K_F16, NSF16FunctionKey },
2537         { K_F17, NSF17FunctionKey },
2538         { K_F18, NSF18FunctionKey },
2539         { K_F19, NSF19FunctionKey },
2540         { K_F20, NSF20FunctionKey },
2541         { K_F21, NSF21FunctionKey },
2542         { K_F22, NSF22FunctionKey },
2543         { K_F23, NSF23FunctionKey },
2544         { K_F24, NSF24FunctionKey },
2545         { K_F25, NSF25FunctionKey },
2546         { K_F26, NSF26FunctionKey },
2547         { K_F27, NSF27FunctionKey },
2548         { K_F28, NSF28FunctionKey },
2549         { K_F29, NSF29FunctionKey },
2550         { K_F30, NSF30FunctionKey },
2551         { K_F31, NSF31FunctionKey },
2552         { K_F32, NSF32FunctionKey },
2553         { K_F33, NSF33FunctionKey },
2554         { K_F34, NSF34FunctionKey },
2555         { K_F35, NSF35FunctionKey },
2556         { K_DEL, NSBackspaceCharacter },
2557         { K_BS, NSDeleteCharacter },
2558         { K_HOME, NSHomeFunctionKey },
2559         { K_END, NSEndFunctionKey },
2560         { K_PAGEUP, NSPageUpFunctionKey },
2561         { K_PAGEDOWN, NSPageDownFunctionKey }
2562     };
2564     int i;
2565     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2566         if (sp2ns[i].special == key)
2567             return sp2ns[i].nskey;
2568     }
2570     return 0;