Lower priority for exception logs
[MacVim.git] / src / MacVim / MMBackend.m
blob33be3e2f5b2c8d872a5d70a3463796cd7cff0d04
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         ASLogDebug(@"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_u*)s length:(int)len row:(int)row
491             column:(int)col cells:(int)cells flags:(int)flags
493     if (len <= 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             ASLogDebug(@"processInput:forIdentifer failed: reason=%@", ex);
599             if (![connection isValid]) {
600                 ASLogDebug(@"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             ASLogDebug(@"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         ASLogDebug(@"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         ASLogDebug(@"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                 CGFloat 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         ASLogDebug(@"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             ASLogDebug(@"serverList failed: reason=%@", ex);
1440         }
1441     } else {
1442         // We get here if a --remote flag is used before MacVim has started.
1443         ASLogInfo(@"No connection to MacVim, server listing not possible.");
1444     }
1446     return list;
1449 - (NSString *)peekForReplyOnPort:(int)port
1451     ASLogDebug(@"port=%d", port);
1453     NSNumber *key = [NSNumber numberWithInt:port];
1454     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1455     if (replies && [replies count]) {
1456         ASLogDebug(@"    %d replies, topmost is: %@", [replies count],
1457                    [replies objectAtIndex:0]);
1458         return [replies objectAtIndex:0];
1459     }
1461     ASLogDebug(@"    No replies");
1462     return nil;
1465 - (NSString *)waitForReplyOnPort:(int)port
1467     ASLogDebug(@"port=%d", port);
1468     
1469     NSConnection *conn = [self connectionForServerPort:port];
1470     if (!conn)
1471         return nil;
1473     NSNumber *key = [NSNumber numberWithInt:port];
1474     NSMutableArray *replies = nil;
1475     NSString *reply = nil;
1477     // Wait for reply as long as the connection to the server is valid (unless
1478     // user interrupts wait with Ctrl-C).
1479     while (!got_int && [conn isValid] &&
1480             !(replies = [serverReplyDict objectForKey:key])) {
1481         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1482                                  beforeDate:[NSDate distantFuture]];
1483     }
1485     if (replies) {
1486         if ([replies count] > 0) {
1487             reply = [[replies objectAtIndex:0] retain];
1488             ASLogDebug(@"    Got reply: %@", reply);
1489             [replies removeObjectAtIndex:0];
1490             [reply autorelease];
1491         }
1493         if ([replies count] == 0)
1494             [serverReplyDict removeObjectForKey:key];
1495     }
1497     return reply;
1500 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1502     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1503     if (client) {
1504         @try {
1505             ASLogDebug(@"reply=%@ port=%d", reply, port);
1506             [client addReply:reply server:self];
1507             return YES;
1508         }
1509         @catch (NSException *ex) {
1510             ASLogDebug(@"addReply:server: failed: reason=%@", ex);
1511         }
1512     } else {
1513         ASLogNotice(@"server2client failed; no client with id %d", port);
1514     }
1516     return NO;
1519 - (BOOL)waitForAck
1521     return waitForAck;
1524 - (void)setWaitForAck:(BOOL)yn
1526     waitForAck = yn;
1529 - (void)waitForConnectionAcknowledgement
1531     if (!waitForAck) return;
1533     while (waitForAck && !got_int && [connection isValid]) {
1534         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1535                                  beforeDate:[NSDate distantFuture]];
1536         ASLogDebug(@"  waitForAck=%d got_int=%d isValid=%d",
1537                    waitForAck, got_int, [connection isValid]);
1538     }
1540     if (waitForAck) {
1541         ASLogDebug(@"Never received a connection acknowledgement");
1542         [[NSNotificationCenter defaultCenter] removeObserver:self];
1543         [appProxy release];  appProxy = nil;
1545         // NOTE: We intentionally do not call mch_exit() since this in turn
1546         // will lead to -[MMBackend exit] getting called which we want to
1547         // avoid.
1548         exit(0);
1549     }
1551     ASLogInfo(@"Connection acknowledgement received");
1552     [self processInputQueue];
1555 - (oneway void)acknowledgeConnection
1557     ASLogDebug(@"");
1558     waitForAck = NO;
1561 - (BOOL)imState
1563     return imState;
1566 - (void)setImState:(BOOL)activated
1568     imState = activated;
1571 static void netbeansReadCallback(CFSocketRef s,
1572                                  CFSocketCallBackType callbackType,
1573                                  CFDataRef address,
1574                                  const void *data,
1575                                  void *info)
1577     // NetBeans socket is readable.
1578     [[MMBackend sharedInstance] messageFromNetbeans];
1581 - (void)messageFromNetbeans
1583     [inputQueue addObject:[NSNumber numberWithInt:NetBeansMsgID]];
1584     [inputQueue addObject:[NSNull null]];
1587 - (void)setNetbeansSocket:(int)socket
1589     if (netbeansSocket) {
1590         CFRelease(netbeansSocket);
1591         netbeansSocket = NULL;
1592     }
1594     if (netbeansRunLoopSource) {
1595         CFRunLoopSourceInvalidate(netbeansRunLoopSource);
1596         netbeansRunLoopSource = NULL;
1597     }
1599     if (socket == -1)
1600         return;
1602     // Tell CFRunLoop that we are interested in NetBeans socket input.
1603     netbeansSocket = CFSocketCreateWithNative(kCFAllocatorDefault,
1604                                               socket,
1605                                               kCFSocketReadCallBack,
1606                                               &netbeansReadCallback,
1607                                               NULL);
1608     netbeansRunLoopSource = CFSocketCreateRunLoopSource(NULL,
1609                                                         netbeansSocket,
1610                                                         0);
1611     CFRunLoopAddSource(CFRunLoopGetCurrent(),
1612                        netbeansRunLoopSource,
1613                        kCFRunLoopCommonModes);
1616 @end // MMBackend
1620 @implementation MMBackend (Private)
1622 - (void)clearDrawData
1624     [drawData setLength:0];
1625     numWholeLineChanges = offsetForDrawDataPrune = 0;
1628 - (void)didChangeWholeLine
1630     // It may happen that draw queue is filled up with lots of changes that
1631     // affect a whole row.  If the number of such changes equals twice the
1632     // number of visible rows then we can prune some commands off the queue.
1633     //
1634     // NOTE: If we don't perform this pruning the draw queue may grow
1635     // indefinitely if Vim were to repeatedly send draw commands without ever
1636     // waiting for new input (that's when the draw queue is flushed).  The one
1637     // instance I know where this can happen is when a command is executed in
1638     // the shell (think ":grep" with thousands of matches).
1640     ++numWholeLineChanges;
1641     if (numWholeLineChanges == gui.num_rows) {
1642         // Remember the offset to prune up to.
1643         offsetForDrawDataPrune = [drawData length];
1644     } else if (numWholeLineChanges == 2*gui.num_rows) {
1645         // Delete all the unnecessary draw commands.
1646         NSMutableData *d = [[NSMutableData alloc]
1647                     initWithBytes:[drawData bytes] + offsetForDrawDataPrune
1648                            length:[drawData length] - offsetForDrawDataPrune];
1649         offsetForDrawDataPrune = [d length];
1650         numWholeLineChanges -= gui.num_rows;
1651         [drawData release];
1652         drawData = d;
1653     }
1656 - (void)waitForDialogReturn
1658     // Keep processing the run loop until a dialog returns.  To avoid getting
1659     // stuck in an endless loop (could happen if the setDialogReturn: message
1660     // was lost) we also do some paranoia checks.
1661     //
1662     // Note that in Cocoa the user can still resize windows and select menu
1663     // items while a sheet is being displayed, so we can't just wait for the
1664     // first message to arrive and assume that is the setDialogReturn: call.
1666     while (nil == dialogReturn && !got_int && [connection isValid])
1667         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1668                                  beforeDate:[NSDate distantFuture]];
1670     // Search for any resize messages on the input queue.  All other messages
1671     // on the input queue are dropped.  The reason why we single out resize
1672     // messages is because the user may have resized the window while a sheet
1673     // was open.
1674     int i, count = [inputQueue count];
1675     if (count > 0) {
1676         id textDimData = nil;
1677         if (count%2 == 0) {
1678             for (i = count-2; i >= 0; i -= 2) {
1679                 int msgid = [[inputQueue objectAtIndex:i] intValue];
1680                 if (SetTextDimensionsMsgID == msgid) {
1681                     textDimData = [[inputQueue objectAtIndex:i+1] retain];
1682                     break;
1683                 }
1684             }
1685         }
1687         [inputQueue removeAllObjects];
1689         if (textDimData) {
1690             [inputQueue addObject:
1691                     [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1692             [inputQueue addObject:textDimData];
1693             [textDimData release];
1694         }
1695     }
1698 - (void)insertVimStateMessage
1700     // NOTE: This is the place to add Vim state that needs to be accessed from
1701     // MacVim.  Do not add state that could potentially require lots of memory
1702     // since this message gets sent each time the output queue is forcibly
1703     // flushed (e.g. storing the currently selected text would be a bad idea).
1704     // We take this approach of "pushing" the state to MacVim to avoid having
1705     // to make synchronous calls from MacVim to Vim in order to get state.
1707     BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1708     int numTabs = tabpage_index(NULL) - 1;
1709     if (numTabs < 0)
1710         numTabs = 0;
1712     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1713         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1714         [NSNumber numberWithInt:p_mh], @"p_mh",
1715         [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1716         [NSNumber numberWithBool:mmta], @"p_mmta",
1717         [NSNumber numberWithInt:numTabs], @"numTabs",
1718         nil];
1720     // Put the state before all other messages.
1721     int msgid = SetVimStateMsgID;
1722     [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1723     [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1724                       atIndex:0];
1727 - (void)processInputQueue
1729     if ([inputQueue count] == 0) return;
1731     // NOTE: One of the input events may cause this method to be called
1732     // recursively, so copy the input queue to a local variable and clear the
1733     // queue before starting to process input events (otherwise we could get
1734     // stuck in an endless loop).
1735     NSArray *q = [inputQueue copy];
1736     unsigned i, count = [q count];
1738     [inputQueue removeAllObjects];
1740     for (i = 1; i < count; i+=2) {
1741         int msgid = [[q objectAtIndex:i-1] intValue];
1742         id data = [q objectAtIndex:i];
1743         if ([data isEqual:[NSNull null]])
1744             data = nil;
1746         ASLogDebug(@"(%d) %s", i, MessageStrings[msgid]);
1747         [self handleInputEvent:msgid data:data];
1748     }
1750     [q release];
1754 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1756     if (KeyDownMsgID == msgid) {
1757         if (!data) return;
1758         const void *bytes = [data bytes];
1759         unsigned mods = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1760         unsigned code = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1761         unsigned len  = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1762         NSString *key = [[NSString alloc] initWithBytes:bytes
1763                                                  length:len
1764                                                encoding:NSUTF8StringEncoding];
1765         mods = eventModifierFlagsToVimModMask(mods);
1767         [self doKeyDown:key keyCode:code modifiers:mods];
1768         [key release];
1769     } else if (ScrollWheelMsgID == msgid) {
1770         if (!data) return;
1771         const void *bytes = [data bytes];
1773         int row = *((int*)bytes);  bytes += sizeof(int);
1774         int col = *((int*)bytes);  bytes += sizeof(int);
1775         int flags = *((int*)bytes);  bytes += sizeof(int);
1776         float dy = *((float*)bytes);  bytes += sizeof(float);
1778         int button = MOUSE_5;
1779         if (dy > 0) button = MOUSE_4;
1781         flags = eventModifierFlagsToVimMouseModMask(flags);
1783         int numLines = (int)round(dy);
1784         if (numLines < 0) numLines = -numLines;
1785         if (numLines == 0) numLines = 1;
1787 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1788         gui.scroll_wheel_force = numLines;
1789 #endif
1791         gui_send_mouse_event(button, col, row, NO, flags);
1792     } else if (MouseDownMsgID == msgid) {
1793         if (!data) return;
1794         const void *bytes = [data bytes];
1796         int row = *((int*)bytes);  bytes += sizeof(int);
1797         int col = *((int*)bytes);  bytes += sizeof(int);
1798         int button = *((int*)bytes);  bytes += sizeof(int);
1799         int flags = *((int*)bytes);  bytes += sizeof(int);
1800         int count = *((int*)bytes);  bytes += sizeof(int);
1802         button = eventButtonNumberToVimMouseButton(button);
1803         if (button >= 0) {
1804             flags = eventModifierFlagsToVimMouseModMask(flags);
1805             gui_send_mouse_event(button, col, row, count>1, flags);
1806         }
1807     } else if (MouseUpMsgID == msgid) {
1808         if (!data) return;
1809         const void *bytes = [data bytes];
1811         int row = *((int*)bytes);  bytes += sizeof(int);
1812         int col = *((int*)bytes);  bytes += sizeof(int);
1813         int flags = *((int*)bytes);  bytes += sizeof(int);
1815         flags = eventModifierFlagsToVimMouseModMask(flags);
1817         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1818     } else if (MouseDraggedMsgID == msgid) {
1819         if (!data) return;
1820         const void *bytes = [data bytes];
1822         int row = *((int*)bytes);  bytes += sizeof(int);
1823         int col = *((int*)bytes);  bytes += sizeof(int);
1824         int flags = *((int*)bytes);  bytes += sizeof(int);
1826         flags = eventModifierFlagsToVimMouseModMask(flags);
1828         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1829     } else if (MouseMovedMsgID == msgid) {
1830         const void *bytes = [data bytes];
1831         int row = *((int*)bytes);  bytes += sizeof(int);
1832         int col = *((int*)bytes);  bytes += sizeof(int);
1834         gui_mouse_moved(col, row);
1835     } else if (AddInputMsgID == msgid) {
1836         NSString *string = [[NSString alloc] initWithData:data
1837                 encoding:NSUTF8StringEncoding];
1838         if (string) {
1839             [self addInput:string];
1840             [string release];
1841         }
1842     } else if (SelectTabMsgID == msgid) {
1843         if (!data) return;
1844         const void *bytes = [data bytes];
1845         int idx = *((int*)bytes) + 1;
1846         send_tabline_event(idx);
1847     } else if (CloseTabMsgID == msgid) {
1848         if (!data) return;
1849         const void *bytes = [data bytes];
1850         int idx = *((int*)bytes) + 1;
1851         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1852     } else if (AddNewTabMsgID == msgid) {
1853         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1854     } else if (DraggedTabMsgID == msgid) {
1855         if (!data) return;
1856         const void *bytes = [data bytes];
1857         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1858         // based.
1859         int idx = *((int*)bytes);
1861         tabpage_move(idx);
1862     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1863             || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1864         if (!data) return;
1865         const void *bytes = [data bytes];
1866         int rows = Rows;
1867         if (SetTextColumnsMsgID != msgid) {
1868             rows = *((int*)bytes);  bytes += sizeof(int);
1869         }
1870         int cols = Columns;
1871         if (SetTextRowsMsgID != msgid) {
1872             cols = *((int*)bytes);  bytes += sizeof(int);
1873         }
1875         NSData *d = data;
1876         if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1877             int dim[2] = { rows, cols };
1878             d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1879             msgid = SetTextDimensionsReplyMsgID;
1880         }
1882         if (SetTextDimensionsMsgID == msgid)
1883             msgid = SetTextDimensionsReplyMsgID;
1885         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1886         // gui_resize_shell(), so we have to manually set the rows and columns
1887         // here since MacVim doesn't change the rows and columns to avoid
1888         // inconsistent states between Vim and MacVim.  The message sent back
1889         // indicates that it is a reply to a message that originated in MacVim
1890         // since we need to be able to determine where a message originated.
1891         [self queueMessage:msgid data:d];
1893         gui_resize_shell(cols, rows);
1894     } else if (ExecuteMenuMsgID == msgid) {
1895         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1896         if (attrs) {
1897             NSArray *desc = [attrs objectForKey:@"descriptor"];
1898             vimmenu_T *menu = menu_for_descriptor(desc);
1899             if (menu)
1900                 gui_menu_cb(menu);
1901         }
1902     } else if (ToggleToolbarMsgID == msgid) {
1903         [self handleToggleToolbar];
1904     } else if (ScrollbarEventMsgID == msgid) {
1905         [self handleScrollbarEvent:data];
1906     } else if (SetFontMsgID == msgid) {
1907         [self handleSetFont:data];
1908     } else if (VimShouldCloseMsgID == msgid) {
1909         gui_shell_closed();
1910     } else if (DropFilesMsgID == msgid) {
1911         [self handleDropFiles:data];
1912     } else if (DropStringMsgID == msgid) {
1913         [self handleDropString:data];
1914     } else if (GotFocusMsgID == msgid) {
1915         if (!gui.in_focus)
1916             [self focusChange:YES];
1917     } else if (LostFocusMsgID == msgid) {
1918         if (gui.in_focus)
1919             [self focusChange:NO];
1920     } else if (SetMouseShapeMsgID == msgid) {
1921         const void *bytes = [data bytes];
1922         int shape = *((int*)bytes);  bytes += sizeof(int);
1923         update_mouseshape(shape);
1924     } else if (XcodeModMsgID == msgid) {
1925         [self handleXcodeMod:data];
1926     } else if (OpenWithArgumentsMsgID == msgid) {
1927         [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1928     } else if (FindReplaceMsgID == msgid) {
1929         [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1930     } else if (ActivatedImMsgID == msgid) {
1931         [self setImState:YES];
1932     } else if (DeactivatedImMsgID == msgid) {
1933         [self setImState:NO];
1934     } else if (NetBeansMsgID == msgid) {
1935 #ifdef FEAT_NETBEANS_INTG
1936         messageFromNetbeansMacVim();
1937 #endif
1938     } else if (SetMarkedTextMsgID == msgid) {
1939         [self handleMarkedText:data];
1940     } else {
1941         ASLogWarn(@"Unknown message received (msgid=%d)", msgid);
1942     }
1945 - (void)doKeyDown:(NSString *)key
1946           keyCode:(unsigned)code
1947         modifiers:(int)mods
1949     ASLogDebug(@"key='%@' code=%#x mods=%#x length=%d", key, code, mods,
1950             [key length]);
1951     if (!key) return;
1953     char_u *str = (char_u*)[key UTF8String];
1954     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1956     if ([self handleSpecialKey:key keyCode:code modifiers:mods])
1957         return;
1959 #ifdef FEAT_MBYTE
1960     char_u *conv_str = NULL;
1961     if (input_conv.vc_type != CONV_NONE) {
1962         conv_str = string_convert(&input_conv, str, &len);
1963         if (conv_str)
1964             str = conv_str;
1965     }
1966 #endif
1968     if (mods & MOD_MASK_CMD) {
1969         // NOTE: For normal input (non-special, 'macmeta' off) the modifier
1970         // flags are already included in the key event.  However, the Cmd key
1971         // flag is special and must always be added manually.
1972         // The Shift flag is already included in the key when the Command
1973         // key is held.  The same goes for Alt, unless Ctrl is held or
1974         // 'macmeta' is set.  It is important that these flags are cleared
1975         // _after_ special keys have been handled, since they should never be
1976         // cleared for special keys.
1977         mods &= ~MOD_MASK_SHIFT;
1978         if (!(mods & MOD_MASK_CTRL)) {
1979             BOOL mmta = curbuf ? curbuf->b_p_mmta : YES;
1980             if (!mmta)
1981                 mods &= ~MOD_MASK_ALT;
1982         }
1984         ASLogDebug(@"add mods=%#x", mods);
1985         char_u modChars[3] = { CSI, KS_MODIFIER, mods };
1986         add_to_input_buf(modChars, 3);
1987     } else if (mods & MOD_MASK_ALT && 1 == len && str[0] < 0x80
1988             && curbuf && curbuf->b_p_mmta) {
1989         // HACK! The 'macmeta' is set so we have to handle Alt key presses
1990         // separately.  Normally Alt key presses are interpreted by the
1991         // frontend but now we have to manually set the 8th bit and deal with
1992         // UTF-8 conversion.
1993         if ([self handleMacMetaKey:str[0] modifiers:mods])
1994             return;
1995     }
1998     for (i = 0; i < len; ++i) {
1999         ASLogDebug(@"add byte [%d/%d]: %#x", i, len, str[i]);
2000         add_to_input_buf(str+i, 1);
2001         if (CSI == str[i]) {
2002             // NOTE: If the converted string contains the byte CSI, then it
2003             // must be followed by the bytes KS_EXTRA, KE_CSI or things
2004             // won't work.
2005             static char_u extra[2] = { KS_EXTRA, KE_CSI };
2006             ASLogDebug(@"add KS_EXTRA, KE_CSI");
2007             add_to_input_buf(extra, 2);
2008         }
2009     }
2011 #ifdef FEAT_MBYTE
2012     if (conv_str)
2013         vim_free(conv_str);
2014 #endif
2017 - (BOOL)handleSpecialKey:(NSString *)key
2018                  keyCode:(unsigned)code
2019                modifiers:(int)mods
2021     int i;
2022     for (i = 0; special_keys[i].key_sym != 0; i++) {
2023         if (special_keys[i].key_sym == code) {
2024             ASLogDebug(@"Special key: %#x", code);
2025             break;
2026         }
2027     }
2028     if (special_keys[i].key_sym == 0)
2029         return NO;
2031     int ikey = special_keys[i].vim_code1 == NUL ? special_keys[i].vim_code0 :
2032             TO_SPECIAL(special_keys[i].vim_code0, special_keys[i].vim_code1);
2033     ikey = simplify_key(ikey, &mods);
2034     if (ikey == CSI)
2035         ikey = K_CSI;
2037     char_u chars[4];
2038     int len = 0;
2040     if (IS_SPECIAL(ikey)) {
2041         chars[0] = CSI;
2042         chars[1] = K_SECOND(ikey);
2043         chars[2] = K_THIRD(ikey);
2044         len = 3;
2045     } else if (mods & MOD_MASK_ALT && special_keys[i].vim_code1 == 0
2046 #ifdef FEAT_MBYTE
2047             && !enc_dbcs    // TODO: ?  (taken from gui_gtk_x11.c)
2048 #endif
2049             ) {
2050         ASLogDebug(@"Alt special=%d", ikey);
2052         // NOTE: The last entries in the special_keys struct when pressed
2053         // together with Alt need to be handled separately or they will not
2054         // work.
2055         // The following code was gleaned from gui_gtk_x11.c.
2056         mods &= ~MOD_MASK_ALT;
2057         int mkey = 0x80 | ikey;
2058 #ifdef FEAT_MBYTE
2059         if (enc_utf8) {  // TODO: What about other encodings?
2060             // Convert to utf-8
2061             chars[0] = (mkey >> 6) + 0xc0;
2062             chars[1] = mkey & 0xbf;
2063             if (chars[1] == CSI) {
2064                 // We end up here when ikey == ESC
2065                 chars[2] = KS_EXTRA;
2066                 chars[3] = KE_CSI;
2067                 len = 4;
2068             } else {
2069                 len = 2;
2070             }
2071         } else
2072 #endif
2073         {
2074             chars[0] = mkey;
2075             len = 1;
2076         }
2077     } else {
2078         ASLogDebug(@"Just ikey=%d", ikey);
2079         chars[0] = ikey;
2080         len = 1;
2081     }
2083     if (len > 0) {
2084         if (mods) {
2085             ASLogDebug(@"Adding mods to special: %d", mods);
2086             char_u modChars[3] = { CSI, KS_MODIFIER, (char_u)mods };
2087             add_to_input_buf(modChars, 3);
2088         }
2090         ASLogDebug(@"Adding special (%d): %x,%x,%x", len,
2091                 chars[0], chars[1], chars[2]);
2092         add_to_input_buf(chars, len);
2093     }
2095     return YES;
2098 - (BOOL)handleMacMetaKey:(int)ikey modifiers:(int)mods
2100     ASLogDebug(@"ikey=%d mods=%d", ikey, mods);
2102     // This code was taken from gui_w48.c and gui_gtk_x11.c.
2103     char_u string[7];
2104     int ch = simplify_key(ikey, &mods);
2106     // Remove the SHIFT modifier for keys where it's already included,
2107     // e.g., '(' and '*'
2108     if (ch < 0x100 && !isalpha(ch) && isprint(ch))
2109         mods &= ~MOD_MASK_SHIFT;
2111     // Interpret the ALT key as making the key META, include SHIFT, etc.
2112     ch = extract_modifiers(ch, &mods);
2113     if (ch == CSI)
2114         ch = K_CSI;
2116     int len = 0;
2117     if (mods) {
2118         string[len++] = CSI;
2119         string[len++] = KS_MODIFIER;
2120         string[len++] = mods;
2121     }
2123     string[len++] = ch;
2124 #ifdef FEAT_MBYTE
2125     // TODO: What if 'enc' is not "utf-8"?
2126     if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
2127         string[len++] = ch & 0xbf;
2128         string[len-2] = ((unsigned)ch >> 6) + 0xc0;
2129         if (string[len-1] == CSI) {
2130             string[len++] = KS_EXTRA;
2131             string[len++] = (int)KE_CSI;
2132         }
2133     }
2134 #endif
2136     add_to_input_buf(string, len);
2137     return YES;
2140 - (void)queueMessage:(int)msgid data:(NSData *)data
2142     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
2143     if (data)
2144         [outputQueue addObject:data];
2145     else
2146         [outputQueue addObject:[NSData data]];
2149 - (void)connectionDidDie:(NSNotification *)notification
2151     // If the main connection to MacVim is lost this means that either MacVim
2152     // has crashed or this process did not receive its termination message
2153     // properly (e.g. if the TerminateNowMsgID was dropped).
2154     //
2155     // NOTE: This is not called if a Vim controller invalidates its connection.
2157     ASLogNotice(@"Main connection was lost before process had a chance "
2158                 "to terminate; preserving swap files.");
2159     getout_preserve_modified(1);
2162 - (void)blinkTimerFired:(NSTimer *)timer
2164     NSTimeInterval timeInterval = 0;
2166     [blinkTimer release];
2167     blinkTimer = nil;
2169     if (MMBlinkStateOn == blinkState) {
2170         gui_undraw_cursor();
2171         blinkState = MMBlinkStateOff;
2172         timeInterval = blinkOffInterval;
2173     } else if (MMBlinkStateOff == blinkState) {
2174         gui_update_cursor(TRUE, FALSE);
2175         blinkState = MMBlinkStateOn;
2176         timeInterval = blinkOnInterval;
2177     }
2179     if (timeInterval > 0) {
2180         blinkTimer = 
2181             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2182                                             selector:@selector(blinkTimerFired:)
2183                                             userInfo:nil repeats:NO] retain];
2184         [self flushQueue:YES];
2185     }
2188 - (void)focusChange:(BOOL)on
2190     gui_focus_change(on);
2193 - (void)handleToggleToolbar
2195     // If 'go' contains 'T', then remove it, else add it.
2197     char_u go[sizeof(GO_ALL)+2];
2198     char_u *p;
2199     int len;
2201     STRCPY(go, p_go);
2202     p = vim_strchr(go, GO_TOOLBAR);
2203     len = STRLEN(go);
2205     if (p != NULL) {
2206         char_u *end = go + len;
2207         while (p < end) {
2208             p[0] = p[1];
2209             ++p;
2210         }
2211     } else {
2212         go[len] = GO_TOOLBAR;
2213         go[len+1] = NUL;
2214     }
2216     set_option_value((char_u*)"guioptions", 0, go, 0);
2218     [self redrawScreen];
2221 - (void)handleScrollbarEvent:(NSData *)data
2223     if (!data) return;
2225     const void *bytes = [data bytes];
2226     int32_t ident = *((int32_t*)bytes);  bytes += sizeof(int32_t);
2227     int hitPart = *((int*)bytes);  bytes += sizeof(int);
2228     float fval = *((float*)bytes);  bytes += sizeof(float);
2229     scrollbar_T *sb = gui_find_scrollbar(ident);
2231     if (sb) {
2232         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2233         long value = sb_info->value;
2234         long size = sb_info->size;
2235         long max = sb_info->max;
2236         BOOL isStillDragging = NO;
2237         BOOL updateKnob = YES;
2239         switch (hitPart) {
2240         case NSScrollerDecrementPage:
2241             value -= (size > 2 ? size - 2 : 1);
2242             break;
2243         case NSScrollerIncrementPage:
2244             value += (size > 2 ? size - 2 : 1);
2245             break;
2246         case NSScrollerDecrementLine:
2247             --value;
2248             break;
2249         case NSScrollerIncrementLine:
2250             ++value;
2251             break;
2252         case NSScrollerKnob:
2253             isStillDragging = YES;
2254             // fall through ...
2255         case NSScrollerKnobSlot:
2256             value = (long)(fval * (max - size + 1));
2257             // fall through ...
2258         default:
2259             updateKnob = NO;
2260             break;
2261         }
2263         gui_drag_scrollbar(sb, value, isStillDragging);
2265         if (updateKnob) {
2266             // Dragging the knob or option+clicking automatically updates
2267             // the knob position (on the actual NSScroller), so we only
2268             // need to set the knob position in the other cases.
2269             if (sb->wp) {
2270                 // Update both the left&right vertical scrollbars.
2271                 int32_t idL = (int32_t)sb->wp->w_scrollbars[SBAR_LEFT].ident;
2272                 int32_t idR = (int32_t)sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2273                 [self setScrollbarThumbValue:value size:size max:max
2274                                   identifier:idL];
2275                 [self setScrollbarThumbValue:value size:size max:max
2276                                   identifier:idR];
2277             } else {
2278                 // Update the horizontal scrollbar.
2279                 [self setScrollbarThumbValue:value size:size max:max
2280                                   identifier:ident];
2281             }
2282         }
2283     }
2286 - (void)handleSetFont:(NSData *)data
2288     if (!data) return;
2290     const void *bytes = [data bytes];
2291     int pointSize = (int)*((float*)bytes);  bytes += sizeof(float);
2293     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2294     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2295     bytes += len;
2297     [name appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2298     char_u *s = (char_u*)[name UTF8String];
2300     unsigned wlen = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2301     char_u *ws = NULL;
2302     if (wlen > 0) {
2303         NSMutableString *wname = [NSMutableString stringWithUTF8String:bytes];
2304         bytes += wlen;
2306         [wname appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2307         ws = (char_u*)[wname UTF8String];
2308     }
2310 #ifdef FEAT_MBYTE
2311     s = CONVERT_FROM_UTF8(s);
2312     if (ws) {
2313         ws = CONVERT_FROM_UTF8(ws);
2314     }
2315 #endif
2317     set_option_value((char_u*)"guifont", 0, s, 0);
2319     if (ws && gui.wide_font != NOFONT) {
2320         // NOTE: This message is sent on Cmd-+/Cmd-- and as such should only
2321         // change the wide font if 'gfw' is non-empty (the frontend always has
2322         // some wide font set, even if 'gfw' is empty).
2323         set_option_value((char_u*)"guifontwide", 0, ws, 0);
2324     }
2326 #ifdef FEAT_MBYTE
2327     if (ws) {
2328         CONVERT_FROM_UTF8_FREE(ws);
2329     }
2330     CONVERT_FROM_UTF8_FREE(s);
2331 #endif
2333     [self redrawScreen];
2336 - (void)handleDropFiles:(NSData *)data
2338     // TODO: Get rid of this method; instead use Vim script directly.  At the
2339     // moment I know how to do this to open files in tabs, but I'm not sure how
2340     // to add the filenames to the command line when in command line mode.
2342     if (!data) return;
2344     NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2345     if (!args) return;
2347     id obj = [args objectForKey:@"forceOpen"];
2348     BOOL forceOpen = YES;
2349     if (obj)
2350         forceOpen = [obj boolValue];
2352     NSArray *filenames = [args objectForKey:@"filenames"];
2353     if (!(filenames && [filenames count] > 0)) return;
2355 #ifdef FEAT_DND
2356     if (!forceOpen && (State & CMDLINE)) {
2357         // HACK!  If Vim is in command line mode then the files names
2358         // should be added to the command line, instead of opening the
2359         // files in tabs (unless forceOpen is set).  This is taken care of by
2360         // gui_handle_drop().
2361         int n = [filenames count];
2362         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2363         if (fnames) {
2364             int i = 0;
2365             for (i = 0; i < n; ++i)
2366                 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2368             // NOTE!  This function will free 'fnames'.
2369             // HACK!  It is assumed that the 'x' and 'y' arguments are
2370             // unused when in command line mode.
2371             gui_handle_drop(0, 0, 0, fnames, n);
2372         }
2373     } else
2374 #endif // FEAT_DND
2375     {
2376         [self handleOpenWithArguments:args];
2377     }
2380 - (void)handleDropString:(NSData *)data
2382     if (!data) return;
2384 #ifdef FEAT_DND
2385     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2386     const void *bytes = [data bytes];
2387     int len = *((int*)bytes);  bytes += sizeof(int);
2388     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2390     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2391     NSRange range = { 0, [string length] };
2392     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2393                                          withString:@"\x0a" options:0
2394                                               range:range];
2395     if (0 == n) {
2396         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2397                                        options:0 range:range];
2398     }
2400     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2401     char_u *s = (char_u*)[string UTF8String];
2402 #ifdef FEAT_MBYTE
2403     if (input_conv.vc_type != CONV_NONE)
2404         s = string_convert(&input_conv, s, &len);
2405 #endif
2406     dnd_yank_drag_data(s, len);
2407 #ifdef FEAT_MBYTE
2408     if (input_conv.vc_type != CONV_NONE)
2409         vim_free(s);
2410 #endif
2411     add_to_input_buf(dropkey, sizeof(dropkey));
2412 #endif // FEAT_DND
2415 - (void)startOdbEditWithArguments:(NSDictionary *)args
2417 #ifdef FEAT_ODB_EDITOR
2418     id obj = [args objectForKey:@"remoteID"];
2419     if (!obj) return;
2421     OSType serverID = [obj unsignedIntValue];
2422     NSString *remotePath = [args objectForKey:@"remotePath"];
2424     NSAppleEventDescriptor *token = nil;
2425     NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2426     obj = [args objectForKey:@"remoteTokenDescType"];
2427     if (tokenData && obj) {
2428         DescType tokenType = [obj unsignedLongValue];
2429         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2430                                                                 data:tokenData];
2431     }
2433     NSArray *filenames = [args objectForKey:@"filenames"];
2434     unsigned i, numFiles = [filenames count];
2435     for (i = 0; i < numFiles; ++i) {
2436         NSString *filename = [filenames objectAtIndex:i];
2437         char_u *s = [filename vimStringSave];
2438         buf_T *buf = buflist_findname(s);
2439         vim_free(s);
2441         if (buf) {
2442             if (buf->b_odb_token) {
2443                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2444                 buf->b_odb_token = NULL;
2445             }
2447             if (buf->b_odb_fname) {
2448                 vim_free(buf->b_odb_fname);
2449                 buf->b_odb_fname = NULL;
2450             }
2452             buf->b_odb_server_id = serverID;
2454             if (token)
2455                 buf->b_odb_token = [token retain];
2456             if (remotePath)
2457                 buf->b_odb_fname = [remotePath vimStringSave];
2458         } else {
2459             ASLogWarn(@"Could not find buffer '%@' for ODB editing.", filename);
2460         }
2461     }
2462 #endif // FEAT_ODB_EDITOR
2465 - (void)handleXcodeMod:(NSData *)data
2467 #if 0
2468     const void *bytes = [data bytes];
2469     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2470     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2471     if (0 == len)
2472         return;
2474     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2475             descriptorWithDescriptorType:type
2476                                    bytes:bytes
2477                                   length:len];
2478 #endif
2481 - (void)handleOpenWithArguments:(NSDictionary *)args
2483     // ARGUMENT:                DESCRIPTION:
2484     // -------------------------------------------------------------
2485     // filenames                list of filenames
2486     // dontOpen                 don't open files specified in above argument
2487     // layout                   which layout to use to open files
2488     // selectionRange           range of lines to select
2489     // searchText               string to search for
2490     // cursorLine               line to position the cursor on
2491     // cursorColumn             column to position the cursor on
2492     //                          (only valid when "cursorLine" is set)
2493     // remoteID                 ODB parameter
2494     // remotePath               ODB parameter
2495     // remoteTokenDescType      ODB parameter
2496     // remoteTokenData          ODB parameter
2498     ASLogDebug(@"args=%@ (starting=%d)", args, starting);
2500     NSArray *filenames = [args objectForKey:@"filenames"];
2501     int i, numFiles = filenames ? [filenames count] : 0;
2502     BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2503     int layout = [[args objectForKey:@"layout"] intValue];
2505     // Change to directory of first file to open if this is an "unused" editor
2506     // (but do not do this if editing remotely).
2507     if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2508             && (starting || [self unusedEditor]) ) {
2509         char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2510         if (mch_isdir(s)) {
2511             mch_chdir((char*)s);
2512         } else {
2513             vim_chdirfile(s);
2514         }
2515         vim_free(s);
2516     }
2518     if (starting > 0) {
2519         // When Vim is starting we simply add the files to be opened to the
2520         // global arglist and Vim will take care of opening them for us.
2521         if (openFiles && numFiles > 0) {
2522             for (i = 0; i < numFiles; i++) {
2523                 NSString *fname = [filenames objectAtIndex:i];
2524                 char_u *p = NULL;
2526                 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2527                         || (p = [fname vimStringSave]) == NULL)
2528                     exit(2); // See comment in -[MMBackend exit]
2529                 else
2530                     alist_add(&global_alist, p, 2);
2531             }
2533             // Vim will take care of arranging the files added to the arglist
2534             // in windows or tabs; all we must do is to specify which layout to
2535             // use.
2536             initialWindowLayout = layout;
2537         }
2538     } else {
2539         // When Vim is already open we resort to some trickery to open the
2540         // files with the specified layout.
2541         //
2542         // TODO: Figure out a better way to handle this?
2543         if (openFiles && numFiles > 0) {
2544             BOOL oneWindowInTab = topframe ? YES
2545                                            : (topframe->fr_layout == FR_LEAF);
2546             BOOL bufChanged = NO;
2547             BOOL bufHasFilename = NO;
2548             if (curbuf) {
2549                 bufChanged = curbufIsChanged();
2550                 bufHasFilename = curbuf->b_ffname != NULL;
2551             }
2553             // Temporarily disable flushing since the following code may
2554             // potentially cause multiple redraws.
2555             flushDisabled = YES;
2557             BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2558             if (WIN_TABS == layout && !onlyOneTab) {
2559                 // By going to the last tabpage we ensure that the new tabs
2560                 // will appear last (if this call is left out, the taborder
2561                 // becomes messy).
2562                 goto_tabpage(9999);
2563             }
2565             // Make sure we're in normal mode first.
2566             [self addInput:@"<C-\\><C-N>"];
2568             if (numFiles > 1) {
2569                 // With "split layout" we open a new tab before opening
2570                 // multiple files if the current tab has more than one window
2571                 // or if there is exactly one window but whose buffer has a
2572                 // filename.  (The :drop command ensures modified buffers get
2573                 // their own window.)
2574                 if ((WIN_HOR == layout || WIN_VER == layout) &&
2575                         (!oneWindowInTab || bufHasFilename))
2576                     [self addInput:@":tabnew<CR>"];
2578                 // The files are opened by constructing a ":drop ..." command
2579                 // and executing it.
2580                 NSMutableString *cmd = (WIN_TABS == layout)
2581                         ? [NSMutableString stringWithString:@":tab drop"]
2582                         : [NSMutableString stringWithString:@":drop"];
2584                 for (i = 0; i < numFiles; ++i) {
2585                     NSString *file = [filenames objectAtIndex:i];
2586                     file = [file stringByEscapingSpecialFilenameCharacters];
2587                     [cmd appendString:@" "];
2588                     [cmd appendString:file];
2589                 }
2591                 // Temporarily clear 'suffixes' so that the files are opened in
2592                 // the same order as they appear in the "filenames" array.
2593                 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2595                 [self addInput:cmd];
2597                 // Split the view into multiple windows if requested.
2598                 if (WIN_HOR == layout)
2599                     [self addInput:@"|sall"];
2600                 else if (WIN_VER == layout)
2601                     [self addInput:@"|vert sall"];
2603                 // Restore the old value of 'suffixes'.
2604                 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2605             } else {
2606                 // When opening one file we try to reuse the current window,
2607                 // but not if its buffer is modified or has a filename.
2608                 // However, the 'arglist' layout always opens the file in the
2609                 // current window.
2610                 NSString *file = [[filenames lastObject]
2611                         stringByEscapingSpecialFilenameCharacters];
2612                 NSString *cmd;
2613                 if (WIN_HOR == layout) {
2614                     if (!(bufHasFilename || bufChanged))
2615                         cmd = [NSString stringWithFormat:@":e %@", file];
2616                     else
2617                         cmd = [NSString stringWithFormat:@":sp %@", file];
2618                 } else if (WIN_VER == layout) {
2619                     if (!(bufHasFilename || bufChanged))
2620                         cmd = [NSString stringWithFormat:@":e %@", file];
2621                     else
2622                         cmd = [NSString stringWithFormat:@":vsp %@", file];
2623                 } else if (WIN_TABS == layout) {
2624                     if (oneWindowInTab && !(bufHasFilename || bufChanged))
2625                         cmd = [NSString stringWithFormat:@":e %@", file];
2626                     else
2627                         cmd = [NSString stringWithFormat:@":tabe %@", file];
2628                 } else {
2629                     // (The :drop command will split if there is a modified
2630                     // buffer.)
2631                     cmd = [NSString stringWithFormat:@":drop %@", file];
2632                 }
2634                 [self addInput:cmd];
2635                 [self addInput:@"<CR>"];
2636             }
2638             // Force screen redraw (does it have to be this complicated?).
2639             // (This code was taken from the end of gui_handle_drop().)
2640             update_screen(NOT_VALID);
2641             setcursor();
2642             out_flush();
2643             gui_update_cursor(FALSE, FALSE);
2644             maketitle();
2646             flushDisabled = NO;
2647         }
2648     }
2650     if ([args objectForKey:@"remoteID"]) {
2651         // NOTE: We have to delay processing any ODB related arguments since
2652         // the file(s) may not be opened until the input buffer is processed.
2653         [self performSelector:@selector(startOdbEditWithArguments:)
2654                    withObject:args
2655                    afterDelay:0];
2656     }
2658     NSString *lineString = [args objectForKey:@"cursorLine"];
2659     if (lineString && [lineString intValue] > 0) {
2660         NSString *columnString = [args objectForKey:@"cursorColumn"];
2661         if (!(columnString && [columnString intValue] > 0))
2662             columnString = @"1";
2664         NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2665                 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2666         [self addInput:cmd];
2667     }
2669     NSString *rangeString = [args objectForKey:@"selectionRange"];
2670     if (rangeString) {
2671         // Build a command line string that will select the given range of
2672         // lines.  If range.length == 0, then position the cursor on the given
2673         // line but do not select.
2674         NSRange range = NSRangeFromString(rangeString);
2675         NSString *cmd;
2676         if (range.length > 0) {
2677             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2678                     NSMaxRange(range), range.location];
2679         } else {
2680             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2681                     range.location];
2682         }
2684         [self addInput:cmd];
2685     }
2687     NSString *searchText = [args objectForKey:@"searchText"];
2688     if (searchText) {
2689         [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2690                 searchText]];
2691     }
2694 - (BOOL)checkForModifiedBuffers
2696     buf_T *buf;
2697     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2698         if (bufIsChanged(buf)) {
2699             return YES;
2700         }
2701     }
2703     return NO;
2706 - (void)addInput:(NSString *)input
2708     // NOTE: This code is essentially identical to server_to_input_buf(),
2709     // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2710     char_u *string = [input vimStringSave];
2711     if (!string) return;
2713     /* Set 'cpoptions' the way we want it.
2714      *    B set - backslashes are *not* treated specially
2715      *    k set - keycodes are *not* reverse-engineered
2716      *    < unset - <Key> sequences *are* interpreted
2717      *  The last but one parameter of replace_termcodes() is TRUE so that the
2718      *  <lt> sequence is recognised - needed for a real backslash.
2719      */
2720     char_u *ptr = NULL;
2721     char_u *cpo_save = p_cpo;
2722     p_cpo = (char_u *)"Bk";
2723     char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2724     p_cpo = cpo_save;
2726     if (*ptr != NUL)    /* trailing CTRL-V results in nothing */
2727     {
2728         /*
2729          * Add the string to the input stream.
2730          * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2731          *
2732          * First clear typed characters from the typeahead buffer, there could
2733          * be half a mapping there.  Then append to the existing string, so
2734          * that multiple commands from a client are concatenated.
2735          */
2736         if (typebuf.tb_maplen < typebuf.tb_len)
2737             del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2738         (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2740         /* Let input_available() know we inserted text in the typeahead
2741          * buffer. */
2742         typebuf_was_filled = TRUE;
2743     }
2744     vim_free(ptr);
2745     vim_free(string);
2748 - (BOOL)unusedEditor
2750     BOOL oneWindowInTab = topframe ? YES
2751                                    : (topframe->fr_layout == FR_LEAF);
2752     BOOL bufChanged = NO;
2753     BOOL bufHasFilename = NO;
2754     if (curbuf) {
2755         bufChanged = curbufIsChanged();
2756         bufHasFilename = curbuf->b_ffname != NULL;
2757     }
2759     BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2761     return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2764 - (void)redrawScreen
2766     // Force screen redraw (does it have to be this complicated?).
2767     redraw_all_later(CLEAR);
2768     update_screen(NOT_VALID);
2769     setcursor();
2770     out_flush();
2771     gui_update_cursor(FALSE, FALSE);
2773     // HACK! The cursor is not put back at the command line by the above
2774     // "redraw commands".  The following test seems to do the trick though.
2775     if (State & CMDLINE)
2776         redrawcmdline();
2779 - (void)handleFindReplace:(NSDictionary *)args
2781     if (!args) return;
2783     NSString *findString = [args objectForKey:@"find"];
2784     if (!findString) return;
2786     char_u *find = [findString vimStringSave];
2787     char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2788     int flags = [[args objectForKey:@"flags"] intValue];
2790     // NOTE: The flag 0x100 is used to indicate a backward search.
2791     gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2793     vim_free(find);
2794     vim_free(replace);
2798 - (void)handleMarkedText:(NSData *)data
2800     const void *bytes = [data bytes];
2801     unsigned pos = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2802     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2803     char *chars = (char *)bytes;
2805     ASLogDebug(@"pos=%d len=%d chars=%s", pos, len, chars);
2807     if (len == 0) {
2808         im_preedit_end_macvim();
2809     } else {
2810         if (!preedit_get_status())
2811             im_preedit_start_macvim();
2813         im_preedit_changed_macvim(chars, pos);
2814     }
2817 @end // MMBackend (Private)
2822 @implementation MMBackend (ClientServer)
2824 - (NSString *)connectionNameFromServerName:(NSString *)name
2826     NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2828     return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2829         lowercaseString];
2832 - (NSConnection *)connectionForServerName:(NSString *)name
2834     // TODO: Try 'name%d' if 'name' fails.
2835     NSString *connName = [self connectionNameFromServerName:name];
2836     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2838     if (!svrConn) {
2839         svrConn = [NSConnection connectionWithRegisteredName:connName
2840                                                            host:nil];
2841         // Try alternate server...
2842         if (!svrConn && alternateServerName) {
2843             ASLogInfo(@"  trying to connect to alternate server: %@",
2844                       alternateServerName);
2845             connName = [self connectionNameFromServerName:alternateServerName];
2846             svrConn = [NSConnection connectionWithRegisteredName:connName
2847                                                             host:nil];
2848         }
2850         // Try looking for alternate servers...
2851         if (!svrConn) {
2852             ASLogInfo(@"  looking for alternate servers...");
2853             NSString *alt = [self alternateServerNameForName:name];
2854             if (alt != alternateServerName) {
2855                 ASLogInfo(@"  found alternate server: %@", alt);
2856                 [alternateServerName release];
2857                 alternateServerName = [alt copy];
2858             }
2859         }
2861         // Try alternate server again...
2862         if (!svrConn && alternateServerName) {
2863             ASLogInfo(@"  trying to connect to alternate server: %@",
2864                       alternateServerName);
2865             connName = [self connectionNameFromServerName:alternateServerName];
2866             svrConn = [NSConnection connectionWithRegisteredName:connName
2867                                                             host:nil];
2868         }
2870         if (svrConn) {
2871             [connectionNameDict setObject:svrConn forKey:connName];
2873             ASLogDebug(@"Adding %@ as connection observer for %@",
2874                        self, svrConn);
2875             [[NSNotificationCenter defaultCenter] addObserver:self
2876                     selector:@selector(serverConnectionDidDie:)
2877                         name:NSConnectionDidDieNotification object:svrConn];
2878         }
2879     }
2881     return svrConn;
2884 - (NSConnection *)connectionForServerPort:(int)port
2886     NSConnection *conn;
2887     NSEnumerator *e = [connectionNameDict objectEnumerator];
2889     while ((conn = [e nextObject])) {
2890         // HACK! Assume connection uses mach ports.
2891         if (port == [(NSMachPort*)[conn sendPort] machPort])
2892             return conn;
2893     }
2895     return nil;
2898 - (void)serverConnectionDidDie:(NSNotification *)notification
2900     ASLogDebug(@"notification=%@", notification);
2902     NSConnection *svrConn = [notification object];
2904     ASLogDebug(@"Removing %@ as connection observer from %@", self, svrConn);
2905     [[NSNotificationCenter defaultCenter]
2906             removeObserver:self
2907                       name:NSConnectionDidDieNotification
2908                     object:svrConn];
2910     [connectionNameDict removeObjectsForKeys:
2911         [connectionNameDict allKeysForObject:svrConn]];
2913     // HACK! Assume connection uses mach ports.
2914     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2915     NSNumber *key = [NSNumber numberWithInt:port];
2917     [clientProxyDict removeObjectForKey:key];
2918     [serverReplyDict removeObjectForKey:key];
2921 - (void)addClient:(NSDistantObject *)client
2923     NSConnection *conn = [client connectionForProxy];
2924     // HACK! Assume connection uses mach ports.
2925     int port = [(NSMachPort*)[conn sendPort] machPort];
2926     NSNumber *key = [NSNumber numberWithInt:port];
2928     if (![clientProxyDict objectForKey:key]) {
2929         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2930         [clientProxyDict setObject:client forKey:key];
2931     }
2933     // NOTE: 'clientWindow' is a global variable which is used by <client>
2934     clientWindow = port;
2937 - (NSString *)alternateServerNameForName:(NSString *)name
2939     if (!(name && [name length] > 0))
2940         return nil;
2942     // Only look for alternates if 'name' doesn't end in a digit.
2943     unichar lastChar = [name characterAtIndex:[name length]-1];
2944     if (lastChar >= '0' && lastChar <= '9')
2945         return nil;
2947     // Look for alternates among all current servers.
2948     NSArray *list = [self serverList];
2949     if (!(list && [list count] > 0))
2950         return nil;
2952     // Filter out servers starting with 'name' and ending with a number. The
2953     // (?i) pattern ensures that the match is case insensitive.
2954     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2955     NSPredicate *pred = [NSPredicate predicateWithFormat:
2956             @"SELF MATCHES %@", pat];
2957     list = [list filteredArrayUsingPredicate:pred];
2958     if ([list count] > 0) {
2959         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2960         return [list objectAtIndex:0];
2961     }
2963     return nil;
2966 @end // MMBackend (ClientServer)
2971 @implementation NSString (MMServerNameCompare)
2972 - (NSComparisonResult)serverNameCompare:(NSString *)string
2974     return [self compare:string
2975                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2977 @end
2982 static int eventModifierFlagsToVimModMask(int modifierFlags)
2984     int modMask = 0;
2986     if (modifierFlags & NSShiftKeyMask)
2987         modMask |= MOD_MASK_SHIFT;
2988     if (modifierFlags & NSControlKeyMask)
2989         modMask |= MOD_MASK_CTRL;
2990     if (modifierFlags & NSAlternateKeyMask)
2991         modMask |= MOD_MASK_ALT;
2992     if (modifierFlags & NSCommandKeyMask)
2993         modMask |= MOD_MASK_CMD;
2995     return modMask;
2998 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
3000     int modMask = 0;
3002     if (modifierFlags & NSShiftKeyMask)
3003         modMask |= MOUSE_SHIFT;
3004     if (modifierFlags & NSControlKeyMask)
3005         modMask |= MOUSE_CTRL;
3006     if (modifierFlags & NSAlternateKeyMask)
3007         modMask |= MOUSE_ALT;
3009     return modMask;
3012 static int eventButtonNumberToVimMouseButton(int buttonNumber)
3014     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
3016     return (buttonNumber >= 0 && buttonNumber < 3)
3017             ? mouseButton[buttonNumber] : -1;
3022 // This function is modeled after the VimToPython function found in if_python.c
3023 // NB This does a deep copy by value, it does not lookup references like the
3024 // VimToPython function does.  This is because I didn't want to deal with the
3025 // retain cycles that this would create, and we can cover 99% of the use cases
3026 // by ignoring it.  If we ever switch to using GC in MacVim then this
3027 // functionality can be implemented easily.
3028 static id vimToCocoa(typval_T * tv, int depth)
3030     id result = nil;
3031     id newObj = nil;
3034     // Avoid infinite recursion
3035     if (depth > 100) {
3036         return nil;
3037     }
3039     if (tv->v_type == VAR_STRING) {
3040         char_u * val = tv->vval.v_string;
3041         // val can be NULL if the string is empty
3042         if (!val) {
3043             result = [NSString string];
3044         } else {
3045 #ifdef FEAT_MBYTE
3046             val = CONVERT_TO_UTF8(val);
3047 #endif
3048             result = [NSString stringWithUTF8String:(char*)val];
3049 #ifdef FEAT_MBYTE
3050             CONVERT_TO_UTF8_FREE(val);
3051 #endif
3052         }
3053     } else if (tv->v_type == VAR_NUMBER) {
3054         // looks like sizeof(varnumber_T) is always <= sizeof(long)
3055         result = [NSNumber numberWithLong:(long)tv->vval.v_number];
3056     } else if (tv->v_type == VAR_LIST) {
3057         list_T * list = tv->vval.v_list;
3058         listitem_T * curr;
3060         NSMutableArray * arr = result = [NSMutableArray array];
3062         if (list != NULL) {
3063             for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
3064                 newObj = vimToCocoa(&curr->li_tv, depth + 1);
3065                 [arr addObject:newObj];
3066             }
3067         }
3068     } else if (tv->v_type == VAR_DICT) {
3069         NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
3071         if (tv->vval.v_dict != NULL) {
3072             hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
3073             int todo = ht->ht_used;
3074             hashitem_T * hi;
3075             dictitem_T * di;
3077             for (hi = ht->ht_array; todo > 0; ++hi) {
3078                 if (!HASHITEM_EMPTY(hi)) {
3079                     --todo;
3081                     di = dict_lookup(hi);
3082                     newObj = vimToCocoa(&di->di_tv, depth + 1);
3084                     char_u * keyval = hi->hi_key;
3085 #ifdef FEAT_MBYTE
3086                     keyval = CONVERT_TO_UTF8(keyval);
3087 #endif
3088                     NSString * key = [NSString stringWithUTF8String:(char*)keyval];
3089 #ifdef FEAT_MBYTE
3090                     CONVERT_TO_UTF8_FREE(keyval);
3091 #endif
3092                     [dict setObject:newObj forKey:key];
3093                 }
3094             }
3095         }
3096     } else { // only func refs should fall into this category?
3097         result = nil;
3098     }
3100     return result;
3104 // This function is modeled after eval_client_expr_to_string found in main.c
3105 // Returns nil if there was an error evaluating the expression, and writes a
3106 // message to errorStr.
3107 // TODO Get the error that occurred while evaluating the expression in vim
3108 // somehow.
3109 static id evalExprCocoa(NSString * expr, NSString ** errstr)
3112     char_u *s = (char_u*)[expr UTF8String];
3114 #ifdef FEAT_MBYTE
3115     s = CONVERT_FROM_UTF8(s);
3116 #endif
3118     int save_dbl = debug_break_level;
3119     int save_ro = redir_off;
3121     debug_break_level = -1;
3122     redir_off = 0;
3123     ++emsg_skip;
3125     typval_T * tvres = eval_expr(s, NULL);
3127     debug_break_level = save_dbl;
3128     redir_off = save_ro;
3129     --emsg_skip;
3131     setcursor();
3132     out_flush();
3134 #ifdef FEAT_MBYTE
3135     CONVERT_FROM_UTF8_FREE(s);
3136 #endif
3138 #ifdef FEAT_GUI
3139     if (gui.in_use)
3140         gui_update_cursor(FALSE, FALSE);
3141 #endif
3143     if (tvres == NULL) {
3144         free_tv(tvres);
3145         *errstr = @"Expression evaluation failed.";
3146     }
3148     id res = vimToCocoa(tvres, 1);
3150     free_tv(tvres);
3152     if (res == nil) {
3153         *errstr = @"Conversion to cocoa values failed.";
3154     }
3156     return res;
3161 @implementation NSString (VimStrings)
3163 + (id)stringWithVimString:(char_u *)s
3165     // This method ensures a non-nil string is returned.  If 's' cannot be
3166     // converted to a utf-8 string it is assumed to be latin-1.  If conversion
3167     // still fails an empty NSString is returned.
3168     NSString *string = nil;
3169     if (s) {
3170 #ifdef FEAT_MBYTE
3171         s = CONVERT_TO_UTF8(s);
3172 #endif
3173         string = [NSString stringWithUTF8String:(char*)s];
3174         if (!string) {
3175             // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
3176             // latin-1?
3177             string = [NSString stringWithCString:(char*)s
3178                                         encoding:NSISOLatin1StringEncoding];
3179         }
3180 #ifdef FEAT_MBYTE
3181         CONVERT_TO_UTF8_FREE(s);
3182 #endif
3183     }
3185     return string != nil ? string : [NSString string];
3188 - (char_u *)vimStringSave
3190     char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3192 #ifdef FEAT_MBYTE
3193     s = CONVERT_FROM_UTF8(s);
3194 #endif
3195     ret = vim_strsave(s);
3196 #ifdef FEAT_MBYTE
3197     CONVERT_FROM_UTF8_FREE(s);
3198 #endif
3200     return ret;
3203 @end // NSString (VimStrings)