Change a few toolbar icons
[MacVim.git] / src / MacVim / MMBackend.m
blob8a0a4800acd9cd1f080806e7795096740509efc8
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 eventModifierFlagsToVimMouseModMask(int modifierFlags);
55 static int eventButtonNumberToVimMouseButton(int buttonNumber);
57 // In gui_macvim.m
58 vimmenu_T *menu_for_descriptor(NSArray *desc);
60 static id evalExprCocoa(NSString * expr, NSString ** errstr);
62 enum {
63     MMBlinkStateNone = 0,
64     MMBlinkStateOn,
65     MMBlinkStateOff
68 static NSString *MMSymlinkWarningString =
69     @"\n\n\tMost likely this is because you have symlinked directly to\n"
70      "\tthe Vim binary, which Cocoa does not allow.  Please use an\n"
71      "\talias or the mvim shell script instead.  If you have not used\n"
72      "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
76 @interface NSString (MMServerNameCompare)
77 - (NSComparisonResult)serverNameCompare:(NSString *)string;
78 @end
83 @interface MMBackend (Private)
84 - (void)waitForDialogReturn;
85 - (void)queueVimStateMessage;
86 - (void)processInputQueue;
87 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
88 + (NSDictionary *)specialKeys;
89 - (void)handleInsertText:(NSData *)data;
90 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
91 - (void)queueMessage:(int)msgid data:(NSData *)data;
92 - (void)connectionDidDie:(NSNotification *)notification;
93 - (void)blinkTimerFired:(NSTimer *)timer;
94 - (void)focusChange:(BOOL)on;
95 - (void)handleToggleToolbar;
96 - (void)handleScrollbarEvent:(NSData *)data;
97 - (void)handleSetFont:(NSData *)data;
98 - (void)handleDropFiles:(NSData *)data;
99 - (void)handleDropString:(NSData *)data;
100 - (void)handleOdbEdit:(NSData *)data;
101 - (void)handleXcodeMod:(NSData *)data;
102 - (BOOL)checkForModifiedBuffers;
103 - (void)addInput:(NSString *)input;
104 @end
108 @interface MMBackend (ClientServer)
109 - (NSString *)connectionNameFromServerName:(NSString *)name;
110 - (NSConnection *)connectionForServerName:(NSString *)name;
111 - (NSConnection *)connectionForServerPort:(int)port;
112 - (void)serverConnectionDidDie:(NSNotification *)notification;
113 - (void)addClient:(NSDistantObject *)client;
114 - (NSString *)alternateServerNameForName:(NSString *)name;
115 @end
119 @implementation MMBackend
121 + (MMBackend *)sharedInstance
123     static MMBackend *singleton = nil;
124     return singleton ? singleton : (singleton = [MMBackend new]);
127 - (id)init
129     self = [super init];
130     if (!self) return nil;
132     fontContainerRef = loadFonts();
134     outputQueue = [[NSMutableArray alloc] init];
135     inputQueue = [[NSMutableArray alloc] init];
136     drawData = [[NSMutableData alloc] initWithCapacity:1024];
137     connectionNameDict = [[NSMutableDictionary alloc] init];
138     clientProxyDict = [[NSMutableDictionary alloc] init];
139     serverReplyDict = [[NSMutableDictionary alloc] init];
141     NSBundle *mainBundle = [NSBundle mainBundle];
142     NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
143     if (path)
144         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
146     path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
147     if (path)
148         sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
149             retain];
151     path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
152     if (path)
153         actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
155     if (!(colorDict && sysColorDict && actionDict))
156         NSLog(@"ERROR: Failed to load dictionaries.%@",
157                 MMSymlinkWarningString);
159     return self;
162 - (void)dealloc
164     //NSLog(@"%@ %s", [self className], _cmd);
165     [[NSNotificationCenter defaultCenter] removeObserver:self];
167     [oldWideFont release];  oldWideFont = nil;
168     [blinkTimer release];  blinkTimer = nil;
169     [alternateServerName release];  alternateServerName = nil;
170     [serverReplyDict release];  serverReplyDict = nil;
171     [clientProxyDict release];  clientProxyDict = nil;
172     [connectionNameDict release];  connectionNameDict = nil;
173     [inputQueue release];  inputQueue = nil;
174     [outputQueue release];  outputQueue = nil;
175     [drawData release];  drawData = nil;
176     [frontendProxy release];  frontendProxy = nil;
177     [connection release];  connection = nil;
178     [actionDict release];  actionDict = nil;
179     [sysColorDict release];  sysColorDict = nil;
180     [colorDict release];  colorDict = nil;
182     [super dealloc];
185 - (void)setBackgroundColor:(int)color
187     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
190 - (void)setForegroundColor:(int)color
192     foregroundColor = MM_COLOR(color);
195 - (void)setSpecialColor:(int)color
197     specialColor = MM_COLOR(color);
200 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
202     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
203     defaultForegroundColor = MM_COLOR(fg);
205     NSMutableData *data = [NSMutableData data];
207     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
208     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
210     [self queueMessage:SetDefaultColorsMsgID data:data];
213 - (NSConnection *)connection
215     if (!connection) {
216         // NOTE!  If the name of the connection changes here it must also be
217         // updated in MMAppController.m.
218         NSString *name = [NSString stringWithFormat:@"%@-connection",
219                [[NSBundle mainBundle] bundleIdentifier]];
221         connection = [NSConnection connectionWithRegisteredName:name host:nil];
222         [connection retain];
223     }
225     // NOTE: 'connection' may be nil here.
226     return connection;
229 - (NSDictionary *)actionDict
231     return actionDict;
234 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
236     [self queueMessage:msgid data:[props dictionaryAsData]];
239 - (BOOL)checkin
241     if (![self connection]) {
242         NSBundle *mainBundle = [NSBundle mainBundle];
243 #if 0
244         OSStatus status;
245         FSRef ref;
247         // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
248         // the API to pass Apple Event parameters is broken on 10.4).
249         NSString *path = [mainBundle bundlePath];
250         status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
251         if (noErr == status) {
252             // Pass parameter to the 'Open' Apple Event that tells MacVim not
253             // to open an untitled window.
254             NSAppleEventDescriptor *desc =
255                     [NSAppleEventDescriptor recordDescriptor];
256             [desc setParamDescriptor:
257                     [NSAppleEventDescriptor descriptorWithBoolean:NO]
258                           forKeyword:keyMMUntitledWindow];
260             LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
261                     kLSLaunchDefaults, NULL };
262             status = LSOpenFromRefSpec(&spec, NULL);
263         }
265         if (noErr != status) {
266         NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
267                 path, MMSymlinkWarningString);
268             return NO;
269         }
270 #else
271         // Launch MacVim using NSTask.  For some reason the above code using
272         // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
273         // fails, the dock icon starts bouncing and never stops).  It seems
274         // like rebuilding the Launch Services database takes care of this
275         // problem, but the NSTask way seems more stable so stick with it.
276         //
277         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
278         // that the GUI won't be activated (or raised) so there is a hack in
279         // MMAppController which raises the app when a new window is opened.
280         NSMutableArray *args = [NSMutableArray arrayWithObjects:
281             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
282         NSString *exeName = [[mainBundle infoDictionary]
283                 objectForKey:@"CFBundleExecutable"];
284         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
285         if (!path) {
286             NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
287                     MMSymlinkWarningString);
288             return NO;
289         }
291         [NSTask launchedTaskWithLaunchPath:path arguments:args];
292 #endif
294         // HACK!  Poll the mach bootstrap server until it returns a valid
295         // connection to detect that MacVim has finished launching.  Also set a
296         // time-out date so that we don't get stuck doing this forever.
297         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
298         while (![self connection] &&
299                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
300             [[NSRunLoop currentRunLoop]
301                     runMode:NSDefaultRunLoopMode
302                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
304         // NOTE: [self connection] will set 'connection' as a side-effect.
305         if (!connection) {
306             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
307             return NO;
308         }
309     }
311     BOOL ok = NO;
312     @try {
313         [[NSNotificationCenter defaultCenter] addObserver:self
314                 selector:@selector(connectionDidDie:)
315                     name:NSConnectionDidDieNotification object:connection];
317         id proxy = [connection rootProxy];
318         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
320         int pid = [[NSProcessInfo processInfo] processIdentifier];
322         frontendProxy = [proxy connectBackend:self pid:pid];
323         if (frontendProxy) {
324             [frontendProxy retain];
325             [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
326             ok = YES;
327         }
328     }
329     @catch (NSException *e) {
330         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
331     }
333     return ok;
336 - (BOOL)openVimWindow
338     [self queueMessage:OpenVimWindowMsgID data:nil];
339     return YES;
342 - (void)clearAll
344     int type = ClearAllDrawType;
346     // Any draw commands in queue are effectively obsolete since this clearAll
347     // will negate any effect they have, therefore we may as well clear the
348     // draw queue.
349     [drawData setLength:0];
351     [drawData appendBytes:&type length:sizeof(int)];
354 - (void)clearBlockFromRow:(int)row1 column:(int)col1
355                     toRow:(int)row2 column:(int)col2
357     int type = ClearBlockDrawType;
359     [drawData appendBytes:&type length:sizeof(int)];
361     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
362     [drawData appendBytes:&row1 length:sizeof(int)];
363     [drawData appendBytes:&col1 length:sizeof(int)];
364     [drawData appendBytes:&row2 length:sizeof(int)];
365     [drawData appendBytes:&col2 length:sizeof(int)];
368 - (void)deleteLinesFromRow:(int)row count:(int)count
369               scrollBottom:(int)bottom left:(int)left right:(int)right
371     int type = DeleteLinesDrawType;
373     [drawData appendBytes:&type length:sizeof(int)];
375     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
376     [drawData appendBytes:&row length:sizeof(int)];
377     [drawData appendBytes:&count length:sizeof(int)];
378     [drawData appendBytes:&bottom length:sizeof(int)];
379     [drawData appendBytes:&left length:sizeof(int)];
380     [drawData appendBytes:&right length:sizeof(int)];
383 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
384              cells:(int)cells flags:(int)flags
386     if (len <= 0 || cells <= 0) return;
388     int type = DrawStringDrawType;
390     [drawData appendBytes:&type length:sizeof(int)];
392     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
393     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
394     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
395     [drawData appendBytes:&row length:sizeof(int)];
396     [drawData appendBytes:&col length:sizeof(int)];
397     [drawData appendBytes:&cells length:sizeof(int)];
398     [drawData appendBytes:&flags length:sizeof(int)];
399     [drawData appendBytes:&len length:sizeof(int)];
400     [drawData appendBytes:s length:len];
403 - (void)insertLinesFromRow:(int)row count:(int)count
404               scrollBottom:(int)bottom left:(int)left right:(int)right
406     int type = InsertLinesDrawType;
408     [drawData appendBytes:&type length:sizeof(int)];
410     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
411     [drawData appendBytes:&row length:sizeof(int)];
412     [drawData appendBytes:&count length:sizeof(int)];
413     [drawData appendBytes:&bottom length:sizeof(int)];
414     [drawData appendBytes:&left length:sizeof(int)];
415     [drawData appendBytes:&right length:sizeof(int)];
418 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
419                fraction:(int)percent color:(int)color
421     int type = DrawCursorDrawType;
422     unsigned uc = MM_COLOR(color);
424     [drawData appendBytes:&type length:sizeof(int)];
426     [drawData appendBytes:&uc length:sizeof(unsigned)];
427     [drawData appendBytes:&row length:sizeof(int)];
428     [drawData appendBytes:&col length:sizeof(int)];
429     [drawData appendBytes:&shape length:sizeof(int)];
430     [drawData appendBytes:&percent length:sizeof(int)];
433 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
434                    numColumns:(int)nc invert:(int)invert
436     int type = DrawInvertedRectDrawType;
437     [drawData appendBytes:&type length:sizeof(int)];
439     [drawData appendBytes:&row length:sizeof(int)];
440     [drawData appendBytes:&col length:sizeof(int)];
441     [drawData appendBytes:&nr length:sizeof(int)];
442     [drawData appendBytes:&nc length:sizeof(int)];
443     [drawData appendBytes:&invert length:sizeof(int)];
446 - (void)update
448     // Tend to the run loop, returning immediately if there are no events
449     // waiting.
450     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
451                              beforeDate:[NSDate distantPast]];
453 #if 0
454     // Keyboard and mouse input is handled directly, other input is queued and
455     // processed here.  This call may enter a blocking loop.
456     if ([inputQueue count] > 0)
457         [self processInputQueue];
458 #endif
461 - (void)flushQueue:(BOOL)force
463     // NOTE! This method gets called a lot; if we were to flush every time it
464     // got called MacVim would feel unresponsive.  So there is a time out which
465     // ensures that the queue isn't flushed too often.
466     if (!force && lastFlushDate
467             && -[lastFlushDate timeIntervalSinceNow] < MMFlushTimeoutInterval
468             && [drawData length] < MMFlushQueueLenHint)
469         return;
471     if ([drawData length] > 0) {
472         // HACK!  Detect changes to 'guifontwide'.
473         if (gui.wide_font != (GuiFont)oldWideFont) {
474             [oldWideFont release];
475             oldWideFont = [(NSFont*)gui.wide_font retain];
476             [self setWideFont:oldWideFont];
477         }
479         int type = SetCursorPosDrawType;
480         [drawData appendBytes:&type length:sizeof(type)];
481         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
482         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
484         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
485         [drawData setLength:0];
486     }
488     if ([outputQueue count] > 0 || force) {
489         // When 'force' is set we always update the Vim state to ensure that
490         // MacVim has a copy of the latest state (since 'force' is typically
491         // set just before Vim takes a nap whilst waiting for input).
492         [self queueVimStateMessage];
494         @try {
495             [frontendProxy processCommandQueue:outputQueue];
496         }
497         @catch (NSException *e) {
498             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
499         }
501         [outputQueue removeAllObjects];
503         [lastFlushDate release];
504         lastFlushDate = [[NSDate date] retain];
505     }
508 - (BOOL)waitForInput:(int)milliseconds
510     //NSLog(@"|ENTER| %s%d",  _cmd, milliseconds);
512     // Only start the run loop if the input queue is empty, otherwise process
513     // the input first so that the input on queue isn't delayed.
514     if ([inputQueue count]) {
515         inputReceived = YES;
516     } else {
517         NSDate *date = milliseconds > 0 ?
518                 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
519                 [NSDate distantFuture];
521         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
522                                  beforeDate:date];
523     }
525     // I know of no way to figure out if the run loop exited because input was
526     // found or because of a time out, so I need to manually indicate when
527     // input was received in processInput:data: and then reset it every time
528     // here.
529     BOOL yn = inputReceived;
530     inputReceived = NO;
532     // Keyboard and mouse input is handled directly, other input is queued and
533     // processed here.  This call may enter a blocking loop.
534     if ([inputQueue count] > 0)
535         [self processInputQueue];
537     //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
538     return yn;
541 - (void)exit
543 #ifdef MAC_CLIENTSERVER
544     // The default connection is used for the client/server code.
545     [[NSConnection defaultConnection] setRootObject:nil];
546     [[NSConnection defaultConnection] invalidate];
547 #endif
549     // By invalidating the NSConnection the MMWindowController immediately
550     // finds out that the connection is down and as a result
551     // [MMWindowController connectionDidDie:] is invoked.
552     //NSLog(@"%@ %s", [self className], _cmd);
553     [[NSNotificationCenter defaultCenter] removeObserver:self];
554     [connection invalidate];
556     if (fontContainerRef) {
557         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
558         fontContainerRef = 0;
559     }
563 - (void)selectTab:(int)index
565     //NSLog(@"%s%d", _cmd, index);
567     index -= 1;
568     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
569     [self queueMessage:SelectTabMsgID data:data];
572 - (void)updateTabBar
574     //NSLog(@"%s", _cmd);
576     NSMutableData *data = [NSMutableData data];
578     int idx = tabpage_index(curtab) - 1;
579     [data appendBytes:&idx length:sizeof(int)];
581     tabpage_T *tp;
582     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
583         // This function puts the label of the tab in the global 'NameBuff'.
584         get_tabline_label(tp, FALSE);
585         char_u *s = NameBuff;
586         int len = STRLEN(s);
587         if (len <= 0) continue;
589 #ifdef FEAT_MBYTE
590         s = CONVERT_TO_UTF8(s);
591 #endif
593         // Count the number of windows in the tabpage.
594         //win_T *wp = tp->tp_firstwin;
595         //int wincount;
596         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
598         //[data appendBytes:&wincount length:sizeof(int)];
599         [data appendBytes:&len length:sizeof(int)];
600         [data appendBytes:s length:len];
602 #ifdef FEAT_MBYTE
603         CONVERT_TO_UTF8_FREE(s);
604 #endif
605     }
607     [self queueMessage:UpdateTabBarMsgID data:data];
610 - (BOOL)tabBarVisible
612     return tabBarVisible;
615 - (void)showTabBar:(BOOL)enable
617     tabBarVisible = enable;
619     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
620     [self queueMessage:msgid data:nil];
623 - (void)setRows:(int)rows columns:(int)cols
625     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
627     int dim[] = { rows, cols };
628     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
630     [self queueMessage:SetTextDimensionsMsgID data:data];
633 - (void)setWindowTitle:(char *)title
635     NSMutableData *data = [NSMutableData data];
636     int len = strlen(title);
637     if (len <= 0) return;
639     [data appendBytes:&len length:sizeof(int)];
640     [data appendBytes:title length:len];
642     [self queueMessage:SetWindowTitleMsgID data:data];
645 - (void)setDocumentFilename:(char *)filename
647     NSMutableData *data = [NSMutableData data];
648     int len = filename ? strlen(filename) : 0;
650     [data appendBytes:&len length:sizeof(int)];
651     if (len > 0)
652         [data appendBytes:filename length:len];
654     [self queueMessage:SetDocumentFilenameMsgID data:data];
657 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
659     char_u *s = NULL;
661     @try {
662         [frontendProxy showSavePanelWithAttributes:attr];
664         [self waitForDialogReturn];
666         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
667             char_u *ret = (char_u*)[dialogReturn UTF8String];
668 #ifdef FEAT_MBYTE
669             ret = CONVERT_FROM_UTF8(ret);
670 #endif
671             s = vim_strsave(ret);
672 #ifdef FEAT_MBYTE
673             CONVERT_FROM_UTF8_FREE(ret);
674 #endif
675         }
677         [dialogReturn release];  dialogReturn = nil;
678     }
679     @catch (NSException *e) {
680         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
681     }
683     return (char *)s;
686 - (oneway void)setDialogReturn:(in bycopy id)obj
688     // NOTE: This is called by
689     //   - [MMVimController panelDidEnd:::], and
690     //   - [MMVimController alertDidEnd:::],
691     // to indicate that a save/open panel or alert has finished.
693     // We want to distinguish between "no dialog return yet" and "dialog
694     // returned nothing".  The former can be tested with dialogReturn == nil,
695     // the latter with dialogReturn == [NSNull null].
696     if (!obj) obj = [NSNull null];
698     if (obj != dialogReturn) {
699         [dialogReturn release];
700         dialogReturn = [obj retain];
701     }
704 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
706     int retval = 0;
708     @try {
709         [frontendProxy presentDialogWithAttributes:attr];
711         [self waitForDialogReturn];
713         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
714                 && [dialogReturn count]) {
715             retval = [[dialogReturn objectAtIndex:0] intValue];
716             if (txtfield && [dialogReturn count] > 1) {
717                 NSString *retString = [dialogReturn objectAtIndex:1];
718                 char_u *ret = (char_u*)[retString UTF8String];
719 #ifdef FEAT_MBYTE
720                 ret = CONVERT_FROM_UTF8(ret);
721 #endif
722                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
723 #ifdef FEAT_MBYTE
724                 CONVERT_FROM_UTF8_FREE(ret);
725 #endif
726             }
727         }
729         [dialogReturn release]; dialogReturn = nil;
730     }
731     @catch (NSException *e) {
732         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
733     }
735     return retval;
738 - (void)showToolbar:(int)enable flags:(int)flags
740     NSMutableData *data = [NSMutableData data];
742     [data appendBytes:&enable length:sizeof(int)];
743     [data appendBytes:&flags length:sizeof(int)];
745     [self queueMessage:ShowToolbarMsgID data:data];
748 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
750     NSMutableData *data = [NSMutableData data];
752     [data appendBytes:&ident length:sizeof(long)];
753     [data appendBytes:&type length:sizeof(int)];
755     [self queueMessage:CreateScrollbarMsgID data:data];
758 - (void)destroyScrollbarWithIdentifier:(long)ident
760     NSMutableData *data = [NSMutableData data];
761     [data appendBytes:&ident length:sizeof(long)];
763     [self queueMessage:DestroyScrollbarMsgID data:data];
766 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
768     NSMutableData *data = [NSMutableData data];
770     [data appendBytes:&ident length:sizeof(long)];
771     [data appendBytes:&visible length:sizeof(int)];
773     [self queueMessage:ShowScrollbarMsgID data:data];
776 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
778     NSMutableData *data = [NSMutableData data];
780     [data appendBytes:&ident length:sizeof(long)];
781     [data appendBytes:&pos length:sizeof(int)];
782     [data appendBytes:&len length:sizeof(int)];
784     [self queueMessage:SetScrollbarPositionMsgID data:data];
787 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
788                     identifier:(long)ident
790     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
791     float prop = (float)size/(max+1);
792     if (fval < 0) fval = 0;
793     else if (fval > 1.0f) fval = 1.0f;
794     if (prop < 0) prop = 0;
795     else if (prop > 1.0f) prop = 1.0f;
797     NSMutableData *data = [NSMutableData data];
799     [data appendBytes:&ident length:sizeof(long)];
800     [data appendBytes:&fval length:sizeof(float)];
801     [data appendBytes:&prop length:sizeof(float)];
803     [self queueMessage:SetScrollbarThumbMsgID data:data];
806 - (void)setFont:(NSFont *)font
808     NSString *fontName = [font displayName];
809     float size = [font pointSize];
810     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
811     if (len > 0) {
812         NSMutableData *data = [NSMutableData data];
814         [data appendBytes:&size length:sizeof(float)];
815         [data appendBytes:&len length:sizeof(int)];
816         [data appendBytes:[fontName UTF8String] length:len];
818         [self queueMessage:SetFontMsgID data:data];
819     }
822 - (void)setWideFont:(NSFont *)font
824     NSString *fontName = [font displayName];
825     float size = [font pointSize];
826     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
827     NSMutableData *data = [NSMutableData data];
829     [data appendBytes:&size length:sizeof(float)];
830     [data appendBytes:&len length:sizeof(int)];
831     if (len > 0)
832         [data appendBytes:[fontName UTF8String] length:len];
834     [self queueMessage:SetWideFontMsgID data:data];
837 - (void)executeActionWithName:(NSString *)name
839     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
841     if (len > 0) {
842         NSMutableData *data = [NSMutableData data];
844         [data appendBytes:&len length:sizeof(int)];
845         [data appendBytes:[name UTF8String] length:len];
847         [self queueMessage:ExecuteActionMsgID data:data];
848     }
851 - (void)setMouseShape:(int)shape
853     NSMutableData *data = [NSMutableData data];
854     [data appendBytes:&shape length:sizeof(int)];
855     [self queueMessage:SetMouseShapeMsgID data:data];
858 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
860     // Vim specifies times in milliseconds, whereas Cocoa wants them in
861     // seconds.
862     blinkWaitInterval = .001f*wait;
863     blinkOnInterval = .001f*on;
864     blinkOffInterval = .001f*off;
867 - (void)startBlink
869     if (blinkTimer) {
870         [blinkTimer invalidate];
871         [blinkTimer release];
872         blinkTimer = nil;
873     }
875     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
876             && gui.in_focus) {
877         blinkState = MMBlinkStateOn;
878         blinkTimer =
879             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
880                                               target:self
881                                             selector:@selector(blinkTimerFired:)
882                                             userInfo:nil repeats:NO] retain];
883         gui_update_cursor(TRUE, FALSE);
884         [self flushQueue:YES];
885     }
888 - (void)stopBlink
890     if (MMBlinkStateOff == blinkState) {
891         gui_update_cursor(TRUE, FALSE);
892         [self flushQueue:YES];
893     }
895     blinkState = MMBlinkStateNone;
898 - (void)adjustLinespace:(int)linespace
900     NSMutableData *data = [NSMutableData data];
901     [data appendBytes:&linespace length:sizeof(int)];
902     [self queueMessage:AdjustLinespaceMsgID data:data];
905 - (void)activate
907     [self queueMessage:ActivateMsgID data:nil];
910 - (void)setPreEditRow:(int)row column:(int)col
912     NSMutableData *data = [NSMutableData data];
913     [data appendBytes:&row length:sizeof(int)];
914     [data appendBytes:&col length:sizeof(int)];
915     [self queueMessage:SetPreEditPositionMsgID data:data];
918 - (int)lookupColorWithKey:(NSString *)key
920     if (!(key && [key length] > 0))
921         return INVALCOLOR;
923     NSString *stripKey = [[[[key lowercaseString]
924         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
925             componentsSeparatedByString:@" "]
926                componentsJoinedByString:@""];
928     if (stripKey && [stripKey length] > 0) {
929         // First of all try to lookup key in the color dictionary; note that
930         // all keys in this dictionary are lowercase with no whitespace.
931         id obj = [colorDict objectForKey:stripKey];
932         if (obj) return [obj intValue];
934         // The key was not in the dictionary; is it perhaps of the form
935         // #rrggbb?
936         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
937             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
938             [scanner setScanLocation:1];
939             unsigned hex = 0;
940             if ([scanner scanHexInt:&hex]) {
941                 return (int)hex;
942             }
943         }
945         // As a last resort, check if it is one of the system defined colors.
946         // The keys in this dictionary are also lowercase with no whitespace.
947         obj = [sysColorDict objectForKey:stripKey];
948         if (obj) {
949             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
950             if (col) {
951                 float r, g, b, a;
952                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
953                 [col getRed:&r green:&g blue:&b alpha:&a];
954                 return (((int)(r*255+.5f) & 0xff) << 16)
955                      + (((int)(g*255+.5f) & 0xff) << 8)
956                      +  ((int)(b*255+.5f) & 0xff);
957             }
958         }
959     }
961     //NSLog(@"WARNING: No color with key %@ found.", stripKey);
962     return INVALCOLOR;
965 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
967     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
968     id obj;
970     while ((obj = [e nextObject])) {
971         if ([value isEqual:obj])
972             return YES;
973     }
975     return NO;
978 - (void)enterFullscreen:(int)fuoptions background:(int)bg
980     NSMutableData *data = [NSMutableData data];
981     [data appendBytes:&fuoptions length:sizeof(int)];
982     bg = MM_COLOR(bg);
983     [data appendBytes:&bg length:sizeof(int)];
984     [self queueMessage:EnterFullscreenMsgID data:data];
987 - (void)leaveFullscreen
989     [self queueMessage:LeaveFullscreenMsgID data:nil];
992 - (void)setAntialias:(BOOL)antialias
994     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
996     [self queueMessage:msgid data:nil];
999 - (void)updateModifiedFlag
1001     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1002     // vice versa.
1003     int msgid = [self checkForModifiedBuffers]
1004             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1006     [self queueMessage:msgid data:nil];
1009 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1011     // NOTE: This method might get called whenever the run loop is tended to.
1012     // Normal keyboard and mouse input is added to input buffers, so there is
1013     // no risk in handling these events directly (they return immediately, and
1014     // do not call any other Vim functions).  However, other events such
1015     // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
1016     // events which would cause this method to be called recursively.  This
1017     // in turn leads to various difficulties that we do not want to have to
1018     // deal with.  To avoid recursive calls here we add all events except
1019     // keyboard and mouse events to an input queue which is processed whenever
1020     // gui_mch_update() is called (see processInputQueue).
1022     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1024     // Don't flush too soon after receiving input or update speed will suffer.
1025     [lastFlushDate release];
1026     lastFlushDate = [[NSDate date] retain];
1028     // Handle keyboard and mouse input now.  All other events are queued.
1029     if (InsertTextMsgID == msgid) {
1030         [self handleInsertText:data];
1031     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1032         if (!data) return;
1033         const void *bytes = [data bytes];
1034         int mods = *((int*)bytes);  bytes += sizeof(int);
1035         int len = *((int*)bytes);  bytes += sizeof(int);
1036         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1037                                               encoding:NSUTF8StringEncoding];
1038         mods = eventModifierFlagsToVimModMask(mods);
1040         [self handleKeyDown:key modifiers:mods];
1042         [key release];
1043     } else if (ScrollWheelMsgID == msgid) {
1044         if (!data) return;
1045         const void *bytes = [data bytes];
1047         int row = *((int*)bytes);  bytes += sizeof(int);
1048         int col = *((int*)bytes);  bytes += sizeof(int);
1049         int flags = *((int*)bytes);  bytes += sizeof(int);
1050         float dy = *((float*)bytes);  bytes += sizeof(float);
1052         int button = MOUSE_5;
1053         if (dy > 0) button = MOUSE_4;
1055         flags = eventModifierFlagsToVimMouseModMask(flags);
1057         int numLines = (int)round(dy);
1058         if (numLines < 0) numLines = -numLines;
1059         if (numLines == 0) numLines = 1;
1061 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1062         gui.scroll_wheel_force = numLines;
1063 #endif
1065         gui_send_mouse_event(button, col, row, NO, flags);
1066     } else if (MouseDownMsgID == msgid) {
1067         if (!data) return;
1068         const void *bytes = [data bytes];
1070         int row = *((int*)bytes);  bytes += sizeof(int);
1071         int col = *((int*)bytes);  bytes += sizeof(int);
1072         int button = *((int*)bytes);  bytes += sizeof(int);
1073         int flags = *((int*)bytes);  bytes += sizeof(int);
1074         int count = *((int*)bytes);  bytes += sizeof(int);
1076         button = eventButtonNumberToVimMouseButton(button);
1077         if (button >= 0) {
1078             flags = eventModifierFlagsToVimMouseModMask(flags);
1079             gui_send_mouse_event(button, col, row, count>1, flags);
1080         }
1081     } else if (MouseUpMsgID == msgid) {
1082         if (!data) return;
1083         const void *bytes = [data bytes];
1085         int row = *((int*)bytes);  bytes += sizeof(int);
1086         int col = *((int*)bytes);  bytes += sizeof(int);
1087         int flags = *((int*)bytes);  bytes += sizeof(int);
1089         flags = eventModifierFlagsToVimMouseModMask(flags);
1091         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1092     } else if (MouseDraggedMsgID == msgid) {
1093         if (!data) return;
1094         const void *bytes = [data bytes];
1096         int row = *((int*)bytes);  bytes += sizeof(int);
1097         int col = *((int*)bytes);  bytes += sizeof(int);
1098         int flags = *((int*)bytes);  bytes += sizeof(int);
1100         flags = eventModifierFlagsToVimMouseModMask(flags);
1102         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1103     } else if (MouseMovedMsgID == msgid) {
1104         const void *bytes = [data bytes];
1105         int row = *((int*)bytes);  bytes += sizeof(int);
1106         int col = *((int*)bytes);  bytes += sizeof(int);
1108         gui_mouse_moved(col, row);
1109     } else if (AddInputMsgID == msgid) {
1110         NSString *string = [[NSString alloc] initWithData:data
1111                 encoding:NSUTF8StringEncoding];
1112         if (string) {
1113             [self addInput:string];
1114             [string release];
1115         }
1116     } else if (TerminateNowMsgID == msgid) {
1117         isTerminating = YES;
1118     } else {
1119         // Not keyboard or mouse event, queue it and handle later.
1120         //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
1121         [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1122         [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
1123     }
1125     // See waitForInput: for an explanation of this flag.
1126     inputReceived = YES;
1129 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1131     // TODO: Get rid of this method?
1132     //NSLog(@"%s%@", _cmd, messages);
1134     unsigned i, count = [messages count];
1135     for (i = 0; i < count; i += 2) {
1136         int msgid = [[messages objectAtIndex:i] intValue];
1137         id data = [messages objectAtIndex:i+1];
1138         if ([data isEqual:[NSNull null]])
1139             data = nil;
1141         [self processInput:msgid data:data];
1142     }
1145 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1146                   errorString:(out bycopy NSString **)errstr
1148     return evalExprCocoa(expr, errstr);
1152 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1154     NSString *eval = nil;
1155     char_u *s = (char_u*)[expr UTF8String];
1157 #ifdef FEAT_MBYTE
1158     s = CONVERT_FROM_UTF8(s);
1159 #endif
1161     char_u *res = eval_client_expr_to_string(s);
1163 #ifdef FEAT_MBYTE
1164     CONVERT_FROM_UTF8_FREE(s);
1165 #endif
1167     if (res != NULL) {
1168         s = res;
1169 #ifdef FEAT_MBYTE
1170         s = CONVERT_TO_UTF8(s);
1171 #endif
1172         eval = [NSString stringWithUTF8String:(char*)s];
1173 #ifdef FEAT_MBYTE
1174         CONVERT_TO_UTF8_FREE(s);
1175 #endif
1176         vim_free(res);
1177     }
1179     return eval;
1182 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1184     // TODO: This method should share code with clip_mch_request_selection().
1186     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1187         // If there is no pasteboard, return YES to indicate that there is text
1188         // to copy.
1189         if (!pboard)
1190             return YES;
1192         clip_copy_selection();
1194         // Get the text to put on the pasteboard.
1195         long_u llen = 0; char_u *str = 0;
1196         int type = clip_convert_selection(&str, &llen, &clip_star);
1197         if (type < 0)
1198             return NO;
1199         
1200         // TODO: Avoid overflow.
1201         int len = (int)llen;
1202 #ifdef FEAT_MBYTE
1203         if (output_conv.vc_type != CONV_NONE) {
1204             char_u *conv_str = string_convert(&output_conv, str, &len);
1205             if (conv_str) {
1206                 vim_free(str);
1207                 str = conv_str;
1208             }
1209         }
1210 #endif
1212         NSString *string = [[NSString alloc]
1213             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1215         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1216         [pboard declareTypes:types owner:nil];
1217         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1218     
1219         [string release];
1220         vim_free(str);
1222         return ok;
1223     }
1225     return NO;
1228 - (oneway void)addReply:(in bycopy NSString *)reply
1229                  server:(in byref id <MMVimServerProtocol>)server
1231     //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1233     // Replies might come at any time and in any order so we keep them in an
1234     // array inside a dictionary with the send port used as key.
1236     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1237     // HACK! Assume connection uses mach ports.
1238     int port = [(NSMachPort*)[conn sendPort] machPort];
1239     NSNumber *key = [NSNumber numberWithInt:port];
1241     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1242     if (!replies) {
1243         replies = [NSMutableArray array];
1244         [serverReplyDict setObject:replies forKey:key];
1245     }
1247     [replies addObject:reply];
1250 - (void)addInput:(in bycopy NSString *)input
1251                  client:(in byref id <MMVimClientProtocol>)client
1253     //NSLog(@"addInput:%@ client:%@", input, (id)client);
1255     [self addInput:input];
1256     [self addClient:(id)client];
1258     inputReceived = YES;
1261 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1262                  client:(in byref id <MMVimClientProtocol>)client
1264     [self addClient:(id)client];
1265     return [self evaluateExpression:expr];
1268 - (void)registerServerWithName:(NSString *)name
1270     NSString *svrName = name;
1271     NSConnection *svrConn = [NSConnection defaultConnection];
1272     unsigned i;
1274     for (i = 0; i < MMServerMax; ++i) {
1275         NSString *connName = [self connectionNameFromServerName:svrName];
1277         if ([svrConn registerName:connName]) {
1278             //NSLog(@"Registered server with name: %@", svrName);
1280             // TODO: Set request/reply time-outs to something else?
1281             //
1282             // Don't wait for requests (time-out means that the message is
1283             // dropped).
1284             [svrConn setRequestTimeout:0];
1285             //[svrConn setReplyTimeout:MMReplyTimeout];
1286             [svrConn setRootObject:self];
1288             char_u *s = (char_u*)[svrName UTF8String];
1289 #ifdef FEAT_MBYTE
1290             s = CONVERT_FROM_UTF8(s);
1291 #endif
1292             // NOTE: 'serverName' is a global variable
1293             serverName = vim_strsave(s);
1294 #ifdef FEAT_MBYTE
1295             CONVERT_FROM_UTF8_FREE(s);
1296 #endif
1297 #ifdef FEAT_EVAL
1298             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1299 #endif
1300 #ifdef FEAT_TITLE
1301             need_maketitle = TRUE;
1302 #endif
1303             [self queueMessage:SetServerNameMsgID data:
1304                     [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1305             break;
1306         }
1308         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1309     }
1312 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1313                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1314               silent:(BOOL)silent
1316     // NOTE: If 'name' equals 'serverName' then the request is local (client
1317     // and server are the same).  This case is not handled separately, so a
1318     // connection will be set up anyway (this simplifies the code).
1320     NSConnection *conn = [self connectionForServerName:name];
1321     if (!conn) {
1322         if (!silent) {
1323             char_u *s = (char_u*)[name UTF8String];
1324 #ifdef FEAT_MBYTE
1325             s = CONVERT_FROM_UTF8(s);
1326 #endif
1327             EMSG2(_(e_noserver), s);
1328 #ifdef FEAT_MBYTE
1329             CONVERT_FROM_UTF8_FREE(s);
1330 #endif
1331         }
1332         return NO;
1333     }
1335     if (port) {
1336         // HACK! Assume connection uses mach ports.
1337         *port = [(NSMachPort*)[conn sendPort] machPort];
1338     }
1340     id proxy = [conn rootProxy];
1341     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1343     @try {
1344         if (expr) {
1345             NSString *eval = [proxy evaluateExpression:string client:self];
1346             if (reply) {
1347                 if (eval) {
1348                     char_u *r = (char_u*)[eval UTF8String];
1349 #ifdef FEAT_MBYTE
1350                     r = CONVERT_FROM_UTF8(r);
1351 #endif
1352                     *reply = vim_strsave(r);
1353 #ifdef FEAT_MBYTE
1354                     CONVERT_FROM_UTF8_FREE(r);
1355 #endif
1356                 } else {
1357                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1358                 }
1359             }
1361             if (!eval)
1362                 return NO;
1363         } else {
1364             [proxy addInput:string client:self];
1365         }
1366     }
1367     @catch (NSException *e) {
1368         NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1369         return NO;
1370     }
1372     return YES;
1375 - (NSArray *)serverList
1377     NSArray *list = nil;
1379     if ([self connection]) {
1380         id proxy = [connection rootProxy];
1381         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1383         @try {
1384             list = [proxy serverList];
1385         }
1386         @catch (NSException *e) {
1387             NSLog(@"Exception caught when listing servers: \"%@\"", e);
1388         }
1389     } else {
1390         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1391     }
1393     return list;
1396 - (NSString *)peekForReplyOnPort:(int)port
1398     //NSLog(@"%s%d", _cmd, port);
1400     NSNumber *key = [NSNumber numberWithInt:port];
1401     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1402     if (replies && [replies count]) {
1403         //NSLog(@"    %d replies, topmost is: %@", [replies count],
1404         //        [replies objectAtIndex:0]);
1405         return [replies objectAtIndex:0];
1406     }
1408     //NSLog(@"    No replies");
1409     return nil;
1412 - (NSString *)waitForReplyOnPort:(int)port
1414     //NSLog(@"%s%d", _cmd, port);
1415     
1416     NSConnection *conn = [self connectionForServerPort:port];
1417     if (!conn)
1418         return nil;
1420     NSNumber *key = [NSNumber numberWithInt:port];
1421     NSMutableArray *replies = nil;
1422     NSString *reply = nil;
1424     // Wait for reply as long as the connection to the server is valid (unless
1425     // user interrupts wait with Ctrl-C).
1426     while (!got_int && [conn isValid] &&
1427             !(replies = [serverReplyDict objectForKey:key])) {
1428         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1429                                  beforeDate:[NSDate distantFuture]];
1430     }
1432     if (replies) {
1433         if ([replies count] > 0) {
1434             reply = [[replies objectAtIndex:0] retain];
1435             //NSLog(@"    Got reply: %@", reply);
1436             [replies removeObjectAtIndex:0];
1437             [reply autorelease];
1438         }
1440         if ([replies count] == 0)
1441             [serverReplyDict removeObjectForKey:key];
1442     }
1444     return reply;
1447 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1449     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1450     if (client) {
1451         @try {
1452             //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1453             [client addReply:reply server:self];
1454             return YES;
1455         }
1456         @catch (NSException *e) {
1457             NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1458         }
1459     } else {
1460         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1461     }
1463     return NO;
1466 @end // MMBackend
1470 @implementation MMBackend (Private)
1472 - (void)waitForDialogReturn
1474     // Keep processing the run loop until a dialog returns.  To avoid getting
1475     // stuck in an endless loop (could happen if the setDialogReturn: message
1476     // was lost) we also do some paranoia checks.
1477     //
1478     // Note that in Cocoa the user can still resize windows and select menu
1479     // items while a sheet is being displayed, so we can't just wait for the
1480     // first message to arrive and assume that is the setDialogReturn: call.
1482     while (nil == dialogReturn && !got_int && [connection isValid]
1483             && !isTerminating)
1484         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1485                                  beforeDate:[NSDate distantFuture]];
1487     // Search for any resize messages on the input queue.  All other messages
1488     // on the input queue are dropped.  The reason why we single out resize
1489     // messages is because the user may have resized the window while a sheet
1490     // was open.
1491     int i, count = [inputQueue count];
1492     if (count > 0) {
1493         id textDimData = nil;
1494         if (count%2 == 0) {
1495             for (i = count-2; i >= 0; i -= 2) {
1496                 int msgid = [[inputQueue objectAtIndex:i] intValue];
1497                 if (SetTextDimensionsMsgID == msgid) {
1498                     textDimData = [[inputQueue objectAtIndex:i+1] retain];
1499                     break;
1500                 }
1501             }
1502         }
1504         [inputQueue removeAllObjects];
1506         if (textDimData) {
1507             [inputQueue addObject:
1508                     [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1509             [inputQueue addObject:textDimData];
1510             [textDimData release];
1511         }
1512     }
1515 - (void)queueVimStateMessage
1517     // NOTE: This is the place to add Vim state that needs to be accessed from
1518     // MacVim.  Do not add state that could potentially require lots of memory
1519     // since this message gets sent each time the output queue is forcibly
1520     // flushed (e.g. storing the currently selected text would be a bad idea).
1521     // We take this approach of "pushing" the state to MacVim to avoid having
1522     // to make synchronous calls from MacVim to Vim in order to get state.
1524     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1525         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1526         [NSNumber numberWithInt:p_mh], @"p_mh",
1527         nil];
1529     [self queueMessage:SetVimStateMsgID data:[vimState dictionaryAsData]];
1532 - (void)processInputQueue
1534     if ([inputQueue count] == 0) return;
1536     // NOTE: One of the input events may cause this method to be called
1537     // recursively, so copy the input queue to a local variable and clear it
1538     // before starting to process input events (otherwise we could get stuck in
1539     // an endless loop).
1540     NSArray *q = [inputQueue copy];
1541     unsigned i, count = [q count];
1543     [inputQueue removeAllObjects];
1545     for (i = 0; i < count-1; i += 2) {
1546         int msgid = [[q objectAtIndex:i] intValue];
1547         id data = [q objectAtIndex:i+1];
1548         if ([data isEqual:[NSNull null]])
1549             data = nil;
1551         //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1552         [self handleInputEvent:msgid data:data];
1553     }
1555     [q release];
1556     //NSLog(@"Clear input event queue");
1559 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1561     // NOTE: Be careful with what you do in this method.  Ideally, a message
1562     // should be handled by adding something to the input buffer and returning
1563     // immediately.  If you call a Vim function then it should not enter a loop
1564     // waiting for key presses or in any other way block the process.  The
1565     // reason for this being that only one message can be processed at a time,
1566     // so if another message is received while processing, then the new message
1567     // is dropped.  See also the comment in processInput:data:.
1569     //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1571     if (SelectTabMsgID == msgid) {
1572         if (!data) return;
1573         const void *bytes = [data bytes];
1574         int idx = *((int*)bytes) + 1;
1575         //NSLog(@"Selecting tab %d", idx);
1576         send_tabline_event(idx);
1577     } else if (CloseTabMsgID == msgid) {
1578         if (!data) return;
1579         const void *bytes = [data bytes];
1580         int idx = *((int*)bytes) + 1;
1581         //NSLog(@"Closing tab %d", idx);
1582         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1583     } else if (AddNewTabMsgID == msgid) {
1584         //NSLog(@"Adding new tab");
1585         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1586     } else if (DraggedTabMsgID == msgid) {
1587         if (!data) return;
1588         const void *bytes = [data bytes];
1589         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1590         // based.
1591         int idx = *((int*)bytes);
1593         tabpage_move(idx);
1594     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) {
1595         if (!data) return;
1596         const void *bytes = [data bytes];
1597         int rows = *((int*)bytes);  bytes += sizeof(int);
1598         int cols = *((int*)bytes);  bytes += sizeof(int);
1600         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1601         // gui_resize_shell(), so we have to manually set the rows and columns
1602         // here.  (MacVim doesn't change the rows and columns to avoid
1603         // inconsistent states between Vim and MacVim.)
1604         [self queueMessage:msgid data:data];
1606         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1607         gui_resize_shell(cols, rows);
1608     } else if (ExecuteMenuMsgID == msgid) {
1609         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1610         if (attrs) {
1611             NSArray *desc = [attrs objectForKey:@"descriptor"];
1612             vimmenu_T *menu = menu_for_descriptor(desc);
1613             if (menu)
1614                 gui_menu_cb(menu);
1615         }
1616     } else if (ToggleToolbarMsgID == msgid) {
1617         [self handleToggleToolbar];
1618     } else if (ScrollbarEventMsgID == msgid) {
1619         [self handleScrollbarEvent:data];
1620     } else if (SetFontMsgID == msgid) {
1621         [self handleSetFont:data];
1622     } else if (VimShouldCloseMsgID == msgid) {
1623         gui_shell_closed();
1624     } else if (DropFilesMsgID == msgid) {
1625         [self handleDropFiles:data];
1626     } else if (DropStringMsgID == msgid) {
1627         [self handleDropString:data];
1628     } else if (GotFocusMsgID == msgid) {
1629         if (!gui.in_focus)
1630             [self focusChange:YES];
1631     } else if (LostFocusMsgID == msgid) {
1632         if (gui.in_focus)
1633             [self focusChange:NO];
1634     } else if (SetMouseShapeMsgID == msgid) {
1635         const void *bytes = [data bytes];
1636         int shape = *((int*)bytes);  bytes += sizeof(int);
1637         update_mouseshape(shape);
1638     } else if (ODBEditMsgID == msgid) {
1639         [self handleOdbEdit:data];
1640     } else if (XcodeModMsgID == msgid) {
1641         [self handleXcodeMod:data];
1642     } else {
1643         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1644     }
1647 + (NSDictionary *)specialKeys
1649     static NSDictionary *specialKeys = nil;
1651     if (!specialKeys) {
1652         NSBundle *mainBundle = [NSBundle mainBundle];
1653         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1654                                               ofType:@"plist"];
1655         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1656     }
1658     return specialKeys;
1661 - (void)handleInsertText:(NSData *)data
1663     if (!data) return;
1665     NSString *key = [[NSString alloc] initWithData:data
1666                                           encoding:NSUTF8StringEncoding];
1667     char_u *str = (char_u*)[key UTF8String];
1668     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1670 #ifdef FEAT_MBYTE
1671     char_u *conv_str = NULL;
1672     if (input_conv.vc_type != CONV_NONE) {
1673         conv_str = string_convert(&input_conv, str, &len);
1674         if (conv_str)
1675             str = conv_str;
1676     }
1677 #endif
1679     if (len == 1 && ((str[0] == Ctrl_C && ctrl_c_interrupts)
1680             || (str[0] == intr_char && intr_char != Ctrl_C))) {
1681         trash_input_buf();
1682         got_int = TRUE;
1683     }
1685     for (i = 0; i < len; ++i) {
1686         add_to_input_buf(str+i, 1);
1687         if (CSI == str[i]) {
1688             // NOTE: If the converted string contains the byte CSI, then it
1689             // must be followed by the bytes KS_EXTRA, KE_CSI or things
1690             // won't work.
1691             static char_u extra[2] = { KS_EXTRA, KE_CSI };
1692             add_to_input_buf(extra, 2);
1693         }
1694     }
1696 #ifdef FEAT_MBYTE
1697     if (conv_str)
1698         vim_free(conv_str);
1699 #endif
1700     [key release];
1703 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1705     char_u special[3];
1706     char_u modChars[3];
1707     char_u *chars = (char_u*)[key UTF8String];
1708 #ifdef FEAT_MBYTE
1709     char_u *conv_str = NULL;
1710 #endif
1711     int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1713     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1714     // that new keys can easily be added.
1715     NSString *specialString = [[MMBackend specialKeys]
1716             objectForKey:key];
1717     if (specialString && [specialString length] > 1) {
1718         //NSLog(@"special key: %@", specialString);
1719         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1720                 [specialString characterAtIndex:1]);
1722         ikey = simplify_key(ikey, &mods);
1723         if (ikey == CSI)
1724             ikey = K_CSI;
1726         special[0] = CSI;
1727         special[1] = K_SECOND(ikey);
1728         special[2] = K_THIRD(ikey);
1730         chars = special;
1731         length = 3;
1732     } else if (1 == length && TAB == chars[0]) {
1733         // Tab is a trouble child:
1734         // - <Tab> is added to the input buffer as is
1735         // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1736         // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1737         //   to be converted to utf-8
1738         // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1739         // - <C-Tab> is reserved by Mac OS X
1740         // - <D-Tab> is reserved by Mac OS X
1741         chars = special;
1742         special[0] = TAB;
1743         length = 1;
1745         if (mods & MOD_MASK_SHIFT) {
1746             mods &= ~MOD_MASK_SHIFT;
1747             special[0] = CSI;
1748             special[1] = K_SECOND(K_S_TAB);
1749             special[2] = K_THIRD(K_S_TAB);
1750             length = 3;
1751         } else if (mods & MOD_MASK_ALT) {
1752             int mtab = 0x80 | TAB;
1753 #ifdef FEAT_MBYTE
1754             if (enc_utf8) {
1755                 // Convert to utf-8
1756                 special[0] = (mtab >> 6) + 0xc0;
1757                 special[1] = mtab & 0xbf;
1758                 length = 2;
1759             } else
1760 #endif
1761             {
1762                 special[0] = mtab;
1763                 length = 1;
1764             }
1765             mods &= ~MOD_MASK_ALT;
1766         }
1767     } else if (length > 0) {
1768         unichar c = [key characterAtIndex:0];
1770         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1771         //        [key characterAtIndex:0], mods);
1773         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1774                 || (c == intr_char && intr_char != Ctrl_C))) {
1775             trash_input_buf();
1776             got_int = TRUE;
1777         }
1779         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1780         // cleared since they are already added to the key by the AppKit.
1781         // Unfortunately, the only way to deal with when to clear the modifiers
1782         // or not seems to be to have hard-wired rules like this.
1783         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1784                     || 0x9 == c || 0xd == c || ESC == c) ) {
1785             mods &= ~MOD_MASK_SHIFT;
1786             mods &= ~MOD_MASK_CTRL;
1787             //NSLog(@"clear shift ctrl");
1788         }
1790         // HACK!  All Option+key presses go via 'insert text' messages, except
1791         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1792         // not work to map to it.
1793         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1794             //NSLog(@"clear alt");
1795             mods &= ~MOD_MASK_ALT;
1796         }
1798 #ifdef FEAT_MBYTE
1799         if (input_conv.vc_type != CONV_NONE) {
1800             conv_str = string_convert(&input_conv, chars, &length);
1801             if (conv_str)
1802                 chars = conv_str;
1803         }
1804 #endif
1805     }
1807     if (chars && length > 0) {
1808         if (mods) {
1809             //NSLog(@"adding mods: %d", mods);
1810             modChars[0] = CSI;
1811             modChars[1] = KS_MODIFIER;
1812             modChars[2] = mods;
1813             add_to_input_buf(modChars, 3);
1814         }
1816         //NSLog(@"add to input buf: 0x%x", chars[0]);
1817         // TODO: Check for CSI bytes?
1818         add_to_input_buf(chars, length);
1819     }
1821 #ifdef FEAT_MBYTE
1822     if (conv_str)
1823         vim_free(conv_str);
1824 #endif
1827 - (void)queueMessage:(int)msgid data:(NSData *)data
1829     //if (msgid != EnableMenuItemMsgID)
1830     //    NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1832     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1833     if (data)
1834         [outputQueue addObject:data];
1835     else
1836         [outputQueue addObject:[NSData data]];
1839 - (void)connectionDidDie:(NSNotification *)notification
1841     // If the main connection to MacVim is lost this means that MacVim was
1842     // either quit (by the user chosing Quit on the MacVim menu), or it has
1843     // crashed.  In the former case the flag 'isTerminating' is set and we then
1844     // quit cleanly; in the latter case we make sure the swap files are left
1845     // for recovery.
1847     //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1848     if (isTerminating)
1849         getout(0);
1850     else
1851         getout_preserve_modified(1);
1854 - (void)blinkTimerFired:(NSTimer *)timer
1856     NSTimeInterval timeInterval = 0;
1858     [blinkTimer release];
1859     blinkTimer = nil;
1861     if (MMBlinkStateOn == blinkState) {
1862         gui_undraw_cursor();
1863         blinkState = MMBlinkStateOff;
1864         timeInterval = blinkOffInterval;
1865     } else if (MMBlinkStateOff == blinkState) {
1866         gui_update_cursor(TRUE, FALSE);
1867         blinkState = MMBlinkStateOn;
1868         timeInterval = blinkOnInterval;
1869     }
1871     if (timeInterval > 0) {
1872         blinkTimer = 
1873             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1874                                             selector:@selector(blinkTimerFired:)
1875                                             userInfo:nil repeats:NO] retain];
1876         [self flushQueue:YES];
1877     }
1880 - (void)focusChange:(BOOL)on
1882     gui_focus_change(on);
1885 - (void)handleToggleToolbar
1887     // If 'go' contains 'T', then remove it, else add it.
1889     char_u go[sizeof(GO_ALL)+2];
1890     char_u *p;
1891     int len;
1893     STRCPY(go, p_go);
1894     p = vim_strchr(go, GO_TOOLBAR);
1895     len = STRLEN(go);
1897     if (p != NULL) {
1898         char_u *end = go + len;
1899         while (p < end) {
1900             p[0] = p[1];
1901             ++p;
1902         }
1903     } else {
1904         go[len] = GO_TOOLBAR;
1905         go[len+1] = NUL;
1906     }
1908     set_option_value((char_u*)"guioptions", 0, go, 0);
1910     // Force screen redraw (does it have to be this complicated?).
1911     redraw_all_later(CLEAR);
1912     update_screen(NOT_VALID);
1913     setcursor();
1914     out_flush();
1915     gui_update_cursor(FALSE, FALSE);
1916     gui_mch_flush();
1919 - (void)handleScrollbarEvent:(NSData *)data
1921     if (!data) return;
1923     const void *bytes = [data bytes];
1924     long ident = *((long*)bytes);  bytes += sizeof(long);
1925     int hitPart = *((int*)bytes);  bytes += sizeof(int);
1926     float fval = *((float*)bytes);  bytes += sizeof(float);
1927     scrollbar_T *sb = gui_find_scrollbar(ident);
1929     if (sb) {
1930         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1931         long value = sb_info->value;
1932         long size = sb_info->size;
1933         long max = sb_info->max;
1934         BOOL isStillDragging = NO;
1935         BOOL updateKnob = YES;
1937         switch (hitPart) {
1938         case NSScrollerDecrementPage:
1939             value -= (size > 2 ? size - 2 : 1);
1940             break;
1941         case NSScrollerIncrementPage:
1942             value += (size > 2 ? size - 2 : 1);
1943             break;
1944         case NSScrollerDecrementLine:
1945             --value;
1946             break;
1947         case NSScrollerIncrementLine:
1948             ++value;
1949             break;
1950         case NSScrollerKnob:
1951             isStillDragging = YES;
1952             // fall through ...
1953         case NSScrollerKnobSlot:
1954             value = (long)(fval * (max - size + 1));
1955             // fall through ...
1956         default:
1957             updateKnob = NO;
1958             break;
1959         }
1961         //NSLog(@"value %d -> %d", sb_info->value, value);
1962         gui_drag_scrollbar(sb, value, isStillDragging);
1964         if (updateKnob) {
1965             // Dragging the knob or option+clicking automatically updates
1966             // the knob position (on the actual NSScroller), so we only
1967             // need to set the knob position in the other cases.
1968             if (sb->wp) {
1969                 // Update both the left&right vertical scrollbars.
1970                 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1971                 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1972                 [self setScrollbarThumbValue:value size:size max:max
1973                                   identifier:identLeft];
1974                 [self setScrollbarThumbValue:value size:size max:max
1975                                   identifier:identRight];
1976             } else {
1977                 // Update the horizontal scrollbar.
1978                 [self setScrollbarThumbValue:value size:size max:max
1979                                   identifier:ident];
1980             }
1981         }
1982     }
1985 - (void)handleSetFont:(NSData *)data
1987     if (!data) return;
1989     const void *bytes = [data bytes];
1990     float pointSize = *((float*)bytes);  bytes += sizeof(float);
1991     //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1992     bytes += sizeof(unsigned);  // len not used
1994     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1995     [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1996     char_u *s = (char_u*)[name UTF8String];
1998 #ifdef FEAT_MBYTE
1999     s = CONVERT_FROM_UTF8(s);
2000 #endif
2002     set_option_value((char_u*)"guifont", 0, s, 0);
2004 #ifdef FEAT_MBYTE
2005     CONVERT_FROM_UTF8_FREE(s);
2006 #endif
2008     // Force screen redraw (does it have to be this complicated?).
2009     redraw_all_later(CLEAR);
2010     update_screen(NOT_VALID);
2011     setcursor();
2012     out_flush();
2013     gui_update_cursor(FALSE, FALSE);
2014     gui_mch_flush();
2017 - (void)handleDropFiles:(NSData *)data
2019     // TODO: Get rid of this method; instead use Vim script directly.  At the
2020     // moment I know how to do this to open files in tabs, but I'm not sure how
2021     // to add the filenames to the command line when in command line mode.
2023     if (!data) return;
2025 #ifdef FEAT_DND
2026     const void *bytes = [data bytes];
2027     const void *end = [data bytes] + [data length];
2028     BOOL forceOpen = *((BOOL*)bytes);  bytes += sizeof(BOOL);
2029     int n = *((int*)bytes);  bytes += sizeof(int);
2031     if (!forceOpen && (State & CMDLINE)) {
2032         // HACK!  If Vim is in command line mode then the files names
2033         // should be added to the command line, instead of opening the
2034         // files in tabs (unless forceOpen is set).  This is taken care of by
2035         // gui_handle_drop().
2036         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2037         if (fnames) {
2038             int i = 0;
2039             while (bytes < end && i < n) {
2040                 int len = *((int*)bytes);  bytes += sizeof(int);
2041                 char_u *s = (char_u*)bytes;
2042 #ifdef FEAT_MBYTE
2043                 s = CONVERT_FROM_UTF8(s);
2044 #endif
2045                 fnames[i++] = vim_strsave(s);
2046 #ifdef FEAT_MBYTE
2047                 CONVERT_FROM_UTF8_FREE(s);
2048 #endif
2049                 bytes += len;
2050             }
2052             // NOTE!  This function will free 'fnames'.
2053             // HACK!  It is assumed that the 'x' and 'y' arguments are
2054             // unused when in command line mode.
2055             gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
2056         }
2057     } else {
2058         // HACK!  I'm not sure how to get Vim to open a list of files in
2059         // tabs, so instead I create a ':tab drop' command with all the
2060         // files to open and execute it.
2061         NSMutableString *cmd = [NSMutableString stringWithString:@":tab drop"];
2063         int i;
2064         for (i = 0; i < n && bytes < end; ++i) {
2065             int len = *((int*)bytes);  bytes += sizeof(int);
2066             NSString *file = [NSString stringWithUTF8String:bytes];
2067             file = [file stringByEscapingSpecialFilenameCharacters];
2068             bytes += len;
2070             [cmd appendString:@" "];
2071             [cmd appendString:file];
2072         }
2074         // By going to the last tabpage we ensure that the new tabs will
2075         // appear last (if this call is left out, the taborder becomes
2076         // messy).
2077         goto_tabpage(9999);
2079         char_u *s = (char_u*)[cmd UTF8String];
2080 #ifdef FEAT_MBYTE
2081         s = CONVERT_FROM_UTF8(s);
2082 #endif
2083         do_cmdline_cmd(s);
2084 #ifdef FEAT_MBYTE
2085         CONVERT_FROM_UTF8_FREE(s);
2086 #endif
2088         // Force screen redraw (does it have to be this complicated?).
2089         // (This code was taken from the end of gui_handle_drop().)
2090         update_screen(NOT_VALID);
2091         setcursor();
2092         out_flush();
2093         gui_update_cursor(FALSE, FALSE);
2094         maketitle();
2095         gui_mch_flush();
2096     }
2097 #endif // FEAT_DND
2100 - (void)handleDropString:(NSData *)data
2102     if (!data) return;
2104 #ifdef FEAT_DND
2105     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2106     const void *bytes = [data bytes];
2107     int len = *((int*)bytes);  bytes += sizeof(int);
2108     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2110     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2111     NSRange range = { 0, [string length] };
2112     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2113                                          withString:@"\x0a" options:0
2114                                               range:range];
2115     if (0 == n) {
2116         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2117                                        options:0 range:range];
2118     }
2120     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2121     char_u *s = (char_u*)[string UTF8String];
2122 #ifdef FEAT_MBYTE
2123     if (input_conv.vc_type != CONV_NONE)
2124         s = string_convert(&input_conv, s, &len);
2125 #endif
2126     dnd_yank_drag_data(s, len);
2127 #ifdef FEAT_MBYTE
2128     if (input_conv.vc_type != CONV_NONE)
2129         vim_free(s);
2130 #endif
2131     add_to_input_buf(dropkey, sizeof(dropkey));
2132 #endif // FEAT_DND
2135 - (void)handleOdbEdit:(NSData *)data
2137 #ifdef FEAT_ODB_EDITOR
2138     const void *bytes = [data bytes];
2140     OSType serverID = *((OSType*)bytes);  bytes += sizeof(OSType);
2142     char_u *path = NULL;
2143     int pathLen = *((int*)bytes);  bytes += sizeof(int);
2144     if (pathLen > 0) {
2145         path = (char_u*)bytes;
2146         bytes += pathLen;
2147 #ifdef FEAT_MBYTE
2148         path = CONVERT_FROM_UTF8(path);
2149 #endif
2150     }
2152     NSAppleEventDescriptor *token = nil;
2153     DescType tokenType = *((DescType*)bytes);  bytes += sizeof(DescType);
2154     int descLen = *((int*)bytes);  bytes += sizeof(int);
2155     if (descLen > 0) {
2156         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2157                                                                bytes:bytes
2158                                                               length:descLen];
2159         bytes += descLen;
2160     }
2162     unsigned i, numFiles = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2163     for (i = 0; i < numFiles; ++i) {
2164         int len = *((int*)bytes);  bytes += sizeof(int);
2165         char_u *filename = (char_u*)bytes;
2166 #ifdef FEAT_MBYTE
2167         filename = CONVERT_FROM_UTF8(filename);
2168 #endif
2169         buf_T *buf = buflist_findname(filename);
2170         if (buf) {
2171             if (buf->b_odb_token) {
2172                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2173                 buf->b_odb_token = NULL;
2174             }
2176             if (buf->b_odb_fname) {
2177                 vim_free(buf->b_odb_fname);
2178                 buf->b_odb_fname = NULL;
2179             }
2181             buf->b_odb_server_id = serverID;
2183             if (token)
2184                 buf->b_odb_token = [token retain];
2185             if (path)
2186                 buf->b_odb_fname = vim_strsave(path);
2187         } else {
2188             NSLog(@"WARNING: Could not find buffer '%s' for ODB editing.",
2189                     filename);
2190         }
2192 #ifdef FEAT_MBYTE
2193         CONVERT_FROM_UTF8_FREE(filename);
2194 #endif
2195         bytes += len;
2196     }
2197 #ifdef FEAT_MBYTE
2198     CONVERT_FROM_UTF8_FREE(path);
2199 #endif
2200 #endif // FEAT_ODB_EDITOR
2203 - (void)handleXcodeMod:(NSData *)data
2205 #if 0
2206     const void *bytes = [data bytes];
2207     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2208     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2209     if (0 == len)
2210         return;
2212     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2213             descriptorWithDescriptorType:type
2214                                    bytes:bytes
2215                                   length:len];
2216 #endif
2219 - (BOOL)checkForModifiedBuffers
2221     buf_T *buf;
2222     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2223         if (bufIsChanged(buf)) {
2224             return YES;
2225         }
2226     }
2228     return NO;
2231 - (void)addInput:(NSString *)input
2233     char_u *s = (char_u*)[input UTF8String];
2235 #ifdef FEAT_MBYTE
2236     s = CONVERT_FROM_UTF8(s);
2237 #endif
2239     server_to_input_buf(s);
2241 #ifdef FEAT_MBYTE
2242     CONVERT_FROM_UTF8_FREE(s);
2243 #endif
2246 @end // MMBackend (Private)
2251 @implementation MMBackend (ClientServer)
2253 - (NSString *)connectionNameFromServerName:(NSString *)name
2255     NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2257     return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2258         lowercaseString];
2261 - (NSConnection *)connectionForServerName:(NSString *)name
2263     // TODO: Try 'name%d' if 'name' fails.
2264     NSString *connName = [self connectionNameFromServerName:name];
2265     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2267     if (!svrConn) {
2268         svrConn = [NSConnection connectionWithRegisteredName:connName
2269                                                            host:nil];
2270         // Try alternate server...
2271         if (!svrConn && alternateServerName) {
2272             //NSLog(@"  trying to connect to alternate server: %@",
2273             //        alternateServerName);
2274             connName = [self connectionNameFromServerName:alternateServerName];
2275             svrConn = [NSConnection connectionWithRegisteredName:connName
2276                                                             host:nil];
2277         }
2279         // Try looking for alternate servers...
2280         if (!svrConn) {
2281             //NSLog(@"  looking for alternate servers...");
2282             NSString *alt = [self alternateServerNameForName:name];
2283             if (alt != alternateServerName) {
2284                 //NSLog(@"  found alternate server: %@", string);
2285                 [alternateServerName release];
2286                 alternateServerName = [alt copy];
2287             }
2288         }
2290         // Try alternate server again...
2291         if (!svrConn && alternateServerName) {
2292             //NSLog(@"  trying to connect to alternate server: %@",
2293             //        alternateServerName);
2294             connName = [self connectionNameFromServerName:alternateServerName];
2295             svrConn = [NSConnection connectionWithRegisteredName:connName
2296                                                             host:nil];
2297         }
2299         if (svrConn) {
2300             [connectionNameDict setObject:svrConn forKey:connName];
2302             //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2303             [[NSNotificationCenter defaultCenter] addObserver:self
2304                     selector:@selector(serverConnectionDidDie:)
2305                         name:NSConnectionDidDieNotification object:svrConn];
2306         }
2307     }
2309     return svrConn;
2312 - (NSConnection *)connectionForServerPort:(int)port
2314     NSConnection *conn;
2315     NSEnumerator *e = [connectionNameDict objectEnumerator];
2317     while ((conn = [e nextObject])) {
2318         // HACK! Assume connection uses mach ports.
2319         if (port == [(NSMachPort*)[conn sendPort] machPort])
2320             return conn;
2321     }
2323     return nil;
2326 - (void)serverConnectionDidDie:(NSNotification *)notification
2328     //NSLog(@"%s%@", _cmd, notification);
2330     NSConnection *svrConn = [notification object];
2332     //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2333     [[NSNotificationCenter defaultCenter]
2334             removeObserver:self
2335                       name:NSConnectionDidDieNotification
2336                     object:svrConn];
2338     [connectionNameDict removeObjectsForKeys:
2339         [connectionNameDict allKeysForObject:svrConn]];
2341     // HACK! Assume connection uses mach ports.
2342     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2343     NSNumber *key = [NSNumber numberWithInt:port];
2345     [clientProxyDict removeObjectForKey:key];
2346     [serverReplyDict removeObjectForKey:key];
2349 - (void)addClient:(NSDistantObject *)client
2351     NSConnection *conn = [client connectionForProxy];
2352     // HACK! Assume connection uses mach ports.
2353     int port = [(NSMachPort*)[conn sendPort] machPort];
2354     NSNumber *key = [NSNumber numberWithInt:port];
2356     if (![clientProxyDict objectForKey:key]) {
2357         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2358         [clientProxyDict setObject:client forKey:key];
2359     }
2361     // NOTE: 'clientWindow' is a global variable which is used by <client>
2362     clientWindow = port;
2365 - (NSString *)alternateServerNameForName:(NSString *)name
2367     if (!(name && [name length] > 0))
2368         return nil;
2370     // Only look for alternates if 'name' doesn't end in a digit.
2371     unichar lastChar = [name characterAtIndex:[name length]-1];
2372     if (lastChar >= '0' && lastChar <= '9')
2373         return nil;
2375     // Look for alternates among all current servers.
2376     NSArray *list = [self serverList];
2377     if (!(list && [list count] > 0))
2378         return nil;
2380     // Filter out servers starting with 'name' and ending with a number. The
2381     // (?i) pattern ensures that the match is case insensitive.
2382     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2383     NSPredicate *pred = [NSPredicate predicateWithFormat:
2384             @"SELF MATCHES %@", pat];
2385     list = [list filteredArrayUsingPredicate:pred];
2386     if ([list count] > 0) {
2387         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2388         return [list objectAtIndex:0];
2389     }
2391     return nil;
2394 @end // MMBackend (ClientServer)
2399 @implementation NSString (MMServerNameCompare)
2400 - (NSComparisonResult)serverNameCompare:(NSString *)string
2402     return [self compare:string
2403                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2405 @end
2410 static int eventModifierFlagsToVimModMask(int modifierFlags)
2412     int modMask = 0;
2414     if (modifierFlags & NSShiftKeyMask)
2415         modMask |= MOD_MASK_SHIFT;
2416     if (modifierFlags & NSControlKeyMask)
2417         modMask |= MOD_MASK_CTRL;
2418     if (modifierFlags & NSAlternateKeyMask)
2419         modMask |= MOD_MASK_ALT;
2420     if (modifierFlags & NSCommandKeyMask)
2421         modMask |= MOD_MASK_CMD;
2423     return modMask;
2426 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2428     int modMask = 0;
2430     if (modifierFlags & NSShiftKeyMask)
2431         modMask |= MOUSE_SHIFT;
2432     if (modifierFlags & NSControlKeyMask)
2433         modMask |= MOUSE_CTRL;
2434     if (modifierFlags & NSAlternateKeyMask)
2435         modMask |= MOUSE_ALT;
2437     return modMask;
2440 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2442     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2444     return (buttonNumber >= 0 && buttonNumber < 3)
2445             ? mouseButton[buttonNumber] : -1;
2448 // This function is modeled after the VimToPython function found in if_python.c
2449 // NB This does a deep copy by value, it does not lookup references like the
2450 // VimToPython function does.  This is because I didn't want to deal with the
2451 // retain cycles that this would create, and we can cover 99% of the use cases
2452 // by ignoring it.  If we ever switch to using GC in MacVim then this
2453 // functionality can be implemented easily.
2454 static id vimToCocoa(typval_T * tv, int depth)
2456     id result = nil;
2457     id newObj = nil;
2460     // Avoid infinite recursion
2461     if (depth > 100) {
2462         return nil;
2463     }
2465     if (tv->v_type == VAR_STRING) {
2466         char_u * val = tv->vval.v_string;
2467         // val can be NULL if the string is empty
2468         if (!val) {
2469             result = [NSString string];
2470         } else {
2471 #ifdef FEAT_MBYTE
2472             val = CONVERT_TO_UTF8(val);
2473 #endif
2474             result = [NSString stringWithUTF8String:(char*)val];
2475 #ifdef FEAT_MBYTE
2476             CONVERT_TO_UTF8_FREE(val);
2477 #endif
2478         }
2479     } else if (tv->v_type == VAR_NUMBER) {
2480         // looks like sizeof(varnumber_T) is always <= sizeof(long)
2481         result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2482     } else if (tv->v_type == VAR_LIST) {
2483         list_T * list = tv->vval.v_list;
2484         listitem_T * curr;
2486         NSMutableArray * arr = result = [NSMutableArray array];
2488         if (list != NULL) {
2489             for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2490                 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2491                 [arr addObject:newObj];
2492             }
2493         }
2494     } else if (tv->v_type == VAR_DICT) {
2495         NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2497         if (tv->vval.v_dict != NULL) {
2498             hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2499             int todo = ht->ht_used;
2500             hashitem_T * hi;
2501             dictitem_T * di;
2503             for (hi = ht->ht_array; todo > 0; ++hi) {
2504                 if (!HASHITEM_EMPTY(hi)) {
2505                     --todo;
2507                     di = dict_lookup(hi);
2508                     newObj = vimToCocoa(&di->di_tv, depth + 1);
2510                     char_u * keyval = hi->hi_key;
2511 #ifdef FEAT_MBYTE
2512                     keyval = CONVERT_TO_UTF8(keyval);
2513 #endif
2514                     NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2515 #ifdef FEAT_MBYTE
2516                     CONVERT_TO_UTF8_FREE(keyval);
2517 #endif
2518                     [dict setObject:newObj forKey:key];
2519                 }
2520             }
2521         }
2522     } else { // only func refs should fall into this category?
2523         result = nil;
2524     }
2526     return result;
2530 // This function is modeled after eval_client_expr_to_string found in main.c
2531 // Returns nil if there was an error evaluating the expression, and writes a
2532 // message to errorStr.
2533 // TODO Get the error that occurred while evaluating the expression in vim
2534 // somehow.
2535 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2538     char_u *s = (char_u*)[expr UTF8String];
2540 #ifdef FEAT_MBYTE
2541     s = CONVERT_FROM_UTF8(s);
2542 #endif
2544     int save_dbl = debug_break_level;
2545     int save_ro = redir_off;
2547     debug_break_level = -1;
2548     redir_off = 0;
2549     ++emsg_skip;
2551     typval_T * tvres = eval_expr(s, NULL);
2553     debug_break_level = save_dbl;
2554     redir_off = save_ro;
2555     --emsg_skip;
2557     setcursor();
2558     out_flush();
2560 #ifdef FEAT_MBYTE
2561     CONVERT_FROM_UTF8_FREE(s);
2562 #endif
2564 #ifdef FEAT_GUI
2565     if (gui.in_use)
2566         gui_update_cursor(FALSE, FALSE);
2567 #endif
2569     if (tvres == NULL) {
2570         free_tv(tvres);
2571         *errstr = @"Expression evaluation failed.";
2572     }
2574     id res = vimToCocoa(tvres, 1);
2576     free_tv(tvres);
2578     if (res == nil) {
2579         *errstr = @"Conversion to cocoa values failed.";
2580     }
2582     return res;