Don't pass 'long' vars over process boundary
[MacVim.git] / src / MacVim / MMBackend.m
blob44ecf11c123302fb6b803d89f55f3834b2efcecf
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 [MMAppController processInput:forIdentifier:].
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) ))
42 // Values for window layout (must match values in main.c).
43 #define WIN_HOR     1       // "-o" horizontally split windows
44 #define WIN_VER     2       // "-O" vertically split windows
45 #define WIN_TABS    3       // "-p" windows on tab pages
47 static unsigned MMServerMax = 1000;
49 // TODO: Move to separate file.
50 static int eventModifierFlagsToVimModMask(int modifierFlags);
51 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
52 static int eventButtonNumberToVimMouseButton(int buttonNumber);
54 // In gui_macvim.m
55 vimmenu_T *menu_for_descriptor(NSArray *desc);
57 static id evalExprCocoa(NSString * expr, NSString ** errstr);
59 extern void im_preedit_start_macvim();
60 extern void im_preedit_end_macvim();
61 extern void im_preedit_changed_macvim(char *preedit_string, int cursor_index);
63 enum {
64     MMBlinkStateNone = 0,
65     MMBlinkStateOn,
66     MMBlinkStateOff
69 static NSString *MMSymlinkWarningString =
70     @"\n\n\tMost likely this is because you have symlinked directly to\n"
71      "\tthe Vim binary, which Cocoa does not allow.  Please use an\n"
72      "\talias or the mvim shell script instead.  If you have not used\n"
73      "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
76 // Keycodes recognized by Vim (struct taken from gui_x11.c and gui_w48.c)
77 // (The key codes were taken from Carbon/HIToolbox/Events.)
78 static struct specialkey
80     unsigned    key_sym;
81     char_u      vim_code0;
82     char_u      vim_code1;
83 } special_keys[] =
85     {0x7e /*kVK_UpArrow*/,       'k', 'u'},
86     {0x7d /*kVK_DownArrow*/,     'k', 'd'},
87     {0x7b /*kVK_LeftArrow*/,     'k', 'l'},
88     {0x7c /*kVK_RightArrow*/,    'k', 'r'},
90     {0x7a /*kVK_F1*/,            'k', '1'},
91     {0x78 /*kVK_F2*/,            'k', '2'},
92     {0x63 /*kVK_F3*/,            'k', '3'},
93     {0x76 /*kVK_F4*/,            'k', '4'},
94     {0x60 /*kVK_F5*/,            'k', '5'},
95     {0x61 /*kVK_F6*/,            'k', '6'},
96     {0x62 /*kVK_F7*/,            'k', '7'},
97     {0x64 /*kVK_F8*/,            'k', '8'},
98     {0x65 /*kVK_F9*/,            'k', '9'},
99     {0x6d /*kVK_F10*/,           'k', ';'},
101     {0x67 /*kVK_F11*/,           'F', '1'},
102     {0x6f /*kVK_F12*/,           'F', '2'},
103     {0x69 /*kVK_F13*/,           'F', '3'},
104     {0x6b /*kVK_F14*/,           'F', '4'},
105     {0x71 /*kVK_F15*/,           'F', '5'},
106     {0x6a /*kVK_F16*/,           'F', '6'},
107     {0x40 /*kVK_F17*/,           'F', '7'},
108     {0x4f /*kVK_F18*/,           'F', '8'},
109     {0x50 /*kVK_F19*/,           'F', '9'},
110     {0x5a /*kVK_F20*/,           'F', 'A'},
112     {0x72 /*kVK_Help*/,          '%', '1'},
113     {0x33 /*kVK_Delete*/,        'k', 'b'},
114     {0x75 /*kVK_ForwardDelete*/, 'k', 'D'},
115     {0x73 /*kVK_Home*/,          'k', 'h'},
116     {0x77 /*kVK_End*/,           '@', '7'},
117     {0x74 /*kVK_PageUp*/,        'k', 'P'},
118     {0x79 /*kVK_PageDown*/,      'k', 'N'},
120     /* Keypad keys: */
121     {0x45 /*kVK_ANSI_KeypadPlus*/,       'K', '6'},
122     {0x4e /*kVK_ANSI_KeypadMinus*/,      'K', '7'},
123     {0x4b /*kVK_ANSI_KeypadDivide*/,     'K', '8'},
124     {0x43 /*kVK_ANSI_KeypadMultiply*/,   'K', '9'},
125     {0x4c /*kVK_ANSI_KeypadEnter*/,      'K', 'A'},
126     {0x41 /*kVK_ANSI_KeypadDecimal*/,    'K', 'B'},
127     {0x47 /*kVK_ANSI_KeypadClear*/,      KS_EXTRA, (char_u)KE_KDEL},
129     {0x52 /*kVK_ANSI_Keypad0*/,  'K', 'C'},
130     {0x53 /*kVK_ANSI_Keypad1*/,  'K', 'D'},
131     {0x54 /*kVK_ANSI_Keypad2*/,  'K', 'E'},
132     {0x55 /*kVK_ANSI_Keypad3*/,  'K', 'F'},
133     {0x56 /*kVK_ANSI_Keypad4*/,  'K', 'G'},
134     {0x57 /*kVK_ANSI_Keypad5*/,  'K', 'H'},
135     {0x58 /*kVK_ANSI_Keypad6*/,  'K', 'I'},
136     {0x59 /*kVK_ANSI_Keypad7*/,  'K', 'J'},
137     {0x5b /*kVK_ANSI_Keypad8*/,  'K', 'K'},
138     {0x5c /*kVK_ANSI_Keypad9*/,  'K', 'L'},
140     /* Keys that we want to be able to use any modifier with: */
141     {0x31 /*kVK_Space*/,         ' ', NUL},
142     {0x30 /*kVK_Tab*/,           TAB, NUL},
143     {0x35 /*kVK_Escape*/,        ESC, NUL},
144     {0x24 /*kVK_Return*/,        CAR, NUL},
146     /* End of list marker: */
147     {0, 0, 0}
151 extern GuiFont gui_mch_retain_font(GuiFont font);
155 @interface NSString (MMServerNameCompare)
156 - (NSComparisonResult)serverNameCompare:(NSString *)string;
157 @end
162 @interface MMBackend (Private)
163 - (void)clearDrawData;
164 - (void)didChangeWholeLine;
165 - (void)waitForDialogReturn;
166 - (void)insertVimStateMessage;
167 - (void)processInputQueue;
168 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
169 - (void)doKeyDown:(NSString *)key
170           keyCode:(unsigned)code
171         modifiers:(int)mods;
172 - (BOOL)handleSpecialKey:(NSString *)key
173                  keyCode:(unsigned)code
174                modifiers:(int)mods;
175 - (BOOL)handleMacMetaKey:(int)ikey modifiers:(int)mods;
176 - (void)queueMessage:(int)msgid data:(NSData *)data;
177 - (void)connectionDidDie:(NSNotification *)notification;
178 - (void)blinkTimerFired:(NSTimer *)timer;
179 - (void)focusChange:(BOOL)on;
180 - (void)handleToggleToolbar;
181 - (void)handleScrollbarEvent:(NSData *)data;
182 - (void)handleSetFont:(NSData *)data;
183 - (void)handleDropFiles:(NSData *)data;
184 - (void)handleDropString:(NSData *)data;
185 - (void)startOdbEditWithArguments:(NSDictionary *)args;
186 - (void)handleXcodeMod:(NSData *)data;
187 - (void)handleOpenWithArguments:(NSDictionary *)args;
188 - (BOOL)checkForModifiedBuffers;
189 - (void)addInput:(NSString *)input;
190 - (BOOL)unusedEditor;
191 - (void)redrawScreen;
192 - (void)handleFindReplace:(NSDictionary *)args;
193 - (void)handleMarkedText:(NSData *)data;
194 @end
198 @interface MMBackend (ClientServer)
199 - (NSString *)connectionNameFromServerName:(NSString *)name;
200 - (NSConnection *)connectionForServerName:(NSString *)name;
201 - (NSConnection *)connectionForServerPort:(int)port;
202 - (void)serverConnectionDidDie:(NSNotification *)notification;
203 - (void)addClient:(NSDistantObject *)client;
204 - (NSString *)alternateServerNameForName:(NSString *)name;
205 @end
209 @implementation MMBackend
211 + (MMBackend *)sharedInstance
213     static MMBackend *singleton = nil;
214     return singleton ? singleton : (singleton = [MMBackend new]);
217 - (id)init
219     self = [super init];
220     if (!self) return nil;
222     outputQueue = [[NSMutableArray alloc] init];
223     inputQueue = [[NSMutableArray alloc] init];
224     drawData = [[NSMutableData alloc] initWithCapacity:1024];
225     connectionNameDict = [[NSMutableDictionary alloc] init];
226     clientProxyDict = [[NSMutableDictionary alloc] init];
227     serverReplyDict = [[NSMutableDictionary alloc] init];
229     NSBundle *mainBundle = [NSBundle mainBundle];
230     NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
231     if (path)
232         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
234     path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
235     if (path)
236         sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
237             retain];
239     path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
240     if (path)
241         actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
243     if (!(colorDict && sysColorDict && actionDict)) {
244         ASLogNotice(@"Failed to load dictionaries.%@", MMSymlinkWarningString);
245     }
247     return self;
250 - (void)dealloc
252     ASLogDebug(@"");
254     [[NSNotificationCenter defaultCenter] removeObserver:self];
256     gui_mch_free_font(oldWideFont);  oldWideFont = NOFONT;
257     [blinkTimer release];  blinkTimer = nil;
258     [alternateServerName release];  alternateServerName = nil;
259     [serverReplyDict release];  serverReplyDict = nil;
260     [clientProxyDict release];  clientProxyDict = nil;
261     [connectionNameDict release];  connectionNameDict = nil;
262     [inputQueue release];  inputQueue = nil;
263     [outputQueue release];  outputQueue = nil;
264     [drawData release];  drawData = nil;
265     [connection release];  connection = nil;
266     [actionDict release];  actionDict = nil;
267     [sysColorDict release];  sysColorDict = nil;
268     [colorDict release];  colorDict = nil;
269     [vimServerConnection release];  vimServerConnection = nil;
271     [super dealloc];
274 - (void)setBackgroundColor:(int)color
276     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
279 - (void)setForegroundColor:(int)color
281     foregroundColor = MM_COLOR(color);
284 - (void)setSpecialColor:(int)color
286     specialColor = MM_COLOR(color);
289 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
291     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
292     defaultForegroundColor = MM_COLOR(fg);
294     NSMutableData *data = [NSMutableData data];
296     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
297     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
299     [self queueMessage:SetDefaultColorsMsgID data:data];
302 - (NSConnection *)connection
304     if (!connection) {
305         // NOTE!  If the name of the connection changes here it must also be
306         // updated in MMAppController.m.
307         NSString *name = [NSString stringWithFormat:@"%@-connection",
308                [[NSBundle mainBundle] bundlePath]];
310         connection = [NSConnection connectionWithRegisteredName:name host:nil];
311         [connection retain];
312     }
314     // NOTE: 'connection' may be nil here.
315     return connection;
318 - (NSDictionary *)actionDict
320     return actionDict;
323 - (int)initialWindowLayout
325     return initialWindowLayout;
328 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
330     [self queueMessage:msgid data:[props dictionaryAsData]];
333 - (BOOL)checkin
335     if (![self connection]) {
336         if (waitForAck) {
337             // This is a preloaded process and as such should not cause the
338             // MacVim to be opened.  We probably got here as a result of the
339             // user quitting MacVim while the process was preloading, so exit
340             // this process too.
341             // (Don't use mch_exit() since it assumes the process has properly
342             // started.)
343             exit(0);
344         }
346         NSBundle *mainBundle = [NSBundle mainBundle];
347 #if 0
348         OSStatus status;
349         FSRef ref;
351         // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
352         // the API to pass Apple Event parameters is broken on 10.4).
353         NSString *path = [mainBundle bundlePath];
354         status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
355         if (noErr == status) {
356             // Pass parameter to the 'Open' Apple Event that tells MacVim not
357             // to open an untitled window.
358             NSAppleEventDescriptor *desc =
359                     [NSAppleEventDescriptor recordDescriptor];
360             [desc setParamDescriptor:
361                     [NSAppleEventDescriptor descriptorWithBoolean:NO]
362                           forKeyword:keyMMUntitledWindow];
364             LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
365                     kLSLaunchDefaults, NULL };
366             status = LSOpenFromRefSpec(&spec, NULL);
367         }
369         if (noErr != status) {
370         ASLogCrit(@"Failed to launch MacVim (path=%@).%@",
371                   path, MMSymlinkWarningString);
372             return NO;
373         }
374 #else
375         // Launch MacVim using NSTask.  For some reason the above code using
376         // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
377         // fails, the dock icon starts bouncing and never stops).  It seems
378         // like rebuilding the Launch Services database takes care of this
379         // problem, but the NSTask way seems more stable so stick with it.
380         //
381         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
382         // that the GUI won't be activated (or raised) so there is a hack in
383         // MMAppController which raises the app when a new window is opened.
384         NSMutableArray *args = [NSMutableArray arrayWithObjects:
385             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
386         NSString *exeName = [[mainBundle infoDictionary]
387                 objectForKey:@"CFBundleExecutable"];
388         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
389         if (!path) {
390             ASLogCrit(@"Could not find MacVim executable in bundle.%@",
391                       MMSymlinkWarningString);
392             return NO;
393         }
395         [NSTask launchedTaskWithLaunchPath:path arguments:args];
396 #endif
398         // HACK!  Poll the mach bootstrap server until it returns a valid
399         // connection to detect that MacVim has finished launching.  Also set a
400         // time-out date so that we don't get stuck doing this forever.
401         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
402         while (![self connection] &&
403                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
404             [[NSRunLoop currentRunLoop]
405                     runMode:NSDefaultRunLoopMode
406                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
408         // NOTE: [self connection] will set 'connection' as a side-effect.
409         if (!connection) {
410             ASLogCrit(@"Timed-out waiting for GUI to launch.");
411             return NO;
412         }
413     }
415     @try {
416         [[NSNotificationCenter defaultCenter] addObserver:self
417                 selector:@selector(connectionDidDie:)
418                     name:NSConnectionDidDieNotification object:connection];
420         appProxy = [[connection rootProxy] retain];
421         [appProxy setProtocolForProxy:@protocol(MMAppProtocol)];
423         // NOTE: We do not set any new timeout values for the connection to the
424         // frontend.  This means that if the frontend is "stuck" (e.g. in a
425         // modal loop) then any calls to the frontend will block indefinitely
426         // (the default timeouts are huge).
428         int pid = [[NSProcessInfo processInfo] processIdentifier];
430         identifier = [appProxy connectBackend:self pid:pid];
431         return YES;
432     }
433     @catch (NSException *ex) {
434         ASLogNotice(@"Connect backend failed: reason=%@", ex);
435     }
437     return NO;
440 - (BOOL)openGUIWindow
442     [self queueMessage:OpenWindowMsgID data:nil];
443     return YES;
446 - (void)clearAll
448     int type = ClearAllDrawType;
450     // Any draw commands in queue are effectively obsolete since this clearAll
451     // will negate any effect they have, therefore we may as well clear the
452     // draw queue.
453     [self clearDrawData];
455     [drawData appendBytes:&type length:sizeof(int)];
458 - (void)clearBlockFromRow:(int)row1 column:(int)col1
459                     toRow:(int)row2 column:(int)col2
461     int type = ClearBlockDrawType;
463     [drawData appendBytes:&type length:sizeof(int)];
465     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
466     [drawData appendBytes:&row1 length:sizeof(int)];
467     [drawData appendBytes:&col1 length:sizeof(int)];
468     [drawData appendBytes:&row2 length:sizeof(int)];
469     [drawData appendBytes:&col2 length:sizeof(int)];
472 - (void)deleteLinesFromRow:(int)row count:(int)count
473               scrollBottom:(int)bottom left:(int)left right:(int)right
475     int type = DeleteLinesDrawType;
477     [drawData appendBytes:&type length:sizeof(int)];
479     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
480     [drawData appendBytes:&row length:sizeof(int)];
481     [drawData appendBytes:&count length:sizeof(int)];
482     [drawData appendBytes:&bottom length:sizeof(int)];
483     [drawData appendBytes:&left length:sizeof(int)];
484     [drawData appendBytes:&right length:sizeof(int)];
486     if (left == 0 && right == gui.num_cols-1)
487         [self didChangeWholeLine];
490 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
491              cells:(int)cells flags:(int)flags
493     if (len <= 0 || cells <= 0) return;
495     int type = DrawStringDrawType;
497     [drawData appendBytes:&type length:sizeof(int)];
499     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
500     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
501     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
502     [drawData appendBytes:&row length:sizeof(int)];
503     [drawData appendBytes:&col length:sizeof(int)];
504     [drawData appendBytes:&cells length:sizeof(int)];
505     [drawData appendBytes:&flags length:sizeof(int)];
506     [drawData appendBytes:&len length:sizeof(int)];
507     [drawData appendBytes:s length:len];
510 - (void)insertLinesFromRow:(int)row count:(int)count
511               scrollBottom:(int)bottom left:(int)left right:(int)right
513     int type = InsertLinesDrawType;
515     [drawData appendBytes:&type length:sizeof(int)];
517     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
518     [drawData appendBytes:&row length:sizeof(int)];
519     [drawData appendBytes:&count length:sizeof(int)];
520     [drawData appendBytes:&bottom length:sizeof(int)];
521     [drawData appendBytes:&left length:sizeof(int)];
522     [drawData appendBytes:&right length:sizeof(int)];
524     if (left == 0 && right == gui.num_cols-1)
525         [self didChangeWholeLine];
528 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
529                fraction:(int)percent color:(int)color
531     int type = DrawCursorDrawType;
532     unsigned uc = MM_COLOR(color);
534     [drawData appendBytes:&type length:sizeof(int)];
536     [drawData appendBytes:&uc length:sizeof(unsigned)];
537     [drawData appendBytes:&row length:sizeof(int)];
538     [drawData appendBytes:&col length:sizeof(int)];
539     [drawData appendBytes:&shape length:sizeof(int)];
540     [drawData appendBytes:&percent length:sizeof(int)];
543 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
544                    numColumns:(int)nc invert:(int)invert
546     int type = DrawInvertedRectDrawType;
547     [drawData appendBytes:&type length:sizeof(int)];
549     [drawData appendBytes:&row length:sizeof(int)];
550     [drawData appendBytes:&col length:sizeof(int)];
551     [drawData appendBytes:&nr length:sizeof(int)];
552     [drawData appendBytes:&nc length:sizeof(int)];
553     [drawData appendBytes:&invert length:sizeof(int)];
556 - (void)update
558     // Keep running the run-loop until there is no more input to process.
559     while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
560             == kCFRunLoopRunHandledSource)
561         ;   // do nothing
564 - (void)flushQueue:(BOOL)force
566     // NOTE: This variable allows for better control over when the queue is
567     // flushed.  It can be set to YES at the beginning of a sequence of calls
568     // that may potentially add items to the queue, and then restored back to
569     // NO.
570     if (flushDisabled) return;
572     if ([drawData length] > 0) {
573         // HACK!  Detect changes to 'guifontwide'.
574         if (gui.wide_font != oldWideFont) {
575             gui_mch_free_font(oldWideFont);
576             oldWideFont = gui_mch_retain_font(gui.wide_font);
577             [self setFont:oldWideFont wide:YES];
578         }
580         int type = SetCursorPosDrawType;
581         [drawData appendBytes:&type length:sizeof(type)];
582         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
583         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
585         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
586         [self clearDrawData];
587     }
589     if ([outputQueue count] > 0) {
590         [self insertVimStateMessage];
592         @try {
593             ASLogDebug(@"Flushing queue: %@",
594                        debugStringForMessageQueue(outputQueue));
595             [appProxy processInput:outputQueue forIdentifier:identifier];
596         }
597         @catch (NSException *ex) {
598             ASLogNotice(@"processInput:forIdentifer failed: reason=%@", ex);
599             if (![connection isValid]) {
600                 ASLogNotice(@"Connection is invalid, exit now!");
601                 ASLogDebug(@"waitForAck=%d got_int=%d", waitForAck, got_int);
602                 mch_exit(-1);
603             }
604         }
606         [outputQueue removeAllObjects];
607     }
610 - (BOOL)waitForInput:(int)milliseconds
612     // Return NO if we timed out waiting for input, otherwise return YES.
613     BOOL inputReceived = NO;
615     // Only start the run loop if the input queue is empty, otherwise process
616     // the input first so that the input on queue isn't delayed.
617     if ([inputQueue count]) {
618         inputReceived = YES;
619     } else {
620         // Wait for the specified amount of time, unless 'milliseconds' is
621         // negative in which case we wait "forever" (1e6 seconds translates to
622         // approximately 11 days).
623         CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
625         while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
626                 == kCFRunLoopRunHandledSource) {
627             // In order to ensure that all input on the run-loop has been
628             // processed we set the timeout to 0 and keep processing until the
629             // run-loop times out.
630             dt = 0.0;
631             inputReceived = YES;
632         }
633     }
635     // The above calls may have placed messages on the input queue so process
636     // it now.  This call may enter a blocking loop.
637     if ([inputQueue count] > 0)
638         [self processInputQueue];
640     return inputReceived;
643 - (void)exit
645     // NOTE: This is called if mch_exit() is called.  Since we assume here that
646     // the process has started properly, be sure to use exit() instead of
647     // mch_exit() to prematurely terminate a process (or set 'isTerminating'
648     // first).
650     // Make sure no connectionDidDie: notification is received now that we are
651     // already exiting.
652     [[NSNotificationCenter defaultCenter] removeObserver:self];
654     // The 'isTerminating' flag indicates that the frontend is also exiting so
655     // there is no need to flush any more output since the frontend won't look
656     // at it anyway.
657     if (!isTerminating && [connection isValid]) {
658         @try {
659             // Flush the entire queue in case a VimLeave autocommand added
660             // something to the queue.
661             [self queueMessage:CloseWindowMsgID data:nil];
662             ASLogDebug(@"Flush output queue before exit: %@",
663                        debugStringForMessageQueue(outputQueue));
664             [appProxy processInput:outputQueue forIdentifier:identifier];
665         }
666         @catch (NSException *ex) {
667             ASLogNotice(@"CloseWindowMsgID send failed: reason=%@", ex);
668         }
670         // NOTE: If Cmd-w was pressed to close the window the menu is briefly
671         // highlighted and during this pause the frontend won't receive any DO
672         // messages.  If the Vim process exits before this highlighting has
673         // finished Cocoa will emit the following error message:
674         //   *** -[NSMachPort handlePortMessage:]: dropping incoming DO message
675         //   because the connection or ports are invalid
676         // To avoid this warning we delay here.  If the warning still appears
677         // this delay may need to be increased.
678         usleep(150000);
679     }
681 #ifdef MAC_CLIENTSERVER
682     // The default connection is used for the client/server code.
683     if (vimServerConnection) {
684         [vimServerConnection setRootObject:nil];
685         [vimServerConnection invalidate];
686     }
687 #endif
690 - (void)selectTab:(int)index
692     index -= 1;
693     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
694     [self queueMessage:SelectTabMsgID data:data];
697 - (void)updateTabBar
699     NSMutableData *data = [NSMutableData data];
701     int idx = tabpage_index(curtab) - 1;
702     [data appendBytes:&idx length:sizeof(int)];
704     tabpage_T *tp;
705     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
706         // Count the number of windows in the tabpage.
707         //win_T *wp = tp->tp_firstwin;
708         //int wincount;
709         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
710         //[data appendBytes:&wincount length:sizeof(int)];
712         int tabProp = MMTabInfoCount;
713         [data appendBytes:&tabProp length:sizeof(int)];
714         for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
715             // This function puts the label of the tab in the global 'NameBuff'.
716             get_tabline_label(tp, (tabProp == MMTabToolTip));
717             NSString *s = [NSString stringWithVimString:NameBuff];
718             int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
719             if (len < 0)
720                 len = 0;
722             [data appendBytes:&len length:sizeof(int)];
723             if (len > 0)
724                 [data appendBytes:[s UTF8String] length:len];
725         }
726     }
728     [self queueMessage:UpdateTabBarMsgID data:data];
731 - (BOOL)tabBarVisible
733     return tabBarVisible;
736 - (void)showTabBar:(BOOL)enable
738     tabBarVisible = enable;
740     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
741     [self queueMessage:msgid data:nil];
744 - (void)setRows:(int)rows columns:(int)cols
746     int dim[] = { rows, cols };
747     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
749     [self queueMessage:SetTextDimensionsMsgID data:data];
752 - (void)setWindowTitle:(char *)title
754     NSMutableData *data = [NSMutableData data];
755     int len = strlen(title);
756     if (len <= 0) return;
758     [data appendBytes:&len length:sizeof(int)];
759     [data appendBytes:title length:len];
761     [self queueMessage:SetWindowTitleMsgID data:data];
764 - (void)setDocumentFilename:(char *)filename
766     NSMutableData *data = [NSMutableData data];
767     int len = filename ? strlen(filename) : 0;
769     [data appendBytes:&len length:sizeof(int)];
770     if (len > 0)
771         [data appendBytes:filename length:len];
773     [self queueMessage:SetDocumentFilenameMsgID data:data];
776 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
778     char_u *s = NULL;
780     [self queueMessage:BrowseForFileMsgID properties:attr];
781     [self flushQueue:YES];
783     @try {
784         [self waitForDialogReturn];
786         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
787             s = [dialogReturn vimStringSave];
789         [dialogReturn release];  dialogReturn = nil;
790     }
791     @catch (NSException *ex) {
792         ASLogNotice(@"Exception: reason=%@", ex);
793     }
795     return (char *)s;
798 - (oneway void)setDialogReturn:(in bycopy id)obj
800     ASLogDebug(@"%@", obj);
802     // NOTE: This is called by
803     //   - [MMVimController panelDidEnd:::], and
804     //   - [MMVimController alertDidEnd:::],
805     // to indicate that a save/open panel or alert has finished.
807     // We want to distinguish between "no dialog return yet" and "dialog
808     // returned nothing".  The former can be tested with dialogReturn == nil,
809     // the latter with dialogReturn == [NSNull null].
810     if (!obj) obj = [NSNull null];
812     if (obj != dialogReturn) {
813         [dialogReturn release];
814         dialogReturn = [obj retain];
815     }
818 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
820     int retval = 0;
822     [self queueMessage:ShowDialogMsgID properties:attr];
823     [self flushQueue:YES];
825     @try {
826         [self waitForDialogReturn];
828         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
829                 && [dialogReturn count]) {
830             retval = [[dialogReturn objectAtIndex:0] intValue];
831             if (txtfield && [dialogReturn count] > 1) {
832                 NSString *retString = [dialogReturn objectAtIndex:1];
833                 char_u *ret = (char_u*)[retString UTF8String];
834 #ifdef FEAT_MBYTE
835                 ret = CONVERT_FROM_UTF8(ret);
836 #endif
837                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
838 #ifdef FEAT_MBYTE
839                 CONVERT_FROM_UTF8_FREE(ret);
840 #endif
841             }
842         }
844         [dialogReturn release]; dialogReturn = nil;
845     }
846     @catch (NSException *ex) {
847         ASLogNotice(@"Exception: reason=%@", ex);
848     }
850     return retval;
853 - (void)showToolbar:(int)enable flags:(int)flags
855     NSMutableData *data = [NSMutableData data];
857     [data appendBytes:&enable length:sizeof(int)];
858     [data appendBytes:&flags length:sizeof(int)];
860     [self queueMessage:ShowToolbarMsgID data:data];
863 - (void)createScrollbarWithIdentifier:(int32_t)ident type:(int)type
865     NSMutableData *data = [NSMutableData data];
867     [data appendBytes:&ident length:sizeof(int32_t)];
868     [data appendBytes:&type length:sizeof(int)];
870     [self queueMessage:CreateScrollbarMsgID data:data];
873 - (void)destroyScrollbarWithIdentifier:(int32_t)ident
875     NSMutableData *data = [NSMutableData data];
876     [data appendBytes:&ident length:sizeof(int32_t)];
878     [self queueMessage:DestroyScrollbarMsgID data:data];
881 - (void)showScrollbarWithIdentifier:(int32_t)ident state:(int)visible
883     NSMutableData *data = [NSMutableData data];
885     [data appendBytes:&ident length:sizeof(int32_t)];
886     [data appendBytes:&visible length:sizeof(int)];
888     [self queueMessage:ShowScrollbarMsgID data:data];
891 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(int32_t)ident
893     NSMutableData *data = [NSMutableData data];
895     [data appendBytes:&ident length:sizeof(int32_t)];
896     [data appendBytes:&pos length:sizeof(int)];
897     [data appendBytes:&len length:sizeof(int)];
899     [self queueMessage:SetScrollbarPositionMsgID data:data];
902 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
903                     identifier:(int32_t)ident
905     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
906     float prop = (float)size/(max+1);
907     if (fval < 0) fval = 0;
908     else if (fval > 1.0f) fval = 1.0f;
909     if (prop < 0) prop = 0;
910     else if (prop > 1.0f) prop = 1.0f;
912     NSMutableData *data = [NSMutableData data];
914     [data appendBytes:&ident length:sizeof(int32_t)];
915     [data appendBytes:&fval length:sizeof(float)];
916     [data appendBytes:&prop length:sizeof(float)];
918     [self queueMessage:SetScrollbarThumbMsgID data:data];
921 - (void)setFont:(GuiFont)font wide:(BOOL)wide
923     NSString *fontName = (NSString *)font;
924     float size = 0;
925     NSArray *components = [fontName componentsSeparatedByString:@":"];
926     if ([components count] == 2) {
927         size = [[components lastObject] floatValue];
928         fontName = [components objectAtIndex:0];
929     }
931     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
932     NSMutableData *data = [NSMutableData data];
933     [data appendBytes:&size length:sizeof(float)];
934     [data appendBytes:&len length:sizeof(int)];
936     if (len > 0)
937         [data appendBytes:[fontName UTF8String] length:len];
938     else if (!wide)
939         return;     // Only the wide font can be set to nothing
941     [self queueMessage:(wide ? SetWideFontMsgID : SetFontMsgID) data:data];
944 - (void)executeActionWithName:(NSString *)name
946     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
948     if (len > 0) {
949         NSMutableData *data = [NSMutableData data];
951         [data appendBytes:&len length:sizeof(int)];
952         [data appendBytes:[name UTF8String] length:len];
954         [self queueMessage:ExecuteActionMsgID data:data];
955     }
958 - (void)setMouseShape:(int)shape
960     NSMutableData *data = [NSMutableData data];
961     [data appendBytes:&shape length:sizeof(int)];
962     [self queueMessage:SetMouseShapeMsgID data:data];
965 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
967     // Vim specifies times in milliseconds, whereas Cocoa wants them in
968     // seconds.
969     blinkWaitInterval = .001f*wait;
970     blinkOnInterval = .001f*on;
971     blinkOffInterval = .001f*off;
974 - (void)startBlink
976     if (blinkTimer) {
977         [blinkTimer invalidate];
978         [blinkTimer release];
979         blinkTimer = nil;
980     }
982     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
983             && gui.in_focus) {
984         blinkState = MMBlinkStateOn;
985         blinkTimer =
986             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
987                                               target:self
988                                             selector:@selector(blinkTimerFired:)
989                                             userInfo:nil repeats:NO] retain];
990         gui_update_cursor(TRUE, FALSE);
991         [self flushQueue:YES];
992     }
995 - (void)stopBlink
997     if (MMBlinkStateOff == blinkState) {
998         gui_update_cursor(TRUE, FALSE);
999         [self flushQueue:YES];
1000     }
1002     blinkState = MMBlinkStateNone;
1005 - (void)adjustLinespace:(int)linespace
1007     NSMutableData *data = [NSMutableData data];
1008     [data appendBytes:&linespace length:sizeof(int)];
1009     [self queueMessage:AdjustLinespaceMsgID data:data];
1012 - (void)activate
1014     [self queueMessage:ActivateMsgID data:nil];
1017 - (void)setPreEditRow:(int)row column:(int)col
1019     NSMutableData *data = [NSMutableData data];
1020     [data appendBytes:&row length:sizeof(int)];
1021     [data appendBytes:&col length:sizeof(int)];
1022     [self queueMessage:SetPreEditPositionMsgID data:data];
1025 - (int)lookupColorWithKey:(NSString *)key
1027     if (!(key && [key length] > 0))
1028         return INVALCOLOR;
1030     NSString *stripKey = [[[[key lowercaseString]
1031         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
1032             componentsSeparatedByString:@" "]
1033                componentsJoinedByString:@""];
1035     if (stripKey && [stripKey length] > 0) {
1036         // First of all try to lookup key in the color dictionary; note that
1037         // all keys in this dictionary are lowercase with no whitespace.
1038         id obj = [colorDict objectForKey:stripKey];
1039         if (obj) return [obj intValue];
1041         // The key was not in the dictionary; is it perhaps of the form
1042         // #rrggbb?
1043         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
1044             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
1045             [scanner setScanLocation:1];
1046             unsigned hex = 0;
1047             if ([scanner scanHexInt:&hex]) {
1048                 return (int)hex;
1049             }
1050         }
1052         // As a last resort, check if it is one of the system defined colors.
1053         // The keys in this dictionary are also lowercase with no whitespace.
1054         obj = [sysColorDict objectForKey:stripKey];
1055         if (obj) {
1056             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
1057             if (col) {
1058                 float r, g, b, a;
1059                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
1060                 [col getRed:&r green:&g blue:&b alpha:&a];
1061                 return (((int)(r*255+.5f) & 0xff) << 16)
1062                      + (((int)(g*255+.5f) & 0xff) << 8)
1063                      +  ((int)(b*255+.5f) & 0xff);
1064             }
1065         }
1066     }
1068     ASLogNotice(@"No color with key %@ found.", stripKey);
1069     return INVALCOLOR;
1072 - (BOOL)hasSpecialKeyWithValue:(char_u *)value
1074     int i;
1075     for (i = 0; special_keys[i].key_sym != 0; i++) {
1076         if (value[0] == special_keys[i].vim_code0
1077                 && value[1] == special_keys[i].vim_code1)
1078             return YES;
1079     }
1081     return NO;
1084 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1086     NSMutableData *data = [NSMutableData data];
1087     [data appendBytes:&fuoptions length:sizeof(int)];
1088     bg = MM_COLOR(bg);
1089     [data appendBytes:&bg length:sizeof(int)];
1090     [self queueMessage:EnterFullscreenMsgID data:data];
1093 - (void)leaveFullscreen
1095     [self queueMessage:LeaveFullscreenMsgID data:nil];
1098 - (void)setFullscreenBackgroundColor:(int)color
1100     NSMutableData *data = [NSMutableData data];
1101     color = MM_COLOR(color);
1102     [data appendBytes:&color length:sizeof(int)];
1104     [self queueMessage:SetFullscreenColorMsgID data:data];
1107 - (void)setAntialias:(BOOL)antialias
1109     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1111     [self queueMessage:msgid data:nil];
1114 - (void)updateModifiedFlag
1116     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1117     // vice versa.
1118     int msgid = [self checkForModifiedBuffers]
1119             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1121     [self queueMessage:msgid data:nil];
1124 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1126     // Look for Ctrl-C immediately instead of waiting until the input queue is
1127     // processed since that only happens in waitForInput: (and Vim regularly
1128     // checks for Ctrl-C in between waiting for input).  Note that the flag
1129     // ctrl_c_interrupts is 0 e.g. when the user has mappings to something like
1130     // <C-c>g.  Also it seems the flag intr_char is 0 when MacVim was started
1131     // from Finder whereas it is 0x03 (= Ctrl_C) when started from Terminal.
1132     //
1133     // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1134     // which waits on the run loop will fail to detect this message (e.g. in
1135     // waitForConnectionAcknowledgement).
1137     if (KeyDownMsgID == msgid && data != nil && ctrl_c_interrupts) {
1138         const void *bytes = [data bytes];
1139         /*unsigned mods = *((unsigned*)bytes);*/  bytes += sizeof(unsigned);
1140         /*unsigned code = *((unsigned*)bytes);*/  bytes += sizeof(unsigned);
1141         unsigned len  = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1142         if (1 == len) {
1143             char_u *str = (char_u*)bytes;
1144             if (str[0] == Ctrl_C || (str[0] == intr_char && intr_char != 0)) {
1145                 ASLogDebug(@"Got INT, str[0]=%#x ctrl_c_interrupts=%d "
1146                         "intr_char=%#x", str[0], ctrl_c_interrupts, intr_char);
1147                 got_int = TRUE;
1148                 [inputQueue removeAllObjects];
1149                 return;
1150             }
1151         }
1152     } else if (TerminateNowMsgID == msgid) {
1153         // Terminate immediately (the frontend is about to quit or this process
1154         // was aborted).  Don't preserve modified files since the user would
1155         // already have been presented with a dialog warning if there were any
1156         // modified files when we get here.
1157         isTerminating = YES;
1158         getout(0);
1159         return;
1160     }
1162     // Remove all previous instances of this message from the input queue, else
1163     // the input queue may fill up as a result of Vim not being able to keep up
1164     // with the speed at which new messages are received.
1165     // Keyboard input is never dropped, unless the input represents an
1166     // auto-repeated key.
1168     BOOL isKeyRepeat = NO;
1169     BOOL isKeyboardInput = NO;
1171     if (data && KeyDownMsgID == msgid) {
1172         isKeyboardInput = YES;
1174         // The lowest bit of the first int is set if this key is a repeat.
1175         int flags = *((int*)[data bytes]);
1176         if (flags & 1)
1177             isKeyRepeat = YES;
1178     }
1180     // Keyboard input is not removed from the queue; repeats are ignored if
1181     // there already is keyboard input on the input queue.
1182     if (isKeyRepeat || !isKeyboardInput) {
1183         int i, count = [inputQueue count];
1184         for (i = 1; i < count; i+=2) {
1185             if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1186                 if (isKeyRepeat)
1187                     return;
1189                 [inputQueue removeObjectAtIndex:i];
1190                 [inputQueue removeObjectAtIndex:i-1];
1191                 break;
1192             }
1193         }
1194     }
1196     [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1197     [inputQueue addObject:(data ? (id)data : [NSNull null])];
1200 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1201                   errorString:(out bycopy NSString **)errstr
1203     return evalExprCocoa(expr, errstr);
1207 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1209     NSString *eval = nil;
1210     char_u *s = (char_u*)[expr UTF8String];
1212 #ifdef FEAT_MBYTE
1213     s = CONVERT_FROM_UTF8(s);
1214 #endif
1216     char_u *res = eval_client_expr_to_string(s);
1218 #ifdef FEAT_MBYTE
1219     CONVERT_FROM_UTF8_FREE(s);
1220 #endif
1222     if (res != NULL) {
1223         s = res;
1224 #ifdef FEAT_MBYTE
1225         s = CONVERT_TO_UTF8(s);
1226 #endif
1227         eval = [NSString stringWithUTF8String:(char*)s];
1228 #ifdef FEAT_MBYTE
1229         CONVERT_TO_UTF8_FREE(s);
1230 #endif
1231         vim_free(res);
1232     }
1234     return eval;
1237 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1239     // TODO: This method should share code with clip_mch_request_selection().
1241     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1242         // If there is no pasteboard, return YES to indicate that there is text
1243         // to copy.
1244         if (!pboard)
1245             return YES;
1247         clip_copy_selection();
1249         // Get the text to put on the pasteboard.
1250         long_u llen = 0; char_u *str = 0;
1251         int type = clip_convert_selection(&str, &llen, &clip_star);
1252         if (type < 0)
1253             return NO;
1254         
1255         // TODO: Avoid overflow.
1256         int len = (int)llen;
1257 #ifdef FEAT_MBYTE
1258         if (output_conv.vc_type != CONV_NONE) {
1259             char_u *conv_str = string_convert(&output_conv, str, &len);
1260             if (conv_str) {
1261                 vim_free(str);
1262                 str = conv_str;
1263             }
1264         }
1265 #endif
1267         NSString *string = [[NSString alloc]
1268             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1270         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1271         [pboard declareTypes:types owner:nil];
1272         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1273     
1274         [string release];
1275         vim_free(str);
1277         return ok;
1278     }
1280     return NO;
1283 - (oneway void)addReply:(in bycopy NSString *)reply
1284                  server:(in byref id <MMVimServerProtocol>)server
1286     ASLogDebug(@"reply=%@ server=%@", reply, (id)server);
1288     // Replies might come at any time and in any order so we keep them in an
1289     // array inside a dictionary with the send port used as key.
1291     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1292     // HACK! Assume connection uses mach ports.
1293     int port = [(NSMachPort*)[conn sendPort] machPort];
1294     NSNumber *key = [NSNumber numberWithInt:port];
1296     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1297     if (!replies) {
1298         replies = [NSMutableArray array];
1299         [serverReplyDict setObject:replies forKey:key];
1300     }
1302     [replies addObject:reply];
1305 - (void)addInput:(in bycopy NSString *)input
1306           client:(in byref id <MMVimClientProtocol>)client
1308     ASLogDebug(@"input=%@ client=%@", input, (id)client);
1310     // NOTE: We don't call addInput: here because it differs from
1311     // server_to_input_buf() in that it always sets the 'silent' flag and we
1312     // don't want the MacVim client/server code to behave differently from
1313     // other platforms.
1314     char_u *s = [input vimStringSave];
1315     server_to_input_buf(s);
1316     vim_free(s);
1318     [self addClient:(id)client];
1321 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1322                  client:(in byref id <MMVimClientProtocol>)client
1324     [self addClient:(id)client];
1325     return [self evaluateExpression:expr];
1328 - (void)registerServerWithName:(NSString *)name
1330     NSString *svrName = name;
1331     unsigned i;
1333     if (vimServerConnection) // Paranoia check, should always be nil
1334         [vimServerConnection release];
1336     vimServerConnection = [[NSConnection alloc]
1337                                             initWithReceivePort:[NSPort port]
1338                                                        sendPort:nil];
1340     for (i = 0; i < MMServerMax; ++i) {
1341         NSString *connName = [self connectionNameFromServerName:svrName];
1343         if ([vimServerConnection registerName:connName]) {
1344             ASLogInfo(@"Registered server with name: %@", svrName);
1346             // TODO: Set request/reply time-outs to something else?
1347             //
1348             // Don't wait for requests (time-out means that the message is
1349             // dropped).
1350             [vimServerConnection setRequestTimeout:0];
1351             //[vimServerConnection setReplyTimeout:MMReplyTimeout];
1352             [vimServerConnection setRootObject:self];
1354             // NOTE: 'serverName' is a global variable
1355             serverName = [svrName vimStringSave];
1356 #ifdef FEAT_EVAL
1357             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1358 #endif
1359 #ifdef FEAT_TITLE
1360             need_maketitle = TRUE;
1361 #endif
1362             [self queueMessage:SetServerNameMsgID
1363                         data:[svrName dataUsingEncoding:NSUTF8StringEncoding]];
1364             break;
1365         }
1367         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1368     }
1371 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1372                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1373               silent:(BOOL)silent
1375     // NOTE: If 'name' equals 'serverName' then the request is local (client
1376     // and server are the same).  This case is not handled separately, so a
1377     // connection will be set up anyway (this simplifies the code).
1379     NSConnection *conn = [self connectionForServerName:name];
1380     if (!conn) {
1381         if (!silent) {
1382             char_u *s = (char_u*)[name UTF8String];
1383 #ifdef FEAT_MBYTE
1384             s = CONVERT_FROM_UTF8(s);
1385 #endif
1386             EMSG2(_(e_noserver), s);
1387 #ifdef FEAT_MBYTE
1388             CONVERT_FROM_UTF8_FREE(s);
1389 #endif
1390         }
1391         return NO;
1392     }
1394     if (port) {
1395         // HACK! Assume connection uses mach ports.
1396         *port = [(NSMachPort*)[conn sendPort] machPort];
1397     }
1399     id proxy = [conn rootProxy];
1400     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1402     @try {
1403         if (expr) {
1404             NSString *eval = [proxy evaluateExpression:string client:self];
1405             if (reply) {
1406                 if (eval) {
1407                     *reply = [eval vimStringSave];
1408                 } else {
1409                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1410                 }
1411             }
1413             if (!eval)
1414                 return NO;
1415         } else {
1416             [proxy addInput:string client:self];
1417         }
1418     }
1419     @catch (NSException *ex) {
1420         ASLogNotice(@"Exception: reason=%@", ex);
1421         return NO;
1422     }
1424     return YES;
1427 - (NSArray *)serverList
1429     NSArray *list = nil;
1431     if ([self connection]) {
1432         id proxy = [connection rootProxy];
1433         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1435         @try {
1436             list = [proxy serverList];
1437         }
1438         @catch (NSException *ex) {
1439             ASLogNotice(@"serverList failed: reason=%@", ex);
1440         }
1441     } else {
1442         EMSG(_("E???: No connection to MacVim, server listing not possible."));
1443     }
1445     return list;
1448 - (NSString *)peekForReplyOnPort:(int)port
1450     ASLogDebug(@"port=%d", port);
1452     NSNumber *key = [NSNumber numberWithInt:port];
1453     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1454     if (replies && [replies count]) {
1455         ASLogDebug(@"    %d replies, topmost is: %@", [replies count],
1456                    [replies objectAtIndex:0]);
1457         return [replies objectAtIndex:0];
1458     }
1460     ASLogDebug(@"    No replies");
1461     return nil;
1464 - (NSString *)waitForReplyOnPort:(int)port
1466     ASLogDebug(@"port=%d", port);
1467     
1468     NSConnection *conn = [self connectionForServerPort:port];
1469     if (!conn)
1470         return nil;
1472     NSNumber *key = [NSNumber numberWithInt:port];
1473     NSMutableArray *replies = nil;
1474     NSString *reply = nil;
1476     // Wait for reply as long as the connection to the server is valid (unless
1477     // user interrupts wait with Ctrl-C).
1478     while (!got_int && [conn isValid] &&
1479             !(replies = [serverReplyDict objectForKey:key])) {
1480         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1481                                  beforeDate:[NSDate distantFuture]];
1482     }
1484     if (replies) {
1485         if ([replies count] > 0) {
1486             reply = [[replies objectAtIndex:0] retain];
1487             ASLogDebug(@"    Got reply: %@", reply);
1488             [replies removeObjectAtIndex:0];
1489             [reply autorelease];
1490         }
1492         if ([replies count] == 0)
1493             [serverReplyDict removeObjectForKey:key];
1494     }
1496     return reply;
1499 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1501     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1502     if (client) {
1503         @try {
1504             ASLogDebug(@"reply=%@ port=%d", reply, port);
1505             [client addReply:reply server:self];
1506             return YES;
1507         }
1508         @catch (NSException *ex) {
1509             ASLogNotice(@"addReply:server: failed: reason=%@", ex);
1510         }
1511     } else {
1512         EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1513     }
1515     return NO;
1518 - (BOOL)waitForAck
1520     return waitForAck;
1523 - (void)setWaitForAck:(BOOL)yn
1525     waitForAck = yn;
1528 - (void)waitForConnectionAcknowledgement
1530     if (!waitForAck) return;
1532     while (waitForAck && !got_int && [connection isValid]) {
1533         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1534                                  beforeDate:[NSDate distantFuture]];
1535         ASLogDebug(@"  waitForAck=%d got_int=%d isValid=%d",
1536                    waitForAck, got_int, [connection isValid]);
1537     }
1539     if (waitForAck) {
1540         ASLogDebug(@"Never received a connection acknowledgement");
1541         [[NSNotificationCenter defaultCenter] removeObserver:self];
1542         [appProxy release];  appProxy = nil;
1544         // NOTE: We intentionally do not call mch_exit() since this in turn
1545         // will lead to -[MMBackend exit] getting called which we want to
1546         // avoid.
1547         exit(0);
1548     }
1550     ASLogInfo(@"Connection acknowledgement received");
1551     [self processInputQueue];
1554 - (oneway void)acknowledgeConnection
1556     ASLogDebug(@"");
1557     waitForAck = NO;
1560 - (BOOL)imState
1562     return imState;
1565 - (void)setImState:(BOOL)activated
1567     imState = activated;
1570 static void netbeansReadCallback(CFSocketRef s,
1571                                  CFSocketCallBackType callbackType,
1572                                  CFDataRef address,
1573                                  const void *data,
1574                                  void *info)
1576     // NetBeans socket is readable.
1577     [[MMBackend sharedInstance] messageFromNetbeans];
1580 - (void)messageFromNetbeans
1582     [inputQueue addObject:[NSNumber numberWithInt:NetBeansMsgID]];
1583     [inputQueue addObject:[NSNull null]];
1586 - (void)setNetbeansSocket:(int)socket
1588     if (netbeansSocket) {
1589         CFRelease(netbeansSocket);
1590         netbeansSocket = NULL;
1591     }
1593     if (netbeansRunLoopSource) {
1594         CFRunLoopSourceInvalidate(netbeansRunLoopSource);
1595         netbeansRunLoopSource = NULL;
1596     }
1598     if (socket == -1)
1599         return;
1601     // Tell CFRunLoop that we are interested in NetBeans socket input.
1602     netbeansSocket = CFSocketCreateWithNative(kCFAllocatorDefault,
1603                                               socket,
1604                                               kCFSocketReadCallBack,
1605                                               &netbeansReadCallback,
1606                                               NULL);
1607     netbeansRunLoopSource = CFSocketCreateRunLoopSource(NULL,
1608                                                         netbeansSocket,
1609                                                         0);
1610     CFRunLoopAddSource(CFRunLoopGetCurrent(),
1611                        netbeansRunLoopSource,
1612                        kCFRunLoopCommonModes);
1615 @end // MMBackend
1619 @implementation MMBackend (Private)
1621 - (void)clearDrawData
1623     [drawData setLength:0];
1624     numWholeLineChanges = offsetForDrawDataPrune = 0;
1627 - (void)didChangeWholeLine
1629     // It may happen that draw queue is filled up with lots of changes that
1630     // affect a whole row.  If the number of such changes equals twice the
1631     // number of visible rows then we can prune some commands off the queue.
1632     //
1633     // NOTE: If we don't perform this pruning the draw queue may grow
1634     // indefinitely if Vim were to repeatedly send draw commands without ever
1635     // waiting for new input (that's when the draw queue is flushed).  The one
1636     // instance I know where this can happen is when a command is executed in
1637     // the shell (think ":grep" with thousands of matches).
1639     ++numWholeLineChanges;
1640     if (numWholeLineChanges == gui.num_rows) {
1641         // Remember the offset to prune up to.
1642         offsetForDrawDataPrune = [drawData length];
1643     } else if (numWholeLineChanges == 2*gui.num_rows) {
1644         // Delete all the unnecessary draw commands.
1645         NSMutableData *d = [[NSMutableData alloc]
1646                     initWithBytes:[drawData bytes] + offsetForDrawDataPrune
1647                            length:[drawData length] - offsetForDrawDataPrune];
1648         offsetForDrawDataPrune = [d length];
1649         numWholeLineChanges -= gui.num_rows;
1650         [drawData release];
1651         drawData = d;
1652     }
1655 - (void)waitForDialogReturn
1657     // Keep processing the run loop until a dialog returns.  To avoid getting
1658     // stuck in an endless loop (could happen if the setDialogReturn: message
1659     // was lost) we also do some paranoia checks.
1660     //
1661     // Note that in Cocoa the user can still resize windows and select menu
1662     // items while a sheet is being displayed, so we can't just wait for the
1663     // first message to arrive and assume that is the setDialogReturn: call.
1665     while (nil == dialogReturn && !got_int && [connection isValid])
1666         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1667                                  beforeDate:[NSDate distantFuture]];
1669     // Search for any resize messages on the input queue.  All other messages
1670     // on the input queue are dropped.  The reason why we single out resize
1671     // messages is because the user may have resized the window while a sheet
1672     // was open.
1673     int i, count = [inputQueue count];
1674     if (count > 0) {
1675         id textDimData = nil;
1676         if (count%2 == 0) {
1677             for (i = count-2; i >= 0; i -= 2) {
1678                 int msgid = [[inputQueue objectAtIndex:i] intValue];
1679                 if (SetTextDimensionsMsgID == msgid) {
1680                     textDimData = [[inputQueue objectAtIndex:i+1] retain];
1681                     break;
1682                 }
1683             }
1684         }
1686         [inputQueue removeAllObjects];
1688         if (textDimData) {
1689             [inputQueue addObject:
1690                     [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1691             [inputQueue addObject:textDimData];
1692             [textDimData release];
1693         }
1694     }
1697 - (void)insertVimStateMessage
1699     // NOTE: This is the place to add Vim state that needs to be accessed from
1700     // MacVim.  Do not add state that could potentially require lots of memory
1701     // since this message gets sent each time the output queue is forcibly
1702     // flushed (e.g. storing the currently selected text would be a bad idea).
1703     // We take this approach of "pushing" the state to MacVim to avoid having
1704     // to make synchronous calls from MacVim to Vim in order to get state.
1706     BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1707     int numTabs = tabpage_index(NULL) - 1;
1708     if (numTabs < 0)
1709         numTabs = 0;
1711     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1712         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1713         [NSNumber numberWithInt:p_mh], @"p_mh",
1714         [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1715         [NSNumber numberWithBool:mmta], @"p_mmta",
1716         [NSNumber numberWithInt:numTabs], @"numTabs",
1717         nil];
1719     // Put the state before all other messages.
1720     int msgid = SetVimStateMsgID;
1721     [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1722     [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1723                       atIndex:0];
1726 - (void)processInputQueue
1728     if ([inputQueue count] == 0) return;
1730     // NOTE: One of the input events may cause this method to be called
1731     // recursively, so copy the input queue to a local variable and clear the
1732     // queue before starting to process input events (otherwise we could get
1733     // stuck in an endless loop).
1734     NSArray *q = [inputQueue copy];
1735     unsigned i, count = [q count];
1737     [inputQueue removeAllObjects];
1739     for (i = 1; i < count; i+=2) {
1740         int msgid = [[q objectAtIndex:i-1] intValue];
1741         id data = [q objectAtIndex:i];
1742         if ([data isEqual:[NSNull null]])
1743             data = nil;
1745         ASLogDebug(@"(%d) %s", i, MessageStrings[msgid]);
1746         [self handleInputEvent:msgid data:data];
1747     }
1749     [q release];
1753 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1755     if (KeyDownMsgID == msgid) {
1756         if (!data) return;
1757         const void *bytes = [data bytes];
1758         unsigned mods = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1759         unsigned code = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1760         unsigned len  = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1761         NSString *key = [[NSString alloc] initWithBytes:bytes
1762                                                  length:len
1763                                                encoding:NSUTF8StringEncoding];
1764         mods = eventModifierFlagsToVimModMask(mods);
1766         [self doKeyDown:key keyCode:code modifiers:mods];
1767         [key release];
1768     } else if (ScrollWheelMsgID == msgid) {
1769         if (!data) return;
1770         const void *bytes = [data bytes];
1772         int row = *((int*)bytes);  bytes += sizeof(int);
1773         int col = *((int*)bytes);  bytes += sizeof(int);
1774         int flags = *((int*)bytes);  bytes += sizeof(int);
1775         float dy = *((float*)bytes);  bytes += sizeof(float);
1777         int button = MOUSE_5;
1778         if (dy > 0) button = MOUSE_4;
1780         flags = eventModifierFlagsToVimMouseModMask(flags);
1782         int numLines = (int)round(dy);
1783         if (numLines < 0) numLines = -numLines;
1784         if (numLines == 0) numLines = 1;
1786 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1787         gui.scroll_wheel_force = numLines;
1788 #endif
1790         gui_send_mouse_event(button, col, row, NO, flags);
1791     } else if (MouseDownMsgID == msgid) {
1792         if (!data) return;
1793         const void *bytes = [data bytes];
1795         int row = *((int*)bytes);  bytes += sizeof(int);
1796         int col = *((int*)bytes);  bytes += sizeof(int);
1797         int button = *((int*)bytes);  bytes += sizeof(int);
1798         int flags = *((int*)bytes);  bytes += sizeof(int);
1799         int count = *((int*)bytes);  bytes += sizeof(int);
1801         button = eventButtonNumberToVimMouseButton(button);
1802         if (button >= 0) {
1803             flags = eventModifierFlagsToVimMouseModMask(flags);
1804             gui_send_mouse_event(button, col, row, count>1, flags);
1805         }
1806     } else if (MouseUpMsgID == msgid) {
1807         if (!data) return;
1808         const void *bytes = [data bytes];
1810         int row = *((int*)bytes);  bytes += sizeof(int);
1811         int col = *((int*)bytes);  bytes += sizeof(int);
1812         int flags = *((int*)bytes);  bytes += sizeof(int);
1814         flags = eventModifierFlagsToVimMouseModMask(flags);
1816         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1817     } else if (MouseDraggedMsgID == msgid) {
1818         if (!data) return;
1819         const void *bytes = [data bytes];
1821         int row = *((int*)bytes);  bytes += sizeof(int);
1822         int col = *((int*)bytes);  bytes += sizeof(int);
1823         int flags = *((int*)bytes);  bytes += sizeof(int);
1825         flags = eventModifierFlagsToVimMouseModMask(flags);
1827         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1828     } else if (MouseMovedMsgID == msgid) {
1829         const void *bytes = [data bytes];
1830         int row = *((int*)bytes);  bytes += sizeof(int);
1831         int col = *((int*)bytes);  bytes += sizeof(int);
1833         gui_mouse_moved(col, row);
1834     } else if (AddInputMsgID == msgid) {
1835         NSString *string = [[NSString alloc] initWithData:data
1836                 encoding:NSUTF8StringEncoding];
1837         if (string) {
1838             [self addInput:string];
1839             [string release];
1840         }
1841     } else if (SelectTabMsgID == msgid) {
1842         if (!data) return;
1843         const void *bytes = [data bytes];
1844         int idx = *((int*)bytes) + 1;
1845         send_tabline_event(idx);
1846     } else if (CloseTabMsgID == msgid) {
1847         if (!data) return;
1848         const void *bytes = [data bytes];
1849         int idx = *((int*)bytes) + 1;
1850         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1851     } else if (AddNewTabMsgID == msgid) {
1852         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1853     } else if (DraggedTabMsgID == msgid) {
1854         if (!data) return;
1855         const void *bytes = [data bytes];
1856         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1857         // based.
1858         int idx = *((int*)bytes);
1860         tabpage_move(idx);
1861     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1862             || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1863         if (!data) return;
1864         const void *bytes = [data bytes];
1865         int rows = Rows;
1866         if (SetTextColumnsMsgID != msgid) {
1867             rows = *((int*)bytes);  bytes += sizeof(int);
1868         }
1869         int cols = Columns;
1870         if (SetTextRowsMsgID != msgid) {
1871             cols = *((int*)bytes);  bytes += sizeof(int);
1872         }
1874         NSData *d = data;
1875         if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1876             int dim[2] = { rows, cols };
1877             d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1878             msgid = SetTextDimensionsReplyMsgID;
1879         }
1881         if (SetTextDimensionsMsgID == msgid)
1882             msgid = SetTextDimensionsReplyMsgID;
1884         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1885         // gui_resize_shell(), so we have to manually set the rows and columns
1886         // here since MacVim doesn't change the rows and columns to avoid
1887         // inconsistent states between Vim and MacVim.  The message sent back
1888         // indicates that it is a reply to a message that originated in MacVim
1889         // since we need to be able to determine where a message originated.
1890         [self queueMessage:msgid data:d];
1892         gui_resize_shell(cols, rows);
1893     } else if (ExecuteMenuMsgID == msgid) {
1894         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1895         if (attrs) {
1896             NSArray *desc = [attrs objectForKey:@"descriptor"];
1897             vimmenu_T *menu = menu_for_descriptor(desc);
1898             if (menu)
1899                 gui_menu_cb(menu);
1900         }
1901     } else if (ToggleToolbarMsgID == msgid) {
1902         [self handleToggleToolbar];
1903     } else if (ScrollbarEventMsgID == msgid) {
1904         [self handleScrollbarEvent:data];
1905     } else if (SetFontMsgID == msgid) {
1906         [self handleSetFont:data];
1907     } else if (VimShouldCloseMsgID == msgid) {
1908         gui_shell_closed();
1909     } else if (DropFilesMsgID == msgid) {
1910         [self handleDropFiles:data];
1911     } else if (DropStringMsgID == msgid) {
1912         [self handleDropString:data];
1913     } else if (GotFocusMsgID == msgid) {
1914         if (!gui.in_focus)
1915             [self focusChange:YES];
1916     } else if (LostFocusMsgID == msgid) {
1917         if (gui.in_focus)
1918             [self focusChange:NO];
1919     } else if (SetMouseShapeMsgID == msgid) {
1920         const void *bytes = [data bytes];
1921         int shape = *((int*)bytes);  bytes += sizeof(int);
1922         update_mouseshape(shape);
1923     } else if (XcodeModMsgID == msgid) {
1924         [self handleXcodeMod:data];
1925     } else if (OpenWithArgumentsMsgID == msgid) {
1926         [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1927     } else if (FindReplaceMsgID == msgid) {
1928         [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1929     } else if (ActivatedImMsgID == msgid) {
1930         [self setImState:YES];
1931     } else if (DeactivatedImMsgID == msgid) {
1932         [self setImState:NO];
1933     } else if (NetBeansMsgID == msgid) {
1934 #ifdef FEAT_NETBEANS_INTG
1935         messageFromNetbeansMacVim();
1936 #endif
1937     } else if (SetMarkedTextMsgID == msgid) {
1938         [self handleMarkedText:data];
1939     } else {
1940         ASLogWarn(@"Unknown message received (msgid=%d)", msgid);
1941     }
1944 - (void)doKeyDown:(NSString *)key
1945           keyCode:(unsigned)code
1946         modifiers:(int)mods
1948     ASLogDebug(@"key='%@' code=%#x mods=%#x length=%d", key, code, mods,
1949             [key length]);
1950     if (!key) return;
1952     char_u *str = (char_u*)[key UTF8String];
1953     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1955     if ([self handleSpecialKey:key keyCode:code modifiers:mods])
1956         return;
1958 #ifdef FEAT_MBYTE
1959     char_u *conv_str = NULL;
1960     if (input_conv.vc_type != CONV_NONE) {
1961         conv_str = string_convert(&input_conv, str, &len);
1962         if (conv_str)
1963             str = conv_str;
1964     }
1965 #endif
1967     if (mods & MOD_MASK_CMD) {
1968         // NOTE: For normal input (non-special, 'macmeta' off) the modifier
1969         // flags are already included in the key event.  However, the Cmd key
1970         // flag is special and must always be added manually.
1971         // The Shift flag is already included in the key when the Command
1972         // key is held.  The same goes for Alt, unless Ctrl is held or
1973         // 'macmeta' is set.  It is important that these flags are cleared
1974         // _after_ special keys have been handled, since they should never be
1975         // cleared for special keys.
1976         mods &= ~MOD_MASK_SHIFT;
1977         if (!(mods & MOD_MASK_CTRL)) {
1978             BOOL mmta = curbuf ? curbuf->b_p_mmta : YES;
1979             if (!mmta)
1980                 mods &= ~MOD_MASK_ALT;
1981         }
1983         ASLogDebug(@"add mods=%#x", mods);
1984         char_u modChars[3] = { CSI, KS_MODIFIER, mods };
1985         add_to_input_buf(modChars, 3);
1986     } else if (mods & MOD_MASK_ALT && 1 == len && str[0] < 0x80
1987             && curbuf && curbuf->b_p_mmta) {
1988         // HACK! The 'macmeta' is set so we have to handle Alt key presses
1989         // separately.  Normally Alt key presses are interpreted by the
1990         // frontend but now we have to manually set the 8th bit and deal with
1991         // UTF-8 conversion.
1992         if ([self handleMacMetaKey:str[0] modifiers:mods])
1993             return;
1994     }
1997     for (i = 0; i < len; ++i) {
1998         ASLogDebug(@"add byte [%d/%d]: %#x", i, len, str[i]);
1999         add_to_input_buf(str+i, 1);
2000         if (CSI == str[i]) {
2001             // NOTE: If the converted string contains the byte CSI, then it
2002             // must be followed by the bytes KS_EXTRA, KE_CSI or things
2003             // won't work.
2004             static char_u extra[2] = { KS_EXTRA, KE_CSI };
2005             ASLogDebug(@"add KS_EXTRA, KE_CSI");
2006             add_to_input_buf(extra, 2);
2007         }
2008     }
2010 #ifdef FEAT_MBYTE
2011     if (conv_str)
2012         vim_free(conv_str);
2013 #endif
2016 - (BOOL)handleSpecialKey:(NSString *)key
2017                  keyCode:(unsigned)code
2018                modifiers:(int)mods
2020     int i;
2021     for (i = 0; special_keys[i].key_sym != 0; i++) {
2022         if (special_keys[i].key_sym == code) {
2023             ASLogDebug(@"Special key: %#x", code);
2024             break;
2025         }
2026     }
2027     if (special_keys[i].key_sym == 0)
2028         return NO;
2030     int ikey = special_keys[i].vim_code1 == NUL ? special_keys[i].vim_code0 :
2031             TO_SPECIAL(special_keys[i].vim_code0, special_keys[i].vim_code1);
2032     ikey = simplify_key(ikey, &mods);
2033     if (ikey == CSI)
2034         ikey = K_CSI;
2036     char_u chars[4];
2037     int len = 0;
2039     if (IS_SPECIAL(ikey)) {
2040         chars[0] = CSI;
2041         chars[1] = K_SECOND(ikey);
2042         chars[2] = K_THIRD(ikey);
2043         len = 3;
2044     } else if (mods & MOD_MASK_ALT && special_keys[i].vim_code1 == 0
2045 #ifdef FEAT_MBYTE
2046             && !enc_dbcs    // TODO: ?  (taken from gui_gtk_x11.c)
2047 #endif
2048             ) {
2049         ASLogDebug(@"Alt special=%d", ikey);
2051         // NOTE: The last entries in the special_keys struct when pressed
2052         // together with Alt need to be handled separately or they will not
2053         // work.
2054         // The following code was gleaned from gui_gtk_x11.c.
2055         mods &= ~MOD_MASK_ALT;
2056         int mkey = 0x80 | ikey;
2057 #ifdef FEAT_MBYTE
2058         if (enc_utf8) {  // TODO: What about other encodings?
2059             // Convert to utf-8
2060             chars[0] = (mkey >> 6) + 0xc0;
2061             chars[1] = mkey & 0xbf;
2062             if (chars[1] == CSI) {
2063                 // We end up here when ikey == ESC
2064                 chars[2] = KS_EXTRA;
2065                 chars[3] = KE_CSI;
2066                 len = 4;
2067             } else {
2068                 len = 2;
2069             }
2070         } else
2071 #endif
2072         {
2073             chars[0] = mkey;
2074             len = 1;
2075         }
2076     } else {
2077         ASLogDebug(@"Just ikey=%d", ikey);
2078         chars[0] = ikey;
2079         len = 1;
2080     }
2082     if (len > 0) {
2083         if (mods) {
2084             ASLogDebug(@"Adding mods to special: %d", mods);
2085             char_u modChars[3] = { CSI, KS_MODIFIER, (char_u)mods };
2086             add_to_input_buf(modChars, 3);
2087         }
2089         ASLogDebug(@"Adding special (%d): %x,%x,%x", len,
2090                 chars[0], chars[1], chars[2]);
2091         add_to_input_buf(chars, len);
2092     }
2094     return YES;
2097 - (BOOL)handleMacMetaKey:(int)ikey modifiers:(int)mods
2099     ASLogDebug(@"ikey=%d mods=%d", ikey, mods);
2101     // This code was taken from gui_w48.c and gui_gtk_x11.c.
2102     char_u string[7];
2103     int ch = simplify_key(ikey, &mods);
2105     // Remove the SHIFT modifier for keys where it's already included,
2106     // e.g., '(' and '*'
2107     if (ch < 0x100 && !isalpha(ch) && isprint(ch))
2108         mods &= ~MOD_MASK_SHIFT;
2110     // Interpret the ALT key as making the key META, include SHIFT, etc.
2111     ch = extract_modifiers(ch, &mods);
2112     if (ch == CSI)
2113         ch = K_CSI;
2115     int len = 0;
2116     if (mods) {
2117         string[len++] = CSI;
2118         string[len++] = KS_MODIFIER;
2119         string[len++] = mods;
2120     }
2122     string[len++] = ch;
2123 #ifdef FEAT_MBYTE
2124     // TODO: What if 'enc' is not "utf-8"?
2125     if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
2126         string[len++] = ch & 0xbf;
2127         string[len-2] = ((unsigned)ch >> 6) + 0xc0;
2128         if (string[len-1] == CSI) {
2129             string[len++] = KS_EXTRA;
2130             string[len++] = (int)KE_CSI;
2131         }
2132     }
2133 #endif
2135     add_to_input_buf(string, len);
2136     return YES;
2139 - (void)queueMessage:(int)msgid data:(NSData *)data
2141     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
2142     if (data)
2143         [outputQueue addObject:data];
2144     else
2145         [outputQueue addObject:[NSData data]];
2148 - (void)connectionDidDie:(NSNotification *)notification
2150     // If the main connection to MacVim is lost this means that either MacVim
2151     // has crashed or this process did not receive its termination message
2152     // properly (e.g. if the TerminateNowMsgID was dropped).
2153     //
2154     // NOTE: This is not called if a Vim controller invalidates its connection.
2156     ASLogNotice(@"Main connection was lost before process had a chance "
2157                 "to terminate; preserving swap files.");
2158     getout_preserve_modified(1);
2161 - (void)blinkTimerFired:(NSTimer *)timer
2163     NSTimeInterval timeInterval = 0;
2165     [blinkTimer release];
2166     blinkTimer = nil;
2168     if (MMBlinkStateOn == blinkState) {
2169         gui_undraw_cursor();
2170         blinkState = MMBlinkStateOff;
2171         timeInterval = blinkOffInterval;
2172     } else if (MMBlinkStateOff == blinkState) {
2173         gui_update_cursor(TRUE, FALSE);
2174         blinkState = MMBlinkStateOn;
2175         timeInterval = blinkOnInterval;
2176     }
2178     if (timeInterval > 0) {
2179         blinkTimer = 
2180             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2181                                             selector:@selector(blinkTimerFired:)
2182                                             userInfo:nil repeats:NO] retain];
2183         [self flushQueue:YES];
2184     }
2187 - (void)focusChange:(BOOL)on
2189     gui_focus_change(on);
2192 - (void)handleToggleToolbar
2194     // If 'go' contains 'T', then remove it, else add it.
2196     char_u go[sizeof(GO_ALL)+2];
2197     char_u *p;
2198     int len;
2200     STRCPY(go, p_go);
2201     p = vim_strchr(go, GO_TOOLBAR);
2202     len = STRLEN(go);
2204     if (p != NULL) {
2205         char_u *end = go + len;
2206         while (p < end) {
2207             p[0] = p[1];
2208             ++p;
2209         }
2210     } else {
2211         go[len] = GO_TOOLBAR;
2212         go[len+1] = NUL;
2213     }
2215     set_option_value((char_u*)"guioptions", 0, go, 0);
2217     [self redrawScreen];
2220 - (void)handleScrollbarEvent:(NSData *)data
2222     if (!data) return;
2224     const void *bytes = [data bytes];
2225     int32_t ident = *((int32_t*)bytes);  bytes += sizeof(int32_t);
2226     int hitPart = *((int*)bytes);  bytes += sizeof(int);
2227     float fval = *((float*)bytes);  bytes += sizeof(float);
2228     scrollbar_T *sb = gui_find_scrollbar(ident);
2230     if (sb) {
2231         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2232         long value = sb_info->value;
2233         long size = sb_info->size;
2234         long max = sb_info->max;
2235         BOOL isStillDragging = NO;
2236         BOOL updateKnob = YES;
2238         switch (hitPart) {
2239         case NSScrollerDecrementPage:
2240             value -= (size > 2 ? size - 2 : 1);
2241             break;
2242         case NSScrollerIncrementPage:
2243             value += (size > 2 ? size - 2 : 1);
2244             break;
2245         case NSScrollerDecrementLine:
2246             --value;
2247             break;
2248         case NSScrollerIncrementLine:
2249             ++value;
2250             break;
2251         case NSScrollerKnob:
2252             isStillDragging = YES;
2253             // fall through ...
2254         case NSScrollerKnobSlot:
2255             value = (long)(fval * (max - size + 1));
2256             // fall through ...
2257         default:
2258             updateKnob = NO;
2259             break;
2260         }
2262         gui_drag_scrollbar(sb, value, isStillDragging);
2264         if (updateKnob) {
2265             // Dragging the knob or option+clicking automatically updates
2266             // the knob position (on the actual NSScroller), so we only
2267             // need to set the knob position in the other cases.
2268             if (sb->wp) {
2269                 // Update both the left&right vertical scrollbars.
2270                 int32_t idL = (int32_t)sb->wp->w_scrollbars[SBAR_LEFT].ident;
2271                 int32_t idR = (int32_t)sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2272                 [self setScrollbarThumbValue:value size:size max:max
2273                                   identifier:idL];
2274                 [self setScrollbarThumbValue:value size:size max:max
2275                                   identifier:idR];
2276             } else {
2277                 // Update the horizontal scrollbar.
2278                 [self setScrollbarThumbValue:value size:size max:max
2279                                   identifier:ident];
2280             }
2281         }
2282     }
2285 - (void)handleSetFont:(NSData *)data
2287     if (!data) return;
2289     const void *bytes = [data bytes];
2290     int pointSize = (int)*((float*)bytes);  bytes += sizeof(float);
2292     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2293     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2294     bytes += len;
2296     [name appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2297     char_u *s = (char_u*)[name UTF8String];
2299     unsigned wlen = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2300     char_u *ws = NULL;
2301     if (wlen > 0) {
2302         NSMutableString *wname = [NSMutableString stringWithUTF8String:bytes];
2303         bytes += wlen;
2305         [wname appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2306         ws = (char_u*)[wname UTF8String];
2307     }
2309 #ifdef FEAT_MBYTE
2310     s = CONVERT_FROM_UTF8(s);
2311     if (ws) {
2312         ws = CONVERT_FROM_UTF8(ws);
2313     }
2314 #endif
2316     set_option_value((char_u*)"guifont", 0, s, 0);
2318     if (ws && gui.wide_font != NOFONT) {
2319         // NOTE: This message is sent on Cmd-+/Cmd-- and as such should only
2320         // change the wide font if 'gfw' is non-empty (the frontend always has
2321         // some wide font set, even if 'gfw' is empty).
2322         set_option_value((char_u*)"guifontwide", 0, ws, 0);
2323     }
2325 #ifdef FEAT_MBYTE
2326     if (ws) {
2327         CONVERT_FROM_UTF8_FREE(ws);
2328     }
2329     CONVERT_FROM_UTF8_FREE(s);
2330 #endif
2332     [self redrawScreen];
2335 - (void)handleDropFiles:(NSData *)data
2337     // TODO: Get rid of this method; instead use Vim script directly.  At the
2338     // moment I know how to do this to open files in tabs, but I'm not sure how
2339     // to add the filenames to the command line when in command line mode.
2341     if (!data) return;
2343     NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2344     if (!args) return;
2346     id obj = [args objectForKey:@"forceOpen"];
2347     BOOL forceOpen = YES;
2348     if (obj)
2349         forceOpen = [obj boolValue];
2351     NSArray *filenames = [args objectForKey:@"filenames"];
2352     if (!(filenames && [filenames count] > 0)) return;
2354 #ifdef FEAT_DND
2355     if (!forceOpen && (State & CMDLINE)) {
2356         // HACK!  If Vim is in command line mode then the files names
2357         // should be added to the command line, instead of opening the
2358         // files in tabs (unless forceOpen is set).  This is taken care of by
2359         // gui_handle_drop().
2360         int n = [filenames count];
2361         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2362         if (fnames) {
2363             int i = 0;
2364             for (i = 0; i < n; ++i)
2365                 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2367             // NOTE!  This function will free 'fnames'.
2368             // HACK!  It is assumed that the 'x' and 'y' arguments are
2369             // unused when in command line mode.
2370             gui_handle_drop(0, 0, 0, fnames, n);
2371         }
2372     } else
2373 #endif // FEAT_DND
2374     {
2375         [self handleOpenWithArguments:args];
2376     }
2379 - (void)handleDropString:(NSData *)data
2381     if (!data) return;
2383 #ifdef FEAT_DND
2384     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2385     const void *bytes = [data bytes];
2386     int len = *((int*)bytes);  bytes += sizeof(int);
2387     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2389     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2390     NSRange range = { 0, [string length] };
2391     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2392                                          withString:@"\x0a" options:0
2393                                               range:range];
2394     if (0 == n) {
2395         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2396                                        options:0 range:range];
2397     }
2399     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2400     char_u *s = (char_u*)[string UTF8String];
2401 #ifdef FEAT_MBYTE
2402     if (input_conv.vc_type != CONV_NONE)
2403         s = string_convert(&input_conv, s, &len);
2404 #endif
2405     dnd_yank_drag_data(s, len);
2406 #ifdef FEAT_MBYTE
2407     if (input_conv.vc_type != CONV_NONE)
2408         vim_free(s);
2409 #endif
2410     add_to_input_buf(dropkey, sizeof(dropkey));
2411 #endif // FEAT_DND
2414 - (void)startOdbEditWithArguments:(NSDictionary *)args
2416 #ifdef FEAT_ODB_EDITOR
2417     id obj = [args objectForKey:@"remoteID"];
2418     if (!obj) return;
2420     OSType serverID = [obj unsignedIntValue];
2421     NSString *remotePath = [args objectForKey:@"remotePath"];
2423     NSAppleEventDescriptor *token = nil;
2424     NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2425     obj = [args objectForKey:@"remoteTokenDescType"];
2426     if (tokenData && obj) {
2427         DescType tokenType = [obj unsignedLongValue];
2428         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2429                                                                 data:tokenData];
2430     }
2432     NSArray *filenames = [args objectForKey:@"filenames"];
2433     unsigned i, numFiles = [filenames count];
2434     for (i = 0; i < numFiles; ++i) {
2435         NSString *filename = [filenames objectAtIndex:i];
2436         char_u *s = [filename vimStringSave];
2437         buf_T *buf = buflist_findname(s);
2438         vim_free(s);
2440         if (buf) {
2441             if (buf->b_odb_token) {
2442                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2443                 buf->b_odb_token = NULL;
2444             }
2446             if (buf->b_odb_fname) {
2447                 vim_free(buf->b_odb_fname);
2448                 buf->b_odb_fname = NULL;
2449             }
2451             buf->b_odb_server_id = serverID;
2453             if (token)
2454                 buf->b_odb_token = [token retain];
2455             if (remotePath)
2456                 buf->b_odb_fname = [remotePath vimStringSave];
2457         } else {
2458             ASLogWarn(@"Could not find buffer '%@' for ODB editing.", filename);
2459         }
2460     }
2461 #endif // FEAT_ODB_EDITOR
2464 - (void)handleXcodeMod:(NSData *)data
2466 #if 0
2467     const void *bytes = [data bytes];
2468     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2469     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2470     if (0 == len)
2471         return;
2473     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2474             descriptorWithDescriptorType:type
2475                                    bytes:bytes
2476                                   length:len];
2477 #endif
2480 - (void)handleOpenWithArguments:(NSDictionary *)args
2482     // ARGUMENT:                DESCRIPTION:
2483     // -------------------------------------------------------------
2484     // filenames                list of filenames
2485     // dontOpen                 don't open files specified in above argument
2486     // layout                   which layout to use to open files
2487     // selectionRange           range of lines to select
2488     // searchText               string to search for
2489     // cursorLine               line to position the cursor on
2490     // cursorColumn             column to position the cursor on
2491     //                          (only valid when "cursorLine" is set)
2492     // remoteID                 ODB parameter
2493     // remotePath               ODB parameter
2494     // remoteTokenDescType      ODB parameter
2495     // remoteTokenData          ODB parameter
2497     ASLogDebug(@"args=%@ (starting=%d)", args, starting);
2499     NSArray *filenames = [args objectForKey:@"filenames"];
2500     int i, numFiles = filenames ? [filenames count] : 0;
2501     BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2502     int layout = [[args objectForKey:@"layout"] intValue];
2504     // Change to directory of first file to open if this is an "unused" editor
2505     // (but do not do this if editing remotely).
2506     if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2507             && (starting || [self unusedEditor]) ) {
2508         char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2509         if (mch_isdir(s)) {
2510             mch_chdir((char*)s);
2511         } else {
2512             vim_chdirfile(s);
2513         }
2514         vim_free(s);
2515     }
2517     if (starting > 0) {
2518         // When Vim is starting we simply add the files to be opened to the
2519         // global arglist and Vim will take care of opening them for us.
2520         if (openFiles && numFiles > 0) {
2521             for (i = 0; i < numFiles; i++) {
2522                 NSString *fname = [filenames objectAtIndex:i];
2523                 char_u *p = NULL;
2525                 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2526                         || (p = [fname vimStringSave]) == NULL)
2527                     exit(2); // See comment in -[MMBackend exit]
2528                 else
2529                     alist_add(&global_alist, p, 2);
2530             }
2532             // Vim will take care of arranging the files added to the arglist
2533             // in windows or tabs; all we must do is to specify which layout to
2534             // use.
2535             initialWindowLayout = layout;
2536         }
2537     } else {
2538         // When Vim is already open we resort to some trickery to open the
2539         // files with the specified layout.
2540         //
2541         // TODO: Figure out a better way to handle this?
2542         if (openFiles && numFiles > 0) {
2543             BOOL oneWindowInTab = topframe ? YES
2544                                            : (topframe->fr_layout == FR_LEAF);
2545             BOOL bufChanged = NO;
2546             BOOL bufHasFilename = NO;
2547             if (curbuf) {
2548                 bufChanged = curbufIsChanged();
2549                 bufHasFilename = curbuf->b_ffname != NULL;
2550             }
2552             // Temporarily disable flushing since the following code may
2553             // potentially cause multiple redraws.
2554             flushDisabled = YES;
2556             BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2557             if (WIN_TABS == layout && !onlyOneTab) {
2558                 // By going to the last tabpage we ensure that the new tabs
2559                 // will appear last (if this call is left out, the taborder
2560                 // becomes messy).
2561                 goto_tabpage(9999);
2562             }
2564             // Make sure we're in normal mode first.
2565             [self addInput:@"<C-\\><C-N>"];
2567             if (numFiles > 1) {
2568                 // With "split layout" we open a new tab before opening
2569                 // multiple files if the current tab has more than one window
2570                 // or if there is exactly one window but whose buffer has a
2571                 // filename.  (The :drop command ensures modified buffers get
2572                 // their own window.)
2573                 if ((WIN_HOR == layout || WIN_VER == layout) &&
2574                         (!oneWindowInTab || bufHasFilename))
2575                     [self addInput:@":tabnew<CR>"];
2577                 // The files are opened by constructing a ":drop ..." command
2578                 // and executing it.
2579                 NSMutableString *cmd = (WIN_TABS == layout)
2580                         ? [NSMutableString stringWithString:@":tab drop"]
2581                         : [NSMutableString stringWithString:@":drop"];
2583                 for (i = 0; i < numFiles; ++i) {
2584                     NSString *file = [filenames objectAtIndex:i];
2585                     file = [file stringByEscapingSpecialFilenameCharacters];
2586                     [cmd appendString:@" "];
2587                     [cmd appendString:file];
2588                 }
2590                 // Temporarily clear 'suffixes' so that the files are opened in
2591                 // the same order as they appear in the "filenames" array.
2592                 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2594                 [self addInput:cmd];
2596                 // Split the view into multiple windows if requested.
2597                 if (WIN_HOR == layout)
2598                     [self addInput:@"|sall"];
2599                 else if (WIN_VER == layout)
2600                     [self addInput:@"|vert sall"];
2602                 // Restore the old value of 'suffixes'.
2603                 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2604             } else {
2605                 // When opening one file we try to reuse the current window,
2606                 // but not if its buffer is modified or has a filename.
2607                 // However, the 'arglist' layout always opens the file in the
2608                 // current window.
2609                 NSString *file = [[filenames lastObject]
2610                         stringByEscapingSpecialFilenameCharacters];
2611                 NSString *cmd;
2612                 if (WIN_HOR == layout) {
2613                     if (!(bufHasFilename || bufChanged))
2614                         cmd = [NSString stringWithFormat:@":e %@", file];
2615                     else
2616                         cmd = [NSString stringWithFormat:@":sp %@", file];
2617                 } else if (WIN_VER == layout) {
2618                     if (!(bufHasFilename || bufChanged))
2619                         cmd = [NSString stringWithFormat:@":e %@", file];
2620                     else
2621                         cmd = [NSString stringWithFormat:@":vsp %@", file];
2622                 } else if (WIN_TABS == layout) {
2623                     if (oneWindowInTab && !(bufHasFilename || bufChanged))
2624                         cmd = [NSString stringWithFormat:@":e %@", file];
2625                     else
2626                         cmd = [NSString stringWithFormat:@":tabe %@", file];
2627                 } else {
2628                     // (The :drop command will split if there is a modified
2629                     // buffer.)
2630                     cmd = [NSString stringWithFormat:@":drop %@", file];
2631                 }
2633                 [self addInput:cmd];
2634                 [self addInput:@"<CR>"];
2635             }
2637             // Force screen redraw (does it have to be this complicated?).
2638             // (This code was taken from the end of gui_handle_drop().)
2639             update_screen(NOT_VALID);
2640             setcursor();
2641             out_flush();
2642             gui_update_cursor(FALSE, FALSE);
2643             maketitle();
2645             flushDisabled = NO;
2646         }
2647     }
2649     if ([args objectForKey:@"remoteID"]) {
2650         // NOTE: We have to delay processing any ODB related arguments since
2651         // the file(s) may not be opened until the input buffer is processed.
2652         [self performSelector:@selector(startOdbEditWithArguments:)
2653                    withObject:args
2654                    afterDelay:0];
2655     }
2657     NSString *lineString = [args objectForKey:@"cursorLine"];
2658     if (lineString && [lineString intValue] > 0) {
2659         NSString *columnString = [args objectForKey:@"cursorColumn"];
2660         if (!(columnString && [columnString intValue] > 0))
2661             columnString = @"1";
2663         NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2664                 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2665         [self addInput:cmd];
2666     }
2668     NSString *rangeString = [args objectForKey:@"selectionRange"];
2669     if (rangeString) {
2670         // Build a command line string that will select the given range of
2671         // lines.  If range.length == 0, then position the cursor on the given
2672         // line but do not select.
2673         NSRange range = NSRangeFromString(rangeString);
2674         NSString *cmd;
2675         if (range.length > 0) {
2676             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2677                     NSMaxRange(range), range.location];
2678         } else {
2679             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2680                     range.location];
2681         }
2683         [self addInput:cmd];
2684     }
2686     NSString *searchText = [args objectForKey:@"searchText"];
2687     if (searchText) {
2688         [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2689                 searchText]];
2690     }
2693 - (BOOL)checkForModifiedBuffers
2695     buf_T *buf;
2696     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2697         if (bufIsChanged(buf)) {
2698             return YES;
2699         }
2700     }
2702     return NO;
2705 - (void)addInput:(NSString *)input
2707     // NOTE: This code is essentially identical to server_to_input_buf(),
2708     // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2709     char_u *string = [input vimStringSave];
2710     if (!string) return;
2712     /* Set 'cpoptions' the way we want it.
2713      *    B set - backslashes are *not* treated specially
2714      *    k set - keycodes are *not* reverse-engineered
2715      *    < unset - <Key> sequences *are* interpreted
2716      *  The last but one parameter of replace_termcodes() is TRUE so that the
2717      *  <lt> sequence is recognised - needed for a real backslash.
2718      */
2719     char_u *ptr = NULL;
2720     char_u *cpo_save = p_cpo;
2721     p_cpo = (char_u *)"Bk";
2722     char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2723     p_cpo = cpo_save;
2725     if (*ptr != NUL)    /* trailing CTRL-V results in nothing */
2726     {
2727         /*
2728          * Add the string to the input stream.
2729          * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2730          *
2731          * First clear typed characters from the typeahead buffer, there could
2732          * be half a mapping there.  Then append to the existing string, so
2733          * that multiple commands from a client are concatenated.
2734          */
2735         if (typebuf.tb_maplen < typebuf.tb_len)
2736             del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2737         (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2739         /* Let input_available() know we inserted text in the typeahead
2740          * buffer. */
2741         typebuf_was_filled = TRUE;
2742     }
2743     vim_free(ptr);
2744     vim_free(string);
2747 - (BOOL)unusedEditor
2749     BOOL oneWindowInTab = topframe ? YES
2750                                    : (topframe->fr_layout == FR_LEAF);
2751     BOOL bufChanged = NO;
2752     BOOL bufHasFilename = NO;
2753     if (curbuf) {
2754         bufChanged = curbufIsChanged();
2755         bufHasFilename = curbuf->b_ffname != NULL;
2756     }
2758     BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2760     return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2763 - (void)redrawScreen
2765     // Force screen redraw (does it have to be this complicated?).
2766     redraw_all_later(CLEAR);
2767     update_screen(NOT_VALID);
2768     setcursor();
2769     out_flush();
2770     gui_update_cursor(FALSE, FALSE);
2772     // HACK! The cursor is not put back at the command line by the above
2773     // "redraw commands".  The following test seems to do the trick though.
2774     if (State & CMDLINE)
2775         redrawcmdline();
2778 - (void)handleFindReplace:(NSDictionary *)args
2780     if (!args) return;
2782     NSString *findString = [args objectForKey:@"find"];
2783     if (!findString) return;
2785     char_u *find = [findString vimStringSave];
2786     char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2787     int flags = [[args objectForKey:@"flags"] intValue];
2789     // NOTE: The flag 0x100 is used to indicate a backward search.
2790     gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2792     vim_free(find);
2793     vim_free(replace);
2797 - (void)handleMarkedText:(NSData *)data
2799     const void *bytes = [data bytes];
2800     unsigned pos = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2801     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2802     char *chars = (char *)bytes;
2804     ASLogDebug(@"pos=%d len=%d chars=%s", pos, len, chars);
2806     if (len == 0) {
2807         im_preedit_end_macvim();
2808     } else {
2809         if (!preedit_get_status())
2810             im_preedit_start_macvim();
2812         im_preedit_changed_macvim(chars, pos);
2813     }
2816 @end // MMBackend (Private)
2821 @implementation MMBackend (ClientServer)
2823 - (NSString *)connectionNameFromServerName:(NSString *)name
2825     NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2827     return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2828         lowercaseString];
2831 - (NSConnection *)connectionForServerName:(NSString *)name
2833     // TODO: Try 'name%d' if 'name' fails.
2834     NSString *connName = [self connectionNameFromServerName:name];
2835     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2837     if (!svrConn) {
2838         svrConn = [NSConnection connectionWithRegisteredName:connName
2839                                                            host:nil];
2840         // Try alternate server...
2841         if (!svrConn && alternateServerName) {
2842             ASLogInfo(@"  trying to connect to alternate server: %@",
2843                       alternateServerName);
2844             connName = [self connectionNameFromServerName:alternateServerName];
2845             svrConn = [NSConnection connectionWithRegisteredName:connName
2846                                                             host:nil];
2847         }
2849         // Try looking for alternate servers...
2850         if (!svrConn) {
2851             ASLogInfo(@"  looking for alternate servers...");
2852             NSString *alt = [self alternateServerNameForName:name];
2853             if (alt != alternateServerName) {
2854                 ASLogInfo(@"  found alternate server: %@", alt);
2855                 [alternateServerName release];
2856                 alternateServerName = [alt copy];
2857             }
2858         }
2860         // Try alternate server again...
2861         if (!svrConn && alternateServerName) {
2862             ASLogInfo(@"  trying to connect to alternate server: %@",
2863                       alternateServerName);
2864             connName = [self connectionNameFromServerName:alternateServerName];
2865             svrConn = [NSConnection connectionWithRegisteredName:connName
2866                                                             host:nil];
2867         }
2869         if (svrConn) {
2870             [connectionNameDict setObject:svrConn forKey:connName];
2872             ASLogDebug(@"Adding %@ as connection observer for %@",
2873                        self, svrConn);
2874             [[NSNotificationCenter defaultCenter] addObserver:self
2875                     selector:@selector(serverConnectionDidDie:)
2876                         name:NSConnectionDidDieNotification object:svrConn];
2877         }
2878     }
2880     return svrConn;
2883 - (NSConnection *)connectionForServerPort:(int)port
2885     NSConnection *conn;
2886     NSEnumerator *e = [connectionNameDict objectEnumerator];
2888     while ((conn = [e nextObject])) {
2889         // HACK! Assume connection uses mach ports.
2890         if (port == [(NSMachPort*)[conn sendPort] machPort])
2891             return conn;
2892     }
2894     return nil;
2897 - (void)serverConnectionDidDie:(NSNotification *)notification
2899     ASLogDebug(@"notification=%@", notification);
2901     NSConnection *svrConn = [notification object];
2903     ASLogDebug(@"Removing %@ as connection observer from %@", self, svrConn);
2904     [[NSNotificationCenter defaultCenter]
2905             removeObserver:self
2906                       name:NSConnectionDidDieNotification
2907                     object:svrConn];
2909     [connectionNameDict removeObjectsForKeys:
2910         [connectionNameDict allKeysForObject:svrConn]];
2912     // HACK! Assume connection uses mach ports.
2913     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2914     NSNumber *key = [NSNumber numberWithInt:port];
2916     [clientProxyDict removeObjectForKey:key];
2917     [serverReplyDict removeObjectForKey:key];
2920 - (void)addClient:(NSDistantObject *)client
2922     NSConnection *conn = [client connectionForProxy];
2923     // HACK! Assume connection uses mach ports.
2924     int port = [(NSMachPort*)[conn sendPort] machPort];
2925     NSNumber *key = [NSNumber numberWithInt:port];
2927     if (![clientProxyDict objectForKey:key]) {
2928         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2929         [clientProxyDict setObject:client forKey:key];
2930     }
2932     // NOTE: 'clientWindow' is a global variable which is used by <client>
2933     clientWindow = port;
2936 - (NSString *)alternateServerNameForName:(NSString *)name
2938     if (!(name && [name length] > 0))
2939         return nil;
2941     // Only look for alternates if 'name' doesn't end in a digit.
2942     unichar lastChar = [name characterAtIndex:[name length]-1];
2943     if (lastChar >= '0' && lastChar <= '9')
2944         return nil;
2946     // Look for alternates among all current servers.
2947     NSArray *list = [self serverList];
2948     if (!(list && [list count] > 0))
2949         return nil;
2951     // Filter out servers starting with 'name' and ending with a number. The
2952     // (?i) pattern ensures that the match is case insensitive.
2953     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2954     NSPredicate *pred = [NSPredicate predicateWithFormat:
2955             @"SELF MATCHES %@", pat];
2956     list = [list filteredArrayUsingPredicate:pred];
2957     if ([list count] > 0) {
2958         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2959         return [list objectAtIndex:0];
2960     }
2962     return nil;
2965 @end // MMBackend (ClientServer)
2970 @implementation NSString (MMServerNameCompare)
2971 - (NSComparisonResult)serverNameCompare:(NSString *)string
2973     return [self compare:string
2974                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2976 @end
2981 static int eventModifierFlagsToVimModMask(int modifierFlags)
2983     int modMask = 0;
2985     if (modifierFlags & NSShiftKeyMask)
2986         modMask |= MOD_MASK_SHIFT;
2987     if (modifierFlags & NSControlKeyMask)
2988         modMask |= MOD_MASK_CTRL;
2989     if (modifierFlags & NSAlternateKeyMask)
2990         modMask |= MOD_MASK_ALT;
2991     if (modifierFlags & NSCommandKeyMask)
2992         modMask |= MOD_MASK_CMD;
2994     return modMask;
2997 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2999     int modMask = 0;
3001     if (modifierFlags & NSShiftKeyMask)
3002         modMask |= MOUSE_SHIFT;
3003     if (modifierFlags & NSControlKeyMask)
3004         modMask |= MOUSE_CTRL;
3005     if (modifierFlags & NSAlternateKeyMask)
3006         modMask |= MOUSE_ALT;
3008     return modMask;
3011 static int eventButtonNumberToVimMouseButton(int buttonNumber)
3013     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
3015     return (buttonNumber >= 0 && buttonNumber < 3)
3016             ? mouseButton[buttonNumber] : -1;
3021 // This function is modeled after the VimToPython function found in if_python.c
3022 // NB This does a deep copy by value, it does not lookup references like the
3023 // VimToPython function does.  This is because I didn't want to deal with the
3024 // retain cycles that this would create, and we can cover 99% of the use cases
3025 // by ignoring it.  If we ever switch to using GC in MacVim then this
3026 // functionality can be implemented easily.
3027 static id vimToCocoa(typval_T * tv, int depth)
3029     id result = nil;
3030     id newObj = nil;
3033     // Avoid infinite recursion
3034     if (depth > 100) {
3035         return nil;
3036     }
3038     if (tv->v_type == VAR_STRING) {
3039         char_u * val = tv->vval.v_string;
3040         // val can be NULL if the string is empty
3041         if (!val) {
3042             result = [NSString string];
3043         } else {
3044 #ifdef FEAT_MBYTE
3045             val = CONVERT_TO_UTF8(val);
3046 #endif
3047             result = [NSString stringWithUTF8String:(char*)val];
3048 #ifdef FEAT_MBYTE
3049             CONVERT_TO_UTF8_FREE(val);
3050 #endif
3051         }
3052     } else if (tv->v_type == VAR_NUMBER) {
3053         // looks like sizeof(varnumber_T) is always <= sizeof(long)
3054         result = [NSNumber numberWithLong:(long)tv->vval.v_number];
3055     } else if (tv->v_type == VAR_LIST) {
3056         list_T * list = tv->vval.v_list;
3057         listitem_T * curr;
3059         NSMutableArray * arr = result = [NSMutableArray array];
3061         if (list != NULL) {
3062             for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
3063                 newObj = vimToCocoa(&curr->li_tv, depth + 1);
3064                 [arr addObject:newObj];
3065             }
3066         }
3067     } else if (tv->v_type == VAR_DICT) {
3068         NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
3070         if (tv->vval.v_dict != NULL) {
3071             hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
3072             int todo = ht->ht_used;
3073             hashitem_T * hi;
3074             dictitem_T * di;
3076             for (hi = ht->ht_array; todo > 0; ++hi) {
3077                 if (!HASHITEM_EMPTY(hi)) {
3078                     --todo;
3080                     di = dict_lookup(hi);
3081                     newObj = vimToCocoa(&di->di_tv, depth + 1);
3083                     char_u * keyval = hi->hi_key;
3084 #ifdef FEAT_MBYTE
3085                     keyval = CONVERT_TO_UTF8(keyval);
3086 #endif
3087                     NSString * key = [NSString stringWithUTF8String:(char*)keyval];
3088 #ifdef FEAT_MBYTE
3089                     CONVERT_TO_UTF8_FREE(keyval);
3090 #endif
3091                     [dict setObject:newObj forKey:key];
3092                 }
3093             }
3094         }
3095     } else { // only func refs should fall into this category?
3096         result = nil;
3097     }
3099     return result;
3103 // This function is modeled after eval_client_expr_to_string found in main.c
3104 // Returns nil if there was an error evaluating the expression, and writes a
3105 // message to errorStr.
3106 // TODO Get the error that occurred while evaluating the expression in vim
3107 // somehow.
3108 static id evalExprCocoa(NSString * expr, NSString ** errstr)
3111     char_u *s = (char_u*)[expr UTF8String];
3113 #ifdef FEAT_MBYTE
3114     s = CONVERT_FROM_UTF8(s);
3115 #endif
3117     int save_dbl = debug_break_level;
3118     int save_ro = redir_off;
3120     debug_break_level = -1;
3121     redir_off = 0;
3122     ++emsg_skip;
3124     typval_T * tvres = eval_expr(s, NULL);
3126     debug_break_level = save_dbl;
3127     redir_off = save_ro;
3128     --emsg_skip;
3130     setcursor();
3131     out_flush();
3133 #ifdef FEAT_MBYTE
3134     CONVERT_FROM_UTF8_FREE(s);
3135 #endif
3137 #ifdef FEAT_GUI
3138     if (gui.in_use)
3139         gui_update_cursor(FALSE, FALSE);
3140 #endif
3142     if (tvres == NULL) {
3143         free_tv(tvres);
3144         *errstr = @"Expression evaluation failed.";
3145     }
3147     id res = vimToCocoa(tvres, 1);
3149     free_tv(tvres);
3151     if (res == nil) {
3152         *errstr = @"Conversion to cocoa values failed.";
3153     }
3155     return res;
3160 @implementation NSString (VimStrings)
3162 + (id)stringWithVimString:(char_u *)s
3164     // This method ensures a non-nil string is returned.  If 's' cannot be
3165     // converted to a utf-8 string it is assumed to be latin-1.  If conversion
3166     // still fails an empty NSString is returned.
3167     NSString *string = nil;
3168     if (s) {
3169 #ifdef FEAT_MBYTE
3170         s = CONVERT_TO_UTF8(s);
3171 #endif
3172         string = [NSString stringWithUTF8String:(char*)s];
3173         if (!string) {
3174             // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
3175             // latin-1?
3176             string = [NSString stringWithCString:(char*)s
3177                                         encoding:NSISOLatin1StringEncoding];
3178         }
3179 #ifdef FEAT_MBYTE
3180         CONVERT_TO_UTF8_FREE(s);
3181 #endif
3182     }
3184     return string != nil ? string : [NSString string];
3187 - (char_u *)vimStringSave
3189     char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3191 #ifdef FEAT_MBYTE
3192     s = CONVERT_FROM_UTF8(s);
3193 #endif
3194     ret = vim_strsave(s);
3195 #ifdef FEAT_MBYTE
3196     CONVERT_FROM_UTF8_FREE(s);
3197 #endif
3199     return ret;
3202 @end // NSString (VimStrings)