Improve <D-w> behaviour
[MacVim.git] / src / MacVim / MMBackend.m
blob3f1fa06850273e9602ad2a02722c3f599f765914
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
11  * MMBackend
12  *
13  * MMBackend communicates with the frontend (MacVim).  It maintains a queue of
14  * output which is flushed to the frontend under controlled circumstances (so
15  * as to maintain a steady framerate).  Input from the frontend is also handled
16  * here.
17  *
18  * The frontend communicates with the backend via the MMBackendProtocol.  In
19  * particular, input is sent to the backend via processInput:data: and Vim
20  * state can be queried from the frontend with evaluateExpression:.
21  *
22  * It is very important to realize that all state is held by the backend, the
23  * frontend must either ask for state [MMBackend evaluateExpression:] or wait
24  * for the backend to update [MMVimController processCommandQueue:].
25  *
26  * The client/server functionality of Vim is handled by the backend.  It sets
27  * up a named NSConnection to which other Vim processes can connect.
28  */
30 #import "MMBackend.h"
34 // NOTE: Colors in MMBackend are stored as unsigned ints on the form 0xaarrggbb
35 // whereas colors in Vim are int without the alpha component.  Also note that
36 // 'transp' is assumed to be a value between 0 and 100.
37 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
38 #define MM_COLOR_WITH_TRANSP(col,transp) \
39     ((unsigned)( ((col)&0xffffff) \
40         | ((((unsigned)((((100-(transp))*255)/100)+.5f))&0xff)<<24) ))
43 // This constant controls how often the command queue may be flushed.  If it is
44 // too small the app might feel unresponsive; if it is too large there might be
45 // long periods without the screen updating (e.g. when sourcing a large session
46 // file).  (The unit is seconds.)
47 static float MMFlushTimeoutInterval = 0.1f;
48 static int MMFlushQueueLenHint = 80*40;
50 static unsigned MMServerMax = 1000;
52 // TODO: Move to separate file.
53 static int eventModifierFlagsToVimModMask(int modifierFlags);
54 static int vimModMaskToEventModifierFlags(int mods);
55 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
56 static int eventButtonNumberToVimMouseButton(int buttonNumber);
57 static int specialKeyToNSKey(int key);
59 enum {
60     MMBlinkStateNone = 0,
61     MMBlinkStateOn,
62     MMBlinkStateOff
65 static NSString *MMSymlinkWarningString =
66     @"\n\n\tMost likely this is because you have symlinked directly to\n"
67      "\tthe Vim binary, which Cocoa does not allow.  Please use an\n"
68      "\talias or the mvim shell script instead.  If you have not used\n"
69      "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
73 @interface NSString (MMServerNameCompare)
74 - (NSComparisonResult)serverNameCompare:(NSString *)string;
75 @end
79 @interface MMBackend (Private)
80 - (void)processInputQueue;
81 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
82 + (NSDictionary *)specialKeys;
83 - (void)handleInsertText:(NSData *)data;
84 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
85 - (void)queueMessage:(int)msgid data:(NSData *)data;
86 - (void)connectionDidDie:(NSNotification *)notification;
87 - (void)blinkTimerFired:(NSTimer *)timer;
88 - (void)focusChange:(BOOL)on;
89 - (void)handleToggleToolbar;
90 - (void)handleScrollbarEvent:(NSData *)data;
91 - (void)handleSetFont:(NSData *)data;
92 - (void)handleDropFiles:(NSData *)data;
93 - (void)handleDropString:(NSData *)data;
94 - (void)handleOdbEdit:(NSData *)data;
95 - (void)handleXcodeMod:(NSData *)data;
96 - (BOOL)checkForModifiedBuffers;
97 - (void)addInput:(NSString *)input;
98 @end
102 @interface MMBackend (ClientServer)
103 - (NSString *)connectionNameFromServerName:(NSString *)name;
104 - (NSConnection *)connectionForServerName:(NSString *)name;
105 - (NSConnection *)connectionForServerPort:(int)port;
106 - (void)serverConnectionDidDie:(NSNotification *)notification;
107 - (void)addClient:(NSDistantObject *)client;
108 - (NSString *)alternateServerNameForName:(NSString *)name;
109 @end
113 @implementation MMBackend
115 + (MMBackend *)sharedInstance
117     static MMBackend *singleton = nil;
118     return singleton ? singleton : (singleton = [MMBackend new]);
121 - (id)init
123     self = [super init];
124     if (!self) return nil;
126     fontContainerRef = loadFonts();
128     outputQueue = [[NSMutableArray alloc] init];
129     inputQueue = [[NSMutableArray alloc] init];
130     drawData = [[NSMutableData alloc] initWithCapacity:1024];
131     connectionNameDict = [[NSMutableDictionary alloc] init];
132     clientProxyDict = [[NSMutableDictionary alloc] init];
133     serverReplyDict = [[NSMutableDictionary alloc] init];
135     NSBundle *mainBundle = [NSBundle mainBundle];
136     NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
137     if (path)
138         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
140     path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
141     if (path)
142         sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
143             retain];
145     if (!(colorDict && sysColorDict))
146         NSLog(@"ERROR: Failed to load color dictionaries.%@",
147                 MMSymlinkWarningString);
149     return self;
152 - (void)dealloc
154     //NSLog(@"%@ %s", [self className], _cmd);
155     [[NSNotificationCenter defaultCenter] removeObserver:self];
157     [oldWideFont release];  oldWideFont = nil;
158     [blinkTimer release];  blinkTimer = nil;
159     [alternateServerName release];  alternateServerName = nil;
160     [serverReplyDict release];  serverReplyDict = nil;
161     [clientProxyDict release];  clientProxyDict = nil;
162     [connectionNameDict release];  connectionNameDict = nil;
163     [inputQueue release];  inputQueue = nil;
164     [outputQueue release];  outputQueue = nil;
165     [drawData release];  drawData = nil;
166     [frontendProxy release];  frontendProxy = nil;
167     [connection release];  connection = nil;
168     [sysColorDict release];  sysColorDict = nil;
169     [colorDict release];  colorDict = nil;
171     [super dealloc];
174 - (void)setBackgroundColor:(int)color
176     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
179 - (void)setForegroundColor:(int)color
181     foregroundColor = MM_COLOR(color);
184 - (void)setSpecialColor:(int)color
186     specialColor = MM_COLOR(color);
189 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
191     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
192     defaultForegroundColor = MM_COLOR(fg);
194     NSMutableData *data = [NSMutableData data];
196     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
197     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
199     [self queueMessage:SetDefaultColorsMsgID data:data];
202 - (NSConnection *)connection
204     if (!connection) {
205         // NOTE!  If the name of the connection changes here it must also be
206         // updated in MMAppController.m.
207         NSString *name = [NSString stringWithFormat:@"%@-connection",
208                [[NSBundle mainBundle] bundleIdentifier]];
210         connection = [NSConnection connectionWithRegisteredName:name host:nil];
211         [connection retain];
212     }
214     // NOTE: 'connection' may be nil here.
215     return connection;
218 - (BOOL)checkin
220     if (![self connection]) {
221         NSBundle *mainBundle = [NSBundle mainBundle];
222 #if 0
223         OSStatus status;
224         FSRef ref;
226         // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
227         // the API to pass Apple Event parameters is broken on 10.4).
228         NSString *path = [mainBundle bundlePath];
229         status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
230         if (noErr == status) {
231             // Pass parameter to the 'Open' Apple Event that tells MacVim not
232             // to open an untitled window.
233             NSAppleEventDescriptor *desc =
234                     [NSAppleEventDescriptor recordDescriptor];
235             [desc setParamDescriptor:
236                     [NSAppleEventDescriptor descriptorWithBoolean:NO]
237                           forKeyword:keyMMUntitledWindow];
239             LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
240                     kLSLaunchDefaults, NULL };
241             status = LSOpenFromRefSpec(&spec, NULL);
242         }
244         if (noErr != status) {
245         NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
246                 path, MMSymlinkWarningString);
247             return NO;
248         }
249 #else
250         // Launch MacVim using NSTask.  For some reason the above code using
251         // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
252         // fails, the dock icon starts bouncing and never stops).  It seems
253         // like rebuilding the Launch Services database takes care of this
254         // problem, but the NSTask way seems more stable so stick with it.
255         //
256         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
257         // that the GUI won't be activated (or raised) so there is a hack in
258         // MMAppController which raises the app when a new window is opened.
259         NSMutableArray *args = [NSMutableArray arrayWithObjects:
260             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
261         NSString *exeName = [[mainBundle infoDictionary]
262                 objectForKey:@"CFBundleExecutable"];
263         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
264         if (!path) {
265             NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
266                     MMSymlinkWarningString);
267             return NO;
268         }
270         [NSTask launchedTaskWithLaunchPath:path arguments:args];
271 #endif
273         // HACK!  Poll the mach bootstrap server until it returns a valid
274         // connection to detect that MacVim has finished launching.  Also set a
275         // time-out date so that we don't get stuck doing this forever.
276         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
277         while (![self connection] &&
278                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
279             [[NSRunLoop currentRunLoop]
280                     runMode:NSDefaultRunLoopMode
281                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
283         // NOTE: [self connection] will set 'connection' as a side-effect.
284         if (!connection) {
285             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
286             return NO;
287         }
288     }
290     BOOL ok = NO;
291     @try {
292         [[NSNotificationCenter defaultCenter] addObserver:self
293                 selector:@selector(connectionDidDie:)
294                     name:NSConnectionDidDieNotification object:connection];
296         id proxy = [connection rootProxy];
297         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
299         int pid = [[NSProcessInfo processInfo] processIdentifier];
301         frontendProxy = [proxy connectBackend:self pid:pid];
302         if (frontendProxy) {
303             [frontendProxy retain];
304             [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
305             ok = YES;
306         }
307     }
308     @catch (NSException *e) {
309         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
310     }
312     return ok;
315 - (BOOL)openVimWindow
317     [self queueMessage:OpenVimWindowMsgID data:nil];
318     return YES;
321 - (void)clearAll
323     int type = ClearAllDrawType;
325     // Any draw commands in queue are effectively obsolete since this clearAll
326     // will negate any effect they have, therefore we may as well clear the
327     // draw queue.
328     [drawData setLength:0];
330     [drawData appendBytes:&type length:sizeof(int)];
333 - (void)clearBlockFromRow:(int)row1 column:(int)col1
334                     toRow:(int)row2 column:(int)col2
336     int type = ClearBlockDrawType;
338     [drawData appendBytes:&type length:sizeof(int)];
340     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
341     [drawData appendBytes:&row1 length:sizeof(int)];
342     [drawData appendBytes:&col1 length:sizeof(int)];
343     [drawData appendBytes:&row2 length:sizeof(int)];
344     [drawData appendBytes:&col2 length:sizeof(int)];
347 - (void)deleteLinesFromRow:(int)row count:(int)count
348               scrollBottom:(int)bottom left:(int)left right:(int)right
350     int type = DeleteLinesDrawType;
352     [drawData appendBytes:&type length:sizeof(int)];
354     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
355     [drawData appendBytes:&row length:sizeof(int)];
356     [drawData appendBytes:&count length:sizeof(int)];
357     [drawData appendBytes:&bottom length:sizeof(int)];
358     [drawData appendBytes:&left length:sizeof(int)];
359     [drawData appendBytes:&right length:sizeof(int)];
362 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
363              cells:(int)cells flags:(int)flags
365     if (len <= 0 || cells <= 0) return;
367     int type = DrawStringDrawType;
369     [drawData appendBytes:&type length:sizeof(int)];
371     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
372     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
373     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
374     [drawData appendBytes:&row length:sizeof(int)];
375     [drawData appendBytes:&col length:sizeof(int)];
376     [drawData appendBytes:&cells length:sizeof(int)];
377     [drawData appendBytes:&flags length:sizeof(int)];
378     [drawData appendBytes:&len length:sizeof(int)];
379     [drawData appendBytes:s length:len];
382 - (void)insertLinesFromRow:(int)row count:(int)count
383               scrollBottom:(int)bottom left:(int)left right:(int)right
385     int type = InsertLinesDrawType;
387     [drawData appendBytes:&type length:sizeof(int)];
389     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
390     [drawData appendBytes:&row length:sizeof(int)];
391     [drawData appendBytes:&count length:sizeof(int)];
392     [drawData appendBytes:&bottom length:sizeof(int)];
393     [drawData appendBytes:&left length:sizeof(int)];
394     [drawData appendBytes:&right length:sizeof(int)];
397 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
398                fraction:(int)percent color:(int)color
400     int type = DrawCursorDrawType;
401     unsigned uc = MM_COLOR(color);
403     [drawData appendBytes:&type length:sizeof(int)];
405     [drawData appendBytes:&uc length:sizeof(unsigned)];
406     [drawData appendBytes:&row length:sizeof(int)];
407     [drawData appendBytes:&col length:sizeof(int)];
408     [drawData appendBytes:&shape length:sizeof(int)];
409     [drawData appendBytes:&percent length:sizeof(int)];
412 - (void)update
414     // Tend to the run loop, returning immediately if there are no events
415     // waiting.
416     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
417                              beforeDate:[NSDate distantPast]];
419 #if 0
420     // Keyboard and mouse input is handled directly, other input is queued and
421     // processed here.  This call may enter a blocking loop.
422     if ([inputQueue count] > 0)
423         [self processInputQueue];
424 #endif
427 - (void)flushQueue:(BOOL)force
429     // NOTE! This method gets called a lot; if we were to flush every time it
430     // got called MacVim would feel unresponsive.  So there is a time out which
431     // ensures that the queue isn't flushed too often.
432     if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
433             < MMFlushTimeoutInterval
434             && [drawData length] < MMFlushQueueLenHint)
435         return;
437     if ([drawData length] > 0) {
438         // HACK!  Detect changes to 'guifontwide'.
439         if (gui.wide_font != (GuiFont)oldWideFont) {
440             [oldWideFont release];
441             oldWideFont = [(NSFont*)gui.wide_font retain];
442             [self setWideFont:oldWideFont];
443         }
445         int type = SetCursorPosDrawType;
446         [drawData appendBytes:&type length:sizeof(type)];
447         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
448         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
450         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
451         [drawData setLength:0];
452     }
454     if ([outputQueue count] > 0) {
455         @try {
456             [frontendProxy processCommandQueue:outputQueue];
457         }
458         @catch (NSException *e) {
459             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
460         }
462         [outputQueue removeAllObjects];
464         [lastFlushDate release];
465         lastFlushDate = [[NSDate date] retain];
466     }
469 - (BOOL)waitForInput:(int)milliseconds
471     //NSLog(@"|ENTER| %s%d",  _cmd, milliseconds);
473     // Only start the run loop if the input queue is empty, otherwise process
474     // the input first so that the input on queue isn't delayed.
475     if ([inputQueue count]) {
476         inputReceived = YES;
477     } else {
478         NSDate *date = milliseconds > 0 ?
479                 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
480                 [NSDate distantFuture];
482         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
483                                  beforeDate:date];
484     }
486     // I know of no way to figure out if the run loop exited because input was
487     // found or because of a time out, so I need to manually indicate when
488     // input was received in processInput:data: and then reset it every time
489     // here.
490     BOOL yn = inputReceived;
491     inputReceived = NO;
493     // Keyboard and mouse input is handled directly, other input is queued and
494     // processed here.  This call may enter a blocking loop.
495     if ([inputQueue count] > 0)
496         [self processInputQueue];
498     //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
499     return yn;
502 - (void)exit
504 #ifdef MAC_CLIENTSERVER
505     // The default connection is used for the client/server code.
506     [[NSConnection defaultConnection] setRootObject:nil];
507     [[NSConnection defaultConnection] invalidate];
508 #endif
510     // By invalidating the NSConnection the MMWindowController immediately
511     // finds out that the connection is down and as a result
512     // [MMWindowController connectionDidDie:] is invoked.
513     //NSLog(@"%@ %s", [self className], _cmd);
514     [[NSNotificationCenter defaultCenter] removeObserver:self];
515     [connection invalidate];
517     if (fontContainerRef) {
518         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
519         fontContainerRef = 0;
520     }
524 - (void)selectTab:(int)index
526     //NSLog(@"%s%d", _cmd, index);
528     index -= 1;
529     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
530     [self queueMessage:SelectTabMsgID data:data];
533 - (void)updateTabBar
535     //NSLog(@"%s", _cmd);
537     NSMutableData *data = [NSMutableData data];
539     int idx = tabpage_index(curtab) - 1;
540     [data appendBytes:&idx length:sizeof(int)];
542     tabpage_T *tp;
543     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
544         // This function puts the label of the tab in the global 'NameBuff'.
545         get_tabline_label(tp, FALSE);
546         char_u *s = NameBuff;
547         int len = STRLEN(s);
548         if (len <= 0) continue;
550 #ifdef FEAT_MBYTE
551         s = CONVERT_TO_UTF8(s);
552 #endif
554         // Count the number of windows in the tabpage.
555         //win_T *wp = tp->tp_firstwin;
556         //int wincount;
557         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
559         //[data appendBytes:&wincount length:sizeof(int)];
560         [data appendBytes:&len length:sizeof(int)];
561         [data appendBytes:s length:len];
563 #ifdef FEAT_MBYTE
564         CONVERT_TO_UTF8_FREE(s);
565 #endif
566     }
568     [self queueMessage:UpdateTabBarMsgID data:data];
571 - (BOOL)tabBarVisible
573     return tabBarVisible;
576 - (void)showTabBar:(BOOL)enable
578     tabBarVisible = enable;
580     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
581     [self queueMessage:msgid data:nil];
584 - (void)setRows:(int)rows columns:(int)cols
586     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
588     int dim[] = { rows, cols };
589     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
591     [self queueMessage:SetTextDimensionsMsgID data:data];
594 - (void)setWindowTitle:(char *)title
596     NSMutableData *data = [NSMutableData data];
597     int len = strlen(title);
598     if (len <= 0) return;
600     [data appendBytes:&len length:sizeof(int)];
601     [data appendBytes:title length:len];
603     [self queueMessage:SetWindowTitleMsgID data:data];
606 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
607                             saving:(int)saving
609     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
610     //        saving);
612     char_u *s = NULL;
613     NSString *ds = dir ? [NSString stringWithUTF8String:dir] : nil;
614     NSString *ts = title ? [NSString stringWithUTF8String:title] : nil;
615     @try {
616         [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
618         // Wait until a reply is sent from MMVimController.
619         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
620                                  beforeDate:[NSDate distantFuture]];
622         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
623             char_u *ret = (char_u*)[dialogReturn UTF8String];
624 #ifdef FEAT_MBYTE
625             ret = CONVERT_FROM_UTF8(ret);
626 #endif
627             s = vim_strsave(ret);
628 #ifdef FEAT_MBYTE
629             CONVERT_FROM_UTF8_FREE(ret);
630 #endif
631         }
633         [dialogReturn release];  dialogReturn = nil;
634     }
635     @catch (NSException *e) {
636         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
637     }
639     return (char *)s;
642 - (oneway void)setDialogReturn:(in bycopy id)obj
644     // NOTE: This is called by
645     //   - [MMVimController panelDidEnd:::], and
646     //   - [MMVimController alertDidEnd:::],
647     // to indicate that a save/open panel or alert has finished.
649     if (obj != dialogReturn) {
650         [dialogReturn release];
651         dialogReturn = [obj retain];
652     }
655 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
656                      buttons:(char *)btns textField:(char *)txtfield
658     int retval = 0;
659     NSString *message = nil, *text = nil, *textFieldString = nil;
660     NSArray *buttons = nil;
661     int style = NSInformationalAlertStyle;
663     if (VIM_WARNING == type) style = NSWarningAlertStyle;
664     else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
666     if (btns) {
667         NSString *btnString = [NSString stringWithUTF8String:btns];
668         buttons = [btnString componentsSeparatedByString:@"\n"];
669     }
670     if (title)
671         message = [NSString stringWithUTF8String:title];
672     if (msg) {
673         text = [NSString stringWithUTF8String:msg];
674         if (!message) {
675             // HACK! If there is a '\n\n' or '\n' sequence in the message, then
676             // make the part up to there into the title.  We only do this
677             // because Vim has lots of dialogs without a title and they look
678             // ugly that way.
679             // TODO: Fix the actual dialog texts.
680             NSRange eolRange = [text rangeOfString:@"\n\n"];
681             if (NSNotFound == eolRange.location)
682                 eolRange = [text rangeOfString:@"\n"];
683             if (NSNotFound != eolRange.location) {
684                 message = [text substringToIndex:eolRange.location];
685                 text = [text substringFromIndex:NSMaxRange(eolRange)];
686             }
687         }
688     }
689     if (txtfield)
690         textFieldString = [NSString stringWithUTF8String:txtfield];
692     @try {
693         [frontendProxy presentDialogWithStyle:style message:message
694                               informativeText:text buttonTitles:buttons
695                               textFieldString:textFieldString];
697         // Wait until a reply is sent from MMVimController.
698         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
699                                  beforeDate:[NSDate distantFuture]];
701         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
702                 && [dialogReturn count]) {
703             retval = [[dialogReturn objectAtIndex:0] intValue];
704             if (txtfield && [dialogReturn count] > 1) {
705                 NSString *retString = [dialogReturn objectAtIndex:1];
706                 char_u *ret = (char_u*)[retString UTF8String];
707 #ifdef FEAT_MBYTE
708                 ret = CONVERT_FROM_UTF8(ret);
709 #endif
710                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
711 #ifdef FEAT_MBYTE
712                 CONVERT_FROM_UTF8_FREE(ret);
713 #endif
714             }
715         }
717         [dialogReturn release]; dialogReturn = nil;
718     }
719     @catch (NSException *e) {
720         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
721     }
723     return retval;
726 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
727                atIndex:(int)index
729     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
730     //        name, index);
732     int namelen = name ? strlen(name) : 0;
733     NSMutableData *data = [NSMutableData data];
735     [data appendBytes:&tag length:sizeof(int)];
736     [data appendBytes:&parentTag length:sizeof(int)];
737     [data appendBytes:&namelen length:sizeof(int)];
738     if (namelen > 0) [data appendBytes:name length:namelen];
739     [data appendBytes:&index length:sizeof(int)];
741     [self queueMessage:AddMenuMsgID data:data];
744 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
745                        tip:(char *)tip icon:(char *)icon
746              keyEquivalent:(int)key modifiers:(int)mods
747                     action:(NSString *)action atIndex:(int)index
749     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
750     //        parentTag, name, tip, index);
752     int namelen = name ? strlen(name) : 0;
753     int tiplen = tip ? strlen(tip) : 0;
754     int iconlen = icon ? strlen(icon) : 0;
755     int eventFlags = vimModMaskToEventModifierFlags(mods);
756     int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
757     NSMutableData *data = [NSMutableData data];
759     key = specialKeyToNSKey(key);
761     [data appendBytes:&tag length:sizeof(int)];
762     [data appendBytes:&parentTag length:sizeof(int)];
763     [data appendBytes:&namelen length:sizeof(int)];
764     if (namelen > 0) [data appendBytes:name length:namelen];
765     [data appendBytes:&tiplen length:sizeof(int)];
766     if (tiplen > 0) [data appendBytes:tip length:tiplen];
767     [data appendBytes:&iconlen length:sizeof(int)];
768     if (iconlen > 0) [data appendBytes:icon length:iconlen];
769     [data appendBytes:&actionlen length:sizeof(int)];
770     if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
771     [data appendBytes:&index length:sizeof(int)];
772     [data appendBytes:&key length:sizeof(int)];
773     [data appendBytes:&eventFlags length:sizeof(int)];
775     [self queueMessage:AddMenuItemMsgID data:data];
778 - (void)removeMenuItemWithTag:(int)tag
780     NSMutableData *data = [NSMutableData data];
781     [data appendBytes:&tag length:sizeof(int)];
783     [self queueMessage:RemoveMenuItemMsgID data:data];
786 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
788     NSMutableData *data = [NSMutableData data];
790     [data appendBytes:&tag length:sizeof(int)];
791     [data appendBytes:&enabled length:sizeof(int)];
793     [self queueMessage:EnableMenuItemMsgID data:data];
796 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
798     int len = strlen(name);
799     int row = -1, col = -1;
801     if (len <= 0) return;
803     if (!mouse && curwin) {
804         row = curwin->w_wrow;
805         col = curwin->w_wcol;
806     }
808     NSMutableData *data = [NSMutableData data];
810     [data appendBytes:&row length:sizeof(int)];
811     [data appendBytes:&col length:sizeof(int)];
812     [data appendBytes:&len length:sizeof(int)];
813     [data appendBytes:name length:len];
815     [self queueMessage:ShowPopupMenuMsgID data:data];
818 - (void)showToolbar:(int)enable flags:(int)flags
820     NSMutableData *data = [NSMutableData data];
822     [data appendBytes:&enable length:sizeof(int)];
823     [data appendBytes:&flags length:sizeof(int)];
825     [self queueMessage:ShowToolbarMsgID data:data];
828 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
830     NSMutableData *data = [NSMutableData data];
832     [data appendBytes:&ident length:sizeof(long)];
833     [data appendBytes:&type length:sizeof(int)];
835     [self queueMessage:CreateScrollbarMsgID data:data];
838 - (void)destroyScrollbarWithIdentifier:(long)ident
840     NSMutableData *data = [NSMutableData data];
841     [data appendBytes:&ident length:sizeof(long)];
843     [self queueMessage:DestroyScrollbarMsgID data:data];
846 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
848     NSMutableData *data = [NSMutableData data];
850     [data appendBytes:&ident length:sizeof(long)];
851     [data appendBytes:&visible length:sizeof(int)];
853     [self queueMessage:ShowScrollbarMsgID data:data];
856 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
858     NSMutableData *data = [NSMutableData data];
860     [data appendBytes:&ident length:sizeof(long)];
861     [data appendBytes:&pos length:sizeof(int)];
862     [data appendBytes:&len length:sizeof(int)];
864     [self queueMessage:SetScrollbarPositionMsgID data:data];
867 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
868                     identifier:(long)ident
870     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
871     float prop = (float)size/(max+1);
872     if (fval < 0) fval = 0;
873     else if (fval > 1.0f) fval = 1.0f;
874     if (prop < 0) prop = 0;
875     else if (prop > 1.0f) prop = 1.0f;
877     NSMutableData *data = [NSMutableData data];
879     [data appendBytes:&ident length:sizeof(long)];
880     [data appendBytes:&fval length:sizeof(float)];
881     [data appendBytes:&prop length:sizeof(float)];
883     [self queueMessage:SetScrollbarThumbMsgID data:data];
886 - (void)setFont:(NSFont *)font
888     NSString *fontName = [font displayName];
889     float size = [font pointSize];
890     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
891     if (len > 0) {
892         NSMutableData *data = [NSMutableData data];
894         [data appendBytes:&size length:sizeof(float)];
895         [data appendBytes:&len length:sizeof(int)];
896         [data appendBytes:[fontName UTF8String] length:len];
898         [self queueMessage:SetFontMsgID data:data];
899     }
902 - (void)setWideFont:(NSFont *)font
904     NSString *fontName = [font displayName];
905     float size = [font pointSize];
906     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
907     NSMutableData *data = [NSMutableData data];
909     [data appendBytes:&size length:sizeof(float)];
910     [data appendBytes:&len length:sizeof(int)];
911     if (len > 0)
912         [data appendBytes:[fontName UTF8String] length:len];
914     [self queueMessage:SetWideFontMsgID data:data];
917 - (void)executeActionWithName:(NSString *)name
919     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
921     if (len > 0) {
922         NSMutableData *data = [NSMutableData data];
924         [data appendBytes:&len length:sizeof(int)];
925         [data appendBytes:[name UTF8String] length:len];
927         [self queueMessage:ExecuteActionMsgID data:data];
928     }
931 - (void)setMouseShape:(int)shape
933     NSMutableData *data = [NSMutableData data];
934     [data appendBytes:&shape length:sizeof(int)];
935     [self queueMessage:SetMouseShapeMsgID data:data];
938 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
940     // Vim specifies times in milliseconds, whereas Cocoa wants them in
941     // seconds.
942     blinkWaitInterval = .001f*wait;
943     blinkOnInterval = .001f*on;
944     blinkOffInterval = .001f*off;
947 - (void)startBlink
949     if (blinkTimer) {
950         [blinkTimer invalidate];
951         [blinkTimer release];
952         blinkTimer = nil;
953     }
955     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
956             && gui.in_focus) {
957         blinkState = MMBlinkStateOn;
958         blinkTimer =
959             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
960                                               target:self
961                                             selector:@selector(blinkTimerFired:)
962                                             userInfo:nil repeats:NO] retain];
963         gui_update_cursor(TRUE, FALSE);
964         [self flushQueue:YES];
965     }
968 - (void)stopBlink
970     if (MMBlinkStateOff == blinkState) {
971         gui_update_cursor(TRUE, FALSE);
972         [self flushQueue:YES];
973     }
975     blinkState = MMBlinkStateNone;
978 - (void)adjustLinespace:(int)linespace
980     NSMutableData *data = [NSMutableData data];
981     [data appendBytes:&linespace length:sizeof(int)];
982     [self queueMessage:AdjustLinespaceMsgID data:data];
985 - (void)activate
987     [self queueMessage:ActivateMsgID data:nil];
990 - (void)setPreEditRow:(int)row column:(int)col
992     NSMutableData *data = [NSMutableData data];
993     [data appendBytes:&row length:sizeof(int)];
994     [data appendBytes:&col length:sizeof(int)];
995     [self queueMessage:SetPreEditPositionMsgID data:data];
998 - (int)lookupColorWithKey:(NSString *)key
1000     if (!(key && [key length] > 0))
1001         return INVALCOLOR;
1003     NSString *stripKey = [[[[key lowercaseString]
1004         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
1005             componentsSeparatedByString:@" "]
1006                componentsJoinedByString:@""];
1008     if (stripKey && [stripKey length] > 0) {
1009         // First of all try to lookup key in the color dictionary; note that
1010         // all keys in this dictionary are lowercase with no whitespace.
1011         id obj = [colorDict objectForKey:stripKey];
1012         if (obj) return [obj intValue];
1014         // The key was not in the dictionary; is it perhaps of the form
1015         // #rrggbb?
1016         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
1017             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
1018             [scanner setScanLocation:1];
1019             unsigned hex = 0;
1020             if ([scanner scanHexInt:&hex]) {
1021                 return (int)hex;
1022             }
1023         }
1025         // As a last resort, check if it is one of the system defined colors.
1026         // The keys in this dictionary are also lowercase with no whitespace.
1027         obj = [sysColorDict objectForKey:stripKey];
1028         if (obj) {
1029             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
1030             if (col) {
1031                 float r, g, b, a;
1032                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
1033                 [col getRed:&r green:&g blue:&b alpha:&a];
1034                 return (((int)(r*255+.5f) & 0xff) << 16)
1035                      + (((int)(g*255+.5f) & 0xff) << 8)
1036                      +  ((int)(b*255+.5f) & 0xff);
1037             }
1038         }
1039     }
1041     //NSLog(@"WARNING: No color with key %@ found.", stripKey);
1042     return INVALCOLOR;
1045 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
1047     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1048     id obj;
1050     while ((obj = [e nextObject])) {
1051         if ([value isEqual:obj])
1052             return YES;
1053     }
1055     return NO;
1058 - (void)enterFullscreen:(int)fuoptions
1060     NSMutableData *data = [NSMutableData data];
1061     [data appendBytes:&fuoptions length:sizeof(int)];
1062     [self queueMessage:EnterFullscreenMsgID data:data];
1065 - (void)leaveFullscreen
1067     [self queueMessage:LeaveFullscreenMsgID data:nil];
1070 - (void)setAntialias:(BOOL)antialias
1072     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1074     [self queueMessage:msgid data:nil];
1077 - (void)updateModifiedFlag
1079     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1080     // vice versa.
1081     int msgid = [self checkForModifiedBuffers]
1082             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1084     [self queueMessage:msgid data:nil];
1087 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1089     // NOTE: This method might get called whenever the run loop is tended to.
1090     // Normal keyboard and mouse input is added to input buffers, so there is
1091     // no risk in handling these events directly (they return immediately, and
1092     // do not call any other Vim functions).  However, other events such
1093     // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
1094     // events which would cause this method to be called recursively.  This
1095     // in turn leads to various difficulties that we do not want to have to
1096     // deal with.  To avoid recursive calls here we add all events except
1097     // keyboard and mouse events to an input queue which is processed whenever
1098     // gui_mch_update() is called (see processInputQueue).
1100     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1102     // Don't flush too soon after receiving input or update speed will suffer.
1103     [lastFlushDate release];
1104     lastFlushDate = [[NSDate date] retain];
1106     // Handle keyboard and mouse input now.  All other events are queued.
1107     if (InsertTextMsgID == msgid) {
1108         [self handleInsertText:data];
1109     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1110         if (!data) return;
1111         const void *bytes = [data bytes];
1112         int mods = *((int*)bytes);  bytes += sizeof(int);
1113         int len = *((int*)bytes);  bytes += sizeof(int);
1114         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1115                                               encoding:NSUTF8StringEncoding];
1116         mods = eventModifierFlagsToVimModMask(mods);
1118         [self handleKeyDown:key modifiers:mods];
1120         [key release];
1121     } else if (ScrollWheelMsgID == msgid) {
1122         if (!data) return;
1123         const void *bytes = [data bytes];
1125         int row = *((int*)bytes);  bytes += sizeof(int);
1126         int col = *((int*)bytes);  bytes += sizeof(int);
1127         int flags = *((int*)bytes);  bytes += sizeof(int);
1128         float dy = *((float*)bytes);  bytes += sizeof(float);
1130         int button = MOUSE_5;
1131         if (dy > 0) button = MOUSE_4;
1133         flags = eventModifierFlagsToVimMouseModMask(flags);
1135         int numLines = (int)round(dy);
1136         if (numLines < 0) numLines = -numLines;
1137         if (numLines == 0) numLines = 1;
1139 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1140         gui.scroll_wheel_force = numLines;
1141 #endif
1143         gui_send_mouse_event(button, col, row, NO, flags);
1144     } else if (MouseDownMsgID == msgid) {
1145         if (!data) return;
1146         const void *bytes = [data bytes];
1148         int row = *((int*)bytes);  bytes += sizeof(int);
1149         int col = *((int*)bytes);  bytes += sizeof(int);
1150         int button = *((int*)bytes);  bytes += sizeof(int);
1151         int flags = *((int*)bytes);  bytes += sizeof(int);
1152         int count = *((int*)bytes);  bytes += sizeof(int);
1154         button = eventButtonNumberToVimMouseButton(button);
1155         if (button >= 0) {
1156             flags = eventModifierFlagsToVimMouseModMask(flags);
1157             gui_send_mouse_event(button, col, row, count>1, flags);
1158         }
1159     } else if (MouseUpMsgID == msgid) {
1160         if (!data) return;
1161         const void *bytes = [data bytes];
1163         int row = *((int*)bytes);  bytes += sizeof(int);
1164         int col = *((int*)bytes);  bytes += sizeof(int);
1165         int flags = *((int*)bytes);  bytes += sizeof(int);
1167         flags = eventModifierFlagsToVimMouseModMask(flags);
1169         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1170     } else if (MouseDraggedMsgID == msgid) {
1171         if (!data) return;
1172         const void *bytes = [data bytes];
1174         int row = *((int*)bytes);  bytes += sizeof(int);
1175         int col = *((int*)bytes);  bytes += sizeof(int);
1176         int flags = *((int*)bytes);  bytes += sizeof(int);
1178         flags = eventModifierFlagsToVimMouseModMask(flags);
1180         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1181     } else if (MouseMovedMsgID == msgid) {
1182         const void *bytes = [data bytes];
1183         int row = *((int*)bytes);  bytes += sizeof(int);
1184         int col = *((int*)bytes);  bytes += sizeof(int);
1186         gui_mouse_moved(col, row);
1187     } else if (AddInputMsgID == msgid) {
1188         NSString *string = [[NSString alloc] initWithData:data
1189                 encoding:NSUTF8StringEncoding];
1190         if (string) {
1191             [self addInput:string];
1192             [string release];
1193         }
1194     } else if (TerminateNowMsgID == msgid) {
1195         isTerminating = YES;
1196     } else {
1197         // Not keyboard or mouse event, queue it and handle later.
1198         //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
1199         [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1200         [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
1201     }
1203     // See waitForInput: for an explanation of this flag.
1204     inputReceived = YES;
1207 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1209     // TODO: Get rid of this method?
1210     //NSLog(@"%s%@", _cmd, messages);
1212     unsigned i, count = [messages count];
1213     for (i = 0; i < count; i += 2) {
1214         int msgid = [[messages objectAtIndex:i] intValue];
1215         id data = [messages objectAtIndex:i+1];
1216         if ([data isEqual:[NSNull null]])
1217             data = nil;
1219         [self processInput:msgid data:data];
1220     }
1223 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1225     NSString *eval = nil;
1226     char_u *s = (char_u*)[expr UTF8String];
1228 #ifdef FEAT_MBYTE
1229     s = CONVERT_FROM_UTF8(s);
1230 #endif
1232     char_u *res = eval_client_expr_to_string(s);
1234 #ifdef FEAT_MBYTE
1235     CONVERT_FROM_UTF8_FREE(s);
1236 #endif
1238     if (res != NULL) {
1239         s = res;
1240 #ifdef FEAT_MBYTE
1241         s = CONVERT_TO_UTF8(s);
1242 #endif
1243         eval = [NSString stringWithUTF8String:(char*)s];
1244 #ifdef FEAT_MBYTE
1245         CONVERT_TO_UTF8_FREE(s);
1246 #endif
1247         vim_free(res);
1248     }
1250     return eval;
1253 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1255     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1256         // If there is no pasteboard, return YES to indicate that there is text
1257         // to copy.
1258         if (!pboard)
1259             return YES;
1261         clip_copy_selection();
1263         // Get the text to put on the pasteboard.
1264         long_u llen = 0; char_u *str = 0;
1265         int type = clip_convert_selection(&str, &llen, &clip_star);
1266         if (type < 0)
1267             return NO;
1268         
1269         // TODO: Avoid overflow.
1270         int len = (int)llen;
1271 #ifdef FEAT_MBYTE
1272         if (output_conv.vc_type != CONV_NONE) {
1273             char_u *conv_str = string_convert(&output_conv, str, &len);
1274             if (conv_str) {
1275                 vim_free(str);
1276                 str = conv_str;
1277             }
1278         }
1279 #endif
1281         NSString *string = [[NSString alloc]
1282             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1284         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1285         [pboard declareTypes:types owner:nil];
1286         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1287     
1288         [string release];
1289         vim_free(str);
1291         return ok;
1292     }
1294     return NO;
1297 - (oneway void)addReply:(in bycopy NSString *)reply
1298                  server:(in byref id <MMVimServerProtocol>)server
1300     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1302     // Replies might come at any time and in any order so we keep them in an
1303     // array inside a dictionary with the send port used as key.
1305     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1306     // HACK! Assume connection uses mach ports.
1307     int port = [(NSMachPort*)[conn sendPort] machPort];
1308     NSNumber *key = [NSNumber numberWithInt:port];
1310     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1311     if (!replies) {
1312         replies = [NSMutableArray array];
1313         [serverReplyDict setObject:replies forKey:key];
1314     }
1316     [replies addObject:reply];
1319 - (void)addInput:(in bycopy NSString *)input
1320                  client:(in byref id <MMVimClientProtocol>)client
1322     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1324     [self addInput:input];
1325     [self addClient:(id)client];
1327     inputReceived = YES;
1330 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1331                  client:(in byref id <MMVimClientProtocol>)client
1333     [self addClient:(id)client];
1334     return [self evaluateExpression:expr];
1337 - (void)registerServerWithName:(NSString *)name
1339     NSString *svrName = name;
1340     NSConnection *svrConn = [NSConnection defaultConnection];
1341     unsigned i;
1343     for (i = 0; i < MMServerMax; ++i) {
1344         NSString *connName = [self connectionNameFromServerName:svrName];
1346         if ([svrConn registerName:connName]) {
1347             //NSLog(@"Registered server with name: %@", svrName);
1349             // TODO: Set request/reply time-outs to something else?
1350             //
1351             // Don't wait for requests (time-out means that the message is
1352             // dropped).
1353             [svrConn setRequestTimeout:0];
1354             //[svrConn setReplyTimeout:MMReplyTimeout];
1355             [svrConn setRootObject:self];
1357             char_u *s = (char_u*)[svrName UTF8String];
1358 #ifdef FEAT_MBYTE
1359             s = CONVERT_FROM_UTF8(s);
1360 #endif
1361             // NOTE: 'serverName' is a global variable
1362             serverName = vim_strsave(s);
1363 #ifdef FEAT_MBYTE
1364             CONVERT_FROM_UTF8_FREE(s);
1365 #endif
1366 #ifdef FEAT_EVAL
1367             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1368 #endif
1369 #ifdef FEAT_TITLE
1370             need_maketitle = TRUE;
1371 #endif
1372             [self queueMessage:SetServerNameMsgID data:
1373                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1374             break;
1375         }
1377         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1378     }
1381 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1382                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1383               silent:(BOOL)silent
1385     // NOTE: If 'name' equals 'serverName' then the request is local (client
1386     // and server are the same).  This case is not handled separately, so a
1387     // connection will be set up anyway (this simplifies the code).
1389     NSConnection *conn = [self connectionForServerName:name];
1390     if (!conn) {
1391         if (!silent) {
1392             char_u *s = (char_u*)[name UTF8String];
1393 #ifdef FEAT_MBYTE
1394             s = CONVERT_FROM_UTF8(s);
1395 #endif
1396             EMSG2(_(e_noserver), s);
1397 #ifdef FEAT_MBYTE
1398             CONVERT_FROM_UTF8_FREE(s);
1399 #endif
1400         }
1401         return NO;
1402     }
1404     if (port) {
1405         // HACK! Assume connection uses mach ports.
1406         *port = [(NSMachPort*)[conn sendPort] machPort];
1407     }
1409     id proxy = [conn rootProxy];
1410     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1412     @try {
1413         if (expr) {
1414             NSString *eval = [proxy evaluateExpression:string client:self];
1415             if (reply) {
1416                 if (eval) {
1417                     char_u *r = (char_u*)[eval UTF8String];
1418 #ifdef FEAT_MBYTE
1419                     r = CONVERT_FROM_UTF8(r);
1420 #endif
1421                     *reply = vim_strsave(r);
1422 #ifdef FEAT_MBYTE
1423                     CONVERT_FROM_UTF8_FREE(r);
1424 #endif
1425                 } else {
1426                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1427                 }
1428             }
1430             if (!eval)
1431                 return NO;
1432         } else {
1433             [proxy addInput:string client:self];
1434         }
1435     }
1436     @catch (NSException *e) {
1437         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1438         return NO;
1439     }
1441     return YES;
1444 - (NSArray *)serverList
1446     NSArray *list = nil;
1448     if ([self connection]) {
1449         id proxy = [connection rootProxy];
1450         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1452         @try {
1453             list = [proxy serverList];
1454         }
1455         @catch (NSException *e) {
1456             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1457         }
1458     } else {
1459         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1460     }
1462     return list;
1465 - (NSString *)peekForReplyOnPort:(int)port
1467     //NSLog(@"%s%d", _cmd, port);
1469     NSNumber *key = [NSNumber numberWithInt:port];
1470     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1471     if (replies && [replies count]) {
1472         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1473         //        [replies objectAtIndex:0]);
1474         return [replies objectAtIndex:0];
1475     }
1477     //NSLog(@"    No replies");
1478     return nil;
1481 - (NSString *)waitForReplyOnPort:(int)port
1483     //NSLog(@"%s%d", _cmd, port);
1484     
1485     NSConnection *conn = [self connectionForServerPort:port];
1486     if (!conn)
1487         return nil;
1489     NSNumber *key = [NSNumber numberWithInt:port];
1490     NSMutableArray *replies = nil;
1491     NSString *reply = nil;
1493     // Wait for reply as long as the connection to the server is valid (unless
1494     // user interrupts wait with Ctrl-C).
1495     while (!got_int && [conn isValid] &&
1496             !(replies = [serverReplyDict objectForKey:key])) {
1497         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1498                                  beforeDate:[NSDate distantFuture]];
1499     }
1501     if (replies) {
1502         if ([replies count] > 0) {
1503             reply = [[replies objectAtIndex:0] retain];
1504             //NSLog(@"    Got reply: %@", reply);
1505             [replies removeObjectAtIndex:0];
1506             [reply autorelease];
1507         }
1509         if ([replies count] == 0)
1510             [serverReplyDict removeObjectForKey:key];
1511     }
1513     return reply;
1516 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1518     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1519     if (client) {
1520         @try {
1521             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1522             [client addReply:reply server:self];
1523             return YES;
1524         }
1525         @catch (NSException *e) {
1526             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1527         }
1528     } else {
1529         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1530     }
1532     return NO;
1535 @end // MMBackend
1539 @implementation MMBackend (Private)
1541 - (void)processInputQueue
1543     // NOTE: One of the input events may cause this method to be called
1544     // recursively, so copy the input queue to a local variable and clear it
1545     // before starting to process input events (otherwise we could get stuck in
1546     // an endless loop).
1547     NSArray *q = [inputQueue copy];
1548     unsigned i, count = [q count];
1550     [inputQueue removeAllObjects];
1552     for (i = 0; i < count-1; i += 2) {
1553         int msgid = [[q objectAtIndex:i] intValue];
1554         id data = [q objectAtIndex:i+1];
1555         if ([data isEqual:[NSNull null]])
1556             data = nil;
1558         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1559         [self handleInputEvent:msgid data:data];
1560     }
1562     [q release];
1563     //NSLog(@"Clear input event queue");
1566 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1568     // NOTE: Be careful with what you do in this method.  Ideally, a message
1569     // should be handled by adding something to the input buffer and returning
1570     // immediately.  If you call a Vim function then it should not enter a loop
1571     // waiting for key presses or in any other way block the process.  The
1572     // reason for this being that only one message can be processed at a time,
1573     // so if another message is received while processing, then the new message
1574     // is dropped.  See also the comment in processInput:data:.
1576     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1578     if (SelectTabMsgID == msgid) {
1579         if (!data) return;
1580         const void *bytes = [data bytes];
1581         int idx = *((int*)bytes) + 1;
1582         //NSLog(@"Selecting tab %d", idx);
1583         send_tabline_event(idx);
1584     } else if (CloseTabMsgID == msgid) {
1585         if (!data) return;
1586         const void *bytes = [data bytes];
1587         int idx = *((int*)bytes) + 1;
1588         //NSLog(@"Closing tab %d", idx);
1589         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1590     } else if (AddNewTabMsgID == msgid) {
1591         //NSLog(@"Adding new tab");
1592         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1593     } else if (DraggedTabMsgID == msgid) {
1594         if (!data) return;
1595         const void *bytes = [data bytes];
1596         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1597         // based.
1598         int idx = *((int*)bytes);
1600         tabpage_move(idx);
1601     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) {
1602         if (!data) return;
1603         const void *bytes = [data bytes];
1604         int rows = *((int*)bytes);  bytes += sizeof(int);
1605         int cols = *((int*)bytes);  bytes += sizeof(int);
1607         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1608         // gui_resize_shell(), so we have to manually set the rows and columns
1609         // here.  (MacVim doesn't change the rows and columns to avoid
1610         // inconsistent states between Vim and MacVim.)
1611         [self queueMessage:msgid data:data];
1613         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1614         gui_resize_shell(cols, rows);
1615     } else if (ExecuteMenuMsgID == msgid) {
1616         if (!data) return;
1617         const void *bytes = [data bytes];
1618         int tag = *((int*)bytes);  bytes += sizeof(int);
1620         vimmenu_T *menu = (vimmenu_T*)tag;
1621         // TODO!  Make sure 'menu' is a valid menu pointer!
1622         if (menu) {
1623             gui_menu_cb(menu);
1624         }
1625     } else if (ToggleToolbarMsgID == msgid) {
1626         [self handleToggleToolbar];
1627     } else if (ScrollbarEventMsgID == msgid) {
1628         [self handleScrollbarEvent:data];
1629     } else if (SetFontMsgID == msgid) {
1630         [self handleSetFont:data];
1631     } else if (VimShouldCloseMsgID == msgid) {
1632         gui_shell_closed();
1633     } else if (DropFilesMsgID == msgid) {
1634         [self handleDropFiles:data];
1635     } else if (DropStringMsgID == msgid) {
1636         [self handleDropString:data];
1637     } else if (GotFocusMsgID == msgid) {
1638         if (!gui.in_focus)
1639             [self focusChange:YES];
1640     } else if (LostFocusMsgID == msgid) {
1641         if (gui.in_focus)
1642             [self focusChange:NO];
1643     } else if (SetMouseShapeMsgID == msgid) {
1644         const void *bytes = [data bytes];
1645         int shape = *((int*)bytes);  bytes += sizeof(int);
1646         update_mouseshape(shape);
1647     } else if (ODBEditMsgID == msgid) {
1648         [self handleOdbEdit:data];
1649     } else if (XcodeModMsgID == msgid) {
1650         [self handleXcodeMod:data];
1651     } else if (CloseMsgID == msgid) {
1652         // If in Ex mode, then simply exit Ex mode (^U:vi<CR>).  Otherwise
1653         // try to close one (Vim-)window by going to Normal mode first
1654         // (CTRL-\_CTRL-N) and then sending ":q<CR>", but only if the
1655         // command-line window is not open.  If the command-line window is open
1656         // then we just go back to normal mode (since CTRL-\_CTRL-N closes the
1657         // command-line window).
1658         if (exmode_active) {
1659             // Exit Ex mode
1660             add_to_input_buf((char_u*)"\x15:vi\n", 5);
1661         } else {
1662             // Go to normal mode
1663             add_to_input_buf((char_u*)"\x1c\xe", 2);
1664             if (0 == cmdwin_type) {
1665                 // Command-line window was not open, so :q
1666                 add_to_input_buf((char_u*)":q\n", 3);
1667             }
1668         }
1669     } else {
1670         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1671     }
1674 + (NSDictionary *)specialKeys
1676     static NSDictionary *specialKeys = nil;
1678     if (!specialKeys) {
1679         NSBundle *mainBundle = [NSBundle mainBundle];
1680         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1681                                               ofType:@"plist"];
1682         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1683     }
1685     return specialKeys;
1688 - (void)handleInsertText:(NSData *)data
1690     if (!data) return;
1692     NSString *key = [[NSString alloc] initWithData:data
1693                                           encoding:NSUTF8StringEncoding];
1694     char_u *str = (char_u*)[key UTF8String];
1695     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1697 #ifdef FEAT_MBYTE
1698     char_u *conv_str = NULL;
1699     if (input_conv.vc_type != CONV_NONE) {
1700         conv_str = string_convert(&input_conv, str, &len);
1701         if (conv_str)
1702             str = conv_str;
1703     }
1704 #endif
1706     if (len == 1 && ((str[0] == Ctrl_C && ctrl_c_interrupts)
1707             || (str[0] == intr_char && intr_char != Ctrl_C))) {
1708         trash_input_buf();
1709         got_int = TRUE;
1710     }
1712     for (i = 0; i < len; ++i) {
1713         add_to_input_buf(str+i, 1);
1714         if (CSI == str[i]) {
1715             // NOTE: If the converted string contains the byte CSI, then it
1716             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1717             // won't work.
1718             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1719             add_to_input_buf(extra, 2);
1720         }
1721     }
1723 #ifdef FEAT_MBYTE
1724     if (conv_str)
1725         vim_free(conv_str);
1726 #endif
1727     [key release];
1730 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1732     char_u special[3];
1733     char_u modChars[3];
1734     char_u *chars = (char_u*)[key UTF8String];
1735 #ifdef FEAT_MBYTE
1736     char_u *conv_str = NULL;
1737 #endif
1738     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1740     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1741     // that new keys can easily be added.
1742     NSString *specialString = [[MMBackend specialKeys]
1743             objectForKey:key];
1744     if (specialString && [specialString length] > 1) {
1745         //NSLog(@"special key: %@", specialString);
1746         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1747                 [specialString characterAtIndex:1]);
1749         ikey = simplify_key(ikey, &mods);
1750         if (ikey == CSI)
1751             ikey = K_CSI;
1753         special[0] = CSI;
1754         special[1] = K_SECOND(ikey);
1755         special[2] = K_THIRD(ikey);
1757         chars = special;
1758         length = 3;
1759     } else if (1 == length && TAB == chars[0]) {
1760         // Tab is a trouble child:
1761         // - <Tab> is added to the input buffer as is
1762         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1763         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1764         //   to be converted to utf-8
1765         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1766         // - <C-Tab> is reserved by Mac OS X
1767         // - <D-Tab> is reserved by Mac OS X
1768         chars = special;
1769         special[0] = TAB;
1770         length = 1;
1772         if (mods & MOD_MASK_SHIFT) {
1773             mods &= ~MOD_MASK_SHIFT;
1774             special[0] = CSI;
1775             special[1] = K_SECOND(K_S_TAB);
1776             special[2] = K_THIRD(K_S_TAB);
1777             length = 3;
1778         } else if (mods & MOD_MASK_ALT) {
1779             int mtab = 0x80 | TAB;
1780 #ifdef FEAT_MBYTE
1781             if (enc_utf8) {
1782                 // Convert to utf-8
1783                 special[0] = (mtab >> 6) + 0xc0;
1784                 special[1] = mtab & 0xbf;
1785                 length = 2;
1786             } else
1787 #endif
1788             {
1789                 special[0] = mtab;
1790                 length = 1;
1791             }
1792             mods &= ~MOD_MASK_ALT;
1793         }
1794     } else if (length > 0) {
1795         unichar c = [key characterAtIndex:0];
1797         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1798         //        [key characterAtIndex:0], mods);
1800         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1801                 || (c == intr_char && intr_char != Ctrl_C))) {
1802             trash_input_buf();
1803             got_int = TRUE;
1804         }
1806         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1807         // cleared since they are already added to the key by the AppKit.
1808         // Unfortunately, the only way to deal with when to clear the modifiers
1809         // or not seems to be to have hard-wired rules like this.
1810         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1811                     || 0x9 == c || 0xd == c || ESC == c) ) {
1812             mods &= ~MOD_MASK_SHIFT;
1813             mods &= ~MOD_MASK_CTRL;
1814             //NSLog(@"clear shift ctrl");
1815         }
1817         // HACK!  All Option+key presses go via 'insert text' messages, except
1818         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1819         // not work to map to it.
1820         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1821             //NSLog(@"clear alt");
1822             mods &= ~MOD_MASK_ALT;
1823         }
1825 #ifdef FEAT_MBYTE
1826         if (input_conv.vc_type != CONV_NONE) {
1827             conv_str = string_convert(&input_conv, chars, &length);
1828             if (conv_str)
1829                 chars = conv_str;
1830         }
1831 #endif
1832     }
1834     if (chars && length > 0) {
1835         if (mods) {
1836             //NSLog(@"adding mods: %d", mods);
1837             modChars[0] = CSI;
1838             modChars[1] = KS_MODIFIER;
1839             modChars[2] = mods;
1840             add_to_input_buf(modChars, 3);
1841         }
1843         //NSLog(@"add to input buf: 0x%x", chars[0]);
1844         // TODO: Check for CSI bytes?
1845         add_to_input_buf(chars, length);
1846     }
1848 #ifdef FEAT_MBYTE
1849     if (conv_str)
1850         vim_free(conv_str);
1851 #endif
1854 - (void)queueMessage:(int)msgid data:(NSData *)data
1856     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1857     if (data)
1858         [outputQueue addObject:data];
1859     else
1860         [outputQueue addObject:[NSData data]];
1863 - (void)connectionDidDie:(NSNotification *)notification
1865     // If the main connection to MacVim is lost this means that MacVim was
1866     // either quit (by the user chosing Quit on the MacVim menu), or it has
1867     // crashed.  In the former case the flag 'isTerminating' is set and we then
1868     // quit cleanly; in the latter case we make sure the swap files are left
1869     // for recovery.
1871     //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1872     if (isTerminating)
1873         getout(0);
1874     else
1875         getout_preserve_modified(1);
1878 - (void)blinkTimerFired:(NSTimer *)timer
1880     NSTimeInterval timeInterval = 0;
1882     [blinkTimer release];
1883     blinkTimer = nil;
1885     if (MMBlinkStateOn == blinkState) {
1886         gui_undraw_cursor();
1887         blinkState = MMBlinkStateOff;
1888         timeInterval = blinkOffInterval;
1889     } else if (MMBlinkStateOff == blinkState) {
1890         gui_update_cursor(TRUE, FALSE);
1891         blinkState = MMBlinkStateOn;
1892         timeInterval = blinkOnInterval;
1893     }
1895     if (timeInterval > 0) {
1896         blinkTimer = 
1897             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1898                                             selector:@selector(blinkTimerFired:)
1899                                             userInfo:nil repeats:NO] retain];
1900         [self flushQueue:YES];
1901     }
1904 - (void)focusChange:(BOOL)on
1906     gui_focus_change(on);
1909 - (void)handleToggleToolbar
1911     // If 'go' contains 'T', then remove it, else add it.
1913     char_u go[sizeof(GO_ALL)+2];
1914     char_u *p;
1915     int len;
1917     STRCPY(go, p_go);
1918     p = vim_strchr(go, GO_TOOLBAR);
1919     len = STRLEN(go);
1921     if (p != NULL) {
1922         char_u *end = go + len;
1923         while (p < end) {
1924             p[0] = p[1];
1925             ++p;
1926         }
1927     } else {
1928         go[len] = GO_TOOLBAR;
1929         go[len+1] = NUL;
1930     }
1932     set_option_value((char_u*)"guioptions", 0, go, 0);
1934     // Force screen redraw (does it have to be this complicated?).
1935     redraw_all_later(CLEAR);
1936     update_screen(NOT_VALID);
1937     setcursor();
1938     out_flush();
1939     gui_update_cursor(FALSE, FALSE);
1940     gui_mch_flush();
1943 - (void)handleScrollbarEvent:(NSData *)data
1945     if (!data) return;
1947     const void *bytes = [data bytes];
1948     long ident = *((long*)bytes);  bytes += sizeof(long);
1949     int hitPart = *((int*)bytes);  bytes += sizeof(int);
1950     float fval = *((float*)bytes);  bytes += sizeof(float);
1951     scrollbar_T *sb = gui_find_scrollbar(ident);
1953     if (sb) {
1954         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1955         long value = sb_info->value;
1956         long size = sb_info->size;
1957         long max = sb_info->max;
1958         BOOL isStillDragging = NO;
1959         BOOL updateKnob = YES;
1961         switch (hitPart) {
1962         case NSScrollerDecrementPage:
1963             value -= (size > 2 ? size - 2 : 1);
1964             break;
1965         case NSScrollerIncrementPage:
1966             value += (size > 2 ? size - 2 : 1);
1967             break;
1968         case NSScrollerDecrementLine:
1969             --value;
1970             break;
1971         case NSScrollerIncrementLine:
1972             ++value;
1973             break;
1974         case NSScrollerKnob:
1975             isStillDragging = YES;
1976             // fall through ...
1977         case NSScrollerKnobSlot:
1978             value = (long)(fval * (max - size + 1));
1979             // fall through ...
1980         default:
1981             updateKnob = NO;
1982             break;
1983         }
1985         //NSLog(@"value %d -> %d", sb_info->value, value);
1986         gui_drag_scrollbar(sb, value, isStillDragging);
1988         if (updateKnob) {
1989             // Dragging the knob or option+clicking automatically updates
1990             // the knob position (on the actual NSScroller), so we only
1991             // need to set the knob position in the other cases.
1992             if (sb->wp) {
1993                 // Update both the left&right vertical scrollbars.
1994                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1995                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1996                 [self setScrollbarThumbValue:value size:size max:max
1997                                   identifier:identLeft];
1998                 [self setScrollbarThumbValue:value size:size max:max
1999                                   identifier:identRight];
2000             } else {
2001                 // Update the horizontal scrollbar.
2002                 [self setScrollbarThumbValue:value size:size max:max
2003                                   identifier:ident];
2004             }
2005         }
2006     }
2009 - (void)handleSetFont:(NSData *)data
2011     if (!data) return;
2013     const void *bytes = [data bytes];
2014     float pointSize = *((float*)bytes);  bytes += sizeof(float);
2015     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2016     bytes += sizeof(unsigned);  // len not used
2018     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2019     [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
2020     char_u *s = (char_u*)[name UTF8String];
2022 #ifdef FEAT_MBYTE
2023     s = CONVERT_FROM_UTF8(s);
2024 #endif
2026     set_option_value((char_u*)"guifont", 0, s, 0);
2028 #ifdef FEAT_MBYTE
2029     CONVERT_FROM_UTF8_FREE(s);
2030 #endif
2032     // Force screen redraw (does it have to be this complicated?).
2033     redraw_all_later(CLEAR);
2034     update_screen(NOT_VALID);
2035     setcursor();
2036     out_flush();
2037     gui_update_cursor(FALSE, FALSE);
2038     gui_mch_flush();
2041 - (void)handleDropFiles:(NSData *)data
2043     // TODO: Get rid of this method; instead use Vim script directly.  At the
2044     // moment I know how to do this to open files in tabs, but I'm not sure how
2045     // to add the filenames to the command line when in command line mode.
2047     if (!data) return;
2049 #ifdef FEAT_DND
2050     const void *bytes = [data bytes];
2051     const void *end = [data bytes] + [data length];
2052     BOOL forceOpen = *((BOOL*)bytes);  bytes += sizeof(BOOL);
2053     int n = *((int*)bytes);  bytes += sizeof(int);
2055     if (!forceOpen && (State & CMDLINE)) {
2056         // HACK!  If Vim is in command line mode then the files names
2057         // should be added to the command line, instead of opening the
2058         // files in tabs (unless forceOpen is set).  This is taken care of by
2059         // gui_handle_drop().
2060         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2061         if (fnames) {
2062             int i = 0;
2063             while (bytes < end && i < n) {
2064                 int len = *((int*)bytes);  bytes += sizeof(int);
2065                 char_u *s = (char_u*)bytes;
2066 #ifdef FEAT_MBYTE
2067                 s = CONVERT_FROM_UTF8(s);
2068 #endif
2069                 fnames[i++] = vim_strsave(s);
2070 #ifdef FEAT_MBYTE
2071                 CONVERT_FROM_UTF8_FREE(s);
2072 #endif
2073                 bytes += len;
2074             }
2076             // NOTE!  This function will free 'fnames'.
2077             // HACK!  It is assumed that the 'x' and 'y' arguments are
2078             // unused when in command line mode.
2079             gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
2080         }
2081     } else {
2082         // HACK!  I'm not sure how to get Vim to open a list of files in
2083         // tabs, so instead I create a ':tab drop' command with all the
2084         // files to open and execute it.
2085         NSMutableString *cmd = [NSMutableString stringWithString:@":tab drop"];
2087         int i;
2088         for (i = 0; i < n && bytes < end; ++i) {
2089             int len = *((int*)bytes);  bytes += sizeof(int);
2090             NSString *file = [NSString stringWithUTF8String:bytes];
2091             file = [file stringByEscapingSpecialFilenameCharacters];
2092             bytes += len;
2094             [cmd appendString:@" "];
2095             [cmd appendString:file];
2096         }
2098         // By going to the last tabpage we ensure that the new tabs will
2099         // appear last (if this call is left out, the taborder becomes
2100         // messy).
2101         goto_tabpage(9999);
2103         char_u *s = (char_u*)[cmd UTF8String];
2104 #ifdef FEAT_MBYTE
2105         s = CONVERT_FROM_UTF8(s);
2106 #endif
2107         do_cmdline_cmd(s);
2108 #ifdef FEAT_MBYTE
2109         CONVERT_FROM_UTF8_FREE(s);
2110 #endif
2112         // Force screen redraw (does it have to be this complicated?).
2113         // (This code was taken from the end of gui_handle_drop().)
2114         update_screen(NOT_VALID);
2115         setcursor();
2116         out_flush();
2117         gui_update_cursor(FALSE, FALSE);
2118         maketitle();
2119         gui_mch_flush();
2120     }
2121 #endif // FEAT_DND
2124 - (void)handleDropString:(NSData *)data
2126     if (!data) return;
2128 #ifdef FEAT_DND
2129     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2130     const void *bytes = [data bytes];
2131     int len = *((int*)bytes);  bytes += sizeof(int);
2132     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2134     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2135     NSRange range = { 0, [string length] };
2136     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2137                                          withString:@"\x0a" options:0
2138                                               range:range];
2139     if (0 == n) {
2140         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2141                                        options:0 range:range];
2142     }
2144     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2145     char_u *s = (char_u*)[string UTF8String];
2146 #ifdef FEAT_MBYTE
2147     if (input_conv.vc_type != CONV_NONE)
2148         s = string_convert(&input_conv, s, &len);
2149 #endif
2150     dnd_yank_drag_data(s, len);
2151 #ifdef FEAT_MBYTE
2152     if (input_conv.vc_type != CONV_NONE)
2153         vim_free(s);
2154 #endif
2155     add_to_input_buf(dropkey, sizeof(dropkey));
2156 #endif // FEAT_DND
2159 - (void)handleOdbEdit:(NSData *)data
2161 #ifdef FEAT_ODB_EDITOR
2162     const void *bytes = [data bytes];
2164     OSType serverID = *((OSType*)bytes);  bytes += sizeof(OSType);
2166     char_u *path = NULL;
2167     int pathLen = *((int*)bytes);  bytes += sizeof(int);
2168     if (pathLen > 0) {
2169         path = (char_u*)bytes;
2170         bytes += pathLen;
2171 #ifdef FEAT_MBYTE
2172         path = CONVERT_FROM_UTF8(path);
2173 #endif
2174     }
2176     NSAppleEventDescriptor *token = nil;
2177     DescType tokenType = *((DescType*)bytes);  bytes += sizeof(DescType);
2178     int descLen = *((int*)bytes);  bytes += sizeof(int);
2179     if (descLen > 0) {
2180         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2181                                                                bytes:bytes
2182                                                               length:descLen];
2183         bytes += descLen;
2184     }
2186     unsigned i, numFiles = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2187     for (i = 0; i < numFiles; ++i) {
2188         int len = *((int*)bytes);  bytes += sizeof(int);
2189         char_u *filename = (char_u*)bytes;
2190 #ifdef FEAT_MBYTE
2191         filename = CONVERT_FROM_UTF8(filename);
2192 #endif
2193         buf_T *buf = buflist_findname(filename);
2194         if (buf) {
2195             if (buf->b_odb_token) {
2196                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2197                 buf->b_odb_token = NULL;
2198             }
2200             if (buf->b_odb_fname) {
2201                 vim_free(buf->b_odb_fname);
2202                 buf->b_odb_fname = NULL;
2203             }
2205             buf->b_odb_server_id = serverID;
2207             if (token)
2208                 buf->b_odb_token = [token retain];
2209             if (path)
2210                 buf->b_odb_fname = vim_strsave(path);
2211         } else {
2212             NSLog(@"WARNING: Could not find buffer '%s' for ODB editing.",
2213                     filename);
2214         }
2216 #ifdef FEAT_MBYTE
2217         CONVERT_FROM_UTF8_FREE(filename);
2218 #endif
2219         bytes += len;
2220     }
2221 #ifdef FEAT_MBYTE
2222     CONVERT_FROM_UTF8_FREE(path);
2223 #endif
2224 #endif // FEAT_ODB_EDITOR
2227 - (void)handleXcodeMod:(NSData *)data
2229 #if 0
2230     const void *bytes = [data bytes];
2231     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2232     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2233     if (0 == len)
2234         return;
2236     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2237             descriptorWithDescriptorType:type
2238                                    bytes:bytes
2239                                   length:len];
2240 #endif
2243 - (BOOL)checkForModifiedBuffers
2245     buf_T *buf;
2246     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2247         if (bufIsChanged(buf)) {
2248             return YES;
2249         }
2250     }
2252     return NO;
2255 - (void)addInput:(NSString *)input
2257     char_u *s = (char_u*)[input UTF8String];
2259 #ifdef FEAT_MBYTE
2260     s = CONVERT_FROM_UTF8(s);
2261 #endif
2263     server_to_input_buf(s);
2265 #ifdef FEAT_MBYTE
2266     CONVERT_FROM_UTF8_FREE(s);
2267 #endif
2270 @end // MMBackend (Private)
2275 @implementation MMBackend (ClientServer)
2277 - (NSString *)connectionNameFromServerName:(NSString *)name
2279     NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2281     return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2282         lowercaseString];
2285 - (NSConnection *)connectionForServerName:(NSString *)name
2287     // TODO: Try 'name%d' if 'name' fails.
2288     NSString *connName = [self connectionNameFromServerName:name];
2289     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2291     if (!svrConn) {
2292         svrConn = [NSConnection connectionWithRegisteredName:connName
2293                                                            host:nil];
2294         // Try alternate server...
2295         if (!svrConn && alternateServerName) {
2296             //NSLog(@"  trying to connect to alternate server: %@",
2297             //        alternateServerName);
2298             connName = [self connectionNameFromServerName:alternateServerName];
2299             svrConn = [NSConnection connectionWithRegisteredName:connName
2300                                                             host:nil];
2301         }
2303         // Try looking for alternate servers...
2304         if (!svrConn) {
2305             //NSLog(@"  looking for alternate servers...");
2306             NSString *alt = [self alternateServerNameForName:name];
2307             if (alt != alternateServerName) {
2308                 //NSLog(@"  found alternate server: %@", string);
2309                 [alternateServerName release];
2310                 alternateServerName = [alt copy];
2311             }
2312         }
2314         // Try alternate server again...
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         if (svrConn) {
2324             [connectionNameDict setObject:svrConn forKey:connName];
2326             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2327             [[NSNotificationCenter defaultCenter] addObserver:self
2328                     selector:@selector(serverConnectionDidDie:)
2329                         name:NSConnectionDidDieNotification object:svrConn];
2330         }
2331     }
2333     return svrConn;
2336 - (NSConnection *)connectionForServerPort:(int)port
2338     NSConnection *conn;
2339     NSEnumerator *e = [connectionNameDict objectEnumerator];
2341     while ((conn = [e nextObject])) {
2342         // HACK! Assume connection uses mach ports.
2343         if (port == [(NSMachPort*)[conn sendPort] machPort])
2344             return conn;
2345     }
2347     return nil;
2350 - (void)serverConnectionDidDie:(NSNotification *)notification
2352     //NSLog(@"%s%@", _cmd, notification);
2354     NSConnection *svrConn = [notification object];
2356     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2357     [[NSNotificationCenter defaultCenter]
2358             removeObserver:self
2359                       name:NSConnectionDidDieNotification
2360                     object:svrConn];
2362     [connectionNameDict removeObjectsForKeys:
2363         [connectionNameDict allKeysForObject:svrConn]];
2365     // HACK! Assume connection uses mach ports.
2366     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2367     NSNumber *key = [NSNumber numberWithInt:port];
2369     [clientProxyDict removeObjectForKey:key];
2370     [serverReplyDict removeObjectForKey:key];
2373 - (void)addClient:(NSDistantObject *)client
2375     NSConnection *conn = [client connectionForProxy];
2376     // HACK! Assume connection uses mach ports.
2377     int port = [(NSMachPort*)[conn sendPort] machPort];
2378     NSNumber *key = [NSNumber numberWithInt:port];
2380     if (![clientProxyDict objectForKey:key]) {
2381         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2382         [clientProxyDict setObject:client forKey:key];
2383     }
2385     // NOTE: 'clientWindow' is a global variable which is used by <client>
2386     clientWindow = port;
2389 - (NSString *)alternateServerNameForName:(NSString *)name
2391     if (!(name && [name length] > 0))
2392         return nil;
2394     // Only look for alternates if 'name' doesn't end in a digit.
2395     unichar lastChar = [name characterAtIndex:[name length]-1];
2396     if (lastChar >= '0' && lastChar <= '9')
2397         return nil;
2399     // Look for alternates among all current servers.
2400     NSArray *list = [self serverList];
2401     if (!(list && [list count] > 0))
2402         return nil;
2404     // Filter out servers starting with 'name' and ending with a number. The
2405     // (?i) pattern ensures that the match is case insensitive.
2406     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2407     NSPredicate *pred = [NSPredicate predicateWithFormat:
2408             @"SELF MATCHES %@", pat];
2409     list = [list filteredArrayUsingPredicate:pred];
2410     if ([list count] > 0) {
2411         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2412         return [list objectAtIndex:0];
2413     }
2415     return nil;
2418 @end // MMBackend (ClientServer)
2423 @implementation NSString (MMServerNameCompare)
2424 - (NSComparisonResult)serverNameCompare:(NSString *)string
2426     return [self compare:string
2427                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2429 @end
2434 static int eventModifierFlagsToVimModMask(int modifierFlags)
2436     int modMask = 0;
2438     if (modifierFlags & NSShiftKeyMask)
2439         modMask |= MOD_MASK_SHIFT;
2440     if (modifierFlags & NSControlKeyMask)
2441         modMask |= MOD_MASK_CTRL;
2442     if (modifierFlags & NSAlternateKeyMask)
2443         modMask |= MOD_MASK_ALT;
2444     if (modifierFlags & NSCommandKeyMask)
2445         modMask |= MOD_MASK_CMD;
2447     return modMask;
2450 static int vimModMaskToEventModifierFlags(int mods)
2452     int flags = 0;
2454     if (mods & MOD_MASK_SHIFT)
2455         flags |= NSShiftKeyMask;
2456     if (mods & MOD_MASK_CTRL)
2457         flags |= NSControlKeyMask;
2458     if (mods & MOD_MASK_ALT)
2459         flags |= NSAlternateKeyMask;
2460     if (mods & MOD_MASK_CMD)
2461         flags |= NSCommandKeyMask;
2463     return flags;
2466 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2468     int modMask = 0;
2470     if (modifierFlags & NSShiftKeyMask)
2471         modMask |= MOUSE_SHIFT;
2472     if (modifierFlags & NSControlKeyMask)
2473         modMask |= MOUSE_CTRL;
2474     if (modifierFlags & NSAlternateKeyMask)
2475         modMask |= MOUSE_ALT;
2477     return modMask;
2480 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2482     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2484     return (buttonNumber >= 0 && buttonNumber < 3)
2485             ? mouseButton[buttonNumber] : -1;
2488 static int specialKeyToNSKey(int key)
2490     if (!IS_SPECIAL(key))
2491         return key;
2493     static struct {
2494         int special;
2495         int nskey;
2496     } sp2ns[] = {
2497         { K_UP, NSUpArrowFunctionKey },
2498         { K_DOWN, NSDownArrowFunctionKey },
2499         { K_LEFT, NSLeftArrowFunctionKey },
2500         { K_RIGHT, NSRightArrowFunctionKey },
2501         { K_F1, NSF1FunctionKey },
2502         { K_F2, NSF2FunctionKey },
2503         { K_F3, NSF3FunctionKey },
2504         { K_F4, NSF4FunctionKey },
2505         { K_F5, NSF5FunctionKey },
2506         { K_F6, NSF6FunctionKey },
2507         { K_F7, NSF7FunctionKey },
2508         { K_F8, NSF8FunctionKey },
2509         { K_F9, NSF9FunctionKey },
2510         { K_F10, NSF10FunctionKey },
2511         { K_F11, NSF11FunctionKey },
2512         { K_F12, NSF12FunctionKey },
2513         { K_F13, NSF13FunctionKey },
2514         { K_F14, NSF14FunctionKey },
2515         { K_F15, NSF15FunctionKey },
2516         { K_F16, NSF16FunctionKey },
2517         { K_F17, NSF17FunctionKey },
2518         { K_F18, NSF18FunctionKey },
2519         { K_F19, NSF19FunctionKey },
2520         { K_F20, NSF20FunctionKey },
2521         { K_F21, NSF21FunctionKey },
2522         { K_F22, NSF22FunctionKey },
2523         { K_F23, NSF23FunctionKey },
2524         { K_F24, NSF24FunctionKey },
2525         { K_F25, NSF25FunctionKey },
2526         { K_F26, NSF26FunctionKey },
2527         { K_F27, NSF27FunctionKey },
2528         { K_F28, NSF28FunctionKey },
2529         { K_F29, NSF29FunctionKey },
2530         { K_F30, NSF30FunctionKey },
2531         { K_F31, NSF31FunctionKey },
2532         { K_F32, NSF32FunctionKey },
2533         { K_F33, NSF33FunctionKey },
2534         { K_F34, NSF34FunctionKey },
2535         { K_F35, NSF35FunctionKey },
2536         { K_DEL, NSBackspaceCharacter },
2537         { K_BS, NSDeleteCharacter },
2538         { K_HOME, NSHomeFunctionKey },
2539         { K_END, NSEndFunctionKey },
2540         { K_PAGEUP, NSPageUpFunctionKey },
2541         { K_PAGEDOWN, NSPageDownFunctionKey }
2542     };
2544     int i;
2545     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2546         if (sp2ns[i].special == key)
2547             return sp2ns[i].nskey;
2548     }
2550     return 0;